summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/CODEOWNERS64
-rw-r--r--.github/workflows/windows_builds.yml27
-rw-r--r--core/config/project_settings.cpp18
-rw-r--r--doc/classes/CPUParticles3D.xml6
-rw-r--r--doc/classes/PackedVector2Array.xml2
-rw-r--r--drivers/d3d12/SCsub6
-rw-r--r--drivers/d3d12/dxil_hash.cpp2
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.cpp9
-rw-r--r--drivers/egl/egl_manager.cpp65
-rw-r--r--drivers/egl/egl_manager.h7
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp21
-rw-r--r--drivers/gles3/rasterizer_gles3.h10
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp6
-rw-r--r--drivers/gles3/storage/mesh_storage.h2
-rw-r--r--editor/animation_track_editor.cpp13
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/editor_settings.cpp22
-rw-r--r--editor/export/export_template_manager.cpp3
-rw-r--r--editor/icons/GuiToggleOffMirrored.svg2
-rw-r--r--editor/import/resource_importer_layered_texture.cpp4
-rw-r--r--editor/import/resource_importer_texture.cpp2
-rw-r--r--editor/multi_node_edit.cpp18
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.cpp309
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.h94
-rw-r--r--editor/plugins/cpu_particles_3d_editor_plugin.cpp217
-rw-r--r--editor/plugins/cpu_particles_3d_editor_plugin.h85
-rw-r--r--editor/plugins/editor_preview_plugins.cpp9
-rw-r--r--editor/plugins/font_config_plugin.cpp8
-rw-r--r--editor/plugins/gpu_particles_2d_editor_plugin.cpp427
-rw-r--r--editor/plugins/gpu_particles_2d_editor_plugin.h101
-rw-r--r--editor/plugins/gpu_particles_3d_editor_plugin.cpp461
-rw-r--r--editor/plugins/gpu_particles_3d_editor_plugin.h118
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp85
-rw-r--r--editor/plugins/node_3d_editor_plugin.h4
-rw-r--r--editor/plugins/particles_editor_plugin.cpp968
-rw-r--r--editor/plugins/particles_editor_plugin.h216
-rw-r--r--editor/register_editor_types.cpp5
-rw-r--r--modules/bcdec/image_decompress_bcdec.cpp35
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp15
-rw-r--r--modules/gdscript/gdscript_editor.cpp14
-rw-r--r--modules/mono/editor/bindings_generator.cpp69
-rw-r--r--modules/mono/editor/bindings_generator.h3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs68
-rw-r--r--platform/windows/detect.py7
-rw-r--r--scene/2d/skeleton_2d.cpp44
-rw-r--r--scene/2d/skeleton_2d.h2
-rw-r--r--scene/3d/cpu_particles_3d.cpp1
-rw-r--r--scene/3d/node_3d.cpp22
-rw-r--r--scene/3d/skeleton_3d.cpp223
-rw-r--r--scene/3d/skeleton_3d.h13
-rw-r--r--scene/gui/code_edit.cpp6
-rw-r--r--scene/main/viewport.cpp10
-rw-r--r--scene/resources/material.cpp9
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp139
-rw-r--r--servers/rendering/renderer_canvas_cull.h18
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp7
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h2
57 files changed, 1990 insertions, 2137 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 748d787b86..68bd4bc4ce 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2,7 +2,7 @@
# Each line is a file pattern followed by one or more owners.
# Owners can be @users, @org/teams or emails
-# Buildsystem
+# Buildsystem (Before everything to be overwritten)
* @godotengine/buildsystem
@@ -17,10 +17,11 @@
# Doc
/doc/ @godotengine/documentation
-**/doc_classes/ @godotengine/documentation
# Drivers
+/drivers/ @godotengine/_systems
+
## Audio
/drivers/alsa/ @godotengine/audio
/drivers/alsamidi/ @godotengine/audio
@@ -34,7 +35,9 @@
## Rendering
/drivers/d3d12/ @godotengine/rendering
/drivers/dummy/ @godotengine/rendering
+/drivers/egl/ @godotengine/rendering
/drivers/gles3/ @godotengine/rendering
+/drivers/metal/ @godotengine/rendering
/drivers/spirv-reflect/ @godotengine/rendering
/drivers/vulkan/ @godotengine/rendering
@@ -47,18 +50,19 @@
# Editor
-/editor/*debugger* @godotengine/debugger
+/editor/ @godotengine/_editor
+/editor/**/*2d* @godotengine/2d-editor
+/editor/**/*3d* @godotengine/3d-editor
+/editor/**/*code* @godotengine/script-editor
+/editor/**/*debugger* @godotengine/debugger
+/editor/**/*dock* @godotengine/docks
+/editor/**/*script* @godotengine/script-editor
+/editor/**/*shader* @godotengine/shaders
+/editor/debugger/ @godotengine/debugger
/editor/gui/ @godotengine/usability @godotengine/gui-nodes
/editor/icons/ @godotengine/usability
/editor/import/ @godotengine/import
-/editor/plugins/*2d_*.* @godotengine/2d-editor
-/editor/plugins/*3d_*.* @godotengine/3d-editor
-/editor/plugins/script_*.* @godotengine/script-editor
-/editor/plugins/*shader*.* @godotengine/shaders
/editor/themes/ @godotengine/usability @godotengine/gui-nodes
-/editor/code_editor.* @godotengine/script-editor
-/editor/*dock*.* @godotengine/docks
-/editor/*shader*.* @godotengine/shaders
# Main
@@ -71,9 +75,16 @@
# Modules
+/modules/ @godotengine/_engine
+/modules/**/doc_classes/ @godotengine/_engine @godotengine/documentation
+/modules/**/editor/ @godotengine/_engine @godotengine/_editor
+/modules/**/icons/ @godotengine/_engine @godotengine/usability
+/modules/**/tests/ @godotengine/_engine @godotengine/tests
+
## Audio (+ video)
/modules/interactive_music/ @godotengine/audio
/modules/interactive_music/doc_classes/ @godotengine/audio @godotengine/documentation
+/modules/interactive_music/editor/ @godotengine/audio @godotengine/_editor
/modules/minimp3/ @godotengine/audio
/modules/minimp3/doc_classes/ @godotengine/audio @godotengine/documentation
/modules/ogg/ @godotengine/audio
@@ -93,8 +104,10 @@
/modules/etcpak/ @godotengine/import
/modules/fbx/ @godotengine/import
/modules/fbx/doc_classes/ @godotengine/import @godotengine/documentation
+/modules/fbx/editor/ @godotengine/import @godotengine/_editor
/modules/gltf/ @godotengine/import
/modules/gltf/doc_classes/ @godotengine/import @godotengine/documentation
+/modules/gltf/editor/ @godotengine/import @godotengine/_editor
/modules/gltf/tests/ @godotengine/import @godotengine/tests
/modules/hdr/ @godotengine/import
/modules/jpg/ @godotengine/import
@@ -112,12 +125,14 @@
/modules/mbedtls/tests/ @godotengine/network @godotengine/tests
/modules/multiplayer/ @godotengine/network
/modules/multiplayer/doc_classes/ @godotengine/network @godotengine/documentation
+/modules/multiplayer/editor/ @godotengine/network @godotengine/_editor
/modules/upnp/ @godotengine/network
/modules/upnp/doc_classes/ @godotengine/network @godotengine/documentation
/modules/webrtc/ @godotengine/network
/modules/webrtc/doc_classes/ @godotengine/network @godotengine/documentation
/modules/websocket/ @godotengine/network
/modules/websocket/doc_classes/ @godotengine/network @godotengine/documentation
+/modules/websocket/editor/ @godotengine/network @godotengine/_editor
## Physics
/modules/godot_physics_2d/ @godotengine/physics
@@ -134,12 +149,14 @@
## Scripting
/modules/gdscript/ @godotengine/gdscript
/modules/gdscript/doc_classes/ @godotengine/gdscript @godotengine/documentation
+/modules/gdscript/editor/ @godotengine/gdscript @godotengine/_editor
/modules/gdscript/icons/ @godotengine/gdscript @godotengine/usability
/modules/gdscript/tests/ @godotengine/gdscript @godotengine/tests
/modules/jsonrpc/ @godotengine/gdscript @godotengine/network
/modules/jsonrpc/tests @godotengine/gdscript @godotengine/network @godotengine/tests
/modules/mono/ @godotengine/dotnet
/modules/mono/doc_classes/ @godotengine/dotnet @godotengine/documentation
+/modules/mono/editor/ @godotengine/dotnet @godotengine/_editor
/modules/mono/icons/ @godotengine/dotnet @godotengine/usability
## Text
@@ -156,19 +173,24 @@
/modules/mobile_vr/doc_classes/ @godotengine/xr @godotengine/documentation
/modules/openxr/ @godotengine/xr
/modules/openxr/doc_classes/ @godotengine/xr @godotengine/documentation
+/modules/openxr/editor/ @godotengine/xr @godotengine/_editor
/modules/webxr/ @godotengine/xr
/modules/webxr/doc_classes/ @godotengine/xr @godotengine/documentation
## Misc
/modules/csg/ @godotengine/3d-nodes
/modules/csg/doc_classes/ @godotengine/3d-nodes @godotengine/documentation
+/modules/csg/editor/ @godotengine/3d-nodes @godotengine/_editor
/modules/csg/icons/ @godotengine/3d-nodes @godotengine/usability
-/modules/navigation/ @godotengine/navigation
/modules/gridmap/ @godotengine/3d-nodes
/modules/gridmap/doc_classes/ @godotengine/3d-nodes @godotengine/documentation
+/modules/gridmap/editor/ @godotengine/3d-nodes @godotengine/_editor
/modules/gridmap/icons/ @godotengine/3d-nodes @godotengine/usability
+/modules/navigation/ @godotengine/navigation
+/modules/navigation/editor/ @godotengine/navigation @godotengine/_editor
/modules/noise/ @godotengine/core
/modules/noise/doc_classes/ @godotengine/core @godotengine/documentation
+/modules/noise/editor/ @godotengine/core @godotengine/_editor
/modules/noise/icons/ @godotengine/core @godotengine/usability
/modules/noise/tests/ @godotengine/core @godotengine/tests
/modules/regex/ @godotengine/core
@@ -180,6 +202,7 @@
# Platform
+/platform/ @godotengine/_platforms
/platform/android/ @godotengine/android
/platform/android/doc_classes/ @godotengine/android @godotengine/documentation
/platform/ios/ @godotengine/ios
@@ -195,6 +218,7 @@
# Scene
+/scene/ @godotengine/_systems @godotengine/core
/scene/2d/ @godotengine/2d-nodes
/scene/2d/physics/ @godotengine/2d-nodes @godotengine/physics
/scene/3d/ @godotengine/3d-nodes
@@ -213,14 +237,16 @@
# Servers
-/servers/audio* @godotengine/audio
-/servers/camera* @godotengine/xr
-/servers/display_server.* @godotengine/_platforms
-/servers/navigation_server*.* @godotengine/navigation
-/servers/physics* @godotengine/physics
-/servers/rendering* @godotengine/rendering
-/servers/text_server.* @godotengine/gui-nodes
-/servers/xr* @godotengine/xr
+/servers/ @godotengine/_systems
+/servers/**/audio* @godotengine/audio
+/servers/**/camera* @godotengine/xr
+/servers/**/debugger* @godotengine/debugger
+/servers/**/display* @godotengine/_platforms
+/servers/**/navigation* @godotengine/navigation
+/servers/**/physics* @godotengine/physics
+/servers/**/rendering* @godotengine/rendering
+/servers/**/text* @godotengine/gui-nodes
+/servers/**/xr* @godotengine/xr
# Tests
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
index 95e3d4a553..384284b604 100644
--- a/.github/workflows/windows_builds.yml
+++ b/.github/workflows/windows_builds.yml
@@ -30,7 +30,7 @@ jobs:
# Skip debug symbols, they're way too big with MSVC.
sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console
bin: ./bin/godot.windows.editor.x86_64.exe
- artifact: true
+ compiler: msvc
- name: Editor w/ clang-cl (target=editor, tests=yes, use_llvm=yes)
cache-name: windows-editor-clang
@@ -38,6 +38,7 @@ jobs:
tests: true
sconsflags: debug_symbols=no windows_subsystem=console use_llvm=yes
bin: ./bin/godot.windows.editor.x86_64.llvm.exe
+ compiler: clang
- name: Template (target=template_release, tests=yes)
cache-name: windows-template
@@ -45,7 +46,16 @@ jobs:
tests: true
sconsflags: debug_symbols=no
bin: ./bin/godot.windows.template_release.x86_64.console.exe
- artifact: true
+ compiler: msvc
+
+ - name: Template w/ GCC (target=template_release, tests=yes, use_mingw=yes)
+ cache-name: windows-template-gcc
+ # MinGW takes MUCH longer to compile; save time by only targeting Template.
+ target: template_release
+ tests: true
+ sconsflags: debug_symbols=no use_mingw=yes
+ bin: ./bin/godot.windows.template_release.x86_64.console.exe
+ compiler: gcc
steps:
- name: Checkout
@@ -69,16 +79,21 @@ jobs:
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
repo: godotengine/godot-angle-static
- version: tags/chromium/6029
- file: Windows.6029-1.MSVC_17.x86_64.x86_32.zip
+ version: tags/chromium/6601.2
+ file: godot-angle-static-x86_64-${{ matrix.compiler == 'gcc' && 'gcc' || 'msvc' }}-release.zip
target: angle/angle.zip
- name: Extract pre-built ANGLE static libraries
run: Expand-Archive -Force angle/angle.zip ${{ github.workspace }}/
- name: Setup MSVC problem matcher
+ if: matrix.compiler == 'msvc'
uses: ammaraskar/msvc-problem-matcher@master
+ - name: Setup GCC problem matcher
+ if: matrix.compiler != 'msvc'
+ uses: ammaraskar/gcc-problem-matcher@master
+
- name: Compilation
uses: ./.github/actions/godot-build
with:
@@ -94,12 +109,12 @@ jobs:
continue-on-error: true
- name: Prepare artifact
- if: ${{ matrix.artifact }}
+ if: matrix.compiler == 'msvc'
run: |
Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force
- name: Upload artifact
- if: ${{ matrix.artifact }}
+ if: matrix.compiler == 'msvc'
uses: ./.github/actions/upload-artifact
with:
name: ${{ matrix.cache-name }}
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 9fe54e57a7..01f15f9c8e 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1167,22 +1167,16 @@ bool ProjectSettings::is_project_loaded() const {
}
bool ProjectSettings::_property_can_revert(const StringName &p_name) const {
- if (!props.has(p_name)) {
- return false;
- }
-
- return props[p_name].initial != props[p_name].variant;
+ return props.has(p_name);
}
bool ProjectSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
- if (!props.has(p_name)) {
- return false;
+ const RBMap<StringName, ProjectSettings::VariantContainer>::Element *value = props.find(p_name);
+ if (value) {
+ r_property = value->value().initial.duplicate();
+ return true;
}
-
- // Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value.
- r_property = props[p_name].initial.duplicate();
-
- return true;
+ return false;
}
void ProjectSettings::set_setting(const String &p_setting, const Variant &p_value) {
diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml
index d7770f2cd5..04ee95457c 100644
--- a/doc/classes/CPUParticles3D.xml
+++ b/doc/classes/CPUParticles3D.xml
@@ -11,6 +11,12 @@
<link title="Particle systems (3D)">$DOCS_URL/tutorials/3d/particles/index.html</link>
</tutorials>
<methods>
+ <method name="capture_aabb" qualifiers="const">
+ <return type="AABB" />
+ <description>
+ 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" />
diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml
index da41812e0b..4d487b6dc2 100644
--- a/doc/classes/PackedVector2Array.xml
+++ b/doc/classes/PackedVector2Array.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
An array specifically designed to hold [Vector2]. Packs data tightly, so it saves memory for large array sizes.
- [b]Differences between packed arrays, typed arrays, and untyped arrays:[/b] Packed arrays are generally faster to iterate on and modify compared to a typed array of the same type (e.g. [PackedVector3Array] versus [code]Array[Vector2][/code]). Also, packed arrays consume less memory. As a downside, packed arrays are less flexible as they don't offer as many convenience methods such as [method Array.map]. Typed arrays are in turn faster to iterate on and modify than untyped arrays.
+ [b]Differences between packed arrays, typed arrays, and untyped arrays:[/b] Packed arrays are generally faster to iterate on and modify compared to a typed array of the same type (e.g. [PackedVector2Array] versus [code]Array[Vector2][/code]). Also, packed arrays consume less memory. As a downside, packed arrays are less flexible as they don't offer as many convenience methods such as [method Array.map]. Typed arrays are in turn faster to iterate on and modify than untyped arrays.
[b]Note:[/b] Packed arrays are always passed by reference. To get a copy of an array that can be modified independently of the original array, use [method duplicate]. This is [i]not[/i] the case for built-in properties and methods. The returned packed array of these are a copies, and changing it will [i]not[/i] affect the original value. To update a built-in property you need to modify the returned array, and then assign it to the property again.
</description>
<tutorials>
diff --git a/drivers/d3d12/SCsub b/drivers/d3d12/SCsub
index b6ceed23ac..beeb13398e 100644
--- a/drivers/d3d12/SCsub
+++ b/drivers/d3d12/SCsub
@@ -4,6 +4,8 @@ from misc.utility.scons_hints import *
import os
from pathlib import Path
+import methods
+
Import("env")
env_d3d12_rdd = env.Clone()
@@ -139,6 +141,10 @@ else:
extra_defines += [
"HAVE_STRUCT_TIMESPEC",
]
+ if methods.using_gcc(env) and methods.get_compiler_version(env)["major"] < 13:
+ # `region` & `endregion` not recognized as valid pragmas.
+ env_d3d12_rdd.Append(CCFLAGS=["-Wno-unknown-pragmas"])
+ env.Append(CCFLAGS=["-Wno-unknown-pragmas"])
# This is needed since rendering_device_d3d12.cpp needs to include some Mesa internals.
env_d3d12_rdd.Prepend(CPPPATH=mesa_private_inc_paths)
diff --git a/drivers/d3d12/dxil_hash.cpp b/drivers/d3d12/dxil_hash.cpp
index f94a4a30df..e08492c9ea 100644
--- a/drivers/d3d12/dxil_hash.cpp
+++ b/drivers/d3d12/dxil_hash.cpp
@@ -96,7 +96,7 @@ void compute_dxil_hash(const BYTE *pData, UINT byteCount, BYTE *pOutHash) {
UINT NextEndState = bTwoRowsPadding ? N - 2 : N - 1;
const BYTE *pCurrData = pData;
for (UINT i = 0; i < N; i++, offset += 64, pCurrData += 64) {
- UINT x[16];
+ UINT x[16] = {};
const UINT *pX;
if (i == NextEndState) {
if (!bTwoRowsPadding && i == N - 1) {
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index 8271d4b7e3..0ef88e7d52 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -1348,7 +1348,14 @@ RDD::TextureID RenderingDeviceDriverD3D12::texture_create(const TextureFormat &p
}
tex_info->states_ptr = &tex_info->owner_info.states;
tex_info->format = p_format.format;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif
tex_info->desc = *(CD3DX12_RESOURCE_DESC *)&resource_desc;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
tex_info->base_layer = 0;
tex_info->layers = resource_desc.ArraySize();
tex_info->base_mip = 0;
@@ -6578,8 +6585,6 @@ static Error create_command_signature(ID3D12Device *device, D3D12_INDIRECT_ARGUM
Error RenderingDeviceDriverD3D12::_initialize_frames(uint32_t p_frame_count) {
Error err;
- D3D12MA::ALLOCATION_DESC allocation_desc = {};
- allocation_desc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
//CD3DX12_RESOURCE_DESC resource_desc = CD3DX12_RESOURCE_DESC::Buffer(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
uint32_t resource_descriptors_per_frame = GLOBAL_GET("rendering/rendering_device/d3d12/max_resource_descriptors_per_frame");
diff --git a/drivers/egl/egl_manager.cpp b/drivers/egl/egl_manager.cpp
index 4477ba7752..603dfadd4b 100644
--- a/drivers/egl/egl_manager.cpp
+++ b/drivers/egl/egl_manager.cpp
@@ -30,6 +30,8 @@
#include "egl_manager.h"
+#include "drivers/gles3/rasterizer_gles3.h"
+
#ifdef EGL_ENABLED
#if defined(EGL_STATIC)
@@ -51,6 +53,16 @@ extern "C" EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplayEXT(EGLenum platfo
#define GLAD_EGL_EXT_platform_base 0
#endif
+#ifdef WINDOWS_ENABLED
+// Unofficial ANGLE extension: EGL_ANGLE_surface_orientation
+#ifndef EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE
+#define EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE 0x33A7
+#define EGL_SURFACE_ORIENTATION_ANGLE 0x33A8
+#define EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE 0x0001
+#define EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE 0x0002
+#endif
+#endif
+
// Creates and caches a GLDisplay. Returns -1 on error.
int EGLManager::_get_gldisplay_id(void *p_display) {
// Look for a cached GLDisplay.
@@ -115,6 +127,18 @@ int EGLManager::_get_gldisplay_id(void *p_display) {
}
#endif
+#ifdef WINDOWS_ENABLED
+ String client_extensions_string = eglQueryString(new_gldisplay.egl_display, EGL_EXTENSIONS);
+ if (eglGetError() == EGL_SUCCESS) {
+ Vector<String> egl_extensions = client_extensions_string.split(" ");
+
+ if (egl_extensions.has("EGL_ANGLE_surface_orientation")) {
+ new_gldisplay.has_EGL_ANGLE_surface_orientation = true;
+ print_verbose("EGL: EGL_ANGLE_surface_orientation is supported.");
+ }
+ }
+#endif
+
displays.push_back(new_gldisplay);
// Return the new GLDisplay's ID.
@@ -237,8 +261,29 @@ Error EGLManager::window_create(DisplayServer::WindowID p_window_id, void *p_dis
GLWindow &glwindow = windows[p_window_id];
glwindow.gldisplay_id = gldisplay_id;
+ Vector<EGLAttrib> egl_attribs;
+
+#ifdef WINDOWS_ENABLED
+ if (gldisplay.has_EGL_ANGLE_surface_orientation) {
+ EGLint optimal_orientation;
+ if (eglGetConfigAttrib(gldisplay.egl_display, gldisplay.egl_config, EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &optimal_orientation)) {
+ // We only need to support inverting Y for optimizing ANGLE on D3D11.
+ if (optimal_orientation & EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE && !(optimal_orientation & EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE)) {
+ egl_attribs.push_back(EGL_SURFACE_ORIENTATION_ANGLE);
+ egl_attribs.push_back(EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE);
+ }
+ } else {
+ ERR_PRINT(vformat("Failed to get EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, error: 0x%08X", eglGetError()));
+ }
+ }
+
+ if (!egl_attribs.is_empty()) {
+ egl_attribs.push_back(EGL_NONE);
+ }
+#endif
+
if (GLAD_EGL_VERSION_1_5) {
- glwindow.egl_surface = eglCreatePlatformWindowSurface(gldisplay.egl_display, gldisplay.egl_config, p_native_window, nullptr);
+ glwindow.egl_surface = eglCreatePlatformWindowSurface(gldisplay.egl_display, gldisplay.egl_config, p_native_window, egl_attribs.ptr());
} else {
EGLNativeWindowType *native_window_type = (EGLNativeWindowType *)p_native_window;
glwindow.egl_surface = eglCreateWindowSurface(gldisplay.egl_display, gldisplay.egl_config, *native_window_type, nullptr);
@@ -250,6 +295,20 @@ Error EGLManager::window_create(DisplayServer::WindowID p_window_id, void *p_dis
glwindow.initialized = true;
+#ifdef WINDOWS_ENABLED
+ if (gldisplay.has_EGL_ANGLE_surface_orientation) {
+ EGLint orientation;
+ if (eglQuerySurface(gldisplay.egl_display, glwindow.egl_surface, EGL_SURFACE_ORIENTATION_ANGLE, &orientation)) {
+ if (orientation & EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE && !(orientation & EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE)) {
+ glwindow.flipped_y = true;
+ print_verbose("EGL: Using optimal surface orientation: Invert Y");
+ }
+ } else {
+ ERR_PRINT(vformat("Failed to get EGL_SURFACE_ORIENTATION_ANGLE, error: 0x%08X", eglGetError()));
+ }
+ }
+#endif
+
window_make_current(p_window_id);
return OK;
@@ -316,6 +375,10 @@ void EGLManager::window_make_current(DisplayServer::WindowID p_window_id) {
GLDisplay &current_display = displays[current_window->gldisplay_id];
eglMakeCurrent(current_display.egl_display, current_window->egl_surface, current_window->egl_surface, current_display.egl_context);
+
+#ifdef WINDOWS_ENABLED
+ RasterizerGLES3::set_screen_flipped_y(glwindow.flipped_y);
+#endif
}
void EGLManager::set_use_vsync(bool p_use) {
diff --git a/drivers/egl/egl_manager.h b/drivers/egl/egl_manager.h
index a4502c0687..f1b3dc99b7 100644
--- a/drivers/egl/egl_manager.h
+++ b/drivers/egl/egl_manager.h
@@ -53,11 +53,18 @@ private:
EGLDisplay egl_display = EGL_NO_DISPLAY;
EGLContext egl_context = EGL_NO_CONTEXT;
EGLConfig egl_config = nullptr;
+
+#ifdef WINDOWS_ENABLED
+ bool has_EGL_ANGLE_surface_orientation = false;
+#endif
};
// EGL specific window data.
struct GLWindow {
bool initialized = false;
+#ifdef WINDOWS_ENABLED
+ bool flipped_y = false;
+#endif
// An handle to the GLDisplay associated with this window.
int gldisplay_id = -1;
diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp
index e79f1db08d..6e508c6ebf 100644
--- a/drivers/gles3/rasterizer_gles3.cpp
+++ b/drivers/gles3/rasterizer_gles3.cpp
@@ -86,6 +86,10 @@
#define strcpy strcpy_s
#endif
+#ifdef WINDOWS_ENABLED
+bool RasterizerGLES3::screen_flipped_y = false;
+#endif
+
bool RasterizerGLES3::gles_over_gl = true;
void RasterizerGLES3::begin_frame(double frame_step) {
@@ -389,6 +393,12 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display
flip_y = false;
}
+#ifdef WINDOWS_ENABLED
+ if (screen_flipped_y) {
+ flip_y = !flip_y;
+ }
+#endif
+
GLuint read_fbo = 0;
glGenFramebuffers(1, &read_fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, read_fbo);
@@ -485,9 +495,14 @@ void RasterizerGLES3::set_boot_image(const Ref<Image> &p_image, const Color &p_c
screenrect.position += ((Size2(win_size.width, win_size.height) - screenrect.size) / 2.0).floor();
}
- // Flip Y.
- screenrect.position.y = win_size.y - screenrect.position.y;
- screenrect.size.y = -screenrect.size.y;
+#ifdef WINDOWS_ENABLED
+ if (!screen_flipped_y)
+#endif
+ {
+ // Flip Y.
+ screenrect.position.y = win_size.y - screenrect.position.y;
+ screenrect.size.y = -screenrect.size.y;
+ }
// Normalize texture coordinates to window size.
screenrect.position /= win_size;
diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h
index 92454e014e..6765d8b4d5 100644
--- a/drivers/gles3/rasterizer_gles3.h
+++ b/drivers/gles3/rasterizer_gles3.h
@@ -58,6 +58,10 @@ private:
double time_total = 0.0;
bool flip_xy_workaround = false;
+#ifdef WINDOWS_ENABLED
+ static bool screen_flipped_y;
+#endif
+
static bool gles_over_gl;
protected:
@@ -118,6 +122,12 @@ public:
low_end = true;
}
+#ifdef WINDOWS_ENABLED
+ static void set_screen_flipped_y(bool p_flipped) {
+ screen_flipped_y = p_flipped;
+ }
+#endif
+
_ALWAYS_INLINE_ uint64_t get_frame_number() const { return frame; }
_ALWAYS_INLINE_ double get_frame_delta_time() const { return delta; }
_ALWAYS_INLINE_ double get_total_time() const { return time_total; }
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index 5058554659..73d95d75ba 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -2201,7 +2201,7 @@ void MeshStorage::skeleton_allocate_data(RID p_skeleton, int p_bones, bool p_2d_
glBindTexture(GL_TEXTURE_2D, 0);
GLES3::Utilities::get_singleton()->texture_allocated_data(skeleton->transforms_texture, skeleton->data.size() * sizeof(float), "Skeleton transforms texture");
- memset(skeleton->data.ptrw(), 0, skeleton->data.size() * sizeof(float));
+ memset(skeleton->data.ptr(), 0, skeleton->data.size() * sizeof(float));
_skeleton_make_dirty(skeleton);
}
@@ -2232,7 +2232,7 @@ void MeshStorage::skeleton_bone_set_transform(RID p_skeleton, int p_bone, const
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(skeleton->use_2d);
- float *dataptr = skeleton->data.ptrw() + p_bone * 12;
+ float *dataptr = skeleton->data.ptr() + p_bone * 12;
dataptr[0] = p_transform.basis.rows[0][0];
dataptr[1] = p_transform.basis.rows[0][1];
@@ -2284,7 +2284,7 @@ void MeshStorage::skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, con
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(!skeleton->use_2d);
- float *dataptr = skeleton->data.ptrw() + p_bone * 8;
+ float *dataptr = skeleton->data.ptr() + p_bone * 8;
dataptr[0] = p_transform.columns[0][0];
dataptr[1] = p_transform.columns[1][0];
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index 31858cd372..8615b89a30 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -214,7 +214,7 @@ struct Skeleton {
bool use_2d = false;
int size = 0;
int height = 0;
- Vector<float> data;
+ LocalVector<float> data;
bool dirty = false;
Skeleton *dirty_list = nullptr;
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index c02efc445f..f8d35f2112 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -4279,7 +4279,18 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
// Let's build a node path.
String path = root->get_path_to(p_node, true);
- Variant value = p_node->get(p_property);
+ // Get the value from the subpath.
+ Variant value = p_node;
+ Vector<String> property_path = p_property.split(":");
+ for (const String &E : property_path) {
+ if (value.get_type() == Variant::OBJECT) {
+ Object *obj = value;
+ value = obj->get(E);
+ } else {
+ value = Variant();
+ break;
+ }
+ }
if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") {
if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index d88fb134f1..95b3c30d1b 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -6737,10 +6737,6 @@ EditorNode::EditorNode() {
ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G);
ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G);
- // Used in the GPUParticles/CPUParticles 2D/3D editor plugins.
- // The shortcut is Ctrl + R even on macOS, as Cmd + R is used to run the current scene on macOS.
- ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R);
-
FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename"));
_update_vsync_mode();
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index ee06f08a2d..12a7c3a2ff 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -1424,24 +1424,20 @@ Variant _EDITOR_GET(const String &p_setting) {
}
bool EditorSettings::_property_can_revert(const StringName &p_name) const {
- if (!props.has(p_name)) {
- return false;
- }
-
- if (!props[p_name].has_default_value) {
- return false;
+ const VariantContainer *property = props.getptr(p_name);
+ if (property) {
+ return property->has_default_value;
}
-
- return props[p_name].initial != props[p_name].variant;
+ return false;
}
bool EditorSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
- if (!props.has(p_name) || !props[p_name].has_default_value) {
- return false;
+ const VariantContainer *value = props.getptr(p_name);
+ if (value && value->has_default_value) {
+ r_property = value->initial;
+ return true;
}
-
- r_property = props[p_name].initial;
- return true;
+ return false;
}
void EditorSettings::add_property_hint(const PropertyInfo &p_hint) {
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
index 5360b6fb60..2309319376 100644
--- a/editor/export/export_template_manager.cpp
+++ b/editor/export/export_template_manager.cpp
@@ -34,6 +34,7 @@
#include "core/io/json.h"
#include "core/io/zip_io.h"
#include "core/version.h"
+#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@@ -876,7 +877,7 @@ Error ExportTemplateManager::install_android_template_from_file(const String &p_
ProgressDialog::get_singleton()->end_task("uncompress_src");
unzClose(pkg);
-
+ EditorFileSystem::get_singleton()->scan_changes();
return OK;
}
diff --git a/editor/icons/GuiToggleOffMirrored.svg b/editor/icons/GuiToggleOffMirrored.svg
index 4e7f63d573..138a856a1d 100644
--- a/editor/icons/GuiToggleOffMirrored.svg
+++ b/editor/icons/GuiToggleOffMirrored.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="38" height="16"><rect width="36" height="14" x="1" y="1" fill="gray" rx="7"/><circle cx="8" cy="8" r="5" fill="#b3b3b3"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" width="38" height="16"><g fill="#e0e0e0"><rect width="36" height="14" x="1" y="1" fill-opacity=".2" rx="7"/><circle cx="30" cy="8" r="5"/></g></svg> \ No newline at end of file
diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp
index 72d715ac2d..9de34cf1db 100644
--- a/editor/import/resource_importer_layered_texture.cpp
+++ b/editor/import/resource_importer_layered_texture.cpp
@@ -276,6 +276,10 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
f->store_32(0);
f->store_32(0);
+ if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_images[0]->get_format() >= Image::FORMAT_RF) {
+ p_compress_mode = COMPRESS_VRAM_UNCOMPRESSED; // These can't go as lossy.
+ }
+
for (int i = 0; i < p_images.size(); i++) {
ResourceImporterTexture::save_to_ctex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
}
diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp
index a205123df1..b1cf079c1f 100644
--- a/editor/import/resource_importer_texture.cpp
+++ b/editor/import/resource_importer_texture.cpp
@@ -380,7 +380,7 @@ void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String
f->store_32(0);
f->store_32(0);
- if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_image->get_format() > Image::FORMAT_RGBA8) {
+ if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_image->get_format() >= Image::FORMAT_RF) {
p_compress_mode = COMPRESS_VRAM_UNCOMPRESSED; //these can't go as lossy
}
diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp
index 45786c0ab5..ef6a9c9a88 100644
--- a/editor/multi_node_edit.cpp
+++ b/editor/multi_node_edit.cpp
@@ -186,25 +186,9 @@ bool MultiNodeEdit::_property_can_revert(const StringName &p_name) const {
}
if (ClassDB::has_property(get_edited_class_name(), p_name)) {
- StringName class_name;
for (const NodePath &E : nodes) {
Node *node = es->get_node_or_null(E);
- if (!node) {
- continue;
- }
-
- class_name = node->get_class_name();
- }
-
- Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name);
- for (const NodePath &E : nodes) {
- Node *node = es->get_node_or_null(E);
- if (!node) {
- continue;
- }
-
- if (node->get(p_name) != default_value) {
- // A node that doesn't have the default value has been found, so show the revert button.
+ if (node) {
return true;
}
}
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
deleted file mode 100644
index 4869a202d7..0000000000
--- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp
+++ /dev/null
@@ -1,309 +0,0 @@
-/**************************************************************************/
-/* cpu_particles_2d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "cpu_particles_2d_editor_plugin.h"
-
-#include "canvas_item_editor_plugin.h"
-#include "core/io/image_loader.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.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"
-#include "scene/gui/separator.h"
-#include "scene/gui/spin_box.h"
-#include "scene/resources/particle_process_material.h"
-
-void CPUParticles2DEditorPlugin::edit(Object *p_object) {
- particles = Object::cast_to<CPUParticles2D>(p_object);
-}
-
-bool CPUParticles2DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("CPUParticles2D");
-}
-
-void CPUParticles2DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- toolbar->show();
- } else {
- toolbar->hide();
- }
-}
-
-void CPUParticles2DEditorPlugin::_file_selected(const String &p_file) {
- source_emission_file = p_file;
- emission_mask->popup_centered();
-}
-
-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"), UndoRedo::MERGE_DISABLE, particles);
- SceneTreeDock::get_singleton()->replace_node(particles, gpu_particles);
- ur->commit_action(false);
- } break;
- }
-}
-
-void CPUParticles2DEditorPlugin::_generate_emission_mask() {
- Ref<Image> img;
- img.instantiate();
- Error err = ImageLoader::load_image(source_emission_file, img);
- ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
-
- if (img->is_compressed()) {
- img->decompress();
- }
- img->convert(Image::FORMAT_RGBA8);
- ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
- Size2i s = img->get_size();
- ERR_FAIL_COND(s.width == 0 || s.height == 0);
-
- Vector<Point2> valid_positions;
- Vector<Point2> valid_normals;
- Vector<uint8_t> valid_colors;
-
- valid_positions.resize(s.width * s.height);
-
- EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- valid_normals.resize(s.width * s.height);
- }
-
- bool capture_colors = emission_colors->is_pressed();
-
- if (capture_colors) {
- valid_colors.resize(s.width * s.height * 4);
- }
-
- int vpc = 0;
-
- {
- Vector<uint8_t> img_data = img->get_data();
- const uint8_t *r = img_data.ptr();
-
- for (int i = 0; i < s.width; i++) {
- for (int j = 0; j < s.height; j++) {
- uint8_t a = r[(j * s.width + i) * 4 + 3];
-
- if (a > 128) {
- if (emode == EMISSION_MODE_SOLID) {
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
- valid_positions.write[vpc++] = Point2(i, j);
-
- } else {
- bool on_border = false;
- for (int x = i - 1; x <= i + 1; x++) {
- for (int y = j - 1; y <= j + 1; y++) {
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- on_border = true;
- break;
- }
- }
-
- if (on_border) {
- break;
- }
- }
-
- if (on_border) {
- valid_positions.write[vpc] = Point2(i, j);
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- Vector2 normal;
- for (int x = i - 2; x <= i + 2; x++) {
- for (int y = j - 2; y <= j + 2; y++) {
- if (x == i && y == j) {
- continue;
- }
-
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- normal += Vector2(x - i, y - j).normalized();
- }
- }
- }
-
- normal.normalize();
- valid_normals.write[vpc] = normal;
- }
-
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
-
- vpc++;
- }
- }
- }
- }
- }
- }
-
- valid_positions.resize(vpc);
- if (valid_normals.size()) {
- valid_normals.resize(vpc);
- }
-
- ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
-
- if (capture_colors) {
- PackedColorArray pca;
- pca.resize(vpc);
- Color *pcaw = pca.ptrw();
- for (int i = 0; i < vpc; i += 1) {
- Color color;
- color.r = valid_colors[i * 4 + 0] / 255.0f;
- color.g = valid_colors[i * 4 + 1] / 255.0f;
- color.b = valid_colors[i * 4 + 2] / 255.0f;
- color.a = valid_colors[i * 4 + 3] / 255.0f;
- pcaw[i] = color;
- }
- particles->set_emission_colors(pca);
- }
-
- if (valid_normals.size()) {
- particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS);
- PackedVector2Array norms;
- norms.resize(valid_normals.size());
- Vector2 *normsw = norms.ptrw();
- for (int i = 0; i < valid_normals.size(); i += 1) {
- normsw[i] = valid_normals[i];
- }
- particles->set_emission_normals(norms);
- } else {
- particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_POINTS);
- }
-
- {
- Vector2 offset;
- if (emission_mask_centered->is_pressed()) {
- offset = Vector2(-s.width * 0.5, -s.height * 0.5);
- }
-
- PackedVector2Array points;
- points.resize(valid_positions.size());
- Vector2 *pointsw = points.ptrw();
- for (int i = 0; i < valid_positions.size(); i += 1) {
- pointsw[i] = valid_positions[i] + offset;
- }
- particles->set_emission_points(points);
- }
-}
-
-void CPUParticles2DEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback));
- menu->set_icon(file->get_editor_theme_icon(SNAME("CPUParticles2D")));
- file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected));
- } break;
- }
-}
-
-CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
- particles = nullptr;
-
- toolbar = memnew(HBoxContainer);
- add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
- toolbar->hide();
-
- menu = memnew(MenuButton);
- menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), 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);
-
- file = memnew(EditorFileDialog);
- List<String> ext;
- ImageLoader::get_recognized_extensions(&ext);
- for (const String &E : ext) {
- file->add_filter("*." + E, E.to_upper());
- }
- file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
- toolbar->add_child(file);
-
- emission_mask = memnew(ConfirmationDialog);
- emission_mask->set_title(TTR("Load Emission Mask"));
- VBoxContainer *emvb = memnew(VBoxContainer);
- emission_mask->add_child(emvb);
- emission_mask_mode = memnew(OptionButton);
- emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
- emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
- emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
- emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
- VBoxContainer *optionsvb = memnew(VBoxContainer);
- emvb->add_margin_child(TTR("Options"), optionsvb);
- emission_mask_centered = memnew(CheckBox);
- emission_mask_centered->set_text(TTR("Centered"));
- optionsvb->add_child(emission_mask_centered);
- emission_colors = memnew(CheckBox);
- emission_colors->set_text(TTR("Capture Colors from Pixel"));
- optionsvb->add_child(emission_colors);
-
- toolbar->add_child(emission_mask);
-
- emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &CPUParticles2DEditorPlugin::_generate_emission_mask));
-}
-
-CPUParticles2DEditorPlugin::~CPUParticles2DEditorPlugin() {
-}
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.h b/editor/plugins/cpu_particles_2d_editor_plugin.h
deleted file mode 100644
index 645e6d8345..0000000000
--- a/editor/plugins/cpu_particles_2d_editor_plugin.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/**************************************************************************/
-/* cpu_particles_2d_editor_plugin.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef CPU_PARTICLES_2D_EDITOR_PLUGIN_H
-#define CPU_PARTICLES_2D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/editor_plugin.h"
-#include "scene/2d/cpu_particles_2d.h"
-#include "scene/2d/physics/collision_polygon_2d.h"
-#include "scene/gui/box_container.h"
-
-class CheckBox;
-class ConfirmationDialog;
-class SpinBox;
-class EditorFileDialog;
-class MenuButton;
-class OptionButton;
-
-class CPUParticles2DEditorPlugin : public EditorPlugin {
- GDCLASS(CPUParticles2DEditorPlugin, EditorPlugin);
-
- enum {
- MENU_LOAD_EMISSION_MASK,
- MENU_CLEAR_EMISSION_MASK,
- MENU_RESTART,
- MENU_CONVERT_TO_GPU_PARTICLES,
- };
-
- enum EmissionMode {
- EMISSION_MODE_SOLID,
- EMISSION_MODE_BORDER,
- EMISSION_MODE_BORDER_DIRECTED
- };
-
- CPUParticles2D *particles = nullptr;
-
- EditorFileDialog *file = nullptr;
-
- HBoxContainer *toolbar = nullptr;
- MenuButton *menu = nullptr;
-
- ConfirmationDialog *emission_mask = nullptr;
- OptionButton *emission_mask_mode = nullptr;
- CheckBox *emission_mask_centered = nullptr;
- CheckBox *emission_colors = nullptr;
-
- String source_emission_file;
-
- void _file_selected(const String &p_file);
- void _menu_callback(int p_idx);
- void _generate_emission_mask();
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual String get_name() const override { return "CPUParticles2D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- CPUParticles2DEditorPlugin();
- ~CPUParticles2DEditorPlugin();
-};
-
-#endif // CPU_PARTICLES_2D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
deleted file mode 100644
index e0ce330813..0000000000
--- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/**************************************************************************/
-/* cpu_particles_3d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "cpu_particles_3d_editor_plugin.h"
-
-#include "editor/editor_node.h"
-#include "editor/editor_settings.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) {
- if (p_node == node) {
- node = nullptr;
- hide();
- }
-}
-
-void CPUParticles3DEditor::_notification(int p_notification) {
- switch (p_notification) {
- case NOTIFICATION_ENTER_TREE: {
- options->set_icon(get_editor_theme_icon(SNAME("CPUParticles3D")));
- } break;
- }
-}
-
-void CPUParticles3DEditor::_menu_option(int p_option) {
- switch (p_option) {
- case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
- emission_tree_dialog->popup_scenetree_dialog();
- } break;
-
- 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"), UndoRedo::MERGE_DISABLE, node);
- SceneTreeDock::get_singleton()->replace_node(node, gpu_particles);
- ur->commit_action(false);
-
- } break;
- case MENU_OPTION_GENERATE_AABB: {
- // Add one second to the default generation lifetime, since the progress is updated every second.
- generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0));
-
- if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) {
- // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
- generate_aabb->popup_centered();
- } else {
- // Generate the visibility AABB immediately.
- _generate_aabb();
- }
- } break;
- }
-}
-
-void CPUParticles3DEditor::_generate_aabb() {
- double time = generate_seconds->get_value();
-
- double running = 0.0;
-
- EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time));
-
- bool was_emitting = node->is_emitting();
- if (!was_emitting) {
- node->set_emitting(true);
- OS::get_singleton()->delay_usec(1000);
- }
-
- AABB rect;
-
- while (running < time) {
- uint64_t ticks = OS::get_singleton()->get_ticks_usec();
- ep.step(TTR("Generating..."), int(running), true);
- OS::get_singleton()->delay_usec(1000);
-
- AABB capture = node->capture_aabb();
- if (rect == AABB()) {
- rect = capture;
- } else {
- rect.merge_with(capture);
- }
-
- running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
- }
-
- if (!was_emitting) {
- node->set_emitting(false);
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Generate Visibility AABB"));
- ur->add_do_method(node, "set_visibility_aabb", rect);
- ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb());
- ur->commit_action();
-}
-
-void CPUParticles3DEditor::edit(CPUParticles3D *p_particles) {
- base_node = p_particles;
- node = p_particles;
-}
-
-void CPUParticles3DEditor::_generate_emission_points() {
- /// hacer codigo aca
- Vector<Vector3> points;
- Vector<Vector3> normals;
-
- if (!_generate(points, normals)) {
- return;
- }
-
- if (normals.size() == 0) {
- node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_POINTS);
- node->set_emission_points(points);
- } else {
- node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_DIRECTED_POINTS);
- node->set_emission_points(points);
- node->set_emission_normals(normals);
- }
-}
-
-CPUParticles3DEditor::CPUParticles3DEditor() {
- particles_editor_hb = memnew(HBoxContainer);
- Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb);
- options = memnew(MenuButton);
- options->set_switch_on_hover(true);
- particles_editor_hb->add_child(options);
- particles_editor_hb->hide();
-
- options->set_text(TTR("CPUParticles3D"));
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_OPTION_RESTART);
- options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB);
- 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(SceneStringName(id_pressed), callable_mp(this, &CPUParticles3DEditor::_menu_option));
-
- generate_aabb = memnew(ConfirmationDialog);
- generate_aabb->set_title(TTR("Generate Visibility AABB"));
- VBoxContainer *genvb = memnew(VBoxContainer);
- generate_aabb->add_child(genvb);
- generate_seconds = memnew(SpinBox);
- genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
- generate_seconds->set_min(0.1);
- generate_seconds->set_max(25);
- generate_seconds->set_value(2);
-
- add_child(generate_aabb);
-
- generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &CPUParticles3DEditor::_generate_aabb));
-}
-
-void CPUParticles3DEditorPlugin::edit(Object *p_object) {
- particles_editor->edit(Object::cast_to<CPUParticles3D>(p_object));
-}
-
-bool CPUParticles3DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("CPUParticles3D");
-}
-
-void CPUParticles3DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- particles_editor->show();
- particles_editor->particles_editor_hb->show();
- } else {
- particles_editor->particles_editor_hb->hide();
- particles_editor->hide();
- particles_editor->edit(nullptr);
- }
-}
-
-CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() {
- particles_editor = memnew(CPUParticles3DEditor);
- EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
-
- particles_editor->hide();
-}
-
-CPUParticles3DEditorPlugin::~CPUParticles3DEditorPlugin() {
-}
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h
deleted file mode 100644
index 2a1ea93ac8..0000000000
--- a/editor/plugins/cpu_particles_3d_editor_plugin.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/**************************************************************************/
-/* cpu_particles_3d_editor_plugin.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef CPU_PARTICLES_3D_EDITOR_PLUGIN_H
-#define CPU_PARTICLES_3D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/gpu_particles_3d_editor_plugin.h"
-#include "scene/3d/cpu_particles_3d.h"
-
-class CPUParticles3DEditor : public GPUParticles3DEditorBase {
- GDCLASS(CPUParticles3DEditor, GPUParticles3DEditorBase);
-
- enum Menu {
- MENU_OPTION_GENERATE_AABB,
- MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
- MENU_OPTION_CLEAR_EMISSION_VOLUME,
- MENU_OPTION_RESTART,
- MENU_OPTION_CONVERT_TO_GPU_PARTICLES,
- };
-
- ConfirmationDialog *generate_aabb = nullptr;
- SpinBox *generate_seconds = nullptr;
- CPUParticles3D *node = nullptr;
-
- void _generate_aabb();
-
- void _menu_option(int);
-
- friend class CPUParticles3DEditorPlugin;
-
- virtual void _generate_emission_points() override;
-
-protected:
- void _notification(int p_notification);
- void _node_removed(Node *p_node);
-
-public:
- void edit(CPUParticles3D *p_particles);
- CPUParticles3DEditor();
-};
-
-class CPUParticles3DEditorPlugin : public EditorPlugin {
- GDCLASS(CPUParticles3DEditorPlugin, EditorPlugin);
-
- CPUParticles3DEditor *particles_editor = nullptr;
-
-public:
- virtual String get_name() const override { return "CPUParticles3D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- CPUParticles3DEditorPlugin();
- ~CPUParticles3DEditorPlugin();
-};
-
-#endif // CPU_PARTICLES_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index a2c36b1f3c..9a53f07a3f 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -112,9 +112,13 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
return Ref<Texture2D>();
}
- const int mid_depth = (tex_3d->get_depth() - 1) / 2;
-
Vector<Ref<Image>> data = tex_3d->get_data();
+ if (data.size() != tex_3d->get_depth()) {
+ return Ref<Texture2D>();
+ }
+
+ // Use the middle slice for the thumbnail.
+ const int mid_depth = (tex_3d->get_depth() - 1) / 2;
if (!data.is_empty() && data[mid_depth].is_valid()) {
img = data[mid_depth]->duplicate();
}
@@ -124,6 +128,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
return Ref<Texture2D>();
}
+ // Use the middle slice for the thumbnail.
const int mid_layer = (tex_lyr->get_layers() - 1) / 2;
Ref<Image> data = tex_lyr->get_layer_data(mid_layer);
diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp
index ec9513363d..6366d20539 100644
--- a/editor/plugins/font_config_plugin.cpp
+++ b/editor/plugins/font_config_plugin.cpp
@@ -121,13 +121,8 @@ bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name)
if (name.begins_with("keys")) {
int key = name.get_slicec('/', 1).to_int();
- if (defaults_dict.has(key) && dict.has(key)) {
- int value = dict[key];
- Vector3i range = defaults_dict[key];
- return range.z != value;
- }
+ return defaults_dict.has(key) && dict.has(key);
}
-
return false;
}
@@ -142,7 +137,6 @@ bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name,
return true;
}
}
-
return false;
}
diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp
deleted file mode 100644
index 1b68b55ff6..0000000000
--- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp
+++ /dev/null
@@ -1,427 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_2d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "gpu_particles_2d_editor_plugin.h"
-
-#include "canvas_item_editor_plugin.h"
-#include "core/io/image_loader.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.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/gui/menu_button.h"
-#include "scene/gui/separator.h"
-#include "scene/resources/image_texture.h"
-#include "scene/resources/particle_process_material.h"
-
-void GPUParticles2DEditorPlugin::edit(Object *p_object) {
- particles = Object::cast_to<GPUParticles2D>(p_object);
-}
-
-bool GPUParticles2DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("GPUParticles2D");
-}
-
-void GPUParticles2DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- toolbar->show();
- } else {
- toolbar->hide();
- }
-}
-
-void GPUParticles2DEditorPlugin::_file_selected(const String &p_file) {
- source_emission_file = p_file;
- emission_mask->popup_centered();
-}
-
-void GPUParticles2DEditorPlugin::_selection_changed() {
- List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list();
-
- if (selected_particles.is_empty() && selected_nodes.is_empty()) {
- return;
- }
-
- for (GPUParticles2D *SP : selected_particles) {
- SP->set_show_visibility_rect(false);
- }
- selected_particles.clear();
-
- for (Node *P : selected_nodes) {
- GPUParticles2D *selected_particle = Object::cast_to<GPUParticles2D>(P);
- if (selected_particle != nullptr) {
- selected_particle->set_show_visibility_rect(true);
- selected_particles.push_back(selected_particle);
- }
- }
-}
-
-void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
- switch (p_idx) {
- case MENU_GENERATE_VISIBILITY_RECT: {
- // Add one second to the default generation lifetime, since the progress is updated every second.
- generate_seconds->set_value(MAX(1.0, trunc(particles->get_lifetime()) + 1.0));
-
- if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) {
- // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
- generate_visibility_rect->popup_centered();
- } else {
- // Generate the visibility rect immediately.
- _generate_visibility_rect();
- }
- } break;
- case MENU_LOAD_EMISSION_MASK: {
- file->popup_file_dialog();
-
- } break;
- case MENU_CLEAR_EMISSION_MASK: {
- emission_mask->popup_centered();
- } break;
- case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: {
- CPUParticles2D *cpu_particles = memnew(CPUParticles2D);
- cpu_particles->convert_from_particles(particles);
- cpu_particles->set_name(particles->get_name());
- cpu_particles->set_transform(particles->get_transform());
- cpu_particles->set_visible(particles->is_visible());
- cpu_particles->set_process_mode(particles->get_process_mode());
- cpu_particles->set_z_index(particles->get_z_index());
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Convert to CPUParticles2D"), UndoRedo::MERGE_DISABLE, particles);
- SceneTreeDock::get_singleton()->replace_node(particles, cpu_particles);
- ur->commit_action(false);
-
- } break;
- case MENU_RESTART: {
- particles->restart();
- }
- }
-}
-
-void GPUParticles2DEditorPlugin::_generate_visibility_rect() {
- double time = generate_seconds->get_value();
-
- float running = 0.0;
-
- EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time));
-
- bool was_emitting = particles->is_emitting();
- if (!was_emitting) {
- particles->set_emitting(true);
- OS::get_singleton()->delay_usec(1000);
- }
-
- Rect2 rect;
- while (running < time) {
- uint64_t ticks = OS::get_singleton()->get_ticks_usec();
- ep.step(TTR("Generating..."), int(running), true);
- OS::get_singleton()->delay_usec(1000);
-
- Rect2 capture = particles->capture_rect();
- if (rect == Rect2()) {
- rect = capture;
- } else {
- rect = rect.merge(capture);
- }
-
- running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
- }
-
- if (!was_emitting) {
- particles->set_emitting(false);
- }
-
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action(TTR("Generate Visibility Rect"));
- undo_redo->add_do_method(particles, "set_visibility_rect", rect);
- undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect());
- undo_redo->commit_action();
-}
-
-void GPUParticles2DEditorPlugin::_generate_emission_mask() {
- Ref<ParticleProcessMaterial> pm = particles->get_process_material();
- if (!pm.is_valid()) {
- EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticleProcessMaterial process material"));
- return;
- }
-
- Ref<Image> img;
- img.instantiate();
- Error err = ImageLoader::load_image(source_emission_file, img);
- ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
-
- if (img->is_compressed()) {
- img->decompress();
- }
- img->convert(Image::FORMAT_RGBA8);
- ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
- Size2i s = img->get_size();
- ERR_FAIL_COND(s.width == 0 || s.height == 0);
-
- Vector<Point2> valid_positions;
- Vector<Point2> valid_normals;
- Vector<uint8_t> valid_colors;
-
- valid_positions.resize(s.width * s.height);
-
- EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- valid_normals.resize(s.width * s.height);
- }
-
- bool capture_colors = emission_colors->is_pressed();
-
- if (capture_colors) {
- valid_colors.resize(s.width * s.height * 4);
- }
-
- int vpc = 0;
-
- {
- Vector<uint8_t> img_data = img->get_data();
- const uint8_t *r = img_data.ptr();
-
- for (int i = 0; i < s.width; i++) {
- for (int j = 0; j < s.height; j++) {
- uint8_t a = r[(j * s.width + i) * 4 + 3];
-
- if (a > 128) {
- if (emode == EMISSION_MODE_SOLID) {
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
- valid_positions.write[vpc++] = Point2(i, j);
-
- } else {
- bool on_border = false;
- for (int x = i - 1; x <= i + 1; x++) {
- for (int y = j - 1; y <= j + 1; y++) {
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- on_border = true;
- break;
- }
- }
-
- if (on_border) {
- break;
- }
- }
-
- if (on_border) {
- valid_positions.write[vpc] = Point2(i, j);
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- Vector2 normal;
- for (int x = i - 2; x <= i + 2; x++) {
- for (int y = j - 2; y <= j + 2; y++) {
- if (x == i && y == j) {
- continue;
- }
-
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- normal += Vector2(x - i, y - j).normalized();
- }
- }
- }
-
- normal.normalize();
- valid_normals.write[vpc] = normal;
- }
-
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
-
- vpc++;
- }
- }
- }
- }
- }
- }
-
- valid_positions.resize(vpc);
- if (valid_normals.size()) {
- valid_normals.resize(vpc);
- }
-
- ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
-
- Vector<uint8_t> texdata;
-
- int w = 2048;
- int h = (vpc / 2048) + 1;
-
- texdata.resize(w * h * 2 * sizeof(float));
-
- {
- Vector2 offset;
- if (emission_mask_centered->is_pressed()) {
- offset = Vector2(-s.width * 0.5, -s.height * 0.5);
- }
-
- uint8_t *tw = texdata.ptrw();
- float *twf = reinterpret_cast<float *>(tw);
- for (int i = 0; i < vpc; i++) {
- twf[i * 2 + 0] = valid_positions[i].x + offset.x;
- twf[i * 2 + 1] = valid_positions[i].y + offset.y;
- }
- }
-
- img.instantiate();
- img->set_data(w, h, false, Image::FORMAT_RGF, texdata);
- pm->set_emission_point_texture(ImageTexture::create_from_image(img));
- pm->set_emission_point_count(vpc);
-
- if (capture_colors) {
- Vector<uint8_t> colordata;
- colordata.resize(w * h * 4); //use RG texture
-
- {
- uint8_t *tw = colordata.ptrw();
- for (int i = 0; i < vpc * 4; i++) {
- tw[i] = valid_colors[i];
- }
- }
-
- img.instantiate();
- img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata);
- pm->set_emission_color_texture(ImageTexture::create_from_image(img));
- }
-
- if (valid_normals.size()) {
- pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
-
- Vector<uint8_t> normdata;
- normdata.resize(w * h * 2 * sizeof(float)); //use RG texture
-
- {
- uint8_t *tw = normdata.ptrw();
- float *twf = reinterpret_cast<float *>(tw);
- for (int i = 0; i < vpc; i++) {
- twf[i * 2 + 0] = valid_normals[i].x;
- twf[i * 2 + 1] = valid_normals[i].y;
- }
- }
-
- img.instantiate();
- img->set_data(w, h, false, Image::FORMAT_RGF, normdata);
- pm->set_emission_normal_texture(ImageTexture::create_from_image(img));
-
- } else {
- pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
- }
-}
-
-void GPUParticles2DEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback));
- menu->set_icon(menu->get_editor_theme_icon(SNAME("GPUParticles2D")));
- file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected));
- EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed));
- } break;
- }
-}
-
-GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() {
- particles = nullptr;
-
- toolbar = memnew(HBoxContainer);
- add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
- toolbar->hide();
-
- menu = memnew(MenuButton);
- menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART);
- menu->get_popup()->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT);
- menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
- // menu->get_popup()->add_item(TTR("Clear Emission Mask"), MENU_CLEAR_EMISSION_MASK);
- menu->get_popup()->add_item(TTR("Convert to CPUParticles2D"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES);
- menu->set_text(TTR("GPUParticles2D"));
- menu->set_switch_on_hover(true);
- toolbar->add_child(menu);
-
- file = memnew(EditorFileDialog);
- List<String> ext;
- ImageLoader::get_recognized_extensions(&ext);
- for (const String &E : ext) {
- file->add_filter("*." + E, E.to_upper());
- }
- file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
- toolbar->add_child(file);
-
- generate_visibility_rect = memnew(ConfirmationDialog);
- generate_visibility_rect->set_title(TTR("Generate Visibility Rect"));
- VBoxContainer *genvb = memnew(VBoxContainer);
- generate_visibility_rect->add_child(genvb);
- generate_seconds = memnew(SpinBox);
- genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
- generate_seconds->set_min(0.1);
- generate_seconds->set_max(25);
- generate_seconds->set_value(2);
-
- toolbar->add_child(generate_visibility_rect);
-
- generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect));
-
- emission_mask = memnew(ConfirmationDialog);
- emission_mask->set_title(TTR("Load Emission Mask"));
- VBoxContainer *emvb = memnew(VBoxContainer);
- emission_mask->add_child(emvb);
- emission_mask_mode = memnew(OptionButton);
- emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
- emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
- emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
- emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
- VBoxContainer *optionsvb = memnew(VBoxContainer);
- emvb->add_margin_child(TTR("Options"), optionsvb);
- emission_mask_centered = memnew(CheckBox);
- emission_mask_centered->set_text(TTR("Centered"));
- optionsvb->add_child(emission_mask_centered);
- emission_colors = memnew(CheckBox);
- emission_colors->set_text(TTR("Capture Colors from Pixel"));
- optionsvb->add_child(emission_colors);
-
- toolbar->add_child(emission_mask);
-
- emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_emission_mask));
-}
-
-GPUParticles2DEditorPlugin::~GPUParticles2DEditorPlugin() {
-}
diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.h b/editor/plugins/gpu_particles_2d_editor_plugin.h
deleted file mode 100644
index 658e4d87e5..0000000000
--- a/editor/plugins/gpu_particles_2d_editor_plugin.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_2d_editor_plugin.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef GPU_PARTICLES_2D_EDITOR_PLUGIN_H
-#define GPU_PARTICLES_2D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/editor_plugin.h"
-#include "scene/2d/gpu_particles_2d.h"
-#include "scene/2d/physics/collision_polygon_2d.h"
-#include "scene/gui/box_container.h"
-#include "scene/gui/spin_box.h"
-
-class CheckBox;
-class ConfirmationDialog;
-class EditorFileDialog;
-class MenuButton;
-class OptionButton;
-
-class GPUParticles2DEditorPlugin : public EditorPlugin {
- GDCLASS(GPUParticles2DEditorPlugin, EditorPlugin);
-
- enum {
- MENU_GENERATE_VISIBILITY_RECT,
- MENU_LOAD_EMISSION_MASK,
- MENU_CLEAR_EMISSION_MASK,
- MENU_OPTION_CONVERT_TO_CPU_PARTICLES,
- MENU_RESTART
- };
-
- enum EmissionMode {
- EMISSION_MODE_SOLID,
- EMISSION_MODE_BORDER,
- EMISSION_MODE_BORDER_DIRECTED
- };
-
- GPUParticles2D *particles = nullptr;
- List<GPUParticles2D *> selected_particles;
-
- EditorFileDialog *file = nullptr;
-
- HBoxContainer *toolbar = nullptr;
- MenuButton *menu = nullptr;
-
- ConfirmationDialog *generate_visibility_rect = nullptr;
- SpinBox *generate_seconds = nullptr;
-
- ConfirmationDialog *emission_mask = nullptr;
- OptionButton *emission_mask_mode = nullptr;
- CheckBox *emission_mask_centered = nullptr;
- CheckBox *emission_colors = nullptr;
-
- String source_emission_file;
-
- void _file_selected(const String &p_file);
- void _menu_callback(int p_idx);
- void _generate_visibility_rect();
- void _generate_emission_mask();
- void _selection_changed();
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual String get_name() const override { return "GPUParticles2D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- GPUParticles2DEditorPlugin();
- ~GPUParticles2DEditorPlugin();
-};
-
-#endif // GPU_PARTICLES_2D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
deleted file mode 100644
index e711f44ccf..0000000000
--- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp
+++ /dev/null
@@ -1,461 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_3d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "gpu_particles_3d_editor_plugin.h"
-
-#include "core/io/resource_loader.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-#include "editor/editor_undo_redo_manager.h"
-#include "editor/plugins/node_3d_editor_plugin.h"
-#include "editor/scene_tree_dock.h"
-#include "scene/3d/cpu_particles_3d.h"
-#include "scene/3d/mesh_instance_3d.h"
-#include "scene/gui/menu_button.h"
-#include "scene/resources/image_texture.h"
-#include "scene/resources/particle_process_material.h"
-
-bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3> &normals) {
- bool use_normals = emission_fill->get_selected() == 1;
-
- if (emission_fill->get_selected() < 2) {
- float area_accum = 0;
- RBMap<float, int> triangle_area_map;
-
- for (int i = 0; i < geometry.size(); i++) {
- float area = geometry[i].get_area();
- if (area < CMP_EPSILON) {
- continue;
- }
- triangle_area_map[area_accum] = i;
- area_accum += area;
- }
-
- if (!triangle_area_map.size() || area_accum == 0) {
- EditorNode::get_singleton()->show_warning(TTR("The geometry's faces don't contain any area."));
- return false;
- }
-
- int emissor_count = emission_amount->get_value();
-
- for (int i = 0; i < emissor_count; i++) {
- float areapos = Math::random(0.0f, area_accum);
-
- RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos);
- ERR_FAIL_COND_V(!E, false);
- int index = E->value;
- ERR_FAIL_INDEX_V(index, geometry.size(), false);
-
- // ok FINALLY get face
- Face3 face = geometry[index];
- //now compute some position inside the face...
-
- Vector3 pos = face.get_random_point_inside();
-
- points.push_back(pos);
-
- if (use_normals) {
- Vector3 normal = face.get_plane().normal;
- normals.push_back(normal);
- }
- }
- } else {
- int gcount = geometry.size();
-
- if (gcount == 0) {
- EditorNode::get_singleton()->show_warning(TTR("The geometry doesn't contain any faces."));
- return false;
- }
-
- const Face3 *r = geometry.ptr();
-
- AABB aabb;
-
- for (int i = 0; i < gcount; i++) {
- for (int j = 0; j < 3; j++) {
- if (i == 0 && j == 0) {
- aabb.position = r[i].vertex[j];
- } else {
- aabb.expand_to(r[i].vertex[j]);
- }
- }
- }
-
- int emissor_count = emission_amount->get_value();
-
- for (int i = 0; i < emissor_count; i++) {
- int attempts = 5;
-
- for (int j = 0; j < attempts; j++) {
- Vector3 dir;
- dir[Math::rand() % 3] = 1.0;
- Vector3 ofs = (Vector3(1, 1, 1) - dir) * Vector3(Math::randf(), Math::randf(), Math::randf()) * aabb.size + aabb.position;
-
- Vector3 ofsv = ofs + aabb.size * dir;
-
- //space it a little
- ofs -= dir;
- ofsv += dir;
-
- float max = -1e7, min = 1e7;
-
- for (int k = 0; k < gcount; k++) {
- const Face3 &f3 = r[k];
-
- Vector3 res;
- if (f3.intersects_segment(ofs, ofsv, &res)) {
- res -= ofs;
- float d = dir.dot(res);
-
- if (d < min) {
- min = d;
- }
- if (d > max) {
- max = d;
- }
- }
- }
-
- if (max < min) {
- continue; //lost attempt
- }
-
- float val = min + (max - min) * Math::randf();
-
- Vector3 point = ofs + dir * val;
-
- points.push_back(point);
- break;
- }
- }
- }
-
- return true;
-}
-
-void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) {
- Node *sel = get_node(p_path);
- if (!sel) {
- return;
- }
-
- if (!sel->is_class("Node3D")) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't inherit from Node3D."), sel->get_name()));
- return;
- }
-
- MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel);
- if (!mi || mi->get_mesh().is_null()) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name()));
- return;
- }
-
- geometry = mi->get_mesh()->get_faces();
-
- if (geometry.size() == 0) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name()));
- return;
- }
-
- Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * mi->get_global_transform();
-
- int gc = geometry.size();
- Face3 *w = geometry.ptrw();
-
- for (int i = 0; i < gc; i++) {
- for (int j = 0; j < 3; j++) {
- w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]);
- }
- }
-
- emission_dialog->popup_centered(Size2(300, 130));
-}
-
-GPUParticles3DEditorBase::GPUParticles3DEditorBase() {
- emission_dialog = memnew(ConfirmationDialog);
- emission_dialog->set_title(TTR("Create Emitter"));
- add_child(emission_dialog);
- VBoxContainer *emd_vb = memnew(VBoxContainer);
- emission_dialog->add_child(emd_vb);
-
- emission_amount = memnew(SpinBox);
- emission_amount->set_min(1);
- emission_amount->set_max(100000);
- emission_amount->set_value(512);
- emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount);
-
- emission_fill = memnew(OptionButton);
- emission_fill->add_item(TTR("Surface Points"));
- emission_fill->add_item(TTR("Surface Points+Normal (Directed)"));
- emission_fill->add_item(TTR("Volume"));
- emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill);
-
- emission_dialog->set_ok_button_text(TTR("Create"));
- emission_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles3DEditorBase::_generate_emission_points));
-
- emission_tree_dialog = memnew(SceneTreeDialog);
- Vector<StringName> valid_types;
- valid_types.push_back("MeshInstance3D");
- emission_tree_dialog->set_valid_types(valid_types);
- add_child(emission_tree_dialog);
- emission_tree_dialog->connect("selected", callable_mp(this, &GPUParticles3DEditorBase::_node_selected));
-}
-
-void GPUParticles3DEditor::_node_removed(Node *p_node) {
- if (p_node == node) {
- node = nullptr;
- hide();
- }
-}
-
-void GPUParticles3DEditor::_notification(int p_notification) {
- switch (p_notification) {
- case NOTIFICATION_ENTER_TREE: {
- options->set_icon(options->get_popup()->get_editor_theme_icon(SNAME("GPUParticles3D")));
- get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed));
- } break;
- }
-}
-
-void GPUParticles3DEditor::_menu_option(int p_option) {
- switch (p_option) {
- case MENU_OPTION_GENERATE_AABB: {
- // Add one second to the default generation lifetime, since the progress is updated every second.
- generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0));
-
- if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) {
- // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
- generate_aabb->popup_centered();
- } else {
- // Generate the visibility AABB immediately.
- _generate_aabb();
- }
- } break;
- case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
- Ref<ParticleProcessMaterial> mat = node->get_process_material();
- if (mat.is_null()) {
- EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required."));
- return;
- }
-
- emission_tree_dialog->popup_scenetree_dialog();
-
- } break;
- case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: {
- CPUParticles3D *cpu_particles = memnew(CPUParticles3D);
- cpu_particles->convert_from_particles(node);
- cpu_particles->set_name(node->get_name());
- cpu_particles->set_transform(node->get_transform());
- cpu_particles->set_visible(node->is_visible());
- cpu_particles->set_process_mode(node->get_process_mode());
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Convert to CPUParticles3D"), UndoRedo::MERGE_DISABLE, node);
- SceneTreeDock::get_singleton()->replace_node(node, cpu_particles);
- ur->commit_action(false);
-
- } break;
- case MENU_OPTION_RESTART: {
- node->restart();
-
- } break;
- }
-}
-
-void GPUParticles3DEditor::_generate_aabb() {
- double time = generate_seconds->get_value();
-
- double running = 0.0;
-
- EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time));
-
- bool was_emitting = node->is_emitting();
- if (!was_emitting) {
- node->set_emitting(true);
- OS::get_singleton()->delay_usec(1000);
- }
-
- AABB rect;
-
- while (running < time) {
- uint64_t ticks = OS::get_singleton()->get_ticks_usec();
- ep.step(TTR("Generating..."), int(running), true);
- OS::get_singleton()->delay_usec(1000);
-
- AABB capture = node->capture_aabb();
- if (rect == AABB()) {
- rect = capture;
- } else {
- rect.merge_with(capture);
- }
-
- running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
- }
-
- if (!was_emitting) {
- node->set_emitting(false);
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Generate Visibility AABB"));
- ur->add_do_method(node, "set_visibility_aabb", rect);
- ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb());
- ur->commit_action();
-}
-
-void GPUParticles3DEditor::edit(GPUParticles3D *p_particles) {
- base_node = p_particles;
- node = p_particles;
-}
-
-void GPUParticles3DEditor::_generate_emission_points() {
- /// hacer codigo aca
- Vector<Vector3> points;
- Vector<Vector3> normals;
-
- if (!_generate(points, normals)) {
- return;
- }
-
- int point_count = points.size();
-
- int w = 2048;
- int h = (point_count / 2048) + 1;
-
- Vector<uint8_t> point_img;
- point_img.resize(w * h * 3 * sizeof(float));
-
- {
- uint8_t *iw = point_img.ptrw();
- memset(iw, 0, w * h * 3 * sizeof(float));
- const Vector3 *r = points.ptr();
- float *wf = reinterpret_cast<float *>(iw);
- for (int i = 0; i < point_count; i++) {
- wf[i * 3 + 0] = r[i].x;
- wf[i * 3 + 1] = r[i].y;
- wf[i * 3 + 2] = r[i].z;
- }
- }
-
- Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img));
- Ref<ImageTexture> tex = ImageTexture::create_from_image(image);
-
- Ref<ParticleProcessMaterial> mat = node->get_process_material();
- ERR_FAIL_COND(mat.is_null());
-
- if (normals.size() > 0) {
- mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
- mat->set_emission_point_count(point_count);
- mat->set_emission_point_texture(tex);
-
- Vector<uint8_t> point_img2;
- point_img2.resize(w * h * 3 * sizeof(float));
-
- {
- uint8_t *iw = point_img2.ptrw();
- memset(iw, 0, w * h * 3 * sizeof(float));
- const Vector3 *r = normals.ptr();
- float *wf = reinterpret_cast<float *>(iw);
- for (int i = 0; i < point_count; i++) {
- wf[i * 3 + 0] = r[i].x;
- wf[i * 3 + 1] = r[i].y;
- wf[i * 3 + 2] = r[i].z;
- }
- }
-
- Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2));
- mat->set_emission_normal_texture(ImageTexture::create_from_image(image2));
- } else {
- mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
- mat->set_emission_point_count(point_count);
- mat->set_emission_point_texture(tex);
- }
-}
-
-GPUParticles3DEditor::GPUParticles3DEditor() {
- node = nullptr;
- particles_editor_hb = memnew(HBoxContainer);
- Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb);
- options = memnew(MenuButton);
- options->set_switch_on_hover(true);
- particles_editor_hb->add_child(options);
- particles_editor_hb->hide();
-
- options->set_text(TTR("GPUParticles3D"));
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_OPTION_RESTART);
- options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB);
- 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 CPUParticles3D"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES);
-
- options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GPUParticles3DEditor::_menu_option));
-
- generate_aabb = memnew(ConfirmationDialog);
- generate_aabb->set_title(TTR("Generate Visibility AABB"));
- VBoxContainer *genvb = memnew(VBoxContainer);
- generate_aabb->add_child(genvb);
- generate_seconds = memnew(SpinBox);
- genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
- generate_seconds->set_min(0.1);
- generate_seconds->set_max(25);
- generate_seconds->set_value(2);
-
- add_child(generate_aabb);
-
- generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles3DEditor::_generate_aabb));
-}
-
-void GPUParticles3DEditorPlugin::edit(Object *p_object) {
- particles_editor->edit(Object::cast_to<GPUParticles3D>(p_object));
-}
-
-bool GPUParticles3DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("GPUParticles3D");
-}
-
-void GPUParticles3DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- particles_editor->show();
- particles_editor->particles_editor_hb->show();
- } else {
- particles_editor->particles_editor_hb->hide();
- particles_editor->hide();
- particles_editor->edit(nullptr);
- }
-}
-
-GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() {
- particles_editor = memnew(GPUParticles3DEditor);
- EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
-
- particles_editor->hide();
-}
-
-GPUParticles3DEditorPlugin::~GPUParticles3DEditorPlugin() {
-}
diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.h b/editor/plugins/gpu_particles_3d_editor_plugin.h
deleted file mode 100644
index 1295836b5f..0000000000
--- a/editor/plugins/gpu_particles_3d_editor_plugin.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_3d_editor_plugin.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef GPU_PARTICLES_3D_EDITOR_PLUGIN_H
-#define GPU_PARTICLES_3D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/editor_plugin.h"
-#include "scene/3d/gpu_particles_3d.h"
-#include "scene/gui/spin_box.h"
-
-class ConfirmationDialog;
-class HBoxContainer;
-class MenuButton;
-class OptionButton;
-class SceneTreeDialog;
-
-class GPUParticles3DEditorBase : public Control {
- GDCLASS(GPUParticles3DEditorBase, Control);
-
-protected:
- Node3D *base_node = nullptr;
- Panel *panel = nullptr;
- MenuButton *options = nullptr;
- HBoxContainer *particles_editor_hb = nullptr;
-
- SceneTreeDialog *emission_tree_dialog = nullptr;
-
- ConfirmationDialog *emission_dialog = nullptr;
- SpinBox *emission_amount = nullptr;
- OptionButton *emission_fill = nullptr;
-
- Vector<Face3> geometry;
-
- bool _generate(Vector<Vector3> &points, Vector<Vector3> &normals);
- virtual void _generate_emission_points() {}
- void _node_selected(const NodePath &p_path);
-
-public:
- GPUParticles3DEditorBase();
-};
-
-class GPUParticles3DEditor : public GPUParticles3DEditorBase {
- GDCLASS(GPUParticles3DEditor, GPUParticles3DEditorBase);
-
- ConfirmationDialog *generate_aabb = nullptr;
- SpinBox *generate_seconds = nullptr;
- GPUParticles3D *node = nullptr;
-
- enum Menu {
- MENU_OPTION_GENERATE_AABB,
- MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
- MENU_OPTION_CLEAR_EMISSION_VOLUME,
- MENU_OPTION_CONVERT_TO_CPU_PARTICLES,
- MENU_OPTION_RESTART,
-
- };
-
- void _generate_aabb();
-
- void _menu_option(int);
-
- friend class GPUParticles3DEditorPlugin;
-
- virtual void _generate_emission_points() override;
-
-protected:
- void _notification(int p_notification);
- void _node_removed(Node *p_node);
-
-public:
- void edit(GPUParticles3D *p_particles);
- GPUParticles3DEditor();
-};
-
-class GPUParticles3DEditorPlugin : public EditorPlugin {
- GDCLASS(GPUParticles3DEditorPlugin, EditorPlugin);
-
- GPUParticles3DEditor *particles_editor = nullptr;
-
-public:
- virtual String get_name() const override { return "GPUParticles3D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- GPUParticles3DEditorPlugin();
- ~GPUParticles3DEditorPlugin();
-};
-
-#endif // GPU_PARTICLES_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 0cf194b7fe..0df0274495 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -673,6 +673,7 @@ void Node3DEditorViewport::cancel_transform() {
sp->set_global_transform(se->original);
}
+ collision_reposition = false;
finish_transform();
set_message(TTR("Transform Aborted."), 3);
}
@@ -1802,7 +1803,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (b->is_pressed()) {
clicked_wants_append = b->is_shift_pressed();
- if (_edit.mode != TRANSFORM_NONE && _edit.instant) {
+ if (_edit.mode != TRANSFORM_NONE && (_edit.instant || collision_reposition)) {
commit_transform();
break; // just commit the edit, stop processing the event so we don't deselect the object
}
@@ -2398,10 +2399,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
cancel_transform();
}
if (!is_freelook_active() && !k->is_echo()) {
- if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) {
+ if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && (_edit.mode != TRANSFORM_TRANSLATE || collision_reposition)) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_TRANSLATE, true);
- } else if (_edit.instant) {
+ } else if (_edit.instant || collision_reposition) {
commit_transform();
begin_transform(TRANSFORM_TRANSLATE, true);
}
@@ -2409,7 +2410,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_ROTATE, true);
- } else if (_edit.instant) {
+ } else if (_edit.instant || collision_reposition) {
commit_transform();
begin_transform(TRANSFORM_ROTATE, true);
}
@@ -2417,11 +2418,23 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_SCALE, true);
- } else if (_edit.instant) {
+ } else if (_edit.instant || collision_reposition) {
commit_transform();
begin_transform(TRANSFORM_SCALE, true);
}
}
+ if (ED_IS_SHORTCUT("spatial_editor/collision_reposition", p_event) && editor_selection->get_selected_node_list().size() == 1 && !collision_reposition) {
+ if (_edit.mode == TRANSFORM_NONE || _edit.instant) {
+ if (_edit.mode == TRANSFORM_NONE) {
+ _compute_edit(_edit.mouse_pos);
+ } else {
+ commit_transform();
+ _compute_edit(_edit.mouse_pos);
+ }
+ _edit.mode = TRANSFORM_TRANSLATE;
+ collision_reposition = true;
+ }
+ }
}
// Freelook doesn't work in orthogonal mode.
@@ -3072,11 +3085,24 @@ void Node3DEditorViewport::_notification(int p_what) {
} break;
case NOTIFICATION_PHYSICS_PROCESS: {
+ if (collision_reposition) {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+
+ if (selection.size() == 1) {
+ Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get());
+ double snap = EDITOR_GET("interface/inspector/default_float_step");
+ int snap_step_decimals = Math::range_step_decimals(snap);
+ set_message(TTR("Translating:") + " (" + String::num(first_selected_node->get_global_position().x, snap_step_decimals) + ", " +
+ String::num(first_selected_node->get_global_position().y, snap_step_decimals) + ", " + String::num(first_selected_node->get_global_position().z, snap_step_decimals) + ")");
+ first_selected_node->set_global_position(spatial_editor->snap_point(_get_instance_position(_edit.mouse_pos, first_selected_node)));
+ }
+ }
+
if (!update_preview_node) {
return;
}
if (preview_node->is_inside_tree()) {
- preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos));
+ preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos, preview_node));
double snap = EDITOR_GET("interface/inspector/default_float_step");
int snap_step_decimals = Math::range_step_decimals(snap);
set_message(TTR("Instantiating:") + " (" + String::num(preview_node_pos.x, snap_step_decimals) + ", " +
@@ -3966,7 +3992,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
return;
}
- bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible;
+ bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition;
for (int i = 0; i < 3; i++) {
Transform3D axis_angle;
if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) {
@@ -3997,7 +4023,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
xform.orthonormalize();
xform.basis.scale(scale);
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);
- RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
+ RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
}
void Node3DEditorViewport::set_state(const Dictionary &p_state) {
@@ -4241,7 +4267,19 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node,
accept = p_accept;
}
-Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const {
+void _insert_rid_recursive(Node *node, HashSet<RID> &rids) {
+ CollisionObject3D *co = Object::cast_to<CollisionObject3D>(node);
+ if (co) {
+ rids.insert(co->get_rid());
+ }
+
+ for (int i = 0; i < node->get_child_count(); i++) {
+ Node *child = node->get_child(i);
+ _insert_rid_recursive(child, rids);
+ }
+}
+
+Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos, Node3D *p_node) const {
const float MAX_DISTANCE = 50.0;
const float FALLBACK_DISTANCE = 5.0;
@@ -4250,13 +4288,28 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const
PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();
+ HashSet<RID> rids;
+
+ if (!preview_node->is_inside_tree()) {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+
+ Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get());
+
+ Array children = first_selected_node->get_children();
+
+ if (first_selected_node) {
+ _insert_rid_recursive(first_selected_node, rids);
+ }
+ }
+
PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.exclude = rids;
ray_params.from = world_pos;
ray_params.to = world_pos + world_ray * camera->get_far();
PhysicsDirectSpaceState3D::RayResult result;
- if (ss->intersect_ray(ray_params, result) && preview_node->get_child_count() > 0) {
- // Calculate an offset for the `preview_node` such that the its bounding box is on top of and touching the contact surface's plane.
+ if (ss->intersect_ray(ray_params, result) && (preview_node->get_child_count() > 0 || !preview_node->is_inside_tree())) {
+ // Calculate an offset for the `p_node` such that the its bounding box is on top of and touching the contact surface's plane.
// Use the Gram-Schmidt process to get an orthonormal Basis aligned with the surface normal.
const Vector3 bb_basis_x = result.normal;
@@ -4271,10 +4324,10 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const
const Basis bb_basis = Basis(bb_basis_x, bb_basis_y, bb_basis_z);
// This normal-aligned Basis allows us to create an AABB that can fit on the surface plane as snugly as possible.
- const Transform3D bb_transform = Transform3D(bb_basis, preview_node->get_transform().origin);
- const AABB preview_node_bb = _calculate_spatial_bounds(preview_node, true, &bb_transform);
- // The x-axis's alignment with the surface normal also makes it trivial to get the distance from `preview_node`'s origin at (0, 0, 0) to the correct AABB face.
- const float offset_distance = -preview_node_bb.position.x;
+ const Transform3D bb_transform = Transform3D(bb_basis, p_node->get_transform().origin);
+ const AABB p_node_bb = _calculate_spatial_bounds(p_node, true, &bb_transform);
+ // The x-axis's alignment with the surface normal also makes it trivial to get the distance from `p_node`'s origin at (0, 0, 0) to the correct AABB face.
+ const float offset_distance = -p_node_bb.position.x;
// `result_offset` is in global space.
const Vector3 result_offset = result.position + result.normal * offset_distance;
@@ -4912,6 +4965,7 @@ void Node3DEditorViewport::commit_transform() {
}
undo_redo->commit_action();
+ collision_reposition = false;
finish_transform();
set_message("");
}
@@ -5509,6 +5563,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
ED_SHORTCUT("spatial_editor/instant_translate", TTR("Begin Translate Transformation"));
ED_SHORTCUT("spatial_editor/instant_rotate", TTR("Begin Rotate Transformation"));
ED_SHORTCUT("spatial_editor/instant_scale", TTR("Begin Scale Transformation"));
+ ED_SHORTCUT("spatial_editor/collision_reposition", TTR("Reposition Using Collisions"), KeyModifierMask::SHIFT | Key::G);
preview_camera = memnew(CheckBox);
preview_camera->set_text(TTR("Preview"));
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index a8cade36fd..1b03362606 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -245,6 +245,7 @@ private:
bool auto_orthogonal;
bool lock_rotation;
bool transform_gizmo_visible = true;
+ bool collision_reposition = false;
real_t gizmo_scale;
bool freelook_active;
@@ -338,7 +339,6 @@ private:
TRANSFORM_ROTATE,
TRANSFORM_TRANSLATE,
TRANSFORM_SCALE
-
};
enum TransformPlane {
TRANSFORM_VIEW,
@@ -471,7 +471,7 @@ private:
void _list_select(Ref<InputEventMouseButton> b);
Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const;
- Vector3 _get_instance_position(const Point2 &p_pos) const;
+ Vector3 _get_instance_position(const Point2 &p_pos, Node3D *p_node) const;
static AABB _calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level = false, const Transform3D *p_bounds_orientation = nullptr);
Node *_sanitize_preview_node(Node *p_node) const;
diff --git a/editor/plugins/particles_editor_plugin.cpp b/editor/plugins/particles_editor_plugin.cpp
new file mode 100644
index 0000000000..34f5dcf963
--- /dev/null
+++ b/editor/plugins/particles_editor_plugin.cpp
@@ -0,0 +1,968 @@
+/**************************************************************************/
+/* particles_editor_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "particles_editor_plugin.h"
+
+#include "canvas_item_editor_plugin.h"
+#include "core/io/image_loader.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.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/3d/cpu_particles_3d.h"
+#include "scene/3d/gpu_particles_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/separator.h"
+#include "scene/gui/spin_box.h"
+#include "scene/resources/image_texture.h"
+#include "scene/resources/particle_process_material.h"
+
+void ParticlesEditorPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (handled_type.ends_with("2D")) {
+ add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
+ } else if (handled_type.ends_with("3D")) {
+ add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, toolbar);
+ } else {
+ DEV_ASSERT(false);
+ }
+
+ menu->set_icon(menu->get_editor_theme_icon(handled_type));
+ menu->set_text(handled_type);
+
+ PopupMenu *popup = menu->get_popup();
+ popup->add_shortcut(ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R), MENU_RESTART);
+ _add_menu_options(popup);
+ popup->add_item(conversion_option_name, MENU_OPTION_CONVERT);
+ } break;
+ }
+}
+
+bool ParticlesEditorPlugin::need_show_lifetime_dialog(SpinBox *p_seconds) {
+ // Add one second to the default generation lifetime, since the progress is updated every second.
+ p_seconds->set_value(MAX(1.0, trunc(edited_node->get("lifetime").operator double()) + 1.0));
+
+ if (p_seconds->get_value() >= 11.0 + CMP_EPSILON) {
+ // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
+ return true;
+ } else {
+ // Generate the visibility rect/AABB immediately.
+ return false;
+ }
+}
+
+void ParticlesEditorPlugin::_menu_callback(int p_idx) {
+ switch (p_idx) {
+ case MENU_OPTION_CONVERT: {
+ Node *converted_node = _convert_particles();
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(conversion_option_name, UndoRedo::MERGE_DISABLE, edited_node);
+ SceneTreeDock::get_singleton()->replace_node(edited_node, converted_node);
+ ur->commit_action(false);
+ } break;
+
+ case MENU_RESTART: {
+ edited_node->call("restart");
+ }
+ }
+}
+
+void ParticlesEditorPlugin::edit(Object *p_object) {
+ edited_node = Object::cast_to<Node>(p_object);
+}
+
+bool ParticlesEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class(handled_type);
+}
+
+void ParticlesEditorPlugin::make_visible(bool p_visible) {
+ toolbar->set_visible(p_visible);
+}
+
+ParticlesEditorPlugin::ParticlesEditorPlugin() {
+ toolbar = memnew(HBoxContainer);
+ toolbar->hide();
+
+ menu = memnew(MenuButton);
+ menu->set_switch_on_hover(true);
+ toolbar->add_child(menu);
+ menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ParticlesEditorPlugin::_menu_callback));
+}
+
+// 2D /////////////////////////////////////////////
+
+void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
+ if (p_idx == MENU_GENERATE_VISIBILITY_RECT) {
+ if (need_show_lifetime_dialog(generate_seconds)) {
+ generate_visibility_rect->popup_centered();
+ } else {
+ _generate_visibility_rect();
+ }
+ } else {
+ Particles2DEditorPlugin::_menu_callback(p_idx);
+ }
+}
+
+void GPUParticles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
+ Particles2DEditorPlugin::_add_menu_options(p_menu);
+ p_menu->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT);
+}
+
+void Particles2DEditorPlugin::_file_selected(const String &p_file) {
+ source_emission_file = p_file;
+ emission_mask->popup_centered();
+}
+
+void Particles2DEditorPlugin::_get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size) {
+ Ref<Image> img;
+ img.instantiate();
+ Error err = ImageLoader::load_image(source_emission_file, img);
+ ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
+
+ if (img->is_compressed()) {
+ img->decompress();
+ }
+ img->convert(Image::FORMAT_RGBA8);
+ ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
+ Size2i s = img->get_size();
+ ERR_FAIL_COND(s.width == 0 || s.height == 0);
+
+ r_image_size = s;
+
+ r_valid_positions.resize(s.width * s.height);
+
+ EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
+
+ if (emode == EMISSION_MODE_BORDER_DIRECTED) {
+ r_valid_normals.resize(s.width * s.height);
+ }
+
+ bool capture_colors = emission_colors->is_pressed();
+
+ if (capture_colors) {
+ r_valid_colors.resize(s.width * s.height * 4);
+ }
+
+ int vpc = 0;
+
+ {
+ Vector<uint8_t> img_data = img->get_data();
+ const uint8_t *r = img_data.ptr();
+
+ for (int i = 0; i < s.width; i++) {
+ for (int j = 0; j < s.height; j++) {
+ uint8_t a = r[(j * s.width + i) * 4 + 3];
+
+ if (a > 128) {
+ if (emode == EMISSION_MODE_SOLID) {
+ if (capture_colors) {
+ r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
+ r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
+ r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
+ r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
+ }
+ r_valid_positions.write[vpc++] = Point2(i, j);
+
+ } else {
+ bool on_border = false;
+ for (int x = i - 1; x <= i + 1; x++) {
+ for (int y = j - 1; y <= j + 1; y++) {
+ if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
+ on_border = true;
+ break;
+ }
+ }
+
+ if (on_border) {
+ break;
+ }
+ }
+
+ if (on_border) {
+ r_valid_positions.write[vpc] = Point2(i, j);
+
+ if (emode == EMISSION_MODE_BORDER_DIRECTED) {
+ Vector2 normal;
+ for (int x = i - 2; x <= i + 2; x++) {
+ for (int y = j - 2; y <= j + 2; y++) {
+ if (x == i && y == j) {
+ continue;
+ }
+
+ if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
+ normal += Vector2(x - i, y - j).normalized();
+ }
+ }
+ }
+
+ normal.normalize();
+ r_valid_normals.write[vpc] = normal;
+ }
+
+ if (capture_colors) {
+ r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
+ r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
+ r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
+ r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
+ }
+
+ vpc++;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ r_valid_positions.resize(vpc);
+ if (!r_valid_normals.is_empty()) {
+ r_valid_normals.resize(vpc);
+ }
+}
+
+Particles2DEditorPlugin::Particles2DEditorPlugin() {
+ file = memnew(EditorFileDialog);
+
+ List<String> ext;
+ ImageLoader::get_recognized_extensions(&ext);
+ for (const String &E : ext) {
+ file->add_filter("*." + E, E.to_upper());
+ }
+
+ file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ EditorNode::get_singleton()->get_gui_base()->add_child(file);
+ file->connect("file_selected", callable_mp(this, &Particles2DEditorPlugin::_file_selected));
+
+ emission_mask = memnew(ConfirmationDialog);
+ emission_mask->set_title(TTR("Load Emission Mask"));
+
+ VBoxContainer *emvb = memnew(VBoxContainer);
+ emission_mask->add_child(emvb);
+
+ emission_mask_mode = memnew(OptionButton);
+ emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
+ emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
+ emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
+ emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
+
+ VBoxContainer *optionsvb = memnew(VBoxContainer);
+ emvb->add_margin_child(TTR("Options"), optionsvb);
+
+ emission_mask_centered = memnew(CheckBox(TTR("Centered")));
+ optionsvb->add_child(emission_mask_centered);
+ emission_colors = memnew(CheckBox(TTR("Capture Colors from Pixel")));
+ optionsvb->add_child(emission_colors);
+
+ EditorNode::get_singleton()->get_gui_base()->add_child(emission_mask);
+
+ emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &Particles2DEditorPlugin::_generate_emission_mask));
+}
+
+void GPUParticles2DEditorPlugin::_selection_changed() {
+ List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list();
+ if (selected_particles.is_empty() && selected_nodes.is_empty()) {
+ return;
+ }
+
+ for (GPUParticles2D *particles : selected_particles) {
+ particles->set_show_visibility_rect(false);
+ }
+ selected_particles.clear();
+
+ for (Node *node : selected_nodes) {
+ GPUParticles2D *selected_particle = Object::cast_to<GPUParticles2D>(node);
+ if (selected_particle) {
+ selected_particle->set_show_visibility_rect(true);
+ selected_particles.push_back(selected_particle);
+ }
+ }
+}
+
+void GPUParticles2DEditorPlugin::_generate_visibility_rect() {
+ GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
+
+ double time = generate_seconds->get_value();
+
+ float running = 0.0;
+
+ EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time));
+
+ bool was_emitting = particles->is_emitting();
+ if (!was_emitting) {
+ particles->set_emitting(true);
+ OS::get_singleton()->delay_usec(1000);
+ }
+
+ Rect2 rect;
+ while (running < time) {
+ uint64_t ticks = OS::get_singleton()->get_ticks_usec();
+ ep.step(TTR("Generating..."), int(running), true);
+ OS::get_singleton()->delay_usec(1000);
+
+ Rect2 capture = particles->capture_rect();
+ if (rect == Rect2()) {
+ rect = capture;
+ } else {
+ rect = rect.merge(capture);
+ }
+
+ running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
+ }
+
+ if (!was_emitting) {
+ particles->set_emitting(false);
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Generate Visibility Rect"));
+ undo_redo->add_do_method(particles, "set_visibility_rect", rect);
+ undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect());
+ undo_redo->commit_action();
+}
+
+void GPUParticles2DEditorPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed));
+ } break;
+ }
+}
+
+void Particles2DEditorPlugin::_menu_callback(int p_idx) {
+ if (p_idx == MENU_LOAD_EMISSION_MASK) {
+ file->popup_file_dialog();
+ } else {
+ ParticlesEditorPlugin::_menu_callback(p_idx);
+ }
+}
+
+void Particles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
+ p_menu->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
+}
+
+Node *GPUParticles2DEditorPlugin::_convert_particles() {
+ GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
+
+ CPUParticles2D *cpu_particles = memnew(CPUParticles2D);
+ cpu_particles->convert_from_particles(particles);
+ cpu_particles->set_name(particles->get_name());
+ cpu_particles->set_transform(particles->get_transform());
+ cpu_particles->set_visible(particles->is_visible());
+ cpu_particles->set_process_mode(particles->get_process_mode());
+ cpu_particles->set_z_index(particles->get_z_index());
+ return cpu_particles;
+}
+
+void GPUParticles2DEditorPlugin::_generate_emission_mask() {
+ GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
+ Ref<ParticleProcessMaterial> pm = particles->get_process_material();
+ if (pm.is_null()) {
+ EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticleProcessMaterial process material"));
+ return;
+ }
+
+ PackedVector2Array valid_positions;
+ PackedVector2Array valid_normals;
+ PackedByteArray valid_colors;
+ Vector2i image_size;
+ _get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
+
+ ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
+
+ Vector<uint8_t> texdata;
+
+ int vpc = valid_positions.size();
+ int w = 2048;
+ int h = (vpc / 2048) + 1;
+
+ texdata.resize(w * h * 2 * sizeof(float));
+
+ {
+ Vector2 offset;
+ if (emission_mask_centered->is_pressed()) {
+ offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5);
+ }
+
+ uint8_t *tw = texdata.ptrw();
+ float *twf = reinterpret_cast<float *>(tw);
+ for (int i = 0; i < vpc; i++) {
+ twf[i * 2 + 0] = valid_positions[i].x + offset.x;
+ twf[i * 2 + 1] = valid_positions[i].y + offset.y;
+ }
+ }
+
+ Ref<Image> img;
+ img.instantiate();
+ img->set_data(w, h, false, Image::FORMAT_RGF, texdata);
+ pm->set_emission_point_texture(ImageTexture::create_from_image(img));
+ pm->set_emission_point_count(vpc);
+
+ if (emission_colors->is_pressed()) {
+ Vector<uint8_t> colordata;
+ colordata.resize(w * h * 4); //use RG texture
+
+ {
+ uint8_t *tw = colordata.ptrw();
+ for (int i = 0; i < vpc * 4; i++) {
+ tw[i] = valid_colors[i];
+ }
+ }
+
+ img.instantiate();
+ img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata);
+ pm->set_emission_color_texture(ImageTexture::create_from_image(img));
+ }
+
+ if (valid_normals.size()) {
+ pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
+
+ Vector<uint8_t> normdata;
+ normdata.resize(w * h * 2 * sizeof(float)); //use RG texture
+
+ {
+ uint8_t *tw = normdata.ptrw();
+ float *twf = reinterpret_cast<float *>(tw);
+ for (int i = 0; i < vpc; i++) {
+ twf[i * 2 + 0] = valid_normals[i].x;
+ twf[i * 2 + 1] = valid_normals[i].y;
+ }
+ }
+
+ img.instantiate();
+ img->set_data(w, h, false, Image::FORMAT_RGF, normdata);
+ pm->set_emission_normal_texture(ImageTexture::create_from_image(img));
+
+ } else {
+ pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
+ }
+}
+
+GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() {
+ handled_type = "GPUParticles2D"; // TTR("GPUParticles2D")
+ conversion_option_name = TTR("Convert to CPUParticles2D");
+
+ generate_visibility_rect = memnew(ConfirmationDialog);
+ generate_visibility_rect->set_title(TTR("Generate Visibility Rect"));
+
+ VBoxContainer *genvb = memnew(VBoxContainer);
+ generate_visibility_rect->add_child(genvb);
+
+ generate_seconds = memnew(SpinBox);
+ generate_seconds->set_min(0.1);
+ generate_seconds->set_max(25);
+ generate_seconds->set_value(2);
+ genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
+
+ EditorNode::get_singleton()->get_gui_base()->add_child(generate_visibility_rect);
+
+ generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect));
+}
+
+Node *CPUParticles2DEditorPlugin::_convert_particles() {
+ CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
+
+ 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());
+ return gpu_particles;
+}
+
+void CPUParticles2DEditorPlugin::_generate_emission_mask() {
+ CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
+
+ PackedVector2Array valid_positions;
+ PackedVector2Array valid_normals;
+ PackedByteArray valid_colors;
+ Vector2i image_size;
+ _get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
+
+ ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
+
+ int vpc = valid_positions.size();
+ if (emission_colors->is_pressed()) {
+ PackedColorArray pca;
+ pca.resize(vpc);
+ Color *pcaw = pca.ptrw();
+ for (int i = 0; i < vpc; i += 1) {
+ Color color;
+ color.r = valid_colors[i * 4 + 0] / 255.0f;
+ color.g = valid_colors[i * 4 + 1] / 255.0f;
+ color.b = valid_colors[i * 4 + 2] / 255.0f;
+ color.a = valid_colors[i * 4 + 3] / 255.0f;
+ pcaw[i] = color;
+ }
+ particles->set_emission_colors(pca);
+ }
+
+ if (valid_normals.size()) {
+ particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS);
+ PackedVector2Array norms;
+ norms.resize(valid_normals.size());
+ Vector2 *normsw = norms.ptrw();
+ for (int i = 0; i < valid_normals.size(); i += 1) {
+ normsw[i] = valid_normals[i];
+ }
+ particles->set_emission_normals(norms);
+ } else {
+ particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_POINTS);
+ }
+
+ {
+ Vector2 offset;
+ if (emission_mask_centered->is_pressed()) {
+ offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5);
+ }
+
+ PackedVector2Array points;
+ points.resize(valid_positions.size());
+ Vector2 *pointsw = points.ptrw();
+ for (int i = 0; i < valid_positions.size(); i += 1) {
+ pointsw[i] = valid_positions[i] + offset;
+ }
+ particles->set_emission_points(points);
+ }
+}
+
+CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
+ handled_type = "CPUParticles2D"; // TTR("CPUParticles2D")
+ conversion_option_name = TTR("Convert to GPUParticles2D");
+}
+
+// 3D /////////////////////////////////////////////
+
+void Particles3DEditorPlugin::_generate_aabb() {
+ double time = generate_seconds->get_value();
+
+ double running = 0.0;
+
+ EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time));
+
+ bool was_emitting = edited_node->get("emitting");
+ if (!was_emitting) {
+ edited_node->set("emitting", true);
+ OS::get_singleton()->delay_usec(1000);
+ }
+
+ AABB rect;
+ Callable capture_aabb = Callable(edited_node, "capture_aabb");
+
+ while (running < time) {
+ uint64_t ticks = OS::get_singleton()->get_ticks_usec();
+ ep.step(TTR("Generating..."), int(running), true);
+ OS::get_singleton()->delay_usec(1000);
+
+ AABB capture = capture_aabb.call();
+ if (rect == AABB()) {
+ rect = capture;
+ } else {
+ rect.merge_with(capture);
+ }
+
+ running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
+ }
+
+ if (!was_emitting) {
+ edited_node->set("emitting", false);
+ }
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Generate Visibility AABB"));
+ ur->add_do_property(edited_node, "visibility_aabb", rect);
+ ur->add_undo_property(edited_node, "visibility_aabb", edited_node->get("visibility_aabb"));
+ ur->commit_action();
+}
+
+void Particles3DEditorPlugin::_node_selected(const NodePath &p_path) {
+ Node *sel = get_node(p_path);
+ if (!sel) {
+ return;
+ }
+
+ if (!sel->is_class("Node3D")) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't inherit from Node3D."), sel->get_name()));
+ return;
+ }
+
+ MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel);
+ if (!mi || mi->get_mesh().is_null()) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name()));
+ return;
+ }
+
+ geometry = mi->get_mesh()->get_faces();
+ if (geometry.size() == 0) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name()));
+ return;
+ }
+
+ Transform3D geom_xform = edited_node->get("global_transform");
+ geom_xform = geom_xform.affine_inverse() * mi->get_global_transform();
+ int gc = geometry.size();
+ Face3 *w = geometry.ptrw();
+
+ for (int i = 0; i < gc; i++) {
+ for (int j = 0; j < 3; j++) {
+ w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]);
+ }
+ }
+ emission_dialog->popup_centered(Size2(300, 130));
+}
+
+void Particles3DEditorPlugin::_menu_callback(int p_idx) {
+ switch (p_idx) {
+ case MENU_OPTION_GENERATE_AABB: {
+ if (need_show_lifetime_dialog(generate_seconds)) {
+ generate_aabb->popup_centered();
+ } else {
+ _generate_aabb();
+ }
+ } break;
+
+ case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
+ if (_can_generate_points()) {
+ emission_tree_dialog->popup_scenetree_dialog();
+ }
+ } break;
+
+ default: {
+ ParticlesEditorPlugin::_menu_callback(p_idx);
+ }
+ }
+}
+
+void Particles3DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
+ p_menu->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB);
+ p_menu->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
+}
+
+bool Particles3DEditorPlugin::_generate(Vector<Vector3> &r_points, Vector<Vector3> &r_normals) {
+ bool use_normals = emission_fill->get_selected() == 1;
+
+ if (emission_fill->get_selected() < 2) {
+ float area_accum = 0;
+ RBMap<float, int> triangle_area_map;
+
+ for (int i = 0; i < geometry.size(); i++) {
+ float area = geometry[i].get_area();
+ if (area < CMP_EPSILON) {
+ continue;
+ }
+ triangle_area_map[area_accum] = i;
+ area_accum += area;
+ }
+
+ if (!triangle_area_map.size() || area_accum == 0) {
+ EditorNode::get_singleton()->show_warning(TTR("The geometry's faces don't contain any area."));
+ return false;
+ }
+
+ int emissor_count = emission_amount->get_value();
+
+ for (int i = 0; i < emissor_count; i++) {
+ float areapos = Math::random(0.0f, area_accum);
+
+ RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos);
+ ERR_FAIL_COND_V(!E, false);
+ int index = E->value;
+ ERR_FAIL_INDEX_V(index, geometry.size(), false);
+
+ // ok FINALLY get face
+ Face3 face = geometry[index];
+ //now compute some position inside the face...
+
+ Vector3 pos = face.get_random_point_inside();
+
+ r_points.push_back(pos);
+
+ if (use_normals) {
+ Vector3 normal = face.get_plane().normal;
+ r_normals.push_back(normal);
+ }
+ }
+ } else {
+ int gcount = geometry.size();
+
+ if (gcount == 0) {
+ EditorNode::get_singleton()->show_warning(TTR("The geometry doesn't contain any faces."));
+ return false;
+ }
+
+ const Face3 *r = geometry.ptr();
+
+ AABB aabb;
+
+ for (int i = 0; i < gcount; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (i == 0 && j == 0) {
+ aabb.position = r[i].vertex[j];
+ } else {
+ aabb.expand_to(r[i].vertex[j]);
+ }
+ }
+ }
+
+ int emissor_count = emission_amount->get_value();
+
+ for (int i = 0; i < emissor_count; i++) {
+ int attempts = 5;
+
+ for (int j = 0; j < attempts; j++) {
+ Vector3 dir;
+ dir[Math::rand() % 3] = 1.0;
+ Vector3 ofs = (Vector3(1, 1, 1) - dir) * Vector3(Math::randf(), Math::randf(), Math::randf()) * aabb.size + aabb.position;
+
+ Vector3 ofsv = ofs + aabb.size * dir;
+
+ //space it a little
+ ofs -= dir;
+ ofsv += dir;
+
+ float max = -1e7, min = 1e7;
+
+ for (int k = 0; k < gcount; k++) {
+ const Face3 &f3 = r[k];
+
+ Vector3 res;
+ if (f3.intersects_segment(ofs, ofsv, &res)) {
+ res -= ofs;
+ float d = dir.dot(res);
+
+ if (d < min) {
+ min = d;
+ }
+ if (d > max) {
+ max = d;
+ }
+ }
+ }
+
+ if (max < min) {
+ continue; //lost attempt
+ }
+
+ float val = min + (max - min) * Math::randf();
+
+ Vector3 point = ofs + dir * val;
+
+ r_points.push_back(point);
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+Particles3DEditorPlugin::Particles3DEditorPlugin() {
+ generate_aabb = memnew(ConfirmationDialog);
+ generate_aabb->set_title(TTR("Generate Visibility AABB"));
+
+ VBoxContainer *genvb = memnew(VBoxContainer);
+ generate_aabb->add_child(genvb);
+
+ generate_seconds = memnew(SpinBox);
+ generate_seconds->set_min(0.1);
+ generate_seconds->set_max(25);
+ generate_seconds->set_value(2);
+ genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
+
+ EditorNode::get_singleton()->get_gui_base()->add_child(generate_aabb);
+
+ generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &Particles3DEditorPlugin::_generate_aabb));
+
+ emission_tree_dialog = memnew(SceneTreeDialog);
+ Vector<StringName> valid_types;
+ valid_types.push_back("MeshInstance3D");
+ emission_tree_dialog->set_valid_types(valid_types);
+ EditorNode::get_singleton()->get_gui_base()->add_child(emission_tree_dialog);
+ emission_tree_dialog->connect("selected", callable_mp(this, &Particles3DEditorPlugin::_node_selected));
+
+ emission_dialog = memnew(ConfirmationDialog);
+ emission_dialog->set_title(TTR("Create Emitter"));
+ EditorNode::get_singleton()->get_gui_base()->add_child(emission_dialog);
+
+ VBoxContainer *emd_vb = memnew(VBoxContainer);
+ emission_dialog->add_child(emd_vb);
+
+ emission_amount = memnew(SpinBox);
+ emission_amount->set_min(1);
+ emission_amount->set_max(100000);
+ emission_amount->set_value(512);
+ emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount);
+
+ emission_fill = memnew(OptionButton);
+ emission_fill->add_item(TTR("Surface Points"));
+ emission_fill->add_item(TTR("Surface Points+Normal (Directed)"));
+ emission_fill->add_item(TTR("Volume"));
+ emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill);
+
+ emission_dialog->set_ok_button_text(TTR("Create"));
+ emission_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Particles3DEditorPlugin::_generate_emission_points));
+}
+
+Node *GPUParticles3DEditorPlugin::_convert_particles() {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node);
+
+ CPUParticles3D *cpu_particles = memnew(CPUParticles3D);
+ cpu_particles->convert_from_particles(particles);
+ cpu_particles->set_name(particles->get_name());
+ cpu_particles->set_transform(particles->get_transform());
+ cpu_particles->set_visible(particles->is_visible());
+ cpu_particles->set_process_mode(particles->get_process_mode());
+ return cpu_particles;
+}
+
+bool GPUParticles3DEditorPlugin::_can_generate_points() const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node);
+ Ref<ParticleProcessMaterial> mat = particles->get_process_material();
+ if (mat.is_null()) {
+ EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required."));
+ return false;
+ }
+ return true;
+}
+
+void GPUParticles3DEditorPlugin::_generate_emission_points() {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node);
+
+ /// hacer codigo aca
+ Vector<Vector3> points;
+ Vector<Vector3> normals;
+
+ if (!_generate(points, normals)) {
+ return;
+ }
+
+ int point_count = points.size();
+
+ int w = 2048;
+ int h = (point_count / 2048) + 1;
+
+ Vector<uint8_t> point_img;
+ point_img.resize(w * h * 3 * sizeof(float));
+
+ {
+ uint8_t *iw = point_img.ptrw();
+ memset(iw, 0, w * h * 3 * sizeof(float));
+ const Vector3 *r = points.ptr();
+ float *wf = reinterpret_cast<float *>(iw);
+ for (int i = 0; i < point_count; i++) {
+ wf[i * 3 + 0] = r[i].x;
+ wf[i * 3 + 1] = r[i].y;
+ wf[i * 3 + 2] = r[i].z;
+ }
+ }
+
+ Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img));
+ Ref<ImageTexture> tex = ImageTexture::create_from_image(image);
+
+ Ref<ParticleProcessMaterial> mat = particles->get_process_material();
+ ERR_FAIL_COND(mat.is_null());
+
+ if (normals.size() > 0) {
+ mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
+ mat->set_emission_point_count(point_count);
+ mat->set_emission_point_texture(tex);
+
+ Vector<uint8_t> point_img2;
+ point_img2.resize(w * h * 3 * sizeof(float));
+
+ {
+ uint8_t *iw = point_img2.ptrw();
+ memset(iw, 0, w * h * 3 * sizeof(float));
+ const Vector3 *r = normals.ptr();
+ float *wf = reinterpret_cast<float *>(iw);
+ for (int i = 0; i < point_count; i++) {
+ wf[i * 3 + 0] = r[i].x;
+ wf[i * 3 + 1] = r[i].y;
+ wf[i * 3 + 2] = r[i].z;
+ }
+ }
+
+ Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2));
+ mat->set_emission_normal_texture(ImageTexture::create_from_image(image2));
+ } else {
+ mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
+ mat->set_emission_point_count(point_count);
+ mat->set_emission_point_texture(tex);
+ }
+}
+
+GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() {
+ handled_type = "GPUParticles3D"; // TTR("GPUParticles3D")
+ conversion_option_name = TTR("Convert to CPUParticles3D");
+}
+
+Node *CPUParticles3DEditorPlugin::_convert_particles() {
+ CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(edited_node);
+
+ GPUParticles3D *gpu_particles = memnew(GPUParticles3D);
+ 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());
+ return gpu_particles;
+}
+
+void CPUParticles3DEditorPlugin::_generate_emission_points() {
+ CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(edited_node);
+
+ /// hacer codigo aca
+ Vector<Vector3> points;
+ Vector<Vector3> normals;
+
+ if (!_generate(points, normals)) {
+ return;
+ }
+
+ if (normals.is_empty()) {
+ particles->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_POINTS);
+ particles->set_emission_points(points);
+ } else {
+ particles->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_DIRECTED_POINTS);
+ particles->set_emission_points(points);
+ particles->set_emission_normals(normals);
+ }
+}
+
+CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() {
+ handled_type = "CPUParticles3D"; // TTR("CPUParticles3D")
+ conversion_option_name = TTR("Convert to GPUParticles3D");
+}
diff --git a/editor/plugins/particles_editor_plugin.h b/editor/plugins/particles_editor_plugin.h
new file mode 100644
index 0000000000..d4459765c4
--- /dev/null
+++ b/editor/plugins/particles_editor_plugin.h
@@ -0,0 +1,216 @@
+/**************************************************************************/
+/* particles_editor_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef PARTICLES_EDITOR_PLUGIN_H
+#define PARTICLES_EDITOR_PLUGIN_H
+
+#include "editor/plugins/editor_plugin.h"
+
+class CheckBox;
+class ConfirmationDialog;
+class EditorFileDialog;
+class GPUParticles2D;
+class HBoxContainer;
+class MenuButton;
+class OptionButton;
+class SceneTreeDialog;
+class SpinBox;
+
+class ParticlesEditorPlugin : public EditorPlugin {
+ GDCLASS(ParticlesEditorPlugin, EditorPlugin);
+
+private:
+ enum {
+ MENU_OPTION_CONVERT,
+ MENU_RESTART
+ };
+
+ HBoxContainer *toolbar = nullptr;
+ MenuButton *menu = nullptr;
+
+protected:
+ String handled_type;
+ String conversion_option_name;
+
+ Node *edited_node = nullptr;
+
+ void _notification(int p_what);
+
+ bool need_show_lifetime_dialog(SpinBox *p_seconds);
+ virtual void _menu_callback(int p_idx);
+
+ virtual void _add_menu_options(PopupMenu *p_menu) {}
+ virtual Node *_convert_particles() = 0;
+
+public:
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+ virtual void make_visible(bool p_visible) override;
+
+ ParticlesEditorPlugin();
+};
+
+// 2D /////////////////////////////////////////////
+
+class Particles2DEditorPlugin : public ParticlesEditorPlugin {
+ GDCLASS(Particles2DEditorPlugin, ParticlesEditorPlugin);
+
+protected:
+ enum {
+ MENU_LOAD_EMISSION_MASK = 100,
+ };
+
+ enum EmissionMode {
+ EMISSION_MODE_SOLID,
+ EMISSION_MODE_BORDER,
+ EMISSION_MODE_BORDER_DIRECTED
+ };
+
+ EditorFileDialog *file = nullptr;
+ ConfirmationDialog *emission_mask = nullptr;
+ OptionButton *emission_mask_mode = nullptr;
+ CheckBox *emission_mask_centered = nullptr;
+ CheckBox *emission_colors = nullptr;
+ String source_emission_file;
+
+ virtual void _menu_callback(int p_idx) override;
+ virtual void _add_menu_options(PopupMenu *p_menu) override;
+
+ void _file_selected(const String &p_file);
+ void _get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size);
+ virtual void _generate_emission_mask() = 0;
+
+public:
+ Particles2DEditorPlugin();
+};
+
+class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
+ GDCLASS(GPUParticles2DEditorPlugin, Particles2DEditorPlugin);
+
+ enum {
+ MENU_GENERATE_VISIBILITY_RECT = 200,
+ };
+
+ List<GPUParticles2D *> selected_particles;
+
+ ConfirmationDialog *generate_visibility_rect = nullptr;
+ SpinBox *generate_seconds = nullptr;
+
+ void _selection_changed();
+ void _generate_visibility_rect();
+
+protected:
+ void _notification(int p_what);
+
+ void _menu_callback(int p_idx) override;
+ void _add_menu_options(PopupMenu *p_menu) override;
+
+ Node *_convert_particles() override;
+
+ void _generate_emission_mask() override;
+
+public:
+ GPUParticles2DEditorPlugin();
+};
+
+class CPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
+ GDCLASS(CPUParticles2DEditorPlugin, Particles2DEditorPlugin);
+
+protected:
+ Node *_convert_particles() override;
+
+ void _generate_emission_mask() override;
+
+public:
+ CPUParticles2DEditorPlugin();
+};
+
+// 3D /////////////////////////////////////////////
+
+class Particles3DEditorPlugin : public ParticlesEditorPlugin {
+ GDCLASS(Particles3DEditorPlugin, ParticlesEditorPlugin);
+
+ enum {
+ MENU_OPTION_GENERATE_AABB = 300,
+ MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
+ };
+
+ ConfirmationDialog *generate_aabb = nullptr;
+ SpinBox *generate_seconds = nullptr;
+
+ SceneTreeDialog *emission_tree_dialog = nullptr;
+ ConfirmationDialog *emission_dialog = nullptr;
+ SpinBox *emission_amount = nullptr;
+ OptionButton *emission_fill = nullptr;
+
+ void _generate_aabb();
+ void _node_selected(const NodePath &p_path);
+
+protected:
+ Vector<Face3> geometry;
+
+ virtual void _menu_callback(int p_idx) override;
+ virtual void _add_menu_options(PopupMenu *p_menu) override;
+
+ bool _generate(Vector<Vector3> &r_points, Vector<Vector3> &r_normals);
+ virtual bool _can_generate_points() const = 0;
+ virtual void _generate_emission_points() = 0;
+
+public:
+ Particles3DEditorPlugin();
+};
+
+class GPUParticles3DEditorPlugin : public Particles3DEditorPlugin {
+ GDCLASS(GPUParticles3DEditorPlugin, Particles3DEditorPlugin);
+
+protected:
+ Node *_convert_particles() override;
+
+ bool _can_generate_points() const override;
+ void _generate_emission_points() override;
+
+public:
+ GPUParticles3DEditorPlugin();
+};
+
+class CPUParticles3DEditorPlugin : public Particles3DEditorPlugin {
+ GDCLASS(CPUParticles3DEditorPlugin, Particles3DEditorPlugin);
+
+protected:
+ Node *_convert_particles() override;
+
+ bool _can_generate_points() const override { return true; }
+ void _generate_emission_points() override;
+
+public:
+ CPUParticles3DEditorPlugin();
+};
+
+#endif // PARTICLES_EDITOR_PLUGIN_H
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 19aeeb0612..c4ebca7308 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -76,15 +76,11 @@
#include "editor/plugins/collision_polygon_2d_editor_plugin.h"
#include "editor/plugins/collision_shape_2d_editor_plugin.h"
#include "editor/plugins/control_editor_plugin.h"
-#include "editor/plugins/cpu_particles_2d_editor_plugin.h"
-#include "editor/plugins/cpu_particles_3d_editor_plugin.h"
#include "editor/plugins/curve_editor_plugin.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_tooltip_plugins.h"
#include "editor/plugins/font_config_plugin.h"
-#include "editor/plugins/gpu_particles_2d_editor_plugin.h"
-#include "editor/plugins/gpu_particles_3d_editor_plugin.h"
#include "editor/plugins/gpu_particles_collision_sdf_editor_plugin.h"
#include "editor/plugins/gradient_editor_plugin.h"
#include "editor/plugins/gradient_texture_2d_editor_plugin.h"
@@ -105,6 +101,7 @@
#include "editor/plugins/occluder_instance_3d_editor_plugin.h"
#include "editor/plugins/packed_scene_editor_plugin.h"
#include "editor/plugins/parallax_background_editor_plugin.h"
+#include "editor/plugins/particles_editor_plugin.h"
#include "editor/plugins/path_2d_editor_plugin.h"
#include "editor/plugins/path_3d_editor_plugin.h"
#include "editor/plugins/physical_bone_3d_editor_plugin.h"
diff --git a/modules/bcdec/image_decompress_bcdec.cpp b/modules/bcdec/image_decompress_bcdec.cpp
index 30ca1fccb3..c76470e3cc 100644
--- a/modules/bcdec/image_decompress_bcdec.cpp
+++ b/modules/bcdec/image_decompress_bcdec.cpp
@@ -92,8 +92,20 @@ static void decompress_image(BCdecFormat format, const void *src, void *dst, con
void image_decompress_bcdec(Image *p_image) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
- int w = p_image->get_width();
- int h = p_image->get_height();
+ int width = p_image->get_width();
+ int height = p_image->get_height();
+
+ // Compressed images' dimensions should be padded to the upper multiple of 4.
+ // If they aren't, they need to be realigned (the actual data is correctly padded though).
+ if (width % 4 != 0 || height % 4 != 0) {
+ int new_width = width + (4 - (width % 4));
+ int new_height = height + (4 - (height % 4));
+
+ print_verbose(vformat("Compressed image's dimensions are not multiples of 4 (%dx%d), aligning to (%dx%d)", width, height, new_width, new_height));
+
+ width = new_width;
+ height = new_height;
+ }
Image::Format source_format = p_image->get_format();
Image::Format target_format = Image::FORMAT_MAX;
@@ -148,30 +160,27 @@ void image_decompress_bcdec(Image *p_image) {
}
int mm_count = p_image->get_mipmap_count();
- int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
+ int64_t target_size = Image::get_image_data_size(width, height, target_format, p_image->has_mipmaps());
+ // Decompressed data.
Vector<uint8_t> data;
data.resize(target_size);
+ uint8_t *wb = data.ptrw();
+ // Source data.
const uint8_t *rb = p_image->get_data().ptr();
- uint8_t *wb = data.ptrw();
// Decompress mipmaps.
for (int i = 0; i <= mm_count; i++) {
- int64_t src_ofs = 0, mipmap_size = 0;
int mipmap_w = 0, mipmap_h = 0;
- p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h);
-
- int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i);
+ int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, source_format, i, mipmap_w, mipmap_h);
+ int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i);
decompress_image(bcdec_format, rb + src_ofs, wb + dst_ofs, mipmap_w, mipmap_h);
-
- w >>= 1;
- h >>= 1;
}
- p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
+ p_image->set_data(width, height, p_image->has_mipmaps(), target_format, data);
- // Swap channels if necessary.
+ // Swap channels if the format is using a channel swizzle.
if (source_format == Image::FORMAT_DXT5_RA_AS_RG) {
p_image->convert_ra_rgba8_to_rg();
}
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 7f0d5005cb..4a3a3a4b61 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -148,6 +148,15 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
return type;
}
+static GDScriptParser::DataType make_class_enum_type(const StringName &p_enum_name, GDScriptParser::ClassNode *p_class, const String &p_script_path, bool p_meta = true) {
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, p_class->fqcn, p_meta);
+
+ type.class_type = p_class;
+ type.script_path = p_script_path;
+
+ return type;
+}
+
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
StringName native_base = p_native_class;
@@ -1101,7 +1110,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
member.m_enum->set_datatype(resolving_datatype);
- GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
+ GDScriptParser::DataType enum_type = make_class_enum_type(member.m_enum->identifier->name, p_class, parser->script_path, true);
const GDScriptParser::EnumNode *prev_enum = current_enum;
current_enum = member.m_enum;
@@ -1194,7 +1203,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Also update the original references.
member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
- member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
+ member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false));
} break;
case GDScriptParser::ClassNode::Member::CLASS:
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@@ -4249,7 +4258,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
if (element.identifier->name == p_identifier->name) {
StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM;
- GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
+ GDScriptParser::DataType type = make_class_enum_type(enum_name, parser->current_class, parser->script_path, false);
if (element.parent_enum->identifier) {
type.enum_type = element.parent_enum->identifier->name;
}
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 0fd891aa80..3de1decc18 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3782,7 +3782,19 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
} break;
case GDScriptParser::DataType::ENUM: {
- if (base_type.enum_values.has(p_symbol)) {
+ if (base_type.class_type && base_type.class_type->has_member(base_type.enum_type)) {
+ GDScriptParser::EnumNode *base_enum = base_type.class_type->get_member(base_type.enum_type).m_enum;
+ for (const GDScriptParser::EnumNode::Value &value : base_enum->values) {
+ if (value.identifier && value.identifier->name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
+ r_result.class_path = base_type.script_path;
+ r_result.location = value.line;
+ Error err = OK;
+ r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
+ return err;
+ }
+ }
+ } else if (base_type.enum_values.has(p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = String(base_type.native_type).get_slicec('.', 0);
r_result.class_member = p_symbol;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 89655e0b56..946e997c1b 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -2348,9 +2348,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
int method_bind_count = 0;
for (const MethodInterface &imethod : itype.methods) {
- Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output);
+ Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output, false);
ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
"Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");
+ if (imethod.is_internal) {
+ // No need to generate span overloads for internal methods.
+ continue;
+ }
+
+ method_err = _generate_cs_method(itype, imethod, method_bind_count, output, true);
+ ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
+ "Failed to generate span overload method '" + imethod.name + "' for class '" + itype.name + "'.");
}
// Signals
@@ -2776,7 +2784,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
return OK;
}
-Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) {
+Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span) {
const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);
ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found.");
@@ -2789,6 +2797,35 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
"' from the editor API. Core API cannot have dependencies on the editor API.");
}
+ if (p_imethod.is_virtual && p_use_span) {
+ return OK;
+ }
+
+ bool has_span_argument = false;
+
+ if (p_use_span) {
+ if (p_imethod.is_vararg) {
+ has_span_argument = true;
+ } else {
+ for (const ArgumentInterface &iarg : p_imethod.arguments) {
+ const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
+ ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
+
+ if (arg_type->is_span_compatible) {
+ has_span_argument = true;
+ break;
+ }
+ }
+ }
+
+ if (has_span_argument) {
+ // Span overloads use the same method bind as the array overloads.
+ // Since both overloads are generated one after the other, we can decrease the count here
+ // to ensure the span overload uses the same method bind.
+ p_method_bind_count--;
+ }
+ }
+
String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count);
String arguments_sig;
@@ -2835,6 +2872,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
+ bool use_span_for_arg = p_use_span && arg_type->is_span_compatible;
+
// Add the current arguments to the signature
// If the argument has a default value which is not a constant, we will make it Nullable
{
@@ -2846,7 +2885,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
arguments_sig += "Nullable<";
}
- arguments_sig += arg_cs_type;
+ if (use_span_for_arg) {
+ arguments_sig += arg_type->c_type_in;
+ } else {
+ arguments_sig += arg_cs_type;
+ }
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
arguments_sig += "> ";
@@ -2856,7 +2899,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
arguments_sig += iarg.name;
- if (!p_imethod.is_compat && iarg.default_argument.size()) {
+ if (!p_use_span && !p_imethod.is_compat && iarg.default_argument.size()) {
if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {
arguments_sig += " = null";
} else {
@@ -2867,7 +2910,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
icall_params += ", ";
- if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT) {
+ if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT && !use_span_for_arg) {
// The default value of an argument must be constant. Otherwise we make it Nullable and do the following:
// Type arg_in = arg.HasValue ? arg.Value : <non-const default value>;
String arg_or_defval_local = iarg.name;
@@ -2927,6 +2970,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;
}
+ if (p_use_span && !has_span_argument) {
+ return OK;
+ }
+
// Collect caller name for MethodBind
if (p_imethod.is_vararg) {
icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";
@@ -2934,7 +2981,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
// Generate method
{
- if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
+ if (!p_imethod.is_virtual && !p_imethod.requires_object_call && !p_use_span) {
p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
<< INDENT1 "private static readonly IntPtr " << method_bind_field << " = ";
@@ -4734,13 +4781,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype = TypeInterface();
itype.name = "VarArg";
itype.cname = itype.name;
- itype.proxy_name = "Variant[]";
+ itype.proxy_name = "ReadOnlySpan<Variant>";
itype.cs_type = "params Variant[]";
- itype.cs_in_expr = "%0 ?? Array.Empty<Variant>()";
+ itype.cs_in_expr = "%0";
// c_type, c_in and c_arg_in are hard-coded in the generator.
// c_out and c_type_out are not applicable to VarArg.
itype.c_arg_in = "&%s_in";
- itype.c_type_in = "Variant[]";
+ itype.c_type_in = "ReadOnlySpan<Variant>";
+ itype.is_span_compatible = true;
builtin_types.insert(itype.cname, itype);
#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \
@@ -4754,9 +4802,10 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \
itype.c_arg_in = "&%s_in"; \
itype.c_type = #m_managed_type; \
- itype.c_type_in = itype.proxy_name; \
+ itype.c_type_in = "ReadOnlySpan<" #m_proxy_t ">"; \
itype.c_type_out = itype.proxy_name; \
itype.c_type_is_disposable_struct = true; \
+ itype.is_span_compatible = true; \
builtin_types.insert(itype.name, itype); \
}
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 556d287af4..1670aca4b3 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -265,6 +265,7 @@ class BindingsGenerator {
bool is_singleton = false;
bool is_singleton_instance = false;
bool is_ref_counted = false;
+ bool is_span_compatible = false;
/**
* Class is a singleton, but can't be declared as a static class as that would
@@ -840,7 +841,7 @@ class BindingsGenerator {
Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file);
Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output);
- Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output);
+ Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span);
Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output);
Error _generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 15b7ce7c73..fc68b11932 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -394,7 +394,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array)
+ public static godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array)
+ {
+ return ConvertSystemArrayToNativePackedByteArray((ReadOnlySpan<byte>)p_array);
+ }
+
+ public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(ReadOnlySpan<byte> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_byte_array();
@@ -417,7 +422,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array)
+ public static godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array)
+ {
+ return ConvertSystemArrayToNativePackedInt32Array((ReadOnlySpan<int>)p_array);
+ }
+
+ public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(ReadOnlySpan<int> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int32_array();
@@ -440,7 +450,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array)
+ public static godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array)
+ {
+ return ConvertSystemArrayToNativePackedInt64Array((ReadOnlySpan<long>)p_array);
+ }
+
+ public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(ReadOnlySpan<long> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int64_array();
@@ -463,8 +478,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(Span<float> p_array)
+ {
+ return ConvertSystemArrayToNativePackedFloat32Array((ReadOnlySpan<float>)p_array);
+ }
+
public static unsafe godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(
- Span<float> p_array)
+ ReadOnlySpan<float> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float32_array();
@@ -487,8 +507,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(Span<double> p_array)
+ {
+ return ConvertSystemArrayToNativePackedFloat64Array((ReadOnlySpan<double>)p_array);
+ }
+
public static unsafe godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(
- Span<double> p_array)
+ ReadOnlySpan<double> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float64_array();
@@ -512,6 +537,11 @@ namespace Godot.NativeInterop
public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(Span<string> p_array)
{
+ return ConvertSystemArrayToNativePackedStringArray((ReadOnlySpan<string>)p_array);
+ }
+
+ public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(ReadOnlySpan<string> p_array)
+ {
godot_packed_string_array dest = new godot_packed_string_array();
if (p_array.IsEmpty)
@@ -544,8 +574,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(Span<Vector2> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector2Array((ReadOnlySpan<Vector2>)p_array);
+ }
+
public static unsafe godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(
- Span<Vector2> p_array)
+ ReadOnlySpan<Vector2> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector2_array();
@@ -568,8 +603,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(Span<Vector3> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector3Array((ReadOnlySpan<Vector3>)p_array);
+ }
+
public static unsafe godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(
- Span<Vector3> p_array)
+ ReadOnlySpan<Vector3> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector3_array();
@@ -592,8 +632,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(Span<Vector4> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector4Array((ReadOnlySpan<Vector4>)p_array);
+ }
+
public static unsafe godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(
- Span<Vector4> p_array)
+ ReadOnlySpan<Vector4> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector4_array();
@@ -616,7 +661,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array)
+ public static godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array)
+ {
+ return ConvertSystemArrayToNativePackedColorArray((ReadOnlySpan<Color>)p_array);
+ }
+
+ public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(ReadOnlySpan<Color> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_color_array();
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 4043f3a8c2..0ee52a09a7 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -662,7 +662,7 @@ def get_ar_version(env):
print_warning("Couldn't check version of `ar`.")
return ret
- match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(?:\.(\d+))?", output)
+ match = re.search(r"GNU ar(?: \(GNU Binutils\)| version) (\d+)\.(\d+)(?:\.(\d+))?", output)
if match:
ret["major"] = int(match[1])
ret["minor"] = int(match[2])
@@ -788,8 +788,9 @@ def configure_mingw(env: "SConsEnvironment"):
env["CXX"] = mingw_bin_prefix + "g++"
if try_cmd("as --version", env["mingw_prefix"], env["arch"]):
env["AS"] = mingw_bin_prefix + "as"
- if try_cmd("gcc-ar --version", env["mingw_prefix"], env["arch"]):
- env["AR"] = mingw_bin_prefix + "gcc-ar"
+ ar = "ar" if os.name == "nt" else "gcc-ar"
+ if try_cmd(f"{ar} --version", env["mingw_prefix"], env["arch"]):
+ env["AR"] = mingw_bin_prefix + ar
if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]):
env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib"
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 90bfb4c84c..39cfccf983 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -159,7 +159,7 @@ void Bone2D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
if (skeleton) {
- for (int i = 0; i < skeleton->bones.size(); i++) {
+ for (uint32_t i = 0; i < skeleton->bones.size(); i++) {
if (skeleton->bones[i].bone == this) {
skeleton->bones.remove_at(i);
break;
@@ -555,17 +555,17 @@ void Skeleton2D::_update_bone_setup() {
bones.sort(); //sorting so that they are always in the same order/index
- for (int i = 0; i < bones.size(); i++) {
- bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
- bones.write[i].bone->skeleton_index = i;
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ bones[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
+ bones[i].bone->skeleton_index = i;
Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent());
if (parent_bone) {
- bones.write[i].parent_index = parent_bone->skeleton_index;
+ bones[i].parent_index = parent_bone->skeleton_index;
} else {
- bones.write[i].parent_index = -1;
+ bones[i].parent_index = -1;
}
- bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest();
+ bones[i].local_pose_override = bones[i].bone->get_skeleton_rest();
}
transform_dirty = true;
@@ -594,16 +594,16 @@ void Skeleton2D::_update_transform() {
transform_dirty = false;
- for (int i = 0; i < bones.size(); i++) {
- ERR_CONTINUE(bones[i].parent_index >= i);
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ ERR_CONTINUE(bones[i].parent_index >= (int)i);
if (bones[i].parent_index >= 0) {
- bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
+ bones[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
} else {
- bones.write[i].accum_transform = bones[i].bone->get_transform();
+ bones[i].accum_transform = bones[i].bone->get_transform();
}
}
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse;
RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform);
}
@@ -621,7 +621,7 @@ int Skeleton2D::get_bone_count() const {
Bone2D *Skeleton2D::get_bone(int p_idx) {
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
- ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr);
+ ERR_FAIL_INDEX_V(p_idx, (int)bones.size(), nullptr);
return bones[p_idx].bone;
}
@@ -733,14 +733,14 @@ RID Skeleton2D::get_skeleton() const {
}
void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent) {
- ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!");
- bones.write[p_bone_idx].local_pose_override = p_override;
- bones.write[p_bone_idx].local_pose_override_amount = p_amount;
- bones.write[p_bone_idx].local_pose_override_persistent = p_persistent;
+ ERR_FAIL_INDEX_MSG(p_bone_idx, (int)bones.size(), "Bone index is out of range!");
+ bones[p_bone_idx].local_pose_override = p_override;
+ bones[p_bone_idx].local_pose_override_amount = p_amount;
+ bones[p_bone_idx].local_pose_override_persistent = p_persistent;
}
Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) {
- ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!");
+ ERR_FAIL_INDEX_V_MSG(p_bone_idx, (int)bones.size(), Transform2D(), "Bone index is out of range!");
return bones[p_bone_idx].local_pose_override;
}
@@ -771,7 +771,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
}
// Do not cache the transform changes caused by the modifications!
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
bones[i].bone->copy_transform_to_cache = false;
}
@@ -783,7 +783,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
// Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform.
if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) {
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
if (bones[i].local_pose_override_amount > 0) {
bones[i].bone->set_meta("_local_pose_override_enabled_", true);
@@ -793,7 +793,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
bones[i].bone->propagate_call("force_update_transform");
if (bones[i].local_pose_override_persistent) {
- bones.write[i].local_pose_override_amount = 0.0;
+ bones[i].local_pose_override_amount = 0.0;
}
} else {
// TODO: see if there is a way to undo the override without having to resort to setting every bone's transform.
@@ -804,7 +804,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
}
// Cache any future transform changes
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
bones[i].bone->copy_transform_to_cache = true;
}
diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h
index 033bdff41d..16bd6fd34b 100644
--- a/scene/2d/skeleton_2d.h
+++ b/scene/2d/skeleton_2d.h
@@ -123,7 +123,7 @@ class Skeleton2D : public Node2D {
bool local_pose_override_persistent = false;
};
- Vector<Bone> bones;
+ LocalVector<Bone> bones;
bool bone_setup_dirty = true;
void _make_bone_setup_dirty();
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index acbc443a93..5b84bf903f 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -1479,6 +1479,7 @@ void CPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles3D::get_mesh);
ClassDB::bind_method(D_METHOD("restart"), &CPUParticles3D::restart);
+ ClassDB::bind_method(D_METHOD("capture_aabb"), &CPUParticles3D::capture_aabb);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 86ce8a881a..85de85a9a6 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -1181,15 +1181,16 @@ void Node3D::_validate_property(PropertyInfo &p_property) const {
}
bool Node3D::_property_can_revert(const StringName &p_name) const {
- if (p_name == "basis") {
+ const String sname = p_name;
+ if (sname == "basis") {
return true;
- } else if (p_name == "scale") {
+ } else if (sname == "scale") {
return true;
- } else if (p_name == "quaternion") {
+ } else if (sname == "quaternion") {
return true;
- } else if (p_name == "rotation") {
+ } else if (sname == "rotation") {
return true;
- } else if (p_name == "position") {
+ } else if (sname == "position") {
return true;
}
return false;
@@ -1198,35 +1199,36 @@ bool Node3D::_property_can_revert(const StringName &p_name) const {
bool Node3D::_property_get_revert(const StringName &p_name, Variant &r_property) const {
bool valid = false;
- if (p_name == "basis") {
+ const String sname = p_name;
+ if (sname == "basis") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Transform3D(variant).get_basis();
} else {
r_property = Basis();
}
- } else if (p_name == "scale") {
+ } else if (sname == "scale") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Transform3D(variant).get_basis().get_scale();
} else {
r_property = Vector3(1.0, 1.0, 1.0);
}
- } else if (p_name == "quaternion") {
+ } else if (sname == "quaternion") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion());
} else {
r_property = Quaternion();
}
- } else if (p_name == "rotation") {
+ } else if (sname == "rotation") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order);
} else {
r_property = Vector3();
}
- } else if (p_name == "position") {
+ } else if (sname == "position") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid) {
r_property = Transform3D(variant).get_origin();
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index db9c4db30d..9e4c9b1832 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -81,7 +81,7 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
return false;
}
- int which = path.get_slicec('/', 1).to_int();
+ uint32_t which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
if (which == bones.size() && what == "name") {
@@ -89,7 +89,7 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
return true;
}
- ERR_FAIL_INDEX_V(which, bones.size(), false);
+ ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false);
if (what == "parent") {
set_bone_parent(which, p_value);
@@ -153,10 +153,10 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
return false;
}
- int which = path.get_slicec('/', 1).to_int();
+ uint32_t which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
- ERR_FAIL_INDEX_V(which, bones.size(), false);
+ ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false);
if (what == "name") {
r_ret = get_bone_name(which);
@@ -182,7 +182,7 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
}
void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
const String prep = vformat("%s/%d/", PNAME("bones"), i);
p_list->push_back(PropertyInfo(Variant::STRING, prep + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::INT, prep + PNAME("parent"), PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
@@ -241,7 +241,7 @@ void Skeleton3D::_update_process_order() {
return;
}
- Bone *bonesptr = bones.ptrw();
+ Bone *bonesptr = bones.ptr();
int len = bones.size();
parentless_bones.clear();
@@ -276,6 +276,8 @@ void Skeleton3D::_update_process_order() {
concatenated_bone_names = StringName();
+ _update_bones_nested_set();
+
process_order_dirty = false;
emit_signal("bone_list_changed");
@@ -283,7 +285,7 @@ void Skeleton3D::_update_process_order() {
void Skeleton3D::_update_bone_names() const {
String names;
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
if (i > 0) {
names += ",";
}
@@ -331,16 +333,21 @@ void Skeleton3D::_notification(int p_what) {
updating = true;
- Bone *bonesptr = bones.ptrw();
+ Bone *bonesptr = bones.ptr();
int len = bones.size();
+ thread_local LocalVector<bool> bone_global_pose_dirty_backup;
+
// Process modifiers.
_find_modifiers();
if (!modifiers.is_empty()) {
// Store unmodified bone poses.
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
bones_backup[i].save(bones[i]);
}
+ // Store dirty flags for global bone poses.
+ bone_global_pose_dirty_backup = bone_global_pose_dirty;
+
_process_modifiers();
}
@@ -412,9 +419,11 @@ void Skeleton3D::_notification(int p_what) {
if (!modifiers.is_empty()) {
// Restore unmodified bone poses.
- for (int i = 0; i < bones.size(); i++) {
- bones_backup[i].restore(bones.write[i]);
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ bones_backup[i].restore(bones[i]);
}
+ // Restore dirty flags for global bone poses.
+ bone_global_pose_dirty = bone_global_pose_dirty_backup;
}
updating = false;
@@ -457,10 +466,111 @@ void Skeleton3D::_make_modifiers_dirty() {
_update_deferred(UPDATE_FLAG_MODIFIER);
}
+void Skeleton3D::_update_bones_nested_set() {
+ nested_set_offset_to_bone_index.resize(bones.size());
+ bone_global_pose_dirty.resize(bones.size());
+ _make_bone_global_poses_dirty();
+
+ int offset = 0;
+ for (int bone : parentless_bones) {
+ offset += _update_bone_nested_set(bone, offset);
+ }
+}
+
+int Skeleton3D::_update_bone_nested_set(int p_bone, int p_offset) {
+ Bone &bone = bones[p_bone];
+ int offset = p_offset + 1;
+ int span = 1;
+
+ for (int child_bone : bone.child_bones) {
+ int subspan = _update_bone_nested_set(child_bone, offset);
+ offset += subspan;
+ span += subspan;
+ }
+
+ nested_set_offset_to_bone_index[p_offset] = p_bone;
+ bone.nested_set_offset = p_offset;
+ bone.nested_set_span = span;
+
+ return span;
+}
+
+void Skeleton3D::_make_bone_global_poses_dirty() {
+ for (uint32_t i = 0; i < bone_global_pose_dirty.size(); i++) {
+ bone_global_pose_dirty[i] = true;
+ }
+}
+
+void Skeleton3D::_make_bone_global_pose_subtree_dirty(int p_bone) {
+ if (process_order_dirty) {
+ return;
+ }
+
+ const Bone &bone = bones[p_bone];
+ int span_offset = bone.nested_set_offset;
+ // No need to make subtree dirty when bone is already dirty.
+ if (bone_global_pose_dirty[span_offset]) {
+ return;
+ }
+
+ // Make global poses of subtree dirty.
+ int span_end = span_offset + bone.nested_set_span;
+ for (int i = span_offset; i < span_end; i++) {
+ bone_global_pose_dirty[i] = true;
+ }
+}
+
+void Skeleton3D::_update_bone_global_pose(int p_bone) {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX(p_bone, bone_size);
+
+ _update_process_order();
+
+ // Global pose is already calculated.
+ int nested_set_offset = bones[p_bone].nested_set_offset;
+ if (!bone_global_pose_dirty[nested_set_offset]) {
+ return;
+ }
+
+ thread_local LocalVector<int> bone_list;
+ bone_list.clear();
+ Transform3D global_pose;
+
+ // Create list of parent bones for which the global pose needs to be recalculated.
+ for (int bone = p_bone; bone >= 0; bone = bones[bone].parent) {
+ int offset = bones[bone].nested_set_offset;
+ // Stop searching when global pose is not dirty.
+ if (!bone_global_pose_dirty[offset]) {
+ global_pose = bones[bone].global_pose;
+ break;
+ }
+
+ bone_list.push_back(bone);
+ }
+
+ // Calculate global poses for all parent bones and the current bone.
+ for (int i = bone_list.size() - 1; i >= 0; i--) {
+ int bone_idx = bone_list[i];
+ Bone &bone = bones[bone_idx];
+ bool bone_enabled = bone.enabled && !show_rest_only;
+ Transform3D bone_pose = bone_enabled ? get_bone_pose(bone_idx) : get_bone_rest(bone_idx);
+
+ global_pose *= bone_pose;
+#ifndef DISABLE_DEPRECATED
+ if (bone.global_pose_override_amount >= CMP_EPSILON) {
+ global_pose = global_pose.interpolate_with(bone.global_pose_override, bone.global_pose_override_amount);
+ }
+#endif // _DISABLE_DEPRECATED
+
+ bone.global_pose = global_pose;
+ bone_global_pose_dirty[bone.nested_set_offset] = false;
+ }
+}
+
Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
- const_cast<Skeleton3D *>(this)->force_update_all_dirty_bones();
+ const_cast<Skeleton3D *>(this)->_update_bone_global_pose(p_bone);
return bones[p_bone].global_pose;
}
@@ -534,7 +644,7 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) {
}
name_to_bone_index.erase(bones[p_bone].name);
- bones.write[p_bone].name = p_name;
+ bones[p_bone].name = p_name;
name_to_bone_index.insert(p_name, p_bone);
version++;
@@ -582,13 +692,13 @@ void Skeleton3D::set_bone_meta(int p_bone, const StringName &p_key, const Varian
ERR_FAIL_INDEX(p_bone, bone_size);
if (p_value.get_type() == Variant::NIL) {
- if (bones.write[p_bone].metadata.has(p_key)) {
- bones.write[p_bone].metadata.erase(p_key);
+ if (bones[p_bone].metadata.has(p_key)) {
+ bones[p_bone].metadata.erase(p_key);
}
return;
}
- bones.write[p_bone].metadata.insert(p_key, p_value, false);
+ bones[p_bone].metadata.insert(p_key, p_value, false);
}
bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const {
@@ -615,7 +725,7 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) {
ERR_FAIL_COND(p_parent != -1 && (p_parent < 0));
ERR_FAIL_COND(p_bone == p_parent);
- bones.write[p_bone].parent = p_parent;
+ bones[p_bone].parent = p_parent;
process_order_dirty = true;
rest_dirty = true;
_make_dirty();
@@ -629,11 +739,11 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) {
int parent = bones[p_bone].parent;
while (parent >= 0) {
- bones.write[p_bone].rest = bones[parent].rest * bones[p_bone].rest;
+ bones[p_bone].rest = bones[parent].rest * bones[p_bone].rest;
parent = bones[parent].parent;
}
- bones.write[p_bone].parent = -1;
+ bones[p_bone].parent = -1;
process_order_dirty = true;
rest_dirty = true;
@@ -669,9 +779,10 @@ void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].rest = p_rest;
+ bones[p_bone].rest = p_rest;
rest_dirty = true;
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
Transform3D Skeleton3D::get_bone_rest(int p_bone) const {
const int bone_size = bones.size();
@@ -692,9 +803,10 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].enabled = p_enabled;
+ bones[p_bone].enabled = p_enabled;
emit_signal(SceneStringName(bone_enabled_changed), p_bone);
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
bool Skeleton3D::is_bone_enabled(int p_bone) const {
@@ -707,6 +819,7 @@ void Skeleton3D::set_show_rest_only(bool p_enabled) {
show_rest_only = p_enabled;
emit_signal(SceneStringName(show_rest_only_changed));
_make_dirty();
+ _make_bone_global_poses_dirty();
}
bool Skeleton3D::is_show_rest_only() const {
@@ -727,12 +840,13 @@ void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_position = p_pose.origin;
- bones.write[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion();
- bones.write[p_bone].pose_scale = p_pose.basis.get_scale();
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_position = p_pose.origin;
+ bones[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion();
+ bones[p_bone].pose_scale = p_pose.basis.get_scale();
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
@@ -740,30 +854,33 @@ void Skeleton3D::set_bone_pose_position(int p_bone, const Vector3 &p_position) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_position = p_position;
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_position = p_position;
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
void Skeleton3D::set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_rotation = p_rotation;
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_rotation = p_rotation;
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
void Skeleton3D::set_bone_pose_scale(int p_bone, const Vector3 &p_scale) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_scale = p_scale;
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_scale = p_scale;
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
@@ -794,7 +911,7 @@ void Skeleton3D::reset_bone_pose(int p_bone) {
}
void Skeleton3D::reset_bone_poses() {
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
reset_bone_pose(i);
}
}
@@ -802,7 +919,7 @@ void Skeleton3D::reset_bone_poses() {
Transform3D Skeleton3D::get_bone_pose(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
- const_cast<Skeleton3D *>(this)->bones.write[p_bone].update_pose_cache();
+ const_cast<Skeleton3D *>(this)->bones[p_bone].update_pose_cache();
return bones[p_bone].pose_cache;
}
@@ -853,7 +970,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
// Pose changed, rebuild cache of inverses.
const Bone *bonesptr = bones.ptr();
- int len = bones.size();
+ uint32_t len = bones.size();
// Calculate global rests and invert them.
LocalVector<int> bones_to_process;
@@ -877,7 +994,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
}
}
- for (int i = 0; i < len; i++) {
+ for (uint32_t i = 0; i < len; i++) {
// The inverse is what is actually required.
skin->set_bind_bone(i, i);
skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse());
@@ -937,15 +1054,15 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone_idx, bone_size);
- Bone *bonesptr = bones.ptrw();
- thread_local LocalVector<int> bones_to_process;
- bones_to_process.clear();
- bones_to_process.push_back(p_bone_idx);
+ Bone *bonesptr = bones.ptr();
- uint32_t index = 0;
- while (index < bones_to_process.size()) {
- int current_bone_idx = bones_to_process[index];
+ // Loop through nested set.
+ for (int offset = 0; offset < bone_size; offset++) {
+ if (!bone_global_pose_dirty[offset]) {
+ continue;
+ }
+ int current_bone_idx = nested_set_offset_to_bone_index[offset];
Bone &b = bonesptr[current_bone_idx];
bool bone_enabled = b.enabled && !show_rest_only;
@@ -992,13 +1109,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
}
#endif // _DISABLE_DEPRECATED
- // Add the bone's children to the list of bones to be processed.
- int child_bone_size = b.child_bones.size();
- for (int i = 0; i < child_bone_size; i++) {
- bones_to_process.push_back(b.child_bones[i]);
- }
-
- index++;
+ bone_global_pose_dirty[offset] = false;
}
}
@@ -1171,20 +1282,22 @@ void Skeleton3D::_bind_methods() {
#ifndef DISABLE_DEPRECATED
void Skeleton3D::clear_bones_global_pose_override() {
- for (int i = 0; i < bones.size(); i += 1) {
- bones.write[i].global_pose_override_amount = 0;
- bones.write[i].global_pose_override_reset = true;
+ for (uint32_t i = 0; i < bones.size(); i += 1) {
+ bones[i].global_pose_override_amount = 0;
+ bones[i].global_pose_override_reset = true;
}
_make_dirty();
+ _make_bone_global_poses_dirty();
}
void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].global_pose_override_amount = p_amount;
- bones.write[p_bone].global_pose_override = p_pose;
- bones.write[p_bone].global_pose_override_reset = !p_persistent;
+ bones[p_bone].global_pose_override_amount = p_amount;
+ bones[p_bone].global_pose_override = p_pose;
+ bones[p_bone].global_pose_override_reset = !p_persistent;
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const {
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 07bdeccf2f..0db12600c3 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -107,6 +107,8 @@ private:
Quaternion pose_rotation;
Vector3 pose_scale = Vector3(1, 1, 1);
Transform3D global_pose;
+ int nested_set_offset = 0; // Offset in nested set of bone hierarchy.
+ int nested_set_span = 0; // Subtree span in nested set of bone hierarchy.
void update_pose_cache() {
if (pose_cache_dirty) {
@@ -153,7 +155,7 @@ private:
HashSet<SkinReference *> skin_bindings;
void _skin_changed();
- Vector<Bone> bones;
+ LocalVector<Bone> bones;
bool process_order_dirty = false;
Vector<int> parentless_bones;
@@ -183,6 +185,15 @@ private:
void _make_modifiers_dirty();
LocalVector<BonePoseBackup> bones_backup;
+ // Global bone pose calculation.
+ LocalVector<int> nested_set_offset_to_bone_index; // Map from Bone::nested_set_offset to bone index.
+ LocalVector<bool> bone_global_pose_dirty; // Indexable with Bone::nested_set_offset.
+ void _update_bones_nested_set();
+ int _update_bone_nested_set(int p_bone, int p_offset);
+ void _make_bone_global_poses_dirty();
+ void _make_bone_global_pose_subtree_dirty(int p_bone);
+ void _update_bone_global_pose(int p_bone);
+
#ifndef DISABLE_DEPRECATED
void _add_bone_bind_compat_88791(const String &p_name);
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 635228670d..7346c9dcd3 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -277,15 +277,17 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
code_completion_force_item_center = -1;
queue_redraw();
}
- code_completion_pan_offset += 1.0f;
+ code_completion_pan_offset = 0;
} else if (code_completion_pan_offset >= +1.0) {
if (code_completion_current_selected < code_completion_options.size() - 1) {
code_completion_current_selected++;
code_completion_force_item_center = -1;
queue_redraw();
}
- code_completion_pan_offset -= 1.0f;
+ code_completion_pan_offset = 0;
}
+ accept_event();
+ return;
}
Ref<InputEventMouseButton> mb = p_gui_input;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 169a5dcb01..0cdb23618f 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1448,7 +1448,11 @@ void Viewport::_gui_show_tooltip() {
&tooltip_owner);
gui.tooltip_text = gui.tooltip_text.strip_edges();
- if (gui.tooltip_text.is_empty()) {
+ // Controls can implement `make_custom_tooltip` to provide their own tooltip.
+ // This should be a Control node which will be added as child to a TooltipPanel.
+ Control *base_tooltip = tooltip_owner ? tooltip_owner->make_custom_tooltip(gui.tooltip_text) : nullptr;
+
+ if (gui.tooltip_text.is_empty() && !base_tooltip) {
return; // Nothing to show.
}
@@ -1469,10 +1473,6 @@ void Viewport::_gui_show_tooltip() {
// Ensure no opaque background behind the panel as its StyleBox can be partially transparent (e.g. corners).
panel->set_transparent_background(true);
- // Controls can implement `make_custom_tooltip` to provide their own tooltip.
- // This should be a Control node which will be added as child to a TooltipPanel.
- Control *base_tooltip = tooltip_owner->make_custom_tooltip(gui.tooltip_text);
-
// If no custom tooltip is given, use a default implementation.
if (!base_tooltip) {
gui.tooltip_label = memnew(Label);
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 9df009ec28..ecc1982aa5 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -382,14 +382,11 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const {
bool ShaderMaterial::_property_can_revert(const StringName &p_name) const {
if (shader.is_valid()) {
- const StringName *pr = remap_cache.getptr(p_name);
- if (pr) {
- Variant default_value = RenderingServer::get_singleton()->shader_get_parameter_default(shader->get_rid(), *pr);
- Variant current_value = get_shader_parameter(*pr);
- return default_value.get_type() != Variant::NIL && default_value != current_value;
- } else if (p_name == "render_priority" || p_name == "next_pass") {
+ if (remap_cache.has(p_name)) {
return true;
}
+ const String sname = p_name;
+ return sname == "render_priority" || sname == "next_pass";
}
return false;
}
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index 0ec161d8cf..b5873528f7 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -51,7 +51,7 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
for (int i = 0; i < p_child_item_count; i++) {
- _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, p_canvas_cull_mask, Point2(), 1, nullptr);
+ _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, false, p_canvas_cull_mask, Point2(), 1, nullptr);
}
RendererCanvasRender::Item *list = nullptr;
@@ -79,45 +79,71 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
}
}
-void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, const Transform2D &p_transform, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
+void RendererCanvasCull::_collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
int child_item_count = p_canvas_item->child_items.size();
RendererCanvasCull::Item **child_items = p_canvas_item->child_items.ptrw();
for (int i = 0; i < child_item_count; i++) {
- int abs_z = 0;
if (child_items[i]->visible) {
- if (r_items) {
- r_items[r_index] = child_items[i];
- child_items[i]->ysort_xform = p_transform;
- child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.columns[2]);
- child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
- child_items[i]->ysort_modulate = p_modulate;
- child_items[i]->ysort_index = r_index;
- child_items[i]->ysort_parent_abs_z_index = p_z;
-
- if (!child_items[i]->repeat_source) {
- child_items[i]->repeat_size = p_canvas_item->repeat_size;
- child_items[i]->repeat_times = p_canvas_item->repeat_times;
- child_items[i]->repeat_source_item = p_canvas_item->repeat_source_item;
- }
+ // To y-sort according to the item's final position, physics interpolation
+ // and transform snapping need to be applied before y-sorting.
+ Transform2D child_xform;
+ if (!_interpolation_data.interpolation_enabled || !child_items[i]->interpolated) {
+ child_xform = child_items[i]->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(child_items[i]->xform_prev, child_items[i]->xform_curr, child_xform, f);
+ }
- // Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items.
- if (child_items[i]->z_relative) {
- abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
- } else {
- abs_z = child_items[i]->z_index;
- }
+ if (snapping_2d_transforms_to_pixel) {
+ child_xform.columns[2] = child_xform.columns[2].round();
+ }
+
+ r_items[r_index] = child_items[i];
+ child_items[i]->ysort_xform = p_canvas_item->ysort_xform * child_xform;
+ child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
+ child_items[i]->ysort_modulate = p_modulate;
+ child_items[i]->ysort_index = r_index;
+ child_items[i]->ysort_parent_abs_z_index = p_z;
+
+ if (!child_items[i]->repeat_source) {
+ child_items[i]->repeat_size = p_canvas_item->repeat_size;
+ child_items[i]->repeat_times = p_canvas_item->repeat_times;
+ child_items[i]->repeat_source_item = p_canvas_item->repeat_source_item;
+ }
+
+ // Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items.
+ int abs_z = 0;
+ if (child_items[i]->z_relative) {
+ abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
+ } else {
+ abs_z = child_items[i]->z_index;
}
r_index++;
if (child_items[i]->sort_y) {
- _collect_ysort_children(child_items[i], p_transform * child_items[i]->xform_curr, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
+ _collect_ysort_children(child_items[i], child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
}
}
}
}
-void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner, RID_Owner<RendererCanvasCull::Item, true> &canvas_item_owner) {
+int RendererCanvasCull::_count_ysort_children(RendererCanvasCull::Item *p_canvas_item) {
+ int ysort_children_count = 0;
+ int child_item_count = p_canvas_item->child_items.size();
+ RendererCanvasCull::Item *const *child_items = p_canvas_item->child_items.ptr();
+ for (int i = 0; i < child_item_count; i++) {
+ if (child_items[i]->visible) {
+ ysort_children_count++;
+ if (child_items[i]->sort_y) {
+ ysort_children_count += _count_ysort_children(child_items[i]);
+ }
+ }
+ }
+ return ysort_children_count;
+}
+
+void RendererCanvasCull::_mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner) {
do {
ysort_owner->ysort_children_count = -1;
ysort_owner = canvas_item_owner.owns(ysort_owner->parent) ? canvas_item_owner.get_or_null(ysort_owner->parent) : nullptr;
@@ -236,7 +262,7 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
}
}
-void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item) {
+void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item) {
Item *ci = p_canvas_item;
if (!ci->visible) {
@@ -260,15 +286,29 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
}
+ Transform2D self_xform;
Transform2D final_xform;
- if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
- final_xform = ci->xform_curr;
+ if (p_is_already_y_sorted) {
+ // Y-sorted item's final transform is calculated before y-sorting,
+ // and is passed as `p_parent_xform` afterwards. No need to recalculate.
+ final_xform = p_parent_xform;
} else {
- real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
- TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
- }
+ if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
+ self_xform = ci->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, self_xform, f);
+ }
- Transform2D parent_xform = p_parent_xform;
+ Transform2D parent_xform = p_parent_xform;
+
+ if (snapping_2d_transforms_to_pixel) {
+ self_xform.columns[2] = self_xform.columns[2].round();
+ parent_xform.columns[2] = parent_xform.columns[2].round();
+ }
+
+ final_xform = parent_xform * self_xform;
+ }
Point2 repeat_size = p_repeat_size;
int repeat_times = p_repeat_times;
@@ -284,13 +324,6 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
ci->repeat_source_item = repeat_source_item;
}
- if (snapping_2d_transforms_to_pixel) {
- final_xform.columns[2] = (final_xform.columns[2] + Point2(0.5, 0.5)).floor();
- parent_xform.columns[2] = (parent_xform.columns[2] + Point2(0.5, 0.5)).floor();
- }
-
- final_xform = parent_xform * final_xform;
-
Rect2 global_rect = final_xform.xform(rect);
if (repeat_source_item && (repeat_size.x || repeat_size.y)) {
// Top-left repeated rect.
@@ -355,29 +388,27 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
if (ci->sort_y) {
- if (p_allow_y_sort) {
+ if (!p_is_already_y_sorted) {
if (ci->ysort_children_count == -1) {
- ci->ysort_children_count = 0;
- _collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), nullptr, ci->ysort_children_count, p_z);
+ ci->ysort_children_count = _count_ysort_children(ci);
}
child_item_count = ci->ysort_children_count + 1;
child_items = (Item **)alloca(child_item_count * sizeof(Item *));
- ci->ysort_xform = ci->xform_curr.affine_inverse();
- ci->ysort_pos = Vector2();
+ ci->ysort_xform = Transform2D();
ci->ysort_modulate = Color(1, 1, 1, 1);
ci->ysort_index = 0;
ci->ysort_parent_abs_z_index = parent_z;
child_items[0] = ci;
int i = 1;
- _collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z);
+ _collect_ysort_children(ci, p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z);
- SortArray<Item *, ItemPtrSort> sorter;
+ SortArray<Item *, ItemYSort> sorter;
sorter.sort(child_items, child_item_count);
for (i = 0; i < child_item_count; i++) {
- _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times, child_items[i]->repeat_source_item);
+ _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, true, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times, child_items[i]->repeat_source_item);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -401,14 +432,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (!child_items[i]->behind && !use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
}
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->behind || use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
}
}
}
@@ -509,7 +540,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) {
item_owner->child_items.erase(canvas_item);
if (item_owner->sort_y) {
- _mark_ysort_dirty(item_owner, canvas_item_owner);
+ _mark_ysort_dirty(item_owner);
}
}
@@ -529,7 +560,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) {
item_owner->children_order_dirty = true;
if (item_owner->sort_y) {
- _mark_ysort_dirty(item_owner, canvas_item_owner);
+ _mark_ysort_dirty(item_owner);
}
} else {
@@ -546,7 +577,7 @@ void RendererCanvasCull::canvas_item_set_visible(RID p_item, bool p_visible) {
canvas_item->visible = p_visible;
- _mark_ysort_dirty(canvas_item, canvas_item_owner);
+ _mark_ysort_dirty(canvas_item);
}
void RendererCanvasCull::canvas_item_set_light_mask(RID p_item, int p_mask) {
@@ -1742,7 +1773,7 @@ void RendererCanvasCull::canvas_item_set_sort_children_by_y(RID p_item, bool p_e
canvas_item->sort_y = p_enable;
- _mark_ysort_dirty(canvas_item, canvas_item_owner);
+ _mark_ysort_dirty(canvas_item);
}
void RendererCanvasCull::canvas_item_set_z_index(RID p_item, int p_z) {
@@ -2423,7 +2454,7 @@ bool RendererCanvasCull::free(RID p_rid) {
item_owner->child_items.erase(canvas_item);
if (item_owner->sort_y) {
- _mark_ysort_dirty(item_owner, canvas_item_owner);
+ _mark_ysort_dirty(item_owner);
}
}
}
diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h
index 91c03054f7..9a088d94ed 100644
--- a/servers/rendering/renderer_canvas_cull.h
+++ b/servers/rendering/renderer_canvas_cull.h
@@ -50,8 +50,7 @@ public:
bool children_order_dirty;
int ysort_children_count;
Color ysort_modulate;
- Transform2D ysort_xform;
- Vector2 ysort_pos;
+ Transform2D ysort_xform; // Relative to y-sorted subtree's root item (identity for such root). Its `origin.y` is used for sorting.
int ysort_index;
int ysort_parent_abs_z_index; // Absolute Z index of parent. Only populated and used when y-sorting.
uint32_t visibility_layer = 0xffffffff;
@@ -84,7 +83,6 @@ public:
index = 0;
ysort_children_count = -1;
ysort_xform = Transform2D();
- ysort_pos = Vector2();
ysort_index = 0;
ysort_parent_abs_z_index = 0;
}
@@ -96,13 +94,15 @@ public:
}
};
- struct ItemPtrSort {
+ struct ItemYSort {
_FORCE_INLINE_ bool operator()(const Item *p_left, const Item *p_right) const {
- if (Math::is_equal_approx(p_left->ysort_pos.y, p_right->ysort_pos.y)) {
+ const real_t left_y = p_left->ysort_xform.columns[2].y;
+ const real_t right_y = p_right->ysort_xform.columns[2].y;
+ if (Math::is_equal_approx(left_y, right_y)) {
return p_left->ysort_index < p_right->ysort_index;
}
- return p_left->ysort_pos.y < p_right->ysort_pos.y;
+ return left_y < right_y;
}
};
@@ -187,7 +187,11 @@ public:
private:
void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr);
- void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item);
+ void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item);
+
+ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z);
+ int _count_ysort_children(RendererCanvasCull::Item *p_canvas_item);
+ void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner);
static constexpr int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1;
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 18975320f7..0d468ad1e3 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -2185,7 +2185,7 @@ void MeshStorage::skeleton_allocate_data(RID p_skeleton, int p_bones, bool p_2d_
if (skeleton->size) {
skeleton->data.resize(skeleton->size * (skeleton->use_2d ? 8 : 12));
skeleton->buffer = RD::get_singleton()->storage_buffer_create(skeleton->data.size() * sizeof(float));
- memset(skeleton->data.ptrw(), 0, skeleton->data.size() * sizeof(float));
+ memset(skeleton->data.ptr(), 0, skeleton->data.size() * sizeof(float));
_skeleton_make_dirty(skeleton);
@@ -2219,7 +2219,7 @@ void MeshStorage::skeleton_bone_set_transform(RID p_skeleton, int p_bone, const
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(skeleton->use_2d);
- float *dataptr = skeleton->data.ptrw() + p_bone * 12;
+ float *dataptr = skeleton->data.ptr() + p_bone * 12;
dataptr[0] = p_transform.basis.rows[0][0];
dataptr[1] = p_transform.basis.rows[0][1];
@@ -2270,8 +2270,7 @@ void MeshStorage::skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, con
ERR_FAIL_NULL(skeleton);
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(!skeleton->use_2d);
-
- float *dataptr = skeleton->data.ptrw() + p_bone * 8;
+ float *dataptr = skeleton->data.ptr() + p_bone * 8;
dataptr[0] = p_transform.columns[0][0];
dataptr[1] = p_transform.columns[1][0];
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index 6784520d17..322f3cc6f4 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -312,7 +312,7 @@ private:
struct Skeleton {
bool use_2d = false;
int size = 0;
- Vector<float> data;
+ LocalVector<float> data;
RID buffer;
bool dirty = false;