diff options
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 ¤t_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; |