summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/linux_builds.yml2
-rw-r--r--doc/classes/GPUParticles2D.xml7
-rw-r--r--doc/classes/GPUParticles3D.xml7
-rw-r--r--doc/classes/ProjectSettings.xml3
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp2
-rw-r--r--editor/plugin_config_dialog.cpp7
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.cpp20
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.h3
-rw-r--r--editor/plugins/cpu_particles_3d_editor_plugin.cpp17
-rw-r--r--editor/plugins/cpu_particles_3d_editor_plugin.h4
-rw-r--r--modules/dds/image_loader_dds.cpp1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp34
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp25
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h5
-rw-r--r--modules/gdscript/gdscript_codegen.h4
-rw-r--r--modules/gdscript/gdscript_compiler.cpp4
-rw-r--r--modules/gdscript/gdscript_parser.cpp13
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/gdscript_warning.cpp9
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd34
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out25
-rw-r--r--scene/2d/gpu_particles_2d.cpp96
-rw-r--r--scene/2d/gpu_particles_2d.h2
-rw-r--r--scene/3d/gpu_particles_3d.cpp93
-rw-r--r--scene/3d/gpu_particles_3d.h2
-rw-r--r--servers/rendering/renderer_rd/cluster_builder_rd.cpp2
36 files changed, 434 insertions, 26 deletions
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml
index c812996fd4..01f2404866 100644
--- a/.github/workflows/linux_builds.yml
+++ b/.github/workflows/linux_builds.yml
@@ -130,7 +130,7 @@ jobs:
- name: Generate C# glue
if: ${{ matrix.build-mono }}
run: |
- ${{ matrix.bin }} --headless --generate-mono-glue ./modules/mono/glue || true
+ ${{ matrix.bin }} --headless --generate-mono-glue ./modules/mono/glue
- name: Build .NET solutions
if: ${{ matrix.build-mono }}
diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml
index d5a4c146e0..ee55288783 100644
--- a/doc/classes/GPUParticles2D.xml
+++ b/doc/classes/GPUParticles2D.xml
@@ -21,6 +21,13 @@
[b]Note:[/b] When using threaded rendering this method synchronizes the rendering thread. Calling it often may have a negative impact on performance.
</description>
</method>
+ <method name="convert_from_particles">
+ <return type="void" />
+ <param index="0" name="particles" type="Node" />
+ <description>
+ Sets this node's properties to match a given [CPUParticles2D] node.
+ </description>
+ </method>
<method name="emit_particle">
<return type="void" />
<param index="0" name="xform" type="Transform2D" />
diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml
index 3310113b61..9ba64feebc 100644
--- a/doc/classes/GPUParticles3D.xml
+++ b/doc/classes/GPUParticles3D.xml
@@ -19,6 +19,13 @@
Returns the axis-aligned bounding box that contains all the particles that are active in the current frame.
</description>
</method>
+ <method name="convert_from_particles">
+ <return type="void" />
+ <param index="0" name="particles" type="Node" />
+ <description>
+ Sets this node's properties to match a given [CPUParticles3D] node.
+ </description>
+ </method>
<method name="emit_particle">
<return type="void" />
<param index="0" name="xform" type="Transform3D" />
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index f426030f98..0e35bc7031 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -492,6 +492,9 @@
<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await.
</member>
+ <member name="debug/gdscript/warnings/redundant_for_variable_type" type="int" setter="" getter="" default="1">
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a [code]for[/code] variable type specifier is a supertype of the inferred type.
+ </member>
<member name="debug/gdscript/warnings/redundant_static_unload" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@static_unload[/code] annotation is used in a script without any static variables.
</member>
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index 59b1d176c6..b955fc2fca 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -2622,7 +2622,7 @@ Error RenderingDeviceVulkan::_texture_update(RID p_texture, uint32_t p_layer, co
vkCmdCopyBufferToImage(command_buffer, staging_buffer_blocks[staging_buffer_current].buffer, texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_image_copy);
- staging_buffer_blocks.write[staging_buffer_current].fill_amount += alloc_size;
+ staging_buffer_blocks.write[staging_buffer_current].fill_amount = alloc_offset + alloc_size;
}
}
}
diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp
index 7d7b156bd0..c5a667cd5f 100644
--- a/editor/plugin_config_dialog.cpp
+++ b/editor/plugin_config_dialog.cpp
@@ -204,6 +204,7 @@ PluginConfigDialog::PluginConfigDialog() {
name_edit = memnew(LineEdit);
name_edit->set_placeholder("MyPlugin");
+ name_edit->set_tooltip_text(TTR("Required. This name will be displayed in the list of plugins."));
name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_child(name_edit);
@@ -215,6 +216,7 @@ PluginConfigDialog::PluginConfigDialog() {
subfolder_edit = memnew(LineEdit);
subfolder_edit->set_placeholder("\"my_plugin\" -> res://addons/my_plugin");
+ subfolder_edit->set_tooltip_text(TTR("Optional. The folder name should generally use `snake_case` naming (avoid spaces and special characters).\nIf left empty, the folder will be named after the plugin name converted to `snake_case`."));
subfolder_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_child(subfolder_edit);
@@ -225,6 +227,7 @@ PluginConfigDialog::PluginConfigDialog() {
grid->add_child(desc_lb);
desc_edit = memnew(TextEdit);
+ desc_edit->set_tooltip_text(TTR("Optional. This description should be kept relatively short (up to 5 lines).\nIt will display when hovering the plugin in the list of plugins."));
desc_edit->set_custom_minimum_size(Size2(400, 80) * EDSCALE);
desc_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY);
desc_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -239,6 +242,7 @@ PluginConfigDialog::PluginConfigDialog() {
author_edit = memnew(LineEdit);
author_edit->set_placeholder("Godette");
+ author_edit->set_tooltip_text(TTR("Optional. The author's username, full name, or organization name."));
author_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_child(author_edit);
@@ -249,6 +253,7 @@ PluginConfigDialog::PluginConfigDialog() {
grid->add_child(version_lb);
version_edit = memnew(LineEdit);
+ version_edit->set_tooltip_text(TTR("Optional. A human-readable version identifier used for informational purposes only."));
version_edit->set_placeholder("1.0");
version_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_child(version_edit);
@@ -260,6 +265,7 @@ PluginConfigDialog::PluginConfigDialog() {
grid->add_child(script_option_lb);
script_option_edit = memnew(OptionButton);
+ script_option_edit->set_tooltip_text(TTR("Required. The scripting language to use for the script.\nNote that a plugin may use several languages at once by adding more scripts to the plugin."));
int default_lang = 0;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptLanguage *lang = ScriptServer::get_language(i);
@@ -278,6 +284,7 @@ PluginConfigDialog::PluginConfigDialog() {
grid->add_child(script_lb);
script_edit = memnew(LineEdit);
+ script_edit->set_tooltip_text(TTR("Optional. The path to the script (relative to the add-on folder). If left empty, will default to \"plugin.gd\"."));
script_edit->set_placeholder("\"plugin.gd\" -> res://addons/my_plugin/plugin.gd");
script_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_child(script_edit);
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
index 7be0d6c172..3ac9fee03f 100644
--- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp
+++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
@@ -33,8 +33,11 @@
#include "canvas_item_editor_plugin.h"
#include "core/io/image_loader.h"
#include "editor/editor_node.h"
+#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/scene_tree_dock.h"
#include "scene/2d/cpu_particles_2d.h"
+#include "scene/2d/gpu_particles_2d.h"
#include "scene/gui/check_box.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/option_button.h"
@@ -67,14 +70,26 @@ void CPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
switch (p_idx) {
case MENU_LOAD_EMISSION_MASK: {
file->popup_file_dialog();
-
} break;
case MENU_CLEAR_EMISSION_MASK: {
emission_mask->popup_centered();
} break;
case MENU_RESTART: {
particles->restart();
- }
+ } break;
+ case MENU_CONVERT_TO_GPU_PARTICLES: {
+ GPUParticles2D *gpu_particles = memnew(GPUParticles2D);
+ gpu_particles->convert_from_particles(particles);
+ gpu_particles->set_name(particles->get_name());
+ gpu_particles->set_transform(particles->get_transform());
+ gpu_particles->set_visible(particles->is_visible());
+ gpu_particles->set_process_mode(particles->get_process_mode());
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Convert to GPUParticles3D"));
+ SceneTreeDock::get_singleton()->replace_node(particles, gpu_particles);
+ ur->commit_action(false);
+ } break;
}
}
@@ -257,6 +272,7 @@ CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
menu = memnew(MenuButton);
menu->get_popup()->add_item(TTR("Restart"), MENU_RESTART);
menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
+ menu->get_popup()->add_item(TTR("Convert to GPUParticles2D"), MENU_CONVERT_TO_GPU_PARTICLES);
menu->set_text(TTR("CPUParticles2D"));
menu->set_switch_on_hover(true);
toolbar->add_child(menu);
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.h b/editor/plugins/cpu_particles_2d_editor_plugin.h
index ff8e171208..5077827ce8 100644
--- a/editor/plugins/cpu_particles_2d_editor_plugin.h
+++ b/editor/plugins/cpu_particles_2d_editor_plugin.h
@@ -49,7 +49,8 @@ class CPUParticles2DEditorPlugin : public EditorPlugin {
enum {
MENU_LOAD_EMISSION_MASK,
MENU_CLEAR_EMISSION_MASK,
- MENU_RESTART
+ MENU_RESTART,
+ MENU_CONVERT_TO_GPU_PARTICLES,
};
enum EmissionMode {
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
index 6edfc2ef2e..1f1bc0e561 100644
--- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp
+++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
@@ -31,8 +31,10 @@
#include "cpu_particles_3d_editor_plugin.h"
#include "editor/editor_node.h"
+#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/plugins/node_3d_editor_plugin.h"
+#include "editor/scene_tree_dock.h"
#include "scene/gui/menu_button.h"
void CPUParticles3DEditor::_node_removed(Node *p_node) {
@@ -59,6 +61,20 @@ void CPUParticles3DEditor::_menu_option(int p_option) {
case MENU_OPTION_RESTART: {
node->restart();
+ } break;
+
+ case MENU_OPTION_CONVERT_TO_GPU_PARTICLES: {
+ GPUParticles3D *gpu_particles = memnew(GPUParticles3D);
+ gpu_particles->convert_from_particles(node);
+ gpu_particles->set_name(node->get_name());
+ gpu_particles->set_transform(node->get_transform());
+ gpu_particles->set_visible(node->is_visible());
+ gpu_particles->set_process_mode(node->get_process_mode());
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Convert to GPUParticles3D"));
+ SceneTreeDock::get_singleton()->replace_node(node, gpu_particles);
+ ur->commit_action(false);
} break;
}
@@ -102,6 +118,7 @@ CPUParticles3DEditor::CPUParticles3DEditor() {
options->set_text(TTR("CPUParticles3D"));
options->get_popup()->add_item(TTR("Restart"), MENU_OPTION_RESTART);
options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
+ options->get_popup()->add_item(TTR("Convert to GPUParticles3D"), MENU_OPTION_CONVERT_TO_GPU_PARTICLES);
options->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles3DEditor::_menu_option));
}
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h
index 894e0dfb31..6de23fc2b8 100644
--- a/editor/plugins/cpu_particles_3d_editor_plugin.h
+++ b/editor/plugins/cpu_particles_3d_editor_plugin.h
@@ -40,8 +40,8 @@ class CPUParticles3DEditor : public GPUParticles3DEditorBase {
enum Menu {
MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
MENU_OPTION_CLEAR_EMISSION_VOLUME,
- MENU_OPTION_RESTART
-
+ MENU_OPTION_RESTART,
+ MENU_OPTION_CONVERT_TO_GPU_PARTICLES,
};
CPUParticles3D *node = nullptr;
diff --git a/modules/dds/image_loader_dds.cpp b/modules/dds/image_loader_dds.cpp
index 42c8120595..13ca1e6bff 100644
--- a/modules/dds/image_loader_dds.cpp
+++ b/modules/dds/image_loader_dds.cpp
@@ -66,7 +66,6 @@ enum DDSFormat {
DDS_BGR5A1,
DDS_BGR565,
DDS_BGR10A2,
- DDS_INDEXED,
DDS_LUMINANCE,
DDS_LUMINANCE_ALPHA,
DDS_MAX
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 9f9accf507..214b484b12 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2001,13 +2001,16 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
}
GDScriptParser::DataType variable_type;
+ String list_visible_type = "<unresolved type>";
if (list_resolved) {
variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
variable_type.kind = GDScriptParser::DataType::BUILTIN;
variable_type.builtin_type = Variant::INT;
+ list_visible_type = "Array[int]"; // NOTE: `range()` has `Array` return type.
} else if (p_for->list) {
resolve_node(p_for->list, false);
GDScriptParser::DataType list_type = p_for->list->get_datatype();
+ list_visible_type = list_type.to_string();
if (!list_type.is_hard_type()) {
mark_node_unsafe(p_for->list);
}
@@ -2051,8 +2054,37 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
push_error(vformat(R"(Unable to iterate on value of type "%s".)", list_type.to_string()), p_for->list);
}
}
+
if (p_for->variable) {
- p_for->variable->set_datatype(variable_type);
+ if (p_for->datatype_specifier) {
+ GDScriptParser::DataType specified_type = type_from_metatype(resolve_datatype(p_for->datatype_specifier));
+ if (!specified_type.is_variant()) {
+ if (variable_type.is_variant() || !variable_type.is_hard_type()) {
+ mark_node_unsafe(p_for->variable);
+ p_for->use_conversion_assign = true;
+ } else if (!is_type_compatible(specified_type, variable_type, true, p_for->variable)) {
+ if (is_type_compatible(variable_type, specified_type)) {
+ mark_node_unsafe(p_for->variable);
+ p_for->use_conversion_assign = true;
+ } else {
+ push_error(vformat(R"(Unable to iterate on value of type "%s" with variable of type "%s".)", list_visible_type, specified_type.to_string()), p_for->datatype_specifier);
+ }
+ } else if (!is_type_compatible(specified_type, variable_type)) {
+ p_for->use_conversion_assign = true;
+#ifdef DEBUG_ENABLED
+ } else {
+ parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string());
+#endif
+ }
+#ifdef DEBUG_ENABLED
+ } else {
+ parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string());
+#endif
+ }
+ p_for->variable->set_datatype(specified_type);
+ } else {
+ p_for->variable->set_datatype(variable_type);
+ }
}
resolve_suite(p_for->loop);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 6057a00f9b..af7862efc5 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -1494,19 +1494,16 @@ void GDScriptByteCodeGenerator::start_for(const GDScriptDataType &p_iterator_typ
for_container_variables.push_back(container);
}
-void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_variable, const Address &p_list) {
+void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_list) {
const Address &container = for_container_variables.back()->get();
// Assign container.
append_opcode(GDScriptFunction::OPCODE_ASSIGN);
append(container);
append(p_list);
-
- for_iterator_variables.push_back(p_variable);
}
-void GDScriptByteCodeGenerator::write_for() {
- const Address &iterator = for_iterator_variables.back()->get();
+void GDScriptByteCodeGenerator::write_for(const Address &p_variable, bool p_use_conversion) {
const Address &counter = for_counter_variables.back()->get();
const Address &container = for_container_variables.back()->get();
@@ -1599,11 +1596,16 @@ void GDScriptByteCodeGenerator::write_for() {
}
}
+ Address temp;
+ if (p_use_conversion) {
+ temp = Address(Address::LOCAL_VARIABLE, add_local("@iterator_temp", GDScriptDataType()));
+ }
+
// Begin loop.
append_opcode(begin_opcode);
append(counter);
append(container);
- append(iterator);
+ append(p_use_conversion ? temp : p_variable);
for_jmp_addrs.push_back(opcodes.size());
append(0); // End of loop address, will be patched.
append_opcode(GDScriptFunction::OPCODE_JUMP);
@@ -1615,9 +1617,17 @@ void GDScriptByteCodeGenerator::write_for() {
append_opcode(iterate_opcode);
append(counter);
append(container);
- append(iterator);
+ append(p_use_conversion ? temp : p_variable);
for_jmp_addrs.push_back(opcodes.size());
append(0); // Jump destination, will be patched.
+
+ if (p_use_conversion) {
+ write_assign_with_conversion(p_variable, temp);
+ const GDScriptDataType &type = p_variable.type;
+ if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
+ write_assign_false(temp); // Can contain RefCounted, so clear it.
+ }
+ }
}
void GDScriptByteCodeGenerator::write_endfor() {
@@ -1639,7 +1649,6 @@ void GDScriptByteCodeGenerator::write_endfor() {
current_breaks_to_patch.pop_back();
// Pop state.
- for_iterator_variables.pop_back();
for_counter_variables.pop_back();
for_container_variables.pop_back();
}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index bbcd252b13..671dea5d6d 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -143,7 +143,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
// Lists since these can be nested.
List<int> if_jmp_addrs;
List<int> for_jmp_addrs;
- List<Address> for_iterator_variables;
List<Address> for_counter_variables;
List<Address> for_container_variables;
List<int> while_jmp_addrs;
@@ -536,8 +535,8 @@ public:
virtual void write_jump_if_shared(const Address &p_value) override;
virtual void write_end_jump_if_shared() override;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override;
- virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override;
- virtual void write_for() override;
+ virtual void write_for_assignment(const Address &p_list) override;
+ virtual void write_for(const Address &p_variable, bool p_use_conversion) override;
virtual void write_endfor() override;
virtual void start_while_condition() override;
virtual void write_while(const Address &p_condition) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index 9810f5395a..cf17353dec 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -145,8 +145,8 @@ public:
virtual void write_jump_if_shared(const Address &p_value) = 0;
virtual void write_end_jump_if_shared() = 0;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0;
- virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0;
- virtual void write_for() = 0;
+ virtual void write_for_assignment(const Address &p_list) = 0;
+ virtual void write_for(const Address &p_variable, bool p_use_conversion) = 0;
virtual void write_endfor() = 0;
virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation.
virtual void write_while(const Address &p_condition) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 3366fa2eec..f964db231a 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1953,13 +1953,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
return err;
}
- gen->write_for_assignment(iterator, list);
+ gen->write_for_assignment(list);
if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
- gen->write_for();
+ gen->write_for(iterator, for_n->use_conversion_assign);
err = _parse_block(codegen, for_n->loop);
if (err) {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index b76ceea11f..1dde67d2d1 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -1850,7 +1850,18 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
n_for->variable = parse_identifier();
}
- consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)");
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ n_for->datatype_specifier = parse_type();
+ if (n_for->datatype_specifier == nullptr) {
+ push_error(R"(Expected type specifier after ":".)");
+ }
+ }
+
+ if (n_for->datatype_specifier == nullptr) {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" or ":" after "for" variable name.)");
+ } else {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable type specifier.)");
+ }
n_for->list = parse_expression(false);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 71660d8f60..652faaebc3 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -814,6 +814,8 @@ public:
struct ForNode : public Node {
IdentifierNode *variable = nullptr;
+ TypeNode *datatype_specifier = nullptr;
+ bool use_conversion_assign = false;
ExpressionNode *list = nullptr;
SuiteNode *loop = nullptr;
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 24aa793c47..4fec445995 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -113,6 +113,14 @@ String GDScriptWarning::get_message() const {
return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
case REDUNDANT_AWAIT:
return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
+ case REDUNDANT_FOR_VARIABLE_TYPE:
+ CHECK_SYMBOLS(3);
+ if (symbols[1] == symbols[2]) {
+ return vformat(R"(The for loop iterator "%s" already has inferred type "%s", the specified type is redundant.)", symbols[0], symbols[1]);
+ } else {
+ return vformat(R"(The for loop iterator "%s" has inferred type "%s" but its supertype "%s" is specified.)", symbols[0], symbols[1], symbols[2]);
+ }
+ break;
case ASSERT_ALWAYS_TRUE:
return "Assert statement is redundant because the expression is always true.";
case ASSERT_ALWAYS_FALSE:
@@ -209,6 +217,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"STATIC_CALLED_ON_INSTANCE",
"REDUNDANT_STATIC_UNLOAD",
"REDUNDANT_AWAIT",
+ "REDUNDANT_FOR_VARIABLE_TYPE",
"ASSERT_ALWAYS_TRUE",
"ASSERT_ALWAYS_FALSE",
"INTEGER_DIVISION",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 8444d46a88..73e12eb20e 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -73,6 +73,7 @@ public:
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
+ REDUNDANT_FOR_VARIABLE_TYPE, // The for variable type specifier is a supertype of the inferred type.
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded.
@@ -120,6 +121,7 @@ public:
WARN, // STATIC_CALLED_ON_INSTANCE
WARN, // REDUNDANT_STATIC_UNLOAD
WARN, // REDUNDANT_AWAIT
+ WARN, // REDUNDANT_FOR_VARIABLE_TYPE
WARN, // ASSERT_ALWAYS_TRUE
WARN, // ASSERT_ALWAYS_FALSE
WARN, // INTEGER_DIVISION
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index 062be0fe20..8c44483288 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -73,6 +73,7 @@ void GDScriptLanguageServer::_notification(int p_what) {
}
void GDScriptLanguageServer::thread_main(void *p_userdata) {
+ set_current_thread_safe_for_nodes(true);
GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
while (self->thread_running) {
// Poll 20 times per second
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd
new file mode 100644
index 0000000000..7e3b6e3c39
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array[Resource] = []
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out
new file mode 100644
index 0000000000..8f8a368f9a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Unable to iterate on value of type "Array[Resource]" with variable of type "Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd
new file mode 100644
index 0000000000..1b32491e48
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array[Node] = []
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out
new file mode 100644
index 0000000000..3b3fbd9bd1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> REDUNDANT_FOR_VARIABLE_TYPE
+>> The for loop iterator "node" already has inferred type "Node", the specified type is redundant.
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd
new file mode 100644
index 0000000000..fcbc13c53d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array[Node2D] = []
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out
new file mode 100644
index 0000000000..36d4a161d3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> REDUNDANT_FOR_VARIABLE_TYPE
+>> The for loop iterator "node" has inferred type "Node2D" but its supertype "Node" is specified.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd
new file mode 100644
index 0000000000..cdc278ecf5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array = [Resource.new()]
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out
new file mode 100644
index 0000000000..b7ef07afb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/for_loop_iterator_type_not_match_specified.gd
+>> 3
+>> Trying to assign value of type 'Resource' to a variable of type 'Node'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
new file mode 100644
index 0000000000..58b4df5a79
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
@@ -0,0 +1,34 @@
+func test():
+ print("Test range.")
+ for e: float in range(2, 5):
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test int.")
+ for e: float in 3:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test untyped int array.")
+ var a1 := [10, 20, 30]
+ for e: float in a1:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test typed int array.")
+ var a2: Array[int] = [10, 20, 30]
+ for e: float in a2:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test String-keys dictionary.")
+ var d1 := {a = 1, b = 2, c = 3}
+ for k: StringName in d1:
+ var key := k
+ prints(var_to_str(k), var_to_str(key))
+
+ print("Test RefCounted-keys dictionary.")
+ var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3}
+ for k: RefCounted in d2:
+ var key := k
+ prints(k.get_class(), key.get_class())
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
new file mode 100644
index 0000000000..f67f7d89d5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
@@ -0,0 +1,25 @@
+GDTEST_OK
+Test range.
+2.0 2.0
+3.0 3.0
+4.0 4.0
+Test int.
+0.0 0.0
+1.0 1.0
+2.0 2.0
+Test untyped int array.
+10.0 10.0
+20.0 20.0
+30.0 30.0
+Test typed int array.
+10.0 10.0
+20.0 20.0
+30.0 30.0
+Test String-keys dictionary.
+&"a" &"a"
+&"b" &"b"
+&"c" &"c"
+Test RefCounted-keys dictionary.
+RefCounted RefCounted
+Resource Resource
+ConfigFile ConfigFile
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 8c5782dc41..70c72dab07 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -30,7 +30,10 @@
#include "gpu_particles_2d.h"
+#include "scene/2d/cpu_particles_2d.h"
#include "scene/resources/atlas_texture.h"
+#include "scene/resources/curve_texture.h"
+#include "scene/resources/gradient_texture.h"
#include "scene/resources/particle_process_material.h"
#include "scene/scene_string_names.h"
@@ -435,6 +438,97 @@ void GPUParticles2D::restart() {
}
}
+void GPUParticles2D::convert_from_particles(Node *p_particles) {
+ CPUParticles2D *cpu_particles = Object::cast_to<CPUParticles2D>(p_particles);
+ ERR_FAIL_NULL_MSG(cpu_particles, "Only CPUParticles2D nodes can be converted to GPUParticles2D.");
+
+ set_emitting(cpu_particles->is_emitting());
+ set_amount(cpu_particles->get_amount());
+ set_lifetime(cpu_particles->get_lifetime());
+ set_one_shot(cpu_particles->get_one_shot());
+ set_pre_process_time(cpu_particles->get_pre_process_time());
+ set_explosiveness_ratio(cpu_particles->get_explosiveness_ratio());
+ set_randomness_ratio(cpu_particles->get_randomness_ratio());
+ set_use_local_coordinates(cpu_particles->get_use_local_coordinates());
+ set_fixed_fps(cpu_particles->get_fixed_fps());
+ set_fractional_delta(cpu_particles->get_fractional_delta());
+ set_speed_scale(cpu_particles->get_speed_scale());
+ set_draw_order(DrawOrder(cpu_particles->get_draw_order()));
+ set_texture(cpu_particles->get_texture());
+
+ Ref<Material> mat = cpu_particles->get_material();
+ if (mat.is_valid()) {
+ set_material(mat);
+ }
+
+ Ref<ParticleProcessMaterial> proc_mat = memnew(ParticleProcessMaterial);
+ set_process_material(proc_mat);
+ Vector2 dir = cpu_particles->get_direction();
+ proc_mat->set_direction(Vector3(dir.x, dir.y, 0));
+ proc_mat->set_spread(cpu_particles->get_spread());
+ proc_mat->set_color(cpu_particles->get_color());
+
+ Ref<Gradient> color_grad = cpu_particles->get_color_ramp();
+ if (color_grad.is_valid()) {
+ Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
+ tex->set_gradient(color_grad);
+ proc_mat->set_color_ramp(tex);
+ }
+
+ Ref<Gradient> color_init_grad = cpu_particles->get_color_initial_ramp();
+ if (color_init_grad.is_valid()) {
+ Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
+ tex->set_gradient(color_init_grad);
+ proc_mat->set_color_initial_ramp(tex);
+ }
+
+ proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, cpu_particles->get_particle_flag(CPUParticles2D::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY));
+
+ proc_mat->set_emission_shape(ParticleProcessMaterial::EmissionShape(cpu_particles->get_emission_shape()));
+ proc_mat->set_emission_sphere_radius(cpu_particles->get_emission_sphere_radius());
+
+ Vector2 rect_extents = cpu_particles->get_emission_rect_extents();
+ proc_mat->set_emission_box_extents(Vector3(rect_extents.x, rect_extents.y, 0));
+
+ if (cpu_particles->get_split_scale()) {
+ Ref<CurveXYZTexture> scale3D = memnew(CurveXYZTexture);
+ scale3D->set_curve_x(cpu_particles->get_scale_curve_x());
+ scale3D->set_curve_y(cpu_particles->get_scale_curve_y());
+ proc_mat->set_param_texture(ParticleProcessMaterial::PARAM_SCALE, scale3D);
+ }
+
+ Vector2 gravity = cpu_particles->get_gravity();
+ proc_mat->set_gravity(Vector3(gravity.x, gravity.y, 0));
+ proc_mat->set_lifetime_randomness(cpu_particles->get_lifetime_randomness());
+
+#define CONVERT_PARAM(m_param) \
+ proc_mat->set_param_min(ParticleProcessMaterial::m_param, cpu_particles->get_param_min(CPUParticles2D::m_param)); \
+ { \
+ Ref<Curve> curve = cpu_particles->get_param_curve(CPUParticles2D::m_param); \
+ if (curve.is_valid()) { \
+ Ref<CurveTexture> tex = memnew(CurveTexture); \
+ tex->set_curve(curve); \
+ proc_mat->set_param_texture(ParticleProcessMaterial::m_param, tex); \
+ } \
+ } \
+ proc_mat->set_param_max(ParticleProcessMaterial::m_param, cpu_particles->get_param_max(CPUParticles2D::m_param));
+
+ CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY);
+ CONVERT_PARAM(PARAM_ANGULAR_VELOCITY);
+ CONVERT_PARAM(PARAM_ORBIT_VELOCITY);
+ CONVERT_PARAM(PARAM_LINEAR_ACCEL);
+ CONVERT_PARAM(PARAM_RADIAL_ACCEL);
+ CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL);
+ CONVERT_PARAM(PARAM_DAMPING);
+ CONVERT_PARAM(PARAM_ANGLE);
+ CONVERT_PARAM(PARAM_SCALE);
+ CONVERT_PARAM(PARAM_HUE_VARIATION);
+ CONVERT_PARAM(PARAM_ANIM_SPEED);
+ CONVERT_PARAM(PARAM_ANIM_OFFSET);
+
+#undef CONVERT_PARAM
+}
+
void GPUParticles2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
@@ -680,6 +774,8 @@ void GPUParticles2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_trail_section_subdivisions", "subdivisions"), &GPUParticles2D::set_trail_section_subdivisions);
ClassDB::bind_method(D_METHOD("get_trail_section_subdivisions"), &GPUParticles2D::get_trail_section_subdivisions);
+ ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &GPUParticles2D::convert_from_particles);
+
ADD_SIGNAL(MethodInfo("finished"));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h
index 3131698e5c..97690b07fa 100644
--- a/scene/2d/gpu_particles_2d.h
+++ b/scene/2d/gpu_particles_2d.h
@@ -169,6 +169,8 @@ public:
void restart();
Rect2 capture_rect() const;
+ void convert_from_particles(Node *p_particles);
+
GPUParticles2D();
~GPUParticles2D();
};
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 3a23cbcff1..a7667267a6 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -30,6 +30,9 @@
#include "gpu_particles_3d.h"
+#include "scene/3d/cpu_particles_3d.h"
+#include "scene/resources/curve_texture.h"
+#include "scene/resources/gradient_texture.h"
#include "scene/resources/particle_process_material.h"
#include "scene/scene_string_names.h"
@@ -546,10 +549,98 @@ void GPUParticles3D::set_transform_align(TransformAlign p_align) {
transform_align = p_align;
RS::get_singleton()->particles_set_transform_align(particles, RS::ParticlesTransformAlign(transform_align));
}
+
GPUParticles3D::TransformAlign GPUParticles3D::get_transform_align() const {
return transform_align;
}
+void GPUParticles3D::convert_from_particles(Node *p_particles) {
+ CPUParticles3D *cpu_particles = Object::cast_to<CPUParticles3D>(p_particles);
+ ERR_FAIL_NULL_MSG(cpu_particles, "Only CPUParticles3D nodes can be converted to GPUParticles3D.");
+
+ set_emitting(cpu_particles->is_emitting());
+ set_amount(cpu_particles->get_amount());
+ set_lifetime(cpu_particles->get_lifetime());
+ set_one_shot(cpu_particles->get_one_shot());
+ set_pre_process_time(cpu_particles->get_pre_process_time());
+ set_explosiveness_ratio(cpu_particles->get_explosiveness_ratio());
+ set_randomness_ratio(cpu_particles->get_randomness_ratio());
+ set_use_local_coordinates(cpu_particles->get_use_local_coordinates());
+ set_fixed_fps(cpu_particles->get_fixed_fps());
+ set_fractional_delta(cpu_particles->get_fractional_delta());
+ set_speed_scale(cpu_particles->get_speed_scale());
+ set_draw_order(DrawOrder(cpu_particles->get_draw_order()));
+ set_draw_pass_mesh(0, cpu_particles->get_mesh());
+
+ Ref<ParticleProcessMaterial> proc_mat = memnew(ParticleProcessMaterial);
+ set_process_material(proc_mat);
+
+ proc_mat->set_direction(cpu_particles->get_direction());
+ proc_mat->set_spread(cpu_particles->get_spread());
+ proc_mat->set_flatness(cpu_particles->get_flatness());
+ proc_mat->set_color(cpu_particles->get_color());
+
+ Ref<Gradient> grad = cpu_particles->get_color_ramp();
+ if (grad.is_valid()) {
+ Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
+ tex->set_gradient(grad);
+ proc_mat->set_color_ramp(tex);
+ }
+
+ Ref<Gradient> grad_init = cpu_particles->get_color_initial_ramp();
+ if (grad_init.is_valid()) {
+ Ref<GradientTexture1D> tex = memnew(GradientTexture1D);
+ tex->set_gradient(grad_init);
+ proc_mat->set_color_initial_ramp(tex);
+ }
+
+ proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY));
+ proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ROTATE_Y, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_ROTATE_Y));
+ proc_mat->set_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z, cpu_particles->get_particle_flag(CPUParticles3D::PARTICLE_FLAG_DISABLE_Z));
+
+ proc_mat->set_emission_shape(ParticleProcessMaterial::EmissionShape(cpu_particles->get_emission_shape()));
+ proc_mat->set_emission_sphere_radius(cpu_particles->get_emission_sphere_radius());
+ proc_mat->set_emission_box_extents(cpu_particles->get_emission_box_extents());
+
+ if (cpu_particles->get_split_scale()) {
+ Ref<CurveXYZTexture> scale3D = memnew(CurveXYZTexture);
+ scale3D->set_curve_x(cpu_particles->get_scale_curve_x());
+ scale3D->set_curve_y(cpu_particles->get_scale_curve_y());
+ scale3D->set_curve_z(cpu_particles->get_scale_curve_z());
+ proc_mat->set_param_texture(ParticleProcessMaterial::PARAM_SCALE, scale3D);
+ }
+
+ proc_mat->set_gravity(cpu_particles->get_gravity());
+ proc_mat->set_lifetime_randomness(cpu_particles->get_lifetime_randomness());
+
+#define CONVERT_PARAM(m_param) \
+ proc_mat->set_param_min(ParticleProcessMaterial::m_param, cpu_particles->get_param_min(CPUParticles3D::m_param)); \
+ { \
+ Ref<Curve> curve = cpu_particles->get_param_curve(CPUParticles3D::m_param); \
+ if (curve.is_valid()) { \
+ Ref<CurveTexture> tex = memnew(CurveTexture); \
+ tex->set_curve(curve); \
+ proc_mat->set_param_texture(ParticleProcessMaterial::m_param, tex); \
+ } \
+ } \
+ proc_mat->set_param_max(ParticleProcessMaterial::m_param, cpu_particles->get_param_max(CPUParticles3D::m_param));
+
+ CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY);
+ CONVERT_PARAM(PARAM_ANGULAR_VELOCITY);
+ CONVERT_PARAM(PARAM_ORBIT_VELOCITY);
+ CONVERT_PARAM(PARAM_LINEAR_ACCEL);
+ CONVERT_PARAM(PARAM_RADIAL_ACCEL);
+ CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL);
+ CONVERT_PARAM(PARAM_DAMPING);
+ CONVERT_PARAM(PARAM_ANGLE);
+ CONVERT_PARAM(PARAM_SCALE);
+ CONVERT_PARAM(PARAM_HUE_VARIATION);
+ CONVERT_PARAM(PARAM_ANIM_SPEED);
+ CONVERT_PARAM(PARAM_ANIM_OFFSET);
+
+#undef CONVERT_PARAM
+}
+
void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &GPUParticles3D::set_emitting);
ClassDB::bind_method(D_METHOD("set_amount", "amount"), &GPUParticles3D::set_amount);
@@ -613,6 +704,8 @@ void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_transform_align", "align"), &GPUParticles3D::set_transform_align);
ClassDB::bind_method(D_METHOD("get_transform_align"), &GPUParticles3D::get_transform_align);
+ ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &GPUParticles3D::convert_from_particles);
+
ADD_SIGNAL(MethodInfo("finished"));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h
index dba6a8f2ab..6e9083bda2 100644
--- a/scene/3d/gpu_particles_3d.h
+++ b/scene/3d/gpu_particles_3d.h
@@ -178,6 +178,8 @@ public:
void emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags);
AABB capture_aabb() const;
+ void convert_from_particles(Node *p_particles);
+
GPUParticles3D();
~GPUParticles3D();
};
diff --git a/servers/rendering/renderer_rd/cluster_builder_rd.cpp b/servers/rendering/renderer_rd/cluster_builder_rd.cpp
index d2a1a5ab9c..64950c6991 100644
--- a/servers/rendering/renderer_rd/cluster_builder_rd.cpp
+++ b/servers/rendering/renderer_rd/cluster_builder_rd.cpp
@@ -285,7 +285,7 @@ void ClusterBuilderRD::setup(Size2i p_screen_size, uint32_t p_max_elements, RID
cluster_render_buffer = RD::get_singleton()->storage_buffer_create(cluster_render_buffer_size);
cluster_buffer = RD::get_singleton()->storage_buffer_create(cluster_buffer_size);
- render_elements = static_cast<RenderElementData *>(memalloc(sizeof(RenderElementData *) * render_element_max));
+ render_elements = static_cast<RenderElementData *>(memalloc(sizeof(RenderElementData) * render_element_max));
render_element_count = 0;
element_buffer = RD::get_singleton()->storage_buffer_create(sizeof(RenderElementData) * render_element_max);