diff options
Diffstat (limited to 'modules')
639 files changed, 37354 insertions, 5788 deletions
diff --git a/modules/SCsub b/modules/SCsub index 739c5de0b5..e16cc17b67 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -1,8 +1,9 @@ #!/usr/bin/env python -import methods import os +import methods + Import("env") env_modules = env.Clone() @@ -13,6 +14,18 @@ env_modules.Append(CPPDEFINES=["GODOT_MODULE"]) Export("env_modules") +# Header with MODULE_*_ENABLED defines. +def modules_enabled_builder(target, source, env): + with methods.generated_wrapper(target) as file: + for module in source[0].read(): + file.write(f"#define MODULE_{module.upper()}_ENABLED\n") + + +modules_enabled = env.CommandNoCache( + "modules_enabled.gen.h", env.Value(env.module_list), env.Run(modules_enabled_builder) +) + + def register_module_types_builder(target, source, env): modules = source[0].read() mod_inc = "\n".join([f'#include "{p}/register_types.h"' for p in modules.values()]) @@ -43,20 +56,12 @@ void uninitialize_modules(ModuleInitializationLevel p_level) {{ register_module_types = env.CommandNoCache( - "register_module_types.gen.cpp", env.Value(env.modules_detected), env.Run(register_module_types_builder) + "register_module_types.gen.cpp", + [env.Value(env.modules_detected), modules_enabled], + env.Run(register_module_types_builder), ) -# Header with MODULE_*_ENABLED defines. -def modules_enabled_builder(target, source, env): - with methods.generated_wrapper(target) as file: - for module in source[0].read(): - file.write(f"#define MODULE_{module.upper()}_ENABLED\n") - - -env.CommandNoCache("modules_enabled.gen.h", env.Value(env.module_list), env.Run(modules_enabled_builder)) - - vs_sources = [] test_headers = [] # libmodule_<name>.a for each active module. diff --git a/modules/astcenc/config.py b/modules/astcenc/config.py index eb565b85b9..37bb65f8ae 100644 --- a/modules/astcenc/config.py +++ b/modules/astcenc/config.py @@ -1,5 +1,7 @@ def can_build(env, platform): - return env.editor_build + # Godot only uses it in the editor, but ANGLE depends on it and we had + # to remove the copy from prebuilt ANGLE libs to solve symbol clashes. + return env.editor_build or env.get("angle_libs") def configure(env): diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp index 499cf739c4..1dfa7da766 100644 --- a/modules/astcenc/image_compress_astcenc.cpp +++ b/modules/astcenc/image_compress_astcenc.cpp @@ -97,7 +97,7 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { // Initialize astcenc. - int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); Vector<uint8_t> dest_data; dest_data.resize(dest_size); uint8_t *dest_write = dest_data.ptrw(); @@ -125,12 +125,12 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; for (int i = 0; i < mip_count + 1; i++) { int src_mip_w, src_mip_h; - int src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); + int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); const uint8_t *slices = &image_data.ptr()[src_ofs]; int dst_mip_w, dst_mip_h; - int dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); + int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). if (unlikely(dst_ofs % 8 != 0)) { astcenc_context_free(context); @@ -231,7 +231,7 @@ void _decompress_astc(Image *r_img) { const bool mipmaps = r_img->has_mipmaps(); int width = r_img->get_width(); int height = r_img->get_height(); - int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); Vector<uint8_t> dest_data; dest_data.resize(dest_size); uint8_t *dest_write = dest_data.ptrw(); @@ -244,9 +244,9 @@ void _decompress_astc(Image *r_img) { for (int i = 0; i < mip_count + 1; i++) { int src_mip_w, src_mip_h; - int src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); + int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); const uint8_t *src_data = &image_data.ptr()[src_ofs]; - int src_size; + int64_t src_size; if (i == mip_count) { src_size = image_data.size() - src_ofs; } else { @@ -255,7 +255,7 @@ void _decompress_astc(Image *r_img) { } int dst_mip_w, dst_mip_h; - int dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); + int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). ERR_FAIL_COND(dst_ofs % 8 != 0); uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs]; diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp index d8ef1c0414..ab20d00b5b 100644 --- a/modules/basis_universal/image_compress_basisu.cpp +++ b/modules/basis_universal/image_compress_basisu.cpp @@ -65,6 +65,12 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha params.m_multithreading = true; params.m_check_for_alpha = false; + if (!OS::get_singleton()->is_stdout_verbose()) { + params.m_print_stats = false; + params.m_compute_stats = false; + params.m_status_output = false; + } + basisu::job_pool job_pool(OS::get_singleton()->get_processor_count()); params.m_pJob_pool = &job_pool; @@ -78,14 +84,12 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha decompress_format = BASIS_DECOMPRESS_RGBA; } break; case Image::USED_CHANNELS_R: { - decompress_format = BASIS_DECOMPRESS_RGB; + decompress_format = BASIS_DECOMPRESS_R; } break; case Image::USED_CHANNELS_RG: { - // Currently RG textures are compressed as DXT5/ETC2_RGBA8 with a RA -> RG swizzle, - // as BasisUniversal didn't use to support ETC2_RG11 transcoding. params.m_force_alpha = true; image->convert_rg_to_ra_rgba8(); - decompress_format = BASIS_DECOMPRESS_RG_AS_RA; + decompress_format = BASIS_DECOMPRESS_RG; } break; case Image::USED_CHANNELS_RGB: { decompress_format = BASIS_DECOMPRESS_RGB; @@ -114,7 +118,8 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha Vector<uint32_t> mip_data_padded; for (int32_t i = 0; i <= image->get_mipmap_count(); i++) { - int ofs, size, width, height; + int64_t ofs, size; + int width, height; image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); const uint8_t *image_mip_data = image_data.ptr() + ofs; @@ -212,15 +217,68 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { // Get supported compression formats. bool bptc_supported = RS::get_singleton()->has_os_feature("bptc"); bool astc_supported = RS::get_singleton()->has_os_feature("astc"); + bool rgtc_supported = RS::get_singleton()->has_os_feature("rgtc"); bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc"); bool etc2_supported = RS::get_singleton()->has_os_feature("etc2"); bool needs_ra_rg_swap = false; + bool needs_rg_trim = false; + + BasisDecompressFormat decompress_format = (BasisDecompressFormat)(*(uint32_t *)(src_ptr)); - switch (*(uint32_t *)(src_ptr)) { + switch (decompress_format) { + case BASIS_DECOMPRESS_R: { + if (rgtc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC4_R; + image_format = Image::FORMAT_RGTC_R; + } else if (s3tc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC1; + image_format = Image::FORMAT_DXT1; + } else if (etc2_supported) { + basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_R11; + image_format = Image::FORMAT_ETC2_R11; + } else { + // No supported VRAM compression formats, decompress. + basisu_format = basist::transcoder_texture_format::cTFRGBA32; + image_format = Image::FORMAT_RGBA8; + needs_rg_trim = true; + } + + } break; case BASIS_DECOMPRESS_RG: { - // RGTC transcoding is currently performed with RG_AS_RA, fail. - ERR_FAIL_V(image); + if (rgtc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC5_RG; + image_format = Image::FORMAT_RGTC_RG; + } else if (s3tc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC3; + image_format = Image::FORMAT_DXT5_RA_AS_RG; + } else if (etc2_supported) { + basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_RG11; + image_format = Image::FORMAT_ETC2_RG11; + } else { + // No supported VRAM compression formats, decompress. + basisu_format = basist::transcoder_texture_format::cTFRGBA32; + image_format = Image::FORMAT_RGBA8; + needs_ra_rg_swap = true; + needs_rg_trim = true; + } + + } break; + case BASIS_DECOMPRESS_RG_AS_RA: { + if (s3tc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC3; + image_format = Image::FORMAT_DXT5_RA_AS_RG; + } else if (etc2_supported) { + basisu_format = basist::transcoder_texture_format::cTFETC2; + image_format = Image::FORMAT_ETC2_RA_AS_RG; + } else { + // No supported VRAM compression formats, decompress. + basisu_format = basist::transcoder_texture_format::cTFRGBA32; + image_format = Image::FORMAT_RGBA8; + needs_ra_rg_swap = true; + needs_rg_trim = true; + } + } break; case BASIS_DECOMPRESS_RGB: { if (bptc_supported) { @@ -260,20 +318,7 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { basisu_format = basist::transcoder_texture_format::cTFRGBA32; image_format = Image::FORMAT_RGBA8; } - } break; - case BASIS_DECOMPRESS_RG_AS_RA: { - if (s3tc_supported) { - basisu_format = basist::transcoder_texture_format::cTFBC3; - image_format = Image::FORMAT_DXT5_RA_AS_RG; - } else if (etc2_supported) { - basisu_format = basist::transcoder_texture_format::cTFETC2; - image_format = Image::FORMAT_ETC2_RA_AS_RG; - } else { - // No supported VRAM compression formats, decompress. - basisu_format = basist::transcoder_texture_format::cTFRGBA32; - image_format = Image::FORMAT_RGBA8; - needs_ra_rg_swap = true; - } + } break; } @@ -300,7 +345,7 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i); uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height; - int ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i); + int64_t ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i); bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, mip_block_or_pixel_count, basisu_format); @@ -317,6 +362,15 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { image->convert_ra_rgba8_to_rg(); } + if (needs_rg_trim) { + // Remove unnecessary color channels from uncompressed textures. + if (decompress_format == BASIS_DECOMPRESS_R) { + image->convert(Image::FORMAT_R8); + } else if (decompress_format == BASIS_DECOMPRESS_RG || decompress_format == BASIS_DECOMPRESS_RG_AS_RA) { + image->convert(Image::FORMAT_RG8); + } + } + return image; } diff --git a/modules/basis_universal/image_compress_basisu.h b/modules/basis_universal/image_compress_basisu.h index ac5d62ae73..5e36d448f6 100644 --- a/modules/basis_universal/image_compress_basisu.h +++ b/modules/basis_universal/image_compress_basisu.h @@ -38,6 +38,7 @@ enum BasisDecompressFormat { BASIS_DECOMPRESS_RGB, BASIS_DECOMPRESS_RGBA, BASIS_DECOMPRESS_RG_AS_RA, + BASIS_DECOMPRESS_R, }; void basis_universal_init(); diff --git a/modules/betsy/CrossPlatformSettings_piece_all.glsl b/modules/betsy/CrossPlatformSettings_piece_all.glsl new file mode 100644 index 0000000000..b7abac7fcc --- /dev/null +++ b/modules/betsy/CrossPlatformSettings_piece_all.glsl @@ -0,0 +1,76 @@ + +#define min3(a, b, c) min(a, min(b, c)) +#define max3(a, b, c) max(a, max(b, c)) + +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 + +#define int2 ivec2 +#define int3 ivec3 +#define int4 ivec4 + +#define uint2 uvec2 +#define uint3 uvec3 +#define uint4 uvec4 + +#define float2x2 mat2 +#define float3x3 mat3 +#define float4x4 mat4 +#define ogre_float4x3 mat3x4 + +#define ushort uint +#define ushort3 uint3 +#define ushort4 uint4 + +//Short used for read operations. It's an int in GLSL & HLSL. An ushort in Metal +#define rshort int +#define rshort2 int2 +#define rint int +//Short used for write operations. It's an int in GLSL. An ushort in HLSL & Metal +#define wshort2 int2 +#define wshort3 int3 + +#define toFloat3x3(x) mat3(x) +#define buildFloat3x3(row0, row1, row2) mat3(row0, row1, row2) + +#define mul(x, y) ((x) * (y)) +#define saturate(x) clamp((x), 0.0, 1.0) +#define lerp mix +#define rsqrt inversesqrt +#define INLINE +#define NO_INTERPOLATION_PREFIX flat +#define NO_INTERPOLATION_SUFFIX + +#define PARAMS_ARG_DECL +#define PARAMS_ARG + +#define reversebits bitfieldReverse + +#define OGRE_Sample(tex, sampler, uv) texture(tex, uv) +#define OGRE_SampleLevel(tex, sampler, uv, lod) textureLod(tex, uv, lod) +#define OGRE_SampleArray2D(tex, sampler, uv, arrayIdx) texture(tex, vec3(uv, arrayIdx)) +#define OGRE_SampleArray2DLevel(tex, sampler, uv, arrayIdx, lod) textureLod(tex, vec3(uv, arrayIdx), lod) +#define OGRE_SampleArrayCubeLevel(tex, sampler, uv, arrayIdx, lod) textureLod(tex, vec4(uv, arrayIdx), lod) +#define OGRE_SampleGrad(tex, sampler, uv, ddx, ddy) textureGrad(tex, uv, ddx, ddy) +#define OGRE_SampleArray2DGrad(tex, sampler, uv, arrayIdx, ddx, ddy) textureGrad(tex, vec3(uv, arrayIdx), ddx, ddy) +#define OGRE_ddx(val) dFdx(val) +#define OGRE_ddy(val) dFdy(val) +#define OGRE_Load2D(tex, iuv, lod) texelFetch(tex, iuv, lod) +#define OGRE_LoadArray2D(tex, iuv, arrayIdx, lod) texelFetch(tex, ivec3(iuv, arrayIdx), lod) +#define OGRE_Load2DMS(tex, iuv, subsample) texelFetch(tex, iuv, subsample) + +#define OGRE_Load3D(tex, iuv, lod) texelFetch(tex, ivec3(iuv), lod) + +#define OGRE_GatherRed(tex, sampler, uv) textureGather(tex, uv, 0) +#define OGRE_GatherGreen(tex, sampler, uv) textureGather(tex, uv, 1) +#define OGRE_GatherBlue(tex, sampler, uv) textureGather(tex, uv, 2) + +#define bufferFetch1(buffer, idx) texelFetch(buffer, idx).x + +#define OGRE_SAMPLER_ARG_DECL(samplerName) +#define OGRE_SAMPLER_ARG(samplerName) + +#define OGRE_Texture3D_float4 sampler3D +#define OGRE_OUT_REF(declType, variableName) out declType variableName +#define OGRE_INOUT_REF(declType, variableName) inout declType variableName diff --git a/modules/betsy/SCsub b/modules/betsy/SCsub new file mode 100644 index 0000000000..ed5dcbf58b --- /dev/null +++ b/modules/betsy/SCsub @@ -0,0 +1,25 @@ +# !/ usr / bin / env python +Import("env") +Import("env_modules") + +env_betsy = env_modules.Clone() +env_betsy.GLSL_HEADER("bc6h.glsl") +env_betsy.GLSL_HEADER("bc1.glsl") +env_betsy.Depends(Glob("*.glsl.gen.h"), ["#glsl_builders.py"]) + +# Thirdparty source files +thirdparty_obj = [] +thirdparty_dir = "#thirdparty/betsy/" +env_betsy.Prepend(CPPPATH=[thirdparty_dir]) + +env_thirdparty = env_betsy.Clone() +env_thirdparty.disable_warnings() +env.modules_sources += thirdparty_obj + +# Godot source files +module_obj = [] +env_betsy.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/betsy/UavCrossPlatform_piece_all.glsl b/modules/betsy/UavCrossPlatform_piece_all.glsl new file mode 100644 index 0000000000..30854df637 --- /dev/null +++ b/modules/betsy/UavCrossPlatform_piece_all.glsl @@ -0,0 +1,17 @@ + +#define OGRE_imageLoad2D(inImage, iuv) imageLoad(inImage, int2(iuv)) +#define OGRE_imageLoad2DArray(inImage, iuvw) imageLoad(inImage, int3(iuvw)) + +#define OGRE_imageWrite2D1(outImage, iuv, value) imageStore(outImage, int2(iuv), float4(value, 0, 0, 0)) +#define OGRE_imageWrite2D2(outImage, iuv, value) imageStore(outImage, int2(iuv), float4(value, 0, 0)) +#define OGRE_imageWrite2D4(outImage, iuv, value) imageStore(outImage, int2(iuv), value) + +#define OGRE_imageLoad3D(inImage, iuv) imageLoad(inImage, int3(iuv)) + +#define OGRE_imageWrite3D1(outImage, iuv, value) imageStore(outImage, int3(iuv), value) +#define OGRE_imageWrite3D4(outImage, iuv, value) imageStore(outImage, int3(iuv), value) + +#define OGRE_imageWrite2DArray1(outImage, iuvw, value) imageStore(outImage, int3(iuvw), value) +#define OGRE_imageWrite2DArray4(outImage, iuvw, value) imageStore(outImage, int3(iuvw), value) + +//#define sharedOnlyBarrier memoryBarrierShared();barrier(); diff --git a/modules/betsy/bc1.glsl b/modules/betsy/bc1.glsl new file mode 100644 index 0000000000..f1b2c28254 --- /dev/null +++ b/modules/betsy/bc1.glsl @@ -0,0 +1,483 @@ +#[versions] + +standard = ""; +dithered = "#define BC1_DITHER"; + +#[compute] +#version 450 + +#include "CrossPlatformSettings_piece_all.glsl" +#include "UavCrossPlatform_piece_all.glsl" + +#define FLT_MAX 340282346638528859811704183484516925440.0f + +layout(binding = 0) uniform sampler2D srcTex; +layout(binding = 1, rg32ui) uniform restrict writeonly uimage2D dstTexture; + +layout(std430, binding = 2) readonly restrict buffer globalBuffer { + float2 c_oMatch5[256]; + float2 c_oMatch6[256]; +}; + +layout(push_constant, std430) uniform Params { + uint p_numRefinements; + uint p_padding[3]; +} +params; + +layout(local_size_x = 8, // + local_size_y = 8, // + local_size_z = 1) in; + +float3 rgb565to888(float rgb565) { + float3 retVal; + retVal.x = floor(rgb565 / 2048.0f); + retVal.y = floor(mod(rgb565, 2048.0f) / 32.0f); + retVal.z = floor(mod(rgb565, 32.0f)); + + // This is the correct 565 to 888 conversion: + // rgb = floor( rgb * ( 255.0f / float3( 31.0f, 63.0f, 31.0f ) ) + 0.5f ) + // + // However stb_dxt follows a different one: + // rb = floor( rb * ( 256 / 32 + 8 / 32 ) ); + // g = floor( g * ( 256 / 64 + 4 / 64 ) ); + // + // I'm not sure exactly why but it's possible this is how the S3TC specifies it should be decoded + // It's quite possible this is the reason: + // http://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/ + // + // Or maybe it's just because it's cheap to do with integer shifts. + // Anyway, we follow stb_dxt's conversion just in case + // (gives almost the same result, with 1 or -1 of difference for a very few values) + // + // Perhaps when we make 888 -> 565 -> 888 it doesn't matter + // because they end up mapping to the original number + + return floor(retVal * float3(8.25f, 4.0625f, 8.25f)); +} + +float rgb888to565(float3 rgbValue) { + rgbValue.rb = floor(rgbValue.rb * 31.0f / 255.0f + 0.5f); + rgbValue.g = floor(rgbValue.g * 63.0f / 255.0f + 0.5f); + + return rgbValue.r * 2048.0f + rgbValue.g * 32.0f + rgbValue.b; +} + +// linear interpolation at 1/3 point between a and b, using desired rounding type +float3 lerp13(float3 a, float3 b) { +#ifdef STB_DXT_USE_ROUNDING_BIAS + // with rounding bias + return a + floor((b - a) * (1.0f / 3.0f) + 0.5f); +#else + // without rounding bias + return floor((2.0f * a + b) / 3.0f); +#endif +} + +/// Unpacks a block of 4 colors from two 16-bit endpoints +void EvalColors(out float3 colors[4], float c0, float c1) { + colors[0] = rgb565to888(c0); + colors[1] = rgb565to888(c1); + colors[2] = lerp13(colors[0], colors[1]); + colors[3] = lerp13(colors[1], colors[0]); +} + +/** The color optimization function. (Clever code, part 1) +@param outMinEndp16 [out] + Minimum endpoint, in RGB565 +@param outMaxEndp16 [out] + Maximum endpoint, in RGB565 +*/ +void OptimizeColorsBlock(const uint srcPixelsBlock[16], out float outMinEndp16, out float outMaxEndp16) { + // determine color distribution + float3 avgColor; + float3 minColor; + float3 maxColor; + + avgColor = minColor = maxColor = unpackUnorm4x8(srcPixelsBlock[0]).xyz; + for (int i = 1; i < 16; ++i) { + const float3 currColorUnorm = unpackUnorm4x8(srcPixelsBlock[i]).xyz; + avgColor += currColorUnorm; + minColor = min(minColor, currColorUnorm); + maxColor = max(maxColor, currColorUnorm); + } + + avgColor = round(avgColor * 255.0f / 16.0f); + maxColor *= 255.0f; + minColor *= 255.0f; + + // determine covariance matrix + float cov[6]; + for (int i = 0; i < 6; ++i) + cov[i] = 0; + + for (int i = 0; i < 16; ++i) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + float3 rgbDiff = currColor - avgColor; + + cov[0] += rgbDiff.r * rgbDiff.r; + cov[1] += rgbDiff.r * rgbDiff.g; + cov[2] += rgbDiff.r * rgbDiff.b; + cov[3] += rgbDiff.g * rgbDiff.g; + cov[4] += rgbDiff.g * rgbDiff.b; + cov[5] += rgbDiff.b * rgbDiff.b; + } + + // convert covariance matrix to float, find principal axis via power iter + for (int i = 0; i < 6; ++i) + cov[i] /= 255.0f; + + float3 vF = maxColor - minColor; + + const int nIterPower = 4; + for (int iter = 0; iter < nIterPower; ++iter) { + const float r = vF.r * cov[0] + vF.g * cov[1] + vF.b * cov[2]; + const float g = vF.r * cov[1] + vF.g * cov[3] + vF.b * cov[4]; + const float b = vF.r * cov[2] + vF.g * cov[4] + vF.b * cov[5]; + + vF.r = r; + vF.g = g; + vF.b = b; + } + + float magn = max3(abs(vF.r), abs(vF.g), abs(vF.b)); + float3 v; + + if (magn < 4.0f) { // too small, default to luminance + v.r = 299.0f; // JPEG YCbCr luma coefs, scaled by 1000. + v.g = 587.0f; + v.b = 114.0f; + } else { + v = trunc(vF * (512.0f / magn)); + } + + // Pick colors at extreme points + float3 minEndpoint, maxEndpoint; + float minDot = FLT_MAX; + float maxDot = -FLT_MAX; + for (int i = 0; i < 16; ++i) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + const float dotValue = dot(currColor, v); + + if (dotValue < minDot) { + minDot = dotValue; + minEndpoint = currColor; + } + + if (dotValue > maxDot) { + maxDot = dotValue; + maxEndpoint = currColor; + } + } + + outMinEndp16 = rgb888to565(minEndpoint); + outMaxEndp16 = rgb888to565(maxEndpoint); +} + +// The color matching function +uint MatchColorsBlock(const uint srcPixelsBlock[16], float3 color[4]) { + uint mask = 0u; + float3 dir = color[0] - color[1]; + float stops[4]; + + for (int i = 0; i < 4; ++i) + stops[i] = dot(color[i], dir); + + // think of the colors as arranged on a line; project point onto that line, then choose + // next color out of available ones. we compute the crossover points for "best color in top + // half"/"best in bottom half" and then the same inside that subinterval. + // + // relying on this 1d approximation isn't always optimal in terms of euclidean distance, + // but it's very close and a lot faster. + // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html + + float c0Point = trunc((stops[1] + stops[3]) * 0.5f); + float halfPoint = trunc((stops[3] + stops[2]) * 0.5f); + float c3Point = trunc((stops[2] + stops[0]) * 0.5f); + +#ifndef BC1_DITHER + // the version without dithering is straightforward + for (uint i = 16u; i-- > 0u;) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + + const float dotValue = dot(currColor, dir); + mask <<= 2u; + + if (dotValue < halfPoint) + mask |= ((dotValue < c0Point) ? 1u : 3u); + else + mask |= ((dotValue < c3Point) ? 2u : 0u); + } +#else + // with floyd-steinberg dithering + float4 ep1 = float4(0, 0, 0, 0); + float4 ep2 = float4(0, 0, 0, 0); + + c0Point *= 16.0f; + halfPoint *= 16.0f; + c3Point *= 16.0f; + + for (uint y = 0u; y < 4u; ++y) { + float ditherDot; + uint lmask, step; + + float3 currColor; + float dotValue; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 0]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (3 * ep2[1] + 5 * ep2[0]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[0] = dotValue - stops[step]; + lmask = step; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 1]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[1] = dotValue - stops[step]; + lmask |= step << 2u; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[2] = dotValue - stops[step]; + lmask |= step << 4u; + + currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f; + dotValue = dot(currColor, dir); + + ditherDot = (dotValue * 16.0f) + (7 * ep1[2] + 5 * ep2[3] + ep2[2]); + if (ditherDot < halfPoint) + step = (ditherDot < c0Point) ? 1u : 3u; + else + step = (ditherDot < c3Point) ? 2u : 0u; + ep1[3] = dotValue - stops[step]; + lmask |= step << 6u; + + mask |= lmask << (y * 8u); + { + float4 tmp = ep1; + ep1 = ep2; + ep2 = tmp; + } // swap + } +#endif + + return mask; +} + +// The refinement function. (Clever code, part 2) +// Tries to optimize colors to suit block contents better. +// (By solving a least squares system via normal equations+Cramer's rule) +bool RefineBlock(const uint srcPixelsBlock[16], uint mask, inout float inOutMinEndp16, + inout float inOutMaxEndp16) { + float newMin16, newMax16; + const float oldMin = inOutMinEndp16; + const float oldMax = inOutMaxEndp16; + + if ((mask ^ (mask << 2u)) < 4u) // all pixels have the same index? + { + // yes, linear system would be singular; solve using optimal + // single-color match on average color + float3 rgbVal = float3(8.0f / 255.0f, 8.0f / 255.0f, 8.0f / 255.0f); + for (int i = 0; i < 16; ++i) + rgbVal += unpackUnorm4x8(srcPixelsBlock[i]).xyz; + + rgbVal = floor(rgbVal * (255.0f / 16.0f)); + + newMax16 = c_oMatch5[uint(rgbVal.r)][0] * 2048.0f + // + c_oMatch6[uint(rgbVal.g)][0] * 32.0f + // + c_oMatch5[uint(rgbVal.b)][0]; + newMin16 = c_oMatch5[uint(rgbVal.r)][1] * 2048.0f + // + c_oMatch6[uint(rgbVal.g)][1] * 32.0f + // + c_oMatch5[uint(rgbVal.b)][1]; + } else { + const float w1Tab[4] = { 3, 0, 2, 1 }; + const float prods[4] = { 589824.0f, 2304.0f, 262402.0f, 66562.0f }; + // ^some magic to save a lot of multiplies in the accumulating loop... + // (precomputed products of weights for least squares system, accumulated inside one 32-bit + // register) + + float akku = 0.0f; + uint cm = mask; + float3 at1 = float3(0, 0, 0); + float3 at2 = float3(0, 0, 0); + for (int i = 0; i < 16; ++i, cm >>= 2u) { + const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f; + + const uint step = cm & 3u; + const float w1 = w1Tab[step]; + akku += prods[step]; + at1 += currColor * w1; + at2 += currColor; + } + + at2 = 3.0f * at2 - at1; + + // extract solutions and decide solvability + const float xx = floor(akku / 65535.0f); + const float yy = floor(mod(akku, 65535.0f) / 256.0f); + const float xy = mod(akku, 256.0f); + + float2 f_rb_g; + f_rb_g.x = 3.0f * 31.0f / 255.0f / (xx * yy - xy * xy); + f_rb_g.y = f_rb_g.x * 63.0f / 31.0f; + + // solve. + const float3 newMaxVal = clamp(floor((at1 * yy - at2 * xy) * f_rb_g.xyx + 0.5f), + float3(0.0f, 0.0f, 0.0f), float3(31, 63, 31)); + newMax16 = newMaxVal.x * 2048.0f + newMaxVal.y * 32.0f + newMaxVal.z; + + const float3 newMinVal = clamp(floor((at2 * xx - at1 * xy) * f_rb_g.xyx + 0.5f), + float3(0.0f, 0.0f, 0.0f), float3(31, 63, 31)); + newMin16 = newMinVal.x * 2048.0f + newMinVal.y * 32.0f + newMinVal.z; + } + + inOutMinEndp16 = newMin16; + inOutMaxEndp16 = newMax16; + + return oldMin != newMin16 || oldMax != newMax16; +} + +#ifdef BC1_DITHER +/// Quantizes 'srcValue' which is originally in 888 (full range), +/// converting it to 565 and then back to 888 (quantized) +float3 quant(float3 srcValue) { + srcValue = clamp(srcValue, 0.0f, 255.0f); + // Convert 888 -> 565 + srcValue = floor(srcValue * float3(31.0f / 255.0f, 63.0f / 255.0f, 31.0f / 255.0f) + 0.5f); + // Convert 565 -> 888 back + srcValue = floor(srcValue * float3(8.25f, 4.0625f, 8.25f)); + + return srcValue; +} + +void DitherBlock(const uint srcPixBlck[16], out uint dthPixBlck[16]) { + float3 ep1[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) }; + float3 ep2[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) }; + + for (uint y = 0u; y < 16u; y += 4u) { + float3 srcPixel, dithPixel; + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 0u]).xyz * 255.0f; + dithPixel = quant(srcPixel + trunc((3 * ep2[1] + 5 * ep2[0]) * (1.0f / 16.0f))); + ep1[0] = srcPixel - dithPixel; + dthPixBlck[y + 0u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 1u]).xyz * 255.0f; + dithPixel = quant( + srcPixel + trunc((7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]) * (1.0f / 16.0f))); + ep1[1] = srcPixel - dithPixel; + dthPixBlck[y + 1u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 2u]).xyz * 255.0f; + dithPixel = quant( + srcPixel + trunc((7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]) * (1.0f / 16.0f))); + ep1[2] = srcPixel - dithPixel; + dthPixBlck[y + 2u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + srcPixel = unpackUnorm4x8(srcPixBlck[y + 3u]).xyz * 255.0f; + dithPixel = quant(srcPixel + trunc((7 * ep1[2] + 5 * ep2[3] + ep2[2]) * (1.0f / 16.0f))); + ep1[3] = srcPixel - dithPixel; + dthPixBlck[y + 3u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f)); + + // swap( ep1, ep2 ) + for (uint i = 0u; i < 4u; ++i) { + float3 tmp = ep1[i]; + ep1[i] = ep2[i]; + ep2[i] = tmp; + } + } +} +#endif + +void main() { + uint srcPixelsBlock[16]; + + bool bAllColorsEqual = true; + + // Load the whole 4x4 block + const uint2 pixelsToLoadBase = gl_GlobalInvocationID.xy << 2u; + for (uint i = 0u; i < 16u; ++i) { + const uint2 pixelsToLoad = pixelsToLoadBase + uint2(i & 0x03u, i >> 2u); + const float3 srcPixels0 = OGRE_Load2D(srcTex, int2(pixelsToLoad), 0).xyz; + srcPixelsBlock[i] = packUnorm4x8(float4(srcPixels0, 1.0f)); + bAllColorsEqual = bAllColorsEqual && srcPixelsBlock[0] == srcPixelsBlock[i]; + } + + float maxEndp16, minEndp16; + uint mask = 0u; + + if (bAllColorsEqual) { + const uint3 rgbVal = uint3(unpackUnorm4x8(srcPixelsBlock[0]).xyz * 255.0f); + mask = 0xAAAAAAAAu; + maxEndp16 = + c_oMatch5[rgbVal.r][0] * 2048.0f + c_oMatch6[rgbVal.g][0] * 32.0f + c_oMatch5[rgbVal.b][0]; + minEndp16 = + c_oMatch5[rgbVal.r][1] * 2048.0f + c_oMatch6[rgbVal.g][1] * 32.0f + c_oMatch5[rgbVal.b][1]; + } else { +#ifdef BC1_DITHER + uint ditherPixelsBlock[16]; + // first step: compute dithered version for PCA if desired + DitherBlock(srcPixelsBlock, ditherPixelsBlock); +#else +#define ditherPixelsBlock srcPixelsBlock +#endif + + // second step: pca+map along principal axis + OptimizeColorsBlock(ditherPixelsBlock, minEndp16, maxEndp16); + if (minEndp16 != maxEndp16) { + float3 colors[4]; + EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted + mask = MatchColorsBlock(srcPixelsBlock, colors); + } + + // third step: refine (multiple times if requested) + bool bStopRefinement = false; + for (uint i = 0u; i < params.p_numRefinements && !bStopRefinement; ++i) { + const uint lastMask = mask; + + if (RefineBlock(ditherPixelsBlock, mask, minEndp16, maxEndp16)) { + if (minEndp16 != maxEndp16) { + float3 colors[4]; + EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted + mask = MatchColorsBlock(srcPixelsBlock, colors); + } else { + mask = 0u; + bStopRefinement = true; + } + } + + bStopRefinement = mask == lastMask || bStopRefinement; + } + } + + // write the color block + if (maxEndp16 < minEndp16) { + const float tmpValue = minEndp16; + minEndp16 = maxEndp16; + maxEndp16 = tmpValue; + mask ^= 0x55555555u; + } + + uint2 outputBytes; + outputBytes.x = uint(maxEndp16) | (uint(minEndp16) << 16u); + outputBytes.y = mask; + + uint2 dstUV = gl_GlobalInvocationID.xy; + imageStore(dstTexture, int2(dstUV), uint4(outputBytes.xy, 0u, 0u)); +} diff --git a/modules/betsy/bc6h.glsl b/modules/betsy/bc6h.glsl new file mode 100644 index 0000000000..37e7591aea --- /dev/null +++ b/modules/betsy/bc6h.glsl @@ -0,0 +1,719 @@ +#[versions] + +signed = "#define SIGNED"; +unsigned = "#define QUALITY"; // The "Quality" preset causes artifacting on signed data, so for now it's exclusive to unsigned. + +#[compute] +#version 450 + +#include "CrossPlatformSettings_piece_all.glsl" +#include "UavCrossPlatform_piece_all.glsl" + +#VERSION_DEFINES + +float3 f32tof16(float3 value) { + return float3(packHalf2x16(float2(value.x, 0.0)), + packHalf2x16(float2(value.y, 0.0)), + packHalf2x16(float2(value.z, 0.0))); +} + +float3 f16tof32(uint3 value) { + return float3(unpackHalf2x16(value.x).x, + unpackHalf2x16(value.y).x, + unpackHalf2x16(value.z).x); +} + +float f32tof16(float value) { + return packHalf2x16(float2(value.x, 0.0)); +} + +float f16tof32(uint value) { + return unpackHalf2x16(value.x).x; +} + +layout(binding = 0) uniform sampler2D srcTexture; +layout(binding = 1, rgba32ui) uniform restrict writeonly uimage2D dstTexture; + +layout(push_constant, std430) uniform Params { + float2 p_textureSizeRcp; + uint padding0; + uint padding1; +} +params; + +const float HALF_MAX = 65504.0f; +const uint PATTERN_NUM = 32u; + +#ifdef SIGNED +const float HALF_MIN = -65504.0f; +#else +const float HALF_MIN = 0.0f; +#endif + +#ifdef SIGNED +// https://github.com/godotengine/godot/pull/96377#issuecomment-2323488254 +// https://github.com/godotengine/godot/pull/96377#issuecomment-2323450950 +bool isNegative(float a) { + return a < 0.0f; +} + +float CalcSignlessMSLE(float a, float b) { + float err = log2((b + 1.0f) / (a + 1.0f)); + err = err * err; + return err; +} + +float CrossCalcMSLE(float a, float b) { + float result = 0.0f; + result += CalcSignlessMSLE(0.0f, abs(a)); + result += CalcSignlessMSLE(0.0f, abs(b)); + return result; +} + +float CalcMSLE(float3 a, float3 b) { + float result = 0.0f; + if (isNegative(a.x) != isNegative(b.x)) { + result += CrossCalcMSLE(a.x, b.x); + } else { + result += CalcSignlessMSLE(abs(a.x), abs(b.x)); + } + if (isNegative(a.y) != isNegative(b.y)) { + result += CrossCalcMSLE(a.y, b.y); + } else { + result += CalcSignlessMSLE(abs(a.y), abs(b.y)); + } + if (isNegative(a.z) != isNegative(b.z)) { + result += CrossCalcMSLE(a.z, b.z); + } else { + result += CalcSignlessMSLE(abs(a.z), abs(b.z)); + } + + return result; +} +#else +float CalcMSLE(float3 a, float3 b) { + float3 err = log2((b + 1.0f) / (a + 1.0f)); + err = err * err; + return err.x + err.y + err.z; +} +#endif + +uint PatternFixupID(uint i) { + uint ret = 15u; + ret = ((3441033216u >> i) & 0x1u) != 0 ? 2u : ret; + ret = ((845414400u >> i) & 0x1u) != 0 ? 8u : ret; + return ret; +} + +uint Pattern(uint p, uint i) { + uint p2 = p / 2u; + uint p3 = p - p2 * 2u; + + uint enc = 0u; + enc = p2 == 0u ? 2290666700u : enc; + enc = p2 == 1u ? 3972591342u : enc; + enc = p2 == 2u ? 4276930688u : enc; + enc = p2 == 3u ? 3967876808u : enc; + enc = p2 == 4u ? 4293707776u : enc; + enc = p2 == 5u ? 3892379264u : enc; + enc = p2 == 6u ? 4278255592u : enc; + enc = p2 == 7u ? 4026597360u : enc; + enc = p2 == 8u ? 9369360u : enc; + enc = p2 == 9u ? 147747072u : enc; + enc = p2 == 10u ? 1930428556u : enc; + enc = p2 == 11u ? 2362323200u : enc; + enc = p2 == 12u ? 823134348u : enc; + enc = p2 == 13u ? 913073766u : enc; + enc = p2 == 14u ? 267393000u : enc; + enc = p2 == 15u ? 966553998u : enc; + + enc = p3 != 0u ? enc >> 16u : enc; + uint ret = (enc >> i) & 0x1u; + return ret; +} + +#ifndef SIGNED +//UF +float3 Quantize7(float3 x) { + return (f32tof16(x) * 128.0f) / (0x7bff + 1.0f); +} + +float3 Quantize9(float3 x) { + return (f32tof16(x) * 512.0f) / (0x7bff + 1.0f); +} + +float3 Quantize10(float3 x) { + return (f32tof16(x) * 1024.0f) / (0x7bff + 1.0f); +} + +float3 Unquantize7(float3 x) { + return (x * 65536.0f + 0x8000) / 128.0f; +} + +float3 Unquantize9(float3 x) { + return (x * 65536.0f + 0x8000) / 512.0f; +} + +float3 Unquantize10(float3 x) { + return (x * 65536.0f + 0x8000) / 1024.0f; +} + +float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) { + float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 4096.0f); + return f16tof32(uint3(comp)); +} +#else +//SF + +float3 cmpSign(float3 value) { + float3 signVal; + signVal.x = value.x >= 0.0f ? 1.0f : -1.0f; + signVal.y = value.y >= 0.0f ? 1.0f : -1.0f; + signVal.z = value.z >= 0.0f ? 1.0f : -1.0f; + return signVal; +} + +float3 Quantize7(float3 x) { + float3 signVal = cmpSign(x); + return signVal * (f32tof16(abs(x)) * 64.0f) / (0x7bff + 1.0f); +} + +float3 Quantize9(float3 x) { + float3 signVal = cmpSign(x); + return signVal * (f32tof16(abs(x)) * 256.0f) / (0x7bff + 1.0f); +} + +float3 Quantize10(float3 x) { + float3 signVal = cmpSign(x); + return signVal * (f32tof16(abs(x)) * 512.0f) / (0x7bff + 1.0f); +} + +float3 Unquantize7(float3 x) { + float3 signVal = sign(x); + x = abs(x); + float3 finalVal = signVal * (x * 32768.0f + 0x4000) / 64.0f; + finalVal.x = x.x >= 64.0f ? 32767.0 : finalVal.x; + finalVal.y = x.y >= 64.0f ? 32767.0 : finalVal.y; + finalVal.z = x.z >= 64.0f ? 32767.0 : finalVal.z; + return finalVal; +} + +float3 Unquantize9(float3 x) { + float3 signVal = sign(x); + x = abs(x); + float3 finalVal = signVal * (x * 32768.0f + 0x4000) / 256.0f; + finalVal.x = x.x >= 256.0f ? 32767.0 : finalVal.x; + finalVal.y = x.y >= 256.0f ? 32767.0 : finalVal.y; + finalVal.z = x.z >= 256.0f ? 32767.0 : finalVal.z; + return finalVal; +} + +float3 Unquantize10(float3 x) { + float3 signVal = sign(x); + x = abs(x); + float3 finalVal = signVal * (x * 32768.0f + 0x4000) / 512.0f; + finalVal.x = x.x >= 512.0f ? 32767.0 : finalVal.x; + finalVal.y = x.y >= 512.0f ? 32767.0 : finalVal.y; + finalVal.z = x.z >= 512.0f ? 32767.0 : finalVal.z; + return finalVal; +} + +float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) { + float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f); + return f16tof32(uint3(comp)); +} +#endif + +void Swap(inout float3 a, inout float3 b) { + float3 tmp = a; + a = b; + b = tmp; +} + +void Swap(inout float a, inout float b) { + float tmp = a; + a = b; + b = tmp; +} + +uint ComputeIndex3(float texelPos, float endPoint0Pos, float endPoint1Pos) { + float r = (texelPos - endPoint0Pos) / (endPoint1Pos - endPoint0Pos); + return uint(clamp(r * 6.98182f + 0.00909f + 0.5f, 0.0f, 7.0f)); +} + +uint ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) { + float r = (texelPos - endPoint0Pos) / (endPoint1Pos - endPoint0Pos); + return uint(clamp(r * 14.93333f + 0.03333f + 0.5f, 0.0f, 15.0f)); +} + +// This adds a bitflag to quantized values that signifies whether they are negative. +void SignExtend(inout float3 v1, uint mask, uint signFlag) { + int3 v = int3(v1); + v.x = (v.x & int(mask)) | (v.x < 0 ? int(signFlag) : 0); + v.y = (v.y & int(mask)) | (v.y < 0 ? int(signFlag) : 0); + v.z = (v.z & int(mask)) | (v.z < 0 ? int(signFlag) : 0); + v1 = v; +} + +// Encodes a block with mode 11 (2x 10-bit endpoints). +void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) { + // compute endpoints (min/max RGB bbox) + float3 blockMin = texels[0]; + float3 blockMax = texels[0]; + for (uint i = 1u; i < 16u; ++i) { + blockMin = min(blockMin, texels[i]); + blockMax = max(blockMax, texels[i]); + } + + // refine endpoints in log2 RGB space + float3 refinedBlockMin = blockMax; + float3 refinedBlockMax = blockMin; + for (uint i = 0u; i < 16u; ++i) { + refinedBlockMin = min(refinedBlockMin, texels[i] == blockMin ? refinedBlockMin : texels[i]); + refinedBlockMax = max(refinedBlockMax, texels[i] == blockMax ? refinedBlockMax : texels[i]); + } + + float3 logBlockMax = log2(blockMax + 1.0f); + float3 logBlockMin = log2(blockMin + 1.0f); + float3 logRefinedBlockMax = log2(refinedBlockMax + 1.0f); + float3 logRefinedBlockMin = log2(refinedBlockMin + 1.0f); + float3 logBlockMaxExt = (logBlockMax - logBlockMin) * (1.0f / 32.0f); + logBlockMin += min(logRefinedBlockMin - logBlockMin, logBlockMaxExt); + logBlockMax -= min(logBlockMax - logRefinedBlockMax, logBlockMaxExt); + blockMin = exp2(logBlockMin) - 1.0f; + blockMax = exp2(logBlockMax) - 1.0f; + + float3 blockDir = blockMax - blockMin; + blockDir = blockDir / (blockDir.x + blockDir.y + blockDir.z); + + float3 endpoint0 = Quantize10(blockMin); + float3 endpoint1 = Quantize10(blockMax); + float endPoint0Pos = f32tof16(dot(blockMin, blockDir)); + float endPoint1Pos = f32tof16(dot(blockMax, blockDir)); + +#ifdef SIGNED + int maxVal10 = 0x1FF; + endpoint0 = clamp(endpoint0, -maxVal10, maxVal10); + endpoint1 = clamp(endpoint1, -maxVal10, maxVal10); +#endif + + // check if endpoint swap is required + float fixupTexelPos = f32tof16(dot(texels[0], blockDir)); + uint fixupIndex = ComputeIndex4(fixupTexelPos, endPoint0Pos, endPoint1Pos); + if (fixupIndex > 7) { + Swap(endPoint0Pos, endPoint1Pos); + Swap(endpoint0, endpoint1); + } + + // compute indices + uint indices[16] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + for (uint i = 0u; i < 16u; ++i) { + float texelPos = f32tof16(dot(texels[i], blockDir)); + indices[i] = ComputeIndex4(texelPos, endPoint0Pos, endPoint1Pos); + } + + // compute compression error (MSLE) + float3 endpoint0Unq = Unquantize10(endpoint0); + float3 endpoint1Unq = Unquantize10(endpoint1); + float msle = 0.0f; + for (uint i = 0u; i < 16u; ++i) { + float weight = floor((indices[i] * 64.0f) / 15.0f + 0.5f); + float3 texelUnc = FinishUnquantize(endpoint0Unq, endpoint1Unq, weight); + + msle += CalcMSLE(texels[i], texelUnc); + } + +#ifdef SIGNED + SignExtend(endpoint0, 0x1FF, 0x200); + SignExtend(endpoint1, 0x1FF, 0x200); +#endif + + // encode block for mode 11 + blockMSLE = msle; + block.x = 0x03; + + // endpoints + block.x |= uint(endpoint0.x) << 5u; + block.x |= uint(endpoint0.y) << 15u; + block.x |= uint(endpoint0.z) << 25u; + block.y |= uint(endpoint0.z) >> 7u; + block.y |= uint(endpoint1.x) << 3u; + block.y |= uint(endpoint1.y) << 13u; + block.y |= uint(endpoint1.z) << 23u; + block.z |= uint(endpoint1.z) >> 9u; + + // indices + block.z |= indices[0] << 1u; + block.z |= indices[1] << 4u; + block.z |= indices[2] << 8u; + block.z |= indices[3] << 12u; + block.z |= indices[4] << 16u; + block.z |= indices[5] << 20u; + block.z |= indices[6] << 24u; + block.z |= indices[7] << 28u; + block.w |= indices[8] << 0u; + block.w |= indices[9] << 4u; + block.w |= indices[10] << 8u; + block.w |= indices[11] << 12u; + block.w |= indices[12] << 16u; + block.w |= indices[13] << 20u; + block.w |= indices[14] << 24u; + block.w |= indices[15] << 28u; +} + +float DistToLineSq(float3 PointOnLine, float3 LineDirection, float3 Point) { + float3 w = Point - PointOnLine; + float3 x = w - dot(w, LineDirection) * LineDirection; + + return dot(x, x); +} + +// Gets the deviation from the source data of a particular pattern (smaller is better). +float EvaluateP2Pattern(uint pattern, float3 texels[16]) { + float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); + float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); + + for (uint i = 0; i < 16; ++i) { + uint paletteID = Pattern(pattern, i); + if (paletteID == 0) { + p0BlockMin = min(p0BlockMin, texels[i]); + p0BlockMax = max(p0BlockMax, texels[i]); + } else { + p1BlockMin = min(p1BlockMin, texels[i]); + p1BlockMax = max(p1BlockMax, texels[i]); + } + } + + float3 p0BlockDir = normalize(p0BlockMax - p0BlockMin); + float3 p1BlockDir = normalize(p1BlockMax - p1BlockMin); + + float sqDistanceFromLine = 0.0f; + + for (uint i = 0; i < 16; ++i) { + uint paletteID = Pattern(pattern, i); + if (paletteID == 0) { + sqDistanceFromLine += DistToLineSq(p0BlockMin, p0BlockDir, texels[i]); + } else { + sqDistanceFromLine += DistToLineSq(p1BlockMin, p1BlockDir, texels[i]); + } + } + + return sqDistanceFromLine; +} + +// Encodes a block with either mode 2 (7-bit base, 3x 6-bit delta), or mode 6 (9-bit base, 3x 5-bit delta). Both use pattern encoding. +void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, float3 texels[16]) { + float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); + float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); + + for (uint i = 0u; i < 16u; ++i) { + uint paletteID = Pattern(pattern, i); + if (paletteID == 0) { + p0BlockMin = min(p0BlockMin, texels[i]); + p0BlockMax = max(p0BlockMax, texels[i]); + } else { + p1BlockMin = min(p1BlockMin, texels[i]); + p1BlockMax = max(p1BlockMax, texels[i]); + } + } + + float3 p0BlockDir = p0BlockMax - p0BlockMin; + float3 p1BlockDir = p1BlockMax - p1BlockMin; + p0BlockDir = p0BlockDir / (p0BlockDir.x + p0BlockDir.y + p0BlockDir.z); + p1BlockDir = p1BlockDir / (p1BlockDir.x + p1BlockDir.y + p1BlockDir.z); + + float p0Endpoint0Pos = f32tof16(dot(p0BlockMin, p0BlockDir)); + float p0Endpoint1Pos = f32tof16(dot(p0BlockMax, p0BlockDir)); + float p1Endpoint0Pos = f32tof16(dot(p1BlockMin, p1BlockDir)); + float p1Endpoint1Pos = f32tof16(dot(p1BlockMax, p1BlockDir)); + + uint fixupID = PatternFixupID(pattern); + float p0FixupTexelPos = f32tof16(dot(texels[0], p0BlockDir)); + float p1FixupTexelPos = f32tof16(dot(texels[fixupID], p1BlockDir)); + uint p0FixupIndex = ComputeIndex3(p0FixupTexelPos, p0Endpoint0Pos, p0Endpoint1Pos); + uint p1FixupIndex = ComputeIndex3(p1FixupTexelPos, p1Endpoint0Pos, p1Endpoint1Pos); + if (p0FixupIndex > 3u) { + Swap(p0Endpoint0Pos, p0Endpoint1Pos); + Swap(p0BlockMin, p0BlockMax); + } + if (p1FixupIndex > 3u) { + Swap(p1Endpoint0Pos, p1Endpoint1Pos); + Swap(p1BlockMin, p1BlockMax); + } + + uint indices[16] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + for (uint i = 0u; i < 16u; ++i) { + float p0TexelPos = f32tof16(dot(texels[i], p0BlockDir)); + float p1TexelPos = f32tof16(dot(texels[i], p1BlockDir)); + uint p0Index = ComputeIndex3(p0TexelPos, p0Endpoint0Pos, p0Endpoint1Pos); + uint p1Index = ComputeIndex3(p1TexelPos, p1Endpoint0Pos, p1Endpoint1Pos); + + uint paletteID = Pattern(pattern, i); + indices[i] = paletteID == 0u ? p0Index : p1Index; + } + + float3 endpoint760 = floor(Quantize7(p0BlockMin)); + float3 endpoint761 = floor(Quantize7(p0BlockMax)); + float3 endpoint762 = floor(Quantize7(p1BlockMin)); + float3 endpoint763 = floor(Quantize7(p1BlockMax)); + + float3 endpoint950 = floor(Quantize9(p0BlockMin)); + float3 endpoint951 = floor(Quantize9(p0BlockMax)); + float3 endpoint952 = floor(Quantize9(p1BlockMin)); + float3 endpoint953 = floor(Quantize9(p1BlockMax)); + + endpoint761 = endpoint761 - endpoint760; + endpoint762 = endpoint762 - endpoint760; + endpoint763 = endpoint763 - endpoint760; + + endpoint951 = endpoint951 - endpoint950; + endpoint952 = endpoint952 - endpoint950; + endpoint953 = endpoint953 - endpoint950; + + int maxVal76 = 0x1F; + endpoint761 = clamp(endpoint761, -maxVal76, maxVal76); + endpoint762 = clamp(endpoint762, -maxVal76, maxVal76); + endpoint763 = clamp(endpoint763, -maxVal76, maxVal76); + + int maxVal95 = 0xF; + endpoint951 = clamp(endpoint951, -maxVal95, maxVal95); + endpoint952 = clamp(endpoint952, -maxVal95, maxVal95); + endpoint953 = clamp(endpoint953, -maxVal95, maxVal95); + +#ifdef SIGNED + int maxVal7 = 0x3F; + int maxVal9 = 0xFF; + endpoint760 = clamp(endpoint760, -maxVal7, maxVal7); + endpoint950 = clamp(endpoint950, -maxVal9, maxVal9); +#endif + + float3 endpoint760Unq = Unquantize7(endpoint760); + float3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761); + float3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762); + float3 endpoint763Unq = Unquantize7(endpoint760 + endpoint763); + float3 endpoint950Unq = Unquantize9(endpoint950); + float3 endpoint951Unq = Unquantize9(endpoint950 + endpoint951); + float3 endpoint952Unq = Unquantize9(endpoint950 + endpoint952); + float3 endpoint953Unq = Unquantize9(endpoint950 + endpoint953); + + float msle76 = 0.0f; + float msle95 = 0.0f; + for (uint i = 0u; i < 16u; ++i) { + uint paletteID = Pattern(pattern, i); + + float3 tmp760Unq = paletteID == 0u ? endpoint760Unq : endpoint762Unq; + float3 tmp761Unq = paletteID == 0u ? endpoint761Unq : endpoint763Unq; + float3 tmp950Unq = paletteID == 0u ? endpoint950Unq : endpoint952Unq; + float3 tmp951Unq = paletteID == 0u ? endpoint951Unq : endpoint953Unq; + + float weight = floor((indices[i] * 64.0f) / 7.0f + 0.5f); + float3 texelUnc76 = FinishUnquantize(tmp760Unq, tmp761Unq, weight); + float3 texelUnc95 = FinishUnquantize(tmp950Unq, tmp951Unq, weight); + + msle76 += CalcMSLE(texels[i], texelUnc76); + msle95 += CalcMSLE(texels[i], texelUnc95); + } + + SignExtend(endpoint761, 0x1F, 0x20); + SignExtend(endpoint762, 0x1F, 0x20); + SignExtend(endpoint763, 0x1F, 0x20); + + SignExtend(endpoint951, 0xF, 0x10); + SignExtend(endpoint952, 0xF, 0x10); + SignExtend(endpoint953, 0xF, 0x10); + +#ifdef SIGNED + SignExtend(endpoint760, 0x3F, 0x40); + SignExtend(endpoint950, 0xFF, 0x100); +#endif + + // encode block + float p2MSLE = min(msle76, msle95); + if (p2MSLE < blockMSLE) { + blockMSLE = p2MSLE; + block = uint4(0u, 0u, 0u, 0u); + + if (p2MSLE == msle76) { + // 7.6 + block.x = 0x1u; + block.x |= (uint(endpoint762.y) & 0x20u) >> 3u; + block.x |= (uint(endpoint763.y) & 0x10u) >> 1u; + block.x |= (uint(endpoint763.y) & 0x20u) >> 1u; + block.x |= uint(endpoint760.x) << 5u; + block.x |= (uint(endpoint763.z) & 0x01u) << 12u; + block.x |= (uint(endpoint763.z) & 0x02u) << 12u; + block.x |= (uint(endpoint762.z) & 0x10u) << 10u; + block.x |= uint(endpoint760.y) << 15u; + block.x |= (uint(endpoint762.z) & 0x20u) << 17u; + block.x |= (uint(endpoint763.z) & 0x04u) << 21u; + block.x |= (uint(endpoint762.y) & 0x10u) << 20u; + block.x |= uint(endpoint760.z) << 25u; + block.y |= (uint(endpoint763.z) & 0x08u) >> 3u; + block.y |= (uint(endpoint763.z) & 0x20u) >> 4u; + block.y |= (uint(endpoint763.z) & 0x10u) >> 2u; + block.y |= uint(endpoint761.x) << 3u; + block.y |= (uint(endpoint762.y) & 0x0Fu) << 9u; + block.y |= uint(endpoint761.y) << 13u; + block.y |= (uint(endpoint763.y) & 0x0Fu) << 19u; + block.y |= uint(endpoint761.z) << 23u; + block.y |= (uint(endpoint762.z) & 0x07u) << 29u; + block.z |= (uint(endpoint762.z) & 0x08u) >> 3u; + block.z |= uint(endpoint762.x) << 1u; + block.z |= uint(endpoint763.x) << 7u; + } else { + // 9.5 + block.x = 0xEu; + block.x |= uint(endpoint950.x) << 5u; + block.x |= (uint(endpoint952.z) & 0x10u) << 10u; + block.x |= uint(endpoint950.y) << 15u; + block.x |= (uint(endpoint952.y) & 0x10u) << 20u; + block.x |= uint(endpoint950.z) << 25u; + block.y |= uint(endpoint950.z) >> 7u; + block.y |= (uint(endpoint953.z) & 0x10u) >> 2u; + block.y |= uint(endpoint951.x) << 3u; + block.y |= (uint(endpoint953.y) & 0x10u) << 4u; + block.y |= (uint(endpoint952.y) & 0x0Fu) << 9u; + block.y |= uint(endpoint951.y) << 13u; + block.y |= (uint(endpoint953.z) & 0x01u) << 18u; + block.y |= (uint(endpoint953.y) & 0x0Fu) << 19u; + block.y |= uint(endpoint951.z) << 23u; + block.y |= (uint(endpoint953.z) & 0x02u) << 27u; + block.y |= uint(endpoint952.z) << 29u; + block.z |= (uint(endpoint952.z) & 0x08u) >> 3u; + block.z |= uint(endpoint952.x) << 1u; + block.z |= (uint(endpoint953.z) & 0x04u) << 4u; + block.z |= uint(endpoint953.x) << 7u; + block.z |= (uint(endpoint953.z) & 0x08u) << 9u; + } + + block.z |= pattern << 13u; + uint blockFixupID = PatternFixupID(pattern); + if (blockFixupID == 15u) { + block.z |= indices[0] << 18u; + block.z |= indices[1] << 20u; + block.z |= indices[2] << 23u; + block.z |= indices[3] << 26u; + block.z |= indices[4] << 29u; + block.w |= indices[5] << 0u; + block.w |= indices[6] << 3u; + block.w |= indices[7] << 6u; + block.w |= indices[8] << 9u; + block.w |= indices[9] << 12u; + block.w |= indices[10] << 15u; + block.w |= indices[11] << 18u; + block.w |= indices[12] << 21u; + block.w |= indices[13] << 24u; + block.w |= indices[14] << 27u; + block.w |= indices[15] << 30u; + } else if (blockFixupID == 2u) { + block.z |= indices[0] << 18u; + block.z |= indices[1] << 20u; + block.z |= indices[2] << 23u; + block.z |= indices[3] << 25u; + block.z |= indices[4] << 28u; + block.z |= indices[5] << 31u; + block.w |= indices[5] >> 1u; + block.w |= indices[6] << 2u; + block.w |= indices[7] << 5u; + block.w |= indices[8] << 8u; + block.w |= indices[9] << 11u; + block.w |= indices[10] << 14u; + block.w |= indices[11] << 17u; + block.w |= indices[12] << 20u; + block.w |= indices[13] << 23u; + block.w |= indices[14] << 26u; + block.w |= indices[15] << 29u; + } else { + block.z |= indices[0] << 18u; + block.z |= indices[1] << 20u; + block.z |= indices[2] << 23u; + block.z |= indices[3] << 26u; + block.z |= indices[4] << 29u; + block.w |= indices[5] << 0u; + block.w |= indices[6] << 3u; + block.w |= indices[7] << 6u; + block.w |= indices[8] << 9u; + block.w |= indices[9] << 11u; + block.w |= indices[10] << 14u; + block.w |= indices[11] << 17u; + block.w |= indices[12] << 20u; + block.w |= indices[13] << 23u; + block.w |= indices[14] << 26u; + block.w |= indices[15] << 29u; + } + } +} + +layout(local_size_x = 8, + local_size_y = 8, + local_size_z = 1) in; + +void main() { + // gather texels for current 4x4 block + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + // 12 13 14 15 + float2 uv = gl_GlobalInvocationID.xy * params.p_textureSizeRcp * 4.0f + params.p_textureSizeRcp; + float2 block0UV = uv; + float2 block1UV = uv + float2(2.0f * params.p_textureSizeRcp.x, 0.0f); + float2 block2UV = uv + float2(0.0f, 2.0f * params.p_textureSizeRcp.y); + float2 block3UV = uv + float2(2.0f * params.p_textureSizeRcp.x, 2.0f * params.p_textureSizeRcp.y); + float4 block0X = OGRE_GatherRed(srcTexture, pointSampler, block0UV); + float4 block1X = OGRE_GatherRed(srcTexture, pointSampler, block1UV); + float4 block2X = OGRE_GatherRed(srcTexture, pointSampler, block2UV); + float4 block3X = OGRE_GatherRed(srcTexture, pointSampler, block3UV); + float4 block0Y = OGRE_GatherGreen(srcTexture, pointSampler, block0UV); + float4 block1Y = OGRE_GatherGreen(srcTexture, pointSampler, block1UV); + float4 block2Y = OGRE_GatherGreen(srcTexture, pointSampler, block2UV); + float4 block3Y = OGRE_GatherGreen(srcTexture, pointSampler, block3UV); + float4 block0Z = OGRE_GatherBlue(srcTexture, pointSampler, block0UV); + float4 block1Z = OGRE_GatherBlue(srcTexture, pointSampler, block1UV); + float4 block2Z = OGRE_GatherBlue(srcTexture, pointSampler, block2UV); + float4 block3Z = OGRE_GatherBlue(srcTexture, pointSampler, block3UV); + + float3 texels[16]; + texels[0] = float3(block0X.w, block0Y.w, block0Z.w); + texels[1] = float3(block0X.z, block0Y.z, block0Z.z); + texels[2] = float3(block1X.w, block1Y.w, block1Z.w); + texels[3] = float3(block1X.z, block1Y.z, block1Z.z); + texels[4] = float3(block0X.x, block0Y.x, block0Z.x); + texels[5] = float3(block0X.y, block0Y.y, block0Z.y); + texels[6] = float3(block1X.x, block1Y.x, block1Z.x); + texels[7] = float3(block1X.y, block1Y.y, block1Z.y); + texels[8] = float3(block2X.w, block2Y.w, block2Z.w); + texels[9] = float3(block2X.z, block2Y.z, block2Z.z); + texels[10] = float3(block3X.w, block3Y.w, block3Z.w); + texels[11] = float3(block3X.z, block3Y.z, block3Z.z); + texels[12] = float3(block2X.x, block2Y.x, block2Z.x); + texels[13] = float3(block2X.y, block2Y.y, block2Z.y); + texels[14] = float3(block3X.x, block3Y.x, block3Z.x); + texels[15] = float3(block3X.y, block3Y.y, block3Z.y); + + uint4 block = uint4(0u, 0u, 0u, 0u); + float blockMSLE = 0.0f; + + EncodeP1(block, blockMSLE, texels); + +#ifdef QUALITY + float bestScore = EvaluateP2Pattern(0, texels); + uint bestPattern = 0; + + for (uint i = 1u; i < PATTERN_NUM; ++i) { + float score = EvaluateP2Pattern(i, texels); + + if (score < bestScore) { + bestPattern = i; + bestScore = score; + } + } + + EncodeP2Pattern(block, blockMSLE, bestPattern, texels); +#endif + + imageStore(dstTexture, int2(gl_GlobalInvocationID.xy), block); +} diff --git a/modules/betsy/betsy_bc1.h b/modules/betsy/betsy_bc1.h new file mode 100644 index 0000000000..2274ed0a81 --- /dev/null +++ b/modules/betsy/betsy_bc1.h @@ -0,0 +1,1061 @@ +/**************************************************************************/ +/* betsy_bc1.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 BETSY_BC1_H +#define BETSY_BC1_H + +constexpr const float dxt1_encoding_table[1024] = { + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 1, + 1, + 2, + 0, + 2, + 0, + 0, + 4, + 2, + 1, + 2, + 1, + 2, + 1, + 3, + 0, + 3, + 0, + 3, + 0, + 3, + 1, + 1, + 5, + 3, + 2, + 3, + 2, + 4, + 0, + 4, + 0, + 4, + 1, + 4, + 1, + 4, + 2, + 4, + 2, + 4, + 2, + 3, + 5, + 5, + 1, + 5, + 1, + 5, + 2, + 4, + 4, + 5, + 3, + 5, + 3, + 5, + 3, + 6, + 2, + 6, + 2, + 6, + 2, + 6, + 3, + 5, + 5, + 6, + 4, + 6, + 4, + 4, + 8, + 7, + 3, + 7, + 3, + 7, + 3, + 7, + 4, + 7, + 4, + 7, + 4, + 7, + 5, + 5, + 9, + 7, + 6, + 7, + 6, + 8, + 4, + 8, + 4, + 8, + 5, + 8, + 5, + 8, + 6, + 8, + 6, + 8, + 6, + 7, + 9, + 9, + 5, + 9, + 5, + 9, + 6, + 8, + 8, + 9, + 7, + 9, + 7, + 9, + 7, + 10, + 6, + 10, + 6, + 10, + 6, + 10, + 7, + 9, + 9, + 10, + 8, + 10, + 8, + 8, + 12, + 11, + 7, + 11, + 7, + 11, + 7, + 11, + 8, + 11, + 8, + 11, + 8, + 11, + 9, + 9, + 13, + 11, + 10, + 11, + 10, + 12, + 8, + 12, + 8, + 12, + 9, + 12, + 9, + 12, + 10, + 12, + 10, + 12, + 10, + 11, + 13, + 13, + 9, + 13, + 9, + 13, + 10, + 12, + 12, + 13, + 11, + 13, + 11, + 13, + 11, + 14, + 10, + 14, + 10, + 14, + 10, + 14, + 11, + 13, + 13, + 14, + 12, + 14, + 12, + 12, + 16, + 15, + 11, + 15, + 11, + 15, + 11, + 15, + 12, + 15, + 12, + 15, + 12, + 15, + 13, + 13, + 17, + 15, + 14, + 15, + 14, + 16, + 12, + 16, + 12, + 16, + 13, + 16, + 13, + 16, + 14, + 16, + 14, + 16, + 14, + 15, + 17, + 17, + 13, + 17, + 13, + 17, + 14, + 16, + 16, + 17, + 15, + 17, + 15, + 17, + 15, + 18, + 14, + 18, + 14, + 18, + 14, + 18, + 15, + 17, + 17, + 18, + 16, + 18, + 16, + 16, + 20, + 19, + 15, + 19, + 15, + 19, + 15, + 19, + 16, + 19, + 16, + 19, + 16, + 19, + 17, + 17, + 21, + 19, + 18, + 19, + 18, + 20, + 16, + 20, + 16, + 20, + 17, + 20, + 17, + 20, + 18, + 20, + 18, + 20, + 18, + 19, + 21, + 21, + 17, + 21, + 17, + 21, + 18, + 20, + 20, + 21, + 19, + 21, + 19, + 21, + 19, + 22, + 18, + 22, + 18, + 22, + 18, + 22, + 19, + 21, + 21, + 22, + 20, + 22, + 20, + 20, + 24, + 23, + 19, + 23, + 19, + 23, + 19, + 23, + 20, + 23, + 20, + 23, + 20, + 23, + 21, + 21, + 25, + 23, + 22, + 23, + 22, + 24, + 20, + 24, + 20, + 24, + 21, + 24, + 21, + 24, + 22, + 24, + 22, + 24, + 22, + 23, + 25, + 25, + 21, + 25, + 21, + 25, + 22, + 24, + 24, + 25, + 23, + 25, + 23, + 25, + 23, + 26, + 22, + 26, + 22, + 26, + 22, + 26, + 23, + 25, + 25, + 26, + 24, + 26, + 24, + 24, + 28, + 27, + 23, + 27, + 23, + 27, + 23, + 27, + 24, + 27, + 24, + 27, + 24, + 27, + 25, + 25, + 29, + 27, + 26, + 27, + 26, + 28, + 24, + 28, + 24, + 28, + 25, + 28, + 25, + 28, + 26, + 28, + 26, + 28, + 26, + 27, + 29, + 29, + 25, + 29, + 25, + 29, + 26, + 28, + 28, + 29, + 27, + 29, + 27, + 29, + 27, + 30, + 26, + 30, + 26, + 30, + 26, + 30, + 27, + 29, + 29, + 30, + 28, + 30, + 28, + 30, + 28, + 31, + 27, + 31, + 27, + 31, + 27, + 31, + 28, + 31, + 28, + 31, + 28, + 31, + 29, + 31, + 29, + 31, + 30, + 31, + 30, + 31, + 30, + 31, + 31, + 31, + 31, + 0, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 1, + 2, + 0, + 2, + 1, + 3, + 0, + 3, + 0, + 3, + 1, + 4, + 0, + 4, + 0, + 4, + 1, + 5, + 0, + 5, + 1, + 6, + 0, + 6, + 0, + 6, + 1, + 7, + 0, + 7, + 0, + 7, + 1, + 8, + 0, + 8, + 1, + 8, + 1, + 8, + 2, + 9, + 1, + 9, + 2, + 9, + 2, + 9, + 3, + 10, + 2, + 10, + 3, + 10, + 3, + 10, + 4, + 11, + 3, + 11, + 4, + 11, + 4, + 11, + 5, + 12, + 4, + 12, + 5, + 12, + 5, + 12, + 6, + 13, + 5, + 13, + 6, + 8, + 16, + 13, + 7, + 14, + 6, + 14, + 7, + 9, + 17, + 14, + 8, + 15, + 7, + 15, + 8, + 11, + 16, + 15, + 9, + 15, + 10, + 16, + 8, + 16, + 9, + 16, + 10, + 15, + 13, + 17, + 9, + 17, + 10, + 17, + 11, + 15, + 16, + 18, + 10, + 18, + 11, + 18, + 12, + 16, + 16, + 19, + 11, + 19, + 12, + 19, + 13, + 17, + 17, + 20, + 12, + 20, + 13, + 20, + 14, + 19, + 16, + 21, + 13, + 21, + 14, + 21, + 15, + 20, + 17, + 22, + 14, + 22, + 15, + 25, + 10, + 22, + 16, + 23, + 15, + 23, + 16, + 26, + 11, + 23, + 17, + 24, + 16, + 24, + 17, + 27, + 12, + 24, + 18, + 25, + 17, + 25, + 18, + 28, + 13, + 25, + 19, + 26, + 18, + 26, + 19, + 29, + 14, + 26, + 20, + 27, + 19, + 27, + 20, + 30, + 15, + 27, + 21, + 28, + 20, + 28, + 21, + 28, + 21, + 28, + 22, + 29, + 21, + 29, + 22, + 24, + 32, + 29, + 23, + 30, + 22, + 30, + 23, + 25, + 33, + 30, + 24, + 31, + 23, + 31, + 24, + 27, + 32, + 31, + 25, + 31, + 26, + 32, + 24, + 32, + 25, + 32, + 26, + 31, + 29, + 33, + 25, + 33, + 26, + 33, + 27, + 31, + 32, + 34, + 26, + 34, + 27, + 34, + 28, + 32, + 32, + 35, + 27, + 35, + 28, + 35, + 29, + 33, + 33, + 36, + 28, + 36, + 29, + 36, + 30, + 35, + 32, + 37, + 29, + 37, + 30, + 37, + 31, + 36, + 33, + 38, + 30, + 38, + 31, + 41, + 26, + 38, + 32, + 39, + 31, + 39, + 32, + 42, + 27, + 39, + 33, + 40, + 32, + 40, + 33, + 43, + 28, + 40, + 34, + 41, + 33, + 41, + 34, + 44, + 29, + 41, + 35, + 42, + 34, + 42, + 35, + 45, + 30, + 42, + 36, + 43, + 35, + 43, + 36, + 46, + 31, + 43, + 37, + 44, + 36, + 44, + 37, + 44, + 37, + 44, + 38, + 45, + 37, + 45, + 38, + 40, + 48, + 45, + 39, + 46, + 38, + 46, + 39, + 41, + 49, + 46, + 40, + 47, + 39, + 47, + 40, + 43, + 48, + 47, + 41, + 47, + 42, + 48, + 40, + 48, + 41, + 48, + 42, + 47, + 45, + 49, + 41, + 49, + 42, + 49, + 43, + 47, + 48, + 50, + 42, + 50, + 43, + 50, + 44, + 48, + 48, + 51, + 43, + 51, + 44, + 51, + 45, + 49, + 49, + 52, + 44, + 52, + 45, + 52, + 46, + 51, + 48, + 53, + 45, + 53, + 46, + 53, + 47, + 52, + 49, + 54, + 46, + 54, + 47, + 57, + 42, + 54, + 48, + 55, + 47, + 55, + 48, + 58, + 43, + 55, + 49, + 56, + 48, + 56, + 49, + 59, + 44, + 56, + 50, + 57, + 49, + 57, + 50, + 60, + 45, + 57, + 51, + 58, + 50, + 58, + 51, + 61, + 46, + 58, + 52, + 59, + 51, + 59, + 52, + 62, + 47, + 59, + 53, + 60, + 52, + 60, + 53, + 60, + 53, + 60, + 54, + 61, + 53, + 61, + 54, + 61, + 54, + 61, + 55, + 62, + 54, + 62, + 55, + 62, + 55, + 62, + 56, + 63, + 55, + 63, + 56, + 63, + 56, + 63, + 57, + 63, + 58, + 63, + 59, + 63, + 59, + 63, + 60, + 63, + 61, + 63, + 62, + 63, + 62, + 63, + 63, +}; + +#endif // BETSY_BC1_H diff --git a/modules/betsy/config.py b/modules/betsy/config.py new file mode 100644 index 0000000000..eb565b85b9 --- /dev/null +++ b/modules/betsy/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return env.editor_build + + +def configure(env): + pass diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp new file mode 100644 index 0000000000..7b4d8b3dfb --- /dev/null +++ b/modules/betsy/image_compress_betsy.cpp @@ -0,0 +1,537 @@ +/**************************************************************************/ +/* image_compress_betsy.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 "image_compress_betsy.h" + +#include "core/config/project_settings.h" + +#include "betsy_bc1.h" + +#include "bc1.glsl.gen.h" +#include "bc6h.glsl.gen.h" + +static Mutex betsy_mutex; +static BetsyCompressor *betsy = nullptr; + +void BetsyCompressor::_init() { + // Create local RD. + RenderingContextDriver *rcd = nullptr; + RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device(); + + if (rd == nullptr) { +#if defined(RD_ENABLED) +#if defined(METAL_ENABLED) + rcd = memnew(RenderingContextDriverMetal); + rd = memnew(RenderingDevice); +#endif +#if defined(VULKAN_ENABLED) + if (rcd == nullptr) { + rcd = memnew(RenderingContextDriverVulkan); + rd = memnew(RenderingDevice); + } +#endif +#endif + if (rcd != nullptr && rd != nullptr) { + Error err = rcd->initialize(); + if (err == OK) { + err = rd->initialize(rcd); + } + + if (err != OK) { + memdelete(rd); + memdelete(rcd); + rd = nullptr; + rcd = nullptr; + } + } + } + + ERR_FAIL_NULL_MSG(rd, "Unable to create a local RenderingDevice."); + + compress_rd = rd; + compress_rcd = rcd; + + // Create the sampler state. + RD::SamplerState src_sampler_state; + { + src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; + src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; + src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST; + src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST; + src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST; + } + + src_sampler = compress_rd->sampler_create(src_sampler_state); +} + +void BetsyCompressor::init() { + WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &BetsyCompressor::_thread_loop), true); + command_queue.set_pump_task_id(tid); + command_queue.push(this, &BetsyCompressor::_assign_mt_ids, tid); + command_queue.push_and_sync(this, &BetsyCompressor::_init); + DEV_ASSERT(task_id == tid); +} + +void BetsyCompressor::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) { + task_id = p_pump_task_id; +} + +// Yield thread to WTP so other tasks can be done on it. +// Automatically regains control as soon a task is pushed to the command queue. +void BetsyCompressor::_thread_loop() { + while (!exit) { + WorkerThreadPool::get_singleton()->yield(); + command_queue.flush_all(); + } +} + +void BetsyCompressor::_thread_exit() { + exit = true; + + if (compress_rd != nullptr) { + if (dxt1_encoding_table_buffer.is_valid()) { + compress_rd->free(dxt1_encoding_table_buffer); + } + + compress_rd->free(src_sampler); + + // Clear the shader cache, pipelines will be unreferenced automatically. + for (KeyValue<String, BetsyShader> &E : cached_shaders) { + if (E.value.compiled.is_valid()) { + compress_rd->free(E.value.compiled); + } + } + cached_shaders.clear(); + } +} + +void BetsyCompressor::finish() { + command_queue.push(this, &BetsyCompressor::_thread_exit); + if (task_id != WorkerThreadPool::INVALID_TASK_ID) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id); + task_id = WorkerThreadPool::INVALID_TASK_ID; + } + + if (compress_rd != nullptr) { + // Free the RD (and RCD if necessary). + memdelete(compress_rd); + compress_rd = nullptr; + if (compress_rcd != nullptr) { + memdelete(compress_rcd); + compress_rcd = nullptr; + } + } +} + +// Helper functions. + +static int get_next_multiple(int n, int m) { + return n + (m - (n % m)); +} + +static String get_shader_name(BetsyFormat p_format) { + switch (p_format) { + case BETSY_FORMAT_BC1: + case BETSY_FORMAT_BC1_DITHER: + return "BC1"; + + case BETSY_FORMAT_BC3: + return "BC3"; + + case BETSY_FORMAT_BC6_SIGNED: + case BETSY_FORMAT_BC6_UNSIGNED: + return "BC6"; + + default: + return ""; + } +} + +Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + + if (r_img->is_compressed()) { + return ERR_INVALID_DATA; + } + + Error err = OK; + + // Destination format. + Image::Format dest_format = Image::FORMAT_MAX; + RD::DataFormat dst_rd_format = RD::DATA_FORMAT_MAX; + + String version = ""; + + switch (p_format) { + case BETSY_FORMAT_BC1: + version = "standard"; + dst_rd_format = RD::DATA_FORMAT_R32G32_UINT; + dest_format = Image::FORMAT_DXT1; + break; + + case BETSY_FORMAT_BC1_DITHER: + version = "dithered"; + dst_rd_format = RD::DATA_FORMAT_R32G32_UINT; + dest_format = Image::FORMAT_DXT1; + break; + + case BETSY_FORMAT_BC6_SIGNED: + version = "signed"; + dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT; + dest_format = Image::FORMAT_BPTC_RGBF; + break; + + case BETSY_FORMAT_BC6_UNSIGNED: + version = "unsigned"; + dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT; + dest_format = Image::FORMAT_BPTC_RGBFU; + break; + + default: + err = ERR_INVALID_PARAMETER; + break; + } + + const String shader_name = get_shader_name(p_format) + "-" + version; + BetsyShader shader; + + if (cached_shaders.has(shader_name)) { + shader = cached_shaders[shader_name]; + + } else { + Ref<RDShaderFile> source; + source.instantiate(); + + switch (p_format) { + case BETSY_FORMAT_BC1: + case BETSY_FORMAT_BC1_DITHER: + err = source->parse_versions_from_text(bc1_shader_glsl); + break; + + case BETSY_FORMAT_BC6_UNSIGNED: + case BETSY_FORMAT_BC6_SIGNED: + err = source->parse_versions_from_text(bc6h_shader_glsl); + break; + + default: + err = ERR_INVALID_PARAMETER; + break; + } + + if (err != OK) { + source->print_errors("Betsy compress shader"); + return err; + } + + // Compile the shader, return early if invalid. + shader.compiled = compress_rd->shader_create_from_spirv(source->get_spirv_stages(version)); + if (shader.compiled.is_null()) { + return ERR_CANT_CREATE; + } + + // Compile the pipeline, return early if invalid. + shader.pipeline = compress_rd->compute_pipeline_create(shader.compiled); + if (shader.pipeline.is_null()) { + return ERR_CANT_CREATE; + } + + cached_shaders[shader_name] = shader; + } + + if (shader.compiled.is_null() || shader.pipeline.is_null()) { + return ERR_INVALID_DATA; + } + + // src_texture format information. + RD::TextureFormat src_texture_format; + { + src_texture_format.array_layers = 1; + src_texture_format.depth = 1; + src_texture_format.mipmaps = 1; + src_texture_format.texture_type = RD::TEXTURE_TYPE_2D; + src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT; + } + + switch (r_img->get_format()) { + case Image::FORMAT_L8: + r_img->convert(Image::FORMAT_RGBA8); + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + + case Image::FORMAT_LA8: + r_img->convert(Image::FORMAT_RGBA8); + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + + case Image::FORMAT_R8: + src_texture_format.format = RD::DATA_FORMAT_R8_UNORM; + break; + + case Image::FORMAT_RG8: + src_texture_format.format = RD::DATA_FORMAT_R8G8_UNORM; + break; + + case Image::FORMAT_RGB8: + r_img->convert(Image::FORMAT_RGBA8); + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + + case Image::FORMAT_RGBA8: + src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + break; + + case Image::FORMAT_RH: + src_texture_format.format = RD::DATA_FORMAT_R16_SFLOAT; + break; + + case Image::FORMAT_RGH: + src_texture_format.format = RD::DATA_FORMAT_R16G16_SFLOAT; + break; + + case Image::FORMAT_RGBH: + r_img->convert(Image::FORMAT_RGBAH); + src_texture_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + break; + + case Image::FORMAT_RGBAH: + src_texture_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + break; + + case Image::FORMAT_RF: + src_texture_format.format = RD::DATA_FORMAT_R32_SFLOAT; + break; + + case Image::FORMAT_RGF: + src_texture_format.format = RD::DATA_FORMAT_R32G32_SFLOAT; + break; + + case Image::FORMAT_RGBF: + r_img->convert(Image::FORMAT_RGBAF); + src_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + break; + + case Image::FORMAT_RGBAF: + src_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + break; + + case Image::FORMAT_RGBE9995: + src_texture_format.format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32; + break; + + default: { + return err; + } + } + + // For the destination format just copy the source format and change the usage bits. + RD::TextureFormat dst_texture_format = src_texture_format; + dst_texture_format.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + dst_texture_format.format = dst_rd_format; + + // Encoding table setup. + if (dest_format == Image::FORMAT_DXT1 && dxt1_encoding_table_buffer.is_null()) { + Vector<uint8_t> data; + data.resize(1024 * 4); + memcpy(data.ptrw(), dxt1_encoding_table, 1024 * 4); + + dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(1024 * 4, data); + } + + const int mip_count = r_img->get_mipmap_count() + 1; + + // Container for the compressed data. + Vector<uint8_t> dst_data; + dst_data.resize(Image::get_image_data_size(r_img->get_width(), r_img->get_height(), dest_format, r_img->has_mipmaps())); + uint8_t *dst_data_ptr = dst_data.ptrw(); + + Vector<Vector<uint8_t>> src_images; + src_images.push_back(Vector<uint8_t>()); + Vector<uint8_t> *src_image_ptr = src_images.ptrw(); + + // Compress each mipmap. + for (int i = 0; i < mip_count; i++) { + int64_t ofs, size; + int width, height; + r_img->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); + + // Set the source texture width and size. + src_texture_format.height = height; + src_texture_format.width = width; + + // Set the destination texture width and size. + dst_texture_format.height = (height + 3) >> 2; + dst_texture_format.width = (width + 3) >> 2; + + // Create a buffer filled with the source mip layer data. + src_image_ptr[0].resize(size); + memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + ofs, size); + + // Create the textures on the GPU. + RID src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images); + RID dst_texture = compress_rd->texture_create(dst_texture_format, RD::TextureView()); + + Vector<RD::Uniform> uniforms; + { + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE; + u.binding = 0; + u.append_id(src_sampler); + u.append_id(src_texture); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 1; + u.append_id(dst_texture); + uniforms.push_back(u); + } + + if (dest_format == Image::FORMAT_DXT1) { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 2; + u.append_id(dxt1_encoding_table_buffer); + uniforms.push_back(u); + } + } + + RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0); + RD::ComputeListID compute_list = compress_rd->compute_list_begin(); + + compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline); + compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0); + + if (dest_format == Image::FORMAT_BPTC_RGBFU || dest_format == Image::FORMAT_BPTC_RGBF) { + BC6PushConstant push_constant; + push_constant.sizeX = 1.0f / width; + push_constant.sizeY = 1.0f / height; + push_constant.padding[0] = 0; + push_constant.padding[1] = 0; + + compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant)); + + } else { + BC1PushConstant push_constant; + push_constant.num_refines = 2; + push_constant.padding[0] = 0; + push_constant.padding[1] = 0; + push_constant.padding[2] = 0; + + compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant)); + } + + compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1); + compress_rd->compute_list_end(); + + compress_rd->submit(); + compress_rd->sync(); + + // Copy data from the GPU to the buffer. + const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture, 0); + int64_t dst_ofs = Image::get_image_mipmap_offset(r_img->get_width(), r_img->get_height(), dest_format, i); + + memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size()); + + // Free the source and dest texture. + compress_rd->free(dst_texture); + compress_rd->free(src_texture); + } + + src_images.clear(); + + // Set the compressed data to the image. + r_img->set_data(r_img->get_width(), r_img->get_height(), r_img->has_mipmaps(), dest_format, dst_data); + + print_verbose(vformat("Betsy: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); + + return OK; +} + +void ensure_betsy_exists() { + betsy_mutex.lock(); + if (betsy == nullptr) { + betsy = memnew(BetsyCompressor); + betsy->init(); + } + betsy_mutex.unlock(); +} + +Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) { + ensure_betsy_exists(); + Image::Format format = r_img->get_format(); + Error result = ERR_UNAVAILABLE; + + if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) { + if (r_img->detect_signed()) { + result = betsy->compress(BETSY_FORMAT_BC6_SIGNED, r_img); + } else { + result = betsy->compress(BETSY_FORMAT_BC6_UNSIGNED, r_img); + } + } + + if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) { + free_device(); + } + + return result; +} + +Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) { + ensure_betsy_exists(); + Error result = ERR_UNAVAILABLE; + + switch (p_channels) { + case Image::USED_CHANNELS_RGB: + result = betsy->compress(BETSY_FORMAT_BC1_DITHER, r_img); + break; + + case Image::USED_CHANNELS_L: + result = betsy->compress(BETSY_FORMAT_BC1, r_img); + break; + + default: + break; + } + + if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) { + free_device(); + } + + return result; +} + +void free_device() { + if (betsy != nullptr) { + betsy->finish(); + memdelete(betsy); + } +} diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h new file mode 100644 index 0000000000..70e4ae85ed --- /dev/null +++ b/modules/betsy/image_compress_betsy.h @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* image_compress_betsy.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 IMAGE_COMPRESS_BETSY_H +#define IMAGE_COMPRESS_BETSY_H + +#include "core/io/image.h" +#include "core/object/worker_thread_pool.h" +#include "core/os/thread.h" +#include "core/templates/command_queue_mt.h" + +#include "servers/rendering/rendering_device_binds.h" +#include "servers/rendering/rendering_server_default.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_context_driver_vulkan.h" +#endif +#if defined(METAL_ENABLED) +#include "drivers/metal/rendering_context_driver_metal.h" +#endif + +enum BetsyFormat { + BETSY_FORMAT_BC1, + BETSY_FORMAT_BC1_DITHER, + BETSY_FORMAT_BC3, + BETSY_FORMAT_BC6_SIGNED, + BETSY_FORMAT_BC6_UNSIGNED, +}; + +struct BC6PushConstant { + float sizeX; + float sizeY; + uint32_t padding[2]; +}; + +struct BC1PushConstant { + uint32_t num_refines; + uint32_t padding[3]; +}; + +void free_device(); + +Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels); +Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels); + +class BetsyCompressor : public Object { + mutable CommandQueueMT command_queue; + bool exit = false; + WorkerThreadPool::TaskID task_id = WorkerThreadPool::INVALID_TASK_ID; + + struct BetsyShader { + RID compiled; + RID pipeline; + }; + + // Resources shared by all compression formats. + RenderingDevice *compress_rd = nullptr; + RenderingContextDriver *compress_rcd = nullptr; + HashMap<String, BetsyShader> cached_shaders; + RID src_sampler = RID(); + + // Format-specific resources. + RID dxt1_encoding_table_buffer = RID(); + + void _init(); + void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id); + void _thread_loop(); + void _thread_exit(); + + Error _compress(BetsyFormat p_format, Image *r_img); + +public: + void init(); + void finish(); + + Error compress(BetsyFormat p_format, Image *r_img) { + Error err; + command_queue.push_and_ret(this, &BetsyCompressor::_compress, p_format, r_img, &err); + return err; + } +}; + +#endif // IMAGE_COMPRESS_BETSY_H diff --git a/modules/betsy/register_types.cpp b/modules/betsy/register_types.cpp new file mode 100644 index 0000000000..a3a3b5a99b --- /dev/null +++ b/modules/betsy/register_types.cpp @@ -0,0 +1,50 @@ +/**************************************************************************/ +/* register_types.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 "register_types.h" + +#include "image_compress_betsy.h" + +void initialize_betsy_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + Image::_image_compress_bptc_rd_func = _betsy_compress_bptc; + Image::_image_compress_bc_rd_func = _betsy_compress_s3tc; +} + +void uninitialize_betsy_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + free_device(); +} diff --git a/modules/betsy/register_types.h b/modules/betsy/register_types.h new file mode 100644 index 0000000000..0ce6c553b6 --- /dev/null +++ b/modules/betsy/register_types.h @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* register_types.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 BETSY_REGISTER_TYPES_H +#define BETSY_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_betsy_module(ModuleInitializationLevel p_level); +void uninitialize_betsy_module(ModuleInitializationLevel p_level); + +#endif // BETSY_REGISTER_TYPES_H diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 72b540496d..1804d73a69 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -59,30 +59,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, size_t height = (size_t)p_header.bmp_info_header.bmp_height; size_t bits_per_pixel = (size_t)p_header.bmp_info_header.bmp_bit_count; - // Check whether we can load it - - if (bits_per_pixel == 1) { - // Requires bit unpacking... - ERR_FAIL_COND_V_MSG(width % 8 != 0, ERR_UNAVAILABLE, - vformat("1-bpp BMP images must have a width that is a multiple of 8, but the imported BMP is %d pixels wide.", int(width))); - ERR_FAIL_COND_V_MSG(height % 8 != 0, ERR_UNAVAILABLE, - vformat("1-bpp BMP images must have a height that is a multiple of 8, but the imported BMP is %d pixels tall.", int(height))); - - } else if (bits_per_pixel == 2) { - // Requires bit unpacking... - ERR_FAIL_COND_V_MSG(width % 4 != 0, ERR_UNAVAILABLE, - vformat("2-bpp BMP images must have a width that is a multiple of 4, but the imported BMP is %d pixels wide.", int(width))); - ERR_FAIL_COND_V_MSG(height % 4 != 0, ERR_UNAVAILABLE, - vformat("2-bpp BMP images must have a height that is a multiple of 4, but the imported BMP is %d pixels tall.", int(height))); - - } else if (bits_per_pixel == 4) { - // Requires bit unpacking... - ERR_FAIL_COND_V_MSG(width % 2 != 0, ERR_UNAVAILABLE, - vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width))); - ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE, - vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height))); - } - // Image data (might be indexed) Vector<uint8_t> data; int data_len = 0; @@ -98,55 +74,32 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, uint8_t *data_w = data.ptrw(); uint8_t *write_buffer = data_w; - const uint32_t width_bytes = width * bits_per_pixel / 8; - const uint32_t line_width = (width_bytes + 3) & ~3; + const uint32_t width_bytes = (width * bits_per_pixel + 7) / 8; + const uint32_t line_width = (width_bytes + 3) & ~3; // Padded to 4 bytes. - // The actual data traversal is determined by - // the data width in case of 8/4/2/1 bit images - const uint32_t w = bits_per_pixel >= 16 ? width : width_bytes; const uint8_t *line = p_buffer + (line_width * (height - 1)); const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset; + ERR_FAIL_COND_V(line + line_width > end_buffer, ERR_FILE_CORRUPT); for (uint64_t i = 0; i < height; i++) { const uint8_t *line_ptr = line; - for (unsigned int j = 0; j < w; j++) { - ERR_FAIL_COND_V(line_ptr >= end_buffer, ERR_FILE_CORRUPT); + for (unsigned int j = 0; j < width; j++) { switch (bits_per_pixel) { case 1: { - uint8_t color_index = *line_ptr; + write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 8))) & 0x01; - write_buffer[index + 0] = (color_index >> 7) & 1; - write_buffer[index + 1] = (color_index >> 6) & 1; - write_buffer[index + 2] = (color_index >> 5) & 1; - write_buffer[index + 3] = (color_index >> 4) & 1; - write_buffer[index + 4] = (color_index >> 3) & 1; - write_buffer[index + 5] = (color_index >> 2) & 1; - write_buffer[index + 6] = (color_index >> 1) & 1; - write_buffer[index + 7] = (color_index >> 0) & 1; - - index += 8; - line_ptr += 1; + index++; } break; case 2: { - uint8_t color_index = *line_ptr; + write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 4))) & 0x03; - write_buffer[index + 0] = (color_index >> 6) & 3; - write_buffer[index + 1] = (color_index >> 4) & 3; - write_buffer[index + 2] = (color_index >> 2) & 3; - write_buffer[index + 3] = color_index & 3; - - index += 4; - line_ptr += 1; + index++; } break; case 4: { - uint8_t color_index = *line_ptr; - - write_buffer[index + 0] = (color_index >> 4) & 0x0f; - write_buffer[index + 1] = color_index & 0x0f; + write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 2))) & 0x0f; - index += 2; - line_ptr += 1; + index++; } break; case 8: { uint8_t color_index = *line_ptr; diff --git a/modules/camera/camera_macos.mm b/modules/camera/camera_macos.mm index c0d8dc2cef..578a1d6325 100644 --- a/modules/camera/camera_macos.mm +++ b/modules/camera/camera_macos.mm @@ -307,11 +307,17 @@ MyDeviceNotifications *device_notifications = nil; // CameraMacOS - Subclass for our camera server on macOS void CameraMacOS::update_feeds() { -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 - AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; - NSArray *devices = session.devices; +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 + AVCaptureDeviceDiscoverySession *session; +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 140000 + // Avoid deprecated warning if the minimum SDK is 14.0. + session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternal, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; #else - NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; +#endif + NSArray<AVCaptureDevice *> *devices = session.devices; +#else + NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; #endif // remove devices that are gone.. diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 8d2847ab1a..8c81c0ce4e 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -141,7 +141,12 @@ bool CSGShape3D::is_root_shape() const { } void CSGShape3D::set_snap(float p_snap) { + if (snap == p_snap) { + return; + } + snap = p_snap; + _make_dirty(); } float CSGShape3D::get_snap() const { @@ -455,27 +460,31 @@ void CSGShape3D::_update_shape() { _update_collision_faces(); } -void CSGShape3D::_update_collision_faces() { - if (use_collision && is_root_shape() && root_collision_shape.is_valid()) { - CSGBrush *n = _get_brush(); - ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush."); - Vector<Vector3> physics_faces; - physics_faces.resize(n->faces.size() * 3); - Vector3 *physicsw = physics_faces.ptrw(); - - for (int i = 0; i < n->faces.size(); i++) { - int order[3] = { 0, 1, 2 }; +Vector<Vector3> CSGShape3D::_get_brush_collision_faces() { + Vector<Vector3> collision_faces; + CSGBrush *n = _get_brush(); + ERR_FAIL_NULL_V_MSG(n, collision_faces, "Cannot get CSGBrush."); + collision_faces.resize(n->faces.size() * 3); + Vector3 *collision_faces_ptrw = collision_faces.ptrw(); - if (n->faces[i].invert) { - SWAP(order[1], order[2]); - } + for (int i = 0; i < n->faces.size(); i++) { + int order[3] = { 0, 1, 2 }; - physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]]; - physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]]; - physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]]; + if (n->faces[i].invert) { + SWAP(order[1], order[2]); } - root_collision_shape->set_faces(physics_faces); + collision_faces_ptrw[i * 3 + 0] = n->faces[i].vertices[order[0]]; + collision_faces_ptrw[i * 3 + 1] = n->faces[i].vertices[order[1]]; + collision_faces_ptrw[i * 3 + 2] = n->faces[i].vertices[order[2]]; + } + + return collision_faces; +} + +void CSGShape3D::_update_collision_faces() { + if (use_collision && is_root_shape() && root_collision_shape.is_valid()) { + root_collision_shape->set_faces(_get_brush_collision_faces()); if (_is_debug_collision_shape_visible()) { _update_debug_collision_shape(); @@ -483,8 +492,28 @@ void CSGShape3D::_update_collision_faces() { } } +Ref<ArrayMesh> CSGShape3D::bake_static_mesh() { + Ref<ArrayMesh> baked_mesh; + if (is_root_shape() && root_mesh.is_valid()) { + baked_mesh = root_mesh; + } + return baked_mesh; +} + +Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() { + Ref<ConcavePolygonShape3D> baked_collision_shape; + if (is_root_shape() && root_collision_shape.is_valid()) { + baked_collision_shape.instantiate(); + baked_collision_shape->set_faces(root_collision_shape->get_faces()); + } else if (is_root_shape()) { + baked_collision_shape.instantiate(); + baked_collision_shape->set_faces(_get_brush_collision_faces()); + } + return baked_collision_shape; +} + bool CSGShape3D::_is_debug_collision_shape_visible() { - return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint()); + return !Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->is_debugging_collisions_hint(); } void CSGShape3D::_update_debug_collision_shape() { @@ -575,11 +604,6 @@ void CSGShape3D::_notification(int p_what) { // Update this node's parent only if its own visibility has changed, not the visibility of parent nodes parent_shape->_make_dirty(); } - if (is_visible()) { - _update_debug_collision_shape(); - } else { - _clear_debug_collision_shape(); - } last_visible = is_visible(); } break; @@ -699,6 +723,9 @@ void CSGShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes); + ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGShape3D::bake_static_mesh); + ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents"); @@ -929,7 +956,8 @@ CSGBrush *CSGMesh3D::_build_brush() { void CSGMesh3D::_mesh_changed() { _make_dirty(); - update_gizmos(); + + callable_mp((Node3D *)this, &Node3D::update_gizmos).call_deferred(); } void CSGMesh3D::set_material(const Ref<Material> &p_material) { diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index bb7c8be431..8f23ae2f9e 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -113,6 +113,7 @@ private: void _update_debug_collision_shape(); void _clear_debug_collision_shape(); void _on_transform_changed(); + Vector<Vector3> _get_brush_collision_faces(); protected: void _notification(int p_what); @@ -161,6 +162,10 @@ public: bool is_calculating_tangents() const; bool is_root_shape() const; + + Ref<ArrayMesh> bake_static_mesh(); + Ref<ConcavePolygonShape3D> bake_collision_shape(); + CSGShape3D(); ~CSGShape3D(); }; diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml index 0414aa362d..ac62d8dd83 100644 --- a/modules/csg/doc_classes/CSGShape3D.xml +++ b/modules/csg/doc_classes/CSGShape3D.xml @@ -5,12 +5,29 @@ </brief_description> <description> This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot. - [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + [b]Performance:[/b] CSG nodes are only intended for prototyping as they have a significant CPU performance cost. + Consider baking final CSG operation results into static geometry that replaces the CSG nodes. + Individual CSG root node results can be baked to nodes with static resources with the editor menu that appears when a CSG root node is selected. + Individual CSG root nodes can also be baked to static resources with scripts by calling [method bake_static_mesh] for the visual mesh or [method bake_collision_shape] for the physics collision. + Entire scenes of CSG nodes can be baked to static geometry and exported with the editor gltf scene exporter. </description> <tutorials> <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <methods> + <method name="bake_collision_shape"> + <return type="ConcavePolygonShape3D" /> + <description> + Returns a baked physics [ConcavePolygonShape3D] of this node's CSG operation result. Returns an empty shape if the node is not a CSG root node or has no valid geometry. + [b]Performance:[/b] If the CSG operation results in a very detailed geometry with many faces physics performance will be very slow. Concave shapes should in general only be used for static level geometry and not with dynamic objects that are moving. + </description> + </method> + <method name="bake_static_mesh"> + <return type="ArrayMesh" /> + <description> + Returns a baked static [ArrayMesh] of this node's CSG operation result. Materials from involved CSG nodes are added as extra mesh surfaces. Returns an empty mesh if the node is not a CSG root node or has no valid geometry. + </description> + </method> <method name="get_collision_layer_value" qualifiers="const"> <return type="bool" /> <param index="0" name="layer_number" type="int" /> @@ -73,7 +90,7 @@ The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent. </member> <member name="snap" type="float" setter="set_snap" getter="get_snap" default="0.001"> - Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust. + Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust. The top-level CSG shape's snap value is used for the entire CSG tree. </member> <member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision" default="false"> Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority]. diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp index ea7b6d225e..95ffeed6c3 100644 --- a/modules/csg/editor/csg_gizmos.cpp +++ b/modules/csg/editor/csg_gizmos.cpp @@ -38,13 +38,142 @@ #include "editor/plugins/gizmos/gizmo_3d_helper.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/physics/collision_shape_3d.h" +#include "scene/3d/physics/static_body_3d.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/menu_button.h" + +void CSGShapeEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + options->hide(); + } +} + +void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) { + node = p_csg_shape; + if (node) { + options->show(); + } else { + options->hide(); + } +} + +void CSGShapeEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + options->set_icon(get_editor_theme_icon(SNAME("CSGCombiner3D"))); + } break; + } +} + +void CSGShapeEditor::_menu_option(int p_option) { + Array meshes = node->get_meshes(); + if (meshes.is_empty()) { + err_dialog->set_text(TTR("CSG operation returned an empty array.")); + err_dialog->popup_centered(); + return; + } + + switch (p_option) { + case MENU_OPTION_BAKE_MESH_INSTANCE: { + _create_baked_mesh_instance(); + } break; + case MENU_OPTION_BAKE_COLLISION_SHAPE: { + _create_baked_collision_shape(); + } break; + } +} + +void CSGShapeEditor::_create_baked_mesh_instance() { + if (node == get_tree()->get_edited_scene_root()) { + err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node.")); + err_dialog->popup_centered(); + return; + } + + Ref<ArrayMesh> mesh = node->bake_static_mesh(); + if (mesh.is_null()) { + err_dialog->set_text(TTR("CSG operation returned an empty mesh.")); + err_dialog->popup_centered(); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Create baked CSGShape3D Mesh Instance")); + + Node *owner = get_tree()->get_edited_scene_root(); + + MeshInstance3D *mi = memnew(MeshInstance3D); + mi->set_mesh(mesh); + mi->set_name("CSGBakedMeshInstance3D"); + mi->set_transform(node->get_transform()); + ur->add_do_method(node, "add_sibling", mi, true); + ur->add_do_method(mi, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi); + + ur->add_do_reference(mi); + ur->add_undo_method(node->get_parent(), "remove_child", mi); + + ur->commit_action(); +} + +void CSGShapeEditor::_create_baked_collision_shape() { + if (node == get_tree()->get_edited_scene_root()) { + err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node.")); + err_dialog->popup_centered(); + return; + } + + Ref<Shape3D> shape = node->bake_collision_shape(); + if (shape.is_null()) { + err_dialog->set_text(TTR("CSG operation returned an empty shape.")); + err_dialog->popup_centered(); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Create baked CSGShape3D Collision Shape")); + + Node *owner = get_tree()->get_edited_scene_root(); + + CollisionShape3D *cshape = memnew(CollisionShape3D); + cshape->set_shape(shape); + cshape->set_name("CSGBakedCollisionShape3D"); + cshape->set_transform(node->get_transform()); + ur->add_do_method(node, "add_sibling", cshape, true); + ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape); + + ur->add_do_reference(cshape); + ur->add_undo_method(node->get_parent(), "remove_child", cshape); + + ur->commit_action(); +} + +CSGShapeEditor::CSGShapeEditor() { + options = memnew(MenuButton); + options->hide(); + options->set_text(TTR("CSG")); + options->set_switch_on_hover(true); + Node3DEditor::get_singleton()->add_control_to_menu_panel(options); + + options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE); + options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE); + + options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGShapeEditor::_menu_option)); + + err_dialog = memnew(AcceptDialog); + add_child(err_dialog); +} /////////// CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() { helper.instantiate(); - Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15)); + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg"); create_material("shape_union_material", gizmo_color); create_material("shape_union_solid_material", gizmo_color); gizmo_color.invert(); @@ -393,9 +522,26 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } +void EditorPluginCSG::edit(Object *p_object) { + CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object); + if (csg_shape && csg_shape->is_root_shape()) { + csg_shape_editor->edit(csg_shape); + } else { + csg_shape_editor->edit(nullptr); + } +} + +bool EditorPluginCSG::handles(Object *p_object) const { + CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object); + return csg_shape && csg_shape->is_root_shape(); +} + EditorPluginCSG::EditorPluginCSG() { Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin)); Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); + + csg_shape_editor = memnew(CSGShapeEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor); } #endif // TOOLS_ENABLED diff --git a/modules/csg/editor/csg_gizmos.h b/modules/csg/editor/csg_gizmos.h index de19b33e7d..c562fe9fe7 100644 --- a/modules/csg/editor/csg_gizmos.h +++ b/modules/csg/editor/csg_gizmos.h @@ -37,8 +37,11 @@ #include "editor/plugins/editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" +#include "scene/gui/control.h" +class AcceptDialog; class Gizmo3DHelper; +class MenuButton; class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin); @@ -62,10 +65,43 @@ public: ~CSGShape3DGizmoPlugin(); }; +class CSGShapeEditor : public Control { + GDCLASS(CSGShapeEditor, Control); + + enum Menu { + MENU_OPTION_BAKE_MESH_INSTANCE, + MENU_OPTION_BAKE_COLLISION_SHAPE, + }; + + CSGShape3D *node = nullptr; + MenuButton *options = nullptr; + AcceptDialog *err_dialog = nullptr; + + void _menu_option(int p_option); + + void _create_baked_mesh_instance(); + void _create_baked_collision_shape(); + +protected: + void _node_removed(Node *p_node); + + void _notification(int p_what); + +public: + void edit(CSGShape3D *p_csg_shape); + CSGShapeEditor(); +}; + class EditorPluginCSG : public EditorPlugin { GDCLASS(EditorPluginCSG, EditorPlugin); + CSGShapeEditor *csg_shape_editor = nullptr; + public: + virtual String get_name() const override { return "CSGShape3D"; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + EditorPluginCSG(); }; diff --git a/modules/csg/icons/CSGBox3D.svg b/modules/csg/icons/CSGBox3D.svg index d425180cf5..ad3e490dcf 100644 --- a/modules/csg/icons/CSGBox3D.svg +++ b/modules/csg/icons/CSGBox3D.svg @@ -1 +1 @@ -<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" mask="url(#a)"/></svg>
\ No newline at end of file diff --git a/modules/csg/icons/CSGCapsule3D.svg b/modules/csg/icons/CSGCapsule3D.svg index 3c2657999c..54a5e3d698 100644 --- a/modules/csg/icons/CSGCapsule3D.svg +++ b/modules/csg/icons/CSGCapsule3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M4 6a4 4 0 0 1 8 0v4a4 4 0 0 1-8 0zm0 1.25a2.5 1 0 0 0 8 0m-4-5v12" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M4 6a4 4 0 0 1 8 0v4a4 4 0 0 1-8 0zm0 1.25a2.5 1 0 0 0 8 0m-4-5v12" mask="url(#a)"/></svg>
\ No newline at end of file diff --git a/modules/csg/icons/CSGCombiner3D.svg b/modules/csg/icons/CSGCombiner3D.svg index 8598897e92..e65bb1c081 100644 --- a/modules/csg/icons/CSGCombiner3D.svg +++ b/modules/csg/icons/CSGCombiner3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M3 1a2 2 0 0 0-2 2h2zm2 0v2h2V1zm4 0v2h2V1zm4 0v2h2a2 2 0 0 0-2-2zM1 5v2h2V5zm12 0v2h2V5zM1 9v2h2V9zm0 4a2 2 0 0 0 2 2v-2zm4 0v2h2v-2z" fill="#fc7f7f"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="#fc7f7f" d="M3 1a2 2 0 0 0-2 2h2zm2 0v2h2V1zm4 0v2h2V1zm4 0v2h2a2 2 0 0 0-2-2zM1 5v2h2V5zm12 0v2h2V5zM1 9v2h2V9zm0 4a2 2 0 0 0 2 2v-2zm4 0v2h2v-2z"/></svg>
\ No newline at end of file diff --git a/modules/csg/icons/CSGCylinder3D.svg b/modules/csg/icons/CSGCylinder3D.svg index 19e48b4dba..366f1d9c4b 100644 --- a/modules/csg/icons/CSGCylinder3D.svg +++ b/modules/csg/icons/CSGCylinder3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" stroke-width="2" fill="none" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" mask="url(#a)"/></svg>
\ No newline at end of file diff --git a/modules/csg/icons/CSGMesh3D.svg b/modules/csg/icons/CSGMesh3D.svg index a1c2282fe5..d115a64f80 100644 --- a/modules/csg/icons/CSGMesh3D.svg +++ b/modules/csg/icons/CSGMesh3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4.73 2A2 2 0 1 0 2 4.73v6.541A2 2 0 1 0 4.729 14H8v-2H4.729A2 2 0 0 0 4 11.271V5.415l4.914 4.916A2 2 0 0 1 9.998 10a2 2 0 0 1 .33-1.084L5.414 4h5.856a2 2 0 0 0 .73.729V8h2V4.729A2 2 0 1 0 11.27 2z" fill="#fc7f7f"/><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fc7f7f" d="M4.73 2A2 2 0 1 0 2 4.73v6.541A2 2 0 1 0 4.729 14H8v-2H4.729A2 2 0 0 0 4 11.271V5.415l4.914 4.916A2 2 0 0 1 9.998 10a2 2 0 0 1 .33-1.084L5.414 4h5.856a2 2 0 0 0 .73.729V8h2V4.729A2 2 0 1 0 11.27 2z"/><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/></svg>
\ No newline at end of file diff --git a/modules/csg/icons/CSGPolygon3D.svg b/modules/csg/icons/CSGPolygon3D.svg index 090047248b..ab7f61132b 100644 --- a/modules/csg/icons/CSGPolygon3D.svg +++ b/modules/csg/icons/CSGPolygon3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" fill="none" stroke-linejoin="round" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-linejoin="round" stroke-width="2" d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" mask="url(#a)"/></svg>
\ No newline at end of file diff --git a/modules/csg/icons/CSGSphere3D.svg b/modules/csg/icons/CSGSphere3D.svg index a677ffaf5c..96a63df153 100644 --- a/modules/csg/icons/CSGSphere3D.svg +++ b/modules/csg/icons/CSGSphere3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" mask="url(#a)"/></svg>
\ No newline at end of file diff --git a/modules/csg/icons/CSGTorus3D.svg b/modules/csg/icons/CSGTorus3D.svg index 60c56bd1ca..1f7565782f 100644 --- a/modules/csg/icons/CSGTorus3D.svg +++ b/modules/csg/icons/CSGTorus3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><g fill="none" stroke="#fc7f7f" mask="url(#a)"><path d="M2.5 10a6 4 0 0 0 11 0 4 4 0 0 0 0-4 6 4 0 0 0-11 0 4 4 0 0 0 0 4z" stroke-width="2"/><path d="M6.2 7.2a2 1 0 1 0 3.6 0" stroke-width="1.75" stroke-linecap="round"/></g></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><g fill="none" stroke="#fc7f7f" mask="url(#a)"><path stroke-width="2" d="M2.5 10a6 4 0 0 0 11 0 4 4 0 0 0 0-4 6 4 0 0 0-11 0 4 4 0 0 0 0 4z"/><path stroke-linecap="round" stroke-width="1.75" d="M6.2 7.2a2 1 0 1 0 3.6 0"/></g></svg>
\ No newline at end of file diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 7335315c51..2087dde2a1 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -142,14 +142,17 @@ static void _digest_job_queue(void *p_job_queue, uint32_t p_index) { } void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + if (p_image->is_compressed()) { return; //do not compress, already compressed } + int w = p_image->get_width(); int h = p_image->get_height(); bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8); - bool is_hdr = (p_image->get_format() >= Image::FORMAT_RH) && (p_image->get_format() <= Image::FORMAT_RGBE9995); + bool is_hdr = (p_image->get_format() >= Image::FORMAT_RF) && (p_image->get_format() <= Image::FORMAT_RGBE9995); if (!is_ldr && !is_hdr) { return; // Not a usable source format @@ -171,17 +174,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { p_image->convert(Image::FORMAT_RGBH); } - const uint8_t *rb = p_image->get_data().ptr(); - - const uint16_t *source_data = reinterpret_cast<const uint16_t *>(&rb[0]); - int pixel_element_count = w * h * 3; - for (int i = 0; i < pixel_element_count; i++) { - if ((source_data[i] & 0x8000) != 0 && (source_data[i] & 0x7fff) != 0) { - is_signed = true; - break; - } - } - + is_signed = p_image->detect_signed(); target_format = is_signed ? Image::FORMAT_BPTC_RGBF : Image::FORMAT_BPTC_RGBFU; } else { p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert @@ -190,14 +183,14 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { const uint8_t *rb = p_image->get_data().ptr(); Vector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->has_mipmaps() ? Image::get_image_required_mipmaps(w, h, target_format) : 0; data.resize(target_size); int shift = Image::get_format_pixel_rshift(target_format); uint8_t *wb = data.ptrw(); - int dst_ofs = 0; + int64_t dst_ofs = 0; CVTTCompressionJobQueue job_queue; job_queue.job_params.is_hdr = is_hdr; @@ -219,7 +212,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { int bw = w % 4 != 0 ? w + (4 - w % 4) : w; int bh = h % 4 != 0 ? h + (4 - h % 4) : h; - int src_ofs = p_image->get_mipmap_offset(i); + int64_t src_ofs = p_image->get_mipmap_offset(i); const uint8_t *in_bytes = &rb[src_ofs]; uint8_t *out_bytes = &wb[dst_ofs]; @@ -250,6 +243,8 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); + + print_verbose(vformat("CVTT: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } void image_decompress_cvtt(Image *p_image) { @@ -279,7 +274,7 @@ void image_decompress_cvtt(Image *p_image) { const uint8_t *rb = p_image->get_data().ptr(); Vector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->get_mipmap_count(); data.resize(target_size); @@ -287,10 +282,10 @@ void image_decompress_cvtt(Image *p_image) { int bytes_per_pixel = is_hdr ? 6 : 4; - int dst_ofs = 0; + int64_t dst_ofs = 0; for (int i = 0; i <= mm_count; i++) { - int src_ofs = p_image->get_mipmap_offset(i); + int64_t src_ofs = p_image->get_mipmap_offset(i); const uint8_t *in_bytes = &rb[src_ofs]; uint8_t *out_bytes = &wb[dst_ofs]; diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index b2de6b656e..6ea44c5fc3 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -42,14 +42,18 @@ enum { DDSD_PITCH = 0x00000008, DDSD_LINEARSIZE = 0x00080000, DDSD_MIPMAPCOUNT = 0x00020000, - DDPF_FOURCC = 0x00000004, DDPF_ALPHAPIXELS = 0x00000001, - DDPF_RGB = 0x00000040 + DDPF_ALPHAONLY = 0x00000002, + DDPF_FOURCC = 0x00000004, + DDPF_RGB = 0x00000040, + DDPF_RG_SNORM = 0x00080000 }; enum DDSFourCC { DDFCC_DXT1 = PF_FOURCC("DXT1"), + DDFCC_DXT2 = PF_FOURCC("DXT2"), DDFCC_DXT3 = PF_FOURCC("DXT3"), + DDFCC_DXT4 = PF_FOURCC("DXT4"), DDFCC_DXT5 = PF_FOURCC("DXT5"), DDFCC_ATI1 = PF_FOURCC("ATI1"), DDFCC_BC4U = PF_FOURCC("BC4U"), @@ -68,17 +72,25 @@ enum DDSFourCC { // Reference: https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format enum DXGIFormat { DXGI_R32G32B32A32_FLOAT = 2, + DXGI_R32G32B32_FLOAT = 6, DXGI_R16G16B16A16_FLOAT = 10, DXGI_R32G32_FLOAT = 16, DXGI_R10G10B10A2_UNORM = 24, DXGI_R8G8B8A8_UNORM = 28, + DXGI_R8G8B8A8_UNORM_SRGB = 29, DXGI_R16G16_FLOAT = 34, DXGI_R32_FLOAT = 41, + DXGI_R8G8_UNORM = 49, DXGI_R16_FLOAT = 54, + DXGI_R8_UNORM = 61, + DXGI_A8_UNORM = 65, DXGI_R9G9B9E5 = 67, DXGI_BC1_UNORM = 71, + DXGI_BC1_UNORM_SRGB = 72, DXGI_BC2_UNORM = 74, + DXGI_BC2_UNORM_SRGB = 75, DXGI_BC3_UNORM = 77, + DXGI_BC3_UNORM_SRGB = 78, DXGI_BC4_UNORM = 80, DXGI_BC5_UNORM = 83, DXGI_B5G6R5_UNORM = 85, @@ -87,6 +99,7 @@ enum DXGIFormat { DXGI_BC6H_UF16 = 95, DXGI_BC6H_SF16 = 96, DXGI_BC7_UNORM = 98, + DXGI_BC7_UNORM_SRGB = 99, DXGI_B4G4R4A4_UNORM = 115 }; @@ -100,25 +113,29 @@ enum DDSFormat { DDS_ATI2, DDS_BC6U, DDS_BC6S, - DDS_BC7U, + DDS_BC7, DDS_R16F, DDS_RG16F, DDS_RGBA16F, DDS_R32F, DDS_RG32F, + DDS_RGB32F, DDS_RGBA32F, DDS_RGB9E5, - DDS_BGRA8, - DDS_BGR8, - DDS_RGBA8, DDS_RGB8, + DDS_RGBA8, + DDS_BGR8, + DDS_BGRA8, DDS_BGR5A1, DDS_BGR565, + DDS_B2GR3, + DDS_B2GR3A8, DDS_BGR10A2, DDS_RGB10A2, DDS_BGRA4, DDS_LUMINANCE, DDS_LUMINANCE_ALPHA, + DDS_LUMINANCE_ALPHA_4, DDS_MAX }; @@ -132,31 +149,35 @@ struct DDSFormatInfo { static const DDSFormatInfo dds_format_info[DDS_MAX] = { { "DXT1/BC1", true, 4, 8, Image::FORMAT_DXT1 }, - { "DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 }, - { "DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 }, + { "DXT2/DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 }, + { "DXT4/DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 }, { "ATI1/BC4", true, 4, 8, Image::FORMAT_RGTC_R }, { "ATI2/A2XY/BC5", true, 4, 16, Image::FORMAT_RGTC_RG }, - { "BC6U", true, 4, 16, Image::FORMAT_BPTC_RGBFU }, - { "BC6S", true, 4, 16, Image::FORMAT_BPTC_RGBF }, - { "BC7U", true, 4, 16, Image::FORMAT_BPTC_RGBA }, + { "BC6UF", true, 4, 16, Image::FORMAT_BPTC_RGBFU }, + { "BC6SF", true, 4, 16, Image::FORMAT_BPTC_RGBF }, + { "BC7", true, 4, 16, Image::FORMAT_BPTC_RGBA }, { "R16F", false, 1, 2, Image::FORMAT_RH }, { "RG16F", false, 1, 4, Image::FORMAT_RGH }, { "RGBA16F", false, 1, 8, Image::FORMAT_RGBAH }, { "R32F", false, 1, 4, Image::FORMAT_RF }, { "RG32F", false, 1, 8, Image::FORMAT_RGF }, + { "RGB32F", false, 1, 12, Image::FORMAT_RGBF }, { "RGBA32F", false, 1, 16, Image::FORMAT_RGBAF }, { "RGB9E5", false, 1, 4, Image::FORMAT_RGBE9995 }, - { "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 }, - { "BGR8", false, 1, 3, Image::FORMAT_RGB8 }, - { "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 }, { "RGB8", false, 1, 3, Image::FORMAT_RGB8 }, + { "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 }, + { "BGR8", false, 1, 3, Image::FORMAT_RGB8 }, + { "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 }, { "BGR5A1", false, 1, 2, Image::FORMAT_RGBA8 }, { "BGR565", false, 1, 2, Image::FORMAT_RGB8 }, + { "B2GR3", false, 1, 1, Image::FORMAT_RGB8 }, + { "B2GR3A8", false, 1, 2, Image::FORMAT_RGBA8 }, { "BGR10A2", false, 1, 4, Image::FORMAT_RGBA8 }, { "RGB10A2", false, 1, 4, Image::FORMAT_RGBA8 }, { "BGRA4", false, 1, 2, Image::FORMAT_RGBA8 }, { "GRAYSCALE", false, 1, 1, Image::FORMAT_L8 }, - { "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 } + { "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 }, + { "GRAYSCALE_ALPHA_4", false, 1, 1, Image::FORMAT_LA8 } }; static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { @@ -164,6 +185,9 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_R32G32B32A32_FLOAT: { return DDS_RGBA32F; } + case DXGI_R32G32B32_FLOAT: { + return DDS_RGB32F; + } case DXGI_R16G16B16A16_FLOAT: { return DDS_RGBA16F; } @@ -173,7 +197,8 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_R10G10B10A2_UNORM: { return DDS_RGB10A2; } - case DXGI_R8G8B8A8_UNORM: { + case DXGI_R8G8B8A8_UNORM: + case DXGI_R8G8B8A8_UNORM_SRGB: { return DDS_RGBA8; } case DXGI_R16G16_FLOAT: { @@ -182,19 +207,29 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_R32_FLOAT: { return DDS_R32F; } + case DXGI_R8_UNORM: + case DXGI_A8_UNORM: { + return DDS_LUMINANCE; + } case DXGI_R16_FLOAT: { return DDS_R16F; } + case DXGI_R8G8_UNORM: { + return DDS_LUMINANCE_ALPHA; + } case DXGI_R9G9B9E5: { return DDS_RGB9E5; } - case DXGI_BC1_UNORM: { + case DXGI_BC1_UNORM: + case DXGI_BC1_UNORM_SRGB: { return DDS_DXT1; } - case DXGI_BC2_UNORM: { + case DXGI_BC2_UNORM: + case DXGI_BC2_UNORM_SRGB: { return DDS_DXT3; } - case DXGI_BC3_UNORM: { + case DXGI_BC3_UNORM: + case DXGI_BC3_UNORM_SRGB: { return DDS_DXT5; } case DXGI_BC4_UNORM: { @@ -218,8 +253,9 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_BC6H_SF16: { return DDS_BC6S; } - case DXGI_BC7_UNORM: { - return DDS_BC7U; + case DXGI_BC7_UNORM: + case DXGI_BC7_UNORM_SRGB: { + return DDS_BC7; } case DXGI_B4G4R4A4_UNORM: { return DDS_BGRA4; @@ -299,9 +335,11 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig case DDFCC_DXT1: { dds_format = DDS_DXT1; } break; + case DDFCC_DXT2: case DDFCC_DXT3: { dds_format = DDS_DXT3; } break; + case DDFCC_DXT4: case DDFCC_DXT5: { dds_format = DDS_DXT5; } break; @@ -363,6 +401,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig dds_format = DDS_RGB10A2; } else if (format_rgb_bits == 16 && format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000) { dds_format = DDS_BGRA4; + } else if (format_rgb_bits == 16 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3 && format_alpha_mask == 0xff00) { + dds_format = DDS_B2GR3A8; } } else { @@ -373,18 +413,38 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig dds_format = DDS_RGB8; } else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) { dds_format = DDS_BGR565; + } else if (format_rgb_bits == 8 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3) { + dds_format = DDS_B2GR3; } } } else { // Other formats. - if (format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) { - dds_format = DDS_LUMINANCE_ALPHA; - } else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff) { + if (format_flags & DDPF_ALPHAONLY && format_rgb_bits == 8 && format_alpha_mask == 0xff) { + // Alpha only. dds_format = DDS_LUMINANCE; } } + // Depending on the writer, luminance formats may or may not have the DDPF_RGB or DDPF_LUMINANCE flags defined, + // so we check for these formats after everything else failed. + if (dds_format == DDS_MAX) { + if (format_flags & DDPF_ALPHAPIXELS) { + // With alpha. + if (format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) { + dds_format = DDS_LUMINANCE_ALPHA; + } else if (format_rgb_bits == 8 && format_red_mask == 0xf && format_alpha_mask == 0xf0) { + dds_format = DDS_LUMINANCE_ALPHA_4; + } + + } else { + // Without alpha. + if (format_rgb_bits == 8 && format_red_mask == 0xff) { + dds_format = DDS_LUMINANCE; + } + } + } + // No format detected, error. if (dds_format == DDS_MAX) { ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'."); @@ -433,10 +493,24 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } // Calculate the space these formats will take up after decoding. - if (dds_format == DDS_BGR565) { - size = size * 3 / 2; - } else if (dds_format == DDS_BGR5A1 || dds_format == DDS_BGRA4) { - size = size * 2; + switch (dds_format) { + case DDS_BGR565: + size = size * 3 / 2; + break; + + case DDS_BGR5A1: + case DDS_BGRA4: + case DDS_B2GR3A8: + case DDS_LUMINANCE_ALPHA_4: + size = size * 2; + break; + + case DDS_B2GR3: + size = size * 3; + break; + + default: + break; } src_data.resize(size); @@ -503,6 +577,44 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } } break; + case DDS_B2GR3: { + // To RGB8. + int colcount = size / 3; + + for (int i = colcount - 1; i >= 0; i--) { + int src_ofs = i; + int dst_ofs = i * 3; + + uint8_t b = (wb[src_ofs] & 0x3) << 6; + uint8_t g = (wb[src_ofs] & 0x1C) << 3; + uint8_t r = (wb[src_ofs] & 0xE0); + + wb[dst_ofs] = r; + wb[dst_ofs + 1] = g; + wb[dst_ofs + 2] = b; + } + + } break; + case DDS_B2GR3A8: { + // To RGBA8. + int colcount = size / 4; + + for (int i = colcount - 1; i >= 0; i--) { + int src_ofs = i * 2; + int dst_ofs = i * 4; + + uint8_t b = (wb[src_ofs] & 0x3) << 6; + uint8_t g = (wb[src_ofs] & 0x1C) << 3; + uint8_t r = (wb[src_ofs] & 0xE0); + uint8_t a = wb[src_ofs + 1]; + + wb[dst_ofs] = r; + wb[dst_ofs + 1] = g; + wb[dst_ofs + 2] = b; + wb[dst_ofs + 3] = a; + } + + } break; case DDS_RGB10A2: { // To RGBA8. int colcount = size / 4; @@ -549,6 +661,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } } break; + + // Channel-swapped. case DDS_BGRA8: { // To RGBA8. int colcount = size / 4; @@ -568,6 +682,24 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } break; + // Grayscale. + case DDS_LUMINANCE_ALPHA_4: { + // To LA8. + int colcount = size / 2; + + for (int i = colcount - 1; i >= 0; i--) { + int src_ofs = i; + int dst_ofs = i * 2; + + uint8_t l = wb[src_ofs] & 0x0F; + uint8_t a = wb[src_ofs] & 0xF0; + + wb[dst_ofs] = (l << 4) | l; + wb[dst_ofs + 1] = a | (a >> 4); + } + + } break; + default: { } } diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index 5795dd8976..84534fec06 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -51,7 +51,7 @@ <param index="3" name="data" type="int" default="0" /> <description> Initiates a connection to a foreign [param address] using the specified [param port] and allocating the requested [param channels]. Optional [param data] can be passed during connection in the form of a 32 bit integer. - [b]Note:[/b] You must call either [method create_host] or [method create_host_bound] before calling this method. + [b]Note:[/b] You must call either [method create_host] or [method create_host_bound] on both ends before calling this method. </description> </method> <method name="create_host"> @@ -61,7 +61,9 @@ <param index="2" name="in_bandwidth" type="int" default="0" /> <param index="3" name="out_bandwidth" type="int" default="0" /> <description> - Create an ENetHost that will allow up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth]. + Creates an ENetHost that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero). + This method binds a random available dynamic UDP port on the host machine at the [i]unspecified[/i] address. Use [method create_host_bound] to specify the address and port. + [b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection. </description> </method> <method name="create_host_bound"> @@ -73,7 +75,8 @@ <param index="4" name="in_bandwidth" type="int" default="0" /> <param index="5" name="out_bandwidth" type="int" default="0" /> <description> - Create an ENetHost like [method create_host] which is also bound to the given [param bind_address] and [param bind_port]. + Creates an ENetHost bound to the given [param bind_address] and [param bind_port] that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero). + [b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection. </description> </method> <method name="destroy"> @@ -141,8 +144,9 @@ <return type="Array" /> <param index="0" name="timeout" type="int" default="0" /> <description> - Waits for events on the host specified and shuttles packets between the host and its peers. The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer]. + Waits for events on this connection and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer]. Call this function regularly to handle connections, disconnections, and to receive new packets. + [b]Note:[/b] This method must be called on both ends involved in the event (sending and receiving hosts). </description> </method> <method name="socket_send"> diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index 4ce0cf50d9..14887ce469 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -50,6 +50,7 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) { return EtcpakType::ETCPAK_TYPE_ETC2; case Image::USED_CHANNELS_RGBA: return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; + default: return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; } @@ -69,6 +70,7 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) { return EtcpakType::ETCPAK_TYPE_DXT1; case Image::USED_CHANNELS_RGBA: return EtcpakType::ETCPAK_TYPE_DXT5; + default: return EtcpakType::ETCPAK_TYPE_DXT5; } @@ -79,71 +81,86 @@ void _compress_etc1(Image *r_img) { } void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) { - EtcpakType type = _determine_etc_type(p_channels); - _compress_etcpak(type, r_img); + _compress_etcpak(_determine_etc_type(p_channels), r_img); } void _compress_bc(Image *r_img, Image::UsedChannels p_channels) { - EtcpakType type = _determine_dxt_type(p_channels); - _compress_etcpak(type, r_img); + _compress_etcpak(_determine_dxt_type(p_channels), r_img); } -void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { +void _compress_etcpak(EtcpakType p_compress_type, Image *r_img) { uint64_t start_time = OS::get_singleton()->get_ticks_msec(); - Image::Format img_format = r_img->get_format(); - if (Image::is_format_compressed(img_format)) { - return; // Do not compress, already compressed. - } - if (img_format > Image::FORMAT_RGBA8) { - // TODO: we should be able to handle FORMAT_RGBA4444 and FORMAT_RGBA5551 eventually + // The image is already compressed, return. + if (r_img->is_compressed()) { return; } - // Use RGBA8 to convert. - if (img_format != Image::FORMAT_RGBA8) { - r_img->convert(Image::FORMAT_RGBA8); - } + // Convert to RGBA8 for compression. + r_img->convert(Image::FORMAT_RGBA8); // Determine output format based on Etcpak type. Image::Format target_format = Image::FORMAT_RGBA8; - if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { - target_format = Image::FORMAT_ETC; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { - target_format = Image::FORMAT_ETC2_RGB8; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) { - target_format = Image::FORMAT_ETC2_R11; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) { - target_format = Image::FORMAT_ETC2_RG11; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { - target_format = Image::FORMAT_ETC2_RA_AS_RG; - r_img->convert_rg_to_ra_rgba8(); - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { - target_format = Image::FORMAT_ETC2_RGBA8; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { - target_format = Image::FORMAT_DXT1; - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { - target_format = Image::FORMAT_DXT5_RA_AS_RG; - r_img->convert_rg_to_ra_rgba8(); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) { - target_format = Image::FORMAT_DXT5; - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) { - target_format = Image::FORMAT_RGTC_R; - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) { - target_format = Image::FORMAT_RGTC_RG; - } else { - ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT."); + + switch (p_compress_type) { + case EtcpakType::ETCPAK_TYPE_ETC1: + target_format = Image::FORMAT_ETC; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2: + target_format = Image::FORMAT_ETC2_RGB8; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA: + target_format = Image::FORMAT_ETC2_RGBA8; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_R: + target_format = Image::FORMAT_ETC2_R11; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_RG: + target_format = Image::FORMAT_ETC2_RG11; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG: + target_format = Image::FORMAT_ETC2_RA_AS_RG; + r_img->convert_rg_to_ra_rgba8(); + break; + + case EtcpakType::ETCPAK_TYPE_DXT1: + target_format = Image::FORMAT_DXT1; + break; + + case EtcpakType::ETCPAK_TYPE_DXT5: + target_format = Image::FORMAT_DXT5; + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_R: + target_format = Image::FORMAT_RGTC_R; + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_RG: + target_format = Image::FORMAT_RGTC_RG; + break; + + case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG: + target_format = Image::FORMAT_DXT5_RA_AS_RG; + r_img->convert_rg_to_ra_rgba8(); + break; + + default: + ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT."); + break; } - // Compress image data and (if required) mipmaps. + // It's badly documented but ETCPAK seems to expect BGRA8 for ETC formats. + if (p_compress_type < EtcpakType::ETCPAK_TYPE_DXT1) { + r_img->convert_rgba8_to_bgra8(); + } - const bool mipmaps = r_img->has_mipmaps(); + // Compress image data and (if required) mipmaps. + const bool has_mipmaps = r_img->has_mipmaps(); int width = r_img->get_width(); int height = r_img->get_height(); @@ -164,109 +181,115 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from, the surface pitch, which can encompass additional padding beyond the physical surface size. */ - int next_width = width <= 2 ? width : (width + 3) & ~3; - int next_height = height <= 2 ? height : (height + 3) & ~3; - if (next_width != width || next_height != height) { - r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS); - width = r_img->get_width(); - height = r_img->get_height(); + + if (width % 4 != 0 || height % 4 != 0) { + width = width <= 2 ? width : (width + 3) & ~3; + height = height <= 2 ? height : (height + 3) & ~3; } - // ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed. + // Multiple-of-4 should be guaranteed by above. // However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels, // which are individually compressed Image objects that violate the above rule. // Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4. - const uint8_t *src_read = r_img->get_data().ptr(); - - print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : "")); - - int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + // Create the buffer for compressed image data. Vector<uint8_t> dest_data; - dest_data.resize(dest_size); + dest_data.resize(Image::get_image_data_size(width, height, target_format, has_mipmaps)); uint8_t *dest_write = dest_data.ptrw(); - int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; + const uint8_t *src_read = r_img->get_data().ptr(); + + const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; Vector<uint32_t> padded_src; for (int i = 0; i < mip_count + 1; i++) { // Get write mip metrics for target image. - int orig_mip_w, orig_mip_h; - int mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h); + int dest_mip_w, dest_mip_h; + int64_t dest_mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dest_mip_w, dest_mip_h); + // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). - ERR_FAIL_COND(mip_ofs % 8 != 0); - uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs]; + ERR_FAIL_COND(dest_mip_ofs % 8 != 0); + uint64_t *dest_mip_write = reinterpret_cast<uint64_t *>(dest_write + dest_mip_ofs); - // Block size. Align stride to multiple of 4 (RGBA8). - int mip_w = (orig_mip_w + 3) & ~3; - int mip_h = (orig_mip_h + 3) & ~3; - const uint32_t blocks = mip_w * mip_h / 16; + // Block size. + dest_mip_w = (dest_mip_w + 3) & ~3; + dest_mip_h = (dest_mip_h + 3) & ~3; + const uint32_t blocks = dest_mip_w * dest_mip_h / 16; // Get mip data from source image for reading. - int src_mip_ofs = r_img->get_mipmap_offset(i); - const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs]; + int64_t src_mip_ofs, src_mip_size; + int src_mip_w, src_mip_h; + + r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h); + + const uint32_t *src_mip_read = reinterpret_cast<const uint32_t *>(src_read + src_mip_ofs); // Pad textures to nearest block by smearing. - if (mip_w != orig_mip_w || mip_h != orig_mip_h) { - padded_src.resize(mip_w * mip_h); + if (dest_mip_w != src_mip_w || dest_mip_h != src_mip_h) { + // Reserve the buffer for padded image data. + padded_src.resize(dest_mip_w * dest_mip_h); uint32_t *ptrw = padded_src.ptrw(); + int x = 0, y = 0; - for (y = 0; y < orig_mip_h; y++) { - for (x = 0; x < orig_mip_w; x++) { - ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x]; + for (y = 0; y < src_mip_h; y++) { + for (x = 0; x < src_mip_w; x++) { + ptrw[dest_mip_w * y + x] = src_mip_read[src_mip_w * y + x]; } + // First, smear in x. - for (; x < mip_w; x++) { - ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1]; + for (; x < dest_mip_w; x++) { + ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - 1]; } } + // Then, smear in y. - for (; y < mip_h; y++) { - for (x = 0; x < mip_w; x++) { - ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w]; + for (; y < dest_mip_h; y++) { + for (x = 0; x < dest_mip_w; x++) { + ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - dest_mip_w]; } } + // Override the src_mip_read pointer to our temporary Vector. src_mip_read = padded_src.ptr(); } - switch (p_compresstype) { + switch (p_compress_type) { case EtcpakType::ETCPAK_TYPE_ETC1: - CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); + CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_ETC2: - CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true); + CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, dest_mip_w, true); break; case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA: case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG: - CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true); + CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, dest_mip_w, true); break; case EtcpakType::ETCPAK_TYPE_ETC2_R: - CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w); + CompressEacR(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_ETC2_RG: - CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w); + CompressEacRg(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_DXT1: - CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); + CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_DXT5: case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG: - CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); + CompressDxt5(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_RGTC_R: - CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w); + CompressBc4(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_RGTC_RG: - CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w); + CompressBc5(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; default: @@ -276,7 +299,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { } // Replace original image with compressed one. - r_img->set_data(width, height, mipmaps, target_format, dest_data); + r_img->set_data(width, height, has_mipmaps, target_format, dest_data); print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h index 9d5343740b..d50b322fe4 100644 --- a/modules/etcpak/image_compress_etcpak.h +++ b/modules/etcpak/image_compress_etcpak.h @@ -51,6 +51,6 @@ void _compress_etc1(Image *r_img); void _compress_etc2(Image *r_img, Image::UsedChannels p_channels); void _compress_bc(Image *r_img, Image::UsedChannels p_channels); -void _compress_etcpak(EtcpakType p_compresstype, Image *r_img); +void _compress_etcpak(EtcpakType p_compress_type, Image *r_img); #endif // IMAGE_COMPRESS_ETCPAK_H diff --git a/modules/fbx/doc_classes/EditorSceneFormatImporterFBX2GLTF.xml b/modules/fbx/doc_classes/EditorSceneFormatImporterFBX2GLTF.xml index e30782780f..3bf4e7ddc0 100644 --- a/modules/fbx/doc_classes/EditorSceneFormatImporterFBX2GLTF.xml +++ b/modules/fbx/doc_classes/EditorSceneFormatImporterFBX2GLTF.xml @@ -5,7 +5,7 @@ </brief_description> <description> Imports Autodesk FBX 3D scenes by way of converting them to glTF 2.0 using the FBX2glTF command line tool. - The location of the FBX2glTF binary is set via the [member EditorSettings.filesystem/import/fbx2gltf/fbx2gltf_path] editor setting. + The location of the FBX2glTF binary is set via the [member EditorSettings.filesystem/import/fbx/fbx2gltf_path] editor setting. This importer is only used if [member ProjectSettings.filesystem/import/fbx2gltf/enabled] is set to [code]true[/code]. </description> <tutorials> diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp index 6629e05197..f5b19f803a 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp @@ -73,7 +73,7 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint // Run fbx2gltf. - String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx2gltf/fbx2gltf_path"); + String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path"); List<String> args; args.push_back("--pbr-metallic-roughness"); @@ -124,12 +124,11 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint #endif } -Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation, +Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { - if (p_option == "fbx/embedded_image_handling") { - return false; - } - if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF && p_option == "fbx/embedded_image_handling") { + // Remove all the FBX options except for 'fbx/importer' if the importer is fbx2gltf. + // These options are available only for ufbx. + if (p_option.begins_with("fbx/") && p_option != "fbx/importer" && p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF) { return false; } return true; diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h index c68e37f0d8..ce2bac6fcf 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h @@ -49,7 +49,7 @@ public: List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override; }; diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp index 3cc919fae2..64075c0664 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp +++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp @@ -76,10 +76,8 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { state->set_import_as_skeleton_bones(true); } - if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { - state->set_import_as_skeleton_bones(true); - } p_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; + state->set_bake_fps(p_options["animation/fps"]); Error err = fbx->append_from_file(path, state, p_flags, p_path.get_base_dir()); if (err != OK) { if (r_err) { @@ -87,31 +85,27 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t } return nullptr; } - return fbx->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], false); + return fbx->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false); } -Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation, +Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { - String file_extension = p_path.get_extension().to_lower(); - if (file_extension != "fbx" && p_option.begins_with("fbx/")) { - return false; - } - if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) { - return false; - } return true; } void EditorSceneFormatImporterUFBX::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES)); + // Returns all the options when path is empty because that means it's for the Project Settings. + if (p_path.is_empty() || p_path.get_extension().to_lower() == "fbx") { + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES)); + } } void EditorSceneFormatImporterUFBX::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { if (!p_import_params.has("fbx/importer")) { - p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX; + p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF; } } diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.h b/modules/fbx/editor/editor_scene_importer_ufbx.h index b81b8df4c1..6e3eafc100 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.h +++ b/modules/fbx/editor/editor_scene_importer_ufbx.h @@ -53,7 +53,7 @@ public: List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override; }; diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index 1361e871de..ce097092fb 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -288,14 +288,8 @@ String FBXDocument::_gen_unique_name(HashSet<String> &unique_names, const String } String FBXDocument::_sanitize_animation_name(const String &p_name) { - // Animations disallow the normal node invalid characters as well as "," and "[" - // (See animation/animation_player.cpp::add_animation) - - // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node. String anim_name = p_name.validate_node_name(); - anim_name = anim_name.replace(",", ""); - anim_name = anim_name.replace("[", ""); - return anim_name; + return AnimationLibrary::validate_library_name(anim_name); } String FBXDocument::_gen_unique_animation_name(Ref<FBXState> p_state, const String &p_name) { @@ -875,7 +869,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) { const int material = int(fbx_material->typed_id); ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT); Ref<Material> mat3d = p_state->materials[material]; - ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT); Ref<BaseMaterial3D> base_material = mat3d; if (has_vertex_color && base_material.is_valid()) { @@ -891,7 +885,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) { } mat = mat3d; } - ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT); mat_name = mat->get_name(); } import_mesh->add_surface(primitive, array, morphs, @@ -1056,7 +1050,7 @@ GLTFImageIndex FBXDocument::_parse_image_save_image(Ref<FBXState> p_state, const } Error FBXDocument::_parse_images(Ref<FBXState> p_state, const String &p_base_path) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); const ufbx_scene *fbx_scene = p_state->scene.get(); for (int texture_i = 0; texture_i < static_cast<int>(fbx_scene->texture_files.count); texture_i++) { @@ -1381,6 +1375,10 @@ Error FBXDocument::_parse_animations(Ref<FBXState> p_state) { additional_data["time_end"] = fbx_anim_stack->time_end; animation->set_additional_data("GODOT_animation_time_begin_time_end", additional_data); ufbx_bake_opts opts = {}; + opts.resample_rate = p_state->get_bake_fps(); + opts.minimum_sample_rate = p_state->get_bake_fps(); + opts.max_keyframe_segments = 1024; + ufbx_error error; ufbx_unique_ptr<ufbx_baked_anim> fbx_baked_anim{ ufbx_bake_anim(fbx_scene, fbx_anim_stack->anim, &opts, &error) }; if (!fbx_baked_anim) { @@ -1759,7 +1757,7 @@ void FBXDocument::_generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTF } } -void FBXDocument::_import_animation(Ref<FBXState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks) { +void FBXDocument::_import_animation(Ref<FBXState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks) { Ref<GLTFAnimation> anim = p_state->animations[p_index]; String anim_name = anim->get_name(); @@ -1771,7 +1769,7 @@ void FBXDocument::_import_animation(Ref<FBXState> p_state, AnimationPlayer *p_an Ref<Animation> animation; animation.instantiate(); animation->set_name(anim_name); - animation->set_step(1.0 / p_bake_fps); + animation->set_step(1.0 / p_state->get_bake_fps()); if (anim->get_loop()) { animation->set_loop_mode(Animation::LOOP_LINEAR); @@ -2013,6 +2011,7 @@ void FBXDocument::_process_mesh_instances(Ref<FBXState> p_state, Node *p_scene_r ERR_CONTINUE_MSG(skeleton == nullptr, vformat("Unable to find Skeleton for node %d skin %d", node_i, skin_i)); mi->get_parent()->remove_child(mi); + mi->set_owner(nullptr); skeleton->add_child(mi, true); mi->set_owner(skeleton->get_owner()); @@ -2110,14 +2109,11 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess> return OK; } -void FBXDocument::_bind_methods() { -} - Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) { Ref<FBXState> state = p_state; ERR_FAIL_COND_V(state.is_null(), nullptr); - ERR_FAIL_NULL_V(state, nullptr); ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); + p_state->set_bake_fps(p_bake_fps); GLTFNodeIndex fbx_root = state->root_nodes.write[0]; Node *fbx_root_node = state->get_scene_node(fbx_root); Node *root = fbx_root_node; @@ -2131,7 +2127,7 @@ Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool root->add_child(ap, true); ap->set_owner(root); for (int i = 0; i < state->animations.size(); i++) { - _import_animation(state, ap, i, p_bake_fps, p_trimming, p_remove_immutable_tracks); + _import_animation(state, ap, i, p_trimming, p_remove_immutable_tracks); } } ERR_FAIL_NULL_V(root, nullptr); @@ -2243,7 +2239,7 @@ Error FBXDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint3 Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); - ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN); + ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN); String base_path = p_base_path; if (base_path.is_empty()) { base_path = p_path.get_base_dir(); diff --git a/modules/fbx/fbx_document.h b/modules/fbx/fbx_document.h index c9256df444..96f1905881 100644 --- a/modules/fbx/fbx_document.h +++ b/modules/fbx/fbx_document.h @@ -61,9 +61,6 @@ public: PackedByteArray generate_buffer(Ref<GLTFState> p_state) override; Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) override; -protected: - static void _bind_methods(); - private: String _get_texture_path(const String &p_base_directory, const String &p_source_file_path) const; void _process_uv_set(PackedVector2Array &uv_array); @@ -99,7 +96,7 @@ public: void _generate_scene_node(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); void _generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); void _import_animation(Ref<FBXState> p_state, AnimationPlayer *p_animation_player, - const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks); + const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks); Error _parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess> p_file); }; diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig index 640c205093..b380846f86 100644 --- a/modules/gdscript/.editorconfig +++ b/modules/gdscript/.editorconfig @@ -1,8 +1,3 @@ [*.gd] -indent_style = tab indent_size = 4 -insert_final_newline = true trim_trailing_whitespace = true - -[*.out] -insert_final_newline = true diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py index a7d5c406e9..ecd33a5dac 100644 --- a/modules/gdscript/config.py +++ b/modules/gdscript/config.py @@ -11,6 +11,7 @@ def get_doc_classes(): return [ "@GDScript", "GDScript", + "GDScriptSyntaxHighlighter", ] diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 07d917ea04..f539f27848 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -52,9 +52,9 @@ <description> Returns a single character (as a [String]) of the given Unicode code point (which is compatible with ASCII code). [codeblock] - a = char(65) # a is "A" - a = char(65 + 32) # a is "a" - a = char(8364) # a is "€" + var upper = char(65) # upper is "A" + var lower = char(65 + 32) # lower is "a" + var euro = char(8364) # euro is "€" [/codeblock] </description> </method> @@ -133,7 +133,7 @@ - An [Object]-derived class which exists in [ClassDB], for example [Node]. - A [Script] (you can use any class, including inner one). Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays). Use the operator instead of this method if you do not need dynamic type checking. - Examples: + [b]Examples:[/b] [codeblock] print(is_instance_of(a, TYPE_INT)) print(is_instance_of(a, Node)) @@ -150,10 +150,10 @@ <description> Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], the element count of any array type, or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped. [codeblock] - a = [1, 2, 3, 4] + var a = [1, 2, 3, 4] len(a) # Returns 4 - b = "Hello!" + var b = "Hello!" len(b) # Returns 6 [/codeblock] </description> @@ -220,7 +220,7 @@ [code]range(b: int, n: int, s: int)[/code]: Starts from [code]b[/code], increases/decreases by steps of [code]s[/code], and stops [i]before[/i] [code]n[/code]. The arguments [code]b[/code] and [code]n[/code] are [b]inclusive[/b] and [b]exclusive[/b], respectively. The argument [code]s[/code] [b]can[/b] be negative, but not [code]0[/code]. If [code]s[/code] is [code]0[/code], an error message is printed. [method range] converts all arguments to [int] before processing. [b]Note:[/b] Returns an empty array if no value meets the value constraint (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]). - Examples: + [b]Examples:[/b] [codeblock] print(range(4)) # Prints [0, 1, 2, 3] print(range(2, 5)) # Prints [2, 3, 4] @@ -314,7 +314,7 @@ @export var image_array: Array[Image] @export var node_array: Array[Node] [/codeblock] - [b]Note:[/b] Custom resources and nodes must be registered as global classes using [code]class_name[/code]. + [b]Note:[/b] Custom resources and nodes should be registered as global classes using [code]class_name[/code], since the Inspector currently only supports global classes. Otherwise, a less specific type will be exported instead. [b]Note:[/b] Node export is only supported in [Node]-derived classes and has a number of other limitations. </description> </annotation> @@ -349,10 +349,11 @@ <param index="1" name="hint_string" type="String" /> <param index="2" name="usage" type="int" enum="PropertyUsageFlags" is_bitfield="true" default="6" /> <description> - Allows you to set a custom hint, hint string, and usage flags for the exported property. Note that there's no validation done in GDScript, it will just pass the hint along to the editor. + Allows you to set a custom hint, hint string, and usage flags for the exported property. Note that there's no validation done in GDScript, it will just pass the parameters to the editor. [codeblock] @export_custom(PROPERTY_HINT_NONE, "suffix:m") var suffix: Vector3 [/codeblock] + [b]Note:[/b] Regardless of the [param usage] value, the [constant PROPERTY_USAGE_SCRIPT_VARIABLE] flag is always added, as with any explicitly declared script variable. </description> </annotation> <annotation name="@export_dir"> diff --git a/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml new file mode 100644 index 0000000000..63a9222901 --- /dev/null +++ b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + A GDScript syntax highlighter that can be used with [TextEdit] and [CodeEdit] nodes. + </brief_description> + <description> + [b]Note:[/b] This class can only be used for editor plugins because it relies on editor settings. + [codeblocks] + [gdscript] + var code_preview = TextEdit.new() + var highlighter = GDScriptSyntaxHighlighter.new() + code_preview.syntax_highlighter = highlighter + [/gdscript] + [csharp] + var codePreview = new TextEdit(); + var highlighter = new GDScriptSyntaxHighlighter(); + codePreview.SyntaxHighlighter = highlighter; + [/csharp] + [/codeblocks] + </description> + <tutorials> + </tutorials> +</class> diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 35b69fab8c..32ef429b0d 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type return; } } + if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) { + String key, value; + _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum); + _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum); + if (key != "Variant" || value != "Variant") { + r_type = "Dictionary[" + key + ", " + value + "]"; + return; + } + } r_type = Variant::get_type_name(p_gdtype.builtin_type); return; case GDType::NATIVE: @@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re return "<Object>"; case Variant::DICTIONARY: { const Dictionary dict = p_variant; + String result; - if (dict.is_empty()) { - return "{}"; - } + if (dict.is_typed()) { + result += "Dictionary["; + + Ref<Script> key_script = dict.get_typed_key_script(); + if (key_script.is_valid()) { + if (key_script->get_global_name() != StringName()) { + result += key_script->get_global_name(); + } else if (!key_script->get_path().get_file().is_empty()) { + result += key_script->get_path().get_file(); + } else { + result += dict.get_typed_key_class_name(); + } + } else if (dict.get_typed_key_class_name() != StringName()) { + result += dict.get_typed_key_class_name(); + } else if (dict.is_typed_key()) { + result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin()); + } else { + result += "Variant"; + } + + result += ", "; - if (p_recursion_level > MAX_RECURSION_LEVEL) { - return "{...}"; + Ref<Script> value_script = dict.get_typed_value_script(); + if (value_script.is_valid()) { + if (value_script->get_global_name() != StringName()) { + result += value_script->get_global_name(); + } else if (!value_script->get_path().get_file().is_empty()) { + result += value_script->get_path().get_file(); + } else { + result += dict.get_typed_value_class_name(); + } + } else if (dict.get_typed_value_class_name() != StringName()) { + result += dict.get_typed_value_class_name(); + } else if (dict.is_typed_value()) { + result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin()); + } else { + result += "Variant"; + } + + result += "]("; } - List<Variant> keys; - dict.get_key_list(&keys); - keys.sort(); + if (dict.is_empty()) { + result += "{}"; + } else if (p_recursion_level > MAX_RECURSION_LEVEL) { + result += "{...}"; + } else { + result += "{"; + + List<Variant> keys; + dict.get_key_list(&keys); + keys.sort(); - String data; - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - if (E->prev()) { - data += ", "; + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + if (E->prev()) { + result += ", "; + } + result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); } - data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); + + result += "}"; + } + + if (dict.is_typed()) { + result += ")"; } - return "{" + data + "}"; + return result; } break; case Variant::ARRAY: { const Array array = p_variant; String result; - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { result += "Array["; Ref<Script> script = array.get_typed_script(); @@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re result += "[...]"; } else { result += "["; + for (int i = 0; i < array.size(); i++) { if (i > 0) { result += ", "; } result += _docvalue_from_variant(array[i], p_recursion_level + 1); } + result += "]"; } - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { result += ")"; } diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index f83b784f85..d765cfa1ea 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -690,7 +690,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { color_regions.clear(); color_region_cache.clear(); - font_color = text_edit->get_theme_color(SNAME("font_color")); + font_color = text_edit->get_theme_color(SceneStringName(font_color)); symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); @@ -852,6 +852,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09); } + // TODO: Move to editor_settings.cpp EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color); EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color); EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color); diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 316281209a..b31ae878ce 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -71,7 +71,7 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) { ERR_FAIL_NULL_V(p_expression, false); - return p_expression->is_constant && (p_expression->reduced_value.get_type() == Variant::STRING || p_expression->reduced_value.get_type() == Variant::STRING_NAME); + return p_expression->is_constant && p_expression->reduced_value.is_string(); } void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) { @@ -276,8 +276,8 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C id_ctx_plural.resize(3); bool extract_id_ctx_plural = true; - if (function_name == tr_func) { - // Extract from tr(id, ctx). + if (function_name == tr_func || function_name == atr_func) { + // Extract from `tr(id, ctx)` or `atr(id, ctx)`. for (int i = 0; i < p_call->arguments.size(); i++) { if (_is_constant_string(p_call->arguments[i])) { id_ctx_plural.write[i] = p_call->arguments[i]->reduced_value; @@ -289,8 +289,8 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C if (extract_id_ctx_plural) { ids_ctx_plural->push_back(id_ctx_plural); } - } else if (function_name == trn_func) { - // Extract from tr_n(id, plural, n, ctx). + } else if (function_name == trn_func || function_name == atrn_func) { + // Extract from `tr_n(id, plural, n, ctx)` or `atr_n(id, plural, n, ctx)`. Vector<int> indices; indices.push_back(0); indices.push_back(3); diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index fe876134c2..61ff81ed66 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -45,6 +45,8 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug // List of patterns used for extracting translation strings. StringName tr_func = "tr"; StringName trn_func = "tr_n"; + StringName atr_func = "atr"; + StringName atrn_func = "atr_n"; HashSet<StringName> assignment_patterns; HashSet<StringName> first_arg_patterns; HashSet<StringName> second_arg_patterns; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index de56dd5ece..7b9aa70686 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -53,9 +53,11 @@ #include "core/io/file_access_encrypted.h" #include "core/os/os.h" +#include "scene/resources/packed_scene.h" #include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED +#include "core/extension/gdextension_manager.h" #include "editor/editor_paths.h" #endif @@ -115,7 +117,7 @@ Variant GDScriptNativeClass::callp(const StringName &p_method, const Variant **p } GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { - if (p_script->initializer) { + if (likely(p_script->valid) && p_script->initializer) { return p_script->initializer; } else { GDScript *base_src = p_script->_base; @@ -136,7 +138,11 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance } } ERR_FAIL_NULL(p_script->implicit_initializer); - p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); + if (likely(p_script->valid)) { + p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); + } else { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } } GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { @@ -396,6 +402,8 @@ bool GDScript::get_property_default_value(const StringName &p_property, Variant } ScriptInstance *GDScript::instance_create(Object *p_this) { + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Script is invalid!"); + GDScript *top = this; while (top->_base) { top = top->_base; @@ -660,7 +668,7 @@ String GDScript::_get_debug_path() const { } Error GDScript::_static_init() { - if (static_initializer) { + if (likely(valid) && static_initializer) { Callable::CallError call_err; static_initializer->call(nullptr, nullptr, 0, call_err); if (call_err.error != Callable::CallError::CALL_OK) { @@ -677,8 +685,6 @@ Error GDScript::_static_init() { return err; } -#ifdef TOOLS_ENABLED - void GDScript::_static_default_init() { for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) { const GDScriptDataType &type = E.value.data_type; @@ -700,6 +706,8 @@ void GDScript::_static_default_init() { } } +#ifdef TOOLS_ENABLED + void GDScript::_save_old_static_data() { old_static_variables_indices = static_variables_indices; old_static_variables = static_variables; @@ -769,8 +777,16 @@ Error GDScript::reload(bool p_keep_state) { if (GDScriptCache::has_parser(source_path)) { Error err = OK; Ref<GDScriptParserRef> parser_ref = GDScriptCache::get_parser(source_path, GDScriptParserRef::EMPTY, err); - if (parser_ref.is_valid() && parser_ref->get_source_hash() != source.hash()) { - GDScriptCache::remove_parser(source_path); + if (parser_ref.is_valid()) { + uint32_t source_hash; + if (!binary_tokens.is_empty()) { + source_hash = hash_djb2_buffer(binary_tokens.ptr(), binary_tokens.size()); + } else { + source_hash = source.hash(); + } + if (parser_ref->get_source_hash() != source_hash) { + GDScriptCache::remove_parser(source_path); + } } } } @@ -863,9 +879,11 @@ Error GDScript::reload(bool p_keep_state) { #ifdef TOOLS_ENABLED if (can_run && p_keep_state) { _restore_old_static_data(); - } else if (!can_run) { - // Initialize static variables with sane default values even if the constructor isn't called. - _static_default_init(); + } + + if (p_keep_state) { + // Update the properties in the inspector. + update_exports(); } #endif @@ -893,7 +911,7 @@ void GDScript::get_members(HashSet<StringName> *p_members) { } } -const Variant GDScript::get_rpc_config() const { +Variant GDScript::get_rpc_config() const { return rpc_config; } @@ -904,11 +922,13 @@ void GDScript::unload_static() const { Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *top = this; while (top) { - HashMap<StringName, GDScriptFunction *>::Iterator E = top->member_functions.find(p_method); - if (E) { - ERR_FAIL_COND_V_MSG(!E->value->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script."); + if (likely(top->valid)) { + HashMap<StringName, GDScriptFunction *>::Iterator E = top->member_functions.find(p_method); + if (E) { + ERR_FAIL_COND_V_MSG(!E->value->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script."); - return E->value->call(nullptr, p_args, p_argcount, r_error); + return E->value->call(nullptr, p_args, p_argcount, r_error); + } } top = top->_base; } @@ -937,9 +957,10 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { { HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name); if (E) { - if (E->value.getter) { + if (likely(top->valid) && E->value.getter) { Callable::CallError ce; - r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); + const Variant ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); + r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant(); return true; } r_ret = top->static_variables[E->value.index]; @@ -947,7 +968,7 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { } } - { + if (likely(top->valid)) { HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name); if (E && E->value->is_static()) { if (top->rpc_config.has(p_name)) { @@ -976,7 +997,7 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { bool GDScript::_set(const StringName &p_name, const Variant &p_value) { if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { set_source_code(p_value); - reload(); + reload(true); return true; } @@ -994,7 +1015,7 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { return false; } } - if (member->setter) { + if (likely(top->valid) && member->setter) { const Variant *args = &value; Callable::CallError err; callp(member->setter, &args, 1, err); @@ -1524,10 +1545,14 @@ void GDScript::clear(ClearData *p_clear_data) { } } - RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); - for (GDScript *E : must_clear_dependencies) { - clear_data->scripts.insert(E); - E->clear(clear_data); + // If we're in the process of shutting things down then every single script will be cleared + // anyway, so we can safely skip this very costly operation. + if (!GDScriptLanguage::singleton->finishing) { + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + for (GDScript *E : must_clear_dependencies) { + clear_data->scripts.insert(E); + E->clear(clear_data); + } } for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { @@ -1643,7 +1668,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { return false; } } - if (member->setter) { + if (likely(script->valid) && member->setter) { const Variant *args = &value; Callable::CallError err; callp(member->setter, &args, 1, err); @@ -1670,7 +1695,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { return false; } } - if (member->setter) { + if (likely(sptr->valid) && member->setter) { const Variant *args = &value; Callable::CallError err; callp(member->setter, &args, 1, err); @@ -1682,7 +1707,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { } } - { + if (likely(sptr->valid)) { HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); if (E) { Variant name = p_name; @@ -1706,12 +1731,11 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { { HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name); if (E) { - if (E->value.getter) { + if (likely(script->valid) && E->value.getter) { Callable::CallError err; - r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); - if (err.error == Callable::CallError::CALL_OK) { - return true; - } + const Variant ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); + r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant(); + return true; } r_ret = members[E->value.index]; return true; @@ -1731,9 +1755,10 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { { HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); if (E) { - if (E->value.getter) { + if (likely(sptr->valid) && E->value.getter) { Callable::CallError ce; - r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); + const Variant ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); + r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant(); return true; } r_ret = sptr->static_variables[E->value.index]; @@ -1749,7 +1774,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } } - { + if (likely(sptr->valid)) { HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name); if (E) { if (sptr->rpc_config.has(p_name)) { @@ -1769,7 +1794,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } } - { + if (likely(sptr->valid)) { HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get); if (E) { Variant name = p_name; @@ -1809,13 +1834,15 @@ void GDScriptInstance::validate_property(PropertyInfo &p_property) const { const GDScript *sptr = script.ptr(); while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property); - if (E) { - Callable::CallError err; - Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); - if (err.error == Callable::CallError::CALL_OK) { - p_property = PropertyInfo::from_dict(property); - return; + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK) { + p_property = PropertyInfo::from_dict(property); + return; + } } } sptr = sptr->_base; @@ -1829,39 +1856,41 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const List<PropertyInfo> props; while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list); - if (E) { - Callable::CallError err; - Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err); - if (err.error == Callable::CallError::CALL_OK) { - ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries."); - - Array arr = ret; - for (int i = 0; i < arr.size(); i++) { - Dictionary d = arr[i]; - ERR_CONTINUE(!d.has("name")); - ERR_CONTINUE(!d.has("type")); - - PropertyInfo pinfo; - pinfo.name = d["name"]; - pinfo.type = Variant::Type(d["type"].operator int()); - if (d.has("hint")) { - pinfo.hint = PropertyHint(d["hint"].operator int()); - } - if (d.has("hint_string")) { - pinfo.hint_string = d["hint_string"]; - } - if (d.has("usage")) { - pinfo.usage = d["usage"]; - } - if (d.has("class_name")) { - pinfo.class_name = d["class_name"]; - } + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list); + if (E) { + Callable::CallError err; + Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err); + if (err.error == Callable::CallError::CALL_OK) { + ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries."); + + Array arr = ret; + for (int i = 0; i < arr.size(); i++) { + Dictionary d = arr[i]; + ERR_CONTINUE(!d.has("name")); + ERR_CONTINUE(!d.has("type")); + + PropertyInfo pinfo; + pinfo.name = d["name"]; + pinfo.type = Variant::Type(d["type"].operator int()); + if (d.has("hint")) { + pinfo.hint = PropertyHint(d["hint"].operator int()); + } + if (d.has("hint_string")) { + pinfo.hint_string = d["hint_string"]; + } + if (d.has("usage")) { + pinfo.usage = d["usage"]; + } + if (d.has("class_name")) { + pinfo.class_name = d["class_name"]; + } - ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE)); - ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); + ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE)); + ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); - props.push_back(pinfo); + props.push_back(pinfo); + } } } } @@ -1906,12 +1935,14 @@ bool GDScriptInstance::property_can_revert(const StringName &p_name) const { const GDScript *sptr = script.ptr(); while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert); - if (E) { - Callable::CallError err; - Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); - if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { - return true; + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { + return true; + } } } sptr = sptr->_base; @@ -1926,13 +1957,15 @@ bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ const GDScript *sptr = script.ptr(); while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert); - if (E) { - Callable::CallError err; - Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); - if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { - r_ret = ret; - return true; + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + r_ret = ret; + return true; + } } } sptr = sptr->_base; @@ -1988,7 +2021,7 @@ void GDScriptInstance::_call_implicit_ready_recursively(GDScript *p_script) { if (p_script->_base) { _call_implicit_ready_recursively(p_script->_base); } - if (p_script->implicit_ready) { + if (likely(p_script->valid) && p_script->implicit_ready) { Callable::CallError err; p_script->implicit_ready->call(this, nullptr, 0, err); } @@ -2001,17 +2034,24 @@ Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_ar _call_implicit_ready_recursively(sptr); } while (sptr) { - HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); - if (E) { - return E->value->call(this, p_args, p_argcount, r_error); + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); + if (E) { + return E->value->call(this, p_args, p_argcount, r_error); + } } sptr = sptr->_base; } + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); } void GDScriptInstance::notification(int p_notification, bool p_reversed) { + if (unlikely(!script->valid)) { + return; + } + //notification is not virtual, it gets called at ALL levels just like in C. Variant value = p_notification; const Variant *args[1] = { &value }; @@ -2027,12 +2067,14 @@ void GDScriptInstance::notification(int p_notification, bool p_reversed) { sptr = sptr->_base; } for (GDScript *sc : pl) { - HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification); - if (E) { - Callable::CallError err; - E->value->call(this, args, 1, err); - if (err.error != Callable::CallError::CALL_OK) { - //print error about notification call + if (likely(sc->valid)) { + HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification); + if (E) { + Callable::CallError err; + E->value->call(this, args, 1, err); + if (err.error != Callable::CallError::CALL_OK) { + //print error about notification call + } } } } @@ -2142,9 +2184,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va global_array.write[globals[p_name]] = p_value; return; } - globals[p_name] = global_array.size(); - global_array.push_back(p_value); - _global_array = global_array.ptrw(); + + if (global_array_empty_indexes.size()) { + int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1]; + globals[p_name] = index; + global_array.write[index] = p_value; + global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1); + } else { + globals[p_name] = global_array.size(); + global_array.push_back(p_value); + _global_array = global_array.ptrw(); + } +} + +void GDScriptLanguage::_remove_global(const StringName &p_name) { + if (!globals.has(p_name)) { + return; + } + global_array_empty_indexes.push_back(globals[p_name]); + global_array.write[globals[p_name]] = Variant::NIL; + globals.erase(p_name); } void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) { @@ -2202,11 +2261,40 @@ void GDScriptLanguage::init() { _add_global(E.name, E.ptr); } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded)); + GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading)); + } +#endif + #ifdef TESTS_ENABLED GDScriptTests::GDScriptTestRunner::handle_cmdline(); #endif } +#ifdef TOOLS_ENABLED +void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) { + List<StringName> class_list; + ClassDB::get_extension_class_list(p_extension, &class_list); + for (const StringName &n : class_list) { + if (globals.has(n)) { + continue; + } + Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n)); + _add_global(n, nc); + } +} + +void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) { + List<StringName> class_list; + ClassDB::get_extension_class_list(p_extension, &class_list); + for (const StringName &n : class_list) { + _remove_global(n); + } +} +#endif + String GDScriptLanguage::get_type() const { return "GDScript"; } @@ -2216,6 +2304,11 @@ String GDScriptLanguage::get_extension() const { } void GDScriptLanguage::finish() { + if (finishing) { + return; + } + finishing = true; + _call_stack.free(); // Clear the cache before parsing the script_list @@ -2251,6 +2344,8 @@ void GDScriptLanguage::finish() { } script_list.clear(); function_list.clear(); + + finishing = false; } void GDScriptLanguage::profiling_start() { @@ -2462,7 +2557,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload SelfList<GDScript> *elem = script_list.first(); while (elem) { // Scripts will reload all subclasses, so only reload root scripts. - if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { + if (elem->self()->is_root_script() && !elem->self()->get_path().is_empty()) { scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } elem = elem->next(); @@ -2530,7 +2625,19 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) { Ref<GDScript> scr = E.key; print_verbose("GDScript: Reloading: " + scr->get_path()); - scr->load_source_code(scr->get_path()); + if (scr->is_built_in()) { + // TODO: It would be nice to do it more efficiently than loading the whole scene again. + Ref<PackedScene> scene = ResourceLoader::load(scr->get_path().get_slice("::", 0), "", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP); + ERR_CONTINUE(scene.is_null()); + + Ref<SceneState> state = scene->get_state(); + Ref<GDScript> fresh = state->get_sub_resource(scr->get_path()); + ERR_CONTINUE(fresh.is_null()); + + scr->set_source_code(fresh->get_source_code()); + } else { + scr->load_source_code(scr->get_path()); + } scr->reload(p_soft_reload); //restore state if saved @@ -2703,7 +2810,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b String source = f->get_as_utf8_string(); GDScriptParser parser; - err = parser.parse(source, p_path, false); + err = parser.parse(source, p_path, false, false); const GDScriptParser::ClassNode *c = parser.get_tree(); if (!c) { @@ -2816,8 +2923,11 @@ GDScriptLanguage::GDScriptLanguage() { _debug_parse_err_line = -1; _debug_parse_err_file = ""; +#ifdef DEBUG_ENABLED profiling = false; + profile_native_calls = false; script_frame_time = 0; +#endif int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 728459de44..9bb39aac0f 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -169,9 +169,7 @@ private: GDScriptFunction *static_initializer = nullptr; Error _static_init(); -#ifdef TOOLS_ENABLED void _static_default_init(); // Initialize static variables with default values based on their types. -#endif int subclass_count = 0; RBSet<Object *> instances; @@ -336,7 +334,7 @@ public: virtual void get_constants(HashMap<StringName, Variant> *p_constants) override; virtual void get_members(HashSet<StringName> *p_members) override; - virtual const Variant get_rpc_config() const override; + virtual Variant get_rpc_config() const override; void unload_static() const; @@ -413,10 +411,13 @@ class GDScriptLanguage : public ScriptLanguage { static GDScriptLanguage *singleton; + bool finishing = false; + Variant *_global_array = nullptr; Vector<Variant> global_array; HashMap<StringName, int> globals; HashMap<StringName, Variant> named_globals; + Vector<int> global_array_empty_indexes; struct CallLevel { Variant *stack = nullptr; @@ -448,6 +449,7 @@ class GDScriptLanguage : public ScriptLanguage { int _debug_max_call_stack = 0; void _add_global(const StringName &p_name, const Variant &p_value); + void _remove_global(const StringName &p_name); friend class GDScriptInstance; @@ -459,12 +461,19 @@ class GDScriptLanguage : public ScriptLanguage { friend class GDScriptFunction; SelfList<GDScriptFunction>::List function_list; +#ifdef DEBUG_ENABLED bool profiling; bool profile_native_calls; uint64_t script_frame_time; +#endif HashMap<String, ObjectID> orphan_subclasses; +#ifdef TOOLS_ENABLED + void _extension_loaded(const Ref<GDExtension> &p_extension); + void _extension_unloading(const Ref<GDExtension> &p_extension); +#endif + public: int calls; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 38d5c59e0e..7f0d5005cb 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -285,7 +285,7 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C return OK; } -void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { +void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list, GDScriptParser::Node *p_source) { ERR_FAIL_NULL(p_node); ERR_FAIL_NULL(p_list); @@ -299,11 +299,15 @@ void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::Clas // Prioritize node base type over its outer class if (p_node->base_type.class_type != nullptr) { - get_class_node_current_scope_classes(p_node->base_type.class_type, p_list); + // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here. + ensure_cached_external_parser_for_class(p_node->base_type.class_type, p_node, "Trying to fetch classes in the current scope", p_source); + get_class_node_current_scope_classes(p_node->base_type.class_type, p_list, p_source); } if (p_node->outer != nullptr) { - get_class_node_current_scope_classes(p_node->outer, p_list); + // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here. + ensure_cached_external_parser_for_class(p_node->outer, p_node, "Trying to fetch classes in the current scope", p_source); + get_class_node_current_scope_classes(p_node->outer, p_list, p_source); } } @@ -312,6 +316,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c p_source = p_class; } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class inheritance", p_source); + Finally finally([&]() { + for (GDScriptParser::ClassNode *look_class = p_class; look_class != nullptr; look_class = look_class->base_type.class_type) { + ensure_cached_external_parser_for_class(look_class->base_type.class_type, look_class, "Trying to resolve class inheritance", p_source); + } + }); + if (p_class->base_type.is_resolving()) { push_error(vformat(R"(Could not resolve class "%s": Cyclic reference.)", type_from_metatype(p_class->get_datatype()).to_string()), p_source); return ERR_PARSE_ERROR; @@ -323,21 +334,17 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + // Error already pushed. return ERR_PARSE_ERROR; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not parse script "%s": %s.)", script_path, error_names[err]), p_source); + push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source); return ERR_PARSE_ERROR; } - ERR_FAIL_COND_V_MSG(!parser_ref->get_parser()->has_class(p_class), ERR_PARSE_ERROR, R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); @@ -411,6 +418,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c return err; } +#ifdef DEBUG_ENABLED + if (!parser->_is_tool && ext_parser->get_parser()->_is_tool) { + parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL); + } +#endif + base = ext_parser->get_parser()->head->get_datatype(); } else { if (p_class->extends.is_empty()) { @@ -438,6 +451,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id); return err; } + +#ifdef DEBUG_ENABLED + if (!parser->_is_tool && base_parser->get_parser()->_is_tool) { + parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL); + } +#endif + base = base_parser->get_parser()->head->get_datatype(); } } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { @@ -458,6 +478,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id); return err; } + +#ifdef DEBUG_ENABLED + if (!parser->_is_tool && info_parser->get_parser()->_is_tool) { + parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL); + } +#endif + base = info_parser->get_parser()->head->get_datatype(); } else if (class_exists(name)) { if (Engine::get_singleton()->has_singleton(name)) { @@ -471,7 +498,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c // Look for other classes in script. bool found = false; List<GDScriptParser::ClassNode *> script_classes; - get_class_node_current_scope_classes(p_class, &script_classes); + get_class_node_current_scope_classes(p_class, &script_classes, id); for (GDScriptParser::ClassNode *look_class : script_classes) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { @@ -697,6 +724,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.set_container_element_type(0, container_type); } } + if (result.builtin_type == Variant::DICTIONARY) { + GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0))); + if (key_type.kind != GDScriptParser::DataType::VARIANT) { + key_type.is_constant = false; + result.set_container_element_type(0, key_type); + } + GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1))); + if (value_type.kind != GDScriptParser::DataType::VARIANT) { + value_type.is_constant = false; + result.set_container_element_type(1, value_type); + } + } } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; @@ -763,7 +802,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type // Classes in current scope. List<GDScriptParser::ClassNode *> script_classes; bool found = false; - get_class_node_current_scope_classes(parser->current_class, &script_classes); + get_class_node_current_scope_classes(parser->current_class, &script_classes, p_type); for (GDScriptParser::ClassNode *script_class : script_classes) { if (found) { break; @@ -857,11 +896,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type if (!p_type->container_types.is_empty()) { if (result.builtin_type == Variant::ARRAY) { if (p_type->container_types.size() != 1) { - push_error("Arrays require exactly one collection element type.", p_type); + push_error(R"(Typed arrays require exactly one collection element type.)", p_type); + return bad_type; + } + } else if (result.builtin_type == Variant::DICTIONARY) { + if (p_type->container_types.size() != 2) { + push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type); return bad_type; } } else { - push_error("Only arrays can specify collection element types.", p_type); + push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type); return bad_type; } } @@ -883,6 +927,15 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, p_source = member.get_source_node(); } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class member", p_source); + Finally finally([&]() { + ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source); + GDScriptParser::DataType member_type = member.get_datatype(); + if (member_type.has_container_element_type(0)) { + ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source); + } + }); + if (member.get_datatype().is_resolving()) { push_error(vformat(R"(Could not resolve member "%s": Cyclic reference.)", member.get_name()), p_source); return; @@ -892,42 +945,39 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, return; } + // If it's already resolving, that's ok. + if (!p_class->base_type.is_resolving()) { + Error err = resolve_class_inheritance(p_class); + if (err) { + return; + } + } + if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s" (While resolving "%s").)", script_path, member.get_name()), p_source); + // Error already pushed. return; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not resolve script "%s": %s (While resolving "%s").)", script_path, error_names[err], member.get_name()), p_source); + push_error(vformat(R"(Could not parse script "%s": %s (While resolving external class member "%s").)", p_class->get_datatype().script_path, error_names[err], member.get_name()), p_source); return; } - ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); int error_count = other_parser->errors.size(); other_analyzer->resolve_class_member(p_class, p_index); if (other_parser->errors.size() > error_count) { - push_error(vformat(R"(Could not resolve member "%s".)", member.get_name()), p_source); + push_error(vformat(R"(Could not resolve external class member "%s".)", member.get_name()), p_source); + return; } return; } - // If it's already resolving, that's ok. - if (!p_class->base_type.is_resolving()) { - Error err = resolve_class_inheritance(p_class); - if (err) { - return; - } - } - GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; @@ -1170,27 +1220,25 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_source = p_class; } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class interface", p_source); + if (!p_class->resolved_interface) { #ifdef DEBUG_ENABLED bool has_static_data = p_class->has_static_data; #endif if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + // Error already pushed. return; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source); return; } - ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); @@ -1198,6 +1246,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas other_analyzer->resolve_class_interface(p_class); if (other_parser->errors.size() > error_count) { push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + return; } return; @@ -1261,26 +1310,24 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co p_source = p_class; } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class body", p_source); + if (p_class->resolved_body) { return; } if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + // Error already pushed. return; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source); return; } - ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); @@ -1288,6 +1335,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co other_analyzer->resolve_class_body(p_class); if (other_parser->errors.size() > error_count) { push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + return; } return; @@ -1895,6 +1943,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi if (has_specified_type && specified_type.has_container_element_type(0)) { update_array_literal_element_type(array, specified_type.get_container_element_type(0)); } + } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { + GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer); + if (has_specified_type && specified_type.has_container_element_types()) { + update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1)); + } } if (is_constant && !p_assignable->initializer->is_constant) { @@ -1956,7 +2009,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); } - } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) { + } else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) { mark_node_unsafe(p_assignable->initializer); #ifdef DEBUG_ENABLED } else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { @@ -1967,15 +2020,20 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } #ifdef DEBUG_ENABLED + const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; if (!has_specified_type) { - const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable"); if (p_assignable->infer_datatype || is_constant) { - parser->push_warning(p_assignable, GDScriptWarning::INFERRED_DECLARATION, declaration_type, p_assignable->identifier->name); + // Do not produce the `INFERRED_DECLARATION` warning on type import because there is no way to specify the true type. + // And removing the metatype makes it impossible to use the constant as a type hint (especially for enums). + const bool is_type_import = is_constant && p_assignable->initializer != nullptr && p_assignable->initializer->datatype.is_meta_type; + if (!is_type_import) { + parser->push_warning(p_assignable, GDScriptWarning::INFERRED_DECLARATION, declaration_type, p_assignable->identifier->name); + } } else { parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); } - } else if (specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { + } else if (!is_parameter && specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { // Warn about enum variables without default value. Unless the enum defines the "0" value, then it's fine. bool has_zero_value = false; for (const KeyValue<StringName, int64_t> &kv : specified_type.enum_values) { @@ -2193,8 +2251,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } else if (!is_type_compatible(specified_type, variable_type)) { p_for->use_conversion_assign = true; } - if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + if (p_for->list) { + if (p_for->list->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + } else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type()); + } } } p_for->variable->set_datatype(specified_type); @@ -2396,6 +2458,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { } else { if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0)); + } else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value), + expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1)); } if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); @@ -2642,6 +2707,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo p_array->set_datatype(array_type); } +// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed. +// This function determines which type is that (if any). +void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) { + GDScriptParser::DataType expected_key_type = p_key_element_type; + GDScriptParser::DataType expected_value_type = p_value_element_type; + expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported. + expected_value_type.container_element_types.clear(); + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key; + if (key_element_node->is_constant) { + update_const_expression_builtin_type(key_element_node, expected_key_type, "include"); + } + const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype(); + if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) { + mark_node_unsafe(key_element_node); + } else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) { + if (is_type_compatible(actual_key_type, expected_key_type)) { + mark_node_unsafe(key_element_node); + } else { + push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node); + return; + } + } + + GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value; + if (value_element_node->is_constant) { + update_const_expression_builtin_type(value_element_node, expected_value_type, "include"); + } + const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype(); + if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) { + mark_node_unsafe(value_element_node); + } else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) { + if (is_type_compatible(actual_value_type, expected_value_type)) { + mark_node_unsafe(value_element_node); + } else { + push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node); + return; + } + } + } + + GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype(); + dictionary_type.set_container_element_type(0, expected_key_type); + dictionary_type.set_container_element_type(1, expected_value_type); + p_dictionary->set_datatype(dictionary_type); +} + void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { reduce_expression(p_assignment->assigned_value); @@ -2658,6 +2771,44 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig reduce_expression(p_assignment->assignee); +#ifdef DEBUG_ENABLED + { + bool is_subscript = false; + GDScriptParser::ExpressionNode *base = p_assignment->assignee; + while (base && base->type == GDScriptParser::Node::SUBSCRIPT) { + is_subscript = true; + base = static_cast<GDScriptParser::SubscriptNode *>(base)->base; + } + if (base && base->type == GDScriptParser::Node::IDENTIFIER) { + GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(base); + if (current_lambda && current_lambda->captures_indices.has(id->name)) { + bool need_warn = false; + if (is_subscript) { + const GDScriptParser::DataType &id_type = id->datatype; + if (id_type.is_hard_type()) { + switch (id_type.kind) { + case GDScriptParser::DataType::BUILTIN: + // TODO: Change `Variant::is_type_shared()` to include packed arrays? + need_warn = !Variant::is_type_shared(id_type.builtin_type) && id_type.builtin_type < Variant::PACKED_BYTE_ARRAY; + break; + case GDScriptParser::DataType::ENUM: + need_warn = true; + break; + default: + break; + } + } + } else { + need_warn = true; + } + if (need_warn) { + parser->push_warning(p_assignment, GDScriptWarning::CONFUSABLE_CAPTURE_REASSIGNMENT, id->name); + } + } + } + } +#endif + if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) { return; } @@ -2696,9 +2847,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } - // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. + // Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate. if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0)); + } else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value), + assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1)); } if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) { @@ -2776,8 +2930,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig // weak non-variant assignee and incompatible result downgrades_assignee = true; } - } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) { - // typed array assignee and untyped array result + } else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) { + // Typed assignee and untyped result. mark_node_unsafe(p_assignment); } } @@ -2975,10 +3129,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) { bool all_is_constant = true; HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. + HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries. for (int i = 0; i < p_call->arguments.size(); i++) { reduce_expression(p_call->arguments[i]); if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) { arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]); + } else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) { + dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]); } all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; } @@ -3171,6 +3328,26 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); } } + +#ifdef DEBUG_ENABLED + // Consider `Signal(self, "my_signal")` as an implicit use of the signal. + if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) { + const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0]; + if (object_arg && object_arg->type == GDScriptParser::Node::SELF) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } + } +#endif + p_call->set_datatype(call_type); return; } else if (GDScriptUtilityFunctions::function_exists(function_name)) { @@ -3363,6 +3540,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0)); } } + for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) { + int index = E.key; + if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) { + GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0); + GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1); + update_dictionary_literal_element_type(E.value, key, value); + } + } validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call); if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { @@ -3394,6 +3579,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } #ifdef DEBUG_ENABLED + // FIXME: No warning for built-in constructors and utilities due to early return. if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL && !(p_call->is_super && p_call->function_name == GDScriptLanguage::get_singleton()->strings._init)) { parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); @@ -3404,6 +3590,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } + + // Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal. + if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } #endif // DEBUG_ENABLED call_type = return_type; @@ -3492,6 +3692,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0)); } + if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand), + cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1)); + } + if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (op_type.is_variant() || !op_type.is_hard_type()) { @@ -3601,12 +3806,116 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str } } +Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) { + if (p_class == nullptr) { + return nullptr; + } + + if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(p_class)) { + return E->value; + } + + if (parser->has_class(p_class)) { + return nullptr; + } + + if (p_from_class == nullptr) { + p_from_class = parser->head; + } + + String script_path = p_class->get_datatype().script_path; + + Ref<GDScriptParserRef> parser_ref; + for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) { + if (parser->has_class(look_class)) { + parser_ref = find_cached_external_parser_for_class(p_class, parser); + if (parser_ref.is_valid()) { + break; + } + } + + if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(look_class)) { + parser_ref = find_cached_external_parser_for_class(p_class, E->value); + if (parser_ref.is_valid()) { + break; + } + } + + String look_class_script_path = look_class->get_datatype().script_path; + if (HashMap<String, Ref<GDScriptParserRef>>::Iterator E = parser->depended_parsers.find(look_class_script_path)) { + parser_ref = find_cached_external_parser_for_class(p_class, E->value); + if (parser_ref.is_valid()) { + break; + } + } + } + + if (parser_ref.is_null()) { + push_error(vformat(R"(Parser bug (please report): Could not find external parser for class "%s". (%s))", p_class->fqcn, p_context), p_source); + // A null parser will be inserted into the cache, so this error won't spam for the same class. + // This is ok, the values of external_class_parser_cache are not assumed to be valid references. + } + + external_class_parser_cache.insert(p_class, parser_ref); + return parser_ref; +} + +Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser) { + if (p_dependant_parser.is_null()) { + return nullptr; + } + + if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = p_dependant_parser->get_analyzer()->external_class_parser_cache.find(p_class)) { + if (E->value.is_valid()) { + // Silently ensure it's parsed. + E->value->raise_status(GDScriptParserRef::PARSED); + if (E->value->get_parser()->has_class(p_class)) { + return E->value; + } + } + } + + if (p_dependant_parser->get_parser()->has_class(p_class)) { + return p_dependant_parser; + } + + // Silently ensure it's parsed. + p_dependant_parser->raise_status(GDScriptParserRef::PARSED); + return find_cached_external_parser_for_class(p_class, p_dependant_parser->get_parser()); +} + +Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser) { + if (p_dependant_parser == nullptr) { + return nullptr; + } + + String script_path = p_class->get_datatype().script_path; + if (HashMap<String, Ref<GDScriptParserRef>>::Iterator E = p_dependant_parser->depended_parsers.find(script_path)) { + if (E->value.is_valid()) { + // Silently ensure it's parsed. + E->value->raise_status(GDScriptParserRef::PARSED); + if (E->value->get_parser()->has_class(p_class)) { + return E->value; + } + } + } + + return nullptr; +} + +Ref<GDScript> GDScriptAnalyzer::get_depended_shallow_script(const String &p_path, Error &r_error) { + // To keep a local cache of the parser for resolving external nodes later. + parser->get_depended_parser_for(p_path); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_path, r_error, parser->script_path); + return scr; +} + void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype) { ERR_FAIL_NULL(p_identifier); p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err, parser->script_path); + Ref<GDScript> scr = get_depended_shallow_script(p_identifier_datatype.script_path, err); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier); return; @@ -3723,7 +4032,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod bool is_base = true; if (base_class != nullptr) { - get_class_node_current_scope_classes(base_class, &script_classes); + get_class_node_current_scope_classes(base_class, &script_classes, p_identifier); } bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new"); @@ -4060,10 +4369,23 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident mark_lambda_use_self(); return; // No need to capture. } - // If the identifier is local, check if it's any kind of capture by comparing their source function. - // Only capture locals and enum values. Constants are still accessible from the lambda using the script reference. If not, this method is done. - if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT) { - return; + + switch (p_identifier->source) { + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: + case GDScriptParser::IdentifierNode::LOCAL_BIND: + break; // Need to capture. + case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: // A global. + case GDScriptParser::IdentifierNode::LOCAL_CONSTANT: + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: + case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: + case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: + case GDScriptParser::IdentifierNode::MEMBER_CLASS: + case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: + case GDScriptParser::IdentifierNode::STATIC_VARIABLE: + return; // No need to capture. } GDScriptParser::FunctionNode *function_test = current_lambda->function; @@ -4280,15 +4602,20 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { // Must load GDScript separately to permit cyclic references // as ResourceLoader::load() detects and rejects those. - if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") { + const String &res_type = ResourceLoader::get_resource_type(p_preload->resolved_path); + if (res_type == "GDScript") { Error err = OK; - Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path); + Ref<GDScript> res = get_depended_shallow_script(p_preload->resolved_path, err); p_preload->resource = res; if (err != OK) { push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path); } } else { - p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + Error err = OK; + p_preload->resource = ResourceLoader::load(p_preload->resolved_path, res_type, ResourceFormatLoader::CACHE_MODE_REUSE, &err); + if (err == ERR_BUSY) { + p_preload->resource = ResourceLoader::ensure_resource_ref_override_for_outer_load(p_preload->resolved_path, res_type); + } if (p_preload->resource.is_null()) { push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); } @@ -4299,6 +4626,11 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { p_preload->is_constant = true; p_preload->reduced_value = p_preload->resource; p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload)); + + // TODO: Not sure if this is necessary anymore. + // 'type_from_variant()' should call 'resolve_class_inheritance()' which would call 'ensure_cached_external_parser_for_class()' + // Better safe than sorry. + ensure_cached_external_parser_for_class(p_preload->get_datatype().class_type, nullptr, "Trying to resolve preload", p_preload); } void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { @@ -4328,15 +4660,45 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); bool valid = false; + // If the base is a metatype, use the analyzer instead. - if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) { - // Just try to get it. - Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); - if (valid) { - p_subscript->is_constant = true; - p_subscript->reduced_value = value; - result_type = type_from_variant(value, p_subscript); + if (p_subscript->base->is_constant && !base_type.is_meta_type) { + // GH-92534. If the base is a GDScript, use the analyzer instead. + bool base_is_gdscript = false; + if (p_subscript->base->reduced_value.get_type() == Variant::OBJECT) { + Ref<GDScript> gdscript = Object::cast_to<GDScript>(p_subscript->base->reduced_value.get_validated_object()); + if (gdscript.is_valid()) { + base_is_gdscript = true; + // Makes a metatype from a constant GDScript, since `base_type` is not a metatype. + GDScriptParser::DataType base_type_meta = type_from_variant(gdscript, p_subscript); + // First try to reduce the attribute from the metatype. + reduce_identifier_from_base(p_subscript->attribute, &base_type_meta); + GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); + if (attr_type.is_set()) { + valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } + if (!valid) { + // If unsuccessful, reset and return to the normal route. + p_subscript->attribute->set_datatype(GDScriptParser::DataType()); + } + } + } + if (!base_is_gdscript) { + // Just try to get it. + Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); + if (valid) { + p_subscript->is_constant = true; + p_subscript->reduced_value = value; + result_type = type_from_variant(value, p_subscript); + } } + } + + if (valid) { + // Do nothing. } else if (base_type.is_variant() || !base_type.is_hard_type()) { valid = !base_type.is_pseudo_type || p_can_be_pseudo_type; result_type.kind = GDScriptParser::DataType::VARIANT; @@ -4359,10 +4721,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_identifier_from_base(p_subscript->attribute, &base_type); GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); if (attr_type.is_set()) { - valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; - result_type = attr_type; - p_subscript->is_constant = p_subscript->attribute->is_constant; - p_subscript->reduced_value = p_subscript->attribute->reduced_value; + if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) { + Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type; + valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME; + if (base_type.has_container_element_type(1)) { + result_type = base_type.get_container_element_type(1); + result_type.type_source = base_type.type_source; + } else { + result_type.builtin_type = Variant::NIL; + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + } else { + valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } } else if (!base_type.is_meta_type || !base_type.is_constant) { valid = base_type.kind != GDScriptParser::DataType::BUILTIN; #ifdef DEBUG_ENABLED @@ -4374,6 +4749,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri mark_node_unsafe(p_subscript); } } + if (!valid) { GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); if (!p_can_be_pseudo_type && (attr_type.is_pseudo_type || result_type.is_pseudo_type)) { @@ -4392,6 +4768,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri if (p_subscript->base->is_constant && p_subscript->index->is_constant) { // Just try to get it. bool valid = false; + // TODO: Check if `p_subscript->base->reduced_value` is GDScript. Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid); if (!valid) { push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index); @@ -4467,8 +4844,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::SIGNAL: case Variant::STRING_NAME: break; - // Here for completeness. + // Support depends on if the dictionary has a typed key, otherwise anything is valid. case Variant::DICTIONARY: + if (base_type.has_container_element_type(0)) { + GDScriptParser::DataType key_type = base_type.get_container_element_type(0); + switch (index_type.builtin_type) { + // Null value will be treated as an empty object, allow. + case Variant::NIL: + error = key_type.builtin_type != Variant::OBJECT; + break; + // Objects are parsed for validity in a similar manner to container types. + case Variant::OBJECT: + if (key_type.builtin_type == Variant::OBJECT) { + error = !key_type.can_reference(index_type); + } else { + error = key_type.builtin_type != Variant::NIL; + } + break; + // String and StringName interchangeable in this context. + case Variant::STRING: + case Variant::STRING_NAME: + error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING; + break; + // Ints are valid indices for floats, but not the other way around. + case Variant::INT: + error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT; + break; + // All other cases require the types to match exactly. + default: + error = key_type.builtin_type != index_type.builtin_type; + break; + } + } + break; + // Here for completeness. case Variant::VARIANT_MAX: break; } @@ -4557,7 +4966,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::PROJECTION: case Variant::PLANE: case Variant::COLOR: - case Variant::DICTIONARY: case Variant::OBJECT: result_type.kind = GDScriptParser::DataType::VARIANT; result_type.type_source = GDScriptParser::DataType::UNDETECTED; @@ -4572,6 +4980,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type.type_source = GDScriptParser::DataType::UNDETECTED; } break; + // Can have two element types, but we only care about the value. + case Variant::DICTIONARY: + if (base_type.has_container_element_type(1)) { + result_type = base_type.get_container_element_type(1); + result_type.type_source = base_type.type_source; + } else { + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + break; // Here for completeness. case Variant::VARIANT_MAX: break; @@ -4751,7 +5169,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_ } Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) { - Dictionary dictionary; + Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types() + ? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1)) + : Dictionary(); for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -4822,7 +5242,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D Ref<Script> script_type = p_element_datatype.script_type; if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err, parser->script_path); + Ref<GDScript> scr = get_depended_shallow_script(p_element_datatype.script_path, err); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); return array; @@ -4838,6 +5258,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D return array; } +Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) { + Dictionary dictionary; + StringName key_name; + Variant key_script; + StringName value_name; + Variant value_script; + + if (p_key_element_datatype.builtin_type == Variant::OBJECT) { + Ref<Script> script_type = p_key_element_datatype.script_type; + if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node); + return dictionary; + } + script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn)); + } + + key_name = p_key_element_datatype.native_type; + key_script = script_type; + } + + if (p_value_element_datatype.builtin_type == Variant::OBJECT) { + Ref<Script> script_type = p_value_element_datatype.script_type; + if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node); + return dictionary; + } + script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn)); + } + + value_name = p_value_element_datatype.native_type; + value_script = script_type; + } + + dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script); + return dictionary; +} + Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) { Variant result = Variant(); @@ -4853,6 +5316,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) { result = make_array_from_element_datatype(datatype.get_container_element_type(0)); + } else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) { + GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0); + GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1); + result = make_dictionary_from_element_datatype(key, value); } else { VariantInternal::initialize(&result, datatype.builtin_type); } @@ -4881,6 +5348,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va } else if (array.get_typed_builtin() != Variant::NIL) { result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin()))); } + } else if (p_value.get_type() == Variant::DICTIONARY) { + const Dictionary &dict = p_value; + if (dict.get_typed_key_script()) { + result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script()))); + } else if (dict.get_typed_key_class_name()) { + result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name()))); + } else if (dict.get_typed_key_builtin() != Variant::NIL) { + result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin()))); + } + if (dict.get_typed_value_script()) { + result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script()))); + } else if (dict.get_typed_value_class_name()) { + result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name()))); + } else if (dict.get_typed_value_builtin() != Variant::NIL) { + result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin()))); + } } else if (p_value.get_type() == Variant::OBJECT) { // Object is treated as a native type, not a builtin type. result.kind = GDScriptParser::DataType::NATIVE; @@ -5013,6 +5496,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } elem_type.is_constant = false; result.set_container_element_type(0, elem_type); + } else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + // Check element type. + StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0); + GDScriptParser::DataType key_elem_type; + key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name); + if (key_elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + key_elem_type.kind = GDScriptParser::DataType::BUILTIN; + key_elem_type.builtin_type = key_elem_builtin_type; + } else if (class_exists(key_elem_type_name)) { + key_elem_type.kind = GDScriptParser::DataType::NATIVE; + key_elem_type.builtin_type = Variant::OBJECT; + key_elem_type.native_type = key_elem_type_name; + } else if (ScriptServer::is_global_class(key_elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name)); + key_elem_type.kind = GDScriptParser::DataType::SCRIPT; + key_elem_type.builtin_type = Variant::OBJECT; + key_elem_type.native_type = script->get_instance_base_type(); + key_elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary."); + } + key_elem_type.is_constant = false; + + StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1); + GDScriptParser::DataType value_elem_type; + value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name); + if (value_elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + value_elem_type.kind = GDScriptParser::DataType::BUILTIN; + value_elem_type.builtin_type = value_elem_builtin_type; + } else if (class_exists(value_elem_type_name)) { + value_elem_type.kind = GDScriptParser::DataType::NATIVE; + value_elem_type.builtin_type = Variant::OBJECT; + value_elem_type.native_type = value_elem_type_name; + } else if (ScriptServer::is_global_class(value_elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name)); + value_elem_type.kind = GDScriptParser::DataType::SCRIPT; + value_elem_type.builtin_type = Variant::OBJECT; + value_elem_type.native_type = script->get_instance_base_type(); + value_elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary."); + } + value_elem_type.is_constant = false; + + result.set_container_element_type(0, key_elem_type); + result.set_container_element_type(1, value_elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) { @@ -5086,7 +5623,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo if (!class_exists(base_native)) { push_error(vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native), p_source); return false; - } else if (p_is_constructor && !ClassDB::can_instantiate(base_native)) { + } else if (p_is_constructor && ClassDB::is_abstract(base_native)) { if (p_base_type.kind == GDScriptParser::DataType::CLASS) { push_error(vformat(R"(Class "%s" cannot be constructed as it is based on abstract native class "%s".)", p_base_type.class_type->fqcn.get_file(), base_native), p_source); } else if (p_base_type.kind == GDScriptParser::DataType::SCRIPT) { @@ -5433,6 +5970,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType & valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0); } } + if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) { + // Check the element types. + if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) { + valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0); + } + if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) { + valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1); + } + } return valid; } @@ -5476,6 +6022,9 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType & // A script type cannot be a subtype of a GDScript class. return false; } + if (p_source.script_type.is_null()) { + return false; + } if (p_source.is_meta_type) { src_native = p_source.script_type->get_class_name(); } else { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 922000df52..3b781409a4 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -41,9 +41,22 @@ class GDScriptAnalyzer { GDScriptParser *parser = nullptr; + template <typename Fn> + class Finally { + Fn fn; + + public: + Finally(Fn p_fn) : + fn(p_fn) {} + ~Finally() { + fn(); + } + }; + const GDScriptParser::EnumNode *current_enum = nullptr; GDScriptParser::LambdaNode *current_lambda = nullptr; List<GDScriptParser::LambdaNode *> pending_body_resolution_lambdas; + HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>> external_class_parser_cache; bool static_context = false; // Tests for detecting invalid overloading of script members @@ -52,7 +65,7 @@ class GDScriptAnalyzer { Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); - void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list); + void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list, GDScriptParser::Node *p_source); Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive); @@ -112,6 +125,7 @@ class GDScriptAnalyzer { // Helpers. Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); + Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; @@ -124,6 +138,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false); void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type); + void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); void mark_node_unsafe(const GDScriptParser::Node *p_node); @@ -132,6 +147,10 @@ class GDScriptAnalyzer { void resolve_pending_lambda_bodies(); bool class_exists(const StringName &p_class) const; void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); + Ref<GDScriptParserRef> ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source); + Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser); + Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser); + Ref<GDScript> get_depended_shallow_script(const String &p_path, Error &r_error); #ifdef DEBUG_ENABLED void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope); #endif diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 4cda3d3037..b77c641eb5 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -634,6 +634,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) { + const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_element_type.builtin_type); + append(key_element_type.native_type); + append(value_element_type.builtin_type); + append(value_element_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN); append(p_target); @@ -889,6 +901,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) { + const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); append(p_target); @@ -935,6 +959,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) { + const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { // Need conversion. append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); @@ -1434,6 +1470,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ ct.cleanup(); } +void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) { + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + CallTarget ct = get_call_target(p_target); + append(ct.target); + append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. + append(p_key_type.builtin_type); + append(p_key_type.native_type); + append(p_value_type.builtin_type); + append(p_value_type.native_type); + ct.cleanup(); +} + void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { append_opcode(GDScriptFunction::OPCODE_AWAIT); append(p_operand); @@ -1711,6 +1764,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY && + function->return_type.has_container_element_types()) { + // Typed dictionary. + const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY); + append(p_return_value); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { // Add conversion. append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); @@ -1735,6 +1801,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) { + const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY); + append(p_return_value); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); append(p_return_value); @@ -1803,6 +1880,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) { case Variant::BOOL: write_assign_false(p_address); break; + case Variant::DICTIONARY: + if (p_address.type.has_container_element_types()) { + write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); + } else { + write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + break; case Variant::ARRAY: if (p_address.type.has_container_element_type(0)) { write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 34f56a2f5c..6303db71fd 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -529,6 +529,7 @@ public: virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override; virtual void write_await(const Address &p_target, const Address &p_operand) override; virtual void write_if(const Address &p_condition) override; virtual void write_else() override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index ac6f5f05c6..b3c0744bdf 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -42,6 +42,10 @@ GDScriptParserRef::Status GDScriptParserRef::get_status() const { return status; } +String GDScriptParserRef::get_path() const { + return path; +} + uint32_t GDScriptParserRef::get_source_hash() const { return source_hash; } @@ -91,12 +95,8 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { result = get_analyzer()->resolve_interface(); } break; case INTERFACE_SOLVED: { - status = BODY_SOLVED; - result = get_analyzer()->resolve_body(); - } break; - case BODY_SOLVED: { status = FULLY_SOLVED; - result = get_analyzer()->resolve_dependencies(); + result = get_analyzer()->resolve_body(); } break; case FULLY_SOLVED: { return result; @@ -136,12 +136,22 @@ void GDScriptParserRef::clear() { GDScriptParserRef::~GDScriptParserRef() { clear(); - MutexLock lock(GDScriptCache::singleton->mutex); - GDScriptCache::singleton->parser_map.erase(path); + if (!abandoned) { + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->parser_map.erase(path); + } } GDScriptCache *GDScriptCache::singleton = nullptr; +SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG> &_get_gdscript_cache_mutex() { + return GDScriptCache::mutex; +} + +template <> +thread_local SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG>::TLSData SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG>::tls_data(_get_gdscript_cache_mutex()); +SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG> GDScriptCache::mutex; + void GDScriptCache::move_script(const String &p_from, const String &p_to) { if (singleton == nullptr || p_from == p_to) { return; @@ -153,10 +163,7 @@ void GDScriptCache::move_script(const String &p_from, const String &p_to) { return; } - if (singleton->parser_map.has(p_from) && !p_from.is_empty()) { - singleton->parser_map[p_to] = singleton->parser_map[p_from]; - } - singleton->parser_map.erase(p_from); + remove_parser(p_from); if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) { singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from]; @@ -180,14 +187,23 @@ void GDScriptCache::remove_script(const String &p_path) { return; } + if (HashMap<String, Vector<ObjectID>>::Iterator E = singleton->abandoned_parser_map.find(p_path)) { + for (ObjectID parser_ref_id : E->value) { + Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) }; + if (parser_ref.is_valid()) { + parser_ref->clear(); + } + } + } + + singleton->abandoned_parser_map.erase(p_path); + if (singleton->parser_map.has(p_path)) { - // Keep a local reference until it goes out of scope. - // Clearing it can trigger a reference to itself to go out of scope, destructing it before clear finishes. - Ref<GDScriptParserRef> parser_ref = singleton->parser_map[p_path]; - singleton->parser_map.erase(p_path); - parser_ref->clear(); + singleton->parser_map[p_path]->clear(); } + remove_parser(p_path); + singleton->dependencies.erase(p_path); singleton->shallow_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path); @@ -198,6 +214,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP Ref<GDScriptParserRef> ref; if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); + singleton->parser_inverse_dependencies[p_path].insert(p_owner); } if (singleton->parser_map.has(p_path)) { ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]); @@ -227,8 +244,22 @@ bool GDScriptCache::has_parser(const String &p_path) { void GDScriptCache::remove_parser(const String &p_path) { MutexLock lock(singleton->mutex); + + if (singleton->parser_map.has(p_path)) { + GDScriptParserRef *parser_ref = singleton->parser_map[p_path]; + parser_ref->abandoned = true; + singleton->abandoned_parser_map[p_path].push_back(parser_ref->get_instance_id()); + } + // Can't clear the parser because some other parser might be currently using it in the chain of calls. singleton->parser_map.erase(p_path); + + // Have to copy while iterating, because parser_inverse_dependencies is modified. + HashSet<String> ideps = singleton->parser_inverse_dependencies[p_path]; + singleton->parser_inverse_dependencies.erase(p_path); + for (String idep_path : ideps) { + remove_parser(idep_path); + } } String GDScriptCache::get_source_code(const String &p_path) { @@ -344,7 +375,11 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro } } + // Allowing lifting the lock might cause a script to be reloaded multiple times, + // which, as a last resort deadlock prevention strategy, is a good tradeoff. + uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(singleton->mutex); r_error = script->reload(true); + WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id); if (r_error) { return script; } @@ -417,6 +452,19 @@ void GDScriptCache::clear() { } singleton->cleared = true; + singleton->parser_inverse_dependencies.clear(); + + for (const KeyValue<String, Vector<ObjectID>> &KV : singleton->abandoned_parser_map) { + for (ObjectID parser_ref_id : KV.value) { + Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) }; + if (parser_ref.is_valid()) { + parser_ref->clear(); + } + } + } + + singleton->abandoned_parser_map.clear(); + RBSet<Ref<GDScriptParserRef>> parser_map_refs; for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { parser_map_refs.insert(E.value); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index c738233beb..4903da92b4 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -34,7 +34,7 @@ #include "gdscript.h" #include "core/object/ref_counted.h" -#include "core/os/mutex.h" +#include "core/os/safe_binary_mutex.h" #include "core/templates/hash_map.h" #include "core/templates/hash_set.h" @@ -48,7 +48,6 @@ public: PARSED, INHERITANCE_SOLVED, INTERFACE_SOLVED, - BODY_SOLVED, FULLY_SOLVED, }; @@ -60,12 +59,14 @@ private: String path; uint32_t source_hash = 0; bool clearing = false; + bool abandoned = false; friend class GDScriptCache; friend class GDScript; public: Status get_status() const; + String get_path() const; uint32_t get_source_hash() const; GDScriptParser *get_parser(); GDScriptAnalyzer *get_analyzer(); @@ -79,10 +80,12 @@ public: class GDScriptCache { // String key is full path. HashMap<String, GDScriptParserRef *> parser_map; + HashMap<String, Vector<ObjectID>> abandoned_parser_map; HashMap<String, Ref<GDScript>> shallow_gdscript_cache; HashMap<String, Ref<GDScript>> full_gdscript_cache; HashMap<String, Ref<GDScript>> static_gdscript_cache; HashMap<String, HashSet<String>> dependencies; + HashMap<String, HashSet<String>> parser_inverse_dependencies; friend class GDScript; friend class GDScriptParserRef; @@ -92,7 +95,12 @@ class GDScriptCache { bool cleared = false; - Mutex mutex; +public: + static const int BINARY_MUTEX_TAG = 2; + +private: + static SafeBinaryMutex<BINARY_MUTEX_TAG> mutex; + friend SafeBinaryMutex<BINARY_MUTEX_TAG> &_get_gdscript_cache_mutex(); public: static void move_script(const String &p_from, const String &p_to); diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index c1c0b61395..f3c4acf1c3 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -142,6 +142,7 @@ public: virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0; virtual void write_await(const Address &p_target, const Address &p_operand) = 0; virtual void write_if(const Address &p_condition) = 0; virtual void write_else() = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index e62972b949..eebf282633 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> elements; // Create the result temporary first since it's the last to be killed. - GDScriptDataType dict_type; - dict_type.has_type = true; - dict_type.kind = GDScriptDataType::BUILTIN; - dict_type.builtin_type = Variant::DICTIONARY; + GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type); for (int i = 0; i < dn->elements.size(); i++) { @@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code elements.push_back(element); } - gen->write_construct_dictionary(result, elements); + if (dict_type.has_container_element_types()) { + gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements); + } else { + gen->write_construct_dictionary(result, elements); + } for (int i = 0; i < elements.size(); i++) { if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -1064,12 +1065,24 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Get at (potential) root stack pos, so it can be returned. GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base); + const bool base_known_type = base.type.has_type; + const bool base_is_shared = Variant::is_type_shared(base.type.builtin_type); + if (r_error) { return GDScriptCodeGenerator::Address(); } GDScriptCodeGenerator::Address prev_base = base; + // In case the base has a setter, don't use the address directly, as we want to call that setter. + // So use a temp value instead and call the setter at the end. + GDScriptCodeGenerator::Address base_temp; + if ((!base_known_type || !base_is_shared) && base.mode == GDScriptCodeGenerator::Address::MEMBER && member_property_has_setter && !member_property_is_in_setter) { + base_temp = codegen.add_temporary(base.type); + gen->write_assign(base_temp, base); + prev_base = base_temp; + } + struct ChainInfo { bool is_named = false; GDScriptCodeGenerator::Address base; @@ -1218,6 +1231,15 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_end_jump_if_shared(); } } + } else if (base_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + if (!base_known_type) { + gen->write_jump_if_shared(base); + } + // Save the temp value back to the base by calling its setter. + gen->write_call(GDScriptCodeGenerator::Address(), base, member_property_setter_function, { assigned }); + if (!base_known_type) { + gen->write_end_jump_if_shared(); + } } if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -1880,7 +1902,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s); - codegen.start_block(); // Add an extra block, since the binding pattern and @special variables belong to the branch scope. + codegen.start_block(); // Add an extra block, since @special locals belong to the match scope. // Evaluate the match expression. GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script)); @@ -1918,7 +1940,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - codegen.start_block(); // Create an extra block around for binds. + codegen.start_block(); // Add an extra block, since binds belong to the match branch scope. // Add locals in block before patterns, so temporaries don't use the stack address for binds. List<GDScriptCodeGenerator::Address> branch_locals = _add_block_locals(codegen, branch->block); @@ -1970,13 +1992,15 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _clear_block_locals(codegen, branch_locals); - codegen.end_block(); // Get out of extra block. + codegen.end_block(); // Get out of extra block for binds. } // End all nested `if`s. for (int j = 0; j < match->branches.size(); j++) { gen->write_endif(); } + + codegen.end_block(); // Get out of extra block for match's @special locals. } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s); @@ -2010,7 +2034,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); - codegen.start_block(); // Add an extra block, since the iterator and @special variables belong to the loop scope. + // Add an extra block, since the iterator and @special locals belong to the loop scope. + // Also we use custom logic to clear block locals. + codegen.start_block(); GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script)); @@ -2043,11 +2069,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit. - codegen.end_block(); // Get out of extra block. + codegen.end_block(); // Get out of extra block for loop iterator, @special locals, and custom locals clearing. } break; case GDScriptParser::Node::WHILE: { const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s); + codegen.start_block(); // Add an extra block, since we use custom logic to clear block locals. + gen->start_while_condition(); GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, while_n->condition); @@ -2074,6 +2102,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui gen->write_endwhile(); _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit. + + codegen.end_block(); // Get out of extra block for custom locals clearing. } break; case GDScriptParser::Node::BREAK: { gen->write_break(); @@ -2296,8 +2326,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); - if (field_type.has_container_element_type(0)) { + if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) { codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) { + codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0), + field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); } else if (field_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } @@ -2486,11 +2519,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS if (field_type.has_type) { codegen.generator->write_newline(field->start_line); - if (field_type.has_container_element_type(0)) { + if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); codegen.generator->pop_temporary(); + } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) { + GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); + codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0), + field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); + codegen.generator->pop_temporary(); } else if (field_type.kind == GDScriptDataType::BUILTIN) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); @@ -3006,6 +3045,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: has_static_data = has_static_data || inner_class->has_static_data; } + p_script->_static_default_init(); + p_script->valid = true; return OK; } diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 0331045078..bc063693a3 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 6; } break; + case OPCODE_TYPE_TEST_DICTIONARY: { + text += "type test "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + text += " is Dictionary["; + + Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + StringName key_native_type = get_global_name(_code_ptr[ip + 6]); + + if (key_script_type.is_valid() && key_script_type->is_valid()) { + text += "script("; + text += GDScript::debug_get_script_name(key_script_type); + text += ")"; + } else if (key_native_type != StringName()) { + text += key_native_type; + } else { + text += Variant::get_type_name(key_builtin_type); + } + + text += ", "; + + Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + StringName value_native_type = get_global_name(_code_ptr[ip + 8]); + + if (value_script_type.is_valid() && value_script_type->is_valid()) { + text += "script("; + text += GDScript::debug_get_script_name(value_script_type); + text += ")"; + } else if (value_native_type != StringName()) { + text += value_native_type; + } else { + text += Variant::get_type_name(value_builtin_type); + } + + text += "]"; + + incr += 9; + } break; case OPCODE_TYPE_TEST_NATIVE: { text += "type test "; text += DADDR(1); @@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 6; } break; + case OPCODE_ASSIGN_TYPED_DICTIONARY: { + text += "assign typed dictionary "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 9; + } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; text += DADDR(3); @@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 3 + argc * 2; } break; + case OPCODE_CONSTRUCT_TYPED_DICTIONARY: { + int instr_var_args = _code_ptr[++ip]; + int argc = _code_ptr[ip + 1 + instr_var_args]; + + Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5]; + StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]); + + String key_type_name; + if (key_script_type.is_valid() && key_script_type->is_valid()) { + key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")"; + } else if (key_native_type != StringName()) { + key_type_name = key_native_type; + } else { + key_type_name = Variant::get_type_name(key_builtin_type); + } + + Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7]; + StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]); + + String value_type_name; + if (value_script_type.is_valid() && value_script_type->is_valid()) { + value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")"; + } else if (value_native_type != StringName()) { + value_type_name = value_native_type; + } else { + value_type_name = Variant::get_type_name(value_builtin_type); + } + + text += "make_typed_dict ("; + text += key_type_name; + text += ", "; + text += value_type_name; + text += ") "; + + text += DADDR(1 + argc * 2); + text += " = {"; + + for (int i = 0; i < argc; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i * 2 + 0); + text += ": "; + text += DADDR(1 + i * 2 + 1); + } + + text += "}"; + + incr += 9 + argc * 2; + } break; case OPCODE_CALL: case OPCODE_CALL_RETURN: case OPCODE_CALL_ASYNC: { @@ -978,6 +1079,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; + case OPCODE_RETURN_TYPED_DICTIONARY: { + text += "return typed dictionary "; + text += DADDR(1); + + incr += 8; + } break; case OPCODE_RETURN_TYPED_NATIVE: { text += "return typed native ("; text += DADDR(2); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index cae85d7c70..73f2b1d618 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -97,8 +97,8 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri } processed_template = processed_template.replace("_BASE_", p_base_class_name) - .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_identifier()) - .replace("_CLASS_", p_class_name.to_pascal_case().validate_identifier()) + .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_unicode_identifier()) + .replace("_CLASS_", p_class_name.to_pascal_case().validate_unicode_identifier()) .replace("_TS_", _get_indentation()); scr->set_source_code(processed_template); return scr; @@ -402,7 +402,9 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> } const Variant &var = gl_array[E.value]; - if (Object *obj = var) { + bool freed = false; + const Object *obj = var.get_validated_object_with_check(freed); + if (obj && !freed) { if (Object::cast_to<GDScriptNativeClass>(obj)) { continue; } @@ -695,6 +697,10 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, co return _trim_parent_class(class_name, p_base_class); } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]"; + } else if (p_info.type == Variant::DICTIONARY && p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE && !p_info.hint_string.is_empty()) { + const String key = p_info.hint_string.get_slice(";", 0); + const String value = p_info.hint_string.get_slice(";", 1); + return "Dictionary[" + _trim_parent_class(key, p_base_class) + ", " + _trim_parent_class(value, p_base_class) + "]"; } else if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; @@ -754,13 +760,17 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool return arghint; } -static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { +static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx, bool p_just_args = false) { String arghint; - if (p_function->get_datatype().builtin_type == Variant::NIL) { - arghint = "void " + p_function->identifier->name.operator String() + "("; + if (p_just_args) { + arghint = "("; } else { - arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "("; + if (p_function->get_datatype().builtin_type == Variant::NIL) { + arghint = "void " + p_function->identifier->name.operator String() + "("; + } else { + arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "("; + } } for (int i = 0; i < p_function->parameters.size(); i++) { @@ -792,7 +802,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer); if (call->is_constant && call->reduced) { - def_val = call->function_name.operator String() + call->reduced_value.operator String(); + def_val = call->reduced_value.get_construct_string(); } else { def_val = call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)"); } @@ -800,7 +810,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer); if (arr->is_constant && arr->reduced) { - def_val = arr->reduced_value.operator String(); + def_val = arr->reduced_value.get_construct_string(); } else { def_val = arr->elements.is_empty() ? "[]" : "[...]"; } @@ -808,24 +818,17 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer); if (dict->is_constant && dict->reduced) { - def_val = dict->reduced_value.operator String(); + def_val = dict->reduced_value.get_construct_string(); } else { def_val = dict->elements.is_empty() ? "{}" : "{...}"; } } break; case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer); - if (sub->is_constant) { - if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { - def_val = sub->get_datatype().to_string(); - } else if (sub->reduced) { - const Variant::Type vt = sub->reduced_value.get_type(); - if (vt == Variant::Type::NIL || vt == Variant::Type::FLOAT || vt == Variant::Type::INT || vt == Variant::Type::STRING || vt == Variant::Type::STRING_NAME || vt == Variant::Type::BOOL || vt == Variant::Type::NODE_PATH) { - def_val = sub->reduced_value.operator String(); - } else { - def_val = sub->get_datatype().to_string() + sub->reduced_value.operator String(); - } - } + if (sub->is_attribute && sub->datatype.kind == GDScriptParser::DataType::ENUM && !sub->datatype.is_meta_type) { + def_val = sub->get_datatype().to_string() + "." + sub->attribute->name; + } else if (sub->is_constant && sub->reduced) { + def_val = sub->reduced_value.get_construct_string(); } } break; default: @@ -911,6 +914,29 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a option.insert_text = option.display.quote(p_quote_style); r_result.insert(option.display, option); } + } else if (p_annotation->name == SNAME("@export_custom")) { + switch (p_argument) { + case 0: { + static HashMap<StringName, int64_t> items; + if (unlikely(items.is_empty())) { + CoreConstants::get_enum_values(SNAME("PropertyHint"), &items); + } + for (const KeyValue<StringName, int64_t> &item : items) { + ScriptLanguage::CodeCompletionOption option(item.key, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); + r_result.insert(option.display, option); + } + } break; + case 2: { + static HashMap<StringName, int64_t> items; + if (unlikely(items.is_empty())) { + CoreConstants::get_enum_values(SNAME("PropertyUsageFlags"), &items); + } + for (const KeyValue<StringName, int64_t> &item : items) { + ScriptLanguage::CodeCompletionOption option(item.key, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); + r_result.insert(option.display, option); + } + } break; + } } else if (p_annotation->name == SNAME("@warning_ignore")) { for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); @@ -1494,22 +1520,19 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, G } if (scr.is_valid()) { ci.type.script_path = scr->get_path(); + ci.type.script_type = scr; + ci.type.native_type = scr->get_instance_base_type(); + ci.type.kind = GDScriptParser::DataType::SCRIPT; if (scr->get_path().ends_with(".gd")) { - Error err; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(scr->get_path()); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.class_type = parser->get_parser()->get_tree(); ci.type.kind = GDScriptParser::DataType::CLASS; - p_context.dependent_parsers.push_back(parser); return ci; } } - - ci.type.kind = GDScriptParser::DataType::SCRIPT; - ci.type.script_type = scr; - ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; } @@ -1784,8 +1807,6 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (mb && mb->is_const()) { bool all_is_const = true; Vector<Variant> args; - GDScriptParser::CompletionContext c2 = p_context; - c2.current_line = call->start_line; for (int i = 0; all_is_const && i < call->arguments.size(); i++) { GDScriptCompletionIdentifier arg; @@ -1822,16 +1843,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } if (FileAccess::exists(script)) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); found = true; } } @@ -1932,11 +1951,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, break; } - if (base.value.in(index.value)) { - Variant value = base.value.get(index.value); - r_type = _type_from_variant(value, p_context); - found = true; - break; + { + bool valid; + Variant value = base.value.get(index.value, &valid); + if (valid) { + r_type = _type_from_variant(value, p_context); + found = true; + break; + } } // Look if it is a dictionary node. @@ -1979,7 +2001,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } // Look for valid indexing in other types - if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) { + if (!found && (index.value.is_string() || index.value.get_type() == Variant::NODE_PATH)) { StringName id = index.value; found = _guess_identifier_type_from_base(c, base, id, r_type); } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) { @@ -2065,6 +2087,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, found = false; } + // If the found type was not fully analyzed we analyze it now. + if (found && r_type.type.kind == GDScriptParser::DataType::CLASS && !r_type.type.class_type->resolved_body) { + Error err; + Ref<GDScriptParserRef> r = GDScriptCache::get_parser(r_type.type.script_path, GDScriptParserRef::FULLY_SOLVED, err); + } + // Check type hint last. For collections we want chance to get the actual value first // This way we can detect types from the content of dictionaries and arrays if (!found && p_expression->get_datatype().is_hard_type()) { @@ -2088,7 +2116,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, // Look in blocks first. int last_assign_line = -1; const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; - GDScriptParser::DataType id_type; + GDScriptCompletionIdentifier id_type; GDScriptParser::SuiteNode *suite = p_context.current_suite; bool is_function_parameter = false; @@ -2110,7 +2138,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (can_be_local && suite && suite->has_local(p_identifier->name)) { const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier->name); - id_type = local.get_datatype(); + id_type.type = local.get_datatype(); // Check initializer as the first assignment. switch (local.type) { @@ -2148,7 +2176,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) { - id_type = base_identifier.type; + id_type = base_identifier; } } } @@ -2188,7 +2216,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, c.current_line = type_test->operand->start_line; c.current_suite = suite; if (type_test->test_datatype.is_hard_type()) { - id_type = type_test->test_datatype; + id_type.type = type_test->test_datatype; if (last_assign_line < c.current_line) { // Override last assignment. last_assign_line = c.current_line; @@ -2206,10 +2234,10 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, c.current_line = last_assign_line; GDScriptCompletionIdentifier assigned_type; if (_guess_expression_type(c, last_assigned_expression, assigned_type)) { - if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) { + if (id_type.type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type.type, assigned_type.type)) { // The assigned type is incompatible. The annotated type takes priority. + r_type = id_type; r_type.assigned_expression = last_assigned_expression; - r_type.type = id_type; } else { r_type = assigned_type; } @@ -2227,8 +2255,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; if (parent_function->parameters_indices.has(p_identifier->name)) { const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier->name]]; - if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { - id_type = parameter->get_datatype(); + if ((!id_type.type.is_set() || id_type.type.is_variant()) && parameter->get_datatype().is_hard_type()) { + id_type.type = parameter->get_datatype(); } if (parameter->initializer) { GDScriptParser::CompletionContext c = p_context; @@ -2244,7 +2272,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::NATIVE: { - if (id_type.is_set() && !id_type.is_variant()) { + if (id_type.type.is_set() && !id_type.type.is_variant()) { base_type = GDScriptParser::DataType(); break; } @@ -2265,8 +2293,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (id_type.is_set() && !id_type.is_variant()) { - r_type.type = id_type; + if (id_type.type.is_set() && !id_type.type.is_variant()) { + r_type = id_type; return true; } @@ -2274,9 +2302,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (ScriptServer::is_global_class(p_identifier->name)) { String script = ScriptServer::get_global_class_path(p_identifier->name); if (script.to_lower().ends_with(".gd")) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); @@ -2284,7 +2311,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); return true; } } else { @@ -2702,6 +2728,25 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c while (base_type.is_set() && !base_type.is_variant()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { + if (base_type.is_meta_type && p_method == SNAME("new")) { + const GDScriptParser::ClassNode *current = base_type.class_type; + + do { + if (current->has_member("_init")) { + const GDScriptParser::ClassNode::Member &member = current->get_member("_init"); + + if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { + r_arghint = base_type.class_type->get_datatype().to_string() + " new" + _make_arguments_hint(member.function, p_argidx, true); + return; + } + } + current = current->base_type.class_type; + } while (current != nullptr); + + r_arghint = base_type.class_type->get_datatype().to_string() + " new()"; + return; + } + if (base_type.class_type->has_member(p_method)) { const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method); @@ -2713,6 +2758,20 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; + case GDScriptParser::DataType::SCRIPT: { + if (base_type.script_type->is_valid() && base_type.script_type->has_method(p_method)) { + r_arghint = _make_arguments_hint(base_type.script_type->get_method_info(p_method), p_argidx); + return; + } + Ref<Script> base_script = base_type.script_type->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; + base_type.native_type = base_type.script_type->get_instance_base_type(); + } + } break; case GDScriptParser::DataType::NATIVE: { StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { @@ -2735,9 +2794,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (opt.is_quoted()) { opt = opt.unquote().quote(quote_style); if (use_string_names && info.arguments.get(p_argidx).type == Variant::STRING_NAME) { - opt = opt.indent("&"); + opt = "&" + opt; } else if (use_node_paths && info.arguments.get(p_argidx).type == Variant::NODE_PATH) { - opt = opt.indent("^"); + opt = "^" + opt; } } ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); @@ -2772,7 +2831,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (E.usage & (PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_INTERNAL)) { continue; } - ScriptLanguage::CodeCompletionOption option(E.name.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n); + String name = E.name.quote(quote_style); + if (use_node_paths) { + name = "^" + name; + } + ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n); r_result.insert(option.display, option); } script = script->get_base_script(); @@ -2786,7 +2849,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c while (clss) { for (GDScriptParser::ClassNode::Member member : clss->members) { if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { - ScriptLanguage::CodeCompletionOption option(member.get_name().quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n); + String name = member.get_name().quote(quote_style); + if (use_node_paths) { + name = "^" + name; + } + ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n); r_result.insert(option.display, option); } } @@ -2809,7 +2876,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (E.usage & (PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_INTERNAL)) { continue; } - ScriptLanguage::CodeCompletionOption option(E.name.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER); + String name = E.name.quote(quote_style); + if (use_node_paths) { + name = "^" + name; + } + ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER); r_result.insert(option.display, option); } } @@ -2825,8 +2896,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c continue; } String name = s.get_slice("/", 1); - ScriptLanguage::CodeCompletionOption option("/root/" + name, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); - option.insert_text = option.display.quote(quote_style); + String path = ("/root/" + name).quote(quote_style); + if (use_node_paths) { + path = "^" + path; + } + ScriptLanguage::CodeCompletionOption option(path, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); r_result.insert(option.display, option); } } @@ -2840,9 +2914,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (!s.begins_with("input/")) { continue; } - String name = s.get_slice("/", 1); + String name = s.get_slice("/", 1).quote(quote_style); + if (use_string_names) { + name = "&" + name; + } ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); - option.insert_text = option.display.quote(quote_style); r_result.insert(option.display, option); } } @@ -3231,11 +3307,36 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c case GDScriptParser::COMPLETION_SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); GDScriptCompletionIdentifier base; - if (!_guess_expression_type(completion_context, subscript->base, base)) { - break; - } + const bool res = _guess_expression_type(completion_context, subscript->base, base); - _find_identifiers_in_base(base, false, false, options, 0); + // If the type is not known, we assume it is BUILTIN, since indices on arrays is the most common use case. + if (!subscript->is_attribute && (!res || base.type.kind == GDScriptParser::DataType::BUILTIN || base.type.is_variant())) { + if (base.value.get_type() == Variant::DICTIONARY) { + List<PropertyInfo> members; + base.value.get_property_list(&members); + + for (const PropertyInfo &E : members) { + ScriptLanguage::CodeCompletionOption option(E.name.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::LOCATION_LOCAL); + options.insert(option.display, option); + } + } + if (!subscript->index || subscript->index->type != GDScriptParser::Node::LITERAL) { + _find_identifiers(completion_context, false, options, 0); + } + } else if (res) { + if (!subscript->is_attribute) { + // Quote the options if they are not accessed as attribute. + + HashMap<String, ScriptLanguage::CodeCompletionOption> opt; + _find_identifiers_in_base(base, false, false, opt, 0); + for (const KeyValue<String, CodeCompletionOption> &E : opt) { + ScriptLanguage::CodeCompletionOption option(E.value.insert_text.quote(quote_style), E.value.kind, E.value.location); + options.insert(option.display, option); + } + } else { + _find_identifiers_in_base(base, false, false, options, 0); + } + } } break; case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { if (!completion_context.current_class) { @@ -3385,11 +3486,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c opt = opt.substr(1); } - // The path needs quotes if at least one of its components (excluding `/` separations) + // The path needs quotes if at least one of its components (excluding `%` prefix and `/` separations) // is not a valid identifier. bool path_needs_quote = false; - for (const String &part : opt.split("/")) { - if (!part.is_valid_identifier()) { + for (const String &part : opt.trim_prefix("%").split("/")) { + if (!part.is_valid_ascii_identifier()) { path_needs_quote = true; break; } @@ -3519,9 +3620,13 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { if (base_type.class_type) { - if (base_type.class_type->has_member(p_symbol)) { + String name = p_symbol; + if (name == "new") { + name = "_init"; + } + if (base_type.class_type->has_member(name)) { r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; - r_result.location = base_type.class_type->get_member(p_symbol).get_line(); + r_result.location = base_type.class_type->get_member(name).get_line(); r_result.class_path = base_type.script_path; Error err = OK; r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); @@ -3584,11 +3689,21 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return OK; } - StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true); - if (enum_name != StringName()) { - r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM; + List<StringName> enums; + ClassDB::get_enum_list(class_name, &enums); + for (const StringName &E : enums) { + if (E == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + } + + if (!String(ClassDB::get_integer_constant_enum(class_name, p_symbol, true)).is_empty()) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = base_type.native_type; - r_result.class_member = enum_name; + r_result.class_member = p_symbol; return OK; } @@ -3662,6 +3777,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return OK; } } break; + case GDScriptParser::DataType::ENUM: { + 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; + return OK; + } + base_type.kind = GDScriptParser::DataType::UNRESOLVED; + } break; default: { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 759e92d68c..6433072b55 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -93,6 +93,41 @@ public: } else { valid = false; } + } else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) { + Dictionary dictionary = p_variant; + if (dictionary.is_typed()) { + if (dictionary.is_typed_key()) { + GDScriptDataType key = get_container_element_type_or_variant(0); + Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin(); + StringName key_native_type = dictionary.get_typed_key_class_name(); + Ref<Script> key_script_type_ref = dictionary.get_typed_key_script(); + + if (key_script_type_ref.is_valid()) { + valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr(); + } else if (key_native_type != StringName()) { + valid = key.kind == NATIVE && key.native_type == key_native_type; + } else { + valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type; + } + } + + if (valid && dictionary.is_typed_value()) { + GDScriptDataType value = get_container_element_type_or_variant(1); + Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin(); + StringName value_native_type = dictionary.get_typed_value_class_name(); + Ref<Script> value_script_type_ref = dictionary.get_typed_value_script(); + + if (value_script_type_ref.is_valid()) { + valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr(); + } else if (value_native_type != StringName()) { + valid = value.kind == NATIVE && value.native_type == value_native_type; + } else { + valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type; + } + } + } else { + valid = false; + } } else if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(var_type, builtin_type); } @@ -156,6 +191,10 @@ public: } return true; case Variant::DICTIONARY: + if (has_container_element_types()) { + return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object(); + } + return true; case Variant::NIL: case Variant::OBJECT: return true; @@ -220,6 +259,7 @@ public: OPCODE_OPERATOR_VALIDATED, OPCODE_TYPE_TEST_BUILTIN, OPCODE_TYPE_TEST_ARRAY, + OPCODE_TYPE_TEST_DICTIONARY, OPCODE_TYPE_TEST_NATIVE, OPCODE_TYPE_TEST_SCRIPT, OPCODE_SET_KEYED, @@ -242,6 +282,7 @@ public: OPCODE_ASSIGN_FALSE, OPCODE_ASSIGN_TYPED_BUILTIN, OPCODE_ASSIGN_TYPED_ARRAY, + OPCODE_ASSIGN_TYPED_DICTIONARY, OPCODE_ASSIGN_TYPED_NATIVE, OPCODE_ASSIGN_TYPED_SCRIPT, OPCODE_CAST_TO_BUILTIN, @@ -252,6 +293,7 @@ public: OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, + OPCODE_CONSTRUCT_TYPED_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, @@ -280,6 +322,7 @@ public: OPCODE_RETURN, OPCODE_RETURN_TYPED_BUILTIN, OPCODE_RETURN_TYPED_ARRAY, + OPCODE_RETURN_TYPED_DICTIONARY, OPCODE_RETURN_TYPED_NATIVE, OPCODE_RETURN_TYPED_SCRIPT, OPCODE_ITERATE_BEGIN, @@ -509,7 +552,7 @@ private: } profile; #endif - _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; + _FORCE_INLINE_ String _get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const; Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); public: diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 626ef6ccb0..d6fd5d043b 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -84,7 +84,7 @@ int GDScriptLambdaCallable::get_argument_count(bool &r_is_valid) const { return 0; } r_is_valid = true; - return function->get_argument_count(); + return function->get_argument_count() - captures.size(); } void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { @@ -97,25 +97,25 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V } if (captures_amount > 0) { - Vector<const Variant *> args; - args.resize(p_argcount + captures_amount); + const int total_argcount = p_argcount + captures_amount; + const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount); for (int i = 0; i < captures_amount; i++) { - args.write[i] = &captures[i]; + args[i] = &captures[i]; if (captures[i].get_type() == Variant::OBJECT) { bool was_freed = false; captures[i].get_validated_object_with_check(was_freed); if (was_freed) { ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); static Variant nil; - args.write[i] = &nil; + args[i] = &nil; } } } for (int i = 0; i < p_argcount; i++) { - args.write[i + captures_amount] = p_arguments[i]; + args[i + captures_amount] = p_arguments[i]; } - r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error); + r_return_value = function->call(nullptr, args, total_argcount, r_call_error); switch (r_call_error.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: r_call_error.argument -= captures_amount; @@ -150,7 +150,7 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) : function(p_function) { - ERR_FAIL_NULL(p_script.ptr()); + ERR_FAIL_COND(p_script.is_null()); ERR_FAIL_NULL(p_function); script = p_script; captures = p_captures; @@ -198,13 +198,17 @@ ObjectID GDScriptLambdaSelfCallable::get_object() const { return object->get_instance_id(); } +StringName GDScriptLambdaSelfCallable::get_method() const { + return function->get_name(); +} + int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const { if (function == nullptr) { r_is_valid = false; return 0; } r_is_valid = true; - return function->get_argument_count(); + return function->get_argument_count() - captures.size(); } void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { @@ -225,25 +229,25 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun } if (captures_amount > 0) { - Vector<const Variant *> args; - args.resize(p_argcount + captures_amount); + const int total_argcount = p_argcount + captures_amount; + const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount); for (int i = 0; i < captures_amount; i++) { - args.write[i] = &captures[i]; + args[i] = &captures[i]; if (captures[i].get_type() == Variant::OBJECT) { bool was_freed = false; captures[i].get_validated_object_with_check(was_freed); if (was_freed) { ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); static Variant nil; - args.write[i] = &nil; + args[i] = &nil; } } } for (int i = 0; i < p_argcount; i++) { - args.write[i + captures_amount] = p_arguments[i]; + args[i + captures_amount] = p_arguments[i]; } - r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error); + r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args, total_argcount, r_call_error); switch (r_call_error.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: r_call_error.argument -= captures_amount; @@ -278,7 +282,7 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) : function(p_function) { - ERR_FAIL_NULL(p_self.ptr()); + ERR_FAIL_COND(p_self.is_null()); ERR_FAIL_NULL(p_function); reference = p_self; object = p_self.ptr(); diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 45c0235913..2d27b8d679 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -87,6 +87,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + StringName get_method() const override; int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 9be9307b8a..65aa150be3 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -101,7 +101,6 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); - register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); @@ -121,6 +120,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); + register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation); register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT)); // Export grouping annotations. register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); @@ -137,11 +137,22 @@ GDScriptParser::GDScriptParser() { #endif #ifdef TOOLS_ENABLED - if (theme_color_names.is_empty()) { + if (unlikely(theme_color_names.is_empty())) { + // Vectors. theme_color_names.insert("x", "axis_x_color"); theme_color_names.insert("y", "axis_y_color"); theme_color_names.insert("z", "axis_z_color"); theme_color_names.insert("w", "axis_w_color"); + + // Color. + theme_color_names.insert("r", "axis_x_color"); + theme_color_names.insert("r8", "axis_x_color"); + theme_color_names.insert("g", "axis_y_color"); + theme_color_names.insert("g8", "axis_y_color"); + theme_color_names.insert("b", "axis_z_color"); + theme_color_names.insert("b8", "axis_z_color"); + theme_color_names.insert("a", "axis_w_color"); + theme_color_names.insert("a8", "axis_w_color"); } #endif } @@ -166,7 +177,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { panic_mode = true; // TODO: Improve positional information. if (p_origin == nullptr) { - errors.push_back({ p_message, current.start_line, current.start_column }); + errors.push_back({ p_message, previous.start_line, previous.start_column }); } else { errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column }); } @@ -234,6 +245,25 @@ void GDScriptParser::apply_pending_warnings() { } #endif +void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) { + if (!for_completion) { + return; + } + if (p_for_node == nullptr || completion_context.node != p_for_node) { + return; + } + CompletionContext context; + context.type = p_type; + context.current_class = current_class; + context.current_function = current_function; + context.current_suite = current_suite; + context.current_line = tokenizer->get_cursor_line(); + context.current_argument = p_argument; + context.node = p_node; + context.parser = this; + completion_context = context; +} + void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; @@ -249,6 +279,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node context.current_line = tokenizer->get_cursor_line(); context.current_argument = p_argument; context.node = p_node; + context.parser = this; completion_context = context; } @@ -266,6 +297,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ context.current_suite = current_suite; context.current_line = tokenizer->get_cursor_line(); context.builtin_type = p_builtin_type; + context.parser = this; completion_context = context; } @@ -298,13 +330,14 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) { completion_call_stack.back()->get().argument = p_argument; } -Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) { +Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body) { clear(); String source = p_source_code; int cursor_line = -1; int cursor_column = -1; for_completion = p_for_completion; + parse_body = p_parse_body; int tab_size = 4; #ifdef TOOLS_ENABLED @@ -401,7 +434,7 @@ Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String } tokenizer = buffer_tokenizer; - script_path = p_script_path; + script_path = p_script_path.simplify_path(); current = tokenizer->scan(); // Avoid error or newline as the first token. // The latter can mess with the parser when opening files filled exclusively with comments and newlines. @@ -678,6 +711,12 @@ void GDScriptParser::parse_program() { } } + // When the only thing needed is the class name and the icon, we don't need to parse the hole file. + // It really speed up the call to GDScriptLanguage::get_global_class_name especially for large script. + if (!parse_body) { + return; + } + #undef PUSH_PENDING_ANNOTATIONS_TO_HEAD parse_class_body(true); @@ -1600,7 +1639,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali advance(); // Arguments. push_completion_call(annotation); - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0); int argument_index = 0; do { if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { @@ -1608,7 +1647,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali break; } - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); set_last_completion_call_arg(argument_index++); ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { @@ -1877,6 +1916,10 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case Node::CALL: // Fine. break; + case Node::PRELOAD: + // `preload` is a function-like keyword. + push_warning(expression, GDScriptWarning::RETURN_VALUE_DISCARDED, "preload"); + break; case Node::LAMBDA: // Standalone lambdas can't be used, so make this an error. push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression); @@ -2428,7 +2471,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr } // Completion can appear whenever an expression is expected. - make_completion_context(COMPLETION_IDENTIFIER, nullptr); + make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false); GDScriptTokenizer::Token token = current; GDScriptTokenizer::Token::Type token_type = token.type; @@ -2445,8 +2488,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr advance(); // Only consume the token if there's a valid rule. + // After a token was consumed, update the completion context regardless of a previously set context. + ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); +#ifdef TOOLS_ENABLED + // HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion. + if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast<ParseFunction>(&GDScriptParser::parse_identifier)) { + make_completion_context(COMPLETION_IDENTIFIER, previous_operand); + } +#endif + while (p_precedence <= get_rule(current.type)->precedence) { if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) { return previous_operand; @@ -2545,8 +2597,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ } LiteralNode *literal = alloc_node<LiteralNode>(); - complete_extents(literal); literal->value = previous.literal; + reset_extents(literal, p_previous_operand); + update_extents(literal); + make_completion_context(COMPLETION_NONE, literal, -1); + complete_extents(literal); return literal; } @@ -2878,6 +2933,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); +#ifdef TOOLS_ENABLED + if (assignment->assigned_value != nullptr && assignment->assigned_value->type == GDScriptParser::Node::IDENTIFIER) { + override_completion_context(assignment->assigned_value, COMPLETION_ASSIGN, assignment); + } +#endif if (assignment->assigned_value == nullptr) { push_error(R"(Expected an expression after "=".)"); } @@ -3041,12 +3101,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); Variant::Type builtin_type = get_builtin_type(id->name); if (builtin_type < Variant::VARIANT_MAX) { - make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true); + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type); is_builtin = true; } } if (!is_builtin) { - make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true); + make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1); } } @@ -3077,6 +3137,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * subscript->base = p_previous_operand; subscript->index = parse_expression(false); +#ifdef TOOLS_ENABLED + if (subscript->index != nullptr && subscript->index->type == Node::LITERAL) { + override_completion_context(subscript->index, COMPLETION_SUBSCRIPT, subscript); + } +#endif + if (subscript->index == nullptr) { push_error(R"(Expected expression after "[".)"); } @@ -3171,23 +3237,24 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre push_completion_call(call); int argument_index = 0; do { - make_completion_context(ct, call, argument_index++, true); + make_completion_context(ct, call, argument_index); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; } - bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE; ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { push_error(R"(Expected expression as the function argument.)"); } else { call->arguments.push_back(argument); - if (argument->type == Node::IDENTIFIER && use_identifier_completion) { - completion_context.type = COMPLETION_IDENTIFIER; + if (argument->type == Node::LITERAL) { + override_completion_context(argument, ct, call, argument_index); } } + ct = COMPLETION_CALL_ARGUMENTS; + argument_index++; } while (match(GDScriptTokenizer::Token::COMMA)); pop_completion_call(); @@ -3200,7 +3267,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { // We want code completion after a DOLLAR even if the current code is invalid. - make_completion_context(COMPLETION_GET_NODE, nullptr, -1, true); + make_completion_context(COMPLETION_GET_NODE, nullptr, -1); if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name())); @@ -3257,7 +3324,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p path_state = PATH_STATE_SLASH; } - make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++, true); + make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++); if (match(GDScriptTokenizer::Token::LITERAL)) { if (previous.literal.get_type() != Variant::STRING) { @@ -3501,7 +3568,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { type->type_chain.push_back(type_element); if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { - // Typed collection (like Array[int]). + // Typed collection (like Array[int], Dictionary[String, int]). bool first_pass = true; do { TypeNode *container_type = parse_type(false); // Don't allow void for element type. @@ -4040,7 +4107,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return true; } -bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED if (_is_tool) { push_error(R"("@tool" annotation can only be used once.)", p_annotation); @@ -4051,7 +4118,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p return true; } -bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); @@ -4082,7 +4149,7 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p return true; } -bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) { @@ -4156,8 +4223,66 @@ static String _get_annotation_error_string(const StringName &p_annotation_name, return vformat(R"("%s" annotation requires a variable of type %s, but type "%s" was given instead.)", p_annotation_name, string, p_provided_type.to_string()); } +static StringName _find_narrowest_native_or_global_class(const GDScriptParser::DataType &p_type) { + switch (p_type.kind) { + case GDScriptParser::DataType::NATIVE: { + if (p_type.is_meta_type) { + return Object::get_class_static(); // `GDScriptNativeClass` is not an exposed class. + } + return p_type.native_type; + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> script; + if (p_type.script_type.is_valid()) { + script = p_type.script_type; + } else { + script = ResourceLoader::load(p_type.script_path, SNAME("Script")); + } + + if (p_type.is_meta_type) { + return script.is_valid() ? script->get_class() : Script::get_class_static(); + } + if (script.is_null()) { + return p_type.native_type; + } + if (script->get_global_name() != StringName()) { + return script->get_global_name(); + } + + Ref<Script> base_script = script->get_base_script(); + if (base_script.is_null()) { + return script->get_instance_base_type(); + } + + GDScriptParser::DataType base_type; + base_type.kind = GDScriptParser::DataType::SCRIPT; + base_type.builtin_type = Variant::OBJECT; + base_type.native_type = base_script->get_instance_base_type(); + base_type.script_type = base_script; + base_type.script_path = base_script->get_path(); + + return _find_narrowest_native_or_global_class(base_type); + } break; + case GDScriptParser::DataType::CLASS: { + if (p_type.is_meta_type) { + return GDScript::get_class_static(); + } + if (p_type.class_type == nullptr) { + return p_type.native_type; + } + if (p_type.class_type->get_global_name() != StringName()) { + return p_type.class_type->get_global_name(); + } + return _find_narrowest_native_or_global_class(p_type.class_type->base_type); + } break; + default: { + ERR_FAIL_V(StringName()); + } break; + } +} + template <PropertyHint t_hint, Variant::Type t_type> -bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); ERR_FAIL_NULL_V(p_class, false); @@ -4260,6 +4385,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node export_type.type_source = variable->datatype.type_source; } + bool is_dict = false; + if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) { + is_dict = true; + DataType inner_type = export_type.get_container_element_type_or_variant(1); + export_type = export_type.get_container_element_type_or_variant(0); + export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after. + } + bool use_default_variable_type_check = true; if (p_annotation->name == SNAME("@export_range")) { @@ -4287,64 +4420,37 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } if (export_type.is_variant() || export_type.has_no_type()) { - push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); - return false; + if (is_dict) { + // Dictionary allowed to have a variant key/value. + export_type.kind = GDScriptParser::DataType::BUILTIN; + } else { + push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); + return false; + } } switch (export_type.kind) { case GDScriptParser::DataType::BUILTIN: variable->export_info.type = export_type.builtin_type; variable->export_info.hint = PROPERTY_HINT_NONE; - variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); + variable->export_info.hint_string = String(); break; case GDScriptParser::DataType::NATIVE: + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::CLASS: { + const StringName class_name = _find_narrowest_native_or_global_class(export_type); if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.native_type; - } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { - variable->export_info.type = Variant::OBJECT; - variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; - variable->export_info.hint_string = export_type.native_type; - } else { - push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); - return false; - } - break; - case GDScriptParser::DataType::CLASS: - if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { - variable->export_info.type = Variant::OBJECT; - variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.to_string(); + variable->export_info.hint_string = class_name; } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; - variable->export_info.hint_string = export_type.to_string(); + variable->export_info.hint_string = class_name; } else { push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); return false; } - - break; - case GDScriptParser::DataType::SCRIPT: { - StringName class_name; - StringName native_base; - if (export_type.script_type.is_valid()) { - class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); - native_base = export_type.script_type->get_instance_base_type(); - } - if (class_name == StringName()) { - Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); - if (script.is_valid()) { - class_name = script->get_language()->get_global_class_name(export_type.script_path); - native_base = script->get_instance_base_type(); - } - } - if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) { - variable->export_info.type = Variant::OBJECT; - variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = class_name; - } } break; case GDScriptParser::DataType::ENUM: { if (export_type.is_meta_type) { @@ -4380,6 +4486,90 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation); return false; } + + if (is_dict) { + String key_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + key_prefix += "/" + itos(variable->export_info.hint); + } + key_prefix += ":" + variable->export_info.hint_string; + + // Now parse value. + export_type = export_type.get_container_element_type(0); + + if (export_type.is_variant() || export_type.has_no_type()) { + export_type.kind = GDScriptParser::DataType::BUILTIN; + } + switch (export_type.kind) { + case GDScriptParser::DataType::BUILTIN: + variable->export_info.type = export_type.builtin_type; + variable->export_info.hint = PROPERTY_HINT_NONE; + variable->export_info.hint_string = String(); + break; + case GDScriptParser::DataType::NATIVE: + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::CLASS: { + const StringName class_name = _find_narrowest_native_or_global_class(export_type); + if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = class_name; + } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; + variable->export_info.hint_string = class_name; + } else { + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); + return false; + } + } break; + case GDScriptParser::DataType::ENUM: { + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); + } + + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } + } break; + default: + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); + return false; + } + + if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) { + push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation); + return false; + } + + String value_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + value_prefix += "/" + itos(variable->export_info.hint); + } + value_prefix += ":" + variable->export_info.hint_string; + + variable->export_info.type = Variant::DICTIONARY; + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = key_prefix + ";" + value_prefix; + variable->export_info.usage = PROPERTY_USAGE_DEFAULT; + variable->export_info.class_name = StringName(); + } } else if (p_annotation->name == SNAME("@export_enum")) { use_default_variable_type_check = false; @@ -4396,12 +4586,6 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation); return false; } - } else if (p_annotation->name == SNAME("@export_storage")) { - use_default_variable_type_check = false; // Can be applied to a variable of any type. - - // Save the info because the compiler uses export info for overwriting member info. - variable->export_info = export_type.to_property_info(variable->identifier->name); - variable->export_info.usage |= PROPERTY_USAGE_STORAGE; } if (use_default_variable_type_check) { @@ -4421,19 +4605,50 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node if (variable->export_info.hint) { hint_prefix += "/" + itos(variable->export_info.hint); } + variable->export_info.type = original_export_type_builtin; variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; - variable->export_info.type = original_export_type_builtin; + variable->export_info.usage = PROPERTY_USAGE_DEFAULT; + variable->export_info.class_name = StringName(); + } + + return true; +} + +// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values, +// or handle array exports in a special way, so they are implemented as separate methods. + +bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); + + VariableNode *variable = static_cast<VariableNode *>(p_node); + if (variable->is_static) { + push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); + return false; + } + if (variable->exported) { + push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); + return false; } + variable->exported = true; + + // Save the info because the compiler uses export info for overwriting member info. + variable->export_info = variable->get_datatype().to_property_info(variable->identifier->name); + variable->export_info.usage |= PROPERTY_USAGE_STORAGE; + return true; } -bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { +bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)"); VariableNode *variable = static_cast<VariableNode *>(p_node); + if (variable->is_static) { + push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); + return false; + } if (variable->exported) { push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); return false; @@ -4454,31 +4669,29 @@ bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation } template <PropertyUsageFlags t_usage> -bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { - AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); - - if (annotation->resolved_arguments.is_empty()) { +bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + if (p_annotation->resolved_arguments.is_empty()) { return false; } - annotation->export_info.name = annotation->resolved_arguments[0]; + p_annotation->export_info.name = p_annotation->resolved_arguments[0]; switch (t_usage) { case PROPERTY_USAGE_CATEGORY: { - annotation->export_info.usage = t_usage; + p_annotation->export_info.usage = t_usage; } break; case PROPERTY_USAGE_GROUP: { - annotation->export_info.usage = t_usage; - if (annotation->resolved_arguments.size() == 2) { - annotation->export_info.hint_string = annotation->resolved_arguments[1]; + p_annotation->export_info.usage = t_usage; + if (p_annotation->resolved_arguments.size() == 2) { + p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1]; } } break; case PROPERTY_USAGE_SUBGROUP: { - annotation->export_info.usage = t_usage; - if (annotation->resolved_arguments.size() == 2) { - annotation->export_info.hint_string = annotation->resolved_arguments[1]; + p_annotation->export_info.usage = t_usage; + if (p_annotation->resolved_arguments.size() == 2) { + p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1]; } } break; } @@ -4486,7 +4699,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation return true; } -bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifndef DEBUG_ENABLED // Only available in debug builds. return true; @@ -4561,7 +4774,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod #endif // DEBUG_ENABLED } -bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); FunctionNode *function = static_cast<FunctionNode *>(p_target); @@ -4622,7 +4835,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ return true; } -bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name)); ClassNode *class_node = static_cast<ClassNode *>(p_target); if (class_node->annotated_static_unload) { @@ -4678,7 +4891,10 @@ String GDScriptParser::DataType::to_string() const { return "null"; } if (builtin_type == Variant::ARRAY && has_container_element_type(0)) { - return vformat("Array[%s]", container_element_types[0].to_string()); + return vformat("Array[%s]", get_container_element_type(0).to_string()); + } + if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string()); } return Variant::get_type_name(builtin_type); case NATIVE: @@ -4693,9 +4909,9 @@ String GDScriptParser::DataType::to_string() const { return class_type->fqcn; case SCRIPT: { if (is_meta_type) { - return script_type != nullptr ? script_type->get_class_name().operator String() : ""; + return script_type.is_valid() ? script_type->get_class_name().operator String() : ""; } - String name = script_type != nullptr ? script_type->get_name() : ""; + String name = script_type.is_valid() ? script_type->get_name() : ""; if (!name.is_empty()) { return name; } @@ -4767,6 +4983,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co case UNRESOLVED: break; } + } else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + const DataType key_type = get_container_element_type_or_variant(0); + const DataType value_type = get_container_element_type_or_variant(1); + if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING || + key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) { + break; + } + String key_hint, value_hint; + switch (key_type.kind) { + case BUILTIN: + key_hint = Variant::get_type_name(key_type.builtin_type); + break; + case NATIVE: + key_hint = key_type.native_type; + break; + case SCRIPT: + if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) { + key_hint = key_type.script_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case CLASS: + if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) { + key_hint = key_type.class_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case ENUM: + key_hint = String(key_type.native_type).replace("::", "."); + break; + default: + key_hint = "Variant"; + break; + } + switch (value_type.kind) { + case BUILTIN: + value_hint = Variant::get_type_name(value_type.builtin_type); + break; + case NATIVE: + value_hint = value_type.native_type; + break; + case SCRIPT: + if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) { + value_hint = value_type.script_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case CLASS: + if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) { + value_hint = value_type.class_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case ENUM: + value_hint = String(value_type.native_type).replace("::", "."); + break; + default: + value_hint = "Variant"; + break; + } + result.hint = PROPERTY_HINT_DICTIONARY_TYPE; + result.hint_string = key_hint + ";" + value_hint; } break; case NATIVE: @@ -4851,6 +5133,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co return type; } +bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const { + if (p_other.is_meta_type) { + return false; + } else if (builtin_type != p_other.builtin_type) { + return false; + } else if (builtin_type != Variant::OBJECT) { + return true; + } + + if (native_type == StringName()) { + return true; + } else if (p_other.native_type == StringName()) { + return false; + } else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) { + return false; + } + + Ref<Script> script = script_type; + if (kind == GDScriptParser::DataType::CLASS && script.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path)); + script.reference_ptr(scr->find_class(class_type->fqcn)); + } + + Ref<Script> script_other = p_other.script_type; + if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path)); + script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn)); + } + + if (script.is_null()) { + return true; + } else if (script_other.is_null()) { + return false; + } else if (script != script_other && !script_other->inherits_script(script)) { + return false; + } + + return true; +} + void GDScriptParser::complete_extents(Node *p_node) { while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) { ERR_PRINT("Parser bug: Mismatch in extents tracking stack."); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 4c11fa7f8b..7840474a89 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -189,6 +189,8 @@ public: GDScriptParser::DataType get_typed_container_type() const; + bool can_reference(const DataType &p_other) const; + bool operator==(const DataType &p_other) const { if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { return true; // Can be considered equal for parsing purposes. @@ -1314,7 +1316,7 @@ public: Variant::Type builtin_type = Variant::VARIANT_MAX; Node *node = nullptr; Object *base = nullptr; - List<Ref<GDScriptParserRef>> dependent_parsers; + GDScriptParser *parser = nullptr; }; struct CompletionCall { @@ -1329,6 +1331,7 @@ private: bool _is_tool = false; String script_path; bool for_completion = false; + bool parse_body = true; bool panic_mode = false; bool can_break = false; bool can_continue = false; @@ -1370,7 +1373,7 @@ private: bool in_lambda = false; bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed. - typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + typedef bool (GDScriptParser::*AnnotationAction)(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); struct AnnotationInfo { enum TargetKind { NONE = 0, @@ -1454,9 +1457,14 @@ private: } void apply_pending_warnings(); #endif - - void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false); - void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false); + // Setting p_force to false will prevent the completion context from being update if a context was already set before. + // This should only be done when we push context before we consumed any tokens for the corresponding structure. + // See parse_precedence for an example. + void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = true); + void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = true); + // In some cases it might become necessary to alter the completion context after parsing a subexpression. + // For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals. + void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1); void push_completion_call(Node *p_call); void pop_completion_call(); void set_last_completion_call_arg(int p_argument); @@ -1492,17 +1500,18 @@ private: static bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); bool validate_annotation_arguments(AnnotationNode *p_annotation); void clear_unused_annotations(); - bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template <PropertyHint t_hint, Variant::Type t_type> - bool export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool export_custom_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template <PropertyUsageFlags t_usage> - bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); // Statements. Node *parse_statement(); VariableNode *parse_variable(bool p_is_static); @@ -1559,7 +1568,7 @@ private: #endif // TOOLS_ENABLED public: - Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion); + Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body = true); Error parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path); ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 5b1639e250..404b61fb40 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -574,7 +574,9 @@ GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() { if (len == 1 && _peek(-1) == '_') { // Lone underscore. - return make_token(Token::UNDERSCORE); + Token token = make_token(Token::UNDERSCORE); + token.literal = "_"; + return token; } String name(_start, len); diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp index e53bc5bc41..2046480f0e 100644 --- a/modules/gdscript/gdscript_tokenizer_buffer.cpp +++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp @@ -296,6 +296,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code, encode_uint32(identifier_map.size(), &contents.write[0]); encode_uint32(constant_map.size(), &contents.write[4]); encode_uint32(token_lines.size(), &contents.write[8]); + encode_uint32(0, &contents.write[12]); // Unused, kept for compatibility. Please remove at next `TOKENIZER_VERSION` increment. encode_uint32(token_counter, &contents.write[16]); int buf_pos = 20; diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 3e1de628d2..59dd983ed2 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -232,7 +232,7 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 5d1805696d..4617a0dbb9 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) { if (p_var->get_type() == Variant::ARRAY) { basestr = "Array"; const Array *p_array = VariantInternal::get_array(p_var); - Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin(); - if (builtin_type != Variant::NIL) { - basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; + if (p_array->is_typed()) { + basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; + } + } else if (p_var->get_type() == Variant::DICTIONARY) { + basestr = "Dictionary"; + const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var); + if (p_dictionary->is_typed()) { + basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) + + ", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]"; } } else { basestr = Variant::get_type_name(p_var->get_type()); @@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT } return array; + } else if (p_data_type.builtin_type == Variant::DICTIONARY) { + Dictionary dict; + // Typed dictionary. + if (p_data_type.has_container_element_types()) { + const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1); + dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type); + } + + return dict; } else { Callable::CallError ce; Variant variant; @@ -134,38 +150,39 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT return Variant(); } -String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { - String err_text; - - if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - int errorarg = p_err.argument; - ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer."); - // Handle the Object to Object case separately as we don't have further class details. +String GDScriptFunction::_get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const { + switch (p_err.error) { + case Callable::CallError::CALL_OK: + return String(); + case Callable::CallError::CALL_ERROR_INVALID_METHOD: + if (p_ret.get_type() == Variant::STRING && !p_ret.operator String().is_empty()) { + return "Invalid call " + p_where + ": " + p_ret.operator String(); + } + return "Invalid call. Nonexistent " + p_where + "."; + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + ERR_FAIL_COND_V_MSG(p_err.argument < 0 || p_argptrs[p_err.argument] == nullptr, "Bug: Invalid CallError argument index or null pointer.", "Bug: Invalid CallError argument index or null pointer."); + // Handle the Object to Object case separately as we don't have further class details. #ifdef DEBUG_ENABLED - if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { - err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; - } else if (p_err.expected == Variant::ARRAY && argptrs[errorarg]->get_type() == p_err.expected) { - err_text = "Invalid type in " + p_where + ". The array of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") does not have the same element type as the expected typed array argument."; - } else + if (p_err.expected == Variant::OBJECT && p_argptrs[p_err.argument]->get_type() == p_err.expected) { + return "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") is not a subclass of the expected argument class."; + } + if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) { + return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument."; + } + if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) { + return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument."; + } #endif // DEBUG_ENABLED - { - err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; - } - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - err_text = "Invalid call. Nonexistent " + p_where + "."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { - err_text = "Attempt to call " + p_where + " on a null instance."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) { - err_text = "Attempt to call " + p_where + " on a const instance."; - } else { - err_text = "Bug, call error: #" + itos(p_err.error); + return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + return "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + return "Attempt to call " + p_where + " on a null instance."; + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: + return "Attempt to call " + p_where + " on a const instance."; } - - return err_text; + return "Bug: Invalid call error code " + itos(p_err.error) + "."; } void (*type_init_function_table[])(Variant *) = { @@ -217,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_OPERATOR_VALIDATED, \ &&OPCODE_TYPE_TEST_BUILTIN, \ &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_DICTIONARY, \ &&OPCODE_TYPE_TEST_NATIVE, \ &&OPCODE_TYPE_TEST_SCRIPT, \ &&OPCODE_SET_KEYED, \ @@ -239,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_ASSIGN_FALSE, \ &&OPCODE_ASSIGN_TYPED_BUILTIN, \ &&OPCODE_ASSIGN_TYPED_ARRAY, \ + &&OPCODE_ASSIGN_TYPED_DICTIONARY, \ &&OPCODE_ASSIGN_TYPED_NATIVE, \ &&OPCODE_ASSIGN_TYPED_SCRIPT, \ &&OPCODE_CAST_TO_BUILTIN, \ @@ -249,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_CONSTRUCT_ARRAY, \ &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \ &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ &&OPCODE_CALL_ASYNC, \ @@ -277,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_RETURN, \ &&OPCODE_RETURN_TYPED_BUILTIN, \ &&OPCODE_RETURN_TYPED_ARRAY, \ + &&OPCODE_RETURN_TYPED_DICTIONARY, \ &&OPCODE_RETURN_TYPED_NATIVE, \ &&OPCODE_RETURN_TYPED_SCRIPT, \ &&OPCODE_ITERATE_BEGIN, \ @@ -550,9 +571,27 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - Variant arg; - Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); - memnew_placement(&stack[i + 3], Variant(arg)); + if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) { + const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0); + const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1); + Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type); + memnew_placement(&stack[i + 3], Variant(dict)); + } else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) { + const GDScriptDataType &arg_type = argument_types[i].container_element_types[0]; + Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type); + memnew_placement(&stack[i + 3], Variant(array)); + } else { + Variant variant; + Variant::construct(argument_types[i].builtin_type, variant, &p_args[i], 1, r_err); + if (unlikely(r_err.error)) { + r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].builtin_type; + call_depth--; + return _get_default_variant_for_data_type(return_type); + } + memnew_placement(&stack[i + 3], Variant(variant)); + } } else { memnew_placement(&stack[i + 3], Variant(*p_args[i])); } @@ -816,6 +855,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_TYPE_TEST_DICTIONARY) { + CHECK_SPACE(9); + + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); + + GET_VARIANT_PTR(key_script_type, 2); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + int key_native_type_idx = _code_ptr[ip + 6]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 3); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + int value_native_type_idx = _code_ptr[ip + 8]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + bool result = false; + if (value->get_type() == Variant::DICTIONARY) { + Dictionary *dictionary = VariantInternal::get_dictionary(value); + result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type && + dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type; + } + + *dst = result; + ip += 9; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_TYPE_TEST_NATIVE) { CHECK_SPACE(4); @@ -1373,6 +1442,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) { + CHECK_SPACE(9); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); + + GET_VARIANT_PTR(key_script_type, 2); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + int key_native_type_idx = _code_ptr[ip + 6]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 3); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + int value_native_type_idx = _code_ptr[ip + 8]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + if (src->get_type() != Variant::DICTIONARY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)", + _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Dictionary *dictionary = VariantInternal::get_dictionary(src); + + if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type || + dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)", + _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + *dst = *src; + + ip += 9; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { CHECK_SPACE(4); GET_VARIANT_PTR(dst, 0); @@ -1595,7 +1708,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs); + err_text = _get_call_error("'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs, *dst, err); OPCODE_BREAK; } #endif @@ -1692,12 +1805,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(dst, argc * 2); + *dst = Variant(); // Clear potential previous typed dictionary. + *dst = dict; ip += 2; } DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) { + LOAD_INSTRUCTION_ARGS + CHECK_SPACE(6 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2]; + int key_native_type_idx = _code_ptr[ip + 3]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int value_native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + Dictionary dict; + + for (int i = 0; i < argc; i++) { + GET_INSTRUCTION_ARG(k, i * 2 + 0); + GET_INSTRUCTION_ARG(v, i * 2 + 1); + dict[*k] = *v; + } + + GET_INSTRUCTION_ARG(dst, argc * 2); + + *dst = Variant(); // Clear potential previous typed dictionary. + + *dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type); + + ip += 6; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_ASYNC) OPCODE(OPCODE_CALL_RETURN) OPCODE(OPCODE_CALL) { @@ -1731,10 +1883,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a StringName base_class = base_obj ? base_obj->get_class_name() : StringName(); #endif + Variant temp_ret; Callable::CallError err; if (call_ret) { GET_INSTRUCTION_ARG(ret, argc + 1); - base->callp(*methodname, (const Variant **)argptrs, argc, *ret, err); + base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err); + *ret = temp_ret; #ifdef DEBUG_ENABLED if (ret->get_type() == Variant::NIL) { if (base_type == Variant::OBJECT) { @@ -1763,8 +1917,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #endif } else { - Variant ret; - base->callp(*methodname, (const Variant **)argptrs, argc, ret, err); + base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err); } #ifdef DEBUG_ENABLED @@ -1794,7 +1947,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; @@ -1809,7 +1962,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } } - err_text = _get_call_error(err, "function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs); + err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err); OPCODE_BREAK; } #endif @@ -1855,12 +2008,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #endif + Variant temp_ret; Callable::CallError err; if (call_ret) { GET_INSTRUCTION_ARG(ret, argc + 1); - *ret = method->call(base_obj, (const Variant **)argptrs, argc, err); + temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err); + *ret = temp_ret; } else { - method->call(base_obj, (const Variant **)argptrs, argc, err); + temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err); } #ifdef DEBUG_ENABLED @@ -1885,7 +2040,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; @@ -1893,7 +2048,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } } - err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); + err_text = _get_call_error("function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs, temp_ret, err); OPCODE_BREAK; } #endif @@ -1926,7 +2081,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs); + err_text = _get_call_error("static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs, *ret, err); OPCODE_BREAK; } #endif @@ -1970,7 +2125,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs); + err_text = _get_call_error("static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs, *ret, err); OPCODE_BREAK; } @@ -2201,7 +2356,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a // Call provided error string. err_text = vformat(R"*(Error calling utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs); + err_text = _get_call_error(vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err); } OPCODE_BREAK; } @@ -2258,7 +2413,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a // Call provided error string. err_text = vformat(R"*(Error calling GDScript utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs); + err_text = _get_call_error(vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err); } OPCODE_BREAK; } @@ -2325,7 +2480,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (err.error != Callable::CallError::CALL_OK) { String methodstr = *methodname; - err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); + err_text = _get_call_error("function '" + methodstr + "'", (const Variant **)argptrs, *dst, err); OPCODE_BREAK; } @@ -2643,6 +2798,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } + OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) { + CHECK_SPACE(8); + GET_VARIANT_PTR(r, 0); + + GET_VARIANT_PTR(key_script_type, 1); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int key_native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 2); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6]; + int value_native_type_idx = _code_ptr[ip + 7]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + if (r->get_type() != Variant::DICTIONARY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)", + _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Dictionary *dictionary = VariantInternal::get_dictionary(r); + + if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type || + dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)", + _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + retvalue = *dictionary; + +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + OPCODE(OPCODE_RETURN_TYPED_NATIVE) { CHECK_SPACE(3); GET_VARIANT_PTR(r, 0); diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 48a0abe617..4ffb4bd9d1 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -74,7 +74,7 @@ String GDScriptWarning::get_message() const { case UNREACHABLE_PATTERN: return "Unreachable pattern (pattern after wildcard or bind)."; case STANDALONE_EXPRESSION: - return "Standalone expression (the line has no effect)."; + return "Standalone expression (the line may have no effect)."; case STANDALONE_TERNARY: return "Standalone ternary operator: the return value is being discarded."; case INCOMPATIBLE_TERNARY: @@ -109,6 +109,8 @@ String GDScriptWarning::get_message() const { case STATIC_CALLED_ON_INSTANCE: CHECK_SYMBOLS(2); return vformat(R"*(The function "%s()" is a static function but was called from an instance. Instead, it should be directly called from the type: "%s.%s()".)*", symbols[0], symbols[1], symbols[0]); + case MISSING_TOOL: + return R"(The base class script has the "@tool" annotation, but this script does not have it.)"; case REDUNDANT_STATIC_UNLOAD: return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)"; case REDUNDANT_AWAIT: @@ -145,6 +147,9 @@ String GDScriptWarning::get_message() const { case CONFUSABLE_LOCAL_USAGE: CHECK_SYMBOLS(1); return vformat(R"(The identifier "%s" will be shadowed below in the block.)", symbols[0]); + case CONFUSABLE_CAPTURE_REASSIGNMENT: + CHECK_SYMBOLS(1); + return vformat(R"(Reassigning lambda capture does not modify the outer local variable "%s".)", symbols[0]); case INFERENCE_ON_VARIANT: CHECK_SYMBOLS(1); return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]); @@ -216,6 +221,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "UNSAFE_VOID_RETURN", "RETURN_VALUE_DISCARDED", "STATIC_CALLED_ON_INSTANCE", + "MISSING_TOOL", "REDUNDANT_STATIC_UNLOAD", "REDUNDANT_AWAIT", "ASSERT_ALWAYS_TRUE", @@ -231,6 +237,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "CONFUSABLE_IDENTIFIER", "CONFUSABLE_LOCAL_DECLARATION", "CONFUSABLE_LOCAL_USAGE", + "CONFUSABLE_CAPTURE_REASSIGNMENT", "INFERENCE_ON_VARIANT", "NATIVE_METHOD_OVERRIDE", "GET_NODE_DEFAULT_WITHOUT_ONREADY", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 3ad9488138..ffcf00a830 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -70,6 +70,7 @@ public: UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked. RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used. STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. + MISSING_TOOL, // The base class script has the "@tool" annotation, but this script does not have it. REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data. REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. @@ -85,6 +86,7 @@ public: CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). CONFUSABLE_LOCAL_DECLARATION, // The parent block declares an identifier with the same name below. CONFUSABLE_LOCAL_USAGE, // The identifier will be shadowed below in the block. + CONFUSABLE_CAPTURE_REASSIGNMENT, // Reassigning lambda capture does not modify the outer local variable. INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant. NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended. GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation. @@ -122,6 +124,7 @@ public: WARN, // UNSAFE_VOID_RETURN IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.). WARN, // STATIC_CALLED_ON_INSTANCE + WARN, // MISSING_TOOL WARN, // REDUNDANT_STATIC_UNLOAD WARN, // REDUNDANT_AWAIT WARN, // ASSERT_ALWAYS_TRUE @@ -137,6 +140,7 @@ public: WARN, // CONFUSABLE_IDENTIFIER WARN, // CONFUSABLE_LOCAL_DECLARATION WARN, // CONFUSABLE_LOCAL_USAGE + WARN, // CONFUSABLE_CAPTURE_REASSIGNMENT ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type. ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected. ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected. diff --git a/modules/gdscript/icons/GDScript.svg b/modules/gdscript/icons/GDScript.svg index 2671c007f3..e44adf60dc 100644 --- a/modules/gdscript/icons/GDScript.svg +++ b/modules/gdscript/icons/GDScript.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z" fill="#e0e0e0"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z"/></svg>
\ No newline at end of file diff --git a/modules/gdscript/icons/GDScriptInternal.svg b/modules/gdscript/icons/GDScriptInternal.svg index 81a59a7387..57471729cd 100644 --- a/modules/gdscript/icons/GDScriptInternal.svg +++ b/modules/gdscript/icons/GDScriptInternal.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z" fill="none" stroke="#e0e0e0"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" stroke="#e0e0e0" d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z"/></svg>
\ No newline at end of file diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index a808f19e5b..239f7d9f43 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -37,10 +37,10 @@ #include "core/variant/variant.h" #ifndef LINE_NUMBER_TO_INDEX -#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) +#define LINE_NUMBER_TO_INDEX(p_line) ((p_line) - 1) #endif #ifndef COLUMN_NUMBER_TO_INDEX -#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1) +#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column) - 1) #endif #ifndef SYMBOL_SEPERATOR diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 03d830741b..b636dbe580 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -196,7 +196,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(), vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id)); Ref<LSPeer> peer = clients.get(latest_client_id); - if (peer != nullptr) { + if (peer.is_valid()) { String msg = Variant(request).to_json_string(); msg = format_output(msg); (*peer)->res_queue.push_back(msg.utf8()); @@ -298,7 +298,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_NULL(peer); + ERR_FAIL_COND(peer.is_null()); Dictionary message = make_notification(p_method, p_params); String msg = Variant(message).to_json_string(); @@ -319,7 +319,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_NULL(peer); + ERR_FAIL_COND(peer.is_null()); Dictionary message = make_request(p_method, p_params, next_server_id); next_server_id++; diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 3df26ea576..731988148d 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -39,6 +39,7 @@ int GDScriptLanguageServer::port_override = -1; GDScriptLanguageServer::GDScriptLanguageServer() { + // TODO: Move to editor_settings.cpp _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); _EDITOR_DEF("network/language_server/enable_smart_resolve", true); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 9bf458e031..06e9775360 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -229,19 +229,6 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { arr[i] = item.to_json(); i++; } - } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - arr = native_member_completions.duplicate(); - - for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) { - ExtendGDScriptParser *scr = E.value; - const Array &items = scr->get_member_completions(); - - const int start_size = arr.size(); - arr.resize(start_size + items.size()); - for (int i = start_size; i < arr.size(); i++) { - arr[i] = items[i - start_size]; - } - } } return arr; } @@ -309,7 +296,7 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { params.load(p_params["data"]); symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function); - } else if (data.get_type() == Variant::STRING) { + } else if (data.is_string()) { String query = data; Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false); @@ -485,12 +472,10 @@ GDScriptTextDocument::GDScriptTextDocument() { void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); - - EditorFileSystem::get_singleton()->update_file(path); } void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { - ScriptEditor::get_singleton()->call_deferred(SNAME("_help_class_goto"), p_symbol_id); + callable_mp(ScriptEditor::get_singleton(), &ScriptEditor::goto_help).call_deferred(p_symbol_id); DisplayServer::get_singleton()->window_move_to_foreground(); } diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 284762018f..6e19cd7a23 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -236,7 +236,7 @@ struct ReferenceContext { /** * Include the declaration of the current symbol. */ - bool includeDeclaration; + bool includeDeclaration = false; }; struct ReferenceParams : TextDocumentPositionParams { @@ -958,28 +958,30 @@ struct CompletionItem { /** * A string that should be used when comparing this item - * with other items. When `falsy` the label is used. + * with other items. When omitted the label is used + * as the filter text for this item. */ String sortText; /** * A string that should be used when filtering a set of - * completion items. When `falsy` the label is used. + * completion items. When omitted the label is used as the + * filter text for this item. */ String filterText; /** * A string that should be inserted into a document when selecting - * this completion. When `falsy` the label is used. + * this completion. When omitted the label is used as the insert text + * for this item. * * The `insertText` is subject to interpretation by the client side. * Some tools might not take the string literally. For example - * VS Code when code complete is requested in this example `con<cursor position>` - * and a completion item with an `insertText` of `console` is provided it - * will only insert `sole`. Therefore it is recommended to use `textEdit` instead - * since it avoids additional client side interpretation. - * - * @deprecated Use textEdit instead. + * VS Code when code complete is requested in this example + * `con<cursor position>` and a completion item with an `insertText` of + * `console` is provided it will only insert `sole`. Therefore it is + * recommended to use `textEdit` instead since it avoids additional client + * side interpretation. */ String insertText; @@ -1034,14 +1036,20 @@ struct CompletionItem { dict["label"] = label; dict["kind"] = kind; dict["data"] = data; - dict["insertText"] = insertText; + if (!insertText.is_empty()) { + dict["insertText"] = insertText; + } if (resolved) { dict["detail"] = detail; dict["documentation"] = documentation.to_json(); dict["deprecated"] = deprecated; dict["preselect"] = preselect; - dict["sortText"] = sortText; - dict["filterText"] = filterText; + if (!sortText.is_empty()) { + dict["sortText"] = sortText; + } + if (!filterText.is_empty()) { + dict["filterText"] = filterText; + } if (commitCharacters.size()) { dict["commitCharacters"] = commitCharacters; } @@ -1064,7 +1072,7 @@ struct CompletionItem { } if (p_dict.has("documentation")) { Variant doc = p_dict["documentation"]; - if (doc.get_type() == Variant::STRING) { + if (doc.is_string()) { documentation.value = doc; } else if (doc.get_type() == Variant::DICTIONARY) { Dictionary v = doc; diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 59e387eece..055f8e4110 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -165,6 +165,13 @@ void initialize_gdscript_module(ModuleInitializationLevel p_level) { gdscript_translation_parser_plugin.instantiate(); EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); + } else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { + ClassDB::APIType prev_api = ClassDB::get_current_api(); + ClassDB::set_current_api(ClassDB::API_EDITOR); + + GDREGISTER_CLASS(GDScriptSyntaxHighlighter); + + ClassDB::set_current_api(prev_api); } #endif // TOOLS_ENABLED } diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md index 714e38397f..4dc706f8d1 100644 --- a/modules/gdscript/tests/README.md +++ b/modules/gdscript/tests/README.md @@ -25,6 +25,8 @@ The config file contains two section: - `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build. - `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test. +- `add_node_path_literals: boolean = false`: Configures the corresponding editor setting for the test. +- `add_string_name_literals: boolean = false`: Configures the corresponding editor setting for the test. - `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened. - `node_path: String`: The node path of the node which holds the current script inside of the scene. Defaults to the scene root node. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index fbfa4a0a79..025fcbd32a 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -622,7 +622,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { enable_stdout(); result.status = GDTEST_COMPILER_ERROR; result.output = get_text_for_status(result.status) + "\n"; - result.output = compiler.get_error(); + result.output += compiler.get_error() + "\n"; if (!p_is_generating) { result.passed = check_output(result.output); } diff --git a/modules/gdscript/tests/scripts/.editorconfig b/modules/gdscript/tests/scripts/.editorconfig new file mode 100644 index 0000000000..da1efefe3c --- /dev/null +++ b/modules/gdscript/tests/scripts/.editorconfig @@ -0,0 +1,12 @@ +# This file is required to workaround `.editorconfig` autogeneration (see #96845). + +# Some tests handle invalid syntax deliberately; exclude relevant attributes. + +[parser/features/mixed_indentation_on_blank_lines.gd] +trim_trailing_whitespace = false + +[parser/warnings/empty_file_newline.notest.gd] +insert_final_newline = false + +[parser/warnings/empty_file_newline_comment.notest.gd] +insert_final_newline = false diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out index 64a6bd417d..878e827d04 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Could not resolve member "v". +Could not resolve external class member "v". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd new file mode 100644 index 0000000000..c180cca03c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd @@ -0,0 +1,3 @@ +func test(): + for key: int in { "a": 1 }: + print(key) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd new file mode 100644 index 0000000000..75d1b7fe62 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Dictionary[float, float] = { 1.0: 0.0 } + var typed: Dictionary[int, int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out new file mode 100644 index 0000000000..e05d4be8c9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type Dictionary[float, float] to variable "typed" with specified type Dictionary[int, int]. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd new file mode 100644 index 0000000000..e0af71823a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd @@ -0,0 +1,2 @@ +func test(): + const dict: Dictionary[int, int] = { "Hello": "World" } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd new file mode 100644 index 0000000000..814ba12aef --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd @@ -0,0 +1,4 @@ +func test(): + var unconvertible := 1 + var typed: Dictionary[Object, Object] = { unconvertible: unconvertible } + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out new file mode 100644 index 0000000000..9d6c9d9144 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd new file mode 100644 index 0000000000..73d8ce2b96 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var differently: Dictionary[float, float] = { 1.0: 0.0 } + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out new file mode 100644 index 0000000000..302109cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "expect_typed()" function: argument 1 should be "Dictionary[int, int]" but is "Dictionary[float, float]". diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd index efd8ad6edb..60bcde4b8c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd @@ -3,14 +3,13 @@ const const_color: Color = 'red' func func_color(arg_color: Color = 'blue') -> bool: return arg_color == Color.BLUE -@warning_ignore("assert_always_true") func test(): - assert(const_color == Color.RED) + Utils.check(const_color == Color.RED) - assert(func_color() == true) - assert(func_color('blue') == true) + Utils.check(func_color() == true) + Utils.check(func_color('blue') == true) var var_color: Color = 'green' - assert(var_color == Color.GREEN) + Utils.check(var_color == Color.GREEN) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd index bed9dd0e96..5318d11f33 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd @@ -5,20 +5,19 @@ const const_float_cast: float = 76 as float const const_packed_empty: PackedFloat64Array = [] const const_packed_ints: PackedFloat64Array = [52] -@warning_ignore("assert_always_true") func test(): - assert(typeof(const_float_int) == TYPE_FLOAT) - assert(str(const_float_int) == '19') - assert(typeof(const_float_plus) == TYPE_FLOAT) - assert(str(const_float_plus) == '34') - assert(typeof(const_float_cast) == TYPE_FLOAT) - assert(str(const_float_cast) == '76') + Utils.check(typeof(const_float_int) == TYPE_FLOAT) + Utils.check(str(const_float_int) == '19') + Utils.check(typeof(const_float_plus) == TYPE_FLOAT) + Utils.check(str(const_float_plus) == '34') + Utils.check(typeof(const_float_cast) == TYPE_FLOAT) + Utils.check(str(const_float_cast) == '76') - assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) - assert(str(const_packed_empty) == '[]') - assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) - assert(str(const_packed_ints) == '[52]') - assert(typeof(const_packed_ints[0]) == TYPE_FLOAT) - assert(str(const_packed_ints[0]) == '52') + Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) + Utils.check(str(const_packed_empty) == '[]') + Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) + Utils.check(str(const_packed_ints) == '[52]') + Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT) + Utils.check(str(const_packed_ints[0]) == '52') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd index d2d9d04508..a569488d3c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd @@ -5,5 +5,5 @@ func test(): for value in range(E.E0, E.E3): var inferable := value total += inferable - assert(total == 0 + 1 + 2) + Utils.check(total == 0 + 1 + 2) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd index 39f490c4b3..ec89226328 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd @@ -2,8 +2,6 @@ class_name TestExportEnumAsDictionary enum MyEnum {A, B, C} -const Utils = preload("../../utils.notest.gd") - @export var test_1 = MyEnum @export var test_2 = MyEnum.A @export var test_3 := MyEnum diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out index 505af5f1f3..0d96f8c021 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out @@ -1,11 +1,11 @@ GDTEST_OK var test_1: Dictionary - hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_2: TestExportEnumAsDictionary.MyEnum - hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM class_name=&"TestExportEnumAsDictionary.MyEnum" var test_3: Dictionary - hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_4: TestExportEnumAsDictionary.MyEnum - hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM class_name=&"TestExportEnumAsDictionary.MyEnum" var test_5: TestExportEnumAsDictionary.MyEnum - hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM class_name=&"TestExportEnumAsDictionary.MyEnum" diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd new file mode 100644 index 0000000000..ad265a7365 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd @@ -0,0 +1,17 @@ +extends "external_parser_base1.notest.gd" + +const External1 = preload("external_parser_script1.notest.gd") + +func baz(e1: External1) -> void: + print(e1.e1c.bar) + print(e1.baz) + +func test_external_base_parser_type_resolve(_v: TypeFromBase): + pass + +func test(): + var ext := External1.new() + print(ext.array[0].test2) + print(ext.get_external2().get_external3().test3) + # TODO: This actually produces a runtime error, but we're testing the analyzer here + #baz(ext) diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser.out b/modules/gdscript/tests/scripts/analyzer/features/external_parser.out new file mode 100644 index 0000000000..26c2527fbc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser.out @@ -0,0 +1,3 @@ +GDTEST_OK +test2 +test3 diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd new file mode 100644 index 0000000000..d27d8cef3b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd @@ -0,0 +1 @@ +extends "external_parser_base2.notest.gd" diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd new file mode 100644 index 0000000000..f8de818485 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd @@ -0,0 +1,2 @@ +class TypeFromBase: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd new file mode 100644 index 0000000000..e1221bb138 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd @@ -0,0 +1,12 @@ +extends "external_parser_script1_base.notest.gd" + +const External2 = preload("external_parser_script2.notest.gd") +const External1c = preload("external_parser_script1c.notest.gd") + +@export var e1c: External1c + +var array: Array[External2] = [ External2.new() ] +var baz: int + +func get_external2() -> External2: + return External2.new() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd new file mode 100644 index 0000000000..4249efa1f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd @@ -0,0 +1 @@ +extends Resource diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd new file mode 100644 index 0000000000..34a48cba6f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd @@ -0,0 +1 @@ +extends "external_parser_script1d.notest.gd" diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd new file mode 100644 index 0000000000..43bc6f3e53 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd @@ -0,0 +1,5 @@ +extends "external_parser_script1f.notest.gd" + +const External1e = preload("external_parser_script1e.notest.gd") + +var bar: Array[External1e] diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd new file mode 100644 index 0000000000..4249efa1f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd @@ -0,0 +1 @@ +extends Resource diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd new file mode 100644 index 0000000000..885f813503 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd @@ -0,0 +1,6 @@ +const External3 = preload("external_parser_script3.notest.gd") + +var test2 := "test2" + +func get_external3() -> External3: + return External3.new() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd new file mode 100644 index 0000000000..3e62974256 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd @@ -0,0 +1 @@ +var test3 := "test3" diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd index 4a7f10f1ee..9ce0782d5c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd @@ -3,5 +3,5 @@ func test(): var result := '' for i in range(array.size(), 0, -1): result += str(array[i - 1]) - assert(result == '963') + Utils.check(result == '963') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd index d678f3acfc..e0cbdacb38 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd @@ -2,11 +2,11 @@ func test(): var instance := Parent.new() var result := instance.my_function(1) print(result) - assert(result == 1) + Utils.check(result == 1) instance = Child.new() result = instance.my_function(2) print(result) - assert(result == 0) + Utils.check(result == 0) class Parent: func my_function(par1: int) -> int: diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd index 0b1576e66e..cbe8e9da34 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd @@ -8,27 +8,27 @@ func convert_var_array_to_packed() -> PackedStringArray: var array := ['79']; re func test(): var converted_literal_int := convert_literal_int_to_float() - assert(typeof(converted_literal_int) == TYPE_FLOAT) - assert(converted_literal_int == 76.0) + Utils.check(typeof(converted_literal_int) == TYPE_FLOAT) + Utils.check(converted_literal_int == 76.0) var converted_arg_int := convert_arg_int_to_float(36) - assert(typeof(converted_arg_int) == TYPE_FLOAT) - assert(converted_arg_int == 36.0) + Utils.check(typeof(converted_arg_int) == TYPE_FLOAT) + Utils.check(converted_arg_int == 36.0) var converted_var_int := convert_var_int_to_float() - assert(typeof(converted_var_int) == TYPE_FLOAT) - assert(converted_var_int == 59.0) + Utils.check(typeof(converted_var_int) == TYPE_FLOAT) + Utils.check(converted_var_int == 59.0) var converted_literal_array := convert_literal_array_to_packed() - assert(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_literal_array) == '["46"]') + Utils.check(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_literal_array) == '["46"]') var converted_arg_array := convert_arg_array_to_packed(['91']) - assert(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_arg_array) == '["91"]') + Utils.check(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_arg_array) == '["91"]') var converted_var_array := convert_var_array_to_packed() - assert(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_var_array) == '["79"]') + Utils.check(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_var_array) == '["79"]') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd index 44ca5f4dd0..d49acaacd3 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd @@ -2,7 +2,7 @@ func test(): var left_hard_int := 1 var right_hard_int := 2 var result_hard_int := left_hard_int if true else right_hard_int - assert(result_hard_int == 1) + Utils.check(result_hard_int == 1) @warning_ignore("inference_on_variant") var left_hard_variant := 1 as Variant @@ -10,6 +10,6 @@ func test(): var right_hard_variant := 2.0 as Variant @warning_ignore("inference_on_variant") var result_hard_variant := left_hard_variant if true else right_hard_variant - assert(result_hard_variant == 1) + Utils.check(result_hard_variant == 1) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd index 12dc0b93df..ee30f01dfb 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd @@ -4,124 +4,123 @@ class A extends RefCounted: class B extends A: pass -@warning_ignore("assert_always_true") func test(): var builtin: Variant = 3 - assert((builtin is Variant) == true) - assert((builtin is int) == true) - assert(is_instance_of(builtin, TYPE_INT) == true) - assert((builtin is float) == false) - assert(is_instance_of(builtin, TYPE_FLOAT) == false) + Utils.check((builtin is Variant) == true) + Utils.check((builtin is int) == true) + Utils.check(is_instance_of(builtin, TYPE_INT) == true) + Utils.check((builtin is float) == false) + Utils.check(is_instance_of(builtin, TYPE_FLOAT) == false) const const_builtin: Variant = 3 - assert((const_builtin is Variant) == true) - assert((const_builtin is int) == true) - assert(is_instance_of(const_builtin, TYPE_INT) == true) - assert((const_builtin is float) == false) - assert(is_instance_of(const_builtin, TYPE_FLOAT) == false) + Utils.check((const_builtin is Variant) == true) + Utils.check((const_builtin is int) == true) + Utils.check(is_instance_of(const_builtin, TYPE_INT) == true) + Utils.check((const_builtin is float) == false) + Utils.check(is_instance_of(const_builtin, TYPE_FLOAT) == false) var int_array: Variant = [] as Array[int] - assert((int_array is Variant) == true) - assert((int_array is Array) == true) - assert(is_instance_of(int_array, TYPE_ARRAY) == true) - assert((int_array is Array[int]) == true) - assert((int_array is Array[float]) == false) - assert((int_array is int) == false) - assert(is_instance_of(int_array, TYPE_INT) == false) + Utils.check((int_array is Variant) == true) + Utils.check((int_array is Array) == true) + Utils.check(is_instance_of(int_array, TYPE_ARRAY) == true) + Utils.check((int_array is Array[int]) == true) + Utils.check((int_array is Array[float]) == false) + Utils.check((int_array is int) == false) + Utils.check(is_instance_of(int_array, TYPE_INT) == false) var const_int_array: Variant = [] as Array[int] - assert((const_int_array is Variant) == true) - assert((const_int_array is Array) == true) - assert(is_instance_of(const_int_array, TYPE_ARRAY) == true) - assert((const_int_array is Array[int]) == true) - assert((const_int_array is Array[float]) == false) - assert((const_int_array is int) == false) - assert(is_instance_of(const_int_array, TYPE_INT) == false) + Utils.check((const_int_array is Variant) == true) + Utils.check((const_int_array is Array) == true) + Utils.check(is_instance_of(const_int_array, TYPE_ARRAY) == true) + Utils.check((const_int_array is Array[int]) == true) + Utils.check((const_int_array is Array[float]) == false) + Utils.check((const_int_array is int) == false) + Utils.check(is_instance_of(const_int_array, TYPE_INT) == false) var b_array: Variant = [] as Array[B] - assert((b_array is Variant) == true) - assert((b_array is Array) == true) - assert(is_instance_of(b_array, TYPE_ARRAY) == true) - assert((b_array is Array[B]) == true) - assert((b_array is Array[A]) == false) - assert((b_array is Array[int]) == false) - assert((b_array is int) == false) - assert(is_instance_of(b_array, TYPE_INT) == false) + Utils.check((b_array is Variant) == true) + Utils.check((b_array is Array) == true) + Utils.check(is_instance_of(b_array, TYPE_ARRAY) == true) + Utils.check((b_array is Array[B]) == true) + Utils.check((b_array is Array[A]) == false) + Utils.check((b_array is Array[int]) == false) + Utils.check((b_array is int) == false) + Utils.check(is_instance_of(b_array, TYPE_INT) == false) var const_b_array: Variant = [] as Array[B] - assert((const_b_array is Variant) == true) - assert((const_b_array is Array) == true) - assert(is_instance_of(const_b_array, TYPE_ARRAY) == true) - assert((const_b_array is Array[B]) == true) - assert((const_b_array is Array[A]) == false) - assert((const_b_array is Array[int]) == false) - assert((const_b_array is int) == false) - assert(is_instance_of(const_b_array, TYPE_INT) == false) + Utils.check((const_b_array is Variant) == true) + Utils.check((const_b_array is Array) == true) + Utils.check(is_instance_of(const_b_array, TYPE_ARRAY) == true) + Utils.check((const_b_array is Array[B]) == true) + Utils.check((const_b_array is Array[A]) == false) + Utils.check((const_b_array is Array[int]) == false) + Utils.check((const_b_array is int) == false) + Utils.check(is_instance_of(const_b_array, TYPE_INT) == false) var native: Variant = RefCounted.new() - assert((native is Variant) == true) - assert((native is Object) == true) - assert(is_instance_of(native, TYPE_OBJECT) == true) - assert(is_instance_of(native, Object) == true) - assert((native is RefCounted) == true) - assert(is_instance_of(native, RefCounted) == true) - assert((native is Node) == false) - assert(is_instance_of(native, Node) == false) - assert((native is int) == false) - assert(is_instance_of(native, TYPE_INT) == false) + Utils.check((native is Variant) == true) + Utils.check((native is Object) == true) + Utils.check(is_instance_of(native, TYPE_OBJECT) == true) + Utils.check(is_instance_of(native, Object) == true) + Utils.check((native is RefCounted) == true) + Utils.check(is_instance_of(native, RefCounted) == true) + Utils.check((native is Node) == false) + Utils.check(is_instance_of(native, Node) == false) + Utils.check((native is int) == false) + Utils.check(is_instance_of(native, TYPE_INT) == false) var a_script: Variant = A.new() - assert((a_script is Variant) == true) - assert((a_script is Object) == true) - assert(is_instance_of(a_script, TYPE_OBJECT) == true) - assert(is_instance_of(a_script, Object) == true) - assert((a_script is RefCounted) == true) - assert(is_instance_of(a_script, RefCounted) == true) - assert((a_script is A) == true) - assert(is_instance_of(a_script, A) == true) - assert((a_script is B) == false) - assert(is_instance_of(a_script, B) == false) - assert((a_script is Node) == false) - assert(is_instance_of(a_script, Node) == false) - assert((a_script is int) == false) - assert(is_instance_of(a_script, TYPE_INT) == false) + Utils.check((a_script is Variant) == true) + Utils.check((a_script is Object) == true) + Utils.check(is_instance_of(a_script, TYPE_OBJECT) == true) + Utils.check(is_instance_of(a_script, Object) == true) + Utils.check((a_script is RefCounted) == true) + Utils.check(is_instance_of(a_script, RefCounted) == true) + Utils.check((a_script is A) == true) + Utils.check(is_instance_of(a_script, A) == true) + Utils.check((a_script is B) == false) + Utils.check(is_instance_of(a_script, B) == false) + Utils.check((a_script is Node) == false) + Utils.check(is_instance_of(a_script, Node) == false) + Utils.check((a_script is int) == false) + Utils.check(is_instance_of(a_script, TYPE_INT) == false) var b_script: Variant = B.new() - assert((b_script is Variant) == true) - assert((b_script is Object) == true) - assert(is_instance_of(b_script, TYPE_OBJECT) == true) - assert(is_instance_of(b_script, Object) == true) - assert((b_script is RefCounted) == true) - assert(is_instance_of(b_script, RefCounted) == true) - assert((b_script is A) == true) - assert(is_instance_of(b_script, A) == true) - assert((b_script is B) == true) - assert(is_instance_of(b_script, B) == true) - assert((b_script is Node) == false) - assert(is_instance_of(b_script, Node) == false) - assert((b_script is int) == false) - assert(is_instance_of(b_script, TYPE_INT) == false) + Utils.check((b_script is Variant) == true) + Utils.check((b_script is Object) == true) + Utils.check(is_instance_of(b_script, TYPE_OBJECT) == true) + Utils.check(is_instance_of(b_script, Object) == true) + Utils.check((b_script is RefCounted) == true) + Utils.check(is_instance_of(b_script, RefCounted) == true) + Utils.check((b_script is A) == true) + Utils.check(is_instance_of(b_script, A) == true) + Utils.check((b_script is B) == true) + Utils.check(is_instance_of(b_script, B) == true) + Utils.check((b_script is Node) == false) + Utils.check(is_instance_of(b_script, Node) == false) + Utils.check((b_script is int) == false) + Utils.check(is_instance_of(b_script, TYPE_INT) == false) var var_null: Variant = null - assert((var_null is Variant) == true) - assert((var_null is int) == false) - assert(is_instance_of(var_null, TYPE_INT) == false) - assert((var_null is Object) == false) - assert(is_instance_of(var_null, TYPE_OBJECT) == false) - assert((var_null is RefCounted) == false) - assert(is_instance_of(var_null, RefCounted) == false) - assert((var_null is A) == false) - assert(is_instance_of(var_null, A) == false) + Utils.check((var_null is Variant) == true) + Utils.check((var_null is int) == false) + Utils.check(is_instance_of(var_null, TYPE_INT) == false) + Utils.check((var_null is Object) == false) + Utils.check(is_instance_of(var_null, TYPE_OBJECT) == false) + Utils.check((var_null is RefCounted) == false) + Utils.check(is_instance_of(var_null, RefCounted) == false) + Utils.check((var_null is A) == false) + Utils.check(is_instance_of(var_null, A) == false) const const_null: Variant = null - assert((const_null is Variant) == true) - assert((const_null is int) == false) - assert(is_instance_of(const_null, TYPE_INT) == false) - assert((const_null is Object) == false) - assert(is_instance_of(const_null, TYPE_OBJECT) == false) - assert((const_null is RefCounted) == false) - assert(is_instance_of(const_null, RefCounted) == false) - assert((const_null is A) == false) - assert(is_instance_of(const_null, A) == false) + Utils.check((const_null is Variant) == true) + Utils.check((const_null is int) == false) + Utils.check(is_instance_of(const_null, TYPE_INT) == false) + Utils.check((const_null is Object) == false) + Utils.check(is_instance_of(const_null, TYPE_OBJECT) == false) + Utils.check((const_null is RefCounted) == false) + Utils.check(is_instance_of(const_null, RefCounted) == false) + Utils.check((const_null is A) == false) + Utils.check(is_instance_of(const_null, A) == false) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index b000c82717..fe0274c27b 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -10,206 +10,205 @@ class Members: var two: Array[int] = one func check_passing() -> bool: - assert(str(one) == '[104]') - assert(str(two) == '[104]') + Utils.check(str(one) == '[104]') + Utils.check(str(two) == '[104]') two.push_back(582) - assert(str(one) == '[104, 582]') - assert(str(two) == '[104, 582]') + Utils.check(str(one) == '[104, 582]') + Utils.check(str(two) == '[104, 582]') two = [486] - assert(str(one) == '[104, 582]') - assert(str(two) == '[486]') + Utils.check(str(one) == '[104, 582]') + Utils.check(str(two) == '[486]') return true @warning_ignore("unsafe_method_access") -@warning_ignore("assert_always_true") @warning_ignore("return_value_discarded") func test(): var untyped_basic = [459] - assert(str(untyped_basic) == '[459]') - assert(untyped_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(untyped_basic) == '[459]') + Utils.check(untyped_basic.get_typed_builtin() == TYPE_NIL) var inferred_basic := [366] - assert(str(inferred_basic) == '[366]') - assert(inferred_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(inferred_basic) == '[366]') + Utils.check(inferred_basic.get_typed_builtin() == TYPE_NIL) var typed_basic: Array = [521] - assert(str(typed_basic) == '[521]') - assert(typed_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(typed_basic) == '[521]') + Utils.check(typed_basic.get_typed_builtin() == TYPE_NIL) var empty_floats: Array[float] = [] - assert(str(empty_floats) == '[]') - assert(empty_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(empty_floats) == '[]') + Utils.check(empty_floats.get_typed_builtin() == TYPE_FLOAT) untyped_basic = empty_floats - assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(untyped_basic.get_typed_builtin() == TYPE_FLOAT) inferred_basic = empty_floats - assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(inferred_basic.get_typed_builtin() == TYPE_FLOAT) typed_basic = empty_floats - assert(typed_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(typed_basic.get_typed_builtin() == TYPE_FLOAT) empty_floats.push_back(705.0) untyped_basic.push_back(430.0) inferred_basic.push_back(263.0) typed_basic.push_back(518.0) - assert(str(empty_floats) == '[705, 430, 263, 518]') - assert(str(untyped_basic) == '[705, 430, 263, 518]') - assert(str(inferred_basic) == '[705, 430, 263, 518]') - assert(str(typed_basic) == '[705, 430, 263, 518]') + Utils.check(str(empty_floats) == '[705, 430, 263, 518]') + Utils.check(str(untyped_basic) == '[705, 430, 263, 518]') + Utils.check(str(inferred_basic) == '[705, 430, 263, 518]') + Utils.check(str(typed_basic) == '[705, 430, 263, 518]') const constant_float := 950.0 const constant_int := 170 var typed_float := 954.0 var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]] - assert(str(filled_floats) == '[950, 170, 954, 693]') - assert(filled_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(filled_floats) == '[950, 170, 954, 693]') + Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT) var casted_floats := [empty_floats[2] * 2] as Array[float] - assert(str(casted_floats) == '[526]') - assert(casted_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(casted_floats) == '[526]') + Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT) var returned_floats = (func () -> Array[float]: return [554]).call() - assert(str(returned_floats) == '[554]') - assert(returned_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(returned_floats) == '[554]') + Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT) var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0]) - assert(str(passed_floats) == '[663]') - assert(passed_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(passed_floats) == '[663]') + Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT) var default_floats = (func (floats: Array[float] = [364.0]): return floats).call() - assert(str(default_floats) == '[364]') - assert(default_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(default_floats) == '[364]') + Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT) var typed_int := 556 var converted_floats: Array[float] = [typed_int] converted_floats.push_back(498) - assert(str(converted_floats) == '[556, 498]') - assert(converted_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(converted_floats) == '[556, 498]') + Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT) const constant_basic = [228] - assert(str(constant_basic) == '[228]') - assert(constant_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(constant_basic) == '[228]') + Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL) const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int] - assert(str(constant_floats) == '[552]') - assert(constant_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(constant_floats) == '[552]') + Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT) var source_floats: Array[float] = [999.74] untyped_basic = source_floats var destination_floats: Array[float] = untyped_basic destination_floats[0] -= 0.74 - assert(str(source_floats) == '[999]') - assert(str(untyped_basic) == '[999]') - assert(str(destination_floats) == '[999]') - assert(destination_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(source_floats) == '[999]') + Utils.check(str(untyped_basic) == '[999]') + Utils.check(str(destination_floats) == '[999]') + Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT) var duplicated_floats := empty_floats.duplicate().slice(2, 3) duplicated_floats[0] *= 3 - assert(str(duplicated_floats) == '[789]') - assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(duplicated_floats) == '[789]') + Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) var b_objects: Array[B] = [B.new(), B.new() as A, null] - assert(b_objects.size() == 3) - assert(b_objects.get_typed_builtin() == TYPE_OBJECT) - assert(b_objects.get_typed_script() == B) + Utils.check(b_objects.size() == 3) + Utils.check(b_objects.get_typed_builtin() == TYPE_OBJECT) + Utils.check(b_objects.get_typed_script() == B) var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]] - assert(a_objects.size() == 4) - assert(a_objects.get_typed_builtin() == TYPE_OBJECT) - assert(a_objects.get_typed_script() == A) + Utils.check(a_objects.size() == 4) + Utils.check(a_objects.get_typed_builtin() == TYPE_OBJECT) + Utils.check(a_objects.get_typed_script() == A) var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects) - assert(a_passed == 4) + Utils.check(a_passed == 4) var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects) - assert(b_passed == true) + Utils.check(b_passed == true) var empty_strings: Array[String] = [] var empty_bools: Array[bool] = [] var empty_basic_one := [] var empty_basic_two := [] - assert(empty_strings == empty_bools) - assert(empty_basic_one == empty_basic_two) - assert(empty_strings.hash() == empty_bools.hash()) - assert(empty_basic_one.hash() == empty_basic_two.hash()) + Utils.check(empty_strings == empty_bools) + Utils.check(empty_basic_one == empty_basic_two) + Utils.check(empty_strings.hash() == empty_bools.hash()) + Utils.check(empty_basic_one.hash() == empty_basic_two.hash()) var assign_source: Array[int] = [527] var assign_target: Array[int] = [] assign_target.assign(assign_source) - assert(str(assign_source) == '[527]') - assert(str(assign_target) == '[527]') + Utils.check(str(assign_source) == '[527]') + Utils.check(str(assign_target) == '[527]') assign_source.push_back(657) - assert(str(assign_source) == '[527, 657]') - assert(str(assign_target) == '[527]') + Utils.check(str(assign_source) == '[527, 657]') + Utils.check(str(assign_target) == '[527]') var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one): one.push_back(887) two.push_back(198) - assert(str(one) == '[887, 198]') - assert(str(two) == '[887, 198]') + Utils.check(str(one) == '[887, 198]') + Utils.check(str(two) == '[887, 198]') two = [130] - assert(str(one) == '[887, 198]') - assert(str(two) == '[130]') + Utils.check(str(one) == '[887, 198]') + Utils.check(str(two) == '[130]') return true ).call() - assert(defaults_passed == true) + Utils.check(defaults_passed == true) var members := Members.new() var members_passed := members.check_passing() - assert(members_passed == true) + Utils.check(members_passed == true) var resized_basic: Array = [] resized_basic.resize(1) - assert(typeof(resized_basic[0]) == TYPE_NIL) - assert(resized_basic[0] == null) + Utils.check(typeof(resized_basic[0]) == TYPE_NIL) + Utils.check(resized_basic[0] == null) var resized_ints: Array[int] = [] resized_ints.resize(1) - assert(typeof(resized_ints[0]) == TYPE_INT) - assert(resized_ints[0] == 0) + Utils.check(typeof(resized_ints[0]) == TYPE_INT) + Utils.check(resized_ints[0] == 0) var resized_arrays: Array[Array] = [] resized_arrays.resize(1) - assert(typeof(resized_arrays[0]) == TYPE_ARRAY) + Utils.check(typeof(resized_arrays[0]) == TYPE_ARRAY) resized_arrays[0].resize(1) resized_arrays[0][0] = 523 - assert(str(resized_arrays) == '[[523]]') + Utils.check(str(resized_arrays) == '[[523]]') var resized_objects: Array[Object] = [] resized_objects.resize(1) - assert(typeof(resized_objects[0]) == TYPE_NIL) - assert(resized_objects[0] == null) + Utils.check(typeof(resized_objects[0]) == TYPE_NIL) + Utils.check(resized_objects[0] == null) var typed_enums: Array[E] = [] typed_enums.resize(1) - assert(str(typed_enums) == '[0]') + Utils.check(str(typed_enums) == '[0]') typed_enums[0] = E.E0 - assert(str(typed_enums) == '[391]') - assert(typed_enums.get_typed_builtin() == TYPE_INT) + Utils.check(str(typed_enums) == '[391]') + Utils.check(typed_enums.get_typed_builtin() == TYPE_INT) const const_enums: Array[E] = [] - assert(const_enums.get_typed_builtin() == TYPE_INT) - assert(const_enums.get_typed_class_name() == &'') + Utils.check(const_enums.get_typed_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_class_name() == &'') var a := A.new() var typed_natives: Array[RefCounted] = [a] var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A) - assert(typed_scripts[0] == a) + Utils.check(typed_scripts[0] == a) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd new file mode 100644 index 0000000000..65f5e7da07 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd @@ -0,0 +1,20 @@ +func print_untyped(dictionary = { 0: 1 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func print_inferred(dictionary := { 2: 3 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func print_typed(dictionary: Dictionary[int, int] = { 4: 5 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func test(): + print_untyped() + print_inferred() + print_typed() + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out new file mode 100644 index 0000000000..c31561bee3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out @@ -0,0 +1,11 @@ +GDTEST_OK +{ 0: 1 } +0 +0 +{ 2: 3 } +0 +0 +{ 4: 5 } +2 +2 +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd new file mode 100644 index 0000000000..0aa3de2c4a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd @@ -0,0 +1,4 @@ +func test(): + var dict := { 0: 0 } + dict[0] = 1 + print(dict[0]) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out new file mode 100644 index 0000000000..a7f1357bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out @@ -0,0 +1,2 @@ +GDTEST_OK +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd new file mode 100644 index 0000000000..9d3fffd1de --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd @@ -0,0 +1,214 @@ +class A: pass +class B extends A: pass + +enum E { E0 = 391, E1 = 193 } + +func floats_identity(floats: Dictionary[float, float]): return floats + +class Members: + var one: Dictionary[int, int] = { 104: 401 } + var two: Dictionary[int, int] = one + + func check_passing() -> bool: + Utils.check(str(one) == '{ 104: 401 }') + Utils.check(str(two) == '{ 104: 401 }') + two[582] = 285 + Utils.check(str(one) == '{ 104: 401, 582: 285 }') + Utils.check(str(two) == '{ 104: 401, 582: 285 }') + two = { 486: 684 } + Utils.check(str(one) == '{ 104: 401, 582: 285 }') + Utils.check(str(two) == '{ 486: 684 }') + return true + + +@warning_ignore("unsafe_method_access") +@warning_ignore("assert_always_true") +@warning_ignore("return_value_discarded") +func test(): + var untyped_basic = { 459: 954 } + Utils.check(str(untyped_basic) == '{ 459: 954 }') + Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_NIL) + + var inferred_basic := { 366: 663 } + Utils.check(str(inferred_basic) == '{ 366: 663 }') + Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_NIL) + + var typed_basic: Dictionary = { 521: 125 } + Utils.check(str(typed_basic) == '{ 521: 125 }') + Utils.check(typed_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(typed_basic.get_typed_value_builtin() == TYPE_NIL) + + + var empty_floats: Dictionary[float, float] = {} + Utils.check(str(empty_floats) == '{ }') + Utils.check(empty_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(empty_floats.get_typed_value_builtin() == TYPE_FLOAT) + + untyped_basic = empty_floats + Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_FLOAT) + + inferred_basic = empty_floats + Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_FLOAT) + + typed_basic = empty_floats + Utils.check(typed_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(typed_basic.get_typed_value_builtin() == TYPE_FLOAT) + + empty_floats[705.0] = 507.0 + untyped_basic[430.0] = 34.0 + inferred_basic[263.0] = 362.0 + typed_basic[518.0] = 815.0 + Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + + + const constant_float := 950.0 + const constant_int := 170 + var typed_float := 954.0 + var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] } + Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }') + Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float] + Utils.check(str(casted_floats) == '{ 724: 181 }') + Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call() + Utils.check(str(returned_floats) == '{ 554: 455 }') + Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 }) + Utils.check(str(passed_floats) == '{ 663: 366 }') + Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call() + Utils.check(str(default_floats) == '{ 364: 463 }') + Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var typed_int := 556 + var converted_floats: Dictionary[float, float] = { typed_int: typed_int } + converted_floats[498.0] = 894 + Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }') + Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + const constant_basic = { 228: 822 } + Utils.check(str(constant_basic) == '{ 228: 822 }') + Utils.check(constant_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL) + + const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int } + Utils.check(str(constant_floats) == '{ -42: 1942 }') + Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var source_floats: Dictionary[float, float] = { 999.74: 47.999 } + untyped_basic = source_floats + var destination_floats: Dictionary[float, float] = untyped_basic + destination_floats[999.74] -= 0.999 + Utils.check(str(source_floats) == '{ 999.74: 47 }') + Utils.check(str(untyped_basic) == '{ 999.74: 47 }') + Utils.check(str(destination_floats) == '{ 999.74: 47 }') + Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var duplicated_floats := empty_floats.duplicate() + duplicated_floats.erase(705.0) + duplicated_floats.erase(430.0) + duplicated_floats.erase(518.0) + duplicated_floats[263.0] *= 3 + Utils.check(str(duplicated_floats) == '{ 263: 1086 }') + Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var b_objects: Dictionary[int, B] = { 0: B.new(), 1: B.new() as A, 2: null } + Utils.check(b_objects.size() == 3) + Utils.check(b_objects.get_typed_value_builtin() == TYPE_OBJECT) + Utils.check(b_objects.get_typed_value_script() == B) + + var a_objects: Dictionary[int, A] = { 0: A.new(), 1: B.new(), 2: null, 3: b_objects[0] } + Utils.check(a_objects.size() == 4) + Utils.check(a_objects.get_typed_value_builtin() == TYPE_OBJECT) + Utils.check(a_objects.get_typed_value_script() == A) + + var a_passed = (func check_a_passing(p_objects: Dictionary[int, A]): return p_objects.size()).call(a_objects) + Utils.check(a_passed == 4) + + var b_passed = (func check_b_passing(basic: Dictionary): return basic[0] != null).call(b_objects) + Utils.check(b_passed == true) + + + var empty_strings: Dictionary[String, String] = {} + var empty_bools: Dictionary[bool, bool] = {} + var empty_basic_one := {} + var empty_basic_two := {} + Utils.check(empty_strings == empty_bools) + Utils.check(empty_basic_one == empty_basic_two) + Utils.check(empty_strings.hash() == empty_bools.hash()) + Utils.check(empty_basic_one.hash() == empty_basic_two.hash()) + + + var assign_source: Dictionary[int, int] = { 527: 725 } + var assign_target: Dictionary[int, int] = {} + assign_target.assign(assign_source) + Utils.check(str(assign_source) == '{ 527: 725 }') + Utils.check(str(assign_target) == '{ 527: 725 }') + assign_source[657] = 756 + Utils.check(str(assign_source) == '{ 527: 725, 657: 756 }') + Utils.check(str(assign_target) == '{ 527: 725 }') + + + var defaults_passed = (func check_defaults_passing(one: Dictionary[int, int] = {}, two := one): + one[887] = 788 + two[198] = 891 + Utils.check(str(one) == '{ 887: 788, 198: 891 }') + Utils.check(str(two) == '{ 887: 788, 198: 891 }') + two = {130: 31} + Utils.check(str(one) == '{ 887: 788, 198: 891 }') + Utils.check(str(two) == '{ 130: 31 }') + return true + ).call() + Utils.check(defaults_passed == true) + + + var members := Members.new() + var members_passed := members.check_passing() + Utils.check(members_passed == true) + + + var typed_enums: Dictionary[E, E] = {} + typed_enums[E.E0] = E.E1 + Utils.check(str(typed_enums) == '{ 391: 193 }') + Utils.check(typed_enums.get_typed_key_builtin() == TYPE_INT) + Utils.check(typed_enums.get_typed_value_builtin() == TYPE_INT) + + const const_enums: Dictionary[E, E] = {} + Utils.check(const_enums.get_typed_key_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_key_class_name() == &'') + Utils.check(const_enums.get_typed_value_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_value_class_name() == &'') + + + var a := A.new() + var b := B.new() + var typed_natives: Dictionary[RefCounted, RefCounted] = { a: b } + var typed_scripts = Dictionary(typed_natives, TYPE_OBJECT, "RefCounted", A, TYPE_OBJECT, "RefCounted", B) + Utils.check(typed_scripts[a] == b) + + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd new file mode 100644 index 0000000000..f4a23ade14 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd @@ -0,0 +1,9 @@ +class Inner: + var prop = "Inner" + +var dict: Dictionary[int, Inner] = { 0: Inner.new() } + + +func test(): + var element: Inner = dict[0] + print(element.prop) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out new file mode 100644 index 0000000000..8f250d2632 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out @@ -0,0 +1,2 @@ +GDTEST_OK +Inner diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.gd new file mode 100644 index 0000000000..9e1041db54 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.gd @@ -0,0 +1,23 @@ +var member := 1 + +func test(): + var number := 1 + var string := "1" + var vector := Vector2i(1, 0) + var array_assign := [1] + var array_index := [1] + var dictionary := { x = 0 } + + var lambda := func (): + member = 2 # Member variable, not captured. + number = 2 # Local variable, captured. + string += "2" # Test compound assignment operator. + vector.x = 2 # Test subscript assignment. + array_assign = [2] # Pass-by-reference type, reassignment. + array_index[0] = 2 # Pass-by-reference type, index access. + dictionary.x = 2 # Pass-by-reference type, attribute access. + + prints("lambda", member, number, string, vector, array_assign, array_index, dictionary) + + lambda.call() + prints("outer", member, number, string, vector, array_assign, array_index, dictionary) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out new file mode 100644 index 0000000000..e6a1cab77d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out @@ -0,0 +1,19 @@ +GDTEST_OK +>> WARNING +>> Line: 13 +>> CONFUSABLE_CAPTURE_REASSIGNMENT +>> Reassigning lambda capture does not modify the outer local variable "number". +>> WARNING +>> Line: 14 +>> CONFUSABLE_CAPTURE_REASSIGNMENT +>> Reassigning lambda capture does not modify the outer local variable "string". +>> WARNING +>> Line: 15 +>> CONFUSABLE_CAPTURE_REASSIGNMENT +>> Reassigning lambda capture does not modify the outer local variable "vector". +>> WARNING +>> Line: 16 +>> CONFUSABLE_CAPTURE_REASSIGNMENT +>> Reassigning lambda capture does not modify the outer local variable "array_assign". +lambda 2 2 12 (2, 0) [2] [2] { &"x": 2 } +outer 2 1 1 (1, 0) [1] [2] { &"x": 2 } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd index 13e3edf93f..f3a8661acf 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd @@ -7,3 +7,17 @@ var has_no_zero: HasNoZero # Warning, because there is no `0` in the enum. func test(): print(has_zero) print(has_no_zero) + + +# GH-94634. A parameter is either mandatory or has a default value. +func test_no_exec(param: HasNoZero) -> void: + print(param) + + # Loop iterator always has a value. + for i: HasNoZero in HasNoZero.values(): + print(i) + + match param: + # Pattern bind always has a value. + var x: + print(x) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd new file mode 100644 index 0000000000..95d497c3f3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd @@ -0,0 +1,7 @@ +extends "./non_tool_extends_tool.notest.gd" + +class InnerClass extends "./non_tool_extends_tool.notest.gd": + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd new file mode 100644 index 0000000000..07427846d1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd @@ -0,0 +1 @@ +@tool diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out new file mode 100644 index 0000000000..f65caf5222 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 1 +>> MISSING_TOOL +>> The base class script has the "@tool" annotation, but this script does not have it. +>> WARNING +>> Line: 3 +>> MISSING_TOOL +>> The base class script has the "@tool" annotation, but this script does not have it. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd new file mode 100644 index 0000000000..a452307d99 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd @@ -0,0 +1,9 @@ +@warning_ignore("missing_tool") +extends "./non_tool_extends_tool.notest.gd" + +@warning_ignore("missing_tool") +class InnerClass extends "./non_tool_extends_tool.notest.gd": + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd index d937dfdcfe..37f118dc5d 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd @@ -1,12 +1,29 @@ -signal s1() -signal s2() -signal s3() +# Doesn't produce the warning: +signal used_as_first_class_signal() +signal used_with_signal_constructor() +signal used_with_signal_emit() +signal used_with_object_emit_signal() +signal used_with_object_connect() +signal used_with_object_disconnect() +signal used_with_self_prefix() + +# Produce the warning: +signal used_with_dynamic_name() +signal just_unused() @warning_ignore("unused_signal") -signal s4() +signal unused_but_ignored() func no_exec(): - s1.emit() - print(s2) + print(used_as_first_class_signal) + print(Signal(self, "used_with_signal_constructor")) + used_with_signal_emit.emit() + print(emit_signal("used_with_object_emit_signal")) + print(connect("used_with_object_connect", Callable())) + disconnect("used_with_object_disconnect", Callable()) + print(self.emit_signal("used_with_self_prefix")) + + var dynamic_name := "used_with_dynamic_name" + print(emit_signal(dynamic_name)) func test(): pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out index ff57017830..39ddf91c76 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out @@ -1,5 +1,9 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 11 >> UNUSED_SIGNAL ->> The signal "s3" is declared but never explicitly used in the class. +>> The signal "used_with_dynamic_name" is declared but never explicitly used in the class. +>> WARNING +>> Line: 12 +>> UNUSED_SIGNAL +>> The signal "just_unused" is declared but never explicitly used in the class. diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween.cfg new file mode 100644 index 0000000000..a8f26d83f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween.cfg @@ -0,0 +1,11 @@ +[input] +add_node_path_literals=true +[output] +include=[ + {"insert_text": "^\"property_of_a\""}, + {"insert_text": "^\"name\""}, +] +exclude=[ + {"insert_text": "\"property_of_a\""}, + {"insert_text": "\"name\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween.gd new file mode 100644 index 0000000000..bfdb5c7995 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween.gd @@ -0,0 +1,8 @@ +extends Node + +const A = preload("res://completion/class_a.notest.gd") + +func _ready() -> void: + var a := A.new() + var tween := get_tree().create_tween() + tween.tween_property(a, ➡) diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event.cfg new file mode 100644 index 0000000000..309fa8ed38 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event.cfg @@ -0,0 +1,9 @@ +[input] +add_string_name_literals=true +[output] +include=[ + {"insert_text": "&\"test_input_action\""}, +] +exclude=[ + {"insert_text": "\"test_input_action\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event.gd new file mode 100644 index 0000000000..9b325e632d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event.gd @@ -0,0 +1,3 @@ +func _input(event: InputEvent) -> void: + event.is_action_pressed(➡) + pass diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg new file mode 100644 index 0000000000..be9bd510e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + {"insert_text": "\"property_of_a\""}, + {"insert_text": "\"name\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd new file mode 100644 index 0000000000..a8e04a62a7 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd @@ -0,0 +1,8 @@ +extends Node + +const A = preload ("res://completion/class_a.notest.gd") + +func _ready() -> void: + var a := A.new() + var tween := get_tree().create_tween() + tween.tween_property(a, "➡") diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_node_path_tween.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_node_path_tween.cfg new file mode 100644 index 0000000000..45d0ad0f3b --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_node_path_tween.cfg @@ -0,0 +1,11 @@ +[input] +add_node_path_literals=false +[output] +include=[ + {"insert_text": "\"property_of_a\""}, + {"insert_text": "\"name\""}, +] +exclude=[ + {"insert_text": "^\"property_of_a\""}, + {"insert_text": "^\"name\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_node_path_tween.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_node_path_tween.gd new file mode 100644 index 0000000000..bfdb5c7995 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_node_path_tween.gd @@ -0,0 +1,8 @@ +extends Node + +const A = preload("res://completion/class_a.notest.gd") + +func _ready() -> void: + var a := A.new() + var tween := get_tree().create_tween() + tween.tween_property(a, ➡) diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_string_name_input_event.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_string_name_input_event.cfg new file mode 100644 index 0000000000..7a388d54e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_string_name_input_event.cfg @@ -0,0 +1,9 @@ +[input] +add_string_name_literals=false +[output] +include=[ + {"insert_text": "\"test_input_action\""}, +] +exclude=[ + {"insert_text": "&\"test_input_action\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_string_name_input_event.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_string_name_input_event.gd new file mode 100644 index 0000000000..9b325e632d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/dont_add_string_name_input_event.gd @@ -0,0 +1,3 @@ +func _input(event: InputEvent) -> void: + event.is_action_pressed(➡) + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg new file mode 100644 index 0000000000..e4759ac76b --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "new"}, + {"display": "SIZE_EXPAND"}, + {"display": "SIZE_EXPAND_FILL"}, + {"display": "SIZE_FILL"}, + {"display": "SIZE_SHRINK_BEGIN"}, + {"display": "SIZE_SHRINK_CENTER"}, + {"display": "SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd new file mode 100644 index 0000000000..4aeafb2e0a --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Control.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg new file mode 100644 index 0000000000..e4759ac76b --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "new"}, + {"display": "SIZE_EXPAND"}, + {"display": "SIZE_EXPAND_FILL"}, + {"display": "SIZE_FILL"}, + {"display": "SIZE_SHRINK_BEGIN"}, + {"display": "SIZE_SHRINK_CENTER"}, + {"display": "SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd new file mode 100644 index 0000000000..47e9bd5a67 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Control.SIZE➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg new file mode 100644 index 0000000000..5cc4ec5fd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd new file mode 100644 index 0000000000..5c96720bd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Con➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg new file mode 100644 index 0000000000..5cc4ec5fd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd new file mode 100644 index 0000000000..d8bf13e51d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = ➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg new file mode 100644 index 0000000000..5f08f9c265 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg @@ -0,0 +1,25 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: identifiers.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd new file mode 100644 index 0000000000..91488c25aa --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd @@ -0,0 +1,18 @@ +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int + print(t➡) diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg new file mode 100644 index 0000000000..5f08f9c265 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg @@ -0,0 +1,25 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: identifiers.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd new file mode 100644 index 0000000000..a2f5b7bc23 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd @@ -0,0 +1,18 @@ +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int + t➡ diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg new file mode 100644 index 0000000000..5f08f9c265 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg @@ -0,0 +1,25 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: identifiers.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd new file mode 100644 index 0000000000..fed0b869c4 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd @@ -0,0 +1,22 @@ +# godotengine/godot#92226 +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int + print(t➡ + + if true: + pass diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg new file mode 100644 index 0000000000..462846c9b2 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg @@ -0,0 +1,26 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +exclude=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + {"display": "add_child"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: no_completion_in_string.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd new file mode 100644 index 0000000000..da52af9fe3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd @@ -0,0 +1,19 @@ +# godotengine/godot#62945 +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int + var a = "➡" diff --git a/modules/gdscript/tests/scripts/completion/common/self.cfg b/modules/gdscript/tests/scripts/completion/common/self.cfg new file mode 100644 index 0000000000..871a404e3a --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/self.cfg @@ -0,0 +1,21 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: self.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/self.gd b/modules/gdscript/tests/scripts/completion/common/self.gd new file mode 100644 index 0000000000..ed181af0c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/self.gd @@ -0,0 +1,17 @@ +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(): + self.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/filter/organized_export.cfg b/modules/gdscript/tests/scripts/completion/filter/organized_export.cfg new file mode 100644 index 0000000000..961a9ea58d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/filter/organized_export.cfg @@ -0,0 +1,6 @@ +[output] +exclude=[ + {"display": "Test Category"}, + {"display": "Test Group"}, + {"display": "Test Subgroup"}, +] diff --git a/modules/gdscript/tests/scripts/completion/filter/organized_export.gd b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd new file mode 100644 index 0000000000..9fa9618cee --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd @@ -0,0 +1,9 @@ +extends CPUParticles2D + +@export_category("Test Category") +@export_group("Test Group") +@export_subgroup("Test Subgroup") + +func _init(): + t➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/filter/usage_internal.cfg b/modules/gdscript/tests/scripts/completion/filter/usage_internal.cfg new file mode 100644 index 0000000000..8c5bff5eac --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/filter/usage_internal.cfg @@ -0,0 +1,4 @@ +[output] +exclude=[ + {"display": "messages"}, +] diff --git a/modules/gdscript/tests/scripts/completion/filter/usage_internal.gd b/modules/gdscript/tests/scripts/completion/filter/usage_internal.gd new file mode 100644 index 0000000000..484c1c0d10 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/filter/usage_internal.gd @@ -0,0 +1,3 @@ +func test(): + var trans = Translation.new() + trans.➡ diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg new file mode 100644 index 0000000000..36c150f6e3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg @@ -0,0 +1,9 @@ +[input] +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + {"display": "%UniqueA"}, +] +exclude=[ + {"display": "\"%UniqueA\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd new file mode 100644 index 0000000000..def050e938 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd @@ -0,0 +1,5 @@ +extends Node + +func a(): + $➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg index ae7d34d87d..ae7d34d87d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd index 7710c2d13b..7710c2d13b 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg index 9c580b711d..9c580b711d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd index 6b29bf5526..6b29bf5526 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg index 446198dd35..446198dd35 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd index 7710c2d13b..7710c2d13b 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg index ae7d34d87d..ae7d34d87d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd index 97b288334e..97b288334e 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg index 9c580b711d..9c580b711d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd index 402fd1d275..402fd1d275 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg index 446198dd35..446198dd35 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd index 97b288334e..97b288334e 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/index/array_type.cfg b/modules/gdscript/tests/scripts/completion/index/array_type.cfg new file mode 100644 index 0000000000..5cd5565d00 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_type.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "append"}, + {"display": "\"append\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/array_type.gd b/modules/gdscript/tests/scripts/completion/index/array_type.gd new file mode 100644 index 0000000000..e0a15da556 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_type.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var array: Array + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/array_value.cfg b/modules/gdscript/tests/scripts/completion/index/array_value.cfg new file mode 100644 index 0000000000..5cd5565d00 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_value.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "append"}, + {"display": "\"append\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/array_value.gd b/modules/gdscript/tests/scripts/completion/index/array_value.gd new file mode 100644 index 0000000000..17451725bc --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_value.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var array = [] + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg new file mode 100644 index 0000000000..ecea284b5d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg @@ -0,0 +1,11 @@ +[output] +include=[ + {"display": "\"key1\""}, + {"display": "\"key2\""}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, + {"display": "key1"}, + {"display": "key2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd new file mode 100644 index 0000000000..06498c57a6 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd @@ -0,0 +1,13 @@ +extends Node + +var outer + +const dict = { + "key1": "value", + "key2": null, +} + +func _ready() -> void: + var inner + + dict["➡"] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg b/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg new file mode 100644 index 0000000000..cddf7b8cc8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd b/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd new file mode 100644 index 0000000000..b02c62eea5 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var dict: Dictionary + + dict[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg b/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg new file mode 100644 index 0000000000..cddf7b8cc8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd b/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd new file mode 100644 index 0000000000..60bf391716 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var dict = {} + + dict[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg new file mode 100644 index 0000000000..ecea284b5d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg @@ -0,0 +1,11 @@ +[output] +include=[ + {"display": "\"key1\""}, + {"display": "\"key2\""}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, + {"display": "key1"}, + {"display": "key2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd new file mode 100644 index 0000000000..2220cdcc59 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd @@ -0,0 +1,13 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var dict: Dictionary = { + "key1": "value", + "key2": null, + } + + dict["➡"] diff --git a/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg new file mode 100644 index 0000000000..8da525bff8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg @@ -0,0 +1,9 @@ +[output] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, + {"display": "key1"}, + {"display": "key2"}, + {"display": "\"key1\""}, + {"display": "\"key2\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd new file mode 100644 index 0000000000..ba8d7f76fd --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd @@ -0,0 +1,13 @@ +extends Node + +var outer + +var dict = { + "key1": "value", + "key2": null, +} + +func _ready() -> void: + var inner + + dict["➡"] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg b/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg new file mode 100644 index 0000000000..1173043f94 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_local.gd b/modules/gdscript/tests/scripts/completion/index/untyped_local.gd new file mode 100644 index 0000000000..1a1157af02 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_local.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var array + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg b/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg new file mode 100644 index 0000000000..1173043f94 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_property.gd b/modules/gdscript/tests/scripts/completion/index/untyped_property.gd new file mode 100644 index 0000000000..9fa23da504 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_property.gd @@ -0,0 +1,9 @@ +extends Node + +var outer +var array + +func _ready() -> void: + var inner + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/types/local/infered.cfg b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/local/infered.gd b/modules/gdscript/tests/scripts/completion/types/local/infered.gd new file mode 100644 index 0000000000..f003c366a4 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/infered.gd @@ -0,0 +1,8 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +func a(): + var test := A.new() + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/local/no_type.cfg b/modules/gdscript/tests/scripts/completion/types/local/no_type.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/no_type.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/local/no_type.gd b/modules/gdscript/tests/scripts/completion/types/local/no_type.gd new file mode 100644 index 0000000000..f6b5ae3aef --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/no_type.gd @@ -0,0 +1,8 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +func a(): + var test = A.new() + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/local/typehint.cfg b/modules/gdscript/tests/scripts/completion/types/local/typehint.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/typehint.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/local/typehint.gd b/modules/gdscript/tests/scripts/completion/types/local/typehint.gd new file mode 100644 index 0000000000..24bcfc04fc --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/typehint.gd @@ -0,0 +1,8 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +func a(): + var test: A + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/local/typehint_broad.cfg b/modules/gdscript/tests/scripts/completion/types/local/typehint_broad.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/typehint_broad.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/local/typehint_broad.gd b/modules/gdscript/tests/scripts/completion/types/local/typehint_broad.gd new file mode 100644 index 0000000000..88b4812c30 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/typehint_broad.gd @@ -0,0 +1,8 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +func a(): + var test: Node = A.new() + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/local/typehint_incompatible.cfg b/modules/gdscript/tests/scripts/completion/types/local/typehint_incompatible.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/typehint_incompatible.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/local/typehint_incompatible.gd b/modules/gdscript/tests/scripts/completion/types/local/typehint_incompatible.gd new file mode 100644 index 0000000000..8e226546f3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/local/typehint_incompatible.gd @@ -0,0 +1,8 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +func a(): + var test: A = Node.new() + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/member/infered.cfg b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/member/infered.gd b/modules/gdscript/tests/scripts/completion/types/member/infered.gd new file mode 100644 index 0000000000..069abd7891 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/infered.gd @@ -0,0 +1,9 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +var test := A.new() + +func a(): + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/member/no_type.cfg b/modules/gdscript/tests/scripts/completion/types/member/no_type.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/no_type.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/member/no_type.gd b/modules/gdscript/tests/scripts/completion/types/member/no_type.gd new file mode 100644 index 0000000000..9bb9549e97 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/no_type.gd @@ -0,0 +1,9 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +var test = A.new() + +func a(): + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/member/typehint.cfg b/modules/gdscript/tests/scripts/completion/types/member/typehint.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/typehint.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/member/typehint.gd b/modules/gdscript/tests/scripts/completion/types/member/typehint.gd new file mode 100644 index 0000000000..7763a2e898 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/typehint.gd @@ -0,0 +1,9 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +var test: A + +func a(): + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/member/typehint_broad.cfg b/modules/gdscript/tests/scripts/completion/types/member/typehint_broad.cfg new file mode 100644 index 0000000000..81401316ec --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/typehint_broad.cfg @@ -0,0 +1,13 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, +] +exclude=[ + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/member/typehint_broad.gd b/modules/gdscript/tests/scripts/completion/types/member/typehint_broad.gd new file mode 100644 index 0000000000..a8506705a1 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/typehint_broad.gd @@ -0,0 +1,9 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +var test: Node = A.new() + +func a(): + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/types/member/typehint_incompatible.cfg b/modules/gdscript/tests/scripts/completion/types/member/typehint_incompatible.cfg new file mode 100644 index 0000000000..8b68d51a89 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/typehint_incompatible.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, +] diff --git a/modules/gdscript/tests/scripts/completion/types/member/typehint_incompatible.gd b/modules/gdscript/tests/scripts/completion/types/member/typehint_incompatible.gd new file mode 100644 index 0000000000..8b5a80cfb3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/types/member/typehint_incompatible.gd @@ -0,0 +1,9 @@ +extends Node + +const A := preload("res://completion/class_a.notest.gd") + +var test: A = Node.new() + +func a(): + test.➡ + pass diff --git a/modules/gdscript/tests/scripts/parser/.editorconfig b/modules/gdscript/tests/scripts/parser/.editorconfig deleted file mode 100644 index fa43b3ad78..0000000000 --- a/modules/gdscript/tests/scripts/parser/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -[*.{gd,out}] -trim_trailing_whitespace = false diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd index 7a7d6d953e..1e5d3fdcad 100644 --- a/modules/gdscript/tests/scripts/parser/features/annotations.gd +++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd @@ -1,7 +1,5 @@ extends Node -const Utils = preload("../../utils.notest.gd") - @export_enum("A", "B", "C") var test_1 @export_enum("A", "B", "C",) var test_2 diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.out b/modules/gdscript/tests/scripts/parser/features/annotations.out index 2ba9dd7496..6516672820 100644 --- a/modules/gdscript/tests/scripts/parser/features/annotations.out +++ b/modules/gdscript/tests/scripts/parser/features/annotations.out @@ -1,25 +1,25 @@ GDTEST_OK var test_1: int = null - hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_2: int = null - hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_3: int = null - hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_4: int = null - hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_5: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_6: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_7: int = 42 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_8: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_9: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_10: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_11: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_12: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd index af24b32322..482d04a63b 100644 --- a/modules/gdscript/tests/scripts/parser/features/class.gd +++ b/modules/gdscript/tests/scripts/parser/features/class.gd @@ -18,8 +18,8 @@ func test(): test_instance.number = 42 var test_sub = TestSub.new() - assert(test_sub.number == 25) # From Test. - assert(test_sub.other_string == "bye") # From TestSub. + Utils.check(test_sub.number == 25) # From Test. + Utils.check(test_sub.other_string == "bye") # From TestSub. var _test_constructor = TestConstructor.new() _test_constructor = TestConstructor.new(500) diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out index 553d40d953..a8ef52583d 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out @@ -1,2 +1,2 @@ GDTEST_OK -{ "a": 1, "b": 2, "with spaces": 3, "2": 4 } +{ &"a": 1, &"b": 2, &"with spaces": 3, &"2": 4 } diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out index cf79845f53..4e404e1d26 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out @@ -1,2 +1,2 @@ GDTEST_OK -{ "hello": { "world": { "is": "beautiful" } } } +{ "hello": { &"world": { "is": "beautiful" } } } diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd index 0d97135a7b..cfda255905 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - @export_dir var test_dir: Array[String] @export_dir var test_dir_packed: PackedStringArray @export_file var test_file: Array[String] diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.out b/modules/gdscript/tests/scripts/parser/features/export_arrays.out index acbf389645..f1522d096f 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_arrays.out +++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.out @@ -1,139 +1,139 @@ GDTEST_OK var test_dir: Array - hint=TYPE_STRING hint_string="String/DIR:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<DIR>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_dir_packed: PackedStringArray - hint=TYPE_STRING hint_string="String/DIR:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<DIR>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_file: Array - hint=TYPE_STRING hint_string="String/FILE:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<FILE>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_file_packed: PackedStringArray - hint=TYPE_STRING hint_string="String/FILE:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<FILE>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_global_dir: Array - hint=TYPE_STRING hint_string="String/GLOBAL_DIR:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<GLOBAL_DIR>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_global_dir_packed: PackedStringArray - hint=TYPE_STRING hint_string="String/GLOBAL_DIR:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<GLOBAL_DIR>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_global_file: Array - hint=TYPE_STRING hint_string="String/GLOBAL_FILE:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<GLOBAL_FILE>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_global_file_packed: PackedStringArray - hint=TYPE_STRING hint_string="String/GLOBAL_FILE:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<GLOBAL_FILE>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag: Array - hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<FLAGS>:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<FLAGS>:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<FLAGS>:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<FLAGS>:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_nav: Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_nav_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_nav_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_nav_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_phys: Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_phys_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_phys_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_phys_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_render: Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_render_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_render_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_2d_render_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_2D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_nav: Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_nav_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_nav_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_nav_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_NAVIGATION>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_phys: Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_phys_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_phys_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_phys_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_PHYSICS>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_render: Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_render_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_render_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_bit_flag_3d_render_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<LAYERS_3D_RENDER>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_multiline: Array - hint=TYPE_STRING hint_string="String/MULTILINE_TEXT:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<MULTILINE_TEXT>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_multiline_packed: PackedStringArray - hint=TYPE_STRING hint_string="String/MULTILINE_TEXT:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<MULTILINE_TEXT>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_placeholder: Array - hint=TYPE_STRING hint_string="String/PLACEHOLDER_TEXT:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<PLACEHOLDER_TEXT>:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_placeholder_packed: PackedStringArray - hint=TYPE_STRING hint_string="String/PLACEHOLDER_TEXT:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<PLACEHOLDER_TEXT>:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int: Array - hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_float_step: Array - hint=TYPE_STRING hint_string="int/RANGE:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_float: Array - hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_float_packed32: PackedFloat32Array - hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_float_packed64: PackedFloat64Array - hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_exp_easing: Array - hint=TYPE_STRING hint_string="float/EXP_EASING:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_exp_easing_packed32: PackedFloat32Array - hint=TYPE_STRING hint_string="float/EXP_EASING:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_exp_easing_packed64: PackedFloat64Array - hint=TYPE_STRING hint_string="float/EXP_EASING:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_node_path: Array - hint=TYPE_STRING hint_string="NodePath/NODE_PATH_VALID_TYPES:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<NodePath>/<NODE_PATH_VALID_TYPES>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_color: Array - hint=TYPE_STRING hint_string="Color/COLOR_NO_ALPHA:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_color_packed: PackedColorArray - hint=TYPE_STRING hint_string="Color/COLOR_NO_ALPHA:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_byte_array: PackedByteArray - hint=TYPE_STRING hint_string="int:int" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_int32_array: PackedInt32Array - hint=TYPE_STRING hint_string="int:int" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_int64_array: PackedInt64Array - hint=TYPE_STRING hint_string="int:int" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_float32_array: PackedFloat32Array - hint=TYPE_STRING hint_string="float:float" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_float64_array: PackedFloat64Array - hint=TYPE_STRING hint_string="float:float" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_color_array: PackedColorArray - hint=TYPE_STRING hint_string="Color:Color" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<Color>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_vector2_array: PackedVector2Array - hint=TYPE_STRING hint_string="Vector2:Vector2" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<Vector2>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_vector3_array: PackedVector3Array - hint=TYPE_STRING hint_string="Vector3:Vector3" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<Vector3>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_vector4_array: PackedVector4Array - hint=TYPE_STRING hint_string="Vector4:Vector4" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<Vector4>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_byte_array: PackedByteArray - hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_int32_array: PackedInt32Array - hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_int64_array: PackedInt64Array - hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_float32_array: PackedFloat32Array - hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_float64_array: PackedFloat64Array - hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_noalpha_weak_packed_color_array: PackedColorArray - hint=TYPE_STRING hint_string="Color/COLOR_NO_ALPHA:" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd index 7f0737f4db..d50f0b2528 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - @export_enum("Red", "Green", "Blue") var test_untyped @export_enum("Red:10", "Green:20", "Blue:30") var test_with_values diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.out b/modules/gdscript/tests/scripts/parser/features/export_enum.out index c87f9b17f0..31d3fa8902 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_enum.out +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.out @@ -1,41 +1,41 @@ GDTEST_OK var test_untyped: int = null - hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_with_values: int = null - hint=ENUM hint_string="Red:10,Green:20,Blue:30" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red:10,Green:20,Blue:30" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_variant: int = null - hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_int: int = 0 - hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_string: String = "" - hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_array_int: Array = Array[int]([]) - hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_array_string: Array = Array[String]([]) - hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_byte_array: PackedByteArray = PackedByteArray() - hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_int32_array: PackedInt32Array = PackedInt32Array() - hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_int64_array: PackedInt64Array = PackedInt64Array() - hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_weak_packed_string_array: PackedStringArray = PackedStringArray() - hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_hard_variant: int = null - hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_hard_int: int = 0 - hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_hard_string: String = "" - hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_hard_array_int: Array = Array[int]([]) - hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_hard_array_string: Array = Array[String]([]) - hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_variant_array_int: Array = Array[int]([]) - hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_variant_packed_int32_array: PackedInt32Array = PackedInt32Array() - hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<int>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_variant_array_string: Array = Array[String]([]) - hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_variant_packed_string_array: PackedStringArray = PackedStringArray() - hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE + hint=TYPE_STRING hint_string="<String>/<ENUM>:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index 2a218774de..1e134d0e0e 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -1,20 +1,51 @@ +class_name ExportVariableTest extends Node -const Utils = preload("../../utils.notest.gd") +const PreloadedGlobalClass = preload("./export_variable_global.notest.gd") +const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd") +# Built-in types. @export var test_weak_int = 1 @export var test_hard_int: int = 2 -@export_storage var test_storage_untyped -@export_storage var test_storage_weak_int = 3 # Property info still `Variant`, unlike `@export`. -@export_storage var test_storage_hard_int: int = 4 @export_range(0, 100) var test_range = 100 @export_range(0, 100, 1) var test_range_step = 101 @export_range(0, 100, 1, "or_greater") var test_range_step_or_greater = 102 @export var test_color: Color @export_color_no_alpha var test_color_no_alpha: Color @export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var test_node_path := ^"hello" -@export var test_node: Node -@export var test_node_array: Array[Node] + +# Enums. +@export var test_side: Side +@export var test_atm: AutoTranslateMode + +# Resources and nodes. +@export var test_image: Image +@export var test_timer: Timer + +# Global custom classes. +@export var test_global_class: ExportVariableTest +@export var test_preloaded_global_class: PreloadedGlobalClass +@export var test_preloaded_unnamed_class: PreloadedUnnamedClass # GH-93168 + +# Arrays. +@export var test_array: Array +@export var test_array_bool: Array[bool] +@export var test_array_array: Array[Array] +@export var test_array_side: Array[Side] +@export var test_array_atm: Array[AutoTranslateMode] +@export var test_array_image: Array[Image] +@export var test_array_timer: Array[Timer] + +# `@export_storage`. +@export_storage var test_storage_untyped +@export_storage var test_storage_weak_int = 3 # Property info still `Variant`, unlike `@export`. +@export_storage var test_storage_hard_int: int = 4 + +# `@export_custom`. +# NOTE: `PROPERTY_USAGE_NIL_IS_VARIANT` flag will be removed. +@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_untyped +@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_weak_int = 5 +@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_hard_int: int = 6 func test(): for property in get_property_list(): diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out index b3f9d0ca9c..d10462bb8d 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.out +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -1,27 +1,57 @@ GDTEST_OK var test_weak_int: int = 1 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_hard_int: int = 2 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE -var test_storage_untyped: Variant = null - hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT -var test_storage_weak_int: Variant = 3 - hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT -var test_storage_hard_int: int = 4 - hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range: int = 100 - hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE + hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_step: int = 101 - hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE + hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_step_or_greater: int = 102 - hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE + hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_color: Color = Color(0, 0, 0, 1) - hint=NONE hint_string="Color" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_color_no_alpha: Color = Color(0, 0, 0, 1) - hint=COLOR_NO_ALPHA hint_string="" usage=DEFAULT|SCRIPT_VARIABLE + hint=COLOR_NO_ALPHA hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_node_path: NodePath = NodePath("hello") - hint=NODE_PATH_VALID_TYPES hint_string="Sprite2D,Sprite3D,Control,Node" usage=DEFAULT|SCRIPT_VARIABLE -var test_node: Node = null - hint=NODE_TYPE hint_string="Node" usage=DEFAULT|SCRIPT_VARIABLE -var test_node_array: Array = Array[Node]([]) - hint=TYPE_STRING hint_string="Object/NODE_TYPE:Node" usage=DEFAULT|SCRIPT_VARIABLE + hint=NODE_PATH_VALID_TYPES hint_string="Sprite2D,Sprite3D,Control,Node" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_side: Side = 0 + hint=ENUM hint_string="Side Left:0,Side Top:1,Side Right:2,Side Bottom:3" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM class_name=&"Side" +var test_atm: Node.AutoTranslateMode = 0 + hint=ENUM hint_string="Auto Translate Mode Inherit:0,Auto Translate Mode Always:1,Auto Translate Mode Disabled:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM class_name=&"Node.AutoTranslateMode" +var test_image: Image = null + hint=RESOURCE_TYPE hint_string="Image" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Image" +var test_timer: Timer = null + hint=NODE_TYPE hint_string="Timer" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Timer" +var test_global_class: ExportVariableTest = null + hint=NODE_TYPE hint_string="ExportVariableTest" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportVariableTest" +var test_preloaded_global_class: ExportVariableTestGlobalClass = null + hint=NODE_TYPE hint_string="ExportVariableTestGlobalClass" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportVariableTestGlobalClass" +var test_preloaded_unnamed_class: Node2D = null + hint=NODE_TYPE hint_string="Node2D" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Node2D" +var test_array: Array = [] + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_array_bool: Array = Array[bool]([]) + hint=TYPE_STRING hint_string="<bool>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_array_array: Array = Array[Array]([]) + hint=TYPE_STRING hint_string="<Array>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_array_side: Array = Array[int]([]) + hint=TYPE_STRING hint_string="<int>/<ENUM>:Side Left:0,Side Top:1,Side Right:2,Side Bottom:3" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_array_atm: Array = Array[int]([]) + hint=TYPE_STRING hint_string="<int>/<ENUM>:Auto Translate Mode Inherit:0,Auto Translate Mode Always:1,Auto Translate Mode Disabled:2" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_array_image: Array = Array[Image]([]) + hint=TYPE_STRING hint_string="<Object>/<RESOURCE_TYPE>:Image" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_array_timer: Array = Array[Timer]([]) + hint=TYPE_STRING hint_string="<Object>/<NODE_TYPE>:Timer" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_storage_untyped: Variant = null + hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT class_name=&"" +var test_storage_weak_int: Variant = 3 + hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT class_name=&"" +var test_storage_hard_int: int = 4 + hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE class_name=&"" +var test_export_custom_untyped: null = null + hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_export_custom_weak_int: int = 5 + hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_export_custom_hard_int: int = 6 + hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable_global.notest.gd b/modules/gdscript/tests/scripts/parser/features/export_variable_global.notest.gd new file mode 100644 index 0000000000..caa2ead214 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_variable_global.notest.gd @@ -0,0 +1,2 @@ +class_name ExportVariableTestGlobalClass +extends Node2D diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable_unnamed.notest.gd b/modules/gdscript/tests/scripts/parser/features/export_variable_unnamed.notest.gd new file mode 100644 index 0000000000..e251cf8aee --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_variable_unnamed.notest.gd @@ -0,0 +1 @@ +extends Node2D diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd index 2fa45c1d7d..0ec118b6b7 100644 --- a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd +++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd @@ -9,5 +9,5 @@ func test(): j_string += str(j) return j_string i_string += lambda.call() - assert(i_string == '0202') + Utils.check(i_string == '0202') print('ok') diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd index 46b6856d22..c3a42288c7 100644 --- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd +++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd @@ -9,6 +9,7 @@ func four_parameters(_a, callable : Callable, b=func(): print(10)): func test(): var v + @warning_ignore("confusable_capture_reassignment") v=func():v=1 if true: v=1 print(v) diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd index 9c67a152f5..736cda7f74 100644 --- a/modules/gdscript/tests/scripts/parser/features/truthiness.gd +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd @@ -1,30 +1,32 @@ func test(): - # The assertions below should all evaluate to `true` for this test to pass. - assert(true) - assert(not false) - assert(500) - assert(not 0) - assert(500.5) - assert(not 0.0) - assert("non-empty string") - assert(["non-empty array"]) - assert({"non-empty": "dictionary"}) - assert(Vector2(1, 0)) - assert(Vector2i(-1, -1)) - assert(Vector3(0, 0, 0.0001)) - assert(Vector3i(0, 0, 10000)) + # The checks below should all evaluate to `true` for this test to pass. + Utils.check(true) + Utils.check(not false) + Utils.check(500) + Utils.check(not 0) + Utils.check(500.5) + Utils.check(not 0.0) + Utils.check("non-empty string") + Utils.check(["non-empty array"]) + Utils.check({"non-empty": "dictionary"}) + Utils.check(Vector2(1, 0)) + Utils.check(Vector2i(-1, -1)) + Utils.check(Vector3(0, 0, 0.0001)) + Utils.check(Vector3i(0, 0, 10000)) # Zero position is `true` only if the Rect2's size is non-zero. - assert(Rect2(0, 0, 0, 1)) + Utils.check(Rect2(0, 0, 0, 1)) # Zero size is `true` only if the position is non-zero. - assert(Rect2(1, 1, 0, 0)) + Utils.check(Rect2(1, 1, 0, 0)) # Zero position is `true` only if the Rect2's size is non-zero. - assert(Rect2i(0, 0, 0, 1)) + Utils.check(Rect2i(0, 0, 0, 1)) # Zero size is `true` only if the position is non-zero. - assert(Rect2i(1, 1, 0, 0)) + Utils.check(Rect2i(1, 1, 0, 0)) # A fully black color is only truthy if its alpha component is not equal to `1`. - assert(Color(0, 0, 0, 0.5)) + Utils.check(Color(0, 0, 0, 0.5)) + + print("ok") diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out index 705524857b..1b47ed10dc 100644 --- a/modules/gdscript/tests/scripts/parser/features/truthiness.out +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out @@ -1,65 +1,2 @@ GDTEST_OK ->> WARNING ->> Line: 3 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 4 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 5 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 6 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 7 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 8 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 9 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 12 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 13 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 14 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 15 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 18 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 21 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 24 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 27 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 30 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. +ok diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd new file mode 100644 index 0000000000..57e6489484 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd @@ -0,0 +1,5 @@ +func test(): + var my_dictionary: Dictionary[int, String] = { 1: "one", 2: "two", 3: "three" } + var inferred_dictionary := { 1: "one", 2: "two", 3: "three" } # This is Dictionary[int, String]. + print(my_dictionary) + print(inferred_dictionary) diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out new file mode 100644 index 0000000000..6021c338ee --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out @@ -0,0 +1,3 @@ +GDTEST_OK +{ 1: "one", 2: "two", 3: "three" } +{ 1: "one", 2: "two", 3: "three" } diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd index 00598e4d50..f8f70b8cc3 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd @@ -4,3 +4,4 @@ func i_return_int() -> int: func test(): i_return_int() + preload("../../utils.notest.gd") # `preload` is a function-like keyword. diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out index f2db4e9307..107051df6c 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out @@ -3,3 +3,7 @@ GDTEST_OK >> Line: 6 >> RETURN_VALUE_DISCARDED >> The function "i_return_int()" returns a value that will be discarded if not used. +>> WARNING +>> Line: 7 +>> RETURN_VALUE_DISCARDED +>> The function "preload()" returns a value that will be discarded if not used. diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd index dc4223ec2d..74f42b012b 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd +++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd @@ -6,3 +6,16 @@ func test(): Vector3.ZERO [true, false] float(125) + # The following statements should not produce `STANDALONE_EXPRESSION`: + var _a = 1 + _a = 2 # Assignment is a local (or global) side effect. + @warning_ignore("redundant_await") + await 3 # The `await` operand is usually a coroutine or a signal. + absi(4) # A call (in general) can have side effects. + @warning_ignore("return_value_discarded") + preload("../../utils.notest.gd") # A static initializer may have side effects. + """ + Python-like "comment". + """ + @warning_ignore("standalone_ternary") + 1 if 2 else 3 # Produces `STANDALONE_TERNARY` instead. diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out index a2c67a6e51..72c659c952 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out +++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out @@ -2,16 +2,16 @@ GDTEST_OK >> WARNING >> Line: 3 >> STANDALONE_EXPRESSION ->> Standalone expression (the line has no effect). +>> Standalone expression (the line may have no effect). >> WARNING >> Line: 4 >> STANDALONE_EXPRESSION ->> Standalone expression (the line has no effect). +>> Standalone expression (the line may have no effect). >> WARNING >> Line: 6 >> STANDALONE_EXPRESSION ->> Standalone expression (the line has no effect). +>> Standalone expression (the line may have no effect). >> WARNING >> Line: 7 >> STANDALONE_EXPRESSION ->> Standalone expression (the line has no effect). +>> Standalone expression (the line may have no effect). diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot index c500ef443d..c9035ecab9 100644 --- a/modules/gdscript/tests/scripts/project.godot +++ b/modules/gdscript/tests/scripts/project.godot @@ -8,3 +8,10 @@ config_version=5 [application] config/name="GDScript Integration Test Suite" + +[input] + +test_input_action={ +"deadzone": 0.5, +"events": [] +} diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd new file mode 100644 index 0000000000..3724c8c713 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/90086 + +class MyObj: + var obj: WeakRef + +func test(): + var obj_1 = MyObj.new() + var obj_2 = MyObj.new() + obj_1.obj = obj_2 diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out new file mode 100644 index 0000000000..dfca5b1eca --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/invalid_property_assignment.gd +>> 9 +>> Invalid assignment of property or key 'obj' with value of type 'RefCounted (MyObj)' on a base object of type 'RefCounted (MyObj)'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out index 7b9f1066b0..9b38957101 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out @@ -1,4 +1,5 @@ GDTEST_RUNTIME_ERROR >> ERROR >> Method/function failed. +>> Unable to convert array index 0 from "Object" to "Object". not ok diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd new file mode 100644 index 0000000000..75004742a2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd @@ -0,0 +1,4 @@ +func test(): + var basic := { 1: 1 } + var typed: Dictionary[int, int] = basic + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out new file mode 100644 index 0000000000..cadb17f570 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_basic_to_typed.gd +>> 3 +>> Trying to assign a dictionary of type "Dictionary" to a variable of type "Dictionary[int, int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd new file mode 100644 index 0000000000..e5ab4a1a85 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float] + var typed: Dictionary[int, int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out new file mode 100644 index 0000000000..fe1e5d1285 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_differently_typed.gd +>> 3 +>> Trying to assign a dictionary of type "Dictionary[float, float]" to a variable of type "Dictionary[int, int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd new file mode 100644 index 0000000000..2f0b3bd0eb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd @@ -0,0 +1,7 @@ +func get_key() -> Variant: + return "key" + +func test(): + var typed: Dictionary[int, int] + typed[get_key()] = 0 + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out new file mode 100644 index 0000000000..5f6dd7f641 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_differently_typed_key.gd +>> 6 +>> Invalid assignment of property or key 'key' with value of type 'int' on a base object of type 'Dictionary[int, int]'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd new file mode 100644 index 0000000000..b171159aed --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd @@ -0,0 +1,7 @@ +func get_value() -> Variant: + return "value" + +func test(): + var typed: Dictionary[int, int] + typed[0] = get_value() + print("not ok") diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out new file mode 100644 index 0000000000..f766d14261 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_differently_typed_value.gd +>> 6 +>> Invalid assignment of property or key '0' with value of type 'String' on a base object of type 'Dictionary[int, int]'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd new file mode 100644 index 0000000000..6cc0e57255 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd @@ -0,0 +1,7 @@ +class Foo: pass +class Bar extends Foo: pass +class Baz extends Foo: pass + +func test(): + var typed: Dictionary[Bar, Bar] = { Baz.new() as Foo: Baz.new() as Foo } + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out new file mode 100644 index 0000000000..18a4c360e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out @@ -0,0 +1,5 @@ +GDTEST_RUNTIME_ERROR +>> ERROR +>> Method/function failed. +>> Unable to convert key from "Object" to "Object". +not ok diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd new file mode 100644 index 0000000000..8f7d732584 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var basic := { 1: 1 } + expect_typed(basic) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out new file mode 100644 index 0000000000..fb45461701 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_pass_basic_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_basic_to_typed.gd)'. The dictionary of argument 1 (Dictionary) does not have the same element type as the expected typed dictionary argument. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd new file mode 100644 index 0000000000..978a9fdfee --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float] + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out new file mode 100644 index 0000000000..4036a1bf01 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_pass_differently_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_differently_to_typed.gd)'. The dictionary of argument 1 (Dictionary[float, float]) does not have the same element type as the expected typed dictionary argument. diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd index bd38259cec..6eec37d64d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -25,7 +25,7 @@ func test(): print("String in Array[StringName]: ", "abc" in stringname_array) var packed_string_array: PackedStringArray = [] - assert(!packed_string_array.push_back("abc")) + Utils.check(!packed_string_array.push_back("abc")) print("StringName in PackedStringArray: ", &"abc" in packed_string_array) string_array.push_back("abc") diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd index d1746979be..6aa863c05f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd +++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd @@ -1,10 +1,9 @@ const array: Array = [0] const dictionary := {1: 2} -@warning_ignore("assert_always_true") func test(): - assert(array.is_read_only() == true) - assert(str(array) == '[0]') - assert(dictionary.is_read_only() == true) - assert(str(dictionary) == '{ 1: 2 }') + Utils.check(array.is_read_only() == true) + Utils.check(str(array) == '[0]') + Utils.check(dictionary.is_read_only() == true) + Utils.check(str(dictionary) == '{ 1: 2 }') print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd index a778fb1a94..0f2526667d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd +++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd @@ -2,8 +2,8 @@ class Foo extends Node: func _init(): name = 'f' var string: String = name - assert(typeof(string) == TYPE_STRING) - assert(string == 'f') + Utils.check(typeof(string) == TYPE_STRING) + Utils.check(string == 'f') print('ok') func test(): diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd index 0851d939dc..9e67e75140 100644 --- a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd +++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd @@ -6,15 +6,15 @@ extends Node @onready var later_untyped = [1] func test(): - assert(typeof(later_inferred) == TYPE_ARRAY) - assert(later_inferred.size() == 0) + Utils.check(typeof(later_inferred) == TYPE_ARRAY) + Utils.check(later_inferred.size() == 0) - assert(typeof(later_static) == TYPE_ARRAY) - assert(later_static.size() == 0) + Utils.check(typeof(later_static) == TYPE_ARRAY) + Utils.check(later_static.size() == 0) - assert(typeof(later_static_with_init) == TYPE_ARRAY) - assert(later_static_with_init.size() == 0) + Utils.check(typeof(later_static_with_init) == TYPE_ARRAY) + Utils.check(later_static_with_init.size() == 0) - assert(typeof(later_untyped) == TYPE_NIL) + Utils.check(typeof(later_untyped) == TYPE_NIL) print("ok") diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd index 94bac1974f..de5eaabb79 100644 --- a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd @@ -7,11 +7,11 @@ func test(): stringname_dict[&"abc"] = 24 print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING) - print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING) + print("StringName key is TYPE_STRING_NAME: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING_NAME) print("StringName gets String: ", string_dict.get(&"abc")) print("String gets StringName: ", stringname_dict.get("abc")) stringname_dict[&"abc"] = 42 - # They compare equal because StringName keys are converted to String. + # They compare equal because StringName keys are considered equivalent to String keys. print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict) diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out index ab5b89d55c..a1461912bf 100644 --- a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out @@ -1,6 +1,6 @@ GDTEST_OK String key is TYPE_STRING: true -StringName key is TYPE_STRING: true +StringName key is TYPE_STRING_NAME: true StringName gets String: 42 String gets StringName: 24 String Dictionary == StringName Dictionary: true diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd index 0133d7fcfc..90df98e05b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd +++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - # GH-73843 @export_group("Resource") diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out index 9387ec50d7..a1e7233078 100644 --- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out +++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out @@ -1,8 +1,8 @@ GDTEST_OK Not shadowed: Resource var test_1: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" @export_category("test_1") - hint=NONE hint_string="" usage=CATEGORY + hint=NONE hint_string="" usage=CATEGORY class_name=&"" var test_2: int = 0 - hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd index bc899a3a6f..393500bd9b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd @@ -28,13 +28,18 @@ func test(): prints(var_to_str(e), var_to_str(elem)) print("Test String-keys dictionary.") - var d1 := {a = 1, b = 2, c = 3} + var d1 := { a = 1, b = 2, c = 3 } for k: StringName in d1: var key := k prints(var_to_str(k), var_to_str(key)) print("Test RefCounted-keys dictionary.") - var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3} + var d2 := { RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3 } for k: RefCounted in d2: var key := k prints(k.get_class(), key.get_class()) + + print("Test implicitly typed dictionary literal.") + for k: StringName in { x = 123, y = 456, z = 789 }: + var key := k + prints(var_to_str(k), var_to_str(key)) diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out index eeebdc4be5..89cc1b76fe 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out @@ -27,3 +27,7 @@ Test RefCounted-keys dictionary. RefCounted RefCounted Resource Resource ConfigFile ConfigFile +Test implicitly typed dictionary literal. +&"x" &"x" +&"y" &"y" +&"z" &"z" diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.gd new file mode 100644 index 0000000000..67225cad6a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.gd @@ -0,0 +1,18 @@ +# https://github.com/godotengine/godot/issues/93952 + +func foo(): + pass + +func test(): + var a: int + + var lambda_self := func (x: int) -> void: + foo() + print(a, x) + + print(lambda_self.get_argument_count()) # Should print 1. + + var lambda_non_self := func (x: int) -> void: + print(a, x) + + print(lambda_non_self.get_argument_count()) # Should print 1. diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.out b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.out new file mode 100644 index 0000000000..04b4638adf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.out @@ -0,0 +1,3 @@ +GDTEST_OK +1 +1 diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_captures.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_captures.gd new file mode 100644 index 0000000000..bbdf745540 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_captures.gd @@ -0,0 +1,26 @@ +# GH-92217 +# TODO: Add more tests. + +static var static_var: int: + set(value): + prints("set static_var", value) + get: + print("get static_var") + return 0 + +var member_var: int: + set(value): + prints("set member_var", value) + get: + print("get member_var") + return 0 + +func test(): + var lambda := func (): + var _tmp := static_var + _tmp = member_var + + static_var = 1 + member_var = 1 + + lambda.call() diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_captures.out b/modules/gdscript/tests/scripts/runtime/features/lambda_captures.out new file mode 100644 index 0000000000..0bdf74a43f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_captures.out @@ -0,0 +1,5 @@ +GDTEST_OK +get static_var +get member_var +set static_var 1 +set member_var 1 diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd new file mode 100644 index 0000000000..160e43a797 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd @@ -0,0 +1,21 @@ +# https://github.com/godotengine/godot/issues/94074 + +func foo(): + pass + +func test(): + var lambda_self := func test() -> void: + foo() + var anon_lambda_self := func() -> void: + foo() + + print(lambda_self.get_method()) # Should print "test". + print(anon_lambda_self.get_method()) # Should print "<anonymous lambda>". + + var lambda_non_self := func test() -> void: + pass + var anon_lambda_non_self := func() -> void: + pass + + print(lambda_non_self.get_method()) # Should print "test". + print(anon_lambda_non_self.get_method()) # Should print "<anonymous lambda>". diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out new file mode 100644 index 0000000000..17ee47fca2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out @@ -0,0 +1,5 @@ +GDTEST_OK +test +<anonymous lambda> +test +<anonymous lambda> diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd index 4cb51f8512..48a9349bf8 100644 --- a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd @@ -62,7 +62,7 @@ func test(): 0 when side_effect(): print("will run the side effect call, but not this") _: - assert(global == 1) + Utils.check(global == 1) print("side effect only ran once") func side_effect(): diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index 42b29eee43..4ce53aa395 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -5,8 +5,6 @@ class MyClass: enum MyEnum {} -const Utils = preload("../../utils.notest.gd") - static var test_static_var_untyped static var test_static_var_weak_null = null static var test_static_var_weak_int = 1 @@ -33,6 +31,16 @@ var test_var_hard_array_my_enum: Array[MyEnum] var test_var_hard_array_resource: Array[Resource] var test_var_hard_array_this: Array[TestMemberInfo] var test_var_hard_array_my_class: Array[MyClass] +var test_var_hard_dictionary: Dictionary +var test_var_hard_dictionary_int_variant: Dictionary[int, Variant] +var test_var_hard_dictionary_variant_int: Dictionary[Variant, int] +var test_var_hard_dictionary_int_int: Dictionary[int, int] +var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type] +var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode] +var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum] +var test_var_hard_dictionary_resource: Dictionary[Resource, Resource] +var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo] +var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass] var test_var_hard_resource: Resource var test_var_hard_this: TestMemberInfo var test_var_hard_my_class: MyClass @@ -45,17 +53,17 @@ func test_func_weak_null(): return null func test_func_weak_int(): return 1 func test_func_hard_variant() -> Variant: return null func test_func_hard_int() -> int: return 1 -func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass +func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass signal test_signal_1() signal test_signal_2(a: Variant, b) -signal test_signal_3(a: int, b: Array[int]) -signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) -signal test_signal_5(a: MyEnum, b: Array[MyEnum]) -signal test_signal_6(a: Resource, b: Array[Resource]) -signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) -signal test_signal_8(a: MyClass, b: Array[MyClass]) +signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type]) +signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo]) +signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass]) func no_exec(): test_signal_1.emit() diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out index 7c826ac05a..2baf451aa5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.out +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out @@ -23,6 +23,16 @@ var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum] var test_var_hard_array_resource: Array[Resource] var test_var_hard_array_this: Array[TestMemberInfo] var test_var_hard_array_my_class: Array[RefCounted] +var test_var_hard_dictionary: Dictionary +var test_var_hard_dictionary_int_variant: Dictionary[int, Variant] +var test_var_hard_dictionary_variant_int: Dictionary[Variant, int] +var test_var_hard_dictionary_int_int: Dictionary[int, int] +var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type] +var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode] +var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum] +var test_var_hard_dictionary_resource: Dictionary[Resource, Resource] +var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo] +var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted] var test_var_hard_resource: Resource var test_var_hard_this: TestMemberInfo var test_var_hard_my_class: RefCounted @@ -33,13 +43,13 @@ func test_func_weak_null() -> Variant func test_func_weak_int() -> Variant func test_func_hard_variant() -> Variant func test_func_hard_int() -> int -func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void +func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e: Variant = 2) -> void func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void signal test_signal_1() signal test_signal_2(a: Variant, b: Variant) -signal test_signal_3(a: int, b: Array[int]) -signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) -signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum]) -signal test_signal_6(a: Resource, b: Array[Resource]) -signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) -signal test_signal_8(a: RefCounted, b: Array[RefCounted]) +signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type]) +signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum], c: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo]) +signal test_signal_8(a: RefCounted, b: Array[RefCounted], c: Dictionary[RefCounted, RefCounted]) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd index ee5c1e1267..4ddbeaec0b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd @@ -1,7 +1,5 @@ # GH-82169 -const Utils = preload("../../utils.notest.gd") - class A: static var test_static_var_a1 static var test_static_var_a2 diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd index 6c5df32ffe..d6847768e6 100644 --- a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd @@ -3,7 +3,6 @@ class MyClass: enum MyEnum {A, B, C} -const Utils = preload("../../utils.notest.gd") const Other = preload("./metatypes.notest.gd") var test_native := JSON @@ -25,12 +24,24 @@ func test(): if str(property.name).begins_with("test_"): print(Utils.get_property_signature(property)) + print("---") check_gdscript_native_class(test_native) check_gdscript(test_script) check_gdscript(test_class) check_enum(test_enum) + print("---") print(test_native.stringify([])) print(test_script.TEST) print(test_class.TEST) print(test_enum.keys()) + + print("---") + # Some users add unnecessary type hints to `const`-`preload`, which removes metatypes. + # For **constant** `GDScript` we still check the class members, despite the wider type. + const ScriptNoMeta: GDScript = Other + const ClassNoMeta: GDScript = MyClass + var a := ScriptNoMeta.TEST + var b := ClassNoMeta.TEST + print(a) + print(b) diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.out b/modules/gdscript/tests/scripts/runtime/features/metatypes.out index 352d1caa59..c42287438c 100644 --- a/modules/gdscript/tests/scripts/runtime/features/metatypes.out +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.out @@ -3,11 +3,16 @@ var test_native: GDScriptNativeClass var test_script: GDScript var test_class: GDScript var test_enum: Dictionary +--- GDScriptNativeClass GDScript GDScript { "A": 0, "B": 1, "C": 2 } +--- [] 100 10 ["A", "B", "C"] +--- +100 +10 diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd index c774ebf83c..df639a7b4d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd +++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd @@ -1,6 +1,5 @@ # GH-77666 - -func test(): +func test_exit_if(): var ref := RefCounted.new() print(ref.get_reference_count()) @@ -8,3 +7,20 @@ func test(): var _temp := ref print(ref.get_reference_count()) + +# GH-94654 +func test_exit_while(): + var slots_data := [] + + while true: + @warning_ignore("confusable_local_declaration") + var slot = 42 + slots_data.append(slot) + break + + var slot: int = slots_data[0] + print(slot) + +func test(): + test_exit_if() + test_exit_while() diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out index 04b4638adf..164eb24963 100644 --- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out +++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out @@ -1,3 +1,4 @@ GDTEST_OK 1 1 +42 diff --git a/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd new file mode 100644 index 0000000000..442335faeb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd @@ -0,0 +1,50 @@ +# https://github.com/godotengine/godot/issues/75658 + +class MyObj: + var callable: Callable + + func run(): + callable.call() + + var prop: + set(value): + callable.call() + get: + callable.call() + return 0 + + func _on_some_signal(): + callable.call() + + func _init(p_callable: Callable): + self.callable = p_callable + +signal some_signal + +var obj: MyObj + +func test(): + # Call. + obj = MyObj.new(nullify_obj) + obj.run() + print(obj) + + # Get. + obj = MyObj.new(nullify_obj) + var _aux = obj.prop + print(obj) + + # Set. + obj = MyObj.new(nullify_obj) + obj.prop = 1 + print(obj) + + # Signal handling. + obj = MyObj.new(nullify_obj) + @warning_ignore("return_value_discarded") + some_signal.connect(obj._on_some_signal) + some_signal.emit() + print(obj) + +func nullify_obj(): + obj = null diff --git a/modules/gdscript/tests/scripts/runtime/features/self_destruction.out b/modules/gdscript/tests/scripts/runtime/features/self_destruction.out new file mode 100644 index 0000000000..ee4024a524 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/self_destruction.out @@ -0,0 +1,5 @@ +GDTEST_OK +<null> +<null> +<null> +<null> diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd new file mode 100644 index 0000000000..dee36d3ae0 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/90086 + +class MyObj: + var obj : WeakRef + +func test(): + var obj_1 = MyObj.new() + var obj_2 = MyObj.new() + Utils.check(obj_2.get_reference_count() == 1) + obj_1.set(&"obj", obj_2) + Utils.check(obj_2.get_reference_count() == 1) diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd new file mode 100644 index 0000000000..f70b521e1a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd @@ -0,0 +1,62 @@ +# GH-94667 + +class Inner: + var subprop: Vector2: + set(value): + prints("subprop setter", value) + subprop = value + get: + print("subprop getter") + return subprop + + func _to_string() -> String: + return "<Inner>" + +var prop1: + set(value): + prints("prop1 setter", value) + prop1 = value + +var prop2: Inner: + set(value): + prints("prop2 setter", value) + prop2 = value + +var prop3: + set(value): + prints("prop3 setter", value) + prop3 = value + get: + print("prop3 getter") + return prop3 + +var prop4: Inner: + set(value): + prints("prop4 setter", value) + prop4 = value + get: + print("prop4 getter") + return prop4 + +func test(): + print("===") + prop1 = Vector2() + prop1.x = 1.0 + print("---") + prop1 = Inner.new() + prop1.subprop.x = 1.0 + + print("===") + prop2 = Inner.new() + prop2.subprop.x = 1.0 + + print("===") + prop3 = Vector2() + prop3.x = 1.0 + print("---") + prop3 = Inner.new() + prop3.subprop.x = 1.0 + + print("===") + prop4 = Inner.new() + prop4.subprop.x = 1.0 diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out new file mode 100644 index 0000000000..c51759f481 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out @@ -0,0 +1,26 @@ +GDTEST_OK +=== +prop1 setter (0, 0) +prop1 setter (1, 0) +--- +prop1 setter <Inner> +subprop getter +subprop setter (1, 0) +=== +prop2 setter <Inner> +subprop getter +subprop setter (1, 0) +=== +prop3 setter (0, 0) +prop3 getter +prop3 setter (1, 0) +--- +prop3 setter <Inner> +prop3 getter +subprop getter +subprop setter (1, 0) +=== +prop4 setter <Inner> +prop4 getter +subprop getter +subprop setter (1, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd new file mode 100644 index 0000000000..9e27a500bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd @@ -0,0 +1,13 @@ +# https://github.com/godotengine/godot/issues/85952 + +var vec: Vector2 = Vector2.ZERO: + set(new_vec): + prints("setting vec from", vec, "to", new_vec) + if new_vec == Vector2(1, 1): + vec = new_vec + +func test(): + vec.x = 2 + vec.y = 2 + + prints("vec is", vec) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out new file mode 100644 index 0000000000..31b3b3a3a8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out @@ -0,0 +1,4 @@ +GDTEST_OK +setting vec from (0, 0) to (2, 0) +setting vec from (0, 0) to (0, 2) +vec is (0, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd new file mode 100644 index 0000000000..11a670a7fb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd @@ -0,0 +1,15 @@ +extends Node + +func test() -> void: + var node1 := Node.new() + node1.name = "_" + var node2 := Node.new() + node2.name = "Child" + var node3 := Node.new() + node3.name = "Child" + + add_child(node1) + node1.add_child(node2) + add_child(node3) + + Utils.check(get_node("_/Child") == $_/Child) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd index 691b611574..d72662736e 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd @@ -14,33 +14,33 @@ func test(): func test_construct(v, f): @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_utility(v, f): abs(v) # Utility function. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_builtin_call(v, f): @warning_ignore("unsafe_method_access") v.angle() # Built-in method call. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_builtin_call_validated(v: Vector2, f): @warning_ignore("return_value_discarded") v.abs() # Built-in method call validated. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call(v, f): @warning_ignore("unsafe_method_access") v.get_reference_count() # Native type method call. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call_method_bind(v: Resource, f): @warning_ignore("return_value_discarded") v.duplicate() # Native type method call with MethodBind. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call_method_bind_validated(v: RefCounted, f): @warning_ignore("return_value_discarded") v.get_reference_count() # Native type method call with validated MethodBind. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd new file mode 100644 index 0000000000..13f2c3b956 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd @@ -0,0 +1,7 @@ +# GH-93990 + +func test_param(array: Array[String]) -> void: + print(array.get_typed_builtin() == TYPE_STRING) + +func test() -> void: + test_param(PackedStringArray()) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out new file mode 100644 index 0000000000..55482c2b52 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out @@ -0,0 +1,2 @@ +GDTEST_OK +true diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd index ec444b4ffa..859bfd7987 100644 --- a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd @@ -1,6 +1,6 @@ func test(): var untyped: Variant = 32 var typed: Array[int] = [untyped] - assert(typed.get_typed_builtin() == TYPE_INT) - assert(str(typed) == '[32]') + Utils.check(typed.get_typed_builtin() == TYPE_INT) + Utils.check(str(typed) == '[32]') print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd new file mode 100644 index 0000000000..0371ee5630 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd @@ -0,0 +1,6 @@ +func test_param(dictionary: Dictionary[int, String]) -> void: + print(dictionary.get_typed_key_builtin() == TYPE_INT) + print(dictionary.get_typed_value_builtin() == TYPE_STRING) + +func test() -> void: + test_param({ 123: "some_string" }) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out new file mode 100644 index 0000000000..9d111a8322 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd new file mode 100644 index 0000000000..ee51440d80 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd @@ -0,0 +1,7 @@ +func test(): + var untyped: Variant = 32 + var typed: Dictionary[int, int] = { untyped: untyped } + Utils.check(typed.get_typed_key_builtin() == TYPE_INT) + Utils.check(typed.get_typed_value_builtin() == TYPE_INT) + Utils.check(str(typed) == '{ 32: 32 }') + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 1cf46c179e..1e2788f765 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -1,3 +1,13 @@ +class_name Utils + + +# `assert()` is not evaluated in non-debug builds. Do not use `assert()` +# for anything other than testing the `assert()` itself. +static func check(condition: Variant) -> void: + if not condition: + printerr("Check failed.") + + static func get_type(property: Dictionary, is_return: bool = false) -> String: match property.type: TYPE_NIL: @@ -14,6 +24,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String: if str(property.hint_string).is_empty(): return "Array[<unknown type>]" return "Array[%s]" % property.hint_string + TYPE_DICTIONARY: + if property.hint == PROPERTY_HINT_DICTIONARY_TYPE: + if str(property.hint_string).is_empty(): + return "Dictionary[<unknown type>, <unknown type>]" + return "Dictionary[%s]" % str(property.hint_string).replace(";", ", ") TYPE_OBJECT: if not str(property.class_name).is_empty(): return property.class_name @@ -46,7 +61,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: while true: if not hint_string.contains(":"): - push_error("Invalid PROPERTY_HINT_TYPE_STRING format.") + printerr("Invalid PROPERTY_HINT_TYPE_STRING format.") var elem_type_hint: String = hint_string.get_slice(":", 0) hint_string = hint_string.substr(elem_type_hint.length() + 1) @@ -55,18 +70,18 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: if elem_type_hint.is_valid_int(): elem_type = elem_type_hint.to_int() - type_hint_prefixes += type_string(elem_type) + ":" + type_hint_prefixes += "<%s>:" % type_string(elem_type) else: if elem_type_hint.count("/") != 1: - push_error("Invalid PROPERTY_HINT_TYPE_STRING format.") + printerr("Invalid PROPERTY_HINT_TYPE_STRING format.") elem_type = elem_type_hint.get_slice("/", 0).to_int() elem_hint = elem_type_hint.get_slice("/", 1).to_int() - type_hint_prefixes += "%s/%s:" % [ - type_string(elem_type), - get_property_hint_name(elem_hint).trim_prefix("PROPERTY_HINT_"), + type_hint_prefixes += "<%s>/<%s>:" % [ + type_string(elem_type), + get_property_hint_name(elem_hint).trim_prefix("PROPERTY_HINT_"), ] - if elem_type < TYPE_ARRAY: + if elem_type < TYPE_ARRAY or hint_string.is_empty(): break return type_hint_prefixes + hint_string @@ -76,10 +91,11 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: static func print_property_extended_info(property: Dictionary, base: Object = null, is_static: bool = false) -> void: print(get_property_signature(property, base, is_static)) - print(' hint=%s hint_string="%s" usage=%s' % [ + print(' hint=%s hint_string="%s" usage=%s class_name=&"%s"' % [ get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"), - get_human_readable_hint_string(property), + get_human_readable_hint_string(property).c_escape(), get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""), + property.class_name.c_escape(), ]) @@ -177,6 +193,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_INT_IS_POINTER" PROPERTY_HINT_ARRAY_TYPE: return "PROPERTY_HINT_ARRAY_TYPE" + PROPERTY_HINT_DICTIONARY_TYPE: + return "PROPERTY_HINT_DICTIONARY_TYPE" PROPERTY_HINT_LOCALE_ID: return "PROPERTY_HINT_LOCALE_ID" PROPERTY_HINT_LOCALIZABLE_STRING: @@ -187,7 +205,7 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" PROPERTY_HINT_PASSWORD: return "PROPERTY_HINT_PASSWORD" - push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") + printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") return "<invalid hint>" @@ -239,7 +257,7 @@ static func get_property_usage_string(usage: int) -> String: usage &= ~flag[0] if usage != PROPERTY_USAGE_NONE: - push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") + printerr("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") return "<invalid usage flags>" return result.left(-1) diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h index 327446acee..387358934d 100644 --- a/modules/gdscript/tests/test_completion.h +++ b/modules/gdscript/tests/test_completion.h @@ -130,6 +130,8 @@ static void test_directory(const String &p_dir) { #endif EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", conf.get_value("input", "use_single_quotes", false)); + EditorSettings::get_singleton()->set_setting("text_editor/completion/add_node_path_literals", conf.get_value("input", "add_node_path_literals", false)); + EditorSettings::get_singleton()->set_setting("text_editor/completion/add_string_name_literals", conf.get_value("input", "add_string_name_literals", false)); List<Dictionary> include; to_dict_list(conf.get_value("output", "include", Array()), include); diff --git a/modules/glslang/config.py b/modules/glslang/config.py index 1169776a73..a024dcf984 100644 --- a/modules/glslang/config.py +++ b/modules/glslang/config.py @@ -1,7 +1,7 @@ def can_build(env, platform): - # glslang is only needed when Vulkan or Direct3D 12-based renderers are available, + # glslang is only needed when Vulkan, Direct3D 12 or Metal-based renderers are available, # as OpenGL doesn't use glslang. - return env["vulkan"] or env["d3d12"] + return env["vulkan"] or env["d3d12"] or env["metal"] def configure(env): diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp index 09ad1d6777..81505f716a 100644 --- a/modules/glslang/register_types.cpp +++ b/modules/glslang/register_types.cpp @@ -33,7 +33,6 @@ #include "core/config/engine.h" #include "servers/rendering/rendering_device.h" -#include <glslang/Include/Types.h> #include <glslang/Public/ResourceLimits.h> #include <glslang/Public/ShaderLang.h> #include <glslang/SPIRV/GlslangToSpv.h> @@ -74,6 +73,9 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage // - SPIRV-Reflect won't be able to parse the compute workgroup size. // - We want to play it safe with NIR-DXIL. TargetVersion = glslang::EShTargetSpv_1_3; + } else if (capabilities.device_family == RDD::DEVICE_METAL) { + ClientVersion = glslang::EShTargetVulkan_1_1; + TargetVersion = glslang::EShTargetSpv_1_6; } else { // once we support other backends we'll need to do something here if (r_error) { diff --git a/modules/gltf/README.md b/modules/gltf/README.md index 5d8966b201..2d1e92e070 100644 --- a/modules/gltf/README.md +++ b/modules/gltf/README.md @@ -1,11 +1,11 @@ -# Godot GLTF import and export module +# Godot glTF import and export module -In a nutshell, the GLTF module works like this: +In a nutshell, the glTF module works like this: -* The [`structures/`](structures/) folder contains GLTF structures, the - small pieces that make up a GLTF file, represented as C++ classes. -* The [`extensions/`](extensions/) folder contains GLTF extensions, which - are optional features that build on top of the base GLTF spec. +* The [`structures/`](structures/) folder contains glTF structures, the + small pieces that make up a glTF file, represented as C++ classes. +* The [`extensions/`](extensions/) folder contains glTF extensions, which + are optional features that build on top of the base glTF spec. * [`GLTFState`](gltf_state.h) holds collections of structures and extensions. * [`GLTFDocument`](gltf_document.h) operates on GLTFState and its elements. * The [`editor/`](editor/) folder uses GLTFDocument to import and export 3D models. diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml index ba7323b7cd..bc142797a3 100644 --- a/modules/gltf/doc_classes/GLTFAccessor.xml +++ b/modules/gltf/doc_classes/GLTFAccessor.xml @@ -1,45 +1,84 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFAccessor" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF accessor. + Represents a glTF accessor. </brief_description> <description> - GLTFAccessor is a data structure representing GLTF a [code]accessor[/code] that would be found in the [code]"accessors"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer. An accessor is a typed interpretation of the data in a buffer view. - Most custom data stored in GLTF does not need accessors, only buffer views (see [GLTFBufferView]). Accessors are for more advanced use cases such as interleaved mesh data encoded for the GPU. + GLTFAccessor is a data structure representing a glTF [code]accessor[/code] that would be found in the [code]"accessors"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer. An accessor is a typed interpretation of the data in a buffer view. + Most custom data stored in glTF does not need accessors, only buffer views (see [GLTFBufferView]). Accessors are for more advanced use cases such as interleaved mesh data encoded for the GPU. </description> <tutorials> <link title="Buffers, BufferViews, and Accessors in Khronos glTF specification">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md</link> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> </tutorials> <members> + <member name="accessor_type" type="int" setter="set_accessor_type" getter="get_accessor_type" enum="GLTFAccessor.GLTFAccessorType" default="0"> + The glTF accessor type as an enum. Possible values are 0 for "SCALAR", 1 for "VEC2", 2 for "VEC3", 3 for "VEC4", 4 for "MAT2", 5 for "MAT3", and 6 for "MAT4". + </member> <member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="-1"> The index of the buffer view this accessor is referencing. If [code]-1[/code], this accessor is not referencing any buffer view. </member> <member name="byte_offset" type="int" setter="set_byte_offset" getter="get_byte_offset" default="0"> + The offset relative to the start of the buffer view in bytes. </member> <member name="component_type" type="int" setter="set_component_type" getter="get_component_type" default="0"> + The glTF component type as an enum. Possible values are 5120 for "BYTE", 5121 for "UNSIGNED_BYTE", 5122 for "SHORT", 5123 for "UNSIGNED_SHORT", 5125 for "UNSIGNED_INT", and 5126 for "FLOAT". A value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices. </member> <member name="count" type="int" setter="set_count" getter="get_count" default="0"> + The number of elements referenced by this accessor. </member> <member name="max" type="PackedFloat64Array" setter="set_max" getter="get_max" default="PackedFloat64Array()"> + Maximum value of each component in this accessor. </member> <member name="min" type="PackedFloat64Array" setter="set_min" getter="get_min" default="PackedFloat64Array()"> + Minimum value of each component in this accessor. </member> <member name="normalized" type="bool" setter="set_normalized" getter="get_normalized" default="false"> + Specifies whether integer data values are normalized before usage. </member> <member name="sparse_count" type="int" setter="set_sparse_count" getter="get_sparse_count" default="0"> + Number of deviating accessor values stored in the sparse array. </member> <member name="sparse_indices_buffer_view" type="int" setter="set_sparse_indices_buffer_view" getter="get_sparse_indices_buffer_view" default="0"> + The index of the buffer view with sparse indices. The referenced buffer view MUST NOT have its target or byteStride properties defined. The buffer view and the optional byteOffset MUST be aligned to the componentType byte length. </member> <member name="sparse_indices_byte_offset" type="int" setter="set_sparse_indices_byte_offset" getter="get_sparse_indices_byte_offset" default="0"> + The offset relative to the start of the buffer view in bytes. </member> <member name="sparse_indices_component_type" type="int" setter="set_sparse_indices_component_type" getter="get_sparse_indices_component_type" default="0"> + The indices component data type as an enum. Possible values are 5121 for "UNSIGNED_BYTE", 5123 for "UNSIGNED_SHORT", and 5125 for "UNSIGNED_INT". </member> <member name="sparse_values_buffer_view" type="int" setter="set_sparse_values_buffer_view" getter="get_sparse_values_buffer_view" default="0"> + The index of the bufferView with sparse values. The referenced buffer view MUST NOT have its target or byteStride properties defined. </member> <member name="sparse_values_byte_offset" type="int" setter="set_sparse_values_byte_offset" getter="get_sparse_values_byte_offset" default="0"> + The offset relative to the start of the bufferView in bytes. </member> - <member name="type" type="int" setter="set_type" getter="get_type" default="0"> + <member name="type" type="int" setter="set_type" getter="get_type" deprecated="Use [member accessor_type] instead."> + The glTF accessor type as an enum. Use [member accessor_type] instead. </member> </members> + <constants> + <constant name="TYPE_SCALAR" value="0" enum="GLTFAccessorType"> + Accessor type "SCALAR". For the glTF object model, this can be used to map to a single float, int, or bool value, or a float array. + </constant> + <constant name="TYPE_VEC2" value="1" enum="GLTFAccessorType"> + Accessor type "VEC2". For the glTF object model, this maps to "float2", represented in the glTF JSON as an array of two floats. + </constant> + <constant name="TYPE_VEC3" value="2" enum="GLTFAccessorType"> + Accessor type "VEC3". For the glTF object model, this maps to "float3", represented in the glTF JSON as an array of three floats. + </constant> + <constant name="TYPE_VEC4" value="3" enum="GLTFAccessorType"> + Accessor type "VEC4". For the glTF object model, this maps to "float4", represented in the glTF JSON as an array of four floats. + </constant> + <constant name="TYPE_MAT2" value="4" enum="GLTFAccessorType"> + Accessor type "MAT2". For the glTF object model, this maps to "float2x2", represented in the glTF JSON as an array of four floats. + </constant> + <constant name="TYPE_MAT3" value="5" enum="GLTFAccessorType"> + Accessor type "MAT3". For the glTF object model, this maps to "float3x3", represented in the glTF JSON as an array of nine floats. + </constant> + <constant name="TYPE_MAT4" value="6" enum="GLTFAccessorType"> + Accessor type "MAT4". For the glTF object model, this maps to "float4x4", represented in the glTF JSON as an array of sixteen floats. + </constant> + </constants> </class> diff --git a/modules/gltf/doc_classes/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml index 2c70f4461f..d269145bbd 100644 --- a/modules/gltf/doc_classes/GLTFAnimation.xml +++ b/modules/gltf/doc_classes/GLTFAnimation.xml @@ -13,7 +13,7 @@ <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="set_additional_data"> @@ -22,7 +22,7 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> </methods> diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml index e191935fc9..b7f499ad72 100644 --- a/modules/gltf/doc_classes/GLTFBufferView.xml +++ b/modules/gltf/doc_classes/GLTFBufferView.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFBufferView" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF buffer view. + Represents a glTF buffer view. </brief_description> <description> - GLTFBufferView is a data structure representing GLTF a [code]bufferView[/code] that would be found in the [code]"bufferViews"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer that can be used to identify and extract data from the buffer. + GLTFBufferView is a data structure representing a glTF [code]bufferView[/code] that would be found in the [code]"bufferViews"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer that can be used to identify and extract data from the buffer. Most custom uses of buffers only need to use the [member buffer], [member byte_length], and [member byte_offset]. The [member byte_stride] and [member indices] properties are for more advanced use cases such as interleaved mesh data encoded for the GPU. </description> <tutorials> diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml index b334bf2867..9fce21659c 100644 --- a/modules/gltf/doc_classes/GLTFCamera.xml +++ b/modules/gltf/doc_classes/GLTFCamera.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFCamera" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF camera. + Represents a glTF camera. </brief_description> <description> - Represents a camera as defined by the base GLTF spec. + Represents a camera as defined by the base glTF spec. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="GLTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link> - <link title="GLTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link> + <link title="glTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link> + <link title="glTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> @@ -41,19 +41,19 @@ </methods> <members> <member name="depth_far" type="float" setter="set_depth_far" getter="get_depth_far" default="4000.0"> - The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to GLTF's [code]zfar[/code] property. + The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]zfar[/code] property. </member> <member name="depth_near" type="float" setter="set_depth_near" getter="get_depth_near" default="0.05"> - The distance to the near culling boundary for this camera relative to its local Z axis, in meters. This maps to GLTF's [code]znear[/code] property. + The distance to the near culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]znear[/code] property. </member> <member name="fov" type="float" setter="set_fov" getter="get_fov" default="1.309"> - The FOV of the camera. This class and GLTF define the camera FOV in radians, while Godot uses degrees. This maps to GLTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is true. + The FOV of the camera. This class and glTF define the camera FOV in radians, while Godot uses degrees. This maps to glTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is true. </member> <member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true"> - Whether or not the camera is in perspective mode. If false, the camera is in orthographic/orthogonal mode. This maps to GLTF's camera [code]type[/code] property. See [member Camera3D.projection] and the GLTF spec for more information. + Whether or not the camera is in perspective mode. If false, the camera is in orthographic/orthogonal mode. This maps to glTF's camera [code]type[/code] property. See [member Camera3D.projection] and the glTF spec for more information. </member> <member name="size_mag" type="float" setter="set_size_mag" getter="get_size_mag" default="0.5"> - The size of the camera. This class and GLTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to GLTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is false. + The size of the camera. This class and glTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to glTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is false. </member> </members> </class> diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 1f172633da..10534594d3 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -5,7 +5,7 @@ </brief_description> <description> GLTFDocument supports reading data from a glTF file, buffer, or Godot scene. This data can then be written to the filesystem, buffer, or used to create a Godot scene. - All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects. + All of the data in a glTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects. GLTFDocument can be extended with arbitrary functionality by extending the [GLTFDocumentExtension] class and registering it with GLTFDocument via [method register_gltf_document_extension]. This allows for custom data to be imported and exported. </description> <tutorials> @@ -21,7 +21,7 @@ <param index="2" name="state" type="GLTFState" /> <param index="3" name="flags" type="int" default="0" /> <description> - Takes a [PackedByteArray] defining a GLTF and imports the data to the given [GLTFState] object through the [param state] parameter. + Takes a [PackedByteArray] defining a glTF and imports the data to the given [GLTFState] object through the [param state] parameter. [b]Note:[/b] The [param base_path] tells [method append_from_buffer] where to find dependencies and can be empty. </description> </method> @@ -32,7 +32,7 @@ <param index="2" name="flags" type="int" default="0" /> <param index="3" name="base_path" type="String" default="""" /> <description> - Takes a path to a GLTF file and imports the data at that file path to the given [GLTFState] object through the [param state] parameter. + Takes a path to a glTF file and imports the data at that file path to the given [GLTFState] object through the [param state] parameter. [b]Note:[/b] The [param base_path] tells [method append_from_file] where to find dependencies and can be empty. </description> </method> @@ -49,7 +49,7 @@ <return type="PackedByteArray" /> <param index="0" name="state" type="GLTFState" /> <description> - Takes a [GLTFState] object through the [param state] parameter and returns a GLTF [PackedByteArray]. + Takes a [GLTFState] object through the [param state] parameter and returns a glTF [PackedByteArray]. </description> </method> <method name="generate_scene"> @@ -60,6 +60,14 @@ <param index="3" name="remove_immutable_tracks" type="bool" default="true" /> <description> Takes a [GLTFState] object through the [param state] parameter and returns a Godot Engine scene node. + The [param bake_fps] parameter overrides the bake_fps in [param state]. + </description> + </method> + <method name="get_supported_gltf_extensions" qualifiers="static"> + <return type="PackedStringArray" /> + <description> + Returns a list of all support glTF extensions, including extensions supported directly by the engine, and extensions supported by user plugins registering [GLTFDocumentExtension] classes. + [b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered. </description> </method> <method name="register_gltf_document_extension" qualifiers="static"> @@ -90,7 +98,7 @@ </methods> <members> <member name="image_format" type="String" setter="set_image_format" getter="get_image_format" default=""PNG""> - The user-friendly name of the export image format. This is used when exporting the GLTF file, including writing to a file and writing to a byte array. + The user-friendly name of the export image format. This is used when exporting the glTF file, including writing to a file and writing to a byte array. By default, Godot allows the following options: "None", "PNG", "JPEG", "Lossless WebP", and "Lossy WebP". Support for more image formats can be added in [GLTFDocumentExtension] classes. </member> <member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75"> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 0eabcb5022..b33e296e1c 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -4,7 +4,7 @@ [GLTFDocument] extension class. </brief_description> <description> - Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of GLTF import or export. + Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of glTF import or export. To use, make a new class extending GLTFDocumentExtension, override any methods you need, make an instance of your class, and register it using [method GLTFDocument.register_gltf_document_extension]. [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode]. </description> @@ -18,7 +18,7 @@ <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="scene_node" type="Node" /> <description> - Part of the export process. This method is run after [method _export_preflight] and before [method _export_preserialize]. + Part of the export process. This method is run after [method _export_preflight] and before [method _export_post_convert]. Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node]. </description> </method> @@ -30,7 +30,7 @@ <param index="3" name="node" type="Node" /> <description> Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _export_post]. If this [GLTFDocumentExtension] is used for exporting images, this runs after [method _serialize_texture_json]. - This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting GLTF data not generated from a Godot scene. + This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting glTF data not generated from a Godot scene. </description> </method> <method name="_export_post" qualifiers="virtual"> @@ -38,7 +38,16 @@ <param index="0" name="state" type="GLTFState" /> <description> Part of the export process. This method is run last, after all other parts of the export process. - This method can be used to modify the final JSON of the generated GLTF file. + This method can be used to modify the final JSON of the generated glTF file. + </description> + </method> + <method name="_export_post_convert" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="root" type="Node" /> + <description> + Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_preserialize]. + This method can be used to modify the converted node data structures before serialization with any additional data from the scene tree. </description> </method> <method name="_export_preflight" qualifiers="virtual"> @@ -47,14 +56,14 @@ <param index="1" name="root" type="Node" /> <description> Part of the export process. This method is run first, before all other parts of the export process. - The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given glTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. </description> </method> <method name="_export_preserialize" qualifiers="virtual"> <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> - Part of the export process. This method is run after [method _convert_scene_node] and before [method _get_saveable_image_formats]. + Part of the export process. This method is run after [method _export_post_convert] and before [method _get_saveable_image_formats]. This method can be used to alter the state before performing serialization. It runs every time when generating a buffer with [method GLTFDocument.generate_buffer] or writing to the file system with [method GLTFDocument.write_to_filesystem]. </description> </method> @@ -64,7 +73,7 @@ <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="scene_parent" type="Node" /> <description> - Part of the import process. This method is run after [method _import_post_parse] and before [method _import_node]. + Part of the import process. This method is run after [method _import_pre_generate] and before [method _import_node]. Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node. [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node. </description> @@ -86,7 +95,7 @@ <return type="PackedStringArray" /> <description> Part of the import process. This method is run after [method _import_preflight] and before [method _parse_node_extensions]. - Returns an array of the GLTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a GLTF file with required extensions can be loaded. + Returns an array of the glTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a glTF file with required extensions can be loaded. </description> </method> <method name="_import_node" qualifiers="virtual"> @@ -113,8 +122,16 @@ <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> - Part of the import process. This method is run after [method _parse_node_extensions] and before [method _generate_scene_node]. - This method can be used to modify any of the data imported so far after parsing, before generating the nodes and then running the final per-node import step. + Part of the import process. This method is run after [method _parse_node_extensions] and before [method _import_pre_generate]. + This method can be used to modify any of the data imported so far after parsing each node, but before generating the scene or any of its nodes. + </description> + </method> + <method name="_import_pre_generate" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="state" type="GLTFState" /> + <description> + Part of the import process. This method is run after [method _import_post_parse] and before [method _generate_scene_node]. + This method can be used to modify or read from any of the processed data structures, before generating the nodes and then running the final per-node import step. </description> </method> <method name="_import_preflight" qualifiers="virtual"> @@ -123,7 +140,7 @@ <param index="1" name="extensions" type="PackedStringArray" /> <description> Part of the import process. This method is run first, before all other parts of the import process. - The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given GLTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given glTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. </description> </method> <method name="_parse_image_data" qualifiers="virtual"> @@ -134,7 +151,7 @@ <param index="3" name="ret_image" type="Image" /> <description> Part of the import process. This method is run after [method _parse_node_extensions] and before [method _parse_texture_json]. - Runs when parsing image data from a GLTF file. The data could be sourced from a separate file, a URI, or a buffer, and then is passed as a byte array. + Runs when parsing image data from a glTF file. The data could be sourced from a separate file, a URI, or a buffer, and then is passed as a byte array. </description> </method> <method name="_parse_node_extensions" qualifiers="virtual"> @@ -154,7 +171,7 @@ <param index="2" name="ret_gltf_texture" type="GLTFTexture" /> <description> Part of the import process. This method is run after [method _parse_image_data] and before [method _generate_scene_node]. - Runs when parsing the texture JSON from the GLTF textures array. This can be used to set the source image index to use as the texture. + Runs when parsing the texture JSON from the glTF textures array. This can be used to set the source image index to use as the texture. </description> </method> <method name="_save_image_at_path" qualifiers="virtual"> @@ -166,7 +183,7 @@ <param index="4" name="lossy_quality" type="float" /> <description> Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json]. - This method is run when saving images separately from the GLTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. + This method is run when saving images separately from the glTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. </description> </method> <method name="_serialize_image_to_bytes" qualifiers="virtual"> @@ -178,7 +195,7 @@ <param index="4" name="lossy_quality" type="float" /> <description> Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json]. - This method is run when embedding images in the GLTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. + This method is run when embedding images in the glTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. This method must set the image MIME type in the [param image_dict] with the [code]"mimeType"[/code] key. For example, for a PNG image, it would be set to [code]"image/png"[/code]. The return value must be a [PackedByteArray] containing the image data. </description> </method> diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml index 87ea159e7c..e07d24a144 100644 --- a/modules/gltf/doc_classes/GLTFLight.xml +++ b/modules/gltf/doc_classes/GLTFLight.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFLight" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF light. + Represents a glTF light. </brief_description> <description> - Represents a light as defined by the [code]KHR_lights_punctual[/code] GLTF extension. + Represents a light as defined by the [code]KHR_lights_punctual[/code] glTF extension. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="KHR_lights_punctual GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link> + <link title="KHR_lights_punctual glTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> @@ -70,7 +70,7 @@ At this angle, the light drops off to zero brightness. Between the inner and outer cone angles, there is a transition from full brightness to zero brightness. If this angle is a half turn, then the spotlight emits in all directions. When creating a Godot [SpotLight3D], the outer cone angle is used as the angle of the spotlight. </member> <member name="range" type="float" setter="set_range" getter="get_range" default="inf"> - The range of the light, beyond which the light has no effect. GLTF lights with no range defined behave like physical lights (which have infinite range). When creating a Godot light, the range is clamped to 4096. + The range of the light, beyond which the light has no effect. glTF lights with no range defined behave like physical lights (which have infinite range). When creating a Godot light, the range is clamped to 4096. </member> </members> </class> diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml index b4c3db7618..da73c20c1d 100644 --- a/modules/gltf/doc_classes/GLTFMesh.xml +++ b/modules/gltf/doc_classes/GLTFMesh.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFMesh" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - GLTFMesh represents a GLTF mesh. + GLTFMesh represents a glTF mesh. </brief_description> <description> - GLTFMesh handles 3D mesh data imported from GLTF files. It includes properties for blend channels, blend weights, instance materials, and the mesh itself. + GLTFMesh handles 3D mesh data imported from glTF files. It includes properties for blend channels, blend weights, instance materials, and the mesh itself. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> @@ -15,7 +15,7 @@ <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="set_additional_data"> @@ -24,7 +24,7 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> </methods> diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 4a7570e4bc..a242a0d1d8 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -1,23 +1,30 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFNode" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - GLTF node class. + glTF node class. </brief_description> <description> - Represents a GLTF node. GLTF nodes may have names, transforms, children (other GLTF nodes), and more specialized properties (represented by their own classes). - GLTF nodes generally exist inside of [GLTFState] which represents all data of a GLTF file. Most of GLTFNode's properties are indices of other data in the GLTF file. You can extend a GLTF node with additional properties by using [method get_additional_data] and [method set_additional_data]. + Represents a glTF node. glTF nodes may have names, transforms, children (other glTF nodes), and more specialized properties (represented by their own classes). + glTF nodes generally exist inside of [GLTFState] which represents all data of a glTF file. Most of GLTFNode's properties are indices of other data in the glTF file. You can extend a glTF node with additional properties by using [method get_additional_data] and [method set_additional_data]. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="GLTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link> + <link title="glTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link> </tutorials> <methods> + <method name="append_child_index"> + <return type="void" /> + <param index="0" name="child_index" type="int" /> + <description> + Appends the given child node index to the [member children] array. + </description> + </method> <method name="get_additional_data"> <return type="Variant" /> <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="set_additional_data"> @@ -26,25 +33,25 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> </methods> <members> <member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1"> - If this GLTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera. + If this glTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera. </member> <member name="children" type="PackedInt32Array" setter="set_children" getter="get_children" default="PackedInt32Array()"> - The indices of the child nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array. + The indices of the child nodes in the [GLTFState]. If this glTF node has no children, this will be an empty array. </member> <member name="height" type="int" setter="set_height" getter="get_height" default="-1"> How deep into the node hierarchy this node is. A root node will have a height of 0, its children will have a height of 1, and so on. If -1, the height has not been calculated. </member> <member name="light" type="int" setter="set_light" getter="get_light" default="-1"> - If this GLTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light. + If this glTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light. </member> <member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1"> - If this GLTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh. + If this glTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh. </member> <member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default=""""> The original name of the node. @@ -53,22 +60,22 @@ The index of the parent node in the [GLTFState]. If -1, this node is a root node. </member> <member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)"> - The position of the GLTF node relative to its parent. + The position of the glTF node relative to its parent. </member> <member name="rotation" type="Quaternion" setter="set_rotation" getter="get_rotation" default="Quaternion(0, 0, 0, 1)"> - The rotation of the GLTF node relative to its parent. + The rotation of the glTF node relative to its parent. </member> <member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3(1, 1, 1)"> - The scale of the GLTF node relative to its parent. + The scale of the glTF node relative to its parent. </member> <member name="skeleton" type="int" setter="set_skeleton" getter="get_skeleton" default="-1"> - If this GLTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton. + If this glTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton. </member> <member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1"> - If this GLTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin. + If this glTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin. </member> <member name="xform" type="Transform3D" setter="set_xform" getter="get_xform" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)"> - The transform of the GLTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred. + The transform of the glTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred. </member> </members> </class> diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml index cd701e2f2f..1a76b190ba 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml @@ -1,21 +1,21 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFPhysicsBody" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF physics body. + Represents a glTF physics body. </brief_description> <description> - Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] glTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different glTF physics extensions in the future. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="OMI_physics_body GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link> + <link title="OMI_physics_body glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> <return type="GLTFPhysicsBody" /> <param index="0" name="dictionary" type="Dictionary" /> <description> - Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] GLTF extension format. + Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] glTF extension format. </description> </method> <method name="from_node" qualifiers="static"> @@ -28,7 +28,7 @@ <method name="to_dictionary" qualifiers="const"> <return type="Dictionary" /> <description> - Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] GLTF extension. + Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] glTF extension. </description> </method> <method name="to_node" qualifiers="const"> diff --git a/modules/gltf/doc_classes/GLTFPhysicsShape.xml b/modules/gltf/doc_classes/GLTFPhysicsShape.xml index a4aaf9415c..53872a942f 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFPhysicsShape" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF physics shape. + Represents a glTF physics shape. </brief_description> <description> - Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] GLTF extensions. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] glTF extensions. This class is an intermediary between the glTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different glTF physics extensions in the future. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="OMI_physics_shape GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link> - <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link> + <link title="OMI_physics_shape glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link> + <link title="OMI_collider glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> @@ -66,7 +66,7 @@ This is the only variable not used in the [method to_node] method, it's intended to be used alongside when deciding where to add the generated node as a child. </member> <member name="mesh_index" type="int" setter="set_mesh_index" getter="get_mesh_index" default="-1"> - The index of the shape's mesh in the GLTF file. This is only used when the shape type is "hull" (convex hull) or "trimesh" (concave trimesh). + The index of the shape's mesh in the glTF file. This is only used when the shape type is "hull" (convex hull) or "trimesh" (concave trimesh). </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5"> The radius of the shape, in meters. This is only used when the shape type is "capsule", "cylinder", or "sphere". This value should not be negative. diff --git a/modules/gltf/doc_classes/GLTFSkeleton.xml b/modules/gltf/doc_classes/GLTFSkeleton.xml index ac03a6ee9e..2dd3a37413 100644 --- a/modules/gltf/doc_classes/GLTFSkeleton.xml +++ b/modules/gltf/doc_classes/GLTFSkeleton.xml @@ -22,7 +22,7 @@ <method name="get_godot_bone_node"> <return type="Dictionary" /> <description> - Returns a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes. + Returns a [Dictionary] that maps skeleton bone indices to the indices of glTF nodes. This property is unused during import, and only set during export. In a glTF file, a bone is a node, so Godot converts skeleton bones to glTF nodes. </description> </method> <method name="get_godot_skeleton"> @@ -39,7 +39,7 @@ <return type="void" /> <param index="0" name="godot_bone_node" type="Dictionary" /> <description> - Sets a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes. + Sets a [Dictionary] that maps skeleton bone indices to the indices of glTF nodes. This property is unused during import, and only set during export. In a glTF file, a bone is a node, so Godot converts skeleton bones to glTF nodes. </description> </method> <method name="set_unique_names"> diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml index 722fa5e9ae..11151f53d0 100644 --- a/modules/gltf/doc_classes/GLTFSpecGloss.xml +++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFSpecGloss" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Archived GLTF extension for specular/glossy materials. + Archived glTF extension for specular/glossy materials. </brief_description> <description> - KHR_materials_pbrSpecularGlossiness is an archived GLTF extension. This means that it is deprecated and not recommended for new files. However, it is still supported for loading old files. + KHR_materials_pbrSpecularGlossiness is an archived glTF extension. This means that it is deprecated and not recommended for new files. However, it is still supported for loading old files. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="KHR_materials_pbrSpecularGlossiness GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link> + <link title="KHR_materials_pbrSpecularGlossiness glTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link> </tutorials> <members> <member name="diffuse_factor" type="Color" setter="set_diffuse_factor" getter="get_diffuse_factor" default="Color(1, 1, 1, 1)"> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 6c7c5ee0e6..de7ec2a4ca 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFState" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents all data of a GLTF file. + Represents all data of a glTF file. </brief_description> <description> - Contains all nodes and resources of a GLTF file. This is used by [GLTFDocument] as data storage, which allows [GLTFDocument] and all [GLTFDocumentExtension] classes to remain stateless. - GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a GLTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the GLTF file or for custom data to be converted to/from Godot nodes. + Contains all nodes and resources of a glTF file. This is used by [GLTFDocument] as data storage, which allows [GLTFDocument] and all [GLTFDocumentExtension] classes to remain stateless. + GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a glTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the glTF file or for custom data to be converted to/from Godot nodes. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="GLTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link> + <link title="glTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link> </tutorials> <methods> <method name="add_used_extension"> @@ -17,7 +17,7 @@ <param index="0" name="extension_name" type="String" /> <param index="1" name="required" type="bool" /> <description> - Appends an extension to the list of extensions used by this GLTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically. + Appends an extension to the list of extensions used by this glTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically. </description> </method> <method name="append_data_to_buffers"> @@ -28,6 +28,17 @@ Appends the given byte array data to the buffers and creates a [GLTFBufferView] for it. The index of the destination [GLTFBufferView] is returned. If [param deduplication] is true, the buffers will first be searched for duplicate data, otherwise new bytes will always be appended. </description> </method> + <method name="append_gltf_node"> + <return type="int" /> + <param index="0" name="gltf_node" type="GLTFNode" /> + <param index="1" name="godot_scene_node" type="Node" /> + <param index="2" name="parent_node_index" type="int" /> + <description> + Append the given [GLTFNode] to the state, and return its new index. This can be used to export one Godot node as multiple glTF nodes, or inject new glTF nodes at import time. On import, this must be called before [method GLTFDocumentExtension._generate_scene_node] finishes for the parent node. On export, this must be called before [method GLTFDocumentExtension._export_node] runs for the parent node. + The [param godot_scene_node] parameter is the Godot scene node that corresponds to this glTF node. This is highly recommended to be set to a valid node, but may be null if there is no corresponding Godot scene node. One Godot scene node may be used for multiple glTF nodes, so if exporting multiple glTF nodes for one Godot scene node, use the same Godot scene node for each. + The [param parent_node_index] parameter is the index of the parent [GLTFNode] in the state. If [code]-1[/code], the node will be a root node, otherwise the new node will be added to the parent's list of children. The index will also be written to the [member GLTFNode.parent] property of the new node. + </description> + </method> <method name="get_accessors"> <return type="GLTFAccessor[]" /> <description> @@ -38,27 +49,27 @@ <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="get_animation_player"> <return type="AnimationPlayer" /> <param index="0" name="idx" type="int" /> <description> - Returns the [AnimationPlayer] node with the given index. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to GLTF animations. + Returns the [AnimationPlayer] node with the given index. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to glTF animations. </description> </method> <method name="get_animation_players_count"> <return type="int" /> <param index="0" name="idx" type="int" /> <description> - Returns the number of [AnimationPlayer] nodes in this [GLTFState]. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to GLTF animations. + Returns the number of [AnimationPlayer] nodes in this [GLTFState]. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to glTF animations. </description> </method> <method name="get_animations"> <return type="GLTFAnimation[]" /> <description> - Returns an array of all [GLTFAnimation]s in the GLTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes. + Returns an array of all [GLTFAnimation]s in the glTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes. </description> </method> <method name="get_buffer_views"> @@ -69,7 +80,7 @@ <method name="get_cameras"> <return type="GLTFCamera[]" /> <description> - Returns an array of all [GLTFCamera]s in the GLTF file. These are the cameras that the [member GLTFNode.camera] index refers to. + Returns an array of all [GLTFCamera]s in the glTF file. These are the cameras that the [member GLTFNode.camera] index refers to. </description> </method> <method name="get_handle_binary_image"> @@ -80,13 +91,13 @@ <method name="get_images"> <return type="Texture2D[]" /> <description> - Gets the images of the GLTF file as an array of [Texture2D]s. These are the images that the [member GLTFTexture.src_image] index refers to. + Gets the images of the glTF file as an array of [Texture2D]s. These are the images that the [member GLTFTexture.src_image] index refers to. </description> </method> <method name="get_lights"> <return type="GLTFLight[]" /> <description> - Returns an array of all [GLTFLight]s in the GLTF file. These are the lights that the [member GLTFNode.light] index refers to. + Returns an array of all [GLTFLight]s in the glTF file. These are the lights that the [member GLTFNode.light] index refers to. </description> </method> <method name="get_materials"> @@ -97,7 +108,7 @@ <method name="get_meshes"> <return type="GLTFMesh[]" /> <description> - Returns an array of all [GLTFMesh]es in the GLTF file. These are the meshes that the [member GLTFNode.mesh] index refers to. + Returns an array of all [GLTFMesh]es in the glTF file. These are the meshes that the [member GLTFNode.mesh] index refers to. </description> </method> <method name="get_node_index"> @@ -111,7 +122,7 @@ <method name="get_nodes"> <return type="GLTFNode[]" /> <description> - Returns an array of all [GLTFNode]s in the GLTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes. + Returns an array of all [GLTFNode]s in the glTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes. </description> </method> <method name="get_scene_node"> @@ -125,19 +136,19 @@ <method name="get_skeletons"> <return type="GLTFSkeleton[]" /> <description> - Returns an array of all [GLTFSkeleton]s in the GLTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to. + Returns an array of all [GLTFSkeleton]s in the glTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to. </description> </method> <method name="get_skins"> <return type="GLTFSkin[]" /> <description> - Returns an array of all [GLTFSkin]s in the GLTF file. These are the skins that the [member GLTFNode.skin] index refers to. + Returns an array of all [GLTFSkin]s in the glTF file. These are the skins that the [member GLTFNode.skin] index refers to. </description> </method> <method name="get_texture_samplers"> <return type="GLTFTextureSampler[]" /> <description> - Retrieves the array of texture samplers that are used by the textures contained in the GLTF. + Retrieves the array of texture samplers that are used by the textures contained in the glTF. </description> </method> <method name="get_textures"> @@ -169,7 +180,7 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> <method name="set_animations"> @@ -250,7 +261,7 @@ <return type="void" /> <param index="0" name="texture_samplers" type="GLTFTextureSampler[]" /> <description> - Sets the array of texture samplers that are used by the textures contained in the GLTF. + Sets the array of texture samplers that are used by the textures contained in the glTF. </description> </method> <method name="set_textures"> @@ -275,18 +286,21 @@ </method> </methods> <members> + <member name="bake_fps" type="float" setter="set_bake_fps" getter="get_bake_fps" default="30.0"> + The baking fps of the animation for either import or export. + </member> <member name="base_path" type="String" setter="set_base_path" getter="get_base_path" default=""""> - The folder path associated with this GLTF data. This is used to find other files the GLTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file. + The folder path associated with this glTF data. This is used to find other files the glTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file. </member> <member name="buffers" type="PackedByteArray[]" setter="set_buffers" getter="get_buffers" default="[]"> </member> <member name="copyright" type="String" setter="set_copyright" getter="get_copyright" default=""""> - The copyright string in the asset header of the GLTF file. This is set during import if present and export if non-empty. See the GLTF asset header documentation for more information. + The copyright string in the asset header of the glTF file. This is set during import if present and export if non-empty. See the glTF asset header documentation for more information. </member> <member name="create_animations" type="bool" setter="set_create_animations" getter="get_create_animations" default="true"> </member> <member name="filename" type="String" setter="set_filename" getter="get_filename" default=""""> - The file name associated with this GLTF data. If it ends with [code].gltf[/code], this is text-based GLTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string. + The file name associated with this glTF data. If it ends with [code].gltf[/code], this is text-based glTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string. </member> <member name="glb_data" type="PackedByteArray" setter="set_glb_data" getter="get_glb_data" default="PackedByteArray()"> The binary buffer attached to a .glb file. @@ -302,10 +316,10 @@ <member name="minor_version" type="int" setter="set_minor_version" getter="get_minor_version" default="0"> </member> <member name="root_nodes" type="PackedInt32Array" setter="set_root_nodes" getter="get_root_nodes" default="PackedInt32Array()"> - The root nodes of the GLTF file. Typically, a GLTF file will only have one scene, and therefore one root node. However, a GLTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene. + The root nodes of the glTF file. Typically, a glTF file will only have one scene, and therefore one root node. However, a glTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene. </member> <member name="scene_name" type="String" setter="set_scene_name" getter="get_scene_name" default=""""> - The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the GLTF file. + The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the glTF file. </member> <member name="use_named_skin_binds" type="bool" setter="set_use_named_skin_binds" getter="get_use_named_skin_binds" default="false"> </member> diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml index 9ad7c0f4c6..2a868a8ba3 100644 --- a/modules/gltf/doc_classes/GLTFTexture.xml +++ b/modules/gltf/doc_classes/GLTFTexture.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFTexture" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - GLTFTexture represents a texture in a GLTF file. + GLTFTexture represents a texture in a glTF file. </brief_description> <description> </description> diff --git a/modules/gltf/doc_classes/GLTFTextureSampler.xml b/modules/gltf/doc_classes/GLTFTextureSampler.xml index 2b5bad6724..d00ab463c2 100644 --- a/modules/gltf/doc_classes/GLTFTextureSampler.xml +++ b/modules/gltf/doc_classes/GLTFTextureSampler.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFTextureSampler" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF texture sampler + Represents a glTF texture sampler </brief_description> <description> - Represents a texture sampler as defined by the base GLTF spec. Texture samplers in GLTF specify how to sample data from the texture's base image, when rendering the texture on an object. + Represents a texture sampler as defined by the base glTF spec. Texture samplers in glTF specify how to sample data from the texture's base image, when rendering the texture on an object. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp index 330310d92a..22c8adfe88 100644 --- a/modules/gltf/editor/editor_import_blend_runner.cpp +++ b/modules/gltf/editor/editor_import_blend_runner.cpp @@ -43,6 +43,7 @@ from xmlrpc.server import SimpleXMLRPCServer req = threading.Condition() res = threading.Condition() info = None +export_err = None def xmlrpc_server(): server = SimpleXMLRPCServer(('127.0.0.1', %d)) server.register_function(export_gltf) @@ -54,6 +55,10 @@ def export_gltf(opts): req.notify() with res: res.wait() + if export_err: + raise export_err + # Important to return a value to prevent the error 'cannot marshal None unless allow_none is enabled'. + return 'BLENDER_GODOT_EXPORT_SUCCESSFUL' if bpy.app.version < (3, 0, 0): print('Blender 3.0 or higher is required.', file=sys.stderr) threading.Thread(target=xmlrpc_server).start() @@ -64,12 +69,13 @@ while True: method, opts = info if method == 'export_gltf': try: + export_err = None bpy.ops.wm.open_mainfile(filepath=opts['path']) if opts['unpack_all']: bpy.ops.file.unpack_all(method='USE_LOCAL') bpy.ops.export_scene.gltf(**opts['gltf_options']) - except: - pass + except Exception as e: + export_err = e info = None with res: res.notify() @@ -184,7 +190,9 @@ Error EditorImportBlendRunner::do_import(const Dictionary &p_options) { EditorSettings::get_singleton()->set_manually("filesystem/import/blender/rpc_port", 0); rpc_port = 0; } - err = do_import_direct(p_options); + if (err != ERR_QUERY_FAILED) { + err = do_import_direct(p_options); + } } return err; } else { @@ -259,6 +267,7 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { // Wait for response. bool done = false; + PackedByteArray response; while (!done) { status = client->get_status(); switch (status) { @@ -268,7 +277,10 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } case HTTPClient::STATUS_BODY: { client->poll(); - // Parse response here if needed. For now we can just ignore it. + response.append_array(client->read_response_body_chunk()); + break; + } + case HTTPClient::STATUS_CONNECTED: { done = true; break; } @@ -278,9 +290,56 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } } + String response_text = "No response from Blender."; + if (response.size() > 0) { + response_text = String::utf8((const char *)response.ptr(), response.size()); + } + + if (client->get_response_code() != HTTPClient::RESPONSE_OK) { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Error received from Blender - status code: %s, error: %s", client->get_response_code(), response_text)); + } else if (response_text.find("BLENDER_GODOT_EXPORT_SUCCESSFUL") < 0) { + // Previous versions of Godot used a Python script where the RPC function did not return + // a value, causing the error 'cannot marshal None unless allow_none is enabled'. + // If an older version of Godot is running and has started Blender with this script, + // we will receive the error, but there's a good chance that the import was successful. + // We are discarding this error to maintain backward compatibility and prevent situations + // where the user needs to close the older version of Godot or kill Blender. + if (response_text.find("cannot marshal None unless allow_none is enabled") < 0) { + String error_message; + if (_extract_error_message_xml(response, error_message)) { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", error_message)); + } else { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", response_text)); + } + } + } + return OK; } +bool EditorImportBlendRunner::_extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message) { + // Based on RPC Xml spec from: https://xmlrpc.com/spec.md + Ref<XMLParser> parser = memnew(XMLParser); + Error err = parser->open_buffer(p_response_data); + if (err) { + return false; + } + + r_error_message = String(); + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + if (parser->get_node_data().size()) { + if (r_error_message.size()) { + r_error_message += " "; + } + r_error_message += parser->get_node_data().trim_suffix("\n"); + } + } + } + + return r_error_message.size(); +} + Error EditorImportBlendRunner::do_import_direct(const Dictionary &p_options) { // Export glTF directly. String python = vformat(PYTHON_SCRIPT_DIRECT, dict_to_python(p_options)); diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h index 626f3c9eba..b3b49ebfb2 100644 --- a/modules/gltf/editor/editor_import_blend_runner.h +++ b/modules/gltf/editor/editor_import_blend_runner.h @@ -47,6 +47,7 @@ class EditorImportBlendRunner : public Node { void _resources_reimported(const PackedStringArray &p_files); void _kill_blender(); void _notification(int p_what); + bool _extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message); protected: int rpc_port = 0; diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp index fee8156375..022d2e4477 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp @@ -107,6 +107,7 @@ void SceneExporterGLTFPlugin::_export_scene_as_gltf(const String &p_file_path) { state->set_copyright(_export_settings->get_copyright()); int32_t flags = 0; flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; + state->set_bake_fps(_export_settings->get_bake_fps()); Error err = _gltf_document->append_from_scene(root, state, flags); if (err != OK) { ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err))); diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp index 16f32af903..511da078d8 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp @@ -182,4 +182,16 @@ void EditorSceneExporterGLTFSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("get_copyright"), &EditorSceneExporterGLTFSettings::get_copyright); ClassDB::bind_method(D_METHOD("set_copyright", "copyright"), &EditorSceneExporterGLTFSettings::set_copyright); ADD_PROPERTY(PropertyInfo(Variant::STRING, "copyright", PROPERTY_HINT_PLACEHOLDER_TEXT, "Example: 2014 Godette"), "set_copyright", "get_copyright"); + + ClassDB::bind_method(D_METHOD("get_bake_fps"), &EditorSceneExporterGLTFSettings::get_bake_fps); + ClassDB::bind_method(D_METHOD("set_bake_fps", "bake_fps"), &EditorSceneExporterGLTFSettings::set_bake_fps); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_fps"), "set_bake_fps", "get_bake_fps"); +} + +double EditorSceneExporterGLTFSettings::get_bake_fps() const { + return _bake_fps; +} + +void EditorSceneExporterGLTFSettings::set_bake_fps(const double p_bake_fps) { + _bake_fps = p_bake_fps; } diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h index e1ce674274..898cddfd68 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h +++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h @@ -42,6 +42,7 @@ class EditorSceneExporterGLTFSettings : public RefCounted { HashMap<String, Ref<GLTFDocumentExtension>> _config_name_to_extension_map; String _copyright; + double _bake_fps = 30.0; protected: static void _bind_methods(); @@ -58,6 +59,9 @@ public: String get_copyright() const; void set_copyright(const String &p_copyright); + + double get_bake_fps() const; + void set_bake_fps(const double p_bake_fps); }; #endif // TOOLS_ENABLED diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index ccf347e03e..8e5a992bd4 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -57,7 +57,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) { if (!FileAccess::exists(p_path)) { if (r_err) { - *r_err = TTR("Path does not contain a Blender installation."); + *r_err = TTR("Path does not point to a valid executable."); } return false; } @@ -67,14 +67,14 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino Error err = OS::get_singleton()->execute(p_path, args, &pipe); if (err != OK) { if (r_err) { - *r_err = TTR("Can't execute Blender binary."); + *r_err = TTR("Couldn't run Blender executable."); } return false; } int bl = pipe.find("Blender "); if (bl == -1) { if (r_err) { - *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), p_path); + *r_err = vformat(TTR("Unexpected --version output from Blender executable at: %s."), p_path); } return false; } @@ -83,7 +83,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino int pp = pipe.find("."); if (pp == -1) { if (r_err) { - *r_err = TTR("Path supplied lacks a Blender binary."); + *r_err = vformat(TTR("Couldn't extract version information from Blender executable at: %s."), p_path); } return false; } @@ -91,7 +91,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino r_major = v.to_int(); if (r_major < 3) { if (r_err) { - *r_err = TTR("This Blender installation is too old for this importer (not 3.0+)."); + *r_err = vformat(TTR("Found Blender version %d.x, which is too old for this importer (3.0+ is required)."), r_major); } return false; } @@ -115,8 +115,15 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ List<String> *r_missing_deps, Error *r_err) { String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); - if (blender_major_version == -1 || blender_minor_version == -1) { - _get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr); + ERR_FAIL_COND_V_MSG(blender_path.is_empty(), nullptr, "Blender path is empty, check your Editor Settings."); + ERR_FAIL_COND_V_MSG(!FileAccess::exists(blender_path), nullptr, vformat("Invalid Blender path: %s, check your Editor Settings.", blender_path)); + + if (blender_major_version == -1 || blender_minor_version == -1 || last_tested_blender_path != blender_path) { + String error; + if (!_get_blender_version(blender_path, blender_major_version, blender_minor_version, &error)) { + ERR_FAIL_V_MSG(nullptr, error); + } + last_tested_blender_path = blender_path; } // Get global paths for source and sink. @@ -132,12 +139,10 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } #endif - source_global = source_global.c_escape(); - const String blend_basename = p_path.get_file().get_basename(); const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( vformat("%s-%s.gltf", blend_basename, p_path.md5_text())); - const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape(); + const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink); // Handle configuration options. @@ -188,10 +193,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } else { parameters_map["export_lights"] = false; } - if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { - parameters_map["export_colors"] = true; + if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 2)) { + if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { + parameters_map["export_vertex_color"] = "MATERIAL"; + } else { + parameters_map["export_vertex_color"] = "NONE"; + } } else { - parameters_map["export_colors"] = false; + if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { + parameters_map["export_colors"] = true; + } else { + parameters_map["export_colors"] = false; + } } if (p_options.has(SNAME("blender/nodes/visible"))) { int32_t visible = p_options["blender/nodes/visible"]; @@ -207,6 +220,9 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ parameters_map["use_renderable"] = false; parameters_map["use_visible"] = false; } + if (p_options.has(SNAME("blender/nodes/active_collection_only")) && p_options[SNAME("blender/nodes/active_collection_only")]) { + parameters_map["use_active_collection"] = true; + } if (p_options.has(SNAME("blender/meshes/uvs")) && p_options[SNAME("blender/meshes/uvs")]) { parameters_map["export_texcoords"] = true; @@ -218,6 +234,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } else { parameters_map["export_normals"] = false; } + + if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 1)) { + if (p_options.has(SNAME("blender/meshes/export_geometry_nodes_instances")) && p_options[SNAME("blender/meshes/export_geometry_nodes_instances")]) { + parameters_map["export_gn_mesh"] = true; + if (blender_major_version == 4 && blender_minor_version == 1) { + // There is a bug in Blender 4.1 where it can't export lights and geometry nodes at the same time, one must be disabled. + parameters_map["export_lights"] = false; + } + } else { + parameters_map["export_gn_mesh"] = false; + } + } if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) { parameters_map["export_tangents"] = true; } else { @@ -308,7 +336,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ #endif } -Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, +Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { if (p_path.get_extension().to_lower() != "blend") { return true; @@ -323,7 +351,8 @@ Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_pa } void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - if (p_path.get_extension().to_lower() != "blend") { + // Returns all the options when path is empty because that means it's for the Project Settings. + if (!p_path.is_empty() && p_path.get_extension().to_lower() != "blend") { return; } #define ADD_OPTION_BOOL(PATH, VALUE) \ @@ -332,6 +361,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, SNAME(PATH), PROPERTY_HINT_ENUM, ENUM_HINT), VALUE)); ADD_OPTION_ENUM("blender/nodes/visible", "All,Visible Only,Renderable", BLEND_VISIBLE_ALL); + ADD_OPTION_BOOL("blender/nodes/active_collection_only", false); ADD_OPTION_BOOL("blender/nodes/punctual_lights", true); ADD_OPTION_BOOL("blender/nodes/cameras", true); ADD_OPTION_BOOL("blender/nodes/custom_properties", true); @@ -339,6 +369,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li ADD_OPTION_BOOL("blender/meshes/colors", false); ADD_OPTION_BOOL("blender/meshes/uvs", true); ADD_OPTION_BOOL("blender/meshes/normals", true); + ADD_OPTION_BOOL("blender/meshes/export_geometry_nodes_instances", false); ADD_OPTION_BOOL("blender/meshes/tangents", true); ADD_OPTION_ENUM("blender/meshes/skins", "None,4 Influences (Compatible),All Influences", BLEND_BONE_INFLUENCES_ALL); ADD_OPTION_BOOL("blender/meshes/export_bones_deforming_mesh_only", false); @@ -381,9 +412,9 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path if (_test_blender_path(p_path, &error)) { success = true; if (auto_detected_path == p_path) { - error = TTR("Path to Blender installation is valid (Autodetected)."); + error = TTR("Path to Blender executable is valid (Autodetected)."); } else { - error = TTR("Path to Blender installation is valid."); + error = TTR("Path to Blender executable is valid."); } } } @@ -391,10 +422,10 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path path_status->set_text(error); if (success) { - path_status->add_theme_color_override("font_color", path_status->get_theme_color(SNAME("success_color"), EditorStringName(Editor))); + path_status->add_theme_color_override(SceneStringName(font_color), path_status->get_theme_color(SNAME("success_color"), EditorStringName(Editor))); configure_blender_dialog->get_ok_button()->set_disabled(false); } else { - path_status->add_theme_color_override("font_color", path_status->get_theme_color(SNAME("error_color"), EditorStringName(Editor))); + path_status->add_theme_color_override(SceneStringName(font_color), path_status->get_theme_color(SNAME("error_color"), EditorStringName(Editor))); configure_blender_dialog->get_ok_button()->set_disabled(true); } } @@ -479,11 +510,15 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { if (!configure_blender_dialog) { configure_blender_dialog = memnew(ConfirmationDialog); configure_blender_dialog->set_title(TTR("Configure Blender Importer")); - configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally . + configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally. configure_blender_dialog->set_close_on_escape(false); + String select_exec_label = TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender executable."); +#ifdef MACOS_ENABLED + select_exec_label += "\n" + TTR("On macOS, this should be the `Contents/MacOS/blender` file within the Blender `.app` folder."); +#endif VBoxContainer *vb = memnew(VBoxContainer); - vb->add_child(memnew(Label(TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender installation:")))); + vb->add_child(memnew(Label(select_exec_label))); HBoxContainer *hb = memnew(HBoxContainer); @@ -506,19 +541,19 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { configure_blender_dialog->add_child(vb); - blender_path->connect("text_changed", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_validate_path)); + blender_path->connect(SceneStringName(text_changed), callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_validate_path)); EditorNode::get_singleton()->get_gui_base()->add_child(configure_blender_dialog); configure_blender_dialog->set_ok_button_text(TTR("Confirm Path")); configure_blender_dialog->set_cancel_button_text(TTR("Disable '.blend' Import")); configure_blender_dialog->get_cancel_button()->set_tooltip_text(TTR("Disables Blender '.blend' files import for this project. Can be re-enabled in Project Settings.")); - configure_blender_dialog->connect("confirmed", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_path_confirmed)); + configure_blender_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_path_confirmed)); browse_dialog = memnew(EditorFileDialog); browse_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); - browse_dialog->connect("dir_selected", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install)); + browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + browse_dialog->connect("file_selected", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install)); EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog); diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index 8a6c65a624..17eb9e5709 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -45,6 +45,7 @@ class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter { int blender_major_version = -1; int blender_minor_version = -1; + String last_tested_blender_path; public: enum { @@ -73,7 +74,7 @@ public: List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; }; diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index e4ed79d15e..41e294cfc6 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -59,6 +59,10 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t int32_t enum_option = p_options["gltf/embedded_image_handling"]; state->set_handle_binary_image(enum_option); } + if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { + state->set_import_as_skeleton_bones(true); + } + state->set_bake_fps(p_options["animation/fps"]); Error err = gltf->append_from_file(p_path, state, p_flags); if (err != OK) { if (r_err) { @@ -72,16 +76,20 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t #ifndef DISABLE_DEPRECATED bool trimming = p_options.has("animation/trimming") ? (bool)p_options["animation/trimming"] : false; - return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, false); + return gltf->generate_scene(state, state->get_bake_fps(), trimming, false); #else - return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], false); + return gltf->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false); #endif } void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.1 or 4.0,Godot 4.2 or later"), 1)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + String file_extension = p_path.get_extension().to_lower(); + // Returns all the options when path is empty because that means it's for the Project Settings. + if (p_path.is_empty() || file_extension == "gltf" || file_extension == "glb") { + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.1 or 4.0,Godot 4.2 or later"), 1)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + } } void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { @@ -92,12 +100,8 @@ void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringN } } -Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, bool p_for_animation, +Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { - String file_extension = p_path.get_extension().to_lower(); - if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) { - return false; - } return true; } diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index d0a9aaf05a..e17b6f3f2e 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -50,7 +50,7 @@ public: virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; }; diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index 9fdd6034a9..6e611762b6 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -38,13 +38,15 @@ void GLTFDocumentExtension::_bind_methods() { GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image"); GDVIRTUAL_BIND(_get_image_file_extension); GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture"); - GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent"); GDVIRTUAL_BIND(_import_post_parse, "state"); + GDVIRTUAL_BIND(_import_pre_generate, "state"); + GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent"); GDVIRTUAL_BIND(_import_node, "state", "gltf_node", "json", "node"); GDVIRTUAL_BIND(_import_post, "state", "root"); // Export process. GDVIRTUAL_BIND(_export_preflight, "state", "root"); GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node"); + GDVIRTUAL_BIND(_export_post_convert, "state", "root"); GDVIRTUAL_BIND(_export_preserialize, "state"); GDVIRTUAL_BIND(_get_saveable_image_formats); GDVIRTUAL_BIND(_serialize_image_to_bytes, "state", "image", "image_dict", "image_format", "lossy_quality"); @@ -56,7 +58,7 @@ void GLTFDocumentExtension::_bind_methods() { // Import process. Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err); return err; @@ -69,16 +71,16 @@ Vector<String> GLTFDocumentExtension::get_supported_extensions() { } Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err); return err; } Error GLTFDocumentExtension::parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(r_image, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(r_image.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_parse_image_data, p_state, p_image_data, p_mime_type, r_image, err); return err; @@ -91,31 +93,38 @@ String GLTFDocumentExtension::get_image_file_extension() { } Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(r_gltf_texture, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(r_gltf_texture.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_parse_texture_json, p_state, p_texture_json, r_gltf_texture, err); return err; } -Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { - ERR_FAIL_NULL_V(p_state, nullptr); - ERR_FAIL_NULL_V(p_gltf_node, nullptr); - Node3D *ret_node = nullptr; - GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node); - return ret_node; -} - Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_post_parse, p_state, err); return err; } +Error GLTFDocumentExtension::import_pre_generate(Ref<GLTFState> p_state) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + Error err = OK; + GDVIRTUAL_CALL(_import_pre_generate, p_state, err); + return err; +} + +Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { + ERR_FAIL_COND_V(p_state.is_null(), nullptr); + ERR_FAIL_COND_V(p_gltf_node.is_null(), nullptr); + Node3D *ret_node = nullptr; + GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node); + return ret_node; +} + Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err); @@ -124,7 +133,7 @@ Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_post, p_state, p_root, err); return err; @@ -139,14 +148,22 @@ Error GLTFDocumentExtension::export_preflight(Ref<GLTFState> p_state, Node *p_ro } void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) { - ERR_FAIL_NULL(p_state); - ERR_FAIL_NULL(p_gltf_node); + ERR_FAIL_COND(p_state.is_null()); + ERR_FAIL_COND(p_gltf_node.is_null()); ERR_FAIL_NULL(p_scene_node); GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node); } +Error GLTFDocumentExtension::export_post_convert(Ref<GLTFState> p_state, Node *p_root) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); + Error err = OK; + GDVIRTUAL_CALL(_export_post_convert, p_state, p_root, err); + return err; +} + Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_export_preserialize, p_state, err); return err; @@ -160,38 +177,38 @@ Vector<String> GLTFDocumentExtension::get_saveable_image_formats() { PackedByteArray GLTFDocumentExtension::serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) { PackedByteArray ret; - ERR_FAIL_NULL_V(p_state, ret); - ERR_FAIL_NULL_V(p_image, ret); + ERR_FAIL_COND_V(p_state.is_null(), ret); + ERR_FAIL_COND_V(p_image.is_null(), ret); GDVIRTUAL_CALL(_serialize_image_to_bytes, p_state, p_image, p_image_dict, p_image_format, p_lossy_quality, ret); return ret; } Error GLTFDocumentExtension::save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_image.is_null(), ERR_INVALID_PARAMETER); Error ret = OK; GDVIRTUAL_CALL(_save_image_at_path, p_state, p_image, p_file_path, p_image_format, p_lossy_quality, ret); return ret; } Error GLTFDocumentExtension::serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_texture, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_texture.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_serialize_texture_json, p_state, p_texture_json, p_gltf_texture, p_image_format, err); return err; } Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err); return err; } Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_export_post, p_state, err); return err; diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h index 761dff725c..b70710e015 100644 --- a/modules/gltf/extensions/gltf_document_extension.h +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -50,12 +50,14 @@ public: virtual String get_image_file_extension(); virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture); virtual Error import_post_parse(Ref<GLTFState> p_state); + virtual Error import_pre_generate(Ref<GLTFState> p_state); virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); virtual Error import_post(Ref<GLTFState> p_state, Node *p_node); // Export process. virtual Error export_preflight(Ref<GLTFState> p_state, Node *p_root); virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node); + virtual Error export_post_convert(Ref<GLTFState> p_state, Node *p_root); virtual Error export_preserialize(Ref<GLTFState> p_state); virtual Vector<String> get_saveable_image_formats(); virtual PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality); @@ -71,13 +73,15 @@ public: GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>); GDVIRTUAL0R(String, _get_image_file_extension); GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>); - GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>); + GDVIRTUAL1R(Error, _import_pre_generate, Ref<GLTFState>); + GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL4R(Error, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); GDVIRTUAL2R(Error, _import_post, Ref<GLTFState>, Node *); // Export process. GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *); GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); + GDVIRTUAL2R(Error, _export_post_convert, Ref<GLTFState>, Node *); GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>); GDVIRTUAL0R(Vector<String>, _get_saveable_image_formats); GDVIRTUAL5R(PackedByteArray, _serialize_image_to_bytes, Ref<GLTFState>, Ref<Image>, Dictionary, String, float); diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp index 1e64a6daa4..cde30bce18 100644 --- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp @@ -34,34 +34,42 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/resources/3d/importer_mesh.h" -void GLTFDocumentExtensionConvertImporterMesh::_bind_methods() { +void GLTFDocumentExtensionConvertImporterMesh::_copy_meta(Object *p_src_object, Object *p_dst_object) { + List<StringName> meta_list; + p_src_object->get_meta_list(&meta_list); + for (const StringName &meta_key : meta_list) { + Variant meta_value = p_src_object->get_meta(meta_key); + p_dst_object->set_meta(meta_key, meta_value); + } } Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_state, Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); List<Node *> queue; queue.push_back(p_root); List<Node *> delete_queue; while (!queue.is_empty()) { List<Node *>::Element *E = queue.front(); Node *node = E->get(); - ImporterMeshInstance3D *mesh_3d = cast_to<ImporterMeshInstance3D>(node); - if (mesh_3d) { - MeshInstance3D *mesh_instance_node_3d = memnew(MeshInstance3D); - Ref<ImporterMesh> mesh = mesh_3d->get_mesh(); + ImporterMeshInstance3D *importer_mesh_3d = Object::cast_to<ImporterMeshInstance3D>(node); + if (importer_mesh_3d) { + Ref<ImporterMesh> mesh = importer_mesh_3d->get_mesh(); if (mesh.is_valid()) { + MeshInstance3D *mesh_instance_node_3d = memnew(MeshInstance3D); Ref<ArrayMesh> array_mesh = mesh->get_mesh(); mesh_instance_node_3d->set_name(node->get_name()); - mesh_instance_node_3d->set_transform(mesh_3d->get_transform()); + mesh_instance_node_3d->set_transform(importer_mesh_3d->get_transform()); mesh_instance_node_3d->set_mesh(array_mesh); - mesh_instance_node_3d->set_skin(mesh_3d->get_skin()); - mesh_instance_node_3d->set_skeleton_path(mesh_3d->get_skeleton_path()); + mesh_instance_node_3d->set_skin(importer_mesh_3d->get_skin()); + mesh_instance_node_3d->set_skeleton_path(importer_mesh_3d->get_skeleton_path()); node->replace_by(mesh_instance_node_3d); + _copy_meta(importer_mesh_3d, mesh_instance_node_3d); + _copy_meta(mesh.ptr(), array_mesh.ptr()); delete_queue.push_back(node); node = mesh_instance_node_3d; } else { - memdelete(mesh_instance_node_3d); + WARN_PRINT("glTF: ImporterMeshInstance3D does not have a valid mesh. This should not happen. Continuing anyway."); } } int child_count = node->get_child_count(); diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h index 1147c2af38..b216a47a7f 100644 --- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h @@ -37,7 +37,7 @@ class GLTFDocumentExtensionConvertImporterMesh : public GLTFDocumentExtension { GDCLASS(GLTFDocumentExtensionConvertImporterMesh, GLTFDocumentExtension); protected: - static void _bind_methods(); + static void _copy_meta(Object *p_src_object, Object *p_dst_object); public: Error import_post(Ref<GLTFState> p_state, Node *p_root) override; diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index c1d2fea98b..f6e91c1635 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -170,7 +170,7 @@ Light3D *GLTFLight::to_node() const { } Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { - ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse GLTF light, missing required field 'type'."); + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse glTF light, missing required field 'type'."); Ref<GLTFLight> light; light.instantiate(); const String &type = p_dictionary["type"]; @@ -181,7 +181,7 @@ Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { if (arr.size() == 3) { light->color = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); } else { - ERR_PRINT("Error parsing GLTF light: The color must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF light: The color must have exactly 3 numbers."); } } if (p_dictionary.has("intensity")) { @@ -195,10 +195,10 @@ Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { light->inner_cone_angle = spot["innerConeAngle"]; light->outer_cone_angle = spot["outerConeAngle"]; if (light->inner_cone_angle >= light->outer_cone_angle) { - ERR_PRINT("Error parsing GLTF light: The inner angle must be smaller than the outer angle."); + ERR_PRINT("Error parsing glTF light: The inner angle must be smaller than the outer angle."); } } else if (type != "point" && type != "directional") { - ERR_PRINT("Error parsing GLTF light: Light type '" + type + "' is unknown."); + ERR_PRINT("Error parsing glTF light: Light type '" + type + "' is unknown."); } return light; } diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp index 7e52cde059..5c26a1686b 100644 --- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp @@ -88,7 +88,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state // "collider" is the index of the collider in the state colliders array. int node_collider_index = node_collider_ext["collider"]; Array state_colliders = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); - ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ")."); + ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), state_colliders[node_collider_index]); } else { p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(node_collider_ext)); @@ -103,7 +103,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state int node_shape_index = node_collider.get("shape", -1); if (node_shape_index != -1) { Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); - ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); + ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]); } else { // If this node is a collider but does not have a collider @@ -117,7 +117,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state int node_shape_index = node_trigger.get("shape", -1); if (node_shape_index != -1) { Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); - ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); + ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]); } else { // If this node is a trigger but does not have a trigger shape, @@ -150,7 +150,7 @@ void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref return; // The mesh resource is already set up. } TypedArray<GLTFMesh> state_meshes = p_state->get_meshes(); - ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); + ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "glTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); Ref<GLTFMesh> gltf_mesh = state_meshes[shape_mesh_index]; ERR_FAIL_COND(gltf_mesh.is_null()); importer_mesh = gltf_mesh->get_mesh(); @@ -164,12 +164,12 @@ CollisionObject3D *_generate_shape_with_body(Ref<GLTFState> p_state, Ref<GLTFNod bool is_trigger = p_physics_shape->get_is_trigger(); // This method is used for the case where we must generate a parent body. // This is can happen for multiple reasons. One possibility is that this - // GLTF file is using OMI_collider but not OMI_physics_body, or at least + // glTF file is using OMI_collider but not OMI_physics_body, or at least // this particular node is not using it. Another possibility is that the - // physics body information is set up on the same GLTF node, not a parent. + // physics body information is set up on the same glTF node, not a parent. CollisionObject3D *body; if (p_physics_body.is_valid()) { - // This code is run when the physics body is on the same GLTF node. + // This code is run when the physics body is on the same glTF node. body = p_physics_body->to_node(); if (is_trigger && (p_physics_body->get_body_type() != "trigger")) { // Edge case: If the body's trigger and the collider's trigger @@ -266,7 +266,7 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state Ref<GLTFPhysicsShape> gltf_physics_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape")); if (gltf_physics_shape.is_valid()) { _setup_shape_mesh_resource_from_index_if_needed(p_state, gltf_physics_shape); - // If this GLTF node specifies both a shape and a body, generate both. + // If this glTF node specifies both a shape and a body, generate both. if (gltf_physics_body.is_valid()) { return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, gltf_physics_body); } @@ -309,7 +309,7 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state } } else if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) { if (p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundCollider"))) { - // If the GLTF file wants this node to group solid shapes together, + // If the glTF file wants this node to group solid shapes together, // and there is no parent body, we need to create a static body. ancestor_col_obj = memnew(StaticBody3D); ret = ancestor_col_obj; @@ -386,7 +386,7 @@ void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Re if (cast_to<CollisionShape3D>(p_scene_node)) { CollisionShape3D *godot_shape = Object::cast_to<CollisionShape3D>(p_scene_node); Ref<GLTFPhysicsShape> gltf_shape = GLTFPhysicsShape::from_node(godot_shape); - ERR_FAIL_COND_MSG(gltf_shape.is_null(), "GLTF Physics: Could not convert CollisionShape3D to GLTFPhysicsShape. Does it have a valid Shape3D?"); + ERR_FAIL_COND_MSG(gltf_shape.is_null(), "glTF Physics: Could not convert CollisionShape3D to GLTFPhysicsShape. Does it have a valid Shape3D?"); { Ref<ImporterMesh> importer_mesh = gltf_shape->get_importer_mesh(); if (importer_mesh.is_valid()) { diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp index 7929b46542..c11aa5d2ff 100644 --- a/modules/gltf/extensions/physics/gltf_physics_body.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -108,7 +108,7 @@ void GLTFPhysicsBody::set_body_type(String p_body_type) { } else if (p_body_type == "trigger") { body_type = PhysicsBodyType::TRIGGER; } else { - ERR_PRINT("Error setting GLTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\"."); + ERR_PRINT("Error setting glTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\"."); } } @@ -194,7 +194,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_ physics_body->center_of_mass = body->get_center_of_mass(); physics_body->inertia_diagonal = body->get_inertia(); if (body->get_center_of_mass() != Vector3()) { - WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to GLTF."); + WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to glTF."); } if (cast_to<VehicleBody3D>(p_body_node)) { physics_body->body_type = PhysicsBodyType::VEHICLE; @@ -289,7 +289,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction physics_body->body_type = PhysicsBodyType::TRIGGER; #endif // DISABLE_DEPRECATED } else { - ERR_PRINT("Error parsing GLTF physics body: The body type in the GLTF file \"" + body_type_string + "\" was not recognized."); + ERR_PRINT("Error parsing glTF physics body: The body type in the glTF file \"" + body_type_string + "\" was not recognized."); } } if (motion.has("mass")) { @@ -300,7 +300,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The linear velocity vector must have exactly 3 numbers."); } } if (motion.has("angularVelocity")) { @@ -308,7 +308,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The angular velocity vector must have exactly 3 numbers."); } } if (motion.has("centerOfMass")) { @@ -316,7 +316,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The center of mass vector must have exactly 3 numbers."); } } if (motion.has("inertiaDiagonal")) { @@ -324,7 +324,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_inertia_diagonal(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The inertia diagonal vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The inertia diagonal vector must have exactly 3 numbers."); } } if (motion.has("inertiaOrientation")) { @@ -332,7 +332,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 4) { physics_body->set_inertia_orientation(Quaternion(arr[0], arr[1], arr[2], arr[3])); } else { - ERR_PRINT("Error parsing GLTF physics body: The inertia orientation quaternion must have exactly 4 numbers."); + ERR_PRINT("Error parsing glTF physics body: The inertia orientation quaternion must have exactly 4 numbers."); } } return physics_body; diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp index 6897bdbd3a..0f2246ce18 100644 --- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp @@ -134,7 +134,7 @@ void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) { Ref<ImporterMesh> _convert_hull_points_to_mesh(const Vector<Vector3> &p_hull_points) { Ref<ImporterMesh> importer_mesh; - ERR_FAIL_COND_V_MSG(p_hull_points.size() < 3, importer_mesh, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(p_hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls."); + ERR_FAIL_COND_V_MSG(p_hull_points.size() < 3, importer_mesh, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(p_hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to glTF, since it uses a mesh to represent convex hulls."); if (p_hull_points.size() > 255) { WARN_PRINT("GLTFPhysicsShape: Convex hull has more points (" + itos(p_hull_points.size()) + ") than the recommended maximum of 255. This may not load correctly in other engines."); } @@ -229,7 +229,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_resource(const Ref<Shape3D> &p_shap } Ref<Shape3D> GLTFPhysicsShape::to_resource(bool p_cache_shapes) { - if (!p_cache_shapes || _shape_cache == nullptr) { + if (!p_cache_shapes || _shape_cache.is_null()) { if (shape_type == "box") { Ref<BoxShape3D> box; box.instantiate(); diff --git a/modules/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h index d044105b42..c1918e5908 100644 --- a/modules/gltf/gltf_defines.h +++ b/modules/gltf/gltf_defines.h @@ -66,14 +66,4 @@ using GLTFSkinIndex = int; using GLTFTextureIndex = int; using GLTFTextureSamplerIndex = int; -enum GLTFType { - TYPE_SCALAR, - TYPE_VEC2, - TYPE_VEC3, - TYPE_VEC4, - TYPE_MAT2, - TYPE_MAT3, - TYPE_MAT4, -}; - #endif // GLTF_DEFINES_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index b92176a63a..992075e980 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -69,6 +69,24 @@ #include <stdlib.h> #include <cstdint> +static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) { + if (!p_extras.is_empty()) { + p_node->set_meta("extras", p_extras); + } +} + +static void _attach_meta_to_extras(Ref<Resource> p_node, Dictionary &p_json) { + if (p_node->has_meta("extras")) { + Dictionary node_extras = p_node->get_meta("extras"); + if (p_json.has("extras")) { + Dictionary extras = p_json["extras"]; + extras.merge(node_extras); + } else { + p_json["extras"] = node_extras; + } + } +} + static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { Ref<ImporterMesh> importer_mesh; importer_mesh.instantiate(); @@ -101,6 +119,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat, mat_name, p_mesh->surface_get_format(surface_i)); } + importer_mesh->merge_meta_from(*p_mesh); return importer_mesh; } @@ -280,8 +299,8 @@ Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> p_state) { } Error GLTFDocument::_parse_glb(Ref<FileAccess> p_file, Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_file, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_file.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_file->get_position() != 0, ERR_FILE_CANT_READ); uint32_t magic = p_file->get_32(); ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF @@ -395,7 +414,6 @@ static Vector<real_t> _xform_to_array(const Transform3D p_transform) { Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) { Array nodes; - const int scene_node_count = p_state->scene_nodes.size(); for (int i = 0; i < p_state->nodes.size(); i++) { Dictionary node; Ref<GLTFNode> gltf_node = p_state->nodes[i]; @@ -446,7 +464,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) { } Node *scene_node = nullptr; - if (i < scene_node_count) { + if (i < (int)p_state->scene_nodes.size()) { scene_node = p_state->scene_nodes[i]; } for (Ref<GLTFDocumentExtension> ext : document_extensions) { @@ -458,7 +476,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) { if (extensions.is_empty()) { node.erase("extensions"); } - + _attach_meta_to_extras(gltf_node, node); nodes.push_back(node); } if (!nodes.is_empty()) { @@ -472,14 +490,8 @@ String GLTFDocument::_gen_unique_name(Ref<GLTFState> p_state, const String &p_na } String GLTFDocument::_sanitize_animation_name(const String &p_name) { - // Animations disallow the normal node invalid characters as well as "," and "[" - // (See animation/animation_player.cpp::add_animation) - - // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node. String anim_name = p_name.validate_node_name(); - anim_name = anim_name.replace(",", ""); - anim_name = anim_name.replace("[", ""); - return anim_name; + return AnimationLibrary::validate_library_name(anim_name); } String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name) { @@ -620,10 +632,14 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) { for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->parse_node_extensions(p_state, node, extensions); - ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing."); + ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing."); } } + if (n.has("extras")) { + _attach_extras_to_meta(n["extras"], node); + } + if (n.has("children")) { const Array &children = n["children"]; for (int j = 0; j < children.size(); j++) { @@ -789,8 +805,9 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> p_state, const String &p_base_ ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. + ERR_FAIL_COND_V_MSG(!FileAccess::exists(uri), ERR_FILE_NOT_FOUND, "glTF: Binary file not found: " + uri); buffer_data = FileAccess::get_file_as_bytes(uri); - ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); + ERR_FAIL_COND_V_MSG(buffer_data.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); } ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); @@ -886,7 +903,7 @@ Error GLTFDocument::_encode_accessors(Ref<GLTFState> p_state) { Ref<GLTFAccessor> accessor = p_state->accessors[i]; d["componentType"] = accessor->component_type; d["count"] = accessor->count; - d["type"] = _get_accessor_type_name(accessor->type); + d["type"] = _get_accessor_type_name(accessor->accessor_type); d["normalized"] = accessor->normalized; d["max"] = accessor->max; d["min"] = accessor->min; @@ -934,58 +951,58 @@ Error GLTFDocument::_encode_accessors(Ref<GLTFState> p_state) { return OK; } -String GLTFDocument::_get_accessor_type_name(const GLTFType p_type) { - if (p_type == GLTFType::TYPE_SCALAR) { +String GLTFDocument::_get_accessor_type_name(const GLTFAccessor::GLTFAccessorType p_accessor_type) { + if (p_accessor_type == GLTFAccessor::TYPE_SCALAR) { return "SCALAR"; } - if (p_type == GLTFType::TYPE_VEC2) { + if (p_accessor_type == GLTFAccessor::TYPE_VEC2) { return "VEC2"; } - if (p_type == GLTFType::TYPE_VEC3) { + if (p_accessor_type == GLTFAccessor::TYPE_VEC3) { return "VEC3"; } - if (p_type == GLTFType::TYPE_VEC4) { + if (p_accessor_type == GLTFAccessor::TYPE_VEC4) { return "VEC4"; } - if (p_type == GLTFType::TYPE_MAT2) { + if (p_accessor_type == GLTFAccessor::TYPE_MAT2) { return "MAT2"; } - if (p_type == GLTFType::TYPE_MAT3) { + if (p_accessor_type == GLTFAccessor::TYPE_MAT3) { return "MAT3"; } - if (p_type == GLTFType::TYPE_MAT4) { + if (p_accessor_type == GLTFAccessor::TYPE_MAT4) { return "MAT4"; } ERR_FAIL_V("SCALAR"); } -GLTFType GLTFDocument::_get_type_from_str(const String &p_string) { +GLTFAccessor::GLTFAccessorType GLTFDocument::_get_accessor_type_from_str(const String &p_string) { if (p_string == "SCALAR") { - return GLTFType::TYPE_SCALAR; + return GLTFAccessor::TYPE_SCALAR; } if (p_string == "VEC2") { - return GLTFType::TYPE_VEC2; + return GLTFAccessor::TYPE_VEC2; } if (p_string == "VEC3") { - return GLTFType::TYPE_VEC3; + return GLTFAccessor::TYPE_VEC3; } if (p_string == "VEC4") { - return GLTFType::TYPE_VEC4; + return GLTFAccessor::TYPE_VEC4; } if (p_string == "MAT2") { - return GLTFType::TYPE_MAT2; + return GLTFAccessor::TYPE_MAT2; } if (p_string == "MAT3") { - return GLTFType::TYPE_MAT3; + return GLTFAccessor::TYPE_MAT3; } if (p_string == "MAT4") { - return GLTFType::TYPE_MAT4; + return GLTFAccessor::TYPE_MAT4; } - ERR_FAIL_V(GLTFType::TYPE_SCALAR); + ERR_FAIL_V(GLTFAccessor::TYPE_SCALAR); } Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) { @@ -1004,7 +1021,7 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) { ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR); accessor->count = d["count"]; ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - accessor->type = _get_type_from_str(d["type"]); + accessor->accessor_type = _get_accessor_type_from_str(d["type"]); if (d.has("bufferView")) { accessor->buffer_view = d["bufferView"]; //optional because it may be sparse... @@ -1088,26 +1105,12 @@ String GLTFDocument::_get_component_type_name(const uint32_t p_component) { return "<Error>"; } -String GLTFDocument::_get_type_name(const GLTFType p_component) { - static const char *names[] = { - "float", - "vec2", - "vec3", - "vec4", - "mat2", - "mat3", - "mat4" - }; - - return names[p_component]; -} - -Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFType p_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) { +Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) { const int component_count_for_type[7] = { 1, 2, 3, 4, 4, 9, 16 }; - const int component_count = component_count_for_type[p_type]; + const int component_count = component_count_for_type[p_accessor_type]; const int component_size = _get_component_type_size(p_component_type); ERR_FAIL_COND_V(component_size == 0, FAILED); @@ -1117,18 +1120,18 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ switch (p_component_type) { case COMPONENT_TYPE_BYTE: case COMPONENT_TYPE_UNSIGNED_BYTE: { - if (p_type == TYPE_MAT2) { + if (p_accessor_type == GLTFAccessor::TYPE_MAT2) { skip_every = 2; skip_bytes = 2; } - if (p_type == TYPE_MAT3) { + if (p_accessor_type == GLTFAccessor::TYPE_MAT3) { skip_every = 3; skip_bytes = 1; } } break; case COMPONENT_TYPE_SHORT: case COMPONENT_TYPE_UNSIGNED_SHORT: { - if (p_type == TYPE_MAT3) { + if (p_accessor_type == GLTFAccessor::TYPE_MAT3) { skip_every = 6; skip_bytes = 4; } @@ -1147,7 +1150,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ stride += 4 - (stride % 4); //according to spec must be multiple of 4 } //use to debug - print_verbose("glTF: encoding type " + _get_type_name(p_type) + " component type: " + _get_component_type_name(p_component_type) + " stride: " + itos(stride) + " amount " + itos(p_count)); + print_verbose("glTF: encoding accessor type " + _get_accessor_type_name(p_accessor_type) + " component type: " + _get_component_type_name(p_component_type) + " stride: " + itos(stride) + " amount " + itos(p_count)); print_verbose("glTF: encoding accessor offset " + itos(p_byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(gltf_buffer.size()) + " view len " + itos(bv->byte_length)); @@ -1310,7 +1313,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ return OK; } -Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFType p_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) { +Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) { const Ref<GLTFBufferView> bv = p_state->buffer_views[p_buffer_view]; int stride = p_element_size; @@ -1328,7 +1331,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c const uint8_t *bufptr = buffer.ptr(); //use to debug - print_verbose("glTF: type " + _get_type_name(p_type) + " component type: " + _get_component_type_name(p_component_type) + " stride: " + itos(stride) + " amount " + itos(p_count)); + print_verbose("glTF: accessor type " + _get_accessor_type_name(p_accessor_type) + " component type: " + _get_component_type_name(p_component_type) + " stride: " + itos(stride) + " amount " + itos(p_count)); print_verbose("glTF: accessor offset " + itos(p_byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv->byte_length)); const int buffer_end = (stride * (p_count - 1)) + p_element_size; @@ -1430,7 +1433,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF 1, 2, 3, 4, 4, 9, 16 }; - const int component_count = component_count_for_type[a->type]; + const int component_count = component_count_for_type[a->accessor_type]; const int component_size = _get_component_type_size(a->component_type); ERR_FAIL_COND_V(component_size == 0, Vector<double>()); int element_size = component_count * component_size; @@ -1441,12 +1444,12 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF switch (a->component_type) { case COMPONENT_TYPE_BYTE: case COMPONENT_TYPE_UNSIGNED_BYTE: { - if (a->type == TYPE_MAT2) { + if (a->accessor_type == GLTFAccessor::TYPE_MAT2) { skip_every = 2; skip_bytes = 2; element_size = 8; //override for this case } - if (a->type == TYPE_MAT3) { + if (a->accessor_type == GLTFAccessor::TYPE_MAT3) { skip_every = 3; skip_bytes = 1; element_size = 12; //override for this case @@ -1454,7 +1457,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF } break; case COMPONENT_TYPE_SHORT: case COMPONENT_TYPE_UNSIGNED_SHORT: { - if (a->type == TYPE_MAT3) { + if (a->accessor_type == GLTFAccessor::TYPE_MAT3) { skip_every = 6; skip_bytes = 4; element_size = 16; //override for this case @@ -1471,7 +1474,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF if (a->buffer_view >= 0) { ERR_FAIL_INDEX_V(a->buffer_view, p_state->buffer_views.size(), Vector<double>()); - const Error err = _decode_buffer_view(p_state, dst, a->buffer_view, skip_every, skip_bytes, element_size, a->count, a->type, component_count, a->component_type, component_size, a->normalized, a->byte_offset, p_for_vertex); + const Error err = _decode_buffer_view(p_state, dst, a->buffer_view, skip_every, skip_bytes, element_size, a->count, a->accessor_type, component_count, a->component_type, component_size, a->normalized, a->byte_offset, p_for_vertex); if (err != OK) { return Vector<double>(); } @@ -1488,14 +1491,14 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF indices.resize(a->sparse_count); const int indices_component_size = _get_component_type_size(a->sparse_indices_component_type); - Error err = _decode_buffer_view(p_state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false); + Error err = _decode_buffer_view(p_state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, GLTFAccessor::TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false); if (err != OK) { return Vector<double>(); } Vector<double> data; data.resize(component_count * a->sparse_count); - err = _decode_buffer_view(p_state, data.ptrw(), a->sparse_values_buffer_view, skip_every, skip_bytes, element_size, a->sparse_count, a->type, component_count, a->component_type, component_size, a->normalized, a->sparse_values_byte_offset, p_for_vertex); + err = _decode_buffer_view(p_state, data.ptrw(), a->sparse_values_buffer_view, skip_every, skip_bytes, element_size, a->sparse_count, a->accessor_type, component_count, a->component_type, component_size, a->normalized, a->sparse_values_byte_offset, p_for_vertex); if (err != OK) { return Vector<double>(); } @@ -1550,7 +1553,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state, p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_SCALAR; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR; int component_type; if (max_index > 65535 || p_for_vertex) { component_type = GLTFDocument::COMPONENT_TYPE_INT; @@ -1562,10 +1565,10 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state, accessor->min = type_min; accessor->normalized = false; accessor->count = ret_size; - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i, p_for_vertex_indices); + Error err = _encode_buffer_view(p_state, attribs.ptr(), attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i, p_for_vertex_indices); if (err != OK) { return -1; } @@ -1664,17 +1667,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state, p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_VEC2; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC2; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = p_attribs.size(); - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -1717,17 +1720,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> p_state p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_VEC4; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = p_attribs.size(); - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -1784,17 +1787,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_sta p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_VEC4; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = p_attribs.size(); - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -1835,17 +1838,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_stat p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_VEC4; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = p_attribs.size(); - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -1888,17 +1891,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_VEC4; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = p_attribs.size(); - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -1963,17 +1966,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_SCALAR; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = ret_size; - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -2013,17 +2016,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state, p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_VEC3; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = p_attribs.size(); - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -2089,12 +2092,12 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_VEC3; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; sparse_accessor->normalized = false; sparse_accessor->count = p_attribs.size(); - sparse_accessor->type = type; + sparse_accessor->accessor_type = accessor_type; sparse_accessor->component_type = component_type; if (p_reference_accessor < p_state->accessors.size() && p_reference_accessor >= 0 && p_state->accessors[p_reference_accessor].is_valid()) { sparse_accessor->byte_offset = p_state->accessors[p_reference_accessor]->byte_offset; @@ -2117,11 +2120,11 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p } else { sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; } - if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFType::TYPE_SCALAR, sparse_accessor->sparse_indices_component_type, sparse_accessor->normalized, sparse_accessor->sparse_indices_byte_offset, false, buffer_view_i_indices) != OK) { + if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFAccessor::TYPE_SCALAR, sparse_accessor->sparse_indices_component_type, sparse_accessor->normalized, sparse_accessor->sparse_indices_byte_offset, false, buffer_view_i_indices) != OK) { return -1; } // We use changed_indices.size() here, because we must pass the number of vec3 values rather than the number of components. - if (_encode_buffer_view(p_state, changed_values.ptr(), changed_indices.size(), sparse_accessor->type, sparse_accessor->component_type, sparse_accessor->normalized, sparse_accessor->sparse_values_byte_offset, false, buffer_view_i_values) != OK) { + if (_encode_buffer_view(p_state, changed_values.ptr(), changed_indices.size(), sparse_accessor->accessor_type, sparse_accessor->component_type, sparse_accessor->normalized, sparse_accessor->sparse_values_byte_offset, false, buffer_view_i_values) != OK) { return -1; } sparse_accessor->sparse_indices_buffer_view = buffer_view_i_indices; @@ -2130,7 +2133,7 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p } else if (changed_indices.size() > 0) { GLTFBufferIndex buffer_view_i; sparse_accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, sparse_accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, sparse_accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -2194,17 +2197,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state p_state->buffers.push_back(Vector<uint8_t>()); } int64_t size = p_state->buffers[0].size(); - const GLTFType type = GLTFType::TYPE_MAT4; + const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_MAT4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; accessor->max = type_max; accessor->min = type_min; accessor->normalized = false; accessor->count = p_attribs.size(); - accessor->type = type; + accessor->accessor_type = accessor_type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), accessor_type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } @@ -2247,10 +2250,10 @@ Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> p_state, co return ret; } - const int type = p_state->accessors[p_accessor]->type; - ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); + const int accessor_type = p_state->accessors[p_accessor]->accessor_type; + ERR_FAIL_COND_V(!(accessor_type == GLTFAccessor::TYPE_VEC3 || accessor_type == GLTFAccessor::TYPE_VEC4), ret); int vec_len = 3; - if (type == TYPE_VEC4) { + if (accessor_type == GLTFAccessor::TYPE_VEC4) { vec_len = 4; } @@ -2740,6 +2743,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { Dictionary e; e["targetNames"] = target_names; + gltf_mesh["extras"] = e; + _attach_meta_to_extras(import_mesh, gltf_mesh); weights.resize(target_names.size()); for (int name_i = 0; name_i < target_names.size(); name_i++) { @@ -2755,8 +2760,6 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED); - gltf_mesh["extras"] = e; - gltf_mesh["primitives"] = primitives; meshes.push_back(gltf_mesh); @@ -2789,6 +2792,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Array primitives = d["primitives"]; const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + _attach_extras_to_meta(extras, mesh); Ref<ImporterMesh> import_mesh; import_mesh.instantiate(); String mesh_name = "mesh"; @@ -3271,7 +3275,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { const int material = p["material"]; ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT); Ref<Material> mat3d = p_state->materials[material]; - ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT); Ref<BaseMaterial3D> base_material = mat3d; if (has_vertex_color && base_material.is_valid()) { @@ -3287,7 +3291,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { } mat = mat3d; } - ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT); mat_name = mat->get_name(); } import_mesh->add_surface(primitive, array, morphs, @@ -3366,7 +3370,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { ERR_CONTINUE(image.is_null()); if (image->is_compressed()) { image->decompress(); - ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "GLTF: Image was compressed, but could not be decompressed."); + ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "glTF: Image was compressed, but could not be decompressed."); } if (p_state->filename.to_lower().ends_with("gltf")) { @@ -3387,7 +3391,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { if (_image_save_extension.is_valid()) { img_name = img_name + _image_save_extension->get_image_file_extension(); Error err = _image_save_extension->save_image_at_path(p_state, image, full_texture_dir.path_join(img_name), _image_format, _lossy_quality); - ERR_FAIL_COND_V_MSG(err != OK, err, "GLTF: Failed to save image in '" + _image_format + "' format as a separate file."); + ERR_FAIL_COND_V_MSG(err != OK, err, "glTF: Failed to save image in '" + _image_format + "' format as a separate file."); } else if (_image_format == "PNG") { img_name = img_name + ".png"; image->save_png(full_texture_dir.path_join(img_name)); @@ -3395,7 +3399,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { img_name = img_name + ".jpg"; image->save_jpg(full_texture_dir.path_join(img_name), _lossy_quality); } else { - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'."); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'."); } image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode(); } else { @@ -3425,9 +3429,9 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { buffer = image->save_jpg_to_buffer(_lossy_quality); image_dict["mimeType"] = "image/jpeg"; } else { - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'."); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'."); } - ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "GLTF: Failed to save image in '" + _image_format + "' format."); + ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "glTF: Failed to save image in '" + _image_format + "' format."); bv->byte_length = buffer.size(); p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length); @@ -3458,7 +3462,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->parse_image_data(p_state, p_bytes, p_mime_type, r_image); - ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing."); + ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing."); if (!r_image->is_empty()) { r_file_extension = ext->get_image_file_extension(); return r_image; @@ -3590,7 +3594,7 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector< } Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_path) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); if (!p_state->json.has("images")) { return OK; } @@ -3749,13 +3753,13 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) { for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->parse_texture_json(p_state, texture_dict, gltf_texture); - ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing."); + ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing."); if (gltf_texture->get_src_image() != -1) { break; } } if (gltf_texture->get_src_image() == -1) { - // No extensions handled it, so use the base GLTF source. + // No extensions handled it, so use the base glTF source. // This may be the fallback, or the only option anyway. ERR_FAIL_COND_V(!texture_dict.has("source"), ERR_PARSE_ERROR); gltf_texture->set_src_image(texture_dict["source"]); @@ -4183,6 +4187,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) { } d["extensions"] = extensions; + _attach_meta_to_extras(material, d); materials.push_back(d); } if (!materials.size()) { @@ -4385,6 +4390,10 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } } + + if (material_dict.has("extras")) { + _attach_extras_to_meta(material_dict["extras"], material); + } p_state->materials.push_back(material); } @@ -4860,7 +4869,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { t["sampler"] = samplers.size(); Dictionary s; Vector<real_t> times; - const double increment = 1.0 / BAKE_FPS; + const double increment = 1.0 / p_state->get_bake_fps(); { double time = 0.0; bool last = false; @@ -5133,7 +5142,11 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshIn ERR_FAIL_COND_V_MSG(p_mesh_instance->get_mesh().is_null(), -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but it has no mesh. This node will be exported without a mesh."); Ref<Mesh> mesh_resource = p_mesh_instance->get_mesh(); ERR_FAIL_COND_V_MSG(mesh_resource->get_surface_count() == 0, -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but its mesh has no surfaces. This node will be exported without a mesh."); - + TypedArray<Material> instance_materials; + for (int32_t surface_i = 0; surface_i < mesh_resource->get_surface_count(); surface_i++) { + Ref<Material> mat = p_mesh_instance->get_active_material(surface_i); + instance_materials.append(mat); + } Ref<ImporterMesh> current_mesh = _mesh_to_importer_mesh(mesh_resource); Vector<float> blend_weights; int32_t blend_count = mesh_resource->get_blend_shape_count(); @@ -5144,17 +5157,6 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshIn Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); - TypedArray<Material> instance_materials; - for (int32_t surface_i = 0; surface_i < current_mesh->get_surface_count(); surface_i++) { - Ref<Material> mat = current_mesh->get_surface_material(surface_i); - if (p_mesh_instance->get_surface_override_material(surface_i).is_valid()) { - mat = p_mesh_instance->get_surface_override_material(surface_i); - } - if (p_mesh_instance->get_material_override().is_valid()) { - mat = p_mesh_instance->get_material_override(); - } - instance_materials.append(mat); - } gltf_mesh->set_instance_materials(instance_materials); gltf_mesh->set_mesh(current_mesh); gltf_mesh->set_blend_weights(blend_weights); @@ -5181,6 +5183,7 @@ ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> p_s return mi; } mi->set_mesh(import_mesh); + import_mesh->merge_meta_from(*mesh); return mi; } @@ -5254,6 +5257,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current, gltf_node.instantiate(); gltf_node->set_original_name(p_current->get_name()); gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name())); + gltf_node->merge_meta_from(p_current); if (cast_to<Node3D>(p_current)) { Node3D *spatial = cast_to<Node3D>(p_current); _convert_spatial(p_state, spatial, gltf_node); @@ -5299,13 +5303,18 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current, ERR_CONTINUE(ext.is_null()); ext->convert_scene_node(p_state, gltf_node, p_current); } - GLTFNodeIndex current_node_i = p_state->nodes.size(); - GLTFNodeIndex gltf_root = p_gltf_root; - if (gltf_root == -1) { - gltf_root = current_node_i; - p_state->root_nodes.push_back(gltf_root); + GLTFNodeIndex current_node_i; + if (gltf_node->get_parent() == -1) { + current_node_i = p_state->append_gltf_node(gltf_node, p_current, p_gltf_parent); + } else if (gltf_node->get_parent() < -1) { + return; + } else { + current_node_i = p_state->nodes.size() - 1; + while (gltf_node != p_state->nodes[current_node_i]) { + current_node_i--; + } } - _create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node); + const GLTFNodeIndex gltf_root = (p_gltf_root == -1) ? current_node_i : p_gltf_root; for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) { _convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root); } @@ -5323,11 +5332,22 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd Ref<ImporterMesh> mesh; mesh.instantiate(); { - Ref<Mesh> csg_mesh = csg->get_meshes()[1]; - + Ref<ArrayMesh> csg_mesh = csg->get_meshes()[1]; for (int32_t surface_i = 0; surface_i < csg_mesh->get_surface_count(); surface_i++) { Array array = csg_mesh->surface_get_arrays(surface_i); - Ref<Material> mat = csg_mesh->surface_get_material(surface_i); + + Ref<Material> mat; + + Ref<Material> mat_override = csg->get_material_override(); + if (mat_override.is_valid()) { + mat = mat_override; + } + + Ref<Material> mat_surface_override = csg_mesh->surface_get_material(surface_i); + if (mat_surface_override.is_valid() && mat.is_null()) { + mat = mat_surface_override; + } + String mat_name; if (mat.is_valid()) { mat_name = mat->get_name(); @@ -5335,6 +5355,7 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd // Assign default material when no material is assigned. mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); } + mesh->add_surface(csg_mesh->surface_get_primitive_type(surface_i), array, csg_mesh->surface_get_blend_shape_arrays(surface_i), csg_mesh->surface_get_lods(surface_i), mat, mat_name, csg_mesh->surface_get_format(surface_i)); @@ -5348,24 +5369,12 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd GLTFMeshIndex mesh_i = p_state->meshes.size(); p_state->meshes.push_back(gltf_mesh); p_gltf_node->mesh = mesh_i; - p_gltf_node->transform = csg->get_meshes()[0]; + p_gltf_node->transform = csg->get_transform(); p_gltf_node->set_original_name(csg->get_name()); p_gltf_node->set_name(_gen_unique_name(p_state, csg->get_name())); } #endif // MODULE_CSG_ENABLED -void GLTFDocument::_create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_parent, GLTFNodeIndex p_current_node_i, - GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref<GLTFNode> p_gltf_node) { - p_state->scene_nodes.insert(p_current_node_i, p_scene_parent); - p_state->nodes.push_back(p_gltf_node); - ERR_FAIL_COND(p_current_node_i == p_parent_node_index); - p_state->nodes.write[p_current_node_i]->parent = p_parent_node_index; - if (p_parent_node_index == -1) { - return; - } - p_state->nodes.write[p_parent_node_index]->children.push_back(p_current_node_i); -} - void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { ERR_FAIL_NULL(p_animation_player); p_state->animation_players.push_back(p_animation_player); @@ -5525,6 +5534,10 @@ void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFS joint_node->set_name(_gen_unique_name(p_state, skeleton->get_bone_name(bone_i))); joint_node->transform = skeleton->get_bone_pose(bone_i); joint_node->joint = true; + + if (p_skeleton3d->has_bone_meta(bone_i, "extras")) { + joint_node->set_meta("extras", p_skeleton3d->get_bone_meta(bone_i, "extras")); + } GLTFNodeIndex current_node_i = p_state->nodes.size(); p_state->scene_nodes.insert(current_node_i, skeleton); p_state->nodes.push_back(joint_node); @@ -5639,7 +5652,7 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn // If none of our GLTFDocumentExtension classes generated us a node, we generate one. if (!current_node) { if (gltf_node->skin >= 0 && gltf_node->mesh >= 0 && !gltf_node->children.is_empty()) { - // GLTF specifies that skinned meshes should ignore their node transforms, + // glTF specifies that skinned meshes should ignore their node transforms, // only being controlled by the skeleton, so Godot will reparent a skinned // mesh to its skeleton. However, we still need to ensure any child nodes // keep their place in the tree, so if there are any child nodes, the skinned @@ -5666,6 +5679,15 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn if (p_scene_root == nullptr) { // If the root node argument is null, this is the root node. p_scene_root = current_node; + // If multiple nodes were generated under the root node, ensure they have the owner set. + if (unlikely(current_node->get_child_count() > 0)) { + Array args; + args.append(p_scene_root); + for (int i = 0; i < current_node->get_child_count(); i++) { + Node *child = current_node->get_child(i); + child->propagate_call(StringName("set_owner"), args); + } + } } else { // Add the node we generated and set the owner to the scene root. p_scene_parent->add_child(current_node, true); @@ -5675,6 +5697,8 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn current_node->set_transform(gltf_node->transform); } + current_node->merge_meta_from(*gltf_node); + p_state->scene_nodes.insert(p_node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { _generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root); @@ -5794,15 +5818,17 @@ struct SceneFormatImporterGLTFInterpolate { return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); } - T bezier(T start, T control_1, T control_2, T end, float t) { - /* Formula from Wikipedia article on Bezier curves. */ - const real_t omt = (1.0 - t); - const real_t omt2 = omt * omt; - const real_t omt3 = omt2 * omt; + T hermite(T start, T tan_start, T end, T tan_end, float t) { + /* Formula from the glTF 2.0 specification. */ const real_t t2 = t * t; const real_t t3 = t2 * t; - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; + const real_t h00 = 2.0 * t3 - 3.0 * t2 + 1.0; + const real_t h10 = t3 - 2.0 * t2 + t; + const real_t h01 = -2.0 * t3 + 3.0 * t2; + const real_t h11 = t3 - t2; + + return start * h00 + tan_start * h10 + end * h01 + tan_end * h11; } }; @@ -5823,7 +5849,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> { return p1.slerp(p2, c).normalized(); } - Quaternion bezier(const Quaternion start, const Quaternion control_1, const Quaternion control_2, const Quaternion end, const float t) { + Quaternion hermite(const Quaternion start, const Quaternion tan_start, const Quaternion end, const Quaternion tan_end, const float t) { ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quaternion(), vformat("The start quaternion %s must be normalized.", start)); ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quaternion(), vformat("The end quaternion %s must be normalized.", end)); @@ -5888,21 +5914,23 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T return p_values[(p_times.size() - 1) * 3 + 1]; } - const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); + const float td = (p_times[idx + 1] - p_times[idx]); + const float c = (p_time - p_times[idx]) / td; const T &from = p_values[idx * 3 + 1]; - const T c1 = from + p_values[idx * 3 + 2]; + const T tan_from = td * p_values[idx * 3 + 2]; const T &to = p_values[idx * 3 + 4]; - const T c2 = to + p_values[idx * 3 + 3]; + const T tan_to = td * p_values[idx * 3 + 3]; - return interp.bezier(from, c1, c2, to, c); + return interp.hermite(from, tan_from, to, tan_to, c); } break; } ERR_FAIL_V(p_values[0]); } -void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks) { +void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks) { + ERR_FAIL_COND(p_state.is_null()); Ref<GLTFAnimation> anim = p_state->animations[p_index]; String anim_name = anim->get_name(); @@ -5914,7 +5942,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ Ref<Animation> animation; animation.instantiate(); animation->set_name(anim_name); - animation->set_step(1.0 / p_bake_fps); + animation->set_step(1.0 / p_state->get_bake_fps()); if (anim->get_loop()) { animation->set_loop_mode(Animation::LOOP_LINEAR); @@ -6081,7 +6109,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ } } - const double increment = 1.0 / p_bake_fps; + const double increment = 1.0 / p_state->get_bake_fps(); double time = anim_start; Vector3 base_pos; @@ -6158,7 +6186,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ } } else { // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / p_bake_fps; + const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { @@ -6336,6 +6364,7 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene ERR_CONTINUE_MSG(skeleton == nullptr, vformat("Unable to find Skeleton for node %d skin %d", node_i, skin_i)); mi->get_parent()->remove_child(mi); + mi->set_owner(nullptr); skeleton->add_child(mi, true); mi->set_owner(p_scene_root); @@ -6372,7 +6401,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta p_track.scale_track.times.clear(); p_track.scale_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / BAKE_FPS; + const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { @@ -6407,7 +6436,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta p_track.position_track.times.clear(); p_track.position_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / BAKE_FPS; + const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { @@ -6442,7 +6471,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta p_track.rotation_track.times.clear(); p_track.rotation_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / BAKE_FPS; + const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { @@ -6482,7 +6511,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta p_track.position_track.times.clear(); p_track.position_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / BAKE_FPS; + const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { @@ -6515,7 +6544,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta p_track.rotation_track.times.clear(); p_track.rotation_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / BAKE_FPS; + const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { @@ -6551,7 +6580,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta p_track.scale_track.times.clear(); p_track.scale_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / BAKE_FPS; + const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { @@ -6577,14 +6606,14 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta } } } else if (track_type == Animation::TYPE_BEZIER) { - const int32_t keys = anim_end * BAKE_FPS; + const int32_t keys = anim_end * p_state->get_bake_fps(); if (path.contains(":scale")) { if (!p_track.scale_track.times.size()) { p_track.scale_track.interpolation = gltf_interpolation; Vector<real_t> new_times; new_times.resize(keys); for (int32_t key_i = 0; key_i < keys; key_i++) { - new_times.write[key_i] = key_i / BAKE_FPS; + new_times.write[key_i] = key_i / p_state->get_bake_fps(); } p_track.scale_track.times = new_times; @@ -6597,11 +6626,11 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.scale_track.values[key_i]; if (path.contains(":scale:x")) { - bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } else if (path.contains(":scale:y")) { - bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } else if (path.contains(":scale:z")) { - bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } p_track.scale_track.values.write[key_i] = bezier_track; } @@ -6612,7 +6641,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta Vector<real_t> new_times; new_times.resize(keys); for (int32_t key_i = 0; key_i < keys; key_i++) { - new_times.write[key_i] = key_i / BAKE_FPS; + new_times.write[key_i] = key_i / p_state->get_bake_fps(); } p_track.position_track.times = new_times; @@ -6622,11 +6651,11 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.position_track.values[key_i]; if (path.contains(":position:x")) { - bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } else if (path.contains(":position:y")) { - bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } else if (path.contains(":position:z")) { - bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } p_track.position_track.values.write[key_i] = bezier_track; } @@ -6636,7 +6665,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta Vector<real_t> new_times; new_times.resize(keys); for (int32_t key_i = 0; key_i < keys; key_i++) { - new_times.write[key_i] = key_i / BAKE_FPS; + new_times.write[key_i] = key_i / p_state->get_bake_fps(); } p_track.rotation_track.times = new_times; @@ -6645,13 +6674,13 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta for (int32_t key_i = 0; key_i < keys; key_i++) { Quaternion bezier_track = p_track.rotation_track.values[key_i]; if (path.contains(":rotation:x")) { - bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } else if (path.contains(":rotation:y")) { - bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } else if (path.contains(":rotation:z")) { - bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } else if (path.contains(":rotation:w")) { - bezier_track.w = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); + bezier_track.w = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); } p_track.rotation_track.values.write[key_i] = bezier_track; } @@ -6928,14 +6957,14 @@ Dictionary _serialize_texture_transform_uv(Vector2 p_offset, Vector2 p_scale) { } Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material) { - ERR_FAIL_NULL_V(p_material, Dictionary()); + ERR_FAIL_COND_V(p_material.is_null(), Dictionary()); Vector3 offset = p_material->get_uv1_offset(); Vector3 scale = p_material->get_uv1_scale(); return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); } Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material) { - ERR_FAIL_NULL_V(p_material, Dictionary()); + ERR_FAIL_COND_V(p_material.is_null(), Dictionary()); Vector3 offset = p_material->get_uv2_offset(); Vector3 scale = p_material->get_uv2_scale(); return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); @@ -7054,6 +7083,8 @@ void GLTFDocument::_bind_methods() { &GLTFDocument::register_gltf_document_extension, DEFVAL(false)); ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"), &GLTFDocument::unregister_gltf_document_extension); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("get_supported_gltf_extensions"), + &GLTFDocument::get_supported_gltf_extensions); } void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> p_state) { @@ -7094,6 +7125,36 @@ Vector<Ref<GLTFDocumentExtension>> GLTFDocument::get_all_gltf_document_extension return all_document_extensions; } +Vector<String> GLTFDocument::get_supported_gltf_extensions() { + HashSet<String> set = get_supported_gltf_extensions_hashset(); + Vector<String> vec; + for (const String &s : set) { + vec.append(s); + } + vec.sort(); + return vec; +} + +HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() { + HashSet<String> supported_extensions; + // If the extension is supported directly in GLTFDocument, list it here. + // Other built-in extensions are supported by GLTFDocumentExtension classes. + supported_extensions.insert("GODOT_single_root"); + supported_extensions.insert("KHR_lights_punctual"); + supported_extensions.insert("KHR_materials_emissive_strength"); + supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); + supported_extensions.insert("KHR_materials_unlit"); + supported_extensions.insert("KHR_texture_transform"); + for (Ref<GLTFDocumentExtension> ext : all_document_extensions) { + ERR_CONTINUE(ext.is_null()); + Vector<String> ext_supported_extensions = ext->get_supported_extensions(); + for (int i = 0; i < ext_supported_extensions.size(); ++i) { + supported_extensions.insert(ext_supported_extensions[i]); + } + } + return supported_extensions; +} + PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err) { Error err = _encode_buffer_glb(p_state, ""); if (r_err) { @@ -7141,9 +7202,15 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { HashMap<ObjectID, SkinSkeletonIndex> skeleton_map; Error err = SkinTool::_create_skeletons(p_state->unique_names, p_state->skins, p_state->nodes, skeleton_map, p_state->skeletons, p_state->scene_nodes); - ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skeletons."); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "glTF: Failed to create skeletons."); err = _create_skins(p_state); - ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skins."); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "glTF: Failed to create skins."); + // Run pre-generate for each extension, in case an extension needs to do something before generating the scene. + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + err = ext->import_pre_generate(p_state); + ERR_CONTINUE(err != OK); + } // Generate the node tree. Node *single_root; if (p_state->extensions_used.has("GODOT_single_root")) { @@ -7274,7 +7341,7 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) { Ref<GLTFState> state = p_state; - ERR_FAIL_NULL_V(state, PackedByteArray()); + ERR_FAIL_COND_V(state.is_null(), PackedByteArray()); // For buffers, set the state filename to an empty string, but // don't touch the base path, in case the user set it manually. state->filename = ""; @@ -7286,7 +7353,7 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) { Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) { Ref<GLTFState> state = p_state; - ERR_FAIL_NULL_V(state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(state.is_null(), ERR_INVALID_PARAMETER); state->base_path = p_path.get_base_dir(); state->filename = p_path.get_file(); Error err = _serialize(state); @@ -7302,9 +7369,10 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) { Ref<GLTFState> state = p_state; - ERR_FAIL_NULL_V(state, nullptr); + ERR_FAIL_COND_V(state.is_null(), nullptr); ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); Error err = OK; + p_state->set_bake_fps(p_bake_fps); Node *root = _generate_scene_node_tree(state); ERR_FAIL_NULL_V(root, nullptr); _process_mesh_instances(state, root); @@ -7313,7 +7381,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, boo root->add_child(ap, true); ap->set_owner(root); for (int i = 0; i < state->animations.size(); i++) { - _import_animation(state, ap, i, p_bake_fps, p_trimming, p_remove_immutable_tracks); + _import_animation(state, ap, i, p_trimming, p_remove_immutable_tracks); } } for (KeyValue<GLTFNodeIndex, Node *> E : state->scene_nodes) { @@ -7377,6 +7445,12 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint state->extensions_used.append("GODOT_single_root"); } _convert_scene_node(state, p_node, -1, -1); + // Run post-convert for each extension, in case an extension needs to do something after converting the scene. + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + Error err = ext->export_post_convert(p_state, p_node); + ERR_CONTINUE(err != OK); + } return OK; } @@ -7419,7 +7493,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); - ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN); + ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN); String base_path = p_base_path; if (base_path.is_empty()) { base_path = p_path.get_base_dir(); @@ -7436,7 +7510,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint } Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_PARSE_ERROR); + ERR_FAIL_COND_V(p_state.is_null(), ERR_PARSE_ERROR); if (p_state->json.has("extensionsUsed")) { Vector<String> ext_array = p_state->json["extensionsUsed"]; p_state->extensions_used = ext_array; @@ -7445,23 +7519,11 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { Vector<String> ext_array = p_state->json["extensionsRequired"]; p_state->extensions_required = ext_array; } - HashSet<String> supported_extensions; - supported_extensions.insert("KHR_lights_punctual"); - supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); - supported_extensions.insert("KHR_texture_transform"); - supported_extensions.insert("KHR_materials_unlit"); - supported_extensions.insert("KHR_materials_emissive_strength"); - for (Ref<GLTFDocumentExtension> ext : document_extensions) { - ERR_CONTINUE(ext.is_null()); - Vector<String> ext_supported_extensions = ext->get_supported_extensions(); - for (int i = 0; i < ext_supported_extensions.size(); ++i) { - supported_extensions.insert(ext_supported_extensions[i]); - } - } + HashSet<String> supported_extensions = get_supported_gltf_extensions_hashset(); Error ret = OK; for (int i = 0; i < p_state->extensions_required.size(); i++) { if (!supported_extensions.has(p_state->extensions_required[i])) { - ERR_PRINT("GLTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); + ERR_PRINT("glTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); ret = ERR_UNAVAILABLE; } } diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 1901e89d51..d347d49102 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -75,7 +75,6 @@ public: }; private: - const float BAKE_FPS = 30.0f; int _naming_version = 1; String _image_format = "PNG"; float _lossy_quality = 0.75f; @@ -93,6 +92,8 @@ public: static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension); static void unregister_all_gltf_document_extensions(); static Vector<Ref<GLTFDocumentExtension>> get_all_gltf_document_extensions(); + static Vector<String> get_supported_gltf_extensions(); + static HashSet<String> get_supported_gltf_extensions_hashset(); void set_naming_version(int p_version); int get_naming_version() const; @@ -112,8 +113,7 @@ private: int _get_component_type_size(const int p_component_type); Error _parse_scenes(Ref<GLTFState> p_state); Error _parse_nodes(Ref<GLTFState> p_state); - String _get_type_name(const GLTFType p_component); - String _get_accessor_type_name(const GLTFType p_type); + String _get_accessor_type_name(const GLTFAccessor::GLTFAccessorType p_accessor_type); String _sanitize_animation_name(const String &p_name); String _gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name); String _sanitize_bone_name(const String &p_name); @@ -133,13 +133,13 @@ private: void _compute_node_heights(Ref<GLTFState> p_state); Error _parse_buffers(Ref<GLTFState> p_state, const String &p_base_path); Error _parse_buffer_views(Ref<GLTFState> p_state); - GLTFType _get_type_from_str(const String &p_string); + GLTFAccessor::GLTFAccessorType _get_accessor_type_from_str(const String &p_string); Error _parse_accessors(Ref<GLTFState> p_state); Error _decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, - const GLTFType p_type, const int p_component_count, + const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex); @@ -268,7 +268,7 @@ private: const Vector<Transform3D> p_attribs, const bool p_for_vertex); Error _encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, - const int p_count, const GLTFType p_type, + const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_indices = false); @@ -328,7 +328,7 @@ public: void _generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); void _generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); void _import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, - const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks); + const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks); void _convert_mesh_instances(Ref<GLTFState> p_state); GLTFCameraIndex _convert_camera(Ref<GLTFState> p_state, Camera3D *p_camera); void _convert_light_to_gltf(Light3D *p_light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node); @@ -342,12 +342,6 @@ public: void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state); #endif // MODULE_CSG_ENABLED - void _create_gltf_node(Ref<GLTFState> p_state, - Node *p_scene_parent, - GLTFNodeIndex p_current_node_i, - GLTFNodeIndex p_parent_node_index, - GLTFNodeIndex p_root_gltf_node, - Ref<GLTFNode> p_gltf_node); void _convert_animation_player_to_gltf( AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index ed31aadc01..7763874d02 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -35,6 +35,7 @@ void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("add_used_extension", "extension_name", "required"), &GLTFState::add_used_extension); ClassDB::bind_method(D_METHOD("append_data_to_buffers", "data", "deduplication"), &GLTFState::append_data_to_buffers); + ClassDB::bind_method(D_METHOD("append_gltf_node", "gltf_node", "godot_scene_node", "parent_node_index"), &GLTFState::append_gltf_node); ClassDB::bind_method(D_METHOD("get_json"), &GLTFState::get_json); ClassDB::bind_method(D_METHOD("set_json", "json"), &GLTFState::set_json); @@ -100,6 +101,8 @@ void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFState::set_additional_data); ClassDB::bind_method(D_METHOD("get_handle_binary_image"), &GLTFState::get_handle_binary_image); ClassDB::bind_method(D_METHOD("set_handle_binary_image", "method"), &GLTFState::set_handle_binary_image); + ClassDB::bind_method(D_METHOD("set_bake_fps", "value"), &GLTFState::set_bake_fps); + ClassDB::bind_method(D_METHOD("get_bake_fps"), &GLTFState::get_bake_fps); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "json"), "set_json", "get_json"); // Dictionary ADD_PROPERTY(PropertyInfo(Variant::INT, "major_version"), "set_major_version", "get_major_version"); // int @@ -130,6 +133,7 @@ void GLTFState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "import_as_skeleton_bones"), "set_import_as_skeleton_bones", "get_import_as_skeleton_bones"); // bool ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector<Ref<GLTFAnimation>> ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_fps"), "set_bake_fps", "get_bake_fps"); BIND_CONSTANT(HANDLE_BINARY_DISCARD_TEXTURES); BIND_CONSTANT(HANDLE_BINARY_EXTRACT_TEXTURES); @@ -438,3 +442,16 @@ GLTFBufferViewIndex GLTFState::append_data_to_buffers(const Vector<uint8_t> &p_d buffer_views.push_back(buffer_view); return new_index; } + +GLTFNodeIndex GLTFState::append_gltf_node(Ref<GLTFNode> p_gltf_node, Node *p_godot_scene_node, GLTFNodeIndex p_parent_node_index) { + p_gltf_node->set_parent(p_parent_node_index); + const GLTFNodeIndex new_index = nodes.size(); + nodes.append(p_gltf_node); + scene_nodes.insert(new_index, p_godot_scene_node); + if (p_parent_node_index == -1) { + root_nodes.append(new_index); + } else if (p_parent_node_index < new_index) { + nodes.write[p_parent_node_index]->append_child_index(new_index); + } + return new_index; +} diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index c9efffa3ae..7954049192 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -57,6 +57,7 @@ protected: int minor_version = 0; String copyright; Vector<uint8_t> glb_data; + double bake_fps = 30.0; bool use_named_skin_binds = false; bool use_khr_texture_transform = false; @@ -108,8 +109,17 @@ protected: static void _bind_methods(); public: + double get_bake_fps() const { + return bake_fps; + } + + void set_bake_fps(double value) { + bake_fps = value; + } + void add_used_extension(const String &p_extension, bool p_required = false); GLTFBufferViewIndex append_data_to_buffers(const Vector<uint8_t> &p_data, const bool p_deduplication); + GLTFNodeIndex append_gltf_node(Ref<GLTFNode> p_gltf_node, Node *p_godot_scene_node, GLTFNodeIndex p_parent_node_index); enum GLTFHandleBinary { HANDLE_BINARY_DISCARD_TEXTURES = 0, diff --git a/modules/gltf/skin_tool.cpp b/modules/gltf/skin_tool.cpp index a344334d93..1522c0e324 100644 --- a/modules/gltf/skin_tool.cpp +++ b/modules/gltf/skin_tool.cpp @@ -602,6 +602,11 @@ Error SkinTool::_create_skeletons( skeleton->set_bone_pose_rotation(bone_index, node->transform.basis.get_rotation_quaternion()); skeleton->set_bone_pose_scale(bone_index, node->transform.basis.get_scale()); + // Store bone-level GLTF extras in skeleton per bone meta. + if (node->has_meta("extras")) { + skeleton->set_bone_meta(bone_index, "extras", node->get_meta("extras")); + } + if (node->parent >= 0 && nodes[node->parent]->skeleton == skel_i) { const int bone_parent = skeleton->find_bone(nodes[node->parent]->get_name()); ERR_FAIL_COND_V(bone_parent < 0, FAILED); diff --git a/modules/gltf/structures/gltf_accessor.cpp b/modules/gltf/structures/gltf_accessor.cpp index 2119a0ee82..1ebc00a514 100644 --- a/modules/gltf/structures/gltf_accessor.cpp +++ b/modules/gltf/structures/gltf_accessor.cpp @@ -31,6 +31,14 @@ #include "gltf_accessor.h" void GLTFAccessor::_bind_methods() { + BIND_ENUM_CONSTANT(TYPE_SCALAR); + BIND_ENUM_CONSTANT(TYPE_VEC2); + BIND_ENUM_CONSTANT(TYPE_VEC3); + BIND_ENUM_CONSTANT(TYPE_VEC4); + BIND_ENUM_CONSTANT(TYPE_MAT2); + BIND_ENUM_CONSTANT(TYPE_MAT3); + BIND_ENUM_CONSTANT(TYPE_MAT4); + ClassDB::bind_method(D_METHOD("get_buffer_view"), &GLTFAccessor::get_buffer_view); ClassDB::bind_method(D_METHOD("set_buffer_view", "buffer_view"), &GLTFAccessor::set_buffer_view); ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFAccessor::get_byte_offset); @@ -41,6 +49,8 @@ void GLTFAccessor::_bind_methods() { ClassDB::bind_method(D_METHOD("set_normalized", "normalized"), &GLTFAccessor::set_normalized); ClassDB::bind_method(D_METHOD("get_count"), &GLTFAccessor::get_count); ClassDB::bind_method(D_METHOD("set_count", "count"), &GLTFAccessor::set_count); + ClassDB::bind_method(D_METHOD("get_accessor_type"), &GLTFAccessor::get_accessor_type); + ClassDB::bind_method(D_METHOD("set_accessor_type", "accessor_type"), &GLTFAccessor::set_accessor_type); ClassDB::bind_method(D_METHOD("get_type"), &GLTFAccessor::get_type); ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFAccessor::set_type); ClassDB::bind_method(D_METHOD("get_min"), &GLTFAccessor::get_min); @@ -65,7 +75,8 @@ void GLTFAccessor::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "component_type"), "set_component_type", "get_component_type"); // int ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalized"), "set_normalized", "get_normalized"); // bool ADD_PROPERTY(PropertyInfo(Variant::INT, "count"), "set_count", "get_count"); // int - ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_type", "get_type"); // GLTFType + ADD_PROPERTY(PropertyInfo(Variant::INT, "accessor_type"), "set_accessor_type", "get_accessor_type"); // GLTFAccessor::GLTFAccessorType + ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_type", "get_type"); // Deprecated, int for GLTFAccessor::GLTFAccessorType ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT64_ARRAY, "min"), "set_min", "get_min"); // Vector<real_t> ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT64_ARRAY, "max"), "set_max", "get_max"); // Vector<real_t> ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_count"), "set_sparse_count", "get_sparse_count"); // int @@ -116,12 +127,20 @@ void GLTFAccessor::set_count(int p_count) { count = p_count; } +GLTFAccessor::GLTFAccessorType GLTFAccessor::get_accessor_type() { + return accessor_type; +} + +void GLTFAccessor::set_accessor_type(GLTFAccessorType p_accessor_type) { + accessor_type = p_accessor_type; +} + int GLTFAccessor::get_type() { - return (int)type; + return (int)accessor_type; } -void GLTFAccessor::set_type(int p_type) { - type = (GLTFType)p_type; // TODO: Register enum +void GLTFAccessor::set_type(int p_accessor_type) { + accessor_type = (GLTFAccessorType)p_accessor_type; // TODO: Register enum } Vector<double> GLTFAccessor::get_min() { diff --git a/modules/gltf/structures/gltf_accessor.h b/modules/gltf/structures/gltf_accessor.h index 6b1734601a..1a3a2cb494 100644 --- a/modules/gltf/structures/gltf_accessor.h +++ b/modules/gltf/structures/gltf_accessor.h @@ -39,13 +39,24 @@ struct GLTFAccessor : public Resource { GDCLASS(GLTFAccessor, Resource); friend class GLTFDocument; +public: + enum GLTFAccessorType { + TYPE_SCALAR, + TYPE_VEC2, + TYPE_VEC3, + TYPE_VEC4, + TYPE_MAT2, + TYPE_MAT3, + TYPE_MAT4, + }; + private: GLTFBufferViewIndex buffer_view = -1; int byte_offset = 0; int component_type = 0; bool normalized = false; int count = 0; - GLTFType type = GLTFType::TYPE_SCALAR; + GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_SCALAR; Vector<double> min; Vector<double> max; int sparse_count = 0; @@ -74,8 +85,11 @@ public: int get_count(); void set_count(int p_count); + GLTFAccessorType get_accessor_type(); + void set_accessor_type(GLTFAccessorType p_accessor_type); + int get_type(); - void set_type(int p_type); + void set_type(int p_accessor_type); Vector<double> get_min(); void set_min(Vector<double> p_min); @@ -102,4 +116,6 @@ public: void set_sparse_values_byte_offset(int p_sparse_values_byte_offset); }; +VARIANT_ENUM_CAST(GLTFAccessor::GLTFAccessorType); + #endif // GLTF_ACCESSOR_H diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index d56f67a092..863e1df967 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -62,9 +62,9 @@ Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { c.instantiate(); ERR_FAIL_NULL_V_MSG(p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null."); c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + // glTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. c->set_fov(Math::deg_to_rad(p_camera->get_fov())); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + // glTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. c->set_size_mag(p_camera->get_size() * 0.5f); c->set_depth_far(p_camera->get_far()); c->set_depth_near(p_camera->get_near()); @@ -74,9 +74,9 @@ Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { Camera3D *GLTFCamera::to_node() const { Camera3D *camera = memnew(Camera3D); camera->set_projection(perspective ? Camera3D::PROJECTION_PERSPECTIVE : Camera3D::PROJECTION_ORTHOGONAL); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + // glTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. camera->set_fov(Math::rad_to_deg(fov)); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + // glTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. camera->set_size(size_mag * 2.0f); camera->set_near(depth_near); camera->set_far(depth_far); @@ -84,7 +84,7 @@ Camera3D *GLTFCamera::to_node() const { } Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) { - ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse GLTF camera, missing required field 'type'."); + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse glTF camera, missing required field 'type'."); Ref<GLTFCamera> camera; camera.instantiate(); const String &type = p_dictionary["type"]; @@ -107,7 +107,7 @@ Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) { camera->set_depth_near(ortho["znear"]); } } else { - ERR_PRINT("Error parsing GLTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic."); + ERR_PRINT("Error parsing glTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic."); } return camera; } diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h index ef55b06a76..1a583c82cc 100644 --- a/modules/gltf/structures/gltf_camera.h +++ b/modules/gltf/structures/gltf_camera.h @@ -42,8 +42,8 @@ class GLTFCamera : public Resource { GDCLASS(GLTFCamera, Resource); private: - // GLTF has no default camera values, they should always be specified in - // the GLTF file. Here we default to Godot's default camera settings. + // glTF has no default camera values, they should always be specified in + // the glTF file. Here we default to Godot's default camera settings. bool perspective = true; real_t fov = Math::deg_to_rad(75.0); real_t size_mag = 0.5; diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp index 2934e4b5ee..ccee5e8ca4 100644 --- a/modules/gltf/structures/gltf_node.cpp +++ b/modules/gltf/structures/gltf_node.cpp @@ -55,6 +55,7 @@ void GLTFNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_scale", "scale"), &GLTFNode::set_scale); ClassDB::bind_method(D_METHOD("get_children"), &GLTFNode::get_children); ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children); + ClassDB::bind_method(D_METHOD("append_child_index", "child_index"), &GLTFNode::append_child_index); ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light); ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light); ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data); @@ -170,6 +171,10 @@ void GLTFNode::set_children(Vector<int> p_children) { children = p_children; } +void GLTFNode::append_child_index(int p_child_index) { + children.append(p_child_index); +} + GLTFLightIndex GLTFNode::get_light() { return light; } diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h index 63399fb32b..f3f6bfa2f1 100644 --- a/modules/gltf/structures/gltf_node.h +++ b/modules/gltf/structures/gltf_node.h @@ -97,6 +97,7 @@ public: Vector<int> get_children(); void set_children(Vector<int> p_children); + void append_child_index(int p_child_index); GLTFLightIndex get_light(); void set_light(GLTFLightIndex p_light); diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h new file mode 100644 index 0000000000..37c8f6925c --- /dev/null +++ b/modules/gltf/tests/test_gltf_extras.h @@ -0,0 +1,222 @@ +/**************************************************************************/ +/* test_gltf_extras.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 TEST_GLTF_EXTRAS_H +#define TEST_GLTF_EXTRAS_H + +#include "tests/test_macros.h" + +#ifdef TOOLS_ENABLED + +#include "core/os/os.h" +#include "editor/import/3d/resource_importer_scene.h" +#include "modules/gltf/editor/editor_scene_importer_gltf.h" +#include "modules/gltf/gltf_document.h" +#include "modules/gltf/gltf_state.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/main/window.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/material.h" +#include "scene/resources/packed_scene.h" + +namespace TestGltfExtras { + +static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) { + Ref<GLTFDocument> doc; + doc.instantiate(); + Ref<GLTFState> state; + state.instantiate(); + Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS); + CHECK_MESSAGE(err == OK, "GLTF state generation failed."); + err = doc->write_to_filesystem(state, p_tempfilebase + ".gltf"); + CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed."); + + // Setting up importers. + Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true)); + ResourceFormatImporter::get_singleton()->add_importer(import_scene); + Ref<EditorSceneFormatImporterGLTF> import_gltf; + import_gltf.instantiate(); + ResourceImporterScene::add_scene_importer(import_gltf); + + // GTLF importer behaves differently outside of editor, it's too late to modify Engine::get_editor_hint + // as the registration of runtime extensions already happened, so remove them. See modules/gltf/register_types.cpp + GLTFDocument::unregister_all_gltf_document_extensions(); + + HashMap<StringName, Variant> options(20); + options["nodes/root_type"] = ""; + options["nodes/root_name"] = ""; + options["nodes/apply_root_scale"] = true; + options["nodes/root_scale"] = 1.0; + options["meshes/ensure_tangents"] = true; + options["meshes/generate_lods"] = false; + options["meshes/create_shadow_meshes"] = true; + options["meshes/light_baking"] = 1; + options["meshes/lightmap_texel_size"] = 0.2; + options["meshes/force_disable_compression"] = false; + options["skins/use_named_skins"] = true; + options["animation/import"] = true; + options["animation/fps"] = 30; + options["animation/trimming"] = false; + options["animation/remove_immutable_tracks"] = true; + options["import_script/path"] = ""; + options["_subresources"] = Dictionary(); + options["gltf/naming_version"] = 1; + + // Process gltf file, note that this generates `.scn` resource from the 2nd argument. + err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr); + CHECK_MESSAGE(err == OK, "GLTF import failed."); + ResourceImporterScene::remove_scene_importer(import_gltf); + + Ref<PackedScene> packed_scene = ResourceLoader::load(p_tempfilebase + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); + CHECK_MESSAGE(err == OK, "Loading scene failed."); + Node *p_scene = packed_scene->instantiate(); + return p_scene; +} + +TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") { + // Setup scene. + Ref<StandardMaterial3D> original_material = memnew(StandardMaterial3D); + original_material->set_albedo(Color(1.0, .0, .0)); + original_material->set_name("material"); + Dictionary material_dict; + material_dict["node_type"] = "material"; + original_material->set_meta("extras", material_dict); + + Ref<PlaneMesh> original_meshdata = memnew(PlaneMesh); + original_meshdata->set_name("planemesh"); + Dictionary meshdata_dict; + meshdata_dict["node_type"] = "planemesh"; + original_meshdata->set_meta("extras", meshdata_dict); + original_meshdata->surface_set_material(0, original_material); + + MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D); + original_mesh_instance->set_mesh(original_meshdata); + original_mesh_instance->set_name("mesh_instance_3d"); + Dictionary mesh_instance_dict; + mesh_instance_dict["node_type"] = "mesh_instance_3d"; + original_mesh_instance->set_meta("extras", mesh_instance_dict); + + Node3D *original = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(original); + original->add_child(original_mesh_instance); + original->set_name("node3d"); + Dictionary node_dict; + node_dict["node_type"] = "node3d"; + original->set_meta("extras", node_dict); + original->set_meta("meta_not_nested_under_extras", "should not propagate"); + + // Convert to GLFT and back. + String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_extras"); + Node *loaded = _gltf_export_then_import(original, tempfile); + + // Compare the results. + CHECK(loaded->get_name() == "node3d"); + CHECK(Dictionary(loaded->get_meta("extras")).size() == 1); + CHECK(Dictionary(loaded->get_meta("extras"))["node_type"] == "node3d"); + CHECK_FALSE(loaded->has_meta("meta_not_nested_under_extras")); + CHECK_FALSE(Dictionary(loaded->get_meta("extras")).has("meta_not_nested_under_extras")); + + MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(loaded->find_child("mesh_instance_3d", false, true)); + CHECK(mesh_instance_3d->get_name() == "mesh_instance_3d"); + CHECK(Dictionary(mesh_instance_3d->get_meta("extras"))["node_type"] == "mesh_instance_3d"); + + Ref<Mesh> mesh = mesh_instance_3d->get_mesh(); + CHECK(Dictionary(mesh->get_meta("extras"))["node_type"] == "planemesh"); + + Ref<Material> material = mesh->surface_get_material(0); + CHECK(material->get_name() == "material"); + CHECK(Dictionary(material->get_meta("extras"))["node_type"] == "material"); + + memdelete(original_mesh_instance); + memdelete(original); + memdelete(loaded); +} + +TEST_CASE("[SceneTree][Node] GLTF test skeleton and bone export and import") { + // Setup scene. + Skeleton3D *skeleton = memnew(Skeleton3D); + skeleton->set_name("skeleton"); + Dictionary skeleton_extras; + skeleton_extras["node_type"] = "skeleton"; + skeleton->set_meta("extras", skeleton_extras); + + skeleton->add_bone("parent"); + skeleton->set_bone_rest(0, Transform3D()); + Dictionary parent_bone_extras; + parent_bone_extras["bone"] = "i_am_parent_bone"; + skeleton->set_bone_meta(0, "extras", parent_bone_extras); + + skeleton->add_bone("child"); + skeleton->set_bone_rest(1, Transform3D()); + skeleton->set_bone_parent(1, 0); + Dictionary child_bone_extras; + child_bone_extras["bone"] = "i_am_child_bone"; + skeleton->set_bone_meta(1, "extras", child_bone_extras); + + // We have to have a mesh to link with skeleton or it will not get imported. + Ref<PlaneMesh> meshdata = memnew(PlaneMesh); + meshdata->set_name("planemesh"); + + MeshInstance3D *mesh = memnew(MeshInstance3D); + mesh->set_mesh(meshdata); + mesh->set_name("mesh_instance_3d"); + + Node3D *scene = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(scene); + scene->add_child(skeleton); + scene->add_child(mesh); + scene->set_name("node3d"); + + // Now that both skeleton and mesh are part of scene, link them. + mesh->set_skeleton_path(mesh->get_path_to(skeleton)); + + // Convert to GLFT and back. + String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_bone_extras"); + Node *loaded = _gltf_export_then_import(scene, tempfile); + + // Compare the results. + CHECK(loaded->get_name() == "node3d"); + Skeleton3D *result = Object::cast_to<Skeleton3D>(loaded->find_child("Skeleton3D", false, true)); + CHECK(result->get_bone_name(0) == "parent"); + CHECK(Dictionary(result->get_bone_meta(0, "extras"))["bone"] == "i_am_parent_bone"); + CHECK(result->get_bone_name(1) == "child"); + CHECK(Dictionary(result->get_bone_meta(1, "extras"))["bone"] == "i_am_child_bone"); + + memdelete(skeleton); + memdelete(mesh); + memdelete(scene); + memdelete(loaded); +} +} // namespace TestGltfExtras + +#endif // TOOLS_ENABLED + +#endif // TEST_GLTF_EXTRAS_H diff --git a/modules/godot_physics_3d/SCsub b/modules/godot_physics_3d/SCsub new file mode 100644 index 0000000000..41a59cd24e --- /dev/null +++ b/modules/godot_physics_3d/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.modules_sources, "*.cpp") + +SConscript("joints/SCsub") diff --git a/modules/godot_physics_3d/config.py b/modules/godot_physics_3d/config.py new file mode 100644 index 0000000000..a42f27fbe1 --- /dev/null +++ b/modules/godot_physics_3d/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return not env["disable_3d"] + + +def configure(env): + pass diff --git a/modules/godot_physics_3d/gjk_epa.cpp b/modules/godot_physics_3d/gjk_epa.cpp new file mode 100644 index 0000000000..e5678914fe --- /dev/null +++ b/modules/godot_physics_3d/gjk_epa.cpp @@ -0,0 +1,1025 @@ +/**************************************************************************/ +/* gjk_epa.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 "gjk_epa.h" + +/* Disabling formatting for thirdparty code snippet */ +/* clang-format off */ + +/*************** Bullet's GJK-EPA2 IMPLEMENTATION *******************/ + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the +use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in a +product, an acknowledgment in the product documentation would be appreciated +but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +/* +GJK-EPA collision solver by Nathanael Presson, 2008 +*/ + + // Config + +/* GJK */ +#define GJK_MAX_ITERATIONS 128 +#define GJK_ACCURACY ((real_t)0.0001) +#define GJK_MIN_DISTANCE ((real_t)0.0001) +#define GJK_DUPLICATED_EPS ((real_t)0.0001) +#define GJK_SIMPLEX2_EPS ((real_t)0.0) +#define GJK_SIMPLEX3_EPS ((real_t)0.0) +#define GJK_SIMPLEX4_EPS ((real_t)0.0) + +/* EPA */ +#define EPA_MAX_VERTICES 128 +#define EPA_MAX_FACES (EPA_MAX_VERTICES*2) +#define EPA_MAX_ITERATIONS 255 +// -- GODOT start -- +//#define EPA_ACCURACY ((real_t)0.0001) +#define EPA_ACCURACY ((real_t)0.00001) +// -- GODOT end -- +#define EPA_FALLBACK (10*EPA_ACCURACY) +#define EPA_PLANE_EPS ((real_t)0.00001) +#define EPA_INSIDE_EPS ((real_t)0.01) + +namespace GjkEpa2 { + + +struct sResults { + enum eStatus { + Separated, /* Shapes doesn't penetrate */ + Penetrating, /* Shapes are penetrating */ + GJK_Failed, /* GJK phase fail, no big issue, shapes are probably just 'touching' */ + EPA_Failed /* EPA phase fail, bigger problem, need to save parameters, and debug */ + } status; + + Vector3 witnesses[2]; + Vector3 normal; + real_t distance = 0.0; +}; + +// Shorthands +typedef unsigned int U; +typedef unsigned char U1; + +// MinkowskiDiff +struct MinkowskiDiff { + const GodotShape3D* m_shapes[2]; + + Transform3D transform_A; + Transform3D transform_B; + + real_t margin_A = 0.0; + real_t margin_B = 0.0; + + Vector3 (*get_support)(const GodotShape3D*, const Vector3&, real_t) = nullptr; + + void Initialize(const GodotShape3D* shape0, const Transform3D& wtrs0, const real_t margin0, + const GodotShape3D* shape1, const Transform3D& wtrs1, const real_t margin1) { + m_shapes[0] = shape0; + m_shapes[1] = shape1; + transform_A = wtrs0; + transform_B = wtrs1; + margin_A = margin0; + margin_B = margin1; + + if ((margin0 > 0.0) || (margin1 > 0.0)) { + get_support = get_support_with_margin; + } else { + get_support = get_support_without_margin; + } + } + + static Vector3 get_support_without_margin(const GodotShape3D* p_shape, const Vector3& p_dir, real_t p_margin) { + return p_shape->get_support(p_dir.normalized()); + } + + static Vector3 get_support_with_margin(const GodotShape3D* p_shape, const Vector3& p_dir, real_t p_margin) { + Vector3 local_dir_norm = p_dir; + if (local_dir_norm.length_squared() < CMP_EPSILON2) { + local_dir_norm = Vector3(-1.0, -1.0, -1.0); + } + local_dir_norm.normalize(); + + return p_shape->get_support(local_dir_norm) + p_margin * local_dir_norm; + } + + // i wonder how this could be sped up... if it can + _FORCE_INLINE_ Vector3 Support0(const Vector3& d) const { + return transform_A.xform(get_support(m_shapes[0], transform_A.basis.xform_inv(d), margin_A)); + } + + _FORCE_INLINE_ Vector3 Support1(const Vector3& d) const { + return transform_B.xform(get_support(m_shapes[1], transform_B.basis.xform_inv(d), margin_B)); + } + + _FORCE_INLINE_ Vector3 Support (const Vector3& d) const { + return (Support0(d) - Support1(-d)); + } + + _FORCE_INLINE_ Vector3 Support(const Vector3& d, U index) const { + if (index) { + return Support1(d); + } else { + return Support0(d); + } + } +}; + +typedef MinkowskiDiff tShape; + + +// GJK +struct GJK +{ + /* Types */ + struct sSV + { + Vector3 d,w; + }; + struct sSimplex + { + sSV* c[4]; + real_t p[4]; + U rank; + }; + struct eStatus { enum _ { + Valid, + Inside, + Failed };}; + /* Fields */ + tShape m_shape; + Vector3 m_ray; + real_t m_distance = 0.0f; + sSimplex m_simplices[2]; + sSV m_store[4]; + sSV* m_free[4]; + U m_nfree = 0; + U m_current = 0; + sSimplex* m_simplex = nullptr; + eStatus::_ m_status; + /* Methods */ + GJK() + { + Initialize(); + } + void Initialize() + { + m_ray = Vector3(0,0,0); + m_nfree = 0; + m_status = eStatus::Failed; + m_current = 0; + m_distance = 0; + } + eStatus::_ Evaluate(const tShape& shapearg,const Vector3& guess) + { + U iterations=0; + real_t sqdist=0; + real_t alpha=0; + Vector3 lastw[4]; + U clastw=0; + /* Initialize solver */ + m_free[0] = &m_store[0]; + m_free[1] = &m_store[1]; + m_free[2] = &m_store[2]; + m_free[3] = &m_store[3]; + m_nfree = 4; + m_current = 0; + m_status = eStatus::Valid; + m_shape = shapearg; + m_distance = 0; + /* Initialize simplex */ + m_simplices[0].rank = 0; + m_ray = guess; + const real_t sqrl= m_ray.length_squared(); + appendvertice(m_simplices[0],sqrl>0?-m_ray:Vector3(1,0,0)); + m_simplices[0].p[0] = 1; + m_ray = m_simplices[0].c[0]->w; + sqdist = sqrl; + lastw[0] = + lastw[1] = + lastw[2] = + lastw[3] = m_ray; + /* Loop */ + do { + const U next=1-m_current; + sSimplex& cs=m_simplices[m_current]; + sSimplex& ns=m_simplices[next]; + /* Check zero */ + const real_t rl=m_ray.length(); + if(rl<GJK_MIN_DISTANCE) + {/* Touching or inside */ + m_status=eStatus::Inside; + break; + } + /* Append new vertice in -'v' direction */ + appendvertice(cs,-m_ray); + const Vector3& w=cs.c[cs.rank-1]->w; + bool found=false; + for(U i=0;i<4;++i) + { + if((w-lastw[i]).length_squared()<GJK_DUPLICATED_EPS) + { found=true;break; } + } + if(found) + {/* Return old simplex */ + removevertice(m_simplices[m_current]); + break; + } + else + {/* Update lastw */ + lastw[clastw=(clastw+1)&3]=w; + } + /* Check for termination */ + const real_t omega=vec3_dot(m_ray,w)/rl; + alpha=MAX(omega,alpha); + if(((rl-alpha)-(GJK_ACCURACY*rl))<=0) + {/* Return old simplex */ + removevertice(m_simplices[m_current]); + break; + } + /* Reduce simplex */ + real_t weights[4]; + U mask=0; + switch(cs.rank) + { + case 2: sqdist=projectorigin( cs.c[0]->w, + cs.c[1]->w, + weights,mask);break; + case 3: sqdist=projectorigin( cs.c[0]->w, + cs.c[1]->w, + cs.c[2]->w, + weights,mask);break; + case 4: sqdist=projectorigin( cs.c[0]->w, + cs.c[1]->w, + cs.c[2]->w, + cs.c[3]->w, + weights,mask);break; + } + if(sqdist>=0) + {/* Valid */ + ns.rank = 0; + m_ray = Vector3(0,0,0); + m_current = next; + for(U i=0,ni=cs.rank;i<ni;++i) + { + if(mask&(1<<i)) + { + ns.c[ns.rank] = cs.c[i]; + ns.p[ns.rank++] = weights[i]; + m_ray += cs.c[i]->w*weights[i]; + } + else + { + m_free[m_nfree++] = cs.c[i]; + } + } + if(mask==15) { m_status=eStatus::Inside; +} + } + else + {/* Return old simplex */ + removevertice(m_simplices[m_current]); + break; + } + m_status=((++iterations)<GJK_MAX_ITERATIONS)?m_status:eStatus::Failed; + } while(m_status==eStatus::Valid); + m_simplex=&m_simplices[m_current]; + switch(m_status) + { + case eStatus::Valid: m_distance=m_ray.length();break; + case eStatus::Inside: m_distance=0;break; + default: {} + } + return(m_status); + } + bool EncloseOrigin() + { + switch(m_simplex->rank) + { + case 1: + { + for(U i=0;i<3;++i) + { + Vector3 axis=Vector3(0,0,0); + axis[i]=1; + appendvertice(*m_simplex, axis); + if(EncloseOrigin()) { return(true); +} + removevertice(*m_simplex); + appendvertice(*m_simplex,-axis); + if(EncloseOrigin()) { return(true); +} + removevertice(*m_simplex); + } + } + break; + case 2: + { + const Vector3 d=m_simplex->c[1]->w-m_simplex->c[0]->w; + for(U i=0;i<3;++i) + { + Vector3 axis=Vector3(0,0,0); + axis[i]=1; + const Vector3 p=vec3_cross(d,axis); + if(p.length_squared()>0) + { + appendvertice(*m_simplex, p); + if(EncloseOrigin()) { return(true); +} + removevertice(*m_simplex); + appendvertice(*m_simplex,-p); + if(EncloseOrigin()) { return(true); +} + removevertice(*m_simplex); + } + } + } + break; + case 3: + { + const Vector3 n=vec3_cross(m_simplex->c[1]->w-m_simplex->c[0]->w, + m_simplex->c[2]->w-m_simplex->c[0]->w); + if(n.length_squared()>0) + { + appendvertice(*m_simplex,n); + if(EncloseOrigin()) { return(true); +} + removevertice(*m_simplex); + appendvertice(*m_simplex,-n); + if(EncloseOrigin()) { return(true); +} + removevertice(*m_simplex); + } + } + break; + case 4: + { + if(Math::abs(det( m_simplex->c[0]->w-m_simplex->c[3]->w, + m_simplex->c[1]->w-m_simplex->c[3]->w, + m_simplex->c[2]->w-m_simplex->c[3]->w))>0) { + return(true); +} + } + break; + } + return(false); + } + /* Internals */ + void getsupport(const Vector3& d,sSV& sv) const + { + sv.d = d/d.length(); + sv.w = m_shape.Support(sv.d); + } + void removevertice(sSimplex& simplex) + { + m_free[m_nfree++]=simplex.c[--simplex.rank]; + } + void appendvertice(sSimplex& simplex,const Vector3& v) + { + simplex.p[simplex.rank]=0; + simplex.c[simplex.rank]=m_free[--m_nfree]; + getsupport(v,*simplex.c[simplex.rank++]); + } + static real_t det(const Vector3& a,const Vector3& b,const Vector3& c) + { + return( a.y*b.z*c.x+a.z*b.x*c.y- + a.x*b.z*c.y-a.y*b.x*c.z+ + a.x*b.y*c.z-a.z*b.y*c.x); + } + static real_t projectorigin( const Vector3& a, + const Vector3& b, + real_t* w,U& m) + { + const Vector3 d=b-a; + const real_t l=d.length_squared(); + if(l>GJK_SIMPLEX2_EPS) + { + const real_t t(l>0?-vec3_dot(a,d)/l:0); + if(t>=1) { w[0]=0;w[1]=1;m=2;return(b.length_squared()); } + else if(t<=0) { w[0]=1;w[1]=0;m=1;return(a.length_squared()); } + else { w[0]=1-(w[1]=t);m=3;return((a+d*t).length_squared()); } + } + return(-1); + } + static real_t projectorigin( const Vector3& a, + const Vector3& b, + const Vector3& c, + real_t* w,U& m) + { + static const U imd3[]={1,2,0}; + const Vector3* vt[]={&a,&b,&c}; + const Vector3 dl[]={a-b,b-c,c-a}; + const Vector3 n=vec3_cross(dl[0],dl[1]); + const real_t l=n.length_squared(); + if(l>GJK_SIMPLEX3_EPS) + { + real_t mindist=-1; + real_t subw[2] = { 0 , 0}; + U subm = 0; + for(U i=0;i<3;++i) + { + if(vec3_dot(*vt[i],vec3_cross(dl[i],n))>0) + { + const U j=imd3[i]; + const real_t subd(projectorigin(*vt[i],*vt[j],subw,subm)); + if((mindist<0)||(subd<mindist)) + { + mindist = subd; + m = static_cast<U>(((subm&1)?1<<i:0)+((subm&2)?1<<j:0)); + w[i] = subw[0]; + w[j] = subw[1]; + w[imd3[j]] = 0; + } + } + } + if(mindist<0) + { + const real_t d=vec3_dot(a,n); + const real_t s=Math::sqrt(l); + const Vector3 p=n*(d/l); + mindist = p.length_squared(); + m = 7; + w[0] = (vec3_cross(dl[1],b-p)).length()/s; + w[1] = (vec3_cross(dl[2],c-p)).length()/s; + w[2] = 1-(w[0]+w[1]); + } + return(mindist); + } + return(-1); + } + static real_t projectorigin( const Vector3& a, + const Vector3& b, + const Vector3& c, + const Vector3& d, + real_t* w,U& m) + { + static const U imd3[]={1,2,0}; + const Vector3* vt[]={&a,&b,&c,&d}; + const Vector3 dl[]={a-d,b-d,c-d}; + const real_t vl=det(dl[0],dl[1],dl[2]); + const bool ng=(vl*vec3_dot(a,vec3_cross(b-c,a-b)))<=0; + if(ng&&(Math::abs(vl)>GJK_SIMPLEX4_EPS)) + { + real_t mindist=-1; + real_t subw[3] = {0.f, 0.f, 0.f}; + U subm=0; + for(U i=0;i<3;++i) + { + const U j=imd3[i]; + const real_t s=vl*vec3_dot(d,vec3_cross(dl[i],dl[j])); + if(s>0) + { + const real_t subd=projectorigin(*vt[i],*vt[j],d,subw,subm); + if((mindist<0)||(subd<mindist)) + { + mindist = subd; + m = static_cast<U>((subm&1?1<<i:0)+ + (subm&2?1<<j:0)+ + (subm&4?8:0)); + w[i] = subw[0]; + w[j] = subw[1]; + w[imd3[j]] = 0; + w[3] = subw[2]; + } + } + } + if(mindist<0) + { + mindist = 0; + m = 15; + w[0] = det(c,b,d)/vl; + w[1] = det(a,c,d)/vl; + w[2] = det(b,a,d)/vl; + w[3] = 1-(w[0]+w[1]+w[2]); + } + return(mindist); + } + return(-1); + } +}; + + // EPA + struct EPA + { + /* Types */ + typedef GJK::sSV sSV; + struct sFace + { + Vector3 n; + real_t d = 0.0f; + sSV* c[3]; + sFace* f[3]; + sFace* l[2]; + U1 e[3]; + U1 pass = 0; + }; + struct sList + { + sFace* root = nullptr; + U count = 0; + sList() {} + }; + struct sHorizon + { + sFace* cf = nullptr; + sFace* ff = nullptr; + U nf = 0; + sHorizon() {} + }; + struct eStatus { enum _ { + Valid, + Touching, + Degenerated, + NonConvex, + InvalidHull, + OutOfFaces, + OutOfVertices, + AccuraryReached, + FallBack, + Failed };}; + /* Fields */ + eStatus::_ m_status; + GJK::sSimplex m_result; + Vector3 m_normal; + real_t m_depth = 0.0f; + sSV m_sv_store[EPA_MAX_VERTICES]; + sFace m_fc_store[EPA_MAX_FACES]; + U m_nextsv = 0; + sList m_hull; + sList m_stock; + /* Methods */ + EPA() + { + Initialize(); + } + + + static inline void bind(sFace* fa,U ea,sFace* fb,U eb) + { + fa->e[ea]=(U1)eb;fa->f[ea]=fb; + fb->e[eb]=(U1)ea;fb->f[eb]=fa; + } + static inline void append(sList& list,sFace* face) + { + face->l[0] = nullptr; + face->l[1] = list.root; + if(list.root) { list.root->l[0]=face; +} + list.root = face; + ++list.count; + } + static inline void remove(sList& list,sFace* face) + { + if(face->l[1]) { face->l[1]->l[0]=face->l[0]; +} + if(face->l[0]) { face->l[0]->l[1]=face->l[1]; +} + if(face==list.root) { list.root=face->l[1]; +} + --list.count; + } + + + void Initialize() + { + m_status = eStatus::Failed; + m_normal = Vector3(0,0,0); + m_depth = 0; + m_nextsv = 0; + for(U i=0;i<EPA_MAX_FACES;++i) + { + append(m_stock,&m_fc_store[EPA_MAX_FACES-i-1]); + } + } + eStatus::_ Evaluate(GJK& gjk,const Vector3& guess) + { + GJK::sSimplex& simplex=*gjk.m_simplex; + if((simplex.rank>1)&&gjk.EncloseOrigin()) + { + /* Clean up */ + while(m_hull.root) + { + sFace* f = m_hull.root; + remove(m_hull,f); + append(m_stock,f); + } + m_status = eStatus::Valid; + m_nextsv = 0; + /* Orient simplex */ + if(gjk.det( simplex.c[0]->w-simplex.c[3]->w, + simplex.c[1]->w-simplex.c[3]->w, + simplex.c[2]->w-simplex.c[3]->w)<0) + { + SWAP(simplex.c[0],simplex.c[1]); + SWAP(simplex.p[0],simplex.p[1]); + } + /* Build initial hull */ + sFace* tetra[]={newface(simplex.c[0],simplex.c[1],simplex.c[2],true), + newface(simplex.c[1],simplex.c[0],simplex.c[3],true), + newface(simplex.c[2],simplex.c[1],simplex.c[3],true), + newface(simplex.c[0],simplex.c[2],simplex.c[3],true)}; + if(m_hull.count==4) + { + sFace* best=findbest(); + sFace outer=*best; + U pass=0; + U iterations=0; + bind(tetra[0],0,tetra[1],0); + bind(tetra[0],1,tetra[2],0); + bind(tetra[0],2,tetra[3],0); + bind(tetra[1],1,tetra[3],2); + bind(tetra[1],2,tetra[2],1); + bind(tetra[2],2,tetra[3],1); + m_status=eStatus::Valid; + for(;iterations<EPA_MAX_ITERATIONS;++iterations) + { + if(m_nextsv<EPA_MAX_VERTICES) + { + sHorizon horizon; + sSV* w=&m_sv_store[m_nextsv++]; + bool valid=true; + best->pass = (U1)(++pass); + gjk.getsupport(best->n,*w); + const real_t wdist=vec3_dot(best->n,w->w)-best->d; + if(wdist>EPA_ACCURACY) + { + for(U j=0;(j<3)&&valid;++j) + { + valid&=expand( pass,w, + best->f[j],best->e[j], + horizon); + } + if(valid&&(horizon.nf>=3)) + { + bind(horizon.cf,1,horizon.ff,2); + remove(m_hull,best); + append(m_stock,best); + best=findbest(); + outer=*best; + } else { m_status=eStatus::InvalidHull;break; } + } else { m_status=eStatus::AccuraryReached;break; } + } else { m_status=eStatus::OutOfVertices;break; } + } + const Vector3 projection=outer.n*outer.d; + m_normal = outer.n; + m_depth = outer.d; + m_result.rank = 3; + m_result.c[0] = outer.c[0]; + m_result.c[1] = outer.c[1]; + m_result.c[2] = outer.c[2]; + m_result.p[0] = vec3_cross( outer.c[1]->w-projection, + outer.c[2]->w-projection).length(); + m_result.p[1] = vec3_cross( outer.c[2]->w-projection, + outer.c[0]->w-projection).length(); + m_result.p[2] = vec3_cross( outer.c[0]->w-projection, + outer.c[1]->w-projection).length(); + const real_t sum=m_result.p[0]+m_result.p[1]+m_result.p[2]; + m_result.p[0] /= sum; + m_result.p[1] /= sum; + m_result.p[2] /= sum; + return(m_status); + } + } + /* Fallback */ + m_status = eStatus::FallBack; + m_normal = -guess; + const real_t nl = m_normal.length(); + if (nl > 0) { + m_normal = m_normal/nl; + } else { + m_normal = Vector3(1,0,0); + } + m_depth = 0; + m_result.rank=1; + m_result.c[0]=simplex.c[0]; + m_result.p[0]=1; + return(m_status); + } + + bool getedgedist(sFace* face, sSV* a, sSV* b, real_t& dist) + { + const Vector3 ba = b->w - a->w; + const Vector3 n_ab = vec3_cross(ba, face->n); // Outward facing edge normal direction, on triangle plane + const real_t a_dot_nab = vec3_dot(a->w, n_ab); // Only care about the sign to determine inside/outside, so not normalization required + + if (a_dot_nab < 0) { + // Outside of edge a->b + const real_t ba_l2 = ba.length_squared(); + const real_t a_dot_ba = vec3_dot(a->w, ba); + const real_t b_dot_ba = vec3_dot(b->w, ba); + + if (a_dot_ba > 0) { + // Pick distance vertex a + dist = a->w.length(); + } else if (b_dot_ba < 0) { + // Pick distance vertex b + dist = b->w.length(); + } else { + // Pick distance to edge a->b + const real_t a_dot_b = vec3_dot(a->w, b->w); + dist = Math::sqrt(MAX((a->w.length_squared() * b->w.length_squared() - a_dot_b * a_dot_b) / ba_l2, 0.0)); + } + + return true; + } + + return false; + } + + sFace* newface(sSV* a,sSV* b,sSV* c,bool forced) + { + if (m_stock.root) { + sFace* face=m_stock.root; + remove(m_stock,face); + append(m_hull,face); + face->pass = 0; + face->c[0] = a; + face->c[1] = b; + face->c[2] = c; + face->n = vec3_cross(b->w-a->w,c->w-a->w); + const real_t l=face->n.length(); + const bool v=l>EPA_ACCURACY; + if (v) { + if (!(getedgedist(face, a, b, face->d) || + getedgedist(face, b, c, face->d) || + getedgedist(face, c, a, face->d))) { + // Origin projects to the interior of the triangle + // Use distance to triangle plane + face->d = vec3_dot(a->w, face->n) / l; + } + face->n /= l; + if (forced||(face->d>=-EPA_PLANE_EPS)) { + return(face); + } else { + m_status=eStatus::NonConvex; + } + } else { + m_status=eStatus::Degenerated; + } + remove(m_hull,face); + append(m_stock,face); + return(nullptr); + } + // -- GODOT start -- + //m_status=m_stock.root?eStatus::OutOfVertices:eStatus::OutOfFaces; + m_status=eStatus::OutOfFaces; + // -- GODOT end -- + return(nullptr); + } + sFace* findbest() + { + sFace* minf=m_hull.root; + real_t mind=minf->d*minf->d; + for(sFace* f=minf->l[1];f;f=f->l[1]) + { + const real_t sqd=f->d*f->d; + if(sqd<mind) + { + minf=f; + mind=sqd; + } + } + return(minf); + } + bool expand(U pass,sSV* w,sFace* f,U e,sHorizon& horizon) + { + static const U i1m3[]={1,2,0}; + static const U i2m3[]={2,0,1}; + if(f->pass!=pass) + { + const U e1=i1m3[e]; + if((vec3_dot(f->n,w->w)-f->d)<-EPA_PLANE_EPS) + { + sFace* nf=newface(f->c[e1],f->c[e],w,false); + if(nf) + { + bind(nf,0,f,e); + if(horizon.cf) { bind(horizon.cf,1,nf,2); } else { horizon.ff=nf; +} + horizon.cf=nf; + ++horizon.nf; + return(true); + } + } + else + { + const U e2=i2m3[e]; + f->pass = (U1)pass; + if( expand(pass,w,f->f[e1],f->e[e1],horizon)&& + expand(pass,w,f->f[e2],f->e[e2],horizon)) + { + remove(m_hull,f); + append(m_stock,f); + return(true); + } + } + } + return(false); + } + + }; + + // + static void Initialize( const GodotShape3D* shape0, const Transform3D& wtrs0, real_t margin0, + const GodotShape3D* shape1, const Transform3D& wtrs1, real_t margin1, + sResults& results, + tShape& shape) + { + /* Results */ + results.witnesses[0] = Vector3(0,0,0); + results.witnesses[1] = Vector3(0,0,0); + results.status = sResults::Separated; + /* Shape */ + shape.Initialize(shape0, wtrs0, margin0, shape1, wtrs1, margin1); + } + + + +// +// Api +// + +// + +// +bool Distance( const GodotShape3D* shape0, + const Transform3D& wtrs0, + real_t margin0, + const GodotShape3D* shape1, + const Transform3D& wtrs1, + real_t margin1, + const Vector3& guess, + sResults& results) +{ + tShape shape; + Initialize(shape0, wtrs0, margin0, shape1, wtrs1, margin1, results, shape); + GJK gjk; + GJK::eStatus::_ gjk_status=gjk.Evaluate(shape,guess); + if(gjk_status==GJK::eStatus::Valid) + { + Vector3 w0=Vector3(0,0,0); + Vector3 w1=Vector3(0,0,0); + for(U i=0;i<gjk.m_simplex->rank;++i) + { + const real_t p=gjk.m_simplex->p[i]; + w0+=shape.Support( gjk.m_simplex->c[i]->d,0)*p; + w1+=shape.Support(-gjk.m_simplex->c[i]->d,1)*p; + } + results.witnesses[0] = w0; + results.witnesses[1] = w1; + results.normal = w0-w1; + results.distance = results.normal.length(); + results.normal /= results.distance>GJK_MIN_DISTANCE?results.distance:1; + return(true); + } + else + { + results.status = gjk_status==GJK::eStatus::Inside? + sResults::Penetrating : + sResults::GJK_Failed; + return(false); + } +} + + +// +bool Penetration( const GodotShape3D* shape0, + const Transform3D& wtrs0, + real_t margin0, + const GodotShape3D* shape1, + const Transform3D& wtrs1, + real_t margin1, + const Vector3& guess, + sResults& results + ) +{ + tShape shape; + Initialize(shape0, wtrs0, margin0, shape1, wtrs1, margin1, results, shape); + GJK gjk; + GJK::eStatus::_ gjk_status=gjk.Evaluate(shape,-guess); + switch(gjk_status) + { + case GJK::eStatus::Inside: + { + EPA epa; + EPA::eStatus::_ epa_status=epa.Evaluate(gjk,-guess); + if(epa_status!=EPA::eStatus::Failed) + { + Vector3 w0=Vector3(0,0,0); + for(U i=0;i<epa.m_result.rank;++i) + { + w0+=shape.Support(epa.m_result.c[i]->d,0)*epa.m_result.p[i]; + } + results.status = sResults::Penetrating; + results.witnesses[0] = w0; + results.witnesses[1] = w0-epa.m_normal*epa.m_depth; + results.normal = -epa.m_normal; + results.distance = -epa.m_depth; + return(true); + } else { results.status=sResults::EPA_Failed; +} + } + break; + case GJK::eStatus::Failed: + results.status=sResults::GJK_Failed; + break; + default: {} + } + return(false); +} + + + +/* Symbols cleanup */ + +#undef GJK_MAX_ITERATIONS +#undef GJK_ACCURARY +#undef GJK_MIN_DISTANCE +#undef GJK_DUPLICATED_EPS +#undef GJK_SIMPLEX2_EPS +#undef GJK_SIMPLEX3_EPS +#undef GJK_SIMPLEX4_EPS + +#undef EPA_MAX_VERTICES +#undef EPA_MAX_FACES +#undef EPA_MAX_ITERATIONS +#undef EPA_ACCURACY +#undef EPA_FALLBACK +#undef EPA_PLANE_EPS +#undef EPA_INSIDE_EPS +} // end of namespace + +/* clang-format on */ + +bool gjk_epa_calculate_distance(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_result_A, Vector3 &r_result_B) { + GjkEpa2::sResults res; + + if (GjkEpa2::Distance(p_shape_A, p_transform_A, 0.0, p_shape_B, p_transform_B, 0.0, p_transform_B.origin - p_transform_A.origin, res)) { + r_result_A = res.witnesses[0]; + r_result_B = res.witnesses[1]; + return true; + } + + return false; +} + +bool gjk_epa_calculate_penetration(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, GodotCollisionSolver3D::CallbackResult p_result_callback, void *p_userdata, bool p_swap, real_t p_margin_A, real_t p_margin_B) { + GjkEpa2::sResults res; + + if (GjkEpa2::Penetration(p_shape_A, p_transform_A, p_margin_A, p_shape_B, p_transform_B, p_margin_B, p_transform_B.origin - p_transform_A.origin, res)) { + if (p_result_callback) { + if (p_swap) { + Vector3 normal = (res.witnesses[1] - res.witnesses[0]).normalized(); + p_result_callback(res.witnesses[1], 0, res.witnesses[0], 0, normal, p_userdata); + } else { + Vector3 normal = (res.witnesses[0] - res.witnesses[1]).normalized(); + p_result_callback(res.witnesses[0], 0, res.witnesses[1], 0, normal, p_userdata); + } + } + return true; + } + + return false; +} diff --git a/modules/godot_physics_3d/gjk_epa.h b/modules/godot_physics_3d/gjk_epa.h new file mode 100644 index 0000000000..48fda9969f --- /dev/null +++ b/modules/godot_physics_3d/gjk_epa.h @@ -0,0 +1,40 @@ +/**************************************************************************/ +/* gjk_epa.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 GJK_EPA_H +#define GJK_EPA_H + +#include "godot_collision_solver_3d.h" +#include "godot_shape_3d.h" + +bool gjk_epa_calculate_penetration(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, GodotCollisionSolver3D::CallbackResult p_result_callback, void *p_userdata, bool p_swap = false, real_t p_margin_A = 0.0, real_t p_margin_B = 0.0); +bool gjk_epa_calculate_distance(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_result_A, Vector3 &r_result_B); + +#endif // GJK_EPA_H diff --git a/modules/godot_physics_3d/godot_area_3d.cpp b/modules/godot_physics_3d/godot_area_3d.cpp new file mode 100644 index 0000000000..d0b287b058 --- /dev/null +++ b/modules/godot_physics_3d/godot_area_3d.cpp @@ -0,0 +1,346 @@ +/**************************************************************************/ +/* godot_area_3d.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 "godot_area_3d.h" + +#include "godot_body_3d.h" +#include "godot_soft_body_3d.h" +#include "godot_space_3d.h" + +GodotArea3D::BodyKey::BodyKey(GodotSoftBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) { + rid = p_body->get_self(); + instance_id = p_body->get_instance_id(); + body_shape = p_body_shape; + area_shape = p_area_shape; +} + +GodotArea3D::BodyKey::BodyKey(GodotBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) { + rid = p_body->get_self(); + instance_id = p_body->get_instance_id(); + body_shape = p_body_shape; + area_shape = p_area_shape; +} + +GodotArea3D::BodyKey::BodyKey(GodotArea3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) { + rid = p_body->get_self(); + instance_id = p_body->get_instance_id(); + body_shape = p_body_shape; + area_shape = p_area_shape; +} + +void GodotArea3D::_shapes_changed() { + if (!moved_list.in_list() && get_space()) { + get_space()->area_add_to_moved_list(&moved_list); + } +} + +void GodotArea3D::set_transform(const Transform3D &p_transform) { + if (!moved_list.in_list() && get_space()) { + get_space()->area_add_to_moved_list(&moved_list); + } + + _set_transform(p_transform); + _set_inv_transform(p_transform.affine_inverse()); +} + +void GodotArea3D::set_space(GodotSpace3D *p_space) { + if (get_space()) { + if (monitor_query_list.in_list()) { + get_space()->area_remove_from_monitor_query_list(&monitor_query_list); + } + if (moved_list.in_list()) { + get_space()->area_remove_from_moved_list(&moved_list); + } + } + + monitored_bodies.clear(); + monitored_areas.clear(); + + _set_space(p_space); +} + +void GodotArea3D::set_monitor_callback(const Callable &p_callback) { + _unregister_shapes(); + + monitor_callback = p_callback; + + monitored_bodies.clear(); + monitored_areas.clear(); + + _shape_changed(); + + if (!moved_list.in_list() && get_space()) { + get_space()->area_add_to_moved_list(&moved_list); + } +} + +void GodotArea3D::set_area_monitor_callback(const Callable &p_callback) { + _unregister_shapes(); + + area_monitor_callback = p_callback; + + monitored_bodies.clear(); + monitored_areas.clear(); + + _shape_changed(); + + if (!moved_list.in_list() && get_space()) { + get_space()->area_add_to_moved_list(&moved_list); + } +} + +void GodotArea3D::_set_space_override_mode(PhysicsServer3D::AreaSpaceOverrideMode &r_mode, PhysicsServer3D::AreaSpaceOverrideMode p_new_mode) { + bool do_override = p_new_mode != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED; + if (do_override == (r_mode != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED)) { + return; + } + _unregister_shapes(); + r_mode = p_new_mode; + _shape_changed(); +} + +void GodotArea3D::set_param(PhysicsServer3D::AreaParameter p_param, const Variant &p_value) { + switch (p_param) { + case PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE: + _set_space_override_mode(gravity_override_mode, (PhysicsServer3D::AreaSpaceOverrideMode)(int)p_value); + break; + case PhysicsServer3D::AREA_PARAM_GRAVITY: + gravity = p_value; + break; + case PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR: + gravity_vector = p_value; + break; + case PhysicsServer3D::AREA_PARAM_GRAVITY_IS_POINT: + gravity_is_point = p_value; + break; + case PhysicsServer3D::AREA_PARAM_GRAVITY_POINT_UNIT_DISTANCE: + gravity_point_unit_distance = p_value; + break; + case PhysicsServer3D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE: + _set_space_override_mode(linear_damping_override_mode, (PhysicsServer3D::AreaSpaceOverrideMode)(int)p_value); + break; + case PhysicsServer3D::AREA_PARAM_LINEAR_DAMP: + linear_damp = p_value; + break; + case PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE: + _set_space_override_mode(angular_damping_override_mode, (PhysicsServer3D::AreaSpaceOverrideMode)(int)p_value); + break; + case PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP: + angular_damp = p_value; + break; + case PhysicsServer3D::AREA_PARAM_PRIORITY: + priority = p_value; + break; + case PhysicsServer3D::AREA_PARAM_WIND_FORCE_MAGNITUDE: + ERR_FAIL_COND_MSG(wind_force_magnitude < 0, "Wind force magnitude must be a non-negative real number, but a negative number was specified."); + wind_force_magnitude = p_value; + break; + case PhysicsServer3D::AREA_PARAM_WIND_SOURCE: + wind_source = p_value; + break; + case PhysicsServer3D::AREA_PARAM_WIND_DIRECTION: + wind_direction = p_value; + break; + case PhysicsServer3D::AREA_PARAM_WIND_ATTENUATION_FACTOR: + ERR_FAIL_COND_MSG(wind_attenuation_factor < 0, "Wind attenuation factor must be a non-negative real number, but a negative number was specified."); + wind_attenuation_factor = p_value; + break; + } +} + +Variant GodotArea3D::get_param(PhysicsServer3D::AreaParameter p_param) const { + switch (p_param) { + case PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE: + return gravity_override_mode; + case PhysicsServer3D::AREA_PARAM_GRAVITY: + return gravity; + case PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR: + return gravity_vector; + case PhysicsServer3D::AREA_PARAM_GRAVITY_IS_POINT: + return gravity_is_point; + case PhysicsServer3D::AREA_PARAM_GRAVITY_POINT_UNIT_DISTANCE: + return gravity_point_unit_distance; + case PhysicsServer3D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE: + return linear_damping_override_mode; + case PhysicsServer3D::AREA_PARAM_LINEAR_DAMP: + return linear_damp; + case PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE: + return angular_damping_override_mode; + case PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP: + return angular_damp; + case PhysicsServer3D::AREA_PARAM_PRIORITY: + return priority; + case PhysicsServer3D::AREA_PARAM_WIND_FORCE_MAGNITUDE: + return wind_force_magnitude; + case PhysicsServer3D::AREA_PARAM_WIND_SOURCE: + return wind_source; + case PhysicsServer3D::AREA_PARAM_WIND_DIRECTION: + return wind_direction; + case PhysicsServer3D::AREA_PARAM_WIND_ATTENUATION_FACTOR: + return wind_attenuation_factor; + } + + return Variant(); +} + +void GodotArea3D::_queue_monitor_update() { + ERR_FAIL_NULL(get_space()); + + if (!monitor_query_list.in_list()) { + get_space()->area_add_to_monitor_query_list(&monitor_query_list); + } +} + +void GodotArea3D::set_monitorable(bool p_monitorable) { + if (monitorable == p_monitorable) { + return; + } + + monitorable = p_monitorable; + _set_static(!monitorable); + _shapes_changed(); +} + +void GodotArea3D::call_queries() { + if (!monitor_callback.is_null() && !monitored_bodies.is_empty()) { + if (monitor_callback.is_valid()) { + Variant res[5]; + Variant *resptr[5]; + for (int i = 0; i < 5; i++) { + resptr[i] = &res[i]; + } + + for (HashMap<BodyKey, BodyState, BodyKey>::Iterator E = monitored_bodies.begin(); E;) { + if (E->value.state == 0) { // Nothing happened + HashMap<BodyKey, BodyState, BodyKey>::Iterator next = E; + ++next; + monitored_bodies.remove(E); + E = next; + continue; + } + + res[0] = E->value.state > 0 ? PhysicsServer3D::AREA_BODY_ADDED : PhysicsServer3D::AREA_BODY_REMOVED; + res[1] = E->key.rid; + res[2] = E->key.instance_id; + res[3] = E->key.body_shape; + res[4] = E->key.area_shape; + + HashMap<BodyKey, BodyState, BodyKey>::Iterator next = E; + ++next; + monitored_bodies.remove(E); + E = next; + + Callable::CallError ce; + Variant ret; + monitor_callback.callp((const Variant **)resptr, 5, ret, ce); + + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT_ONCE("Error calling monitor callback method " + Variant::get_callable_error_text(monitor_callback, (const Variant **)resptr, 5, ce)); + } + } + } else { + monitored_bodies.clear(); + monitor_callback = Callable(); + } + } + + if (!area_monitor_callback.is_null() && !monitored_areas.is_empty()) { + if (area_monitor_callback.is_valid()) { + Variant res[5]; + Variant *resptr[5]; + for (int i = 0; i < 5; i++) { + resptr[i] = &res[i]; + } + + for (HashMap<BodyKey, BodyState, BodyKey>::Iterator E = monitored_areas.begin(); E;) { + if (E->value.state == 0) { // Nothing happened + HashMap<BodyKey, BodyState, BodyKey>::Iterator next = E; + ++next; + monitored_areas.remove(E); + E = next; + continue; + } + + res[0] = E->value.state > 0 ? PhysicsServer3D::AREA_BODY_ADDED : PhysicsServer3D::AREA_BODY_REMOVED; + res[1] = E->key.rid; + res[2] = E->key.instance_id; + res[3] = E->key.body_shape; + res[4] = E->key.area_shape; + + HashMap<BodyKey, BodyState, BodyKey>::Iterator next = E; + ++next; + monitored_areas.remove(E); + E = next; + + Callable::CallError ce; + Variant ret; + area_monitor_callback.callp((const Variant **)resptr, 5, ret, ce); + + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT_ONCE("Error calling area monitor callback method " + Variant::get_callable_error_text(area_monitor_callback, (const Variant **)resptr, 5, ce)); + } + } + } else { + monitored_areas.clear(); + area_monitor_callback = Callable(); + } + } +} + +void GodotArea3D::compute_gravity(const Vector3 &p_position, Vector3 &r_gravity) const { + if (is_gravity_point()) { + const real_t gr_unit_dist = get_gravity_point_unit_distance(); + Vector3 v = get_transform().xform(get_gravity_vector()) - p_position; + if (gr_unit_dist > 0) { + const real_t v_length_sq = v.length_squared(); + if (v_length_sq > 0) { + const real_t gravity_strength = get_gravity() * gr_unit_dist * gr_unit_dist / v_length_sq; + r_gravity = v.normalized() * gravity_strength; + } else { + r_gravity = Vector3(); + } + } else { + r_gravity = v.normalized() * get_gravity(); + } + } else { + r_gravity = get_gravity_vector() * get_gravity(); + } +} + +GodotArea3D::GodotArea3D() : + GodotCollisionObject3D(TYPE_AREA), + monitor_query_list(this), + moved_list(this) { + _set_static(true); //areas are never active + set_ray_pickable(false); +} + +GodotArea3D::~GodotArea3D() { +} diff --git a/modules/godot_physics_3d/godot_area_3d.h b/modules/godot_physics_3d/godot_area_3d.h new file mode 100644 index 0000000000..2c1a782630 --- /dev/null +++ b/modules/godot_physics_3d/godot_area_3d.h @@ -0,0 +1,240 @@ +/**************************************************************************/ +/* godot_area_3d.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 GODOT_AREA_3D_H +#define GODOT_AREA_3D_H + +#include "godot_collision_object_3d.h" + +#include "core/templates/self_list.h" +#include "servers/physics_server_3d.h" + +class GodotSpace3D; +class GodotBody3D; +class GodotSoftBody3D; +class GodotConstraint3D; + +class GodotArea3D : public GodotCollisionObject3D { + PhysicsServer3D::AreaSpaceOverrideMode gravity_override_mode = PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED; + PhysicsServer3D::AreaSpaceOverrideMode linear_damping_override_mode = PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED; + PhysicsServer3D::AreaSpaceOverrideMode angular_damping_override_mode = PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED; + + real_t gravity = 9.80665; + Vector3 gravity_vector = Vector3(0, -1, 0); + bool gravity_is_point = false; + real_t gravity_point_unit_distance = 0.0; + real_t linear_damp = 0.1; + real_t angular_damp = 0.1; + real_t wind_force_magnitude = 0.0; + real_t wind_attenuation_factor = 0.0; + Vector3 wind_source; + Vector3 wind_direction; + int priority = 0; + bool monitorable = false; + + Callable monitor_callback; + Callable area_monitor_callback; + + SelfList<GodotArea3D> monitor_query_list; + SelfList<GodotArea3D> moved_list; + + struct BodyKey { + RID rid; + ObjectID instance_id; + uint32_t body_shape = 0; + uint32_t area_shape = 0; + + static uint32_t hash(const BodyKey &p_key) { + uint32_t h = hash_one_uint64(p_key.rid.get_id()); + h = hash_murmur3_one_64(p_key.instance_id, h); + h = hash_murmur3_one_32(p_key.area_shape, h); + return hash_fmix32(hash_murmur3_one_32(p_key.body_shape, h)); + } + + _FORCE_INLINE_ bool operator==(const BodyKey &p_key) const { + return rid == p_key.rid && instance_id == p_key.instance_id && body_shape == p_key.body_shape && area_shape == p_key.area_shape; + } + + _FORCE_INLINE_ BodyKey() {} + BodyKey(GodotSoftBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape); + BodyKey(GodotBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape); + BodyKey(GodotArea3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape); + }; + + struct BodyState { + int state = 0; + _FORCE_INLINE_ void inc() { state++; } + _FORCE_INLINE_ void dec() { state--; } + }; + + HashMap<BodyKey, BodyState, BodyKey> monitored_soft_bodies; + HashMap<BodyKey, BodyState, BodyKey> monitored_bodies; + HashMap<BodyKey, BodyState, BodyKey> monitored_areas; + + HashSet<GodotConstraint3D *> constraints; + + virtual void _shapes_changed() override; + void _queue_monitor_update(); + + void _set_space_override_mode(PhysicsServer3D::AreaSpaceOverrideMode &r_mode, PhysicsServer3D::AreaSpaceOverrideMode p_new_mode); + +public: + void set_monitor_callback(const Callable &p_callback); + _FORCE_INLINE_ bool has_monitor_callback() const { return monitor_callback.is_valid(); } + + void set_area_monitor_callback(const Callable &p_callback); + _FORCE_INLINE_ bool has_area_monitor_callback() const { return area_monitor_callback.is_valid(); } + + _FORCE_INLINE_ void add_body_to_query(GodotBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape); + _FORCE_INLINE_ void remove_body_from_query(GodotBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape); + + _FORCE_INLINE_ void add_soft_body_to_query(GodotSoftBody3D *p_soft_body, uint32_t p_soft_body_shape, uint32_t p_area_shape); + _FORCE_INLINE_ void remove_soft_body_from_query(GodotSoftBody3D *p_soft_body, uint32_t p_soft_body_shape, uint32_t p_area_shape); + + _FORCE_INLINE_ void add_area_to_query(GodotArea3D *p_area, uint32_t p_area_shape, uint32_t p_self_shape); + _FORCE_INLINE_ void remove_area_from_query(GodotArea3D *p_area, uint32_t p_area_shape, uint32_t p_self_shape); + + void set_param(PhysicsServer3D::AreaParameter p_param, const Variant &p_value); + Variant get_param(PhysicsServer3D::AreaParameter p_param) const; + + _FORCE_INLINE_ void set_gravity(real_t p_gravity) { gravity = p_gravity; } + _FORCE_INLINE_ real_t get_gravity() const { return gravity; } + + _FORCE_INLINE_ void set_gravity_vector(const Vector3 &p_gravity) { gravity_vector = p_gravity; } + _FORCE_INLINE_ Vector3 get_gravity_vector() const { return gravity_vector; } + + _FORCE_INLINE_ void set_gravity_as_point(bool p_enable) { gravity_is_point = p_enable; } + _FORCE_INLINE_ bool is_gravity_point() const { return gravity_is_point; } + + _FORCE_INLINE_ void set_gravity_point_unit_distance(real_t scale) { gravity_point_unit_distance = scale; } + _FORCE_INLINE_ real_t get_gravity_point_unit_distance() const { return gravity_point_unit_distance; } + + _FORCE_INLINE_ void set_linear_damp(real_t p_linear_damp) { linear_damp = p_linear_damp; } + _FORCE_INLINE_ real_t get_linear_damp() const { return linear_damp; } + + _FORCE_INLINE_ void set_angular_damp(real_t p_angular_damp) { angular_damp = p_angular_damp; } + _FORCE_INLINE_ real_t get_angular_damp() const { return angular_damp; } + + _FORCE_INLINE_ void set_priority(int p_priority) { priority = p_priority; } + _FORCE_INLINE_ int get_priority() const { return priority; } + + _FORCE_INLINE_ void set_wind_force_magnitude(real_t p_wind_force_magnitude) { wind_force_magnitude = p_wind_force_magnitude; } + _FORCE_INLINE_ real_t get_wind_force_magnitude() const { return wind_force_magnitude; } + + _FORCE_INLINE_ void set_wind_attenuation_factor(real_t p_wind_attenuation_factor) { wind_attenuation_factor = p_wind_attenuation_factor; } + _FORCE_INLINE_ real_t get_wind_attenuation_factor() const { return wind_attenuation_factor; } + + _FORCE_INLINE_ void set_wind_source(const Vector3 &p_wind_source) { wind_source = p_wind_source; } + _FORCE_INLINE_ const Vector3 &get_wind_source() const { return wind_source; } + + _FORCE_INLINE_ void set_wind_direction(const Vector3 &p_wind_direction) { wind_direction = p_wind_direction; } + _FORCE_INLINE_ const Vector3 &get_wind_direction() const { return wind_direction; } + + _FORCE_INLINE_ void add_constraint(GodotConstraint3D *p_constraint) { constraints.insert(p_constraint); } + _FORCE_INLINE_ void remove_constraint(GodotConstraint3D *p_constraint) { constraints.erase(p_constraint); } + _FORCE_INLINE_ const HashSet<GodotConstraint3D *> &get_constraints() const { return constraints; } + _FORCE_INLINE_ void clear_constraints() { constraints.clear(); } + + void set_monitorable(bool p_monitorable); + _FORCE_INLINE_ bool is_monitorable() const { return monitorable; } + + void set_transform(const Transform3D &p_transform); + + void set_space(GodotSpace3D *p_space) override; + + void call_queries(); + + void compute_gravity(const Vector3 &p_position, Vector3 &r_gravity) const; + + GodotArea3D(); + ~GodotArea3D(); +}; + +void GodotArea3D::add_soft_body_to_query(GodotSoftBody3D *p_soft_body, uint32_t p_soft_body_shape, uint32_t p_area_shape) { + BodyKey bk(p_soft_body, p_soft_body_shape, p_area_shape); + monitored_soft_bodies[bk].inc(); + if (!monitor_query_list.in_list()) { + _queue_monitor_update(); + } +} + +void GodotArea3D::remove_soft_body_from_query(GodotSoftBody3D *p_soft_body, uint32_t p_soft_body_shape, uint32_t p_area_shape) { + BodyKey bk(p_soft_body, p_soft_body_shape, p_area_shape); + monitored_soft_bodies[bk].dec(); + if (get_space() && !monitor_query_list.in_list()) { + _queue_monitor_update(); + } +} + +void GodotArea3D::add_body_to_query(GodotBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) { + BodyKey bk(p_body, p_body_shape, p_area_shape); + monitored_bodies[bk].inc(); + if (!monitor_query_list.in_list()) { + _queue_monitor_update(); + } +} + +void GodotArea3D::remove_body_from_query(GodotBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) { + BodyKey bk(p_body, p_body_shape, p_area_shape); + monitored_bodies[bk].dec(); + if (get_space() && !monitor_query_list.in_list()) { + _queue_monitor_update(); + } +} + +void GodotArea3D::add_area_to_query(GodotArea3D *p_area, uint32_t p_area_shape, uint32_t p_self_shape) { + BodyKey bk(p_area, p_area_shape, p_self_shape); + monitored_areas[bk].inc(); + if (!monitor_query_list.in_list()) { + _queue_monitor_update(); + } +} + +void GodotArea3D::remove_area_from_query(GodotArea3D *p_area, uint32_t p_area_shape, uint32_t p_self_shape) { + BodyKey bk(p_area, p_area_shape, p_self_shape); + monitored_areas[bk].dec(); + if (get_space() && !monitor_query_list.in_list()) { + _queue_monitor_update(); + } +} + +struct AreaCMP { + GodotArea3D *area = nullptr; + int refCount = 0; + _FORCE_INLINE_ bool operator==(const AreaCMP &p_cmp) const { return area->get_self() == p_cmp.area->get_self(); } + _FORCE_INLINE_ bool operator<(const AreaCMP &p_cmp) const { return area->get_priority() < p_cmp.area->get_priority(); } + _FORCE_INLINE_ AreaCMP() {} + _FORCE_INLINE_ AreaCMP(GodotArea3D *p_area) { + area = p_area; + refCount = 1; + } +}; + +#endif // GODOT_AREA_3D_H diff --git a/modules/godot_physics_3d/godot_area_pair_3d.cpp b/modules/godot_physics_3d/godot_area_pair_3d.cpp new file mode 100644 index 0000000000..aaa96f5a28 --- /dev/null +++ b/modules/godot_physics_3d/godot_area_pair_3d.cpp @@ -0,0 +1,294 @@ +/**************************************************************************/ +/* godot_area_pair_3d.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 "godot_area_pair_3d.h" + +#include "godot_collision_solver_3d.h" + +bool GodotAreaPair3D::setup(real_t p_step) { + bool result = false; + if (area->collides_with(body) && GodotCollisionSolver3D::solve_static(body->get_shape(body_shape), body->get_transform() * body->get_shape_transform(body_shape), area->get_shape(area_shape), area->get_transform() * area->get_shape_transform(area_shape), nullptr, this)) { + result = true; + } + + process_collision = false; + has_space_override = false; + if (result != colliding) { + if ((int)area->get_param(PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE) != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + has_space_override = true; + } else if ((int)area->get_param(PhysicsServer3D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE) != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + has_space_override = true; + } else if ((int)area->get_param(PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE) != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + has_space_override = true; + } + process_collision = has_space_override; + + if (area->has_monitor_callback()) { + process_collision = true; + } + + colliding = result; + } + + return process_collision; +} + +bool GodotAreaPair3D::pre_solve(real_t p_step) { + if (!process_collision) { + return false; + } + + if (colliding) { + if (has_space_override) { + body_has_attached_area = true; + body->add_area(area); + } + + if (area->has_monitor_callback()) { + area->add_body_to_query(body, body_shape, area_shape); + } + } else { + if (has_space_override) { + body_has_attached_area = false; + body->remove_area(area); + } + + if (area->has_monitor_callback()) { + area->remove_body_from_query(body, body_shape, area_shape); + } + } + + return false; // Never do any post solving. +} + +void GodotAreaPair3D::solve(real_t p_step) { + // Nothing to do. +} + +GodotAreaPair3D::GodotAreaPair3D(GodotBody3D *p_body, int p_body_shape, GodotArea3D *p_area, int p_area_shape) { + body = p_body; + area = p_area; + body_shape = p_body_shape; + area_shape = p_area_shape; + body->add_constraint(this, 0); + area->add_constraint(this); + if (p_body->get_mode() == PhysicsServer3D::BODY_MODE_KINEMATIC) { + p_body->set_active(true); + } +} + +GodotAreaPair3D::~GodotAreaPair3D() { + if (colliding) { + if (body_has_attached_area) { + body_has_attached_area = false; + body->remove_area(area); + } + if (area->has_monitor_callback()) { + area->remove_body_from_query(body, body_shape, area_shape); + } + } + body->remove_constraint(this); + area->remove_constraint(this); +} + +//////////////////////////////////////////////////// + +bool GodotArea2Pair3D::setup(real_t p_step) { + bool result_a = area_a->collides_with(area_b); + bool result_b = area_b->collides_with(area_a); + if ((result_a || result_b) && !GodotCollisionSolver3D::solve_static(area_a->get_shape(shape_a), area_a->get_transform() * area_a->get_shape_transform(shape_a), area_b->get_shape(shape_b), area_b->get_transform() * area_b->get_shape_transform(shape_b), nullptr, this)) { + result_a = false; + result_b = false; + } + + bool process_collision = false; + + process_collision_a = false; + if (result_a != colliding_a) { + if (area_a->has_area_monitor_callback() && area_b_monitorable) { + process_collision_a = true; + process_collision = true; + } + colliding_a = result_a; + } + + process_collision_b = false; + if (result_b != colliding_b) { + if (area_b->has_area_monitor_callback() && area_a_monitorable) { + process_collision_b = true; + process_collision = true; + } + colliding_b = result_b; + } + + return process_collision; +} + +bool GodotArea2Pair3D::pre_solve(real_t p_step) { + if (process_collision_a) { + if (colliding_a) { + area_a->add_area_to_query(area_b, shape_b, shape_a); + } else { + area_a->remove_area_from_query(area_b, shape_b, shape_a); + } + } + + if (process_collision_b) { + if (colliding_b) { + area_b->add_area_to_query(area_a, shape_a, shape_b); + } else { + area_b->remove_area_from_query(area_a, shape_a, shape_b); + } + } + + return false; // Never do any post solving. +} + +void GodotArea2Pair3D::solve(real_t p_step) { + // Nothing to do. +} + +GodotArea2Pair3D::GodotArea2Pair3D(GodotArea3D *p_area_a, int p_shape_a, GodotArea3D *p_area_b, int p_shape_b) { + area_a = p_area_a; + area_b = p_area_b; + shape_a = p_shape_a; + shape_b = p_shape_b; + area_a_monitorable = area_a->is_monitorable(); + area_b_monitorable = area_b->is_monitorable(); + area_a->add_constraint(this); + area_b->add_constraint(this); +} + +GodotArea2Pair3D::~GodotArea2Pair3D() { + if (colliding_a) { + if (area_a->has_area_monitor_callback() && area_b_monitorable) { + area_a->remove_area_from_query(area_b, shape_b, shape_a); + } + } + + if (colliding_b) { + if (area_b->has_area_monitor_callback() && area_a_monitorable) { + area_b->remove_area_from_query(area_a, shape_a, shape_b); + } + } + + area_a->remove_constraint(this); + area_b->remove_constraint(this); +} + +//////////////////////////////////////////////////// + +bool GodotAreaSoftBodyPair3D::setup(real_t p_step) { + bool result = false; + if ( + area->collides_with(soft_body) && + GodotCollisionSolver3D::solve_static( + soft_body->get_shape(soft_body_shape), + soft_body->get_transform() * soft_body->get_shape_transform(soft_body_shape), + area->get_shape(area_shape), + area->get_transform() * area->get_shape_transform(area_shape), + nullptr, + this)) { + result = true; + } + + process_collision = false; + has_space_override = false; + if (result != colliding) { + if ((int)area->get_param(PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE) != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + has_space_override = true; + } else if (area->get_wind_force_magnitude() > CMP_EPSILON) { + has_space_override = true; + } + + if (area->has_monitor_callback()) { + process_collision = true; + } + + colliding = result; + } + + return process_collision; +} + +bool GodotAreaSoftBodyPair3D::pre_solve(real_t p_step) { + if (!process_collision) { + return false; + } + + if (colliding) { + if (has_space_override) { + body_has_attached_area = true; + soft_body->add_area(area); + } + + if (area->has_monitor_callback()) { + area->add_soft_body_to_query(soft_body, soft_body_shape, area_shape); + } + } else { + if (has_space_override) { + body_has_attached_area = false; + soft_body->remove_area(area); + } + + if (area->has_monitor_callback()) { + area->remove_soft_body_from_query(soft_body, soft_body_shape, area_shape); + } + } + + return false; // Never do any post solving. +} + +void GodotAreaSoftBodyPair3D::solve(real_t p_step) { + // Nothing to do. +} + +GodotAreaSoftBodyPair3D::GodotAreaSoftBodyPair3D(GodotSoftBody3D *p_soft_body, int p_soft_body_shape, GodotArea3D *p_area, int p_area_shape) { + soft_body = p_soft_body; + area = p_area; + soft_body_shape = p_soft_body_shape; + area_shape = p_area_shape; + soft_body->add_constraint(this); + area->add_constraint(this); +} + +GodotAreaSoftBodyPair3D::~GodotAreaSoftBodyPair3D() { + if (colliding) { + if (body_has_attached_area) { + body_has_attached_area = false; + soft_body->remove_area(area); + } + if (area->has_monitor_callback()) { + area->remove_soft_body_from_query(soft_body, soft_body_shape, area_shape); + } + } + soft_body->remove_constraint(this); + area->remove_constraint(this); +} diff --git a/modules/godot_physics_3d/godot_area_pair_3d.h b/modules/godot_physics_3d/godot_area_pair_3d.h new file mode 100644 index 0000000000..a2c5df0f7a --- /dev/null +++ b/modules/godot_physics_3d/godot_area_pair_3d.h @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* godot_area_pair_3d.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 GODOT_AREA_PAIR_3D_H +#define GODOT_AREA_PAIR_3D_H + +#include "godot_area_3d.h" +#include "godot_body_3d.h" +#include "godot_constraint_3d.h" +#include "godot_soft_body_3d.h" + +class GodotAreaPair3D : public GodotConstraint3D { + GodotBody3D *body = nullptr; + GodotArea3D *area = nullptr; + int body_shape; + int area_shape; + bool colliding = false; + bool process_collision = false; + bool has_space_override = false; + bool body_has_attached_area = false; + +public: + virtual bool setup(real_t p_step) override; + virtual bool pre_solve(real_t p_step) override; + virtual void solve(real_t p_step) override; + + GodotAreaPair3D(GodotBody3D *p_body, int p_body_shape, GodotArea3D *p_area, int p_area_shape); + ~GodotAreaPair3D(); +}; + +class GodotArea2Pair3D : public GodotConstraint3D { + GodotArea3D *area_a = nullptr; + GodotArea3D *area_b = nullptr; + int shape_a; + int shape_b; + bool colliding_a = false; + bool colliding_b = false; + bool process_collision_a = false; + bool process_collision_b = false; + bool area_a_monitorable; + bool area_b_monitorable; + +public: + virtual bool setup(real_t p_step) override; + virtual bool pre_solve(real_t p_step) override; + virtual void solve(real_t p_step) override; + + GodotArea2Pair3D(GodotArea3D *p_area_a, int p_shape_a, GodotArea3D *p_area_b, int p_shape_b); + ~GodotArea2Pair3D(); +}; + +class GodotAreaSoftBodyPair3D : public GodotConstraint3D { + GodotSoftBody3D *soft_body = nullptr; + GodotArea3D *area = nullptr; + int soft_body_shape; + int area_shape; + bool colliding = false; + bool process_collision = false; + bool has_space_override = false; + bool body_has_attached_area = false; + +public: + virtual bool setup(real_t p_step) override; + virtual bool pre_solve(real_t p_step) override; + virtual void solve(real_t p_step) override; + + GodotAreaSoftBodyPair3D(GodotSoftBody3D *p_sof_body, int p_soft_body_shape, GodotArea3D *p_area, int p_area_shape); + ~GodotAreaSoftBodyPair3D(); +}; + +#endif // GODOT_AREA_PAIR_3D_H diff --git a/modules/godot_physics_3d/godot_body_3d.cpp b/modules/godot_physics_3d/godot_body_3d.cpp new file mode 100644 index 0000000000..669c4b985b --- /dev/null +++ b/modules/godot_physics_3d/godot_body_3d.cpp @@ -0,0 +1,841 @@ +/**************************************************************************/ +/* godot_body_3d.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 "godot_body_3d.h" + +#include "godot_area_3d.h" +#include "godot_body_direct_state_3d.h" +#include "godot_space_3d.h" + +void GodotBody3D::_mass_properties_changed() { + if (get_space() && !mass_properties_update_list.in_list()) { + get_space()->body_add_to_mass_properties_update_list(&mass_properties_update_list); + } +} + +void GodotBody3D::_update_transform_dependent() { + center_of_mass = get_transform().basis.xform(center_of_mass_local); + principal_inertia_axes = get_transform().basis * principal_inertia_axes_local; + + // Update inertia tensor. + Basis tb = principal_inertia_axes; + Basis tbt = tb.transposed(); + Basis diag; + diag.scale(_inv_inertia); + _inv_inertia_tensor = tb * diag * tbt; +} + +void GodotBody3D::update_mass_properties() { + // Update shapes and motions. + + switch (mode) { + case PhysicsServer3D::BODY_MODE_RIGID: { + real_t total_area = 0; + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } + + total_area += get_shape_area(i); + } + + if (calculate_center_of_mass) { + // We have to recompute the center of mass. + center_of_mass_local.zero(); + + if (total_area != 0.0) { + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } + + real_t area = get_shape_area(i); + + real_t mass_new = area * mass / total_area; + + // NOTE: we assume that the shape origin is also its center of mass. + center_of_mass_local += mass_new * get_shape_transform(i).origin; + } + + center_of_mass_local /= mass; + } + } + + if (calculate_inertia) { + // Recompute the inertia tensor. + Basis inertia_tensor; + inertia_tensor.set_zero(); + bool inertia_set = false; + + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } + + real_t area = get_shape_area(i); + if (area == 0.0) { + continue; + } + + inertia_set = true; + + const GodotShape3D *shape = get_shape(i); + + real_t mass_new = area * mass / total_area; + + Basis shape_inertia_tensor = Basis::from_scale(shape->get_moment_of_inertia(mass_new)); + Transform3D shape_transform = get_shape_transform(i); + Basis shape_basis = shape_transform.basis.orthonormalized(); + + // NOTE: we don't take the scale of collision shapes into account when computing the inertia tensor! + shape_inertia_tensor = shape_basis * shape_inertia_tensor * shape_basis.transposed(); + + Vector3 shape_origin = shape_transform.origin - center_of_mass_local; + inertia_tensor += shape_inertia_tensor + (Basis() * shape_origin.dot(shape_origin) - shape_origin.outer(shape_origin)) * mass_new; + } + + // Set the inertia to a valid value when there are no valid shapes. + if (!inertia_set) { + inertia_tensor = Basis(); + } + + // Handle partial custom inertia. + if (inertia.x > 0.0) { + inertia_tensor[0][0] = inertia.x; + } + if (inertia.y > 0.0) { + inertia_tensor[1][1] = inertia.y; + } + if (inertia.z > 0.0) { + inertia_tensor[2][2] = inertia.z; + } + + // Compute the principal axes of inertia. + principal_inertia_axes_local = inertia_tensor.diagonalize().transposed(); + _inv_inertia = inertia_tensor.get_main_diagonal().inverse(); + } + + if (mass) { + _inv_mass = 1.0 / mass; + } else { + _inv_mass = 0; + } + + } break; + case PhysicsServer3D::BODY_MODE_KINEMATIC: + case PhysicsServer3D::BODY_MODE_STATIC: { + _inv_inertia = Vector3(); + _inv_mass = 0; + } break; + case PhysicsServer3D::BODY_MODE_RIGID_LINEAR: { + _inv_inertia_tensor.set_zero(); + _inv_mass = 1.0 / mass; + + } break; + } + + _update_transform_dependent(); +} + +void GodotBody3D::reset_mass_properties() { + calculate_inertia = true; + calculate_center_of_mass = true; + _mass_properties_changed(); +} + +void GodotBody3D::set_active(bool p_active) { + if (active == p_active) { + return; + } + + active = p_active; + + if (active) { + if (mode == PhysicsServer3D::BODY_MODE_STATIC) { + // Static bodies can't be active. + active = false; + } else if (get_space()) { + get_space()->body_add_to_active_list(&active_list); + } + } else if (get_space()) { + get_space()->body_remove_from_active_list(&active_list); + } +} + +void GodotBody3D::set_param(PhysicsServer3D::BodyParameter p_param, const Variant &p_value) { + switch (p_param) { + case PhysicsServer3D::BODY_PARAM_BOUNCE: { + bounce = p_value; + } break; + case PhysicsServer3D::BODY_PARAM_FRICTION: { + friction = p_value; + } break; + case PhysicsServer3D::BODY_PARAM_MASS: { + real_t mass_value = p_value; + ERR_FAIL_COND(mass_value <= 0); + mass = mass_value; + if (mode >= PhysicsServer3D::BODY_MODE_RIGID) { + _mass_properties_changed(); + } + } break; + case PhysicsServer3D::BODY_PARAM_INERTIA: { + inertia = p_value; + if ((inertia.x <= 0.0) || (inertia.y <= 0.0) || (inertia.z <= 0.0)) { + calculate_inertia = true; + if (mode == PhysicsServer3D::BODY_MODE_RIGID) { + _mass_properties_changed(); + } + } else { + calculate_inertia = false; + if (mode == PhysicsServer3D::BODY_MODE_RIGID) { + principal_inertia_axes_local = Basis(); + _inv_inertia = inertia.inverse(); + _update_transform_dependent(); + } + } + } break; + case PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS: { + calculate_center_of_mass = false; + center_of_mass_local = p_value; + _update_transform_dependent(); + } break; + case PhysicsServer3D::BODY_PARAM_GRAVITY_SCALE: { + if (Math::is_zero_approx(gravity_scale)) { + wakeup(); + } + gravity_scale = p_value; + } break; + case PhysicsServer3D::BODY_PARAM_LINEAR_DAMP_MODE: { + int mode_value = p_value; + linear_damp_mode = (PhysicsServer3D::BodyDampMode)mode_value; + } break; + case PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP_MODE: { + int mode_value = p_value; + angular_damp_mode = (PhysicsServer3D::BodyDampMode)mode_value; + } break; + case PhysicsServer3D::BODY_PARAM_LINEAR_DAMP: { + linear_damp = p_value; + } break; + case PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP: { + angular_damp = p_value; + } break; + default: { + } + } +} + +Variant GodotBody3D::get_param(PhysicsServer3D::BodyParameter p_param) const { + switch (p_param) { + case PhysicsServer3D::BODY_PARAM_BOUNCE: { + return bounce; + } break; + case PhysicsServer3D::BODY_PARAM_FRICTION: { + return friction; + } break; + case PhysicsServer3D::BODY_PARAM_MASS: { + return mass; + } break; + case PhysicsServer3D::BODY_PARAM_INERTIA: { + if (mode == PhysicsServer3D::BODY_MODE_RIGID) { + return _inv_inertia.inverse(); + } else { + return Vector3(); + } + } break; + case PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS: { + return center_of_mass_local; + } break; + case PhysicsServer3D::BODY_PARAM_GRAVITY_SCALE: { + return gravity_scale; + } break; + case PhysicsServer3D::BODY_PARAM_LINEAR_DAMP_MODE: { + return linear_damp_mode; + } + case PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP_MODE: { + return angular_damp_mode; + } + case PhysicsServer3D::BODY_PARAM_LINEAR_DAMP: { + return linear_damp; + } break; + case PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP: { + return angular_damp; + } break; + + default: { + } + } + + return 0; +} + +void GodotBody3D::set_mode(PhysicsServer3D::BodyMode p_mode) { + PhysicsServer3D::BodyMode prev = mode; + mode = p_mode; + + switch (p_mode) { + case PhysicsServer3D::BODY_MODE_STATIC: + case PhysicsServer3D::BODY_MODE_KINEMATIC: { + _set_inv_transform(get_transform().affine_inverse()); + _inv_mass = 0; + _inv_inertia = Vector3(); + _set_static(p_mode == PhysicsServer3D::BODY_MODE_STATIC); + set_active(p_mode == PhysicsServer3D::BODY_MODE_KINEMATIC && contacts.size()); + linear_velocity = Vector3(); + angular_velocity = Vector3(); + if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC && prev != mode) { + first_time_kinematic = true; + } + _update_transform_dependent(); + + } break; + case PhysicsServer3D::BODY_MODE_RIGID: { + _inv_mass = mass > 0 ? (1.0 / mass) : 0; + if (!calculate_inertia) { + principal_inertia_axes_local = Basis(); + _inv_inertia = inertia.inverse(); + _update_transform_dependent(); + } + _mass_properties_changed(); + _set_static(false); + set_active(true); + + } break; + case PhysicsServer3D::BODY_MODE_RIGID_LINEAR: { + _inv_mass = mass > 0 ? (1.0 / mass) : 0; + _inv_inertia = Vector3(); + angular_velocity = Vector3(); + _update_transform_dependent(); + _set_static(false); + set_active(true); + } + } +} + +PhysicsServer3D::BodyMode GodotBody3D::get_mode() const { + return mode; +} + +void GodotBody3D::_shapes_changed() { + _mass_properties_changed(); + wakeup(); + wakeup_neighbours(); +} + +void GodotBody3D::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant) { + switch (p_state) { + case PhysicsServer3D::BODY_STATE_TRANSFORM: { + if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC) { + new_transform = p_variant; + //wakeup_neighbours(); + set_active(true); + if (first_time_kinematic) { + _set_transform(p_variant); + _set_inv_transform(get_transform().affine_inverse()); + first_time_kinematic = false; + } + + } else if (mode == PhysicsServer3D::BODY_MODE_STATIC) { + _set_transform(p_variant); + _set_inv_transform(get_transform().affine_inverse()); + wakeup_neighbours(); + } else { + Transform3D t = p_variant; + t.orthonormalize(); + new_transform = get_transform(); //used as old to compute motion + if (new_transform == t) { + break; + } + _set_transform(t); + _set_inv_transform(get_transform().inverse()); + _update_transform_dependent(); + } + wakeup(); + + } break; + case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { + linear_velocity = p_variant; + constant_linear_velocity = linear_velocity; + wakeup(); + } break; + case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { + angular_velocity = p_variant; + constant_angular_velocity = angular_velocity; + wakeup(); + + } break; + case PhysicsServer3D::BODY_STATE_SLEEPING: { + if (mode == PhysicsServer3D::BODY_MODE_STATIC || mode == PhysicsServer3D::BODY_MODE_KINEMATIC) { + break; + } + bool do_sleep = p_variant; + if (do_sleep) { + linear_velocity = Vector3(); + //biased_linear_velocity=Vector3(); + angular_velocity = Vector3(); + //biased_angular_velocity=Vector3(); + set_active(false); + } else { + set_active(true); + } + } break; + case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { + can_sleep = p_variant; + if (mode >= PhysicsServer3D::BODY_MODE_RIGID && !active && !can_sleep) { + set_active(true); + } + + } break; + } +} + +Variant GodotBody3D::get_state(PhysicsServer3D::BodyState p_state) const { + switch (p_state) { + case PhysicsServer3D::BODY_STATE_TRANSFORM: { + return get_transform(); + } break; + case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { + return linear_velocity; + } break; + case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { + return angular_velocity; + } break; + case PhysicsServer3D::BODY_STATE_SLEEPING: { + return !is_active(); + } break; + case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { + return can_sleep; + } break; + } + + return Variant(); +} + +void GodotBody3D::set_space(GodotSpace3D *p_space) { + if (get_space()) { + if (mass_properties_update_list.in_list()) { + get_space()->body_remove_from_mass_properties_update_list(&mass_properties_update_list); + } + if (active_list.in_list()) { + get_space()->body_remove_from_active_list(&active_list); + } + if (direct_state_query_list.in_list()) { + get_space()->body_remove_from_state_query_list(&direct_state_query_list); + } + } + + _set_space(p_space); + + if (get_space()) { + _mass_properties_changed(); + + if (active && !active_list.in_list()) { + get_space()->body_add_to_active_list(&active_list); + } + } +} + +void GodotBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool lock) { + if (lock) { + locked_axis |= p_axis; + } else { + locked_axis &= ~p_axis; + } +} + +bool GodotBody3D::is_axis_locked(PhysicsServer3D::BodyAxis p_axis) const { + return locked_axis & p_axis; +} + +void GodotBody3D::integrate_forces(real_t p_step) { + if (mode == PhysicsServer3D::BODY_MODE_STATIC) { + return; + } + + ERR_FAIL_NULL(get_space()); + + int ac = areas.size(); + + bool gravity_done = false; + bool linear_damp_done = false; + bool angular_damp_done = false; + + bool stopped = false; + + gravity = Vector3(0, 0, 0); + + total_linear_damp = 0.0; + total_angular_damp = 0.0; + + // Combine gravity and damping from overlapping areas in priority order. + if (ac) { + areas.sort(); + const AreaCMP *aa = &areas[0]; + for (int i = ac - 1; i >= 0 && !stopped; i--) { + if (!gravity_done) { + PhysicsServer3D::AreaSpaceOverrideMode area_gravity_mode = (PhysicsServer3D::AreaSpaceOverrideMode)(int)aa[i].area->get_param(PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE); + if (area_gravity_mode != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + Vector3 area_gravity; + aa[i].area->compute_gravity(get_transform().get_origin(), area_gravity); + switch (area_gravity_mode) { + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { + gravity += area_gravity; + gravity_done = area_gravity_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE; + } break; + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: { + gravity = area_gravity; + gravity_done = area_gravity_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE; + } break; + default: { + } + } + } + } + if (!linear_damp_done) { + PhysicsServer3D::AreaSpaceOverrideMode area_linear_damp_mode = (PhysicsServer3D::AreaSpaceOverrideMode)(int)aa[i].area->get_param(PhysicsServer3D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE); + if (area_linear_damp_mode != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + real_t area_linear_damp = aa[i].area->get_linear_damp(); + switch (area_linear_damp_mode) { + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { + total_linear_damp += area_linear_damp; + linear_damp_done = area_linear_damp_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE; + } break; + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: { + total_linear_damp = area_linear_damp; + linear_damp_done = area_linear_damp_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE; + } break; + default: { + } + } + } + } + if (!angular_damp_done) { + PhysicsServer3D::AreaSpaceOverrideMode area_angular_damp_mode = (PhysicsServer3D::AreaSpaceOverrideMode)(int)aa[i].area->get_param(PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE); + if (area_angular_damp_mode != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + real_t area_angular_damp = aa[i].area->get_angular_damp(); + switch (area_angular_damp_mode) { + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { + total_angular_damp += area_angular_damp; + angular_damp_done = area_angular_damp_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE; + } break; + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: { + total_angular_damp = area_angular_damp; + angular_damp_done = area_angular_damp_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE; + } break; + default: { + } + } + } + } + stopped = gravity_done && linear_damp_done && angular_damp_done; + } + } + + // Add default gravity and damping from space area. + if (!stopped) { + GodotArea3D *default_area = get_space()->get_default_area(); + ERR_FAIL_NULL(default_area); + + if (!gravity_done) { + Vector3 default_gravity; + default_area->compute_gravity(get_transform().get_origin(), default_gravity); + gravity += default_gravity; + } + + if (!linear_damp_done) { + total_linear_damp += default_area->get_linear_damp(); + } + + if (!angular_damp_done) { + total_angular_damp += default_area->get_angular_damp(); + } + } + + // Override linear damping with body's value. + switch (linear_damp_mode) { + case PhysicsServer3D::BODY_DAMP_MODE_COMBINE: { + total_linear_damp += linear_damp; + } break; + case PhysicsServer3D::BODY_DAMP_MODE_REPLACE: { + total_linear_damp = linear_damp; + } break; + } + + // Override angular damping with body's value. + switch (angular_damp_mode) { + case PhysicsServer3D::BODY_DAMP_MODE_COMBINE: { + total_angular_damp += angular_damp; + } break; + case PhysicsServer3D::BODY_DAMP_MODE_REPLACE: { + total_angular_damp = angular_damp; + } break; + } + + gravity *= gravity_scale; + + prev_linear_velocity = linear_velocity; + prev_angular_velocity = angular_velocity; + + Vector3 motion; + bool do_motion = false; + + if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC) { + //compute motion, angular and etc. velocities from prev transform + motion = new_transform.origin - get_transform().origin; + do_motion = true; + linear_velocity = constant_linear_velocity + motion / p_step; + + //compute a FAKE angular velocity, not so easy + Basis rot = new_transform.basis.orthonormalized() * get_transform().basis.orthonormalized().transposed(); + Vector3 axis; + real_t angle; + + rot.get_axis_angle(axis, angle); + axis.normalize(); + angular_velocity = constant_angular_velocity + axis * (angle / p_step); + } else { + if (!omit_force_integration) { + //overridden by direct state query + + Vector3 force = gravity * mass + applied_force + constant_force; + Vector3 torque = applied_torque + constant_torque; + + real_t damp = 1.0 - p_step * total_linear_damp; + + if (damp < 0) { // reached zero in the given time + damp = 0; + } + + real_t angular_damp_new = 1.0 - p_step * total_angular_damp; + + if (angular_damp_new < 0) { // reached zero in the given time + angular_damp_new = 0; + } + + linear_velocity *= damp; + angular_velocity *= angular_damp_new; + + linear_velocity += _inv_mass * force * p_step; + angular_velocity += _inv_inertia_tensor.xform(torque) * p_step; + } + + if (continuous_cd) { + motion = linear_velocity * p_step; + do_motion = true; + } + } + + applied_force = Vector3(); + applied_torque = Vector3(); + + biased_angular_velocity = Vector3(); + biased_linear_velocity = Vector3(); + + if (do_motion) { //shapes temporarily extend for raycast + _update_shapes_with_motion(motion); + } + + contact_count = 0; +} + +void GodotBody3D::integrate_velocities(real_t p_step) { + if (mode == PhysicsServer3D::BODY_MODE_STATIC) { + return; + } + + ERR_FAIL_NULL(get_space()); + + if (fi_callback_data || body_state_callback.is_valid()) { + get_space()->body_add_to_state_query_list(&direct_state_query_list); + } + + //apply axis lock linear + for (int i = 0; i < 3; i++) { + if (is_axis_locked((PhysicsServer3D::BodyAxis)(1 << i))) { + linear_velocity[i] = 0; + biased_linear_velocity[i] = 0; + new_transform.origin[i] = get_transform().origin[i]; + } + } + //apply axis lock angular + for (int i = 0; i < 3; i++) { + if (is_axis_locked((PhysicsServer3D::BodyAxis)(1 << (i + 3)))) { + angular_velocity[i] = 0; + biased_angular_velocity[i] = 0; + } + } + + if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC) { + _set_transform(new_transform, false); + _set_inv_transform(new_transform.affine_inverse()); + if (contacts.size() == 0 && linear_velocity == Vector3() && angular_velocity == Vector3()) { + set_active(false); //stopped moving, deactivate + } + + return; + } + + Vector3 total_angular_velocity = angular_velocity + biased_angular_velocity; + + real_t ang_vel = total_angular_velocity.length(); + Transform3D transform_new = get_transform(); + + if (!Math::is_zero_approx(ang_vel)) { + Vector3 ang_vel_axis = total_angular_velocity / ang_vel; + Basis rot(ang_vel_axis, ang_vel * p_step); + Basis identity3(1, 0, 0, 0, 1, 0, 0, 0, 1); + transform_new.origin += ((identity3 - rot) * transform_new.basis).xform(center_of_mass_local); + transform_new.basis = rot * transform_new.basis; + transform_new.orthonormalize(); + } + + Vector3 total_linear_velocity = linear_velocity + biased_linear_velocity; + /*for(int i=0;i<3;i++) { + if (axis_lock&(1<<i)) { + transform_new.origin[i]=0.0; + } + }*/ + + transform_new.origin += total_linear_velocity * p_step; + + _set_transform(transform_new); + _set_inv_transform(get_transform().inverse()); + + _update_transform_dependent(); +} + +void GodotBody3D::wakeup_neighbours() { + for (const KeyValue<GodotConstraint3D *, int> &E : constraint_map) { + const GodotConstraint3D *c = E.key; + GodotBody3D **n = c->get_body_ptr(); + int bc = c->get_body_count(); + + for (int i = 0; i < bc; i++) { + if (i == E.value) { + continue; + } + GodotBody3D *b = n[i]; + if (b->mode < PhysicsServer3D::BODY_MODE_RIGID) { + continue; + } + + if (!b->is_active()) { + b->set_active(true); + } + } + } +} + +void GodotBody3D::call_queries() { + Variant direct_state_variant = get_direct_state(); + + if (fi_callback_data) { + if (!fi_callback_data->callable.is_valid()) { + set_force_integration_callback(Callable()); + } else { + const Variant *vp[2] = { &direct_state_variant, &fi_callback_data->udata }; + + Callable::CallError ce; + int argc = (fi_callback_data->udata.get_type() == Variant::NIL) ? 1 : 2; + Variant rv; + fi_callback_data->callable.callp(vp, argc, rv, ce); + } + } + + if (body_state_callback.is_valid()) { + body_state_callback.call(direct_state_variant); + } +} + +bool GodotBody3D::sleep_test(real_t p_step) { + if (mode == PhysicsServer3D::BODY_MODE_STATIC || mode == PhysicsServer3D::BODY_MODE_KINEMATIC) { + return true; + } else if (!can_sleep) { + return false; + } + + ERR_FAIL_NULL_V(get_space(), true); + + if (Math::abs(angular_velocity.length()) < get_space()->get_body_angular_velocity_sleep_threshold() && Math::abs(linear_velocity.length_squared()) < get_space()->get_body_linear_velocity_sleep_threshold() * get_space()->get_body_linear_velocity_sleep_threshold()) { + still_time += p_step; + + return still_time > get_space()->get_body_time_to_sleep(); + } else { + still_time = 0; //maybe this should be set to 0 on set_active? + return false; + } +} + +void GodotBody3D::set_state_sync_callback(const Callable &p_callable) { + body_state_callback = p_callable; +} + +void GodotBody3D::set_force_integration_callback(const Callable &p_callable, const Variant &p_udata) { + if (p_callable.is_valid()) { + if (!fi_callback_data) { + fi_callback_data = memnew(ForceIntegrationCallbackData); + } + fi_callback_data->callable = p_callable; + fi_callback_data->udata = p_udata; + } else if (fi_callback_data) { + memdelete(fi_callback_data); + fi_callback_data = nullptr; + } +} + +GodotPhysicsDirectBodyState3D *GodotBody3D::get_direct_state() { + if (!direct_state) { + direct_state = memnew(GodotPhysicsDirectBodyState3D); + direct_state->body = this; + } + return direct_state; +} + +GodotBody3D::GodotBody3D() : + GodotCollisionObject3D(TYPE_BODY), + active_list(this), + mass_properties_update_list(this), + direct_state_query_list(this) { + _set_static(false); +} + +GodotBody3D::~GodotBody3D() { + if (fi_callback_data) { + memdelete(fi_callback_data); + } + if (direct_state) { + memdelete(direct_state); + } +} diff --git a/modules/godot_physics_3d/godot_body_3d.h b/modules/godot_physics_3d/godot_body_3d.h new file mode 100644 index 0000000000..81b668122a --- /dev/null +++ b/modules/godot_physics_3d/godot_body_3d.h @@ -0,0 +1,396 @@ +/**************************************************************************/ +/* godot_body_3d.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 GODOT_BODY_3D_H +#define GODOT_BODY_3D_H + +#include "godot_area_3d.h" +#include "godot_collision_object_3d.h" + +#include "core/templates/vset.h" + +class GodotConstraint3D; +class GodotPhysicsDirectBodyState3D; + +class GodotBody3D : public GodotCollisionObject3D { + PhysicsServer3D::BodyMode mode = PhysicsServer3D::BODY_MODE_RIGID; + + Vector3 linear_velocity; + Vector3 angular_velocity; + + Vector3 prev_linear_velocity; + Vector3 prev_angular_velocity; + + Vector3 constant_linear_velocity; + Vector3 constant_angular_velocity; + + Vector3 biased_linear_velocity; + Vector3 biased_angular_velocity; + real_t mass = 1.0; + real_t bounce = 0.0; + real_t friction = 1.0; + Vector3 inertia; + + PhysicsServer3D::BodyDampMode linear_damp_mode = PhysicsServer3D::BODY_DAMP_MODE_COMBINE; + PhysicsServer3D::BodyDampMode angular_damp_mode = PhysicsServer3D::BODY_DAMP_MODE_COMBINE; + + real_t linear_damp = 0.0; + real_t angular_damp = 0.0; + + real_t total_linear_damp = 0.0; + real_t total_angular_damp = 0.0; + + real_t gravity_scale = 1.0; + + uint16_t locked_axis = 0; + + real_t _inv_mass = 1.0; + Vector3 _inv_inertia; // Relative to the principal axes of inertia + + // Relative to the local frame of reference + Basis principal_inertia_axes_local; + Vector3 center_of_mass_local; + + // In world orientation with local origin + Basis _inv_inertia_tensor; + Basis principal_inertia_axes; + Vector3 center_of_mass; + + bool calculate_inertia = true; + bool calculate_center_of_mass = true; + + Vector3 gravity; + + real_t still_time = 0.0; + + Vector3 applied_force; + Vector3 applied_torque; + + Vector3 constant_force; + Vector3 constant_torque; + + SelfList<GodotBody3D> active_list; + SelfList<GodotBody3D> mass_properties_update_list; + SelfList<GodotBody3D> direct_state_query_list; + + VSet<RID> exceptions; + bool omit_force_integration = false; + bool active = true; + + bool continuous_cd = false; + bool can_sleep = true; + bool first_time_kinematic = false; + + void _mass_properties_changed(); + virtual void _shapes_changed() override; + Transform3D new_transform; + + HashMap<GodotConstraint3D *, int> constraint_map; + + Vector<AreaCMP> areas; + + struct Contact { + Vector3 local_pos; + Vector3 local_normal; + Vector3 local_velocity_at_pos; + real_t depth = 0.0; + int local_shape = 0; + Vector3 collider_pos; + int collider_shape = 0; + ObjectID collider_instance_id; + RID collider; + Vector3 collider_velocity_at_pos; + Vector3 impulse; + }; + + Vector<Contact> contacts; //no contacts by default + int contact_count = 0; + + Callable body_state_callback; + + struct ForceIntegrationCallbackData { + Callable callable; + Variant udata; + }; + + ForceIntegrationCallbackData *fi_callback_data = nullptr; + + GodotPhysicsDirectBodyState3D *direct_state = nullptr; + + uint64_t island_step = 0; + + void _update_transform_dependent(); + + friend class GodotPhysicsDirectBodyState3D; // i give up, too many functions to expose + +public: + void set_state_sync_callback(const Callable &p_callable); + void set_force_integration_callback(const Callable &p_callable, const Variant &p_udata = Variant()); + + GodotPhysicsDirectBodyState3D *get_direct_state(); + + _FORCE_INLINE_ void add_area(GodotArea3D *p_area) { + int index = areas.find(AreaCMP(p_area)); + if (index > -1) { + areas.write[index].refCount += 1; + } else { + areas.ordered_insert(AreaCMP(p_area)); + } + } + + _FORCE_INLINE_ void remove_area(GodotArea3D *p_area) { + int index = areas.find(AreaCMP(p_area)); + if (index > -1) { + areas.write[index].refCount -= 1; + if (areas[index].refCount < 1) { + areas.remove_at(index); + } + } + } + + _FORCE_INLINE_ void set_max_contacts_reported(int p_size) { + contacts.resize(p_size); + contact_count = 0; + if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC && p_size) { + set_active(true); + } + } + _FORCE_INLINE_ int get_max_contacts_reported() const { return contacts.size(); } + + _FORCE_INLINE_ bool can_report_contacts() const { return !contacts.is_empty(); } + _FORCE_INLINE_ void add_contact(const Vector3 &p_local_pos, const Vector3 &p_local_normal, real_t p_depth, int p_local_shape, const Vector3 &p_local_velocity_at_pos, const Vector3 &p_collider_pos, int p_collider_shape, ObjectID p_collider_instance_id, const RID &p_collider, const Vector3 &p_collider_velocity_at_pos, const Vector3 &p_impulse); + + _FORCE_INLINE_ void add_exception(const RID &p_exception) { exceptions.insert(p_exception); } + _FORCE_INLINE_ void remove_exception(const RID &p_exception) { exceptions.erase(p_exception); } + _FORCE_INLINE_ bool has_exception(const RID &p_exception) const { return exceptions.has(p_exception); } + _FORCE_INLINE_ const VSet<RID> &get_exceptions() const { return exceptions; } + + _FORCE_INLINE_ uint64_t get_island_step() const { return island_step; } + _FORCE_INLINE_ void set_island_step(uint64_t p_step) { island_step = p_step; } + + _FORCE_INLINE_ void add_constraint(GodotConstraint3D *p_constraint, int p_pos) { constraint_map[p_constraint] = p_pos; } + _FORCE_INLINE_ void remove_constraint(GodotConstraint3D *p_constraint) { constraint_map.erase(p_constraint); } + const HashMap<GodotConstraint3D *, int> &get_constraint_map() const { return constraint_map; } + _FORCE_INLINE_ void clear_constraint_map() { constraint_map.clear(); } + + _FORCE_INLINE_ void set_omit_force_integration(bool p_omit_force_integration) { omit_force_integration = p_omit_force_integration; } + _FORCE_INLINE_ bool get_omit_force_integration() const { return omit_force_integration; } + + _FORCE_INLINE_ Basis get_principal_inertia_axes() const { return principal_inertia_axes; } + _FORCE_INLINE_ Vector3 get_center_of_mass() const { return center_of_mass; } + _FORCE_INLINE_ Vector3 get_center_of_mass_local() const { return center_of_mass_local; } + _FORCE_INLINE_ Vector3 xform_local_to_principal(const Vector3 &p_pos) const { return principal_inertia_axes_local.xform(p_pos - center_of_mass_local); } + + _FORCE_INLINE_ void set_linear_velocity(const Vector3 &p_velocity) { linear_velocity = p_velocity; } + _FORCE_INLINE_ Vector3 get_linear_velocity() const { return linear_velocity; } + + _FORCE_INLINE_ void set_angular_velocity(const Vector3 &p_velocity) { angular_velocity = p_velocity; } + _FORCE_INLINE_ Vector3 get_angular_velocity() const { return angular_velocity; } + + _FORCE_INLINE_ Vector3 get_prev_linear_velocity() const { return prev_linear_velocity; } + _FORCE_INLINE_ Vector3 get_prev_angular_velocity() const { return prev_angular_velocity; } + + _FORCE_INLINE_ const Vector3 &get_biased_linear_velocity() const { return biased_linear_velocity; } + _FORCE_INLINE_ const Vector3 &get_biased_angular_velocity() const { return biased_angular_velocity; } + + _FORCE_INLINE_ void apply_central_impulse(const Vector3 &p_impulse) { + linear_velocity += p_impulse * _inv_mass; + } + + _FORCE_INLINE_ void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3()) { + linear_velocity += p_impulse * _inv_mass; + angular_velocity += _inv_inertia_tensor.xform((p_position - center_of_mass).cross(p_impulse)); + } + + _FORCE_INLINE_ void apply_torque_impulse(const Vector3 &p_impulse) { + angular_velocity += _inv_inertia_tensor.xform(p_impulse); + } + + _FORCE_INLINE_ void apply_bias_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3(), real_t p_max_delta_av = -1.0) { + biased_linear_velocity += p_impulse * _inv_mass; + if (p_max_delta_av != 0.0) { + Vector3 delta_av = _inv_inertia_tensor.xform((p_position - center_of_mass).cross(p_impulse)); + if (p_max_delta_av > 0 && delta_av.length() > p_max_delta_av) { + delta_av = delta_av.normalized() * p_max_delta_av; + } + biased_angular_velocity += delta_av; + } + } + + _FORCE_INLINE_ void apply_bias_torque_impulse(const Vector3 &p_impulse) { + biased_angular_velocity += _inv_inertia_tensor.xform(p_impulse); + } + + _FORCE_INLINE_ void apply_central_force(const Vector3 &p_force) { + applied_force += p_force; + } + + _FORCE_INLINE_ void apply_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) { + applied_force += p_force; + applied_torque += (p_position - center_of_mass).cross(p_force); + } + + _FORCE_INLINE_ void apply_torque(const Vector3 &p_torque) { + applied_torque += p_torque; + } + + _FORCE_INLINE_ void add_constant_central_force(const Vector3 &p_force) { + constant_force += p_force; + } + + _FORCE_INLINE_ void add_constant_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) { + constant_force += p_force; + constant_torque += (p_position - center_of_mass).cross(p_force); + } + + _FORCE_INLINE_ void add_constant_torque(const Vector3 &p_torque) { + constant_torque += p_torque; + } + + void set_constant_force(const Vector3 &p_force) { constant_force = p_force; } + Vector3 get_constant_force() const { return constant_force; } + + void set_constant_torque(const Vector3 &p_torque) { constant_torque = p_torque; } + Vector3 get_constant_torque() const { return constant_torque; } + + void set_active(bool p_active); + _FORCE_INLINE_ bool is_active() const { return active; } + + _FORCE_INLINE_ void wakeup() { + if ((!get_space()) || mode == PhysicsServer3D::BODY_MODE_STATIC || mode == PhysicsServer3D::BODY_MODE_KINEMATIC) { + return; + } + set_active(true); + } + + void set_param(PhysicsServer3D::BodyParameter p_param, const Variant &p_value); + Variant get_param(PhysicsServer3D::BodyParameter p_param) const; + + void set_mode(PhysicsServer3D::BodyMode p_mode); + PhysicsServer3D::BodyMode get_mode() const; + + void set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant); + Variant get_state(PhysicsServer3D::BodyState p_state) const; + + _FORCE_INLINE_ void set_continuous_collision_detection(bool p_enable) { continuous_cd = p_enable; } + _FORCE_INLINE_ bool is_continuous_collision_detection_enabled() const { return continuous_cd; } + + void set_space(GodotSpace3D *p_space) override; + + void update_mass_properties(); + void reset_mass_properties(); + + _FORCE_INLINE_ real_t get_inv_mass() const { return _inv_mass; } + _FORCE_INLINE_ const Vector3 &get_inv_inertia() const { return _inv_inertia; } + _FORCE_INLINE_ const Basis &get_inv_inertia_tensor() const { return _inv_inertia_tensor; } + _FORCE_INLINE_ real_t get_friction() const { return friction; } + _FORCE_INLINE_ real_t get_bounce() const { return bounce; } + + void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool lock); + bool is_axis_locked(PhysicsServer3D::BodyAxis p_axis) const; + + void integrate_forces(real_t p_step); + void integrate_velocities(real_t p_step); + + _FORCE_INLINE_ Vector3 get_velocity_in_local_point(const Vector3 &rel_pos) const { + return linear_velocity + angular_velocity.cross(rel_pos - center_of_mass); + } + + _FORCE_INLINE_ real_t compute_impulse_denominator(const Vector3 &p_pos, const Vector3 &p_normal) const { + Vector3 r0 = p_pos - get_transform().origin - center_of_mass; + + Vector3 c0 = (r0).cross(p_normal); + + Vector3 vec = (_inv_inertia_tensor.xform_inv(c0)).cross(r0); + + return _inv_mass + p_normal.dot(vec); + } + + _FORCE_INLINE_ real_t compute_angular_impulse_denominator(const Vector3 &p_axis) const { + return p_axis.dot(_inv_inertia_tensor.xform_inv(p_axis)); + } + + //void simulate_motion(const Transform3D& p_xform,real_t p_step); + void call_queries(); + void wakeup_neighbours(); + + bool sleep_test(real_t p_step); + + GodotBody3D(); + ~GodotBody3D(); +}; + +//add contact inline + +void GodotBody3D::add_contact(const Vector3 &p_local_pos, const Vector3 &p_local_normal, real_t p_depth, int p_local_shape, const Vector3 &p_local_velocity_at_pos, const Vector3 &p_collider_pos, int p_collider_shape, ObjectID p_collider_instance_id, const RID &p_collider, const Vector3 &p_collider_velocity_at_pos, const Vector3 &p_impulse) { + int c_max = contacts.size(); + + if (c_max == 0) { + return; + } + + Contact *c = contacts.ptrw(); + + int idx = -1; + + if (contact_count < c_max) { + idx = contact_count++; + } else { + real_t least_depth = 1e20; + int least_deep = -1; + for (int i = 0; i < c_max; i++) { + if (i == 0 || c[i].depth < least_depth) { + least_deep = i; + least_depth = c[i].depth; + } + } + + if (least_deep >= 0 && least_depth < p_depth) { + idx = least_deep; + } + if (idx == -1) { + return; //none least deepe than this + } + } + + c[idx].local_pos = p_local_pos; + c[idx].local_normal = p_local_normal; + c[idx].local_velocity_at_pos = p_local_velocity_at_pos; + c[idx].depth = p_depth; + c[idx].local_shape = p_local_shape; + c[idx].collider_pos = p_collider_pos; + c[idx].collider_shape = p_collider_shape; + c[idx].collider_instance_id = p_collider_instance_id; + c[idx].collider = p_collider; + c[idx].collider_velocity_at_pos = p_collider_velocity_at_pos; + c[idx].impulse = p_impulse; +} + +#endif // GODOT_BODY_3D_H diff --git a/modules/godot_physics_3d/godot_body_direct_state_3d.cpp b/modules/godot_physics_3d/godot_body_direct_state_3d.cpp new file mode 100644 index 0000000000..0af746c68d --- /dev/null +++ b/modules/godot_physics_3d/godot_body_direct_state_3d.cpp @@ -0,0 +1,237 @@ +/**************************************************************************/ +/* godot_body_direct_state_3d.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 "godot_body_direct_state_3d.h" + +#include "godot_body_3d.h" +#include "godot_space_3d.h" + +Vector3 GodotPhysicsDirectBodyState3D::get_total_gravity() const { + return body->gravity; +} + +real_t GodotPhysicsDirectBodyState3D::get_total_angular_damp() const { + return body->total_angular_damp; +} + +real_t GodotPhysicsDirectBodyState3D::get_total_linear_damp() const { + return body->total_linear_damp; +} + +Vector3 GodotPhysicsDirectBodyState3D::get_center_of_mass() const { + return body->get_center_of_mass(); +} + +Vector3 GodotPhysicsDirectBodyState3D::get_center_of_mass_local() const { + return body->get_center_of_mass_local(); +} + +Basis GodotPhysicsDirectBodyState3D::get_principal_inertia_axes() const { + return body->get_principal_inertia_axes(); +} + +real_t GodotPhysicsDirectBodyState3D::get_inverse_mass() const { + return body->get_inv_mass(); +} + +Vector3 GodotPhysicsDirectBodyState3D::get_inverse_inertia() const { + return body->get_inv_inertia(); +} + +Basis GodotPhysicsDirectBodyState3D::get_inverse_inertia_tensor() const { + return body->get_inv_inertia_tensor(); +} + +void GodotPhysicsDirectBodyState3D::set_linear_velocity(const Vector3 &p_velocity) { + body->wakeup(); + body->set_linear_velocity(p_velocity); +} + +Vector3 GodotPhysicsDirectBodyState3D::get_linear_velocity() const { + return body->get_linear_velocity(); +} + +void GodotPhysicsDirectBodyState3D::set_angular_velocity(const Vector3 &p_velocity) { + body->wakeup(); + body->set_angular_velocity(p_velocity); +} + +Vector3 GodotPhysicsDirectBodyState3D::get_angular_velocity() const { + return body->get_angular_velocity(); +} + +void GodotPhysicsDirectBodyState3D::set_transform(const Transform3D &p_transform) { + body->set_state(PhysicsServer3D::BODY_STATE_TRANSFORM, p_transform); +} + +Transform3D GodotPhysicsDirectBodyState3D::get_transform() const { + return body->get_transform(); +} + +Vector3 GodotPhysicsDirectBodyState3D::get_velocity_at_local_position(const Vector3 &p_position) const { + return body->get_velocity_in_local_point(p_position); +} + +void GodotPhysicsDirectBodyState3D::apply_central_impulse(const Vector3 &p_impulse) { + body->wakeup(); + body->apply_central_impulse(p_impulse); +} + +void GodotPhysicsDirectBodyState3D::apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position) { + body->wakeup(); + body->apply_impulse(p_impulse, p_position); +} + +void GodotPhysicsDirectBodyState3D::apply_torque_impulse(const Vector3 &p_impulse) { + body->wakeup(); + body->apply_torque_impulse(p_impulse); +} + +void GodotPhysicsDirectBodyState3D::apply_central_force(const Vector3 &p_force) { + body->wakeup(); + body->apply_central_force(p_force); +} + +void GodotPhysicsDirectBodyState3D::apply_force(const Vector3 &p_force, const Vector3 &p_position) { + body->wakeup(); + body->apply_force(p_force, p_position); +} + +void GodotPhysicsDirectBodyState3D::apply_torque(const Vector3 &p_torque) { + body->wakeup(); + body->apply_torque(p_torque); +} + +void GodotPhysicsDirectBodyState3D::add_constant_central_force(const Vector3 &p_force) { + body->wakeup(); + body->add_constant_central_force(p_force); +} + +void GodotPhysicsDirectBodyState3D::add_constant_force(const Vector3 &p_force, const Vector3 &p_position) { + body->wakeup(); + body->add_constant_force(p_force, p_position); +} + +void GodotPhysicsDirectBodyState3D::add_constant_torque(const Vector3 &p_torque) { + body->wakeup(); + body->add_constant_torque(p_torque); +} + +void GodotPhysicsDirectBodyState3D::set_constant_force(const Vector3 &p_force) { + if (!p_force.is_zero_approx()) { + body->wakeup(); + } + body->set_constant_force(p_force); +} + +Vector3 GodotPhysicsDirectBodyState3D::get_constant_force() const { + return body->get_constant_force(); +} + +void GodotPhysicsDirectBodyState3D::set_constant_torque(const Vector3 &p_torque) { + if (!p_torque.is_zero_approx()) { + body->wakeup(); + } + body->set_constant_torque(p_torque); +} + +Vector3 GodotPhysicsDirectBodyState3D::get_constant_torque() const { + return body->get_constant_torque(); +} + +void GodotPhysicsDirectBodyState3D::set_sleep_state(bool p_sleep) { + body->set_active(!p_sleep); +} + +bool GodotPhysicsDirectBodyState3D::is_sleeping() const { + return !body->is_active(); +} + +int GodotPhysicsDirectBodyState3D::get_contact_count() const { + return body->contact_count; +} + +Vector3 GodotPhysicsDirectBodyState3D::get_contact_local_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].local_pos; +} + +Vector3 GodotPhysicsDirectBodyState3D::get_contact_local_normal(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].local_normal; +} + +Vector3 GodotPhysicsDirectBodyState3D::get_contact_impulse(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].impulse; +} + +Vector3 GodotPhysicsDirectBodyState3D::get_contact_local_velocity_at_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].local_velocity_at_pos; +} + +int GodotPhysicsDirectBodyState3D::get_contact_local_shape(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1); + return body->contacts[p_contact_idx].local_shape; +} + +RID GodotPhysicsDirectBodyState3D::get_contact_collider(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID()); + return body->contacts[p_contact_idx].collider; +} + +Vector3 GodotPhysicsDirectBodyState3D::get_contact_collider_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].collider_pos; +} + +ObjectID GodotPhysicsDirectBodyState3D::get_contact_collider_id(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID()); + return body->contacts[p_contact_idx].collider_instance_id; +} + +int GodotPhysicsDirectBodyState3D::get_contact_collider_shape(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0); + return body->contacts[p_contact_idx].collider_shape; +} + +Vector3 GodotPhysicsDirectBodyState3D::get_contact_collider_velocity_at_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].collider_velocity_at_pos; +} + +PhysicsDirectSpaceState3D *GodotPhysicsDirectBodyState3D::get_space_state() { + return body->get_space()->get_direct_state(); +} + +real_t GodotPhysicsDirectBodyState3D::get_step() const { + return body->get_space()->get_last_step(); +} diff --git a/modules/godot_physics_3d/godot_body_direct_state_3d.h b/modules/godot_physics_3d/godot_body_direct_state_3d.h new file mode 100644 index 0000000000..8066050c9f --- /dev/null +++ b/modules/godot_physics_3d/godot_body_direct_state_3d.h @@ -0,0 +1,107 @@ +/**************************************************************************/ +/* godot_body_direct_state_3d.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 GODOT_BODY_DIRECT_STATE_3D_H +#define GODOT_BODY_DIRECT_STATE_3D_H + +#include "servers/physics_server_3d.h" + +class GodotBody3D; + +class GodotPhysicsDirectBodyState3D : public PhysicsDirectBodyState3D { + GDCLASS(GodotPhysicsDirectBodyState3D, PhysicsDirectBodyState3D); + +public: + GodotBody3D *body = nullptr; + + virtual Vector3 get_total_gravity() const override; + virtual real_t get_total_angular_damp() const override; + virtual real_t get_total_linear_damp() const override; + + virtual Vector3 get_center_of_mass() const override; + virtual Vector3 get_center_of_mass_local() const override; + virtual Basis get_principal_inertia_axes() const override; + + virtual real_t get_inverse_mass() const override; + virtual Vector3 get_inverse_inertia() const override; + virtual Basis get_inverse_inertia_tensor() const override; + + virtual void set_linear_velocity(const Vector3 &p_velocity) override; + virtual Vector3 get_linear_velocity() const override; + + virtual void set_angular_velocity(const Vector3 &p_velocity) override; + virtual Vector3 get_angular_velocity() const override; + + virtual void set_transform(const Transform3D &p_transform) override; + virtual Transform3D get_transform() const override; + + virtual Vector3 get_velocity_at_local_position(const Vector3 &p_position) const override; + + virtual void apply_central_impulse(const Vector3 &p_impulse) override; + virtual void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3()) override; + virtual void apply_torque_impulse(const Vector3 &p_impulse) override; + + virtual void apply_central_force(const Vector3 &p_force) override; + virtual void apply_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) override; + virtual void apply_torque(const Vector3 &p_torque) override; + + virtual void add_constant_central_force(const Vector3 &p_force) override; + virtual void add_constant_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) override; + virtual void add_constant_torque(const Vector3 &p_torque) override; + + virtual void set_constant_force(const Vector3 &p_force) override; + virtual Vector3 get_constant_force() const override; + + virtual void set_constant_torque(const Vector3 &p_torque) override; + virtual Vector3 get_constant_torque() const override; + + virtual void set_sleep_state(bool p_sleep) override; + virtual bool is_sleeping() const override; + + virtual int get_contact_count() const override; + + virtual Vector3 get_contact_local_position(int p_contact_idx) const override; + virtual Vector3 get_contact_local_normal(int p_contact_idx) const override; + virtual Vector3 get_contact_impulse(int p_contact_idx) const override; + virtual int get_contact_local_shape(int p_contact_idx) const override; + virtual Vector3 get_contact_local_velocity_at_position(int p_contact_idx) const override; + + virtual RID get_contact_collider(int p_contact_idx) const override; + virtual Vector3 get_contact_collider_position(int p_contact_idx) const override; + virtual ObjectID get_contact_collider_id(int p_contact_idx) const override; + virtual int get_contact_collider_shape(int p_contact_idx) const override; + virtual Vector3 get_contact_collider_velocity_at_position(int p_contact_idx) const override; + + virtual PhysicsDirectSpaceState3D *get_space_state() override; + + virtual real_t get_step() const override; +}; + +#endif // GODOT_BODY_DIRECT_STATE_3D_H diff --git a/modules/godot_physics_3d/godot_body_pair_3d.cpp b/modules/godot_physics_3d/godot_body_pair_3d.cpp new file mode 100644 index 0000000000..84fae73616 --- /dev/null +++ b/modules/godot_physics_3d/godot_body_pair_3d.cpp @@ -0,0 +1,988 @@ +/**************************************************************************/ +/* godot_body_pair_3d.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 "godot_body_pair_3d.h" + +#include "godot_collision_solver_3d.h" +#include "godot_space_3d.h" + +#include "core/os/os.h" + +#define MIN_VELOCITY 0.0001 +#define MAX_BIAS_ROTATION (Math_PI / 8) + +void GodotBodyPair3D::_contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata) { + GodotBodyPair3D *pair = static_cast<GodotBodyPair3D *>(p_userdata); + pair->contact_added_callback(p_point_A, p_index_A, p_point_B, p_index_B, normal); +} + +void GodotBodyPair3D::contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal) { + Vector3 local_A = A->get_inv_transform().basis.xform(p_point_A); + Vector3 local_B = B->get_inv_transform().basis.xform(p_point_B - offset_B); + + int new_index = contact_count; + + ERR_FAIL_COND(new_index >= (MAX_CONTACTS + 1)); + + Contact contact; + contact.index_A = p_index_A; + contact.index_B = p_index_B; + contact.local_A = local_A; + contact.local_B = local_B; + contact.normal = (p_point_A - p_point_B).normalized(); + contact.used = true; + + // Attempt to determine if the contact will be reused. + real_t contact_recycle_radius = space->get_contact_recycle_radius(); + + for (int i = 0; i < contact_count; i++) { + Contact &c = contacts[i]; + if (c.local_A.distance_squared_to(local_A) < (contact_recycle_radius * contact_recycle_radius) && + c.local_B.distance_squared_to(local_B) < (contact_recycle_radius * contact_recycle_radius)) { + contact.acc_normal_impulse = c.acc_normal_impulse; + contact.acc_bias_impulse = c.acc_bias_impulse; + contact.acc_bias_impulse_center_of_mass = c.acc_bias_impulse_center_of_mass; + contact.acc_tangent_impulse = c.acc_tangent_impulse; + c = contact; + return; + } + } + + // Figure out if the contact amount must be reduced to fit the new contact. + if (new_index == MAX_CONTACTS) { + // Remove the contact with the minimum depth. + + const Basis &basis_A = A->get_transform().basis; + const Basis &basis_B = B->get_transform().basis; + + int least_deep = -1; + real_t min_depth; + + // Start with depth for new contact. + { + Vector3 global_A = basis_A.xform(contact.local_A); + Vector3 global_B = basis_B.xform(contact.local_B) + offset_B; + + Vector3 axis = global_A - global_B; + min_depth = axis.dot(contact.normal); + } + + for (int i = 0; i < contact_count; i++) { + const Contact &c = contacts[i]; + Vector3 global_A = basis_A.xform(c.local_A); + Vector3 global_B = basis_B.xform(c.local_B) + offset_B; + + Vector3 axis = global_A - global_B; + real_t depth = axis.dot(c.normal); + + if (depth < min_depth) { + min_depth = depth; + least_deep = i; + } + } + + if (least_deep > -1) { + // Replace the least deep contact by the new one. + contacts[least_deep] = contact; + } + + return; + } + + contacts[new_index] = contact; + contact_count++; +} + +void GodotBodyPair3D::validate_contacts() { + // Make sure to erase contacts that are no longer valid. + real_t max_separation = space->get_contact_max_separation(); + real_t max_separation2 = max_separation * max_separation; + + const Basis &basis_A = A->get_transform().basis; + const Basis &basis_B = B->get_transform().basis; + + for (int i = 0; i < contact_count; i++) { + Contact &c = contacts[i]; + + bool erase = false; + if (!c.used) { + // Was left behind in previous frame. + erase = true; + } else { + c.used = false; + + Vector3 global_A = basis_A.xform(c.local_A); + Vector3 global_B = basis_B.xform(c.local_B) + offset_B; + Vector3 axis = global_A - global_B; + real_t depth = axis.dot(c.normal); + + if (depth < -max_separation || (global_B + c.normal * depth - global_A).length_squared() > max_separation2) { + erase = true; + } + } + + if (erase) { + // Contact no longer needed, remove. + if ((i + 1) < contact_count) { + // Swap with the last one. + SWAP(contacts[i], contacts[contact_count - 1]); + } + + i--; + contact_count--; + } + } +} + +// _test_ccd prevents tunneling by slowing down a high velocity body that is about to collide so that next frame it will be at an appropriate location to collide (i.e. slight overlap) +// Warning: the way velocity is adjusted down to cause a collision means the momentum will be weaker than it should for a bounce! +// Process: only proceed if body A's motion is high relative to its size. +// cast forward along motion vector to see if A is going to enter/pass B's collider next frame, only proceed if it does. +// adjust the velocity of A down so that it will just slightly intersect the collider instead of blowing right past it. +bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, const Transform3D &p_xform_A, GodotBody3D *p_B, int p_shape_B, const Transform3D &p_xform_B) { + GodotShape3D *shape_A_ptr = p_A->get_shape(p_shape_A); + + Vector3 motion = p_A->get_linear_velocity() * p_step; + real_t mlen = motion.length(); + if (mlen < CMP_EPSILON) { + return false; + } + + Vector3 mnormal = motion / mlen; + + real_t min = 0.0, max = 0.0; + shape_A_ptr->project_range(mnormal, p_xform_A, min, max); + + // Did it move enough in this direction to even attempt raycast? + // Let's say it should move more than 1/3 the size of the object in that axis. + bool fast_object = mlen > (max - min) * 0.3; + if (!fast_object) { + return false; // moving slow enough that there's no chance of tunneling. + } + + // A is moving fast enough that tunneling might occur. See if it's really about to collide. + + // Roughly predict body B's position in the next frame (ignoring collisions). + Transform3D predicted_xform_B = p_xform_B.translated(p_B->get_linear_velocity() * p_step); + + // Support points are the farthest forward points on A in the direction of the motion vector. + // i.e. the candidate points of which one should hit B first if any collision does occur. + static const int max_supports = 16; + Vector3 supports_A[max_supports]; + int support_count_A; + GodotShape3D::FeatureType support_type_A; + // Convert mnormal into body A's local xform because get_supports requires (and returns) local coordinates. + shape_A_ptr->get_supports(p_xform_A.basis.xform_inv(mnormal).normalized(), max_supports, supports_A, support_count_A, support_type_A); + + // Cast a segment from each support point of A in the motion direction. + int segment_support_idx = -1; + float segment_hit_length = FLT_MAX; + Vector3 segment_hit_local; + for (int i = 0; i < support_count_A; i++) { + supports_A[i] = p_xform_A.xform(supports_A[i]); + + Vector3 from = supports_A[i]; + Vector3 to = from + motion; + + Transform3D from_inv = predicted_xform_B.affine_inverse(); + + // Back up 10% of the per-frame motion behind the support point and use that as the beginning of our cast. + // At high speeds, this may mean we're actually casting from well behind the body instead of inside it, which is odd. + // But it still works out. + Vector3 local_from = from_inv.xform(from - motion * 0.1); + Vector3 local_to = from_inv.xform(to); + + Vector3 rpos, rnorm; + int fi = -1; + if (p_B->get_shape(p_shape_B)->intersect_segment(local_from, local_to, rpos, rnorm, fi, true)) { + float hit_length = local_from.distance_to(rpos); + if (hit_length < segment_hit_length) { + segment_support_idx = i; + segment_hit_length = hit_length; + segment_hit_local = rpos; + } + } + } + + if (segment_support_idx == -1) { + // There was no hit. Since the segment is the length of per-frame motion, this means the bodies will not + // actually collide yet on next frame. We'll probably check again next frame once they're closer. + return false; + } + + Vector3 hitpos = predicted_xform_B.xform(segment_hit_local); + + real_t newlen = hitpos.distance_to(supports_A[segment_support_idx]); + // Adding 1% of body length to the distance between collision and support point + // should cause body A's support point to arrive just within B's collider next frame. + newlen += (max - min) * 0.01; + // FIXME: This doesn't always work well when colliding with a triangle face of a trimesh shape. + + p_A->set_linear_velocity((mnormal * newlen) / p_step); + + return true; +} + +real_t combine_bounce(GodotBody3D *A, GodotBody3D *B) { + return CLAMP(A->get_bounce() + B->get_bounce(), 0, 1); +} + +real_t combine_friction(GodotBody3D *A, GodotBody3D *B) { + return ABS(MIN(A->get_friction(), B->get_friction())); +} + +bool GodotBodyPair3D::setup(real_t p_step) { + check_ccd = false; + + if (!A->interacts_with(B) || A->has_exception(B->get_self()) || B->has_exception(A->get_self())) { + collided = false; + return false; + } + + collide_A = (A->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC) && A->collides_with(B); + collide_B = (B->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC) && B->collides_with(A); + + report_contacts_only = false; + if (!collide_A && !collide_B) { + if ((A->get_max_contacts_reported() > 0) || (B->get_max_contacts_reported() > 0)) { + report_contacts_only = true; + } else { + collided = false; + return false; + } + } + + offset_B = B->get_transform().get_origin() - A->get_transform().get_origin(); + + validate_contacts(); + + const Vector3 &offset_A = A->get_transform().get_origin(); + Transform3D xform_Au = Transform3D(A->get_transform().basis, Vector3()); + Transform3D xform_A = xform_Au * A->get_shape_transform(shape_A); + + Transform3D xform_Bu = B->get_transform(); + xform_Bu.origin -= offset_A; + Transform3D xform_B = xform_Bu * B->get_shape_transform(shape_B); + + GodotShape3D *shape_A_ptr = A->get_shape(shape_A); + GodotShape3D *shape_B_ptr = B->get_shape(shape_B); + + collided = GodotCollisionSolver3D::solve_static(shape_A_ptr, xform_A, shape_B_ptr, xform_B, _contact_added_callback, this, &sep_axis); + + if (!collided) { + if (A->is_continuous_collision_detection_enabled() && collide_A) { + check_ccd = true; + return true; + } + + if (B->is_continuous_collision_detection_enabled() && collide_B) { + check_ccd = true; + return true; + } + + return false; + } + + return true; +} + +bool GodotBodyPair3D::pre_solve(real_t p_step) { + if (!collided) { + if (check_ccd) { + const Vector3 &offset_A = A->get_transform().get_origin(); + Transform3D xform_Au = Transform3D(A->get_transform().basis, Vector3()); + Transform3D xform_A = xform_Au * A->get_shape_transform(shape_A); + + Transform3D xform_Bu = B->get_transform(); + xform_Bu.origin -= offset_A; + Transform3D xform_B = xform_Bu * B->get_shape_transform(shape_B); + + if (A->is_continuous_collision_detection_enabled() && collide_A) { + _test_ccd(p_step, A, shape_A, xform_A, B, shape_B, xform_B); + } + + if (B->is_continuous_collision_detection_enabled() && collide_B) { + _test_ccd(p_step, B, shape_B, xform_B, A, shape_A, xform_A); + } + } + + return false; + } + + real_t max_penetration = space->get_contact_max_allowed_penetration(); + + real_t bias = 0.8; + + GodotShape3D *shape_A_ptr = A->get_shape(shape_A); + GodotShape3D *shape_B_ptr = B->get_shape(shape_B); + + if (shape_A_ptr->get_custom_bias() || shape_B_ptr->get_custom_bias()) { + if (shape_A_ptr->get_custom_bias() == 0) { + bias = shape_B_ptr->get_custom_bias(); + } else if (shape_B_ptr->get_custom_bias() == 0) { + bias = shape_A_ptr->get_custom_bias(); + } else { + bias = (shape_B_ptr->get_custom_bias() + shape_A_ptr->get_custom_bias()) * 0.5; + } + } + + real_t inv_dt = 1.0 / p_step; + + bool do_process = false; + + const Vector3 &offset_A = A->get_transform().get_origin(); + + const Basis &basis_A = A->get_transform().basis; + const Basis &basis_B = B->get_transform().basis; + + Basis zero_basis; + zero_basis.set_zero(); + + const Basis &inv_inertia_tensor_A = collide_A ? A->get_inv_inertia_tensor() : zero_basis; + const Basis &inv_inertia_tensor_B = collide_B ? B->get_inv_inertia_tensor() : zero_basis; + + real_t inv_mass_A = collide_A ? A->get_inv_mass() : 0.0; + real_t inv_mass_B = collide_B ? B->get_inv_mass() : 0.0; + + for (int i = 0; i < contact_count; i++) { + Contact &c = contacts[i]; + c.active = false; + + Vector3 global_A = basis_A.xform(c.local_A); + Vector3 global_B = basis_B.xform(c.local_B) + offset_B; + + Vector3 axis = global_A - global_B; + real_t depth = axis.dot(c.normal); + + if (depth <= 0.0) { + continue; + } + +#ifdef DEBUG_ENABLED + if (space->is_debugging_contacts()) { + space->add_debug_contact(global_A + offset_A); + space->add_debug_contact(global_B + offset_A); + } +#endif + + c.rA = global_A - A->get_center_of_mass(); + c.rB = global_B - B->get_center_of_mass() - offset_B; + + // Precompute normal mass, tangent mass, and bias. + Vector3 inertia_A = inv_inertia_tensor_A.xform(c.rA.cross(c.normal)); + Vector3 inertia_B = inv_inertia_tensor_B.xform(c.rB.cross(c.normal)); + real_t kNormal = inv_mass_A + inv_mass_B; + kNormal += c.normal.dot(inertia_A.cross(c.rA)) + c.normal.dot(inertia_B.cross(c.rB)); + c.mass_normal = 1.0f / kNormal; + + c.bias = -bias * inv_dt * MIN(0.0f, -depth + max_penetration); + c.depth = depth; + + Vector3 j_vec = c.normal * c.acc_normal_impulse + c.acc_tangent_impulse; + + c.acc_impulse -= j_vec; + + // contact query reporting... + + if (A->can_report_contacts() || B->can_report_contacts()) { + Vector3 crB = B->get_angular_velocity().cross(c.rB) + B->get_linear_velocity(); + Vector3 crA = A->get_angular_velocity().cross(c.rA) + A->get_linear_velocity(); + + if (A->can_report_contacts()) { + A->add_contact(global_A + offset_A, -c.normal, depth, shape_A, crA, global_B + offset_A, shape_B, B->get_instance_id(), B->get_self(), crB, c.acc_impulse); + } + + if (B->can_report_contacts()) { + B->add_contact(global_B + offset_A, c.normal, depth, shape_B, crB, global_A + offset_A, shape_A, A->get_instance_id(), A->get_self(), crA, -c.acc_impulse); + } + } + + if (report_contacts_only) { + collided = false; + continue; + } + + c.active = true; + do_process = true; + + if (collide_A) { + A->apply_impulse(-j_vec, c.rA + A->get_center_of_mass()); + } + if (collide_B) { + B->apply_impulse(j_vec, c.rB + B->get_center_of_mass()); + } + + c.bounce = combine_bounce(A, B); + if (c.bounce) { + Vector3 crA = A->get_prev_angular_velocity().cross(c.rA); + Vector3 crB = B->get_prev_angular_velocity().cross(c.rB); + Vector3 dv = B->get_prev_linear_velocity() + crB - A->get_prev_linear_velocity() - crA; + c.bounce = c.bounce * dv.dot(c.normal); + } + } + + return do_process; +} + +void GodotBodyPair3D::solve(real_t p_step) { + if (!collided) { + return; + } + + const real_t max_bias_av = MAX_BIAS_ROTATION / p_step; + + Basis zero_basis; + zero_basis.set_zero(); + + const Basis &inv_inertia_tensor_A = collide_A ? A->get_inv_inertia_tensor() : zero_basis; + const Basis &inv_inertia_tensor_B = collide_B ? B->get_inv_inertia_tensor() : zero_basis; + + real_t inv_mass_A = collide_A ? A->get_inv_mass() : 0.0; + real_t inv_mass_B = collide_B ? B->get_inv_mass() : 0.0; + + for (int i = 0; i < contact_count; i++) { + Contact &c = contacts[i]; + if (!c.active) { + continue; + } + + c.active = false; //try to deactivate, will activate itself if still needed + + //bias impulse + + Vector3 crbA = A->get_biased_angular_velocity().cross(c.rA); + Vector3 crbB = B->get_biased_angular_velocity().cross(c.rB); + Vector3 dbv = B->get_biased_linear_velocity() + crbB - A->get_biased_linear_velocity() - crbA; + + real_t vbn = dbv.dot(c.normal); + + if (Math::abs(-vbn + c.bias) > MIN_VELOCITY) { + real_t jbn = (-vbn + c.bias) * c.mass_normal; + real_t jbnOld = c.acc_bias_impulse; + c.acc_bias_impulse = MAX(jbnOld + jbn, 0.0f); + + Vector3 jb = c.normal * (c.acc_bias_impulse - jbnOld); + + if (collide_A) { + A->apply_bias_impulse(-jb, c.rA + A->get_center_of_mass(), max_bias_av); + } + if (collide_B) { + B->apply_bias_impulse(jb, c.rB + B->get_center_of_mass(), max_bias_av); + } + + crbA = A->get_biased_angular_velocity().cross(c.rA); + crbB = B->get_biased_angular_velocity().cross(c.rB); + dbv = B->get_biased_linear_velocity() + crbB - A->get_biased_linear_velocity() - crbA; + + vbn = dbv.dot(c.normal); + + if (Math::abs(-vbn + c.bias) > MIN_VELOCITY) { + real_t jbn_com = (-vbn + c.bias) / (inv_mass_A + inv_mass_B); + real_t jbnOld_com = c.acc_bias_impulse_center_of_mass; + c.acc_bias_impulse_center_of_mass = MAX(jbnOld_com + jbn_com, 0.0f); + + Vector3 jb_com = c.normal * (c.acc_bias_impulse_center_of_mass - jbnOld_com); + + if (collide_A) { + A->apply_bias_impulse(-jb_com, A->get_center_of_mass(), 0.0f); + } + if (collide_B) { + B->apply_bias_impulse(jb_com, B->get_center_of_mass(), 0.0f); + } + } + + c.active = true; + } + + Vector3 crA = A->get_angular_velocity().cross(c.rA); + Vector3 crB = B->get_angular_velocity().cross(c.rB); + Vector3 dv = B->get_linear_velocity() + crB - A->get_linear_velocity() - crA; + + //normal impulse + real_t vn = dv.dot(c.normal); + + if (Math::abs(vn) > MIN_VELOCITY) { + real_t jn = -(c.bounce + vn) * c.mass_normal; + real_t jnOld = c.acc_normal_impulse; + c.acc_normal_impulse = MAX(jnOld + jn, 0.0f); + + Vector3 j = c.normal * (c.acc_normal_impulse - jnOld); + + if (collide_A) { + A->apply_impulse(-j, c.rA + A->get_center_of_mass()); + } + if (collide_B) { + B->apply_impulse(j, c.rB + B->get_center_of_mass()); + } + c.acc_impulse -= j; + + c.active = true; + } + + //friction impulse + + real_t friction = combine_friction(A, B); + + Vector3 lvA = A->get_linear_velocity() + A->get_angular_velocity().cross(c.rA); + Vector3 lvB = B->get_linear_velocity() + B->get_angular_velocity().cross(c.rB); + + Vector3 dtv = lvB - lvA; + real_t tn = c.normal.dot(dtv); + + // tangential velocity + Vector3 tv = dtv - c.normal * tn; + real_t tvl = tv.length(); + + if (tvl > MIN_VELOCITY) { + tv /= tvl; + + Vector3 temp1 = inv_inertia_tensor_A.xform(c.rA.cross(tv)); + Vector3 temp2 = inv_inertia_tensor_B.xform(c.rB.cross(tv)); + + real_t t = -tvl / (inv_mass_A + inv_mass_B + tv.dot(temp1.cross(c.rA) + temp2.cross(c.rB))); + + Vector3 jt = t * tv; + + Vector3 jtOld = c.acc_tangent_impulse; + c.acc_tangent_impulse += jt; + + real_t fi_len = c.acc_tangent_impulse.length(); + real_t jtMax = c.acc_normal_impulse * friction; + + if (fi_len > CMP_EPSILON && fi_len > jtMax) { + c.acc_tangent_impulse *= jtMax / fi_len; + } + + jt = c.acc_tangent_impulse - jtOld; + + if (collide_A) { + A->apply_impulse(-jt, c.rA + A->get_center_of_mass()); + } + if (collide_B) { + B->apply_impulse(jt, c.rB + B->get_center_of_mass()); + } + c.acc_impulse -= jt; + + c.active = true; + } + } +} + +GodotBodyPair3D::GodotBodyPair3D(GodotBody3D *p_A, int p_shape_A, GodotBody3D *p_B, int p_shape_B) : + GodotBodyContact3D(_arr, 2) { + A = p_A; + B = p_B; + shape_A = p_shape_A; + shape_B = p_shape_B; + space = A->get_space(); + A->add_constraint(this, 0); + B->add_constraint(this, 1); +} + +GodotBodyPair3D::~GodotBodyPair3D() { + A->remove_constraint(this); + B->remove_constraint(this); +} + +void GodotBodySoftBodyPair3D::_contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata) { + GodotBodySoftBodyPair3D *pair = static_cast<GodotBodySoftBodyPair3D *>(p_userdata); + pair->contact_added_callback(p_point_A, p_index_A, p_point_B, p_index_B, normal); +} + +void GodotBodySoftBodyPair3D::contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal) { + Vector3 local_A = body->get_inv_transform().xform(p_point_A); + Vector3 local_B = p_point_B - soft_body->get_node_position(p_index_B); + + Contact contact; + contact.index_A = p_index_A; + contact.index_B = p_index_B; + contact.local_A = local_A; + contact.local_B = local_B; + contact.normal = (normal.dot((p_point_A - p_point_B)) < 0 ? -normal : normal); + contact.used = true; + + // Attempt to determine if the contact will be reused. + real_t contact_recycle_radius = space->get_contact_recycle_radius(); + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + if (c.index_B == p_index_B) { + if (c.local_A.distance_squared_to(local_A) < (contact_recycle_radius * contact_recycle_radius) && + c.local_B.distance_squared_to(local_B) < (contact_recycle_radius * contact_recycle_radius)) { + contact.acc_normal_impulse = c.acc_normal_impulse; + contact.acc_bias_impulse = c.acc_bias_impulse; + contact.acc_bias_impulse_center_of_mass = c.acc_bias_impulse_center_of_mass; + contact.acc_tangent_impulse = c.acc_tangent_impulse; + } + c = contact; + return; + } + } + + contacts.push_back(contact); +} + +void GodotBodySoftBodyPair3D::validate_contacts() { + // Make sure to erase contacts that are no longer valid. + real_t max_separation = space->get_contact_max_separation(); + real_t max_separation2 = max_separation * max_separation; + + const Transform3D &transform_A = body->get_transform(); + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + + bool erase = false; + if (!c.used) { + // Was left behind in previous frame. + erase = true; + } else { + c.used = false; + + Vector3 global_A = transform_A.xform(c.local_A); + Vector3 global_B = soft_body->get_node_position(c.index_B) + c.local_B; + Vector3 axis = global_A - global_B; + real_t depth = axis.dot(c.normal); + + if (depth < -max_separation || (global_B + c.normal * depth - global_A).length_squared() > max_separation2) { + erase = true; + } + } + + if (erase) { + // Contact no longer needed, remove. + if ((contact_index + 1) < contact_count) { + // Swap with the last one. + SWAP(c, contacts[contact_count - 1]); + } + + contact_index--; + contact_count--; + } + } + + contacts.resize(contact_count); +} + +bool GodotBodySoftBodyPair3D::setup(real_t p_step) { + if (!body->interacts_with(soft_body) || body->has_exception(soft_body->get_self()) || soft_body->has_exception(body->get_self())) { + collided = false; + return false; + } + + body_collides = (body->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC) && body->collides_with(soft_body); + soft_body_collides = soft_body->collides_with(body); + + if (!body_collides && !soft_body_collides) { + if (body->get_max_contacts_reported() > 0) { + report_contacts_only = true; + } else { + collided = false; + return false; + } + } + + const Transform3D &xform_Au = body->get_transform(); + Transform3D xform_A = xform_Au * body->get_shape_transform(body_shape); + + Transform3D xform_Bu = soft_body->get_transform(); + Transform3D xform_B = xform_Bu * soft_body->get_shape_transform(0); + + validate_contacts(); + + GodotShape3D *shape_A_ptr = body->get_shape(body_shape); + GodotShape3D *shape_B_ptr = soft_body->get_shape(0); + + collided = GodotCollisionSolver3D::solve_static(shape_A_ptr, xform_A, shape_B_ptr, xform_B, _contact_added_callback, this, &sep_axis); + + return collided; +} + +bool GodotBodySoftBodyPair3D::pre_solve(real_t p_step) { + if (!collided) { + return false; + } + + real_t max_penetration = space->get_contact_max_allowed_penetration(); + + real_t bias = space->get_contact_bias(); + + GodotShape3D *shape_A_ptr = body->get_shape(body_shape); + + if (shape_A_ptr->get_custom_bias()) { + bias = shape_A_ptr->get_custom_bias(); + } + + real_t inv_dt = 1.0 / p_step; + + bool do_process = false; + + const Transform3D &transform_A = body->get_transform(); + + Basis zero_basis; + zero_basis.set_zero(); + + const Basis &body_inv_inertia_tensor = body_collides ? body->get_inv_inertia_tensor() : zero_basis; + + real_t body_inv_mass = body_collides ? body->get_inv_mass() : 0.0; + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + c.active = false; + + real_t node_inv_mass = soft_body_collides ? soft_body->get_node_inv_mass(c.index_B) : 0.0; + if ((node_inv_mass == 0.0) && (body_inv_mass == 0.0)) { + continue; + } + + Vector3 global_A = transform_A.xform(c.local_A); + Vector3 global_B = soft_body->get_node_position(c.index_B) + c.local_B; + Vector3 axis = global_A - global_B; + real_t depth = axis.dot(c.normal); + + if (depth <= 0.0) { + continue; + } + +#ifdef DEBUG_ENABLED + if (space->is_debugging_contacts()) { + space->add_debug_contact(global_A); + space->add_debug_contact(global_B); + } +#endif + + c.rA = global_A - transform_A.origin - body->get_center_of_mass(); + c.rB = global_B; + + // Precompute normal mass, tangent mass, and bias. + Vector3 inertia_A = body_inv_inertia_tensor.xform(c.rA.cross(c.normal)); + real_t kNormal = body_inv_mass + node_inv_mass; + kNormal += c.normal.dot(inertia_A.cross(c.rA)); + c.mass_normal = 1.0f / kNormal; + + c.bias = -bias * inv_dt * MIN(0.0f, -depth + max_penetration); + c.depth = depth; + + Vector3 j_vec = c.normal * c.acc_normal_impulse + c.acc_tangent_impulse; + if (body_collides) { + body->apply_impulse(-j_vec, c.rA + body->get_center_of_mass()); + } + if (soft_body_collides) { + soft_body->apply_node_impulse(c.index_B, j_vec); + } + c.acc_impulse -= j_vec; + + if (body->can_report_contacts()) { + Vector3 crA = body->get_angular_velocity().cross(c.rA) + body->get_linear_velocity(); + Vector3 crB = soft_body->get_node_velocity(c.index_B); + body->add_contact(global_A, -c.normal, depth, body_shape, crA, global_B, 0, soft_body->get_instance_id(), soft_body->get_self(), crB, c.acc_impulse); + } + if (report_contacts_only) { + collided = false; + continue; + } + + c.active = true; + do_process = true; + + if (body_collides) { + body->set_active(true); + } + + c.bounce = body->get_bounce(); + + if (c.bounce) { + Vector3 crA = body->get_angular_velocity().cross(c.rA); + Vector3 dv = soft_body->get_node_velocity(c.index_B) - body->get_linear_velocity() - crA; + + // Normal impulse. + c.bounce = c.bounce * dv.dot(c.normal); + } + } + + return do_process; +} + +void GodotBodySoftBodyPair3D::solve(real_t p_step) { + if (!collided) { + return; + } + + const real_t max_bias_av = MAX_BIAS_ROTATION / p_step; + + Basis zero_basis; + zero_basis.set_zero(); + + const Basis &body_inv_inertia_tensor = body_collides ? body->get_inv_inertia_tensor() : zero_basis; + + real_t body_inv_mass = body_collides ? body->get_inv_mass() : 0.0; + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + if (!c.active) { + continue; + } + + c.active = false; + + real_t node_inv_mass = soft_body_collides ? soft_body->get_node_inv_mass(c.index_B) : 0.0; + + // Bias impulse. + Vector3 crbA = body->get_biased_angular_velocity().cross(c.rA); + Vector3 dbv = soft_body->get_node_biased_velocity(c.index_B) - body->get_biased_linear_velocity() - crbA; + + real_t vbn = dbv.dot(c.normal); + + if (Math::abs(-vbn + c.bias) > MIN_VELOCITY) { + real_t jbn = (-vbn + c.bias) * c.mass_normal; + real_t jbnOld = c.acc_bias_impulse; + c.acc_bias_impulse = MAX(jbnOld + jbn, 0.0f); + + Vector3 jb = c.normal * (c.acc_bias_impulse - jbnOld); + + if (body_collides) { + body->apply_bias_impulse(-jb, c.rA + body->get_center_of_mass(), max_bias_av); + } + if (soft_body_collides) { + soft_body->apply_node_bias_impulse(c.index_B, jb); + } + + crbA = body->get_biased_angular_velocity().cross(c.rA); + dbv = soft_body->get_node_biased_velocity(c.index_B) - body->get_biased_linear_velocity() - crbA; + + vbn = dbv.dot(c.normal); + + if (Math::abs(-vbn + c.bias) > MIN_VELOCITY) { + real_t jbn_com = (-vbn + c.bias) / (body_inv_mass + node_inv_mass); + real_t jbnOld_com = c.acc_bias_impulse_center_of_mass; + c.acc_bias_impulse_center_of_mass = MAX(jbnOld_com + jbn_com, 0.0f); + + Vector3 jb_com = c.normal * (c.acc_bias_impulse_center_of_mass - jbnOld_com); + + if (body_collides) { + body->apply_bias_impulse(-jb_com, body->get_center_of_mass(), 0.0f); + } + if (soft_body_collides) { + soft_body->apply_node_bias_impulse(c.index_B, jb_com); + } + } + + c.active = true; + } + + Vector3 crA = body->get_angular_velocity().cross(c.rA); + Vector3 dv = soft_body->get_node_velocity(c.index_B) - body->get_linear_velocity() - crA; + + // Normal impulse. + real_t vn = dv.dot(c.normal); + + if (Math::abs(vn) > MIN_VELOCITY) { + real_t jn = -(c.bounce + vn) * c.mass_normal; + real_t jnOld = c.acc_normal_impulse; + c.acc_normal_impulse = MAX(jnOld + jn, 0.0f); + + Vector3 j = c.normal * (c.acc_normal_impulse - jnOld); + + if (body_collides) { + body->apply_impulse(-j, c.rA + body->get_center_of_mass()); + } + if (soft_body_collides) { + soft_body->apply_node_impulse(c.index_B, j); + } + c.acc_impulse -= j; + + c.active = true; + } + + // Friction impulse. + real_t friction = body->get_friction(); + + Vector3 lvA = body->get_linear_velocity() + body->get_angular_velocity().cross(c.rA); + Vector3 lvB = soft_body->get_node_velocity(c.index_B); + Vector3 dtv = lvB - lvA; + + real_t tn = c.normal.dot(dtv); + + // Tangential velocity. + Vector3 tv = dtv - c.normal * tn; + real_t tvl = tv.length(); + + if (tvl > MIN_VELOCITY) { + tv /= tvl; + + Vector3 temp1 = body_inv_inertia_tensor.xform(c.rA.cross(tv)); + + real_t t = -tvl / (body_inv_mass + node_inv_mass + tv.dot(temp1.cross(c.rA))); + + Vector3 jt = t * tv; + + Vector3 jtOld = c.acc_tangent_impulse; + c.acc_tangent_impulse += jt; + + real_t fi_len = c.acc_tangent_impulse.length(); + real_t jtMax = c.acc_normal_impulse * friction; + + if (fi_len > CMP_EPSILON && fi_len > jtMax) { + c.acc_tangent_impulse *= jtMax / fi_len; + } + + jt = c.acc_tangent_impulse - jtOld; + + if (body_collides) { + body->apply_impulse(-jt, c.rA + body->get_center_of_mass()); + } + if (soft_body_collides) { + soft_body->apply_node_impulse(c.index_B, jt); + } + c.acc_impulse -= jt; + + c.active = true; + } + } +} + +GodotBodySoftBodyPair3D::GodotBodySoftBodyPair3D(GodotBody3D *p_A, int p_shape_A, GodotSoftBody3D *p_B) : + GodotBodyContact3D(&body, 1) { + body = p_A; + soft_body = p_B; + body_shape = p_shape_A; + space = p_A->get_space(); + body->add_constraint(this, 0); + soft_body->add_constraint(this); +} + +GodotBodySoftBodyPair3D::~GodotBodySoftBodyPair3D() { + body->remove_constraint(this); + soft_body->remove_constraint(this); +} diff --git a/modules/godot_physics_3d/godot_body_pair_3d.h b/modules/godot_physics_3d/godot_body_pair_3d.h new file mode 100644 index 0000000000..a8f5180dd5 --- /dev/null +++ b/modules/godot_physics_3d/godot_body_pair_3d.h @@ -0,0 +1,147 @@ +/**************************************************************************/ +/* godot_body_pair_3d.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 GODOT_BODY_PAIR_3D_H +#define GODOT_BODY_PAIR_3D_H + +#include "godot_body_3d.h" +#include "godot_constraint_3d.h" +#include "godot_soft_body_3d.h" + +#include "core/templates/local_vector.h" + +class GodotBodyContact3D : public GodotConstraint3D { +protected: + struct Contact { + Vector3 position; + Vector3 normal; + int index_A = 0, index_B = 0; + Vector3 local_A, local_B; + Vector3 acc_impulse; // accumulated impulse - only one of the object's impulse is needed as impulse_a == -impulse_b + real_t acc_normal_impulse = 0.0; // accumulated normal impulse (Pn) + Vector3 acc_tangent_impulse; // accumulated tangent impulse (Pt) + real_t acc_bias_impulse = 0.0; // accumulated normal impulse for position bias (Pnb) + real_t acc_bias_impulse_center_of_mass = 0.0; // accumulated normal impulse for position bias applied to com + real_t mass_normal = 0.0; + real_t bias = 0.0; + real_t bounce = 0.0; + + real_t depth = 0.0; + bool active = false; + bool used = false; + Vector3 rA, rB; // Offset in world orientation with respect to center of mass + }; + + Vector3 sep_axis; + bool collided = false; + bool check_ccd = false; + + GodotSpace3D *space = nullptr; + + GodotBodyContact3D(GodotBody3D **p_body_ptr = nullptr, int p_body_count = 0) : + GodotConstraint3D(p_body_ptr, p_body_count) { + } +}; + +class GodotBodyPair3D : public GodotBodyContact3D { + enum { + MAX_CONTACTS = 4 + }; + + union { + struct { + GodotBody3D *A; + GodotBody3D *B; + }; + + GodotBody3D *_arr[2] = { nullptr, nullptr }; + }; + + int shape_A = 0; + int shape_B = 0; + + bool collide_A = false; + bool collide_B = false; + + bool report_contacts_only = false; + + Vector3 offset_B; //use local A coordinates to avoid numerical issues on collision detection + + Contact contacts[MAX_CONTACTS]; + int contact_count = 0; + + static void _contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata); + + void contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal); + + void validate_contacts(); + bool _test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, const Transform3D &p_xform_A, GodotBody3D *p_B, int p_shape_B, const Transform3D &p_xform_B); + +public: + virtual bool setup(real_t p_step) override; + virtual bool pre_solve(real_t p_step) override; + virtual void solve(real_t p_step) override; + + GodotBodyPair3D(GodotBody3D *p_A, int p_shape_A, GodotBody3D *p_B, int p_shape_B); + ~GodotBodyPair3D(); +}; + +class GodotBodySoftBodyPair3D : public GodotBodyContact3D { + GodotBody3D *body = nullptr; + GodotSoftBody3D *soft_body = nullptr; + + int body_shape = 0; + + bool body_collides = false; + bool soft_body_collides = false; + + bool report_contacts_only = false; + + LocalVector<Contact> contacts; + + static void _contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata); + + void contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal); + + void validate_contacts(); + +public: + virtual bool setup(real_t p_step) override; + virtual bool pre_solve(real_t p_step) override; + virtual void solve(real_t p_step) override; + + virtual GodotSoftBody3D *get_soft_body_ptr(int p_index) const override { return soft_body; } + virtual int get_soft_body_count() const override { return 1; } + + GodotBodySoftBodyPair3D(GodotBody3D *p_A, int p_shape_A, GodotSoftBody3D *p_B); + ~GodotBodySoftBodyPair3D(); +}; + +#endif // GODOT_BODY_PAIR_3D_H diff --git a/modules/godot_physics_3d/godot_broad_phase_3d.cpp b/modules/godot_physics_3d/godot_broad_phase_3d.cpp new file mode 100644 index 0000000000..ebd11fb51f --- /dev/null +++ b/modules/godot_physics_3d/godot_broad_phase_3d.cpp @@ -0,0 +1,36 @@ +/**************************************************************************/ +/* godot_broad_phase_3d.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 "godot_broad_phase_3d.h" + +GodotBroadPhase3D::CreateFunction GodotBroadPhase3D::create_func = nullptr; + +GodotBroadPhase3D::~GodotBroadPhase3D() { +} diff --git a/modules/godot_physics_3d/godot_broad_phase_3d.h b/modules/godot_physics_3d/godot_broad_phase_3d.h new file mode 100644 index 0000000000..f70321be64 --- /dev/null +++ b/modules/godot_physics_3d/godot_broad_phase_3d.h @@ -0,0 +1,72 @@ +/**************************************************************************/ +/* godot_broad_phase_3d.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 GODOT_BROAD_PHASE_3D_H +#define GODOT_BROAD_PHASE_3D_H + +#include "core/math/aabb.h" +#include "core/math/math_funcs.h" + +class GodotCollisionObject3D; + +class GodotBroadPhase3D { +public: + typedef GodotBroadPhase3D *(*CreateFunction)(); + + static CreateFunction create_func; + + typedef uint32_t ID; + + typedef void *(*PairCallback)(GodotCollisionObject3D *A, int p_subindex_A, GodotCollisionObject3D *B, int p_subindex_B, void *p_userdata); + typedef void (*UnpairCallback)(GodotCollisionObject3D *A, int p_subindex_A, GodotCollisionObject3D *B, int p_subindex_B, void *p_data, void *p_userdata); + + // 0 is an invalid ID + virtual ID create(GodotCollisionObject3D *p_object_, int p_subindex = 0, const AABB &p_aabb = AABB(), bool p_static = false) = 0; + virtual void move(ID p_id, const AABB &p_aabb) = 0; + virtual void set_static(ID p_id, bool p_static) = 0; + virtual void remove(ID p_id) = 0; + + virtual GodotCollisionObject3D *get_object(ID p_id) const = 0; + virtual bool is_static(ID p_id) const = 0; + virtual int get_subindex(ID p_id) const = 0; + + virtual int cull_point(const Vector3 &p_point, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices = nullptr) = 0; + virtual int cull_segment(const Vector3 &p_from, const Vector3 &p_to, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices = nullptr) = 0; + virtual int cull_aabb(const AABB &p_aabb, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices = nullptr) = 0; + + virtual void set_pair_callback(PairCallback p_pair_callback, void *p_userdata) = 0; + virtual void set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) = 0; + + virtual void update() = 0; + + virtual ~GodotBroadPhase3D(); +}; + +#endif // GODOT_BROAD_PHASE_3D_H diff --git a/modules/godot_physics_3d/godot_broad_phase_3d_bvh.cpp b/modules/godot_physics_3d/godot_broad_phase_3d_bvh.cpp new file mode 100644 index 0000000000..0faa56b52e --- /dev/null +++ b/modules/godot_physics_3d/godot_broad_phase_3d_bvh.cpp @@ -0,0 +1,128 @@ +/**************************************************************************/ +/* godot_broad_phase_3d_bvh.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 "godot_broad_phase_3d_bvh.h" + +#include "godot_collision_object_3d.h" + +GodotBroadPhase3DBVH::ID GodotBroadPhase3DBVH::create(GodotCollisionObject3D *p_object, int p_subindex, const AABB &p_aabb, bool p_static) { + uint32_t tree_id = p_static ? TREE_STATIC : TREE_DYNAMIC; + uint32_t tree_collision_mask = p_static ? TREE_FLAG_DYNAMIC : (TREE_FLAG_STATIC | TREE_FLAG_DYNAMIC); + ID oid = bvh.create(p_object, true, tree_id, tree_collision_mask, p_aabb, p_subindex); // Pair everything, don't care? + return oid + 1; +} + +void GodotBroadPhase3DBVH::move(ID p_id, const AABB &p_aabb) { + ERR_FAIL_COND(!p_id); + bvh.move(p_id - 1, p_aabb); +} + +void GodotBroadPhase3DBVH::set_static(ID p_id, bool p_static) { + ERR_FAIL_COND(!p_id); + uint32_t tree_id = p_static ? TREE_STATIC : TREE_DYNAMIC; + uint32_t tree_collision_mask = p_static ? TREE_FLAG_DYNAMIC : (TREE_FLAG_STATIC | TREE_FLAG_DYNAMIC); + bvh.set_tree(p_id - 1, tree_id, tree_collision_mask, false); +} + +void GodotBroadPhase3DBVH::remove(ID p_id) { + ERR_FAIL_COND(!p_id); + bvh.erase(p_id - 1); +} + +GodotCollisionObject3D *GodotBroadPhase3DBVH::get_object(ID p_id) const { + ERR_FAIL_COND_V(!p_id, nullptr); + GodotCollisionObject3D *it = bvh.get(p_id - 1); + ERR_FAIL_NULL_V(it, nullptr); + return it; +} + +bool GodotBroadPhase3DBVH::is_static(ID p_id) const { + ERR_FAIL_COND_V(!p_id, false); + uint32_t tree_id = bvh.get_tree_id(p_id - 1); + return tree_id == 0; +} + +int GodotBroadPhase3DBVH::get_subindex(ID p_id) const { + ERR_FAIL_COND_V(!p_id, 0); + return bvh.get_subindex(p_id - 1); +} + +int GodotBroadPhase3DBVH::cull_point(const Vector3 &p_point, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices) { + return bvh.cull_point(p_point, p_results, p_max_results, nullptr, 0xFFFFFFFF, p_result_indices); +} + +int GodotBroadPhase3DBVH::cull_segment(const Vector3 &p_from, const Vector3 &p_to, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices) { + return bvh.cull_segment(p_from, p_to, p_results, p_max_results, nullptr, 0xFFFFFFFF, p_result_indices); +} + +int GodotBroadPhase3DBVH::cull_aabb(const AABB &p_aabb, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices) { + return bvh.cull_aabb(p_aabb, p_results, p_max_results, nullptr, 0xFFFFFFFF, p_result_indices); +} + +void *GodotBroadPhase3DBVH::_pair_callback(void *self, uint32_t p_A, GodotCollisionObject3D *p_object_A, int subindex_A, uint32_t p_B, GodotCollisionObject3D *p_object_B, int subindex_B) { + GodotBroadPhase3DBVH *bpo = static_cast<GodotBroadPhase3DBVH *>(self); + if (!bpo->pair_callback) { + return nullptr; + } + + return bpo->pair_callback(p_object_A, subindex_A, p_object_B, subindex_B, bpo->pair_userdata); +} + +void GodotBroadPhase3DBVH::_unpair_callback(void *self, uint32_t p_A, GodotCollisionObject3D *p_object_A, int subindex_A, uint32_t p_B, GodotCollisionObject3D *p_object_B, int subindex_B, void *pairdata) { + GodotBroadPhase3DBVH *bpo = static_cast<GodotBroadPhase3DBVH *>(self); + if (!bpo->unpair_callback) { + return; + } + + bpo->unpair_callback(p_object_A, subindex_A, p_object_B, subindex_B, pairdata, bpo->unpair_userdata); +} + +void GodotBroadPhase3DBVH::set_pair_callback(PairCallback p_pair_callback, void *p_userdata) { + pair_callback = p_pair_callback; + pair_userdata = p_userdata; +} + +void GodotBroadPhase3DBVH::set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) { + unpair_callback = p_unpair_callback; + unpair_userdata = p_userdata; +} + +void GodotBroadPhase3DBVH::update() { + bvh.update(); +} + +GodotBroadPhase3D *GodotBroadPhase3DBVH::_create() { + return memnew(GodotBroadPhase3DBVH); +} + +GodotBroadPhase3DBVH::GodotBroadPhase3DBVH() { + bvh.set_pair_callback(_pair_callback, this); + bvh.set_unpair_callback(_unpair_callback, this); +} diff --git a/modules/godot_physics_3d/godot_broad_phase_3d_bvh.h b/modules/godot_physics_3d/godot_broad_phase_3d_bvh.h new file mode 100644 index 0000000000..63968dea64 --- /dev/null +++ b/modules/godot_physics_3d/godot_broad_phase_3d_bvh.h @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* godot_broad_phase_3d_bvh.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 GODOT_BROAD_PHASE_3D_BVH_H +#define GODOT_BROAD_PHASE_3D_BVH_H + +#include "godot_broad_phase_3d.h" + +#include "core/math/bvh.h" + +class GodotBroadPhase3DBVH : public GodotBroadPhase3D { + template <typename T> + class UserPairTestFunction { + public: + static bool user_pair_check(const T *p_a, const T *p_b) { + // return false if no collision, decided by masks etc + return p_a->interacts_with(p_b); + } + }; + + template <typename T> + class UserCullTestFunction { + public: + static bool user_cull_check(const T *p_a, const T *p_b) { + return true; + } + }; + + enum Tree { + TREE_STATIC = 0, + TREE_DYNAMIC = 1, + }; + + enum TreeFlag { + TREE_FLAG_STATIC = 1 << TREE_STATIC, + TREE_FLAG_DYNAMIC = 1 << TREE_DYNAMIC, + }; + + BVH_Manager<GodotCollisionObject3D, 2, true, 128, UserPairTestFunction<GodotCollisionObject3D>, UserCullTestFunction<GodotCollisionObject3D>> bvh; + + static void *_pair_callback(void *, uint32_t, GodotCollisionObject3D *, int, uint32_t, GodotCollisionObject3D *, int); + static void _unpair_callback(void *, uint32_t, GodotCollisionObject3D *, int, uint32_t, GodotCollisionObject3D *, int, void *); + + PairCallback pair_callback = nullptr; + void *pair_userdata = nullptr; + UnpairCallback unpair_callback = nullptr; + void *unpair_userdata = nullptr; + +public: + // 0 is an invalid ID + virtual ID create(GodotCollisionObject3D *p_object, int p_subindex = 0, const AABB &p_aabb = AABB(), bool p_static = false) override; + virtual void move(ID p_id, const AABB &p_aabb) override; + virtual void set_static(ID p_id, bool p_static) override; + virtual void remove(ID p_id) override; + + virtual GodotCollisionObject3D *get_object(ID p_id) const override; + virtual bool is_static(ID p_id) const override; + virtual int get_subindex(ID p_id) const override; + + virtual int cull_point(const Vector3 &p_point, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices = nullptr) override; + virtual int cull_segment(const Vector3 &p_from, const Vector3 &p_to, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices = nullptr) override; + virtual int cull_aabb(const AABB &p_aabb, GodotCollisionObject3D **p_results, int p_max_results, int *p_result_indices = nullptr) override; + + virtual void set_pair_callback(PairCallback p_pair_callback, void *p_userdata) override; + virtual void set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) override; + + virtual void update() override; + + static GodotBroadPhase3D *_create(); + GodotBroadPhase3DBVH(); +}; + +#endif // GODOT_BROAD_PHASE_3D_BVH_H diff --git a/modules/godot_physics_3d/godot_collision_object_3d.cpp b/modules/godot_physics_3d/godot_collision_object_3d.cpp new file mode 100644 index 0000000000..283614a43d --- /dev/null +++ b/modules/godot_physics_3d/godot_collision_object_3d.cpp @@ -0,0 +1,242 @@ +/**************************************************************************/ +/* godot_collision_object_3d.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 "godot_collision_object_3d.h" + +#include "godot_physics_server_3d.h" +#include "godot_space_3d.h" + +void GodotCollisionObject3D::add_shape(GodotShape3D *p_shape, const Transform3D &p_transform, bool p_disabled) { + Shape s; + s.shape = p_shape; + s.xform = p_transform; + s.xform_inv = s.xform.affine_inverse(); + s.bpid = 0; //needs update + s.disabled = p_disabled; + shapes.push_back(s); + p_shape->add_owner(this); + + if (!pending_shape_update_list.in_list()) { + GodotPhysicsServer3D::godot_singleton->pending_shape_update_list.add(&pending_shape_update_list); + } +} + +void GodotCollisionObject3D::set_shape(int p_index, GodotShape3D *p_shape) { + ERR_FAIL_INDEX(p_index, shapes.size()); + shapes[p_index].shape->remove_owner(this); + shapes.write[p_index].shape = p_shape; + + p_shape->add_owner(this); + if (!pending_shape_update_list.in_list()) { + GodotPhysicsServer3D::godot_singleton->pending_shape_update_list.add(&pending_shape_update_list); + } +} + +void GodotCollisionObject3D::set_shape_transform(int p_index, const Transform3D &p_transform) { + ERR_FAIL_INDEX(p_index, shapes.size()); + + shapes.write[p_index].xform = p_transform; + shapes.write[p_index].xform_inv = p_transform.affine_inverse(); + if (!pending_shape_update_list.in_list()) { + GodotPhysicsServer3D::godot_singleton->pending_shape_update_list.add(&pending_shape_update_list); + } +} + +void GodotCollisionObject3D::set_shape_disabled(int p_idx, bool p_disabled) { + ERR_FAIL_INDEX(p_idx, shapes.size()); + + GodotCollisionObject3D::Shape &shape = shapes.write[p_idx]; + if (shape.disabled == p_disabled) { + return; + } + + shape.disabled = p_disabled; + + if (!space) { + return; + } + + if (p_disabled && shape.bpid != 0) { + space->get_broadphase()->remove(shape.bpid); + shape.bpid = 0; + if (!pending_shape_update_list.in_list()) { + GodotPhysicsServer3D::godot_singleton->pending_shape_update_list.add(&pending_shape_update_list); + } + } else if (!p_disabled && shape.bpid == 0) { + if (!pending_shape_update_list.in_list()) { + GodotPhysicsServer3D::godot_singleton->pending_shape_update_list.add(&pending_shape_update_list); + } + } +} + +void GodotCollisionObject3D::remove_shape(GodotShape3D *p_shape) { + //remove a shape, all the times it appears + for (int i = 0; i < shapes.size(); i++) { + if (shapes[i].shape == p_shape) { + remove_shape(i); + i--; + } + } +} + +void GodotCollisionObject3D::remove_shape(int p_index) { + //remove anything from shape to be erased to end, so subindices don't change + ERR_FAIL_INDEX(p_index, shapes.size()); + for (int i = p_index; i < shapes.size(); i++) { + if (shapes[i].bpid == 0) { + continue; + } + //should never get here with a null owner + space->get_broadphase()->remove(shapes[i].bpid); + shapes.write[i].bpid = 0; + } + shapes[p_index].shape->remove_owner(this); + shapes.remove_at(p_index); + + if (!pending_shape_update_list.in_list()) { + GodotPhysicsServer3D::godot_singleton->pending_shape_update_list.add(&pending_shape_update_list); + } +} + +void GodotCollisionObject3D::_set_static(bool p_static) { + if (_static == p_static) { + return; + } + _static = p_static; + + if (!space) { + return; + } + for (int i = 0; i < get_shape_count(); i++) { + const Shape &s = shapes[i]; + if (s.bpid > 0) { + space->get_broadphase()->set_static(s.bpid, _static); + } + } +} + +void GodotCollisionObject3D::_unregister_shapes() { + for (int i = 0; i < shapes.size(); i++) { + Shape &s = shapes.write[i]; + if (s.bpid > 0) { + space->get_broadphase()->remove(s.bpid); + s.bpid = 0; + } + } +} + +void GodotCollisionObject3D::_update_shapes() { + if (!space) { + return; + } + + for (int i = 0; i < shapes.size(); i++) { + Shape &s = shapes.write[i]; + if (s.disabled) { + continue; + } + + //not quite correct, should compute the next matrix.. + AABB shape_aabb = s.shape->get_aabb(); + Transform3D xform = transform * s.xform; + shape_aabb = xform.xform(shape_aabb); + shape_aabb.grow_by((s.aabb_cache.size.x + s.aabb_cache.size.y) * 0.5 * 0.05); + s.aabb_cache = shape_aabb; + + Vector3 scale = xform.get_basis().get_scale(); + s.area_cache = s.shape->get_volume() * scale.x * scale.y * scale.z; + + if (s.bpid == 0) { + s.bpid = space->get_broadphase()->create(this, i, shape_aabb, _static); + space->get_broadphase()->set_static(s.bpid, _static); + } + + space->get_broadphase()->move(s.bpid, shape_aabb); + } +} + +void GodotCollisionObject3D::_update_shapes_with_motion(const Vector3 &p_motion) { + if (!space) { + return; + } + + for (int i = 0; i < shapes.size(); i++) { + Shape &s = shapes.write[i]; + if (s.disabled) { + continue; + } + + //not quite correct, should compute the next matrix.. + AABB shape_aabb = s.shape->get_aabb(); + Transform3D xform = transform * s.xform; + shape_aabb = xform.xform(shape_aabb); + shape_aabb.merge_with(AABB(shape_aabb.position + p_motion, shape_aabb.size)); //use motion + s.aabb_cache = shape_aabb; + + if (s.bpid == 0) { + s.bpid = space->get_broadphase()->create(this, i, shape_aabb, _static); + space->get_broadphase()->set_static(s.bpid, _static); + } + + space->get_broadphase()->move(s.bpid, shape_aabb); + } +} + +void GodotCollisionObject3D::_set_space(GodotSpace3D *p_space) { + GodotSpace3D *old_space = space; + space = p_space; + + if (old_space) { + old_space->remove_object(this); + + for (int i = 0; i < shapes.size(); i++) { + Shape &s = shapes.write[i]; + if (s.bpid) { + old_space->get_broadphase()->remove(s.bpid); + s.bpid = 0; + } + } + } + + if (space) { + space->add_object(this); + _update_shapes(); + } +} + +void GodotCollisionObject3D::_shape_changed() { + _update_shapes(); + _shapes_changed(); +} + +GodotCollisionObject3D::GodotCollisionObject3D(Type p_type) : + pending_shape_update_list(this) { + type = p_type; +} diff --git a/modules/godot_physics_3d/godot_collision_object_3d.h b/modules/godot_physics_3d/godot_collision_object_3d.h new file mode 100644 index 0000000000..bf28bcc45a --- /dev/null +++ b/modules/godot_physics_3d/godot_collision_object_3d.h @@ -0,0 +1,194 @@ +/**************************************************************************/ +/* godot_collision_object_3d.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 GODOT_COLLISION_OBJECT_3D_H +#define GODOT_COLLISION_OBJECT_3D_H + +#include "godot_broad_phase_3d.h" +#include "godot_shape_3d.h" + +#include "core/templates/self_list.h" +#include "servers/physics_server_3d.h" + +#ifdef DEBUG_ENABLED +#define MAX_OBJECT_DISTANCE 3.1622776601683791e+18 + +#define MAX_OBJECT_DISTANCE_X2 (MAX_OBJECT_DISTANCE * MAX_OBJECT_DISTANCE) +#endif + +class GodotSpace3D; + +class GodotCollisionObject3D : public GodotShapeOwner3D { +public: + enum Type { + TYPE_AREA, + TYPE_BODY, + TYPE_SOFT_BODY, + }; + +private: + Type type; + RID self; + ObjectID instance_id; + uint32_t collision_layer = 1; + uint32_t collision_mask = 1; + real_t collision_priority = 1.0; + + struct Shape { + Transform3D xform; + Transform3D xform_inv; + GodotBroadPhase3D::ID bpid; + AABB aabb_cache; //for rayqueries + real_t area_cache = 0.0; + GodotShape3D *shape = nullptr; + bool disabled = false; + }; + + Vector<Shape> shapes; + GodotSpace3D *space = nullptr; + Transform3D transform; + Transform3D inv_transform; + bool _static = true; + + SelfList<GodotCollisionObject3D> pending_shape_update_list; + + void _update_shapes(); + +protected: + void _update_shapes_with_motion(const Vector3 &p_motion); + void _unregister_shapes(); + + _FORCE_INLINE_ void _set_transform(const Transform3D &p_transform, bool p_update_shapes = true) { +#ifdef DEBUG_ENABLED + + ERR_FAIL_COND_MSG(p_transform.origin.length_squared() > MAX_OBJECT_DISTANCE_X2, "Object went too far away (more than '" + itos(MAX_OBJECT_DISTANCE) + "' units from origin)."); +#endif + + transform = p_transform; + if (p_update_shapes) { + _update_shapes(); + } + } + _FORCE_INLINE_ void _set_inv_transform(const Transform3D &p_transform) { inv_transform = p_transform; } + void _set_static(bool p_static); + + virtual void _shapes_changed() = 0; + void _set_space(GodotSpace3D *p_space); + + bool ray_pickable = true; + + GodotCollisionObject3D(Type p_type); + +public: + _FORCE_INLINE_ void set_self(const RID &p_self) { self = p_self; } + _FORCE_INLINE_ RID get_self() const { return self; } + + _FORCE_INLINE_ void set_instance_id(const ObjectID &p_instance_id) { instance_id = p_instance_id; } + _FORCE_INLINE_ ObjectID get_instance_id() const { return instance_id; } + + void _shape_changed() override; + + _FORCE_INLINE_ Type get_type() const { return type; } + void add_shape(GodotShape3D *p_shape, const Transform3D &p_transform = Transform3D(), bool p_disabled = false); + void set_shape(int p_index, GodotShape3D *p_shape); + void set_shape_transform(int p_index, const Transform3D &p_transform); + _FORCE_INLINE_ int get_shape_count() const { return shapes.size(); } + _FORCE_INLINE_ GodotShape3D *get_shape(int p_index) const { + CRASH_BAD_INDEX(p_index, shapes.size()); + return shapes[p_index].shape; + } + _FORCE_INLINE_ const Transform3D &get_shape_transform(int p_index) const { + CRASH_BAD_INDEX(p_index, shapes.size()); + return shapes[p_index].xform; + } + _FORCE_INLINE_ const Transform3D &get_shape_inv_transform(int p_index) const { + CRASH_BAD_INDEX(p_index, shapes.size()); + return shapes[p_index].xform_inv; + } + _FORCE_INLINE_ const AABB &get_shape_aabb(int p_index) const { + CRASH_BAD_INDEX(p_index, shapes.size()); + return shapes[p_index].aabb_cache; + } + _FORCE_INLINE_ real_t get_shape_area(int p_index) const { + CRASH_BAD_INDEX(p_index, shapes.size()); + return shapes[p_index].area_cache; + } + + _FORCE_INLINE_ const Transform3D &get_transform() const { return transform; } + _FORCE_INLINE_ const Transform3D &get_inv_transform() const { return inv_transform; } + _FORCE_INLINE_ GodotSpace3D *get_space() const { return space; } + + _FORCE_INLINE_ void set_ray_pickable(bool p_enable) { ray_pickable = p_enable; } + _FORCE_INLINE_ bool is_ray_pickable() const { return ray_pickable; } + + void set_shape_disabled(int p_idx, bool p_disabled); + _FORCE_INLINE_ bool is_shape_disabled(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, shapes.size(), false); + return shapes[p_idx].disabled; + } + + _FORCE_INLINE_ void set_collision_layer(uint32_t p_layer) { + collision_layer = p_layer; + _shape_changed(); + } + _FORCE_INLINE_ uint32_t get_collision_layer() const { return collision_layer; } + + _FORCE_INLINE_ void set_collision_mask(uint32_t p_mask) { + collision_mask = p_mask; + _shape_changed(); + } + _FORCE_INLINE_ uint32_t get_collision_mask() const { return collision_mask; } + + _FORCE_INLINE_ void set_collision_priority(real_t p_priority) { + ERR_FAIL_COND_MSG(p_priority <= 0, "Priority must be greater than 0."); + collision_priority = p_priority; + _shape_changed(); + } + _FORCE_INLINE_ real_t get_collision_priority() const { return collision_priority; } + + _FORCE_INLINE_ bool collides_with(GodotCollisionObject3D *p_other) const { + return p_other->collision_layer & collision_mask; + } + + _FORCE_INLINE_ bool interacts_with(const GodotCollisionObject3D *p_other) const { + return collision_layer & p_other->collision_mask || p_other->collision_layer & collision_mask; + } + + void remove_shape(GodotShape3D *p_shape) override; + void remove_shape(int p_index); + + virtual void set_space(GodotSpace3D *p_space) = 0; + + _FORCE_INLINE_ bool is_static() const { return _static; } + + virtual ~GodotCollisionObject3D() {} +}; + +#endif // GODOT_COLLISION_OBJECT_3D_H diff --git a/modules/godot_physics_3d/godot_collision_solver_3d.cpp b/modules/godot_physics_3d/godot_collision_solver_3d.cpp new file mode 100644 index 0000000000..db48111eea --- /dev/null +++ b/modules/godot_physics_3d/godot_collision_solver_3d.cpp @@ -0,0 +1,589 @@ +/**************************************************************************/ +/* godot_collision_solver_3d.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 "godot_collision_solver_3d.h" + +#include "godot_collision_solver_3d_sat.h" +#include "godot_soft_body_3d.h" + +#include "gjk_epa.h" + +#define collision_solver sat_calculate_penetration +//#define collision_solver gjk_epa_calculate_penetration + +bool GodotCollisionSolver3D::solve_static_world_boundary(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin) { + const GodotWorldBoundaryShape3D *world_boundary = static_cast<const GodotWorldBoundaryShape3D *>(p_shape_A); + if (p_shape_B->get_type() == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) { + return false; + } + Plane p = p_transform_A.xform(world_boundary->get_plane()); + + static const int max_supports = 16; + Vector3 supports[max_supports]; + int support_count; + GodotShape3D::FeatureType support_type = GodotShape3D::FeatureType::FEATURE_POINT; + p_shape_B->get_supports(p_transform_B.basis.xform_inv(-p.normal).normalized(), max_supports, supports, support_count, support_type); + + if (support_type == GodotShape3D::FEATURE_CIRCLE) { + ERR_FAIL_COND_V(support_count != 3, false); + + Vector3 circle_pos = supports[0]; + Vector3 circle_axis_1 = supports[1] - circle_pos; + Vector3 circle_axis_2 = supports[2] - circle_pos; + + // Use 3 equidistant points on the circle. + for (int i = 0; i < 3; ++i) { + Vector3 vertex_pos = circle_pos; + vertex_pos += circle_axis_1 * Math::cos(2.0 * Math_PI * i / 3.0); + vertex_pos += circle_axis_2 * Math::sin(2.0 * Math_PI * i / 3.0); + supports[i] = vertex_pos; + } + } + + bool found = false; + + for (int i = 0; i < support_count; i++) { + supports[i] += p_margin * supports[i].normalized(); + supports[i] = p_transform_B.xform(supports[i]); + if (p.distance_to(supports[i]) >= 0) { + continue; + } + found = true; + + Vector3 support_A = p.project(supports[i]); + + if (p_result_callback) { + if (p_swap_result) { + Vector3 normal = (support_A - supports[i]).normalized(); + p_result_callback(supports[i], 0, support_A, 0, normal, p_userdata); + } else { + Vector3 normal = (supports[i] - support_A).normalized(); + p_result_callback(support_A, 0, supports[i], 0, normal, p_userdata); + } + } + } + + return found; +} + +bool GodotCollisionSolver3D::solve_separation_ray(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin) { + const GodotSeparationRayShape3D *ray = static_cast<const GodotSeparationRayShape3D *>(p_shape_A); + + Vector3 from = p_transform_A.origin; + Vector3 to = from + p_transform_A.basis.get_column(2) * (ray->get_length() + p_margin); + Vector3 support_A = to; + + Transform3D ai = p_transform_B.affine_inverse(); + + from = ai.xform(from); + to = ai.xform(to); + + Vector3 p, n; + int fi = -1; + if (!p_shape_B->intersect_segment(from, to, p, n, fi, true)) { + return false; + } + + // Discard contacts when the ray is fully contained inside the shape. + if (n == Vector3()) { + return false; + } + + // Discard contacts in the wrong direction. + if (n.dot(from - to) < CMP_EPSILON) { + return false; + } + + Vector3 support_B = p_transform_B.xform(p); + if (ray->get_slide_on_slope()) { + Vector3 global_n = ai.basis.xform_inv(n).normalized(); + support_B = support_A + (support_B - support_A).length() * global_n; + } + + if (p_result_callback) { + Vector3 normal = (support_B - support_A).normalized(); + if (p_swap_result) { + p_result_callback(support_B, 0, support_A, 0, -normal, p_userdata); + } else { + p_result_callback(support_A, 0, support_B, 0, normal, p_userdata); + } + } + return true; +} + +struct _SoftBodyContactCollisionInfo { + int node_index = 0; + GodotCollisionSolver3D::CallbackResult result_callback = nullptr; + void *userdata = nullptr; + bool swap_result = false; + int contact_count = 0; +}; + +void GodotCollisionSolver3D::soft_body_contact_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata) { + _SoftBodyContactCollisionInfo &cinfo = *(static_cast<_SoftBodyContactCollisionInfo *>(p_userdata)); + + ++cinfo.contact_count; + + if (!cinfo.result_callback) { + return; + } + + if (cinfo.swap_result) { + cinfo.result_callback(p_point_B, cinfo.node_index, p_point_A, p_index_A, -normal, cinfo.userdata); + } else { + cinfo.result_callback(p_point_A, p_index_A, p_point_B, cinfo.node_index, normal, cinfo.userdata); + } +} + +struct _SoftBodyQueryInfo { + GodotSoftBody3D *soft_body = nullptr; + const GodotShape3D *shape_A = nullptr; + const GodotShape3D *shape_B = nullptr; + Transform3D transform_A; + Transform3D node_transform; + _SoftBodyContactCollisionInfo contact_info; +#ifdef DEBUG_ENABLED + int node_query_count = 0; + int convex_query_count = 0; +#endif +}; + +bool GodotCollisionSolver3D::soft_body_query_callback(uint32_t p_node_index, void *p_userdata) { + _SoftBodyQueryInfo &query_cinfo = *(static_cast<_SoftBodyQueryInfo *>(p_userdata)); + + Vector3 node_position = query_cinfo.soft_body->get_node_position(p_node_index); + + Transform3D transform_B; + transform_B.origin = query_cinfo.node_transform.xform(node_position); + + query_cinfo.contact_info.node_index = p_node_index; + bool collided = solve_static(query_cinfo.shape_A, query_cinfo.transform_A, query_cinfo.shape_B, transform_B, soft_body_contact_callback, &query_cinfo.contact_info); + +#ifdef DEBUG_ENABLED + ++query_cinfo.node_query_count; +#endif + + // Stop at first collision if contacts are not needed. + return (collided && !query_cinfo.contact_info.result_callback); +} + +bool GodotCollisionSolver3D::soft_body_concave_callback(void *p_userdata, GodotShape3D *p_convex) { + _SoftBodyQueryInfo &query_cinfo = *(static_cast<_SoftBodyQueryInfo *>(p_userdata)); + + query_cinfo.shape_A = p_convex; + + // Calculate AABB for internal soft body query (in world space). + AABB shape_aabb; + for (int i = 0; i < 3; i++) { + Vector3 axis; + axis[i] = 1.0; + + real_t smin, smax; + p_convex->project_range(axis, query_cinfo.transform_A, smin, smax); + + shape_aabb.position[i] = smin; + shape_aabb.size[i] = smax - smin; + } + + shape_aabb.grow_by(query_cinfo.soft_body->get_collision_margin()); + + query_cinfo.soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo); + + bool collided = (query_cinfo.contact_info.contact_count > 0); + +#ifdef DEBUG_ENABLED + ++query_cinfo.convex_query_count; +#endif + + // Stop at first collision if contacts are not needed. + return (collided && !query_cinfo.contact_info.result_callback); +} + +bool GodotCollisionSolver3D::solve_soft_body(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result) { + const GodotSoftBodyShape3D *soft_body_shape_B = static_cast<const GodotSoftBodyShape3D *>(p_shape_B); + + GodotSoftBody3D *soft_body = soft_body_shape_B->get_soft_body(); + const Transform3D &world_to_local = soft_body->get_inv_transform(); + + const real_t collision_margin = soft_body->get_collision_margin(); + + GodotSphereShape3D sphere_shape; + sphere_shape.set_data(collision_margin); + + _SoftBodyQueryInfo query_cinfo; + query_cinfo.contact_info.result_callback = p_result_callback; + query_cinfo.contact_info.userdata = p_userdata; + query_cinfo.contact_info.swap_result = p_swap_result; + query_cinfo.soft_body = soft_body; + query_cinfo.node_transform = p_transform_B * world_to_local; + query_cinfo.shape_A = p_shape_A; + query_cinfo.transform_A = p_transform_A; + query_cinfo.shape_B = &sphere_shape; + + if (p_shape_A->is_concave()) { + // In case of concave shape, query convex shapes first. + const GodotConcaveShape3D *concave_shape_A = static_cast<const GodotConcaveShape3D *>(p_shape_A); + + AABB soft_body_aabb = soft_body->get_bounds(); + soft_body_aabb.grow_by(collision_margin); + + // Calculate AABB for internal concave shape query (in local space). + AABB local_aabb; + for (int i = 0; i < 3; i++) { + Vector3 axis(p_transform_A.basis.get_column(i)); + real_t axis_scale = 1.0 / axis.length(); + + real_t smin = soft_body_aabb.position[i]; + real_t smax = smin + soft_body_aabb.size[i]; + + smin *= axis_scale; + smax *= axis_scale; + + local_aabb.position[i] = smin; + local_aabb.size[i] = smax - smin; + } + + concave_shape_A->cull(local_aabb, soft_body_concave_callback, &query_cinfo, true); + } else { + AABB shape_aabb = p_transform_A.xform(p_shape_A->get_aabb()); + shape_aabb.grow_by(collision_margin); + + soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo); + } + + return (query_cinfo.contact_info.contact_count > 0); +} + +struct _ConcaveCollisionInfo { + const Transform3D *transform_A = nullptr; + const GodotShape3D *shape_A = nullptr; + const Transform3D *transform_B = nullptr; + GodotCollisionSolver3D::CallbackResult result_callback = nullptr; + void *userdata = nullptr; + bool swap_result = false; + bool collided = false; + int aabb_tests = 0; + int collisions = 0; + bool tested = false; + real_t margin_A = 0.0f; + real_t margin_B = 0.0f; + Vector3 close_A; + Vector3 close_B; +}; + +bool GodotCollisionSolver3D::concave_callback(void *p_userdata, GodotShape3D *p_convex) { + _ConcaveCollisionInfo &cinfo = *(static_cast<_ConcaveCollisionInfo *>(p_userdata)); + cinfo.aabb_tests++; + + bool collided = collision_solver(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, cinfo.result_callback, cinfo.userdata, cinfo.swap_result, nullptr, cinfo.margin_A, cinfo.margin_B); + if (!collided) { + return false; + } + + cinfo.collided = true; + cinfo.collisions++; + + // Stop at first collision if contacts are not needed. + return !cinfo.result_callback; +} + +bool GodotCollisionSolver3D::solve_concave(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A, real_t p_margin_B) { + const GodotConcaveShape3D *concave_B = static_cast<const GodotConcaveShape3D *>(p_shape_B); + + _ConcaveCollisionInfo cinfo; + cinfo.transform_A = &p_transform_A; + cinfo.shape_A = p_shape_A; + cinfo.transform_B = &p_transform_B; + cinfo.result_callback = p_result_callback; + cinfo.userdata = p_userdata; + cinfo.swap_result = p_swap_result; + cinfo.collided = false; + cinfo.collisions = 0; + cinfo.margin_A = p_margin_A; + cinfo.margin_B = p_margin_B; + + cinfo.aabb_tests = 0; + + Transform3D rel_transform = p_transform_A; + rel_transform.origin -= p_transform_B.origin; + + //quickly compute a local AABB + + AABB local_aabb; + for (int i = 0; i < 3; i++) { + Vector3 axis(p_transform_B.basis.get_column(i)); + real_t axis_scale = 1.0 / axis.length(); + axis *= axis_scale; + + real_t smin = 0.0, smax = 0.0; + p_shape_A->project_range(axis, rel_transform, smin, smax); + smin -= p_margin_A; + smax += p_margin_A; + smin *= axis_scale; + smax *= axis_scale; + + local_aabb.position[i] = smin; + local_aabb.size[i] = smax - smin; + } + + concave_B->cull(local_aabb, concave_callback, &cinfo, false); + + return cinfo.collided; +} + +bool GodotCollisionSolver3D::solve_static(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, Vector3 *r_sep_axis, real_t p_margin_A, real_t p_margin_B) { + PhysicsServer3D::ShapeType type_A = p_shape_A->get_type(); + PhysicsServer3D::ShapeType type_B = p_shape_B->get_type(); + bool concave_A = p_shape_A->is_concave(); + bool concave_B = p_shape_B->is_concave(); + + bool swap = false; + + if (type_A > type_B) { + SWAP(type_A, type_B); + SWAP(concave_A, concave_B); + swap = true; + } + + if (type_A == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) { + if (type_B == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) { + WARN_PRINT_ONCE("Collisions between world boundaries are not supported."); + return false; + } + if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) { + WARN_PRINT_ONCE("Collisions between world boundaries and rays are not supported."); + return false; + } + if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) { + WARN_PRINT_ONCE("Collisions between world boundaries and soft bodies are not supported."); + return false; + } + + if (swap) { + return solve_static_world_boundary(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_A); + } else { + return solve_static_world_boundary(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_B); + } + + } else if (type_A == PhysicsServer3D::SHAPE_SEPARATION_RAY) { + if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) { + WARN_PRINT_ONCE("Collisions between rays are not supported."); + return false; + } + + if (swap) { + return solve_separation_ray(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_B); + } else { + return solve_separation_ray(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_A); + } + + } else if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) { + if (type_A == PhysicsServer3D::SHAPE_SOFT_BODY) { + WARN_PRINT_ONCE("Collisions between soft bodies are not supported."); + return false; + } + + if (swap) { + return solve_soft_body(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true); + } else { + return solve_soft_body(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false); + } + + } else if (concave_B) { + if (concave_A) { + WARN_PRINT_ONCE("Collisions between two concave shapes are not supported."); + return false; + } + + if (!swap) { + return solve_concave(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_A, p_margin_B); + } else { + return solve_concave(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_A, p_margin_B); + } + + } else { + return collision_solver(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, r_sep_axis, p_margin_A, p_margin_B); + } +} + +bool GodotCollisionSolver3D::concave_distance_callback(void *p_userdata, GodotShape3D *p_convex) { + _ConcaveCollisionInfo &cinfo = *(static_cast<_ConcaveCollisionInfo *>(p_userdata)); + cinfo.aabb_tests++; + + Vector3 close_A, close_B; + cinfo.collided = !gjk_epa_calculate_distance(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, close_A, close_B); + + if (cinfo.collided) { + // No need to process any more result. + return true; + } + + if (!cinfo.tested || close_A.distance_squared_to(close_B) < cinfo.close_A.distance_squared_to(cinfo.close_B)) { + cinfo.close_A = close_A; + cinfo.close_B = close_B; + cinfo.tested = true; + } + + cinfo.collisions++; + return false; +} + +bool GodotCollisionSolver3D::solve_distance_world_boundary(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B) { + const GodotWorldBoundaryShape3D *world_boundary = static_cast<const GodotWorldBoundaryShape3D *>(p_shape_A); + if (p_shape_B->get_type() == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) { + return false; + } + Plane p = p_transform_A.xform(world_boundary->get_plane()); + + static const int max_supports = 16; + Vector3 supports[max_supports]; + int support_count; + GodotShape3D::FeatureType support_type; + Vector3 support_direction = p_transform_B.basis.xform_inv(-p.normal).normalized(); + + p_shape_B->get_supports(support_direction, max_supports, supports, support_count, support_type); + + if (support_count == 0) { // This is a poor man's way to detect shapes that don't implement get_supports, such as GodotMotionShape3D. + Vector3 support_B = p_transform_B.xform(p_shape_B->get_support(support_direction)); + r_point_A = p.project(support_B); + r_point_B = support_B; + bool collided = p.distance_to(support_B) <= 0; + return collided; + } + + if (support_type == GodotShape3D::FEATURE_CIRCLE) { + ERR_FAIL_COND_V(support_count != 3, false); + + Vector3 circle_pos = supports[0]; + Vector3 circle_axis_1 = supports[1] - circle_pos; + Vector3 circle_axis_2 = supports[2] - circle_pos; + + // Use 3 equidistant points on the circle. + for (int i = 0; i < 3; ++i) { + Vector3 vertex_pos = circle_pos; + vertex_pos += circle_axis_1 * Math::cos(2.0 * Math_PI * i / 3.0); + vertex_pos += circle_axis_2 * Math::sin(2.0 * Math_PI * i / 3.0); + supports[i] = vertex_pos; + } + } + + bool collided = false; + Vector3 closest; + real_t closest_d = 0; + + for (int i = 0; i < support_count; i++) { + supports[i] = p_transform_B.xform(supports[i]); + real_t d = p.distance_to(supports[i]); + if (i == 0 || d < closest_d) { + closest = supports[i]; + closest_d = d; + if (d <= 0) { + collided = true; + } + } + } + + r_point_A = p.project(closest); + r_point_B = closest; + + return collided; +} + +bool GodotCollisionSolver3D::solve_distance(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B, const AABB &p_concave_hint, Vector3 *r_sep_axis) { + if (p_shape_B->get_type() == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) { + Vector3 a, b; + bool col = solve_distance_world_boundary(p_shape_B, p_transform_B, p_shape_A, p_transform_A, a, b); + r_point_A = b; + r_point_B = a; + return !col; + + } else if (p_shape_B->is_concave()) { + if (p_shape_A->is_concave()) { + return false; + } + + const GodotConcaveShape3D *concave_B = static_cast<const GodotConcaveShape3D *>(p_shape_B); + + _ConcaveCollisionInfo cinfo; + cinfo.transform_A = &p_transform_A; + cinfo.shape_A = p_shape_A; + cinfo.transform_B = &p_transform_B; + cinfo.result_callback = nullptr; + cinfo.userdata = nullptr; + cinfo.swap_result = false; + cinfo.collided = false; + cinfo.collisions = 0; + cinfo.aabb_tests = 0; + cinfo.tested = false; + + Transform3D rel_transform = p_transform_A; + rel_transform.origin -= p_transform_B.origin; + + //quickly compute a local AABB + + bool use_cc_hint = p_concave_hint != AABB(); + AABB cc_hint_aabb; + if (use_cc_hint) { + cc_hint_aabb = p_concave_hint; + cc_hint_aabb.position -= p_transform_B.origin; + } + + AABB local_aabb; + for (int i = 0; i < 3; i++) { + Vector3 axis(p_transform_B.basis.get_column(i)); + real_t axis_scale = ((real_t)1.0) / axis.length(); + axis *= axis_scale; + + real_t smin, smax; + + if (use_cc_hint) { + cc_hint_aabb.project_range_in_plane(Plane(axis), smin, smax); + } else { + p_shape_A->project_range(axis, rel_transform, smin, smax); + } + + smin *= axis_scale; + smax *= axis_scale; + + local_aabb.position[i] = smin; + local_aabb.size[i] = smax - smin; + } + + concave_B->cull(local_aabb, concave_distance_callback, &cinfo, false); + if (!cinfo.collided) { + r_point_A = cinfo.close_A; + r_point_B = cinfo.close_B; + } + + return !cinfo.collided; + } else { + return gjk_epa_calculate_distance(p_shape_A, p_transform_A, p_shape_B, p_transform_B, r_point_A, r_point_B); //should pass sepaxis.. + } +} diff --git a/modules/godot_physics_3d/godot_collision_solver_3d.h b/modules/godot_physics_3d/godot_collision_solver_3d.h new file mode 100644 index 0000000000..36ea79576e --- /dev/null +++ b/modules/godot_physics_3d/godot_collision_solver_3d.h @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* godot_collision_solver_3d.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 GODOT_COLLISION_SOLVER_3D_H +#define GODOT_COLLISION_SOLVER_3D_H + +#include "godot_shape_3d.h" + +class GodotCollisionSolver3D { +public: + typedef void (*CallbackResult)(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata); + +private: + static bool soft_body_query_callback(uint32_t p_node_index, void *p_userdata); + static void soft_body_contact_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata); + static bool soft_body_concave_callback(void *p_userdata, GodotShape3D *p_convex); + static bool concave_callback(void *p_userdata, GodotShape3D *p_convex); + static bool solve_static_world_boundary(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin = 0); + static bool solve_separation_ray(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin = 0); + static bool solve_soft_body(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); + static bool solve_concave(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A = 0, real_t p_margin_B = 0); + static bool concave_distance_callback(void *p_userdata, GodotShape3D *p_convex); + static bool solve_distance_world_boundary(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B); + +public: + static bool solve_static(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, Vector3 *r_sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0); + static bool solve_distance(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B, const AABB &p_concave_hint, Vector3 *r_sep_axis = nullptr); +}; + +#endif // GODOT_COLLISION_SOLVER_3D_H diff --git a/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp new file mode 100644 index 0000000000..c53c8481f4 --- /dev/null +++ b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp @@ -0,0 +1,2417 @@ +/**************************************************************************/ +/* godot_collision_solver_3d_sat.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 "godot_collision_solver_3d_sat.h" + +#include "gjk_epa.h" + +#include "core/math/geometry_3d.h" + +#define fallback_collision_solver gjk_epa_calculate_penetration + +#define _BACKFACE_NORMAL_THRESHOLD -0.0002 + +// Cylinder SAT analytic methods and face-circle contact points for cylinder-trimesh and cylinder-box collision are based on ODE colliders. + +/* + * Cylinder-trimesh and Cylinder-box colliders by Alen Ladavac + * Ported to ODE by Nguyen Binh + */ + +/************************************************************************* + * * + * Open Dynamics Engine, Copyright (C) 2001-2003 Russell L. Smith. * + * All rights reserved. Email: russ@q12.org Web: www.q12.org * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of EITHER: * + * (1) The GNU Lesser General Public License as published by the Free * + * Software Foundation; either version 2.1 of the License, or (at * + * your option) any later version. The text of the GNU Lesser * + * General Public License is included with this library in the * + * file LICENSE.TXT. * + * (2) The BSD-style license that is included with this library in * + * the file LICENSE-BSD.TXT. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files * + * LICENSE.TXT and LICENSE-BSD.TXT for more details. * + * * + *************************************************************************/ + +struct _CollectorCallback { + GodotCollisionSolver3D::CallbackResult callback = nullptr; + void *userdata = nullptr; + bool swap = false; + bool collided = false; + Vector3 normal; + Vector3 *prev_axis = nullptr; + + _FORCE_INLINE_ void call(const Vector3 &p_point_A, const Vector3 &p_point_B, Vector3 p_normal) { + if (p_normal.dot(p_point_B - p_point_A) < 0) + p_normal = -p_normal; + if (swap) { + callback(p_point_B, 0, p_point_A, 0, -p_normal, userdata); + } else { + callback(p_point_A, 0, p_point_B, 0, p_normal, userdata); + } + } +}; + +typedef void (*GenerateContactsFunc)(const Vector3 *, int, const Vector3 *, int, _CollectorCallback *); + +static void _generate_contacts_point_point(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 1); + ERR_FAIL_COND(p_point_count_B != 1); +#endif + + p_callback->call(*p_points_A, *p_points_B, p_callback->normal); +} + +static void _generate_contacts_point_edge(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 1); + ERR_FAIL_COND(p_point_count_B != 2); +#endif + + Vector3 closest_B = Geometry3D::get_closest_point_to_segment_uncapped(*p_points_A, p_points_B); + p_callback->call(*p_points_A, closest_B, p_callback->normal); +} + +static void _generate_contacts_point_face(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 1); + ERR_FAIL_COND(p_point_count_B < 3); +#endif + + Plane plane(p_points_B[0], p_points_B[1], p_points_B[2]); + Vector3 closest_B = plane.project(*p_points_A); + p_callback->call(*p_points_A, closest_B, plane.get_normal()); +} + +static void _generate_contacts_point_circle(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 1); + ERR_FAIL_COND(p_point_count_B != 3); +#endif + + Plane plane(p_points_B[0], p_points_B[1], p_points_B[2]); + Vector3 closest_B = plane.project(*p_points_A); + p_callback->call(*p_points_A, closest_B, plane.get_normal()); +} + +static void _generate_contacts_edge_edge(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 2); + ERR_FAIL_COND(p_point_count_B != 2); // circle is actually a 4x3 matrix +#endif + + Vector3 rel_A = p_points_A[1] - p_points_A[0]; + Vector3 rel_B = p_points_B[1] - p_points_B[0]; + + Vector3 c = rel_A.cross(rel_B).cross(rel_B); + + if (Math::is_zero_approx(rel_A.dot(c))) { + // should handle somehow.. + //ERR_PRINT("TODO FIX"); + //return; + + Vector3 axis = rel_A.normalized(); //make an axis + Vector3 base_A = p_points_A[0] - axis * axis.dot(p_points_A[0]); + Vector3 base_B = p_points_B[0] - axis * axis.dot(p_points_B[0]); + + //sort all 4 points in axis + real_t dvec[4] = { axis.dot(p_points_A[0]), axis.dot(p_points_A[1]), axis.dot(p_points_B[0]), axis.dot(p_points_B[1]) }; + + SortArray<real_t> sa; + sa.sort(dvec, 4); + + //use the middle ones as contacts + p_callback->call(base_A + axis * dvec[1], base_B + axis * dvec[1], p_callback->normal); + p_callback->call(base_A + axis * dvec[2], base_B + axis * dvec[2], p_callback->normal); + + return; + } + + real_t d = (c.dot(p_points_B[0]) - p_points_A[0].dot(c)) / rel_A.dot(c); + + if (d < 0.0) { + d = 0.0; + } else if (d > 1.0) { + d = 1.0; + } + + Vector3 closest_A = p_points_A[0] + rel_A * d; + Vector3 closest_B = Geometry3D::get_closest_point_to_segment_uncapped(closest_A, p_points_B); + // The normal should be perpendicular to both edges. + Vector3 normal = rel_A.cross(rel_B); + real_t normal_len = normal.length(); + if (normal_len > 1e-3) + normal /= normal_len; + else + normal = p_callback->normal; + p_callback->call(closest_A, closest_B, normal); +} + +static void _generate_contacts_edge_circle(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 2); + ERR_FAIL_COND(p_point_count_B != 3); +#endif + + const Vector3 &circle_B_pos = p_points_B[0]; + Vector3 circle_B_line_1 = p_points_B[1] - circle_B_pos; + Vector3 circle_B_line_2 = p_points_B[2] - circle_B_pos; + + real_t circle_B_radius = circle_B_line_1.length(); + Vector3 circle_B_normal = circle_B_line_1.cross(circle_B_line_2).normalized(); + + Plane circle_plane(circle_B_normal, circle_B_pos); + + static const int max_clip = 2; + Vector3 contact_points[max_clip]; + int num_points = 0; + + // Project edge point in circle plane. + const Vector3 &edge_A_1 = p_points_A[0]; + Vector3 proj_point_1 = circle_plane.project(edge_A_1); + + Vector3 dist_vec = proj_point_1 - circle_B_pos; + real_t dist_sq = dist_vec.length_squared(); + + // Point 1 is inside disk, add as contact point. + if (dist_sq <= circle_B_radius * circle_B_radius) { + contact_points[num_points] = edge_A_1; + ++num_points; + } + + const Vector3 &edge_A_2 = p_points_A[1]; + Vector3 proj_point_2 = circle_plane.project(edge_A_2); + + Vector3 dist_vec_2 = proj_point_2 - circle_B_pos; + real_t dist_sq_2 = dist_vec_2.length_squared(); + + // Point 2 is inside disk, add as contact point. + if (dist_sq_2 <= circle_B_radius * circle_B_radius) { + contact_points[num_points] = edge_A_2; + ++num_points; + } + + if (num_points < 2) { + Vector3 line_vec = proj_point_2 - proj_point_1; + real_t line_length_sq = line_vec.length_squared(); + + // Create a quadratic formula of the form ax^2 + bx + c = 0 + real_t a, b, c; + + a = line_length_sq; + b = 2.0 * dist_vec.dot(line_vec); + c = dist_sq - circle_B_radius * circle_B_radius; + + // Solve for t. + real_t sqrtterm = b * b - 4.0 * a * c; + + // If the term we intend to square root is less than 0 then the answer won't be real, + // so the line doesn't intersect. + if (sqrtterm >= 0) { + sqrtterm = Math::sqrt(sqrtterm); + + Vector3 edge_dir = edge_A_2 - edge_A_1; + + real_t fraction_1 = (-b - sqrtterm) / (2.0 * a); + if ((fraction_1 > 0.0) && (fraction_1 < 1.0)) { + Vector3 face_point_1 = edge_A_1 + fraction_1 * edge_dir; + ERR_FAIL_COND(num_points >= max_clip); + contact_points[num_points] = face_point_1; + ++num_points; + } + + real_t fraction_2 = (-b + sqrtterm) / (2.0 * a); + if ((fraction_2 > 0.0) && (fraction_2 < 1.0) && !Math::is_equal_approx(fraction_1, fraction_2)) { + Vector3 face_point_2 = edge_A_1 + fraction_2 * edge_dir; + ERR_FAIL_COND(num_points >= max_clip); + contact_points[num_points] = face_point_2; + ++num_points; + } + } + } + + // Generate contact points. + for (int i = 0; i < num_points; i++) { + const Vector3 &contact_point_A = contact_points[i]; + + real_t d = circle_plane.distance_to(contact_point_A); + Vector3 closest_B = contact_point_A - circle_plane.normal * d; + + if (p_callback->normal.dot(contact_point_A) >= p_callback->normal.dot(closest_B)) { + continue; + } + + p_callback->call(contact_point_A, closest_B, circle_plane.get_normal()); + } +} + +static void _generate_contacts_face_face(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A < 2); + ERR_FAIL_COND(p_point_count_B < 3); +#endif + + static const int max_clip = 32; + + Vector3 _clipbuf1[max_clip]; + Vector3 _clipbuf2[max_clip]; + Vector3 *clipbuf_src = _clipbuf1; + Vector3 *clipbuf_dst = _clipbuf2; + int clipbuf_len = p_point_count_A; + + // copy A points to clipbuf_src + for (int i = 0; i < p_point_count_A; i++) { + clipbuf_src[i] = p_points_A[i]; + } + + Plane plane_B(p_points_B[0], p_points_B[1], p_points_B[2]); + + // go through all of B points + for (int i = 0; i < p_point_count_B; i++) { + int i_n = (i + 1) % p_point_count_B; + + Vector3 edge0_B = p_points_B[i]; + Vector3 edge1_B = p_points_B[i_n]; + + Vector3 clip_normal = (edge0_B - edge1_B).cross(plane_B.normal).normalized(); + // make a clip plane + + Plane clip(clip_normal, edge0_B); + // avoid double clip if A is edge + int dst_idx = 0; + bool edge = clipbuf_len == 2; + for (int j = 0; j < clipbuf_len; j++) { + int j_n = (j + 1) % clipbuf_len; + + Vector3 edge0_A = clipbuf_src[j]; + Vector3 edge1_A = clipbuf_src[j_n]; + + real_t dist0 = clip.distance_to(edge0_A); + real_t dist1 = clip.distance_to(edge1_A); + + if (dist0 <= 0) { // behind plane + + ERR_FAIL_COND(dst_idx >= max_clip); + clipbuf_dst[dst_idx++] = clipbuf_src[j]; + } + + // check for different sides and non coplanar + //if ( (dist0*dist1) < -CMP_EPSILON && !(edge && j)) { + if ((dist0 * dist1) < 0 && !(edge && j)) { + // calculate intersection + Vector3 rel = edge1_A - edge0_A; + real_t den = clip.normal.dot(rel); + real_t dist = -(clip.normal.dot(edge0_A) - clip.d) / den; + Vector3 inters = edge0_A + rel * dist; + + ERR_FAIL_COND(dst_idx >= max_clip); + clipbuf_dst[dst_idx] = inters; + dst_idx++; + } + } + + clipbuf_len = dst_idx; + SWAP(clipbuf_src, clipbuf_dst); + } + + // generate contacts + //Plane plane_A(p_points_A[0],p_points_A[1],p_points_A[2]); + + for (int i = 0; i < clipbuf_len; i++) { + real_t d = plane_B.distance_to(clipbuf_src[i]); + + Vector3 closest_B = clipbuf_src[i] - plane_B.normal * d; + + if (p_callback->normal.dot(clipbuf_src[i]) >= p_callback->normal.dot(closest_B)) { + continue; + } + + p_callback->call(clipbuf_src[i], closest_B, plane_B.get_normal()); + } +} + +static void _generate_contacts_face_circle(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A < 3); + ERR_FAIL_COND(p_point_count_B != 3); +#endif + + const Vector3 &circle_B_pos = p_points_B[0]; + Vector3 circle_B_line_1 = p_points_B[1] - circle_B_pos; + Vector3 circle_B_line_2 = p_points_B[2] - circle_B_pos; + + // Clip face with circle segments. + static const int circle_segments = 8; + Vector3 circle_points[circle_segments]; + + real_t angle_delta = 2.0 * Math_PI / circle_segments; + + for (int i = 0; i < circle_segments; ++i) { + Vector3 point_pos = circle_B_pos; + point_pos += circle_B_line_1 * Math::cos(i * angle_delta); + point_pos += circle_B_line_2 * Math::sin(i * angle_delta); + circle_points[i] = point_pos; + } + + _generate_contacts_face_face(p_points_A, p_point_count_A, circle_points, circle_segments, p_callback); + + // Clip face with circle plane. + Vector3 circle_B_normal = circle_B_line_1.cross(circle_B_line_2).normalized(); + + Plane circle_plane(circle_B_normal, circle_B_pos); + + static const int max_clip = 32; + Vector3 contact_points[max_clip]; + int num_points = 0; + + for (int i = 0; i < p_point_count_A; i++) { + int i_n = (i + 1) % p_point_count_A; + + const Vector3 &edge0_A = p_points_A[i]; + const Vector3 &edge1_A = p_points_A[i_n]; + + real_t dist0 = circle_plane.distance_to(edge0_A); + real_t dist1 = circle_plane.distance_to(edge1_A); + + // First point in front of plane, generate contact point. + if (dist0 * circle_plane.d >= 0) { + ERR_FAIL_COND(num_points >= max_clip); + contact_points[num_points] = edge0_A; + ++num_points; + } + + // Points on different sides, generate contact point. + if (dist0 * dist1 < 0) { + // calculate intersection + Vector3 rel = edge1_A - edge0_A; + real_t den = circle_plane.normal.dot(rel); + real_t dist = -(circle_plane.normal.dot(edge0_A) - circle_plane.d) / den; + Vector3 inters = edge0_A + rel * dist; + + ERR_FAIL_COND(num_points >= max_clip); + contact_points[num_points] = inters; + ++num_points; + } + } + + // Generate contact points. + for (int i = 0; i < num_points; i++) { + const Vector3 &contact_point_A = contact_points[i]; + + real_t d = circle_plane.distance_to(contact_point_A); + Vector3 closest_B = contact_point_A - circle_plane.normal * d; + + if (p_callback->normal.dot(contact_point_A) >= p_callback->normal.dot(closest_B)) { + continue; + } + + p_callback->call(contact_point_A, closest_B, circle_plane.get_normal()); + } +} + +static void _generate_contacts_circle_circle(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 3); + ERR_FAIL_COND(p_point_count_B != 3); +#endif + + const Vector3 &circle_A_pos = p_points_A[0]; + Vector3 circle_A_line_1 = p_points_A[1] - circle_A_pos; + Vector3 circle_A_line_2 = p_points_A[2] - circle_A_pos; + + real_t circle_A_radius = circle_A_line_1.length(); + Vector3 circle_A_normal = circle_A_line_1.cross(circle_A_line_2).normalized(); + + const Vector3 &circle_B_pos = p_points_B[0]; + Vector3 circle_B_line_1 = p_points_B[1] - circle_B_pos; + Vector3 circle_B_line_2 = p_points_B[2] - circle_B_pos; + + real_t circle_B_radius = circle_B_line_1.length(); + Vector3 circle_B_normal = circle_B_line_1.cross(circle_B_line_2).normalized(); + + static const int max_clip = 4; + Vector3 contact_points[max_clip]; + int num_points = 0; + + Vector3 centers_diff = circle_B_pos - circle_A_pos; + Vector3 norm_proj = circle_A_normal.dot(centers_diff) * circle_A_normal; + Vector3 comp_proj = centers_diff - norm_proj; + real_t proj_dist = comp_proj.length(); + if (!Math::is_zero_approx(proj_dist)) { + comp_proj /= proj_dist; + if ((proj_dist > circle_A_radius - circle_B_radius) && (proj_dist > circle_B_radius - circle_A_radius)) { + // Circles are overlapping, use the 2 points of intersection as contacts. + real_t radius_a_sqr = circle_A_radius * circle_A_radius; + real_t radius_b_sqr = circle_B_radius * circle_B_radius; + real_t d_sqr = proj_dist * proj_dist; + real_t s = (1.0 + (radius_a_sqr - radius_b_sqr) / d_sqr) * 0.5; + real_t h = Math::sqrt(MAX(radius_a_sqr - d_sqr * s * s, 0.0)); + Vector3 midpoint = circle_A_pos + s * comp_proj * proj_dist; + Vector3 h_vec = h * circle_A_normal.cross(comp_proj); + + Vector3 point_A = midpoint + h_vec; + contact_points[num_points] = point_A; + ++num_points; + + point_A = midpoint - h_vec; + contact_points[num_points] = point_A; + ++num_points; + + // Add 2 points from circle A and B along the line between the centers. + point_A = circle_A_pos + comp_proj * circle_A_radius; + contact_points[num_points] = point_A; + ++num_points; + + point_A = circle_B_pos - comp_proj * circle_B_radius - norm_proj; + contact_points[num_points] = point_A; + ++num_points; + } // Otherwise one circle is inside the other one, use 3 arbitrary equidistant points. + } // Otherwise circles are concentric, use 3 arbitrary equidistant points. + + if (num_points == 0) { + // Generate equidistant points. + if (circle_A_radius < circle_B_radius) { + // Circle A inside circle B. + for (int i = 0; i < 3; ++i) { + Vector3 circle_A_point = circle_A_pos; + circle_A_point += circle_A_line_1 * Math::cos(2.0 * Math_PI * i / 3.0); + circle_A_point += circle_A_line_2 * Math::sin(2.0 * Math_PI * i / 3.0); + + contact_points[num_points] = circle_A_point; + ++num_points; + } + } else { + // Circle B inside circle A. + for (int i = 0; i < 3; ++i) { + Vector3 circle_B_point = circle_B_pos; + circle_B_point += circle_B_line_1 * Math::cos(2.0 * Math_PI * i / 3.0); + circle_B_point += circle_B_line_2 * Math::sin(2.0 * Math_PI * i / 3.0); + + Vector3 circle_A_point = circle_B_point - norm_proj; + + contact_points[num_points] = circle_A_point; + ++num_points; + } + } + } + + Plane circle_B_plane(circle_B_normal, circle_B_pos); + + // Generate contact points. + for (int i = 0; i < num_points; i++) { + const Vector3 &contact_point_A = contact_points[i]; + + real_t d = circle_B_plane.distance_to(contact_point_A); + Vector3 closest_B = contact_point_A - circle_B_plane.normal * d; + + if (p_callback->normal.dot(contact_point_A) >= p_callback->normal.dot(closest_B)) { + continue; + } + + p_callback->call(contact_point_A, closest_B, circle_B_plane.get_normal()); + } +} + +static void _generate_contacts_from_supports(const Vector3 *p_points_A, int p_point_count_A, GodotShape3D::FeatureType p_feature_type_A, const Vector3 *p_points_B, int p_point_count_B, GodotShape3D::FeatureType p_feature_type_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A < 1); + ERR_FAIL_COND(p_point_count_B < 1); +#endif + + static const GenerateContactsFunc generate_contacts_func_table[4][4] = { + { + _generate_contacts_point_point, + _generate_contacts_point_edge, + _generate_contacts_point_face, + _generate_contacts_point_circle, + }, + { + nullptr, + _generate_contacts_edge_edge, + _generate_contacts_face_face, + _generate_contacts_edge_circle, + }, + { + nullptr, + nullptr, + _generate_contacts_face_face, + _generate_contacts_face_circle, + }, + { + nullptr, + nullptr, + nullptr, + _generate_contacts_circle_circle, + }, + }; + + int pointcount_B; + int pointcount_A; + const Vector3 *points_A; + const Vector3 *points_B; + int version_A; + int version_B; + + if (p_feature_type_A > p_feature_type_B) { + //swap + p_callback->swap = !p_callback->swap; + p_callback->normal = -p_callback->normal; + + pointcount_B = p_point_count_A; + pointcount_A = p_point_count_B; + points_A = p_points_B; + points_B = p_points_A; + version_A = p_feature_type_B; + version_B = p_feature_type_A; + } else { + pointcount_B = p_point_count_B; + pointcount_A = p_point_count_A; + points_A = p_points_A; + points_B = p_points_B; + version_A = p_feature_type_A; + version_B = p_feature_type_B; + } + + GenerateContactsFunc contacts_func = generate_contacts_func_table[version_A][version_B]; + ERR_FAIL_NULL(contacts_func); + contacts_func(points_A, pointcount_A, points_B, pointcount_B, p_callback); +} + +template <typename ShapeA, typename ShapeB, bool withMargin = false> +class SeparatorAxisTest { + const ShapeA *shape_A = nullptr; + const ShapeB *shape_B = nullptr; + const Transform3D *transform_A = nullptr; + const Transform3D *transform_B = nullptr; + real_t best_depth = 1e15; + _CollectorCallback *callback = nullptr; + real_t margin_A = 0.0; + real_t margin_B = 0.0; + Vector3 separator_axis; + +public: + Vector3 best_axis; + + _FORCE_INLINE_ bool test_previous_axis() { + if (callback && callback->prev_axis && *callback->prev_axis != Vector3()) { + return test_axis(*callback->prev_axis); + } else { + return true; + } + } + + _FORCE_INLINE_ bool test_axis(const Vector3 &p_axis) { + Vector3 axis = p_axis; + + if (axis.is_zero_approx()) { + // strange case, try an upwards separator + axis = Vector3(0.0, 1.0, 0.0); + } + + real_t min_A = 0.0, max_A = 0.0, min_B = 0.0, max_B = 0.0; + + shape_A->project_range(axis, *transform_A, min_A, max_A); + shape_B->project_range(axis, *transform_B, min_B, max_B); + + if (withMargin) { + min_A -= margin_A; + max_A += margin_A; + min_B -= margin_B; + max_B += margin_B; + } + + min_B -= (max_A - min_A) * 0.5; + max_B += (max_A - min_A) * 0.5; + + min_B -= (min_A + max_A) * 0.5; + max_B -= (min_A + max_A) * 0.5; + + if (min_B > 0.0 || max_B < 0.0) { + separator_axis = axis; + return false; // doesn't contain 0 + } + + //use the smallest depth + + if (min_B < 0.0) { // could be +0.0, we don't want it to become -0.0 + min_B = -min_B; + } + + if (max_B < min_B) { + if (max_B < best_depth) { + best_depth = max_B; + best_axis = axis; + } + } else { + if (min_B < best_depth) { + best_depth = min_B; + best_axis = -axis; // keep it as A axis + } + } + + return true; + } + + static _FORCE_INLINE_ void test_contact_points(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata) { + SeparatorAxisTest<ShapeA, ShapeB, withMargin> *separator = (SeparatorAxisTest<ShapeA, ShapeB, withMargin> *)p_userdata; + Vector3 axis = (p_point_B - p_point_A); + real_t depth = axis.length(); + + // Filter out bogus directions with a threshold and re-testing axis. + if (separator->best_depth - depth > 0.001) { + separator->test_axis(axis / depth); + } + } + + _FORCE_INLINE_ void generate_contacts() { + // nothing to do, don't generate + if (best_axis == Vector3(0.0, 0.0, 0.0)) { + return; + } + + if (!callback->callback) { + //just was checking intersection? + callback->collided = true; + if (callback->prev_axis) { + *callback->prev_axis = best_axis; + } + return; + } + + static const int max_supports = 16; + + Vector3 supports_A[max_supports]; + int support_count_A; + GodotShape3D::FeatureType support_type_A; + shape_A->get_supports(transform_A->basis.xform_inv(-best_axis).normalized(), max_supports, supports_A, support_count_A, support_type_A); + for (int i = 0; i < support_count_A; i++) { + supports_A[i] = transform_A->xform(supports_A[i]); + } + + if (withMargin) { + for (int i = 0; i < support_count_A; i++) { + supports_A[i] += -best_axis * margin_A; + } + } + + Vector3 supports_B[max_supports]; + int support_count_B; + GodotShape3D::FeatureType support_type_B; + shape_B->get_supports(transform_B->basis.xform_inv(best_axis).normalized(), max_supports, supports_B, support_count_B, support_type_B); + for (int i = 0; i < support_count_B; i++) { + supports_B[i] = transform_B->xform(supports_B[i]); + } + + if (withMargin) { + for (int i = 0; i < support_count_B; i++) { + supports_B[i] += best_axis * margin_B; + } + } + + callback->normal = best_axis; + if (callback->prev_axis) { + *callback->prev_axis = best_axis; + } + _generate_contacts_from_supports(supports_A, support_count_A, support_type_A, supports_B, support_count_B, support_type_B, callback); + + callback->collided = true; + } + + _FORCE_INLINE_ SeparatorAxisTest(const ShapeA *p_shape_A, const Transform3D &p_transform_A, const ShapeB *p_shape_B, const Transform3D &p_transform_B, _CollectorCallback *p_callback, real_t p_margin_A = 0, real_t p_margin_B = 0) { + shape_A = p_shape_A; + shape_B = p_shape_B; + transform_A = &p_transform_A; + transform_B = &p_transform_B; + callback = p_callback; + margin_A = p_margin_A; + margin_B = p_margin_B; + } +}; + +/****** SAT TESTS *******/ + +typedef void (*CollisionFunc)(const GodotShape3D *, const Transform3D &, const GodotShape3D *, const Transform3D &, _CollectorCallback *p_callback, real_t, real_t); + +// Perform analytic sphere-sphere collision and report results to collector +template <bool withMargin> +static void analytic_sphere_collision(const Vector3 &p_origin_a, real_t p_radius_a, const Vector3 &p_origin_b, real_t p_radius_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + // Expand the spheres by the margins if enabled + if (withMargin) { + p_radius_a += p_margin_a; + p_radius_b += p_margin_b; + } + + // Get the vector from sphere B to A + Vector3 b_to_a = p_origin_a - p_origin_b; + + // Get the length from B to A + real_t b_to_a_len = b_to_a.length(); + + // Calculate the sphere overlap, and bail if not overlapping + real_t overlap = p_radius_a + p_radius_b - b_to_a_len; + if (overlap < 0) + return; + + // Report collision + p_collector->collided = true; + + // Bail if there is no callback to receive the A and B collision points. + if (!p_collector->callback) { + return; + } + + // Normalize the B to A vector + if (b_to_a_len < CMP_EPSILON) { + b_to_a = Vector3(0, 1, 0); // Spheres coincident, use arbitrary direction + } else { + b_to_a /= b_to_a_len; + } + + // Report collision points. The operations below are intended to minimize + // floating-point precision errors. This is done by calculating the first + // collision point from the smaller sphere, and then jumping across to + // the larger spheres collision point using the overlap distance. This + // jump is usually small even if the large sphere is massive, and so the + // second point will not suffer from precision errors. + if (p_radius_a < p_radius_b) { + Vector3 point_a = p_origin_a - b_to_a * p_radius_a; + Vector3 point_b = point_a + b_to_a * overlap; + p_collector->call(point_a, point_b, b_to_a); // Consider adding b_to_a vector + } else { + Vector3 point_b = p_origin_b + b_to_a * p_radius_b; + Vector3 point_a = point_b - b_to_a * overlap; + p_collector->call(point_a, point_b, b_to_a); // Consider adding b_to_a vector + } +} + +template <bool withMargin> +static void _collision_sphere_sphere(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotSphereShape3D *sphere_A = static_cast<const GodotSphereShape3D *>(p_a); + const GodotSphereShape3D *sphere_B = static_cast<const GodotSphereShape3D *>(p_b); + + // Perform an analytic sphere collision between the two spheres + analytic_sphere_collision<withMargin>( + p_transform_a.origin, + sphere_A->get_radius() * p_transform_a.basis[0].length(), + p_transform_b.origin, + sphere_B->get_radius() * p_transform_b.basis[0].length(), + p_collector, + p_margin_a, + p_margin_b); +} + +template <bool withMargin> +static void _collision_sphere_box(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotSphereShape3D *sphere_A = static_cast<const GodotSphereShape3D *>(p_a); + const GodotBoxShape3D *box_B = static_cast<const GodotBoxShape3D *>(p_b); + + // Find the point on the box nearest to the center of the sphere. + + Vector3 center = p_transform_b.affine_inverse().xform(p_transform_a.origin); + Vector3 extents = box_B->get_half_extents(); + Vector3 nearest(MIN(MAX(center.x, -extents.x), extents.x), + MIN(MAX(center.y, -extents.y), extents.y), + MIN(MAX(center.z, -extents.z), extents.z)); + nearest = p_transform_b.xform(nearest); + + // See if it is inside the sphere. + + Vector3 delta = nearest - p_transform_a.origin; + real_t length = delta.length(); + real_t radius = sphere_A->get_radius() * p_transform_a.basis[0].length(); + if (length > radius + p_margin_a + p_margin_b) { + return; + } + p_collector->collided = true; + if (!p_collector->callback) { + return; + } + Vector3 axis; + if (length == 0) { + // The box passes through the sphere center. Select an axis based on the box's center. + axis = (p_transform_b.origin - nearest).normalized(); + } else { + axis = delta / length; + } + Vector3 point_a = p_transform_a.origin + (radius + p_margin_a) * axis; + Vector3 point_b = (withMargin ? nearest - p_margin_b * axis : nearest); + p_collector->call(point_a, point_b, axis); +} + +template <bool withMargin> +static void _collision_sphere_capsule(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotSphereShape3D *sphere_A = static_cast<const GodotSphereShape3D *>(p_a); + const GodotCapsuleShape3D *capsule_B = static_cast<const GodotCapsuleShape3D *>(p_b); + + real_t scale_A = p_transform_a.basis[0].length(); + real_t scale_B = p_transform_b.basis[0].length(); + + // Construct the capsule segment (ball-center to ball-center) + Vector3 capsule_segment[2]; + Vector3 capsule_axis = p_transform_b.basis.get_column(1) * (capsule_B->get_height() * 0.5 - capsule_B->get_radius()); + capsule_segment[0] = p_transform_b.origin + capsule_axis; + capsule_segment[1] = p_transform_b.origin - capsule_axis; + + // Get the capsules closest segment-point to the sphere + Vector3 capsule_closest = Geometry3D::get_closest_point_to_segment(p_transform_a.origin, capsule_segment); + + // Perform an analytic sphere collision between the sphere and the sphere-collider in the capsule + analytic_sphere_collision<withMargin>( + p_transform_a.origin, + sphere_A->get_radius() * scale_A, + capsule_closest, + capsule_B->get_radius() * scale_B, + p_collector, + p_margin_a, + p_margin_b); +} + +template <bool withMargin> +static void analytic_sphere_cylinder_collision(real_t p_radius_a, real_t p_radius_b, real_t p_height_b, const Transform3D &p_transform_a, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + // Find the point on the cylinder nearest to the center of the sphere. + + Vector3 center = p_transform_b.affine_inverse().xform(p_transform_a.origin); + Vector3 nearest = center; + real_t scale_A = p_transform_a.basis[0].length(); + real_t r = Math::sqrt(center.x * center.x + center.z * center.z); + if (r > p_radius_b) { + real_t scale = p_radius_b / r; + nearest.x *= scale; + nearest.z *= scale; + } + real_t half_height = p_height_b / 2; + nearest.y = MIN(MAX(center.y, -half_height), half_height); + nearest = p_transform_b.xform(nearest); + + // See if it is inside the sphere. + + Vector3 delta = nearest - p_transform_a.origin; + real_t length = delta.length(); + if (length > p_radius_a * scale_A + p_margin_a + p_margin_b) { + return; + } + p_collector->collided = true; + if (!p_collector->callback) { + return; + } + Vector3 axis; + if (length == 0) { + // The cylinder passes through the sphere center. Select an axis based on the cylinder's center. + axis = (p_transform_b.origin - nearest).normalized(); + } else { + axis = delta / length; + } + Vector3 point_a = p_transform_a.origin + (p_radius_a * scale_A + p_margin_a) * axis; + Vector3 point_b = (withMargin ? nearest - p_margin_b * axis : nearest); + p_collector->call(point_a, point_b, axis); +} + +template <bool withMargin> +static void _collision_sphere_cylinder(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotSphereShape3D *sphere_A = static_cast<const GodotSphereShape3D *>(p_a); + const GodotCylinderShape3D *cylinder_B = static_cast<const GodotCylinderShape3D *>(p_b); + + analytic_sphere_cylinder_collision<withMargin>(sphere_A->get_radius(), cylinder_B->get_radius(), cylinder_B->get_height(), p_transform_a, p_transform_b, p_collector, p_margin_a, p_margin_b); +} + +template <bool withMargin> +static void _collision_sphere_convex_polygon(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotSphereShape3D *sphere_A = static_cast<const GodotSphereShape3D *>(p_a); + const GodotConvexPolygonShape3D *convex_polygon_B = static_cast<const GodotConvexPolygonShape3D *>(p_b); + + SeparatorAxisTest<GodotSphereShape3D, GodotConvexPolygonShape3D, withMargin> separator(sphere_A, p_transform_a, convex_polygon_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + const Geometry3D::MeshData &mesh = convex_polygon_B->get_mesh(); + + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int face_count = mesh.faces.size(); + const Geometry3D::MeshData::Edge *edges = mesh.edges.ptr(); + int edge_count = mesh.edges.size(); + const Vector3 *vertices = mesh.vertices.ptr(); + int vertex_count = mesh.vertices.size(); + + // Precalculating this makes the transforms faster. + Basis b_xform_normal = p_transform_b.basis.inverse().transposed(); + + // faces of B + for (int i = 0; i < face_count; i++) { + Vector3 axis = b_xform_normal.xform(faces[i].plane.normal).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // edges of B + for (int i = 0; i < edge_count; i++) { + Vector3 v1 = p_transform_b.xform(vertices[edges[i].vertex_a]); + Vector3 v2 = p_transform_b.xform(vertices[edges[i].vertex_b]); + Vector3 v3 = p_transform_a.origin; + + Vector3 n1 = v2 - v1; + Vector3 n2 = v2 - v3; + + Vector3 axis = n1.cross(n2).cross(n1).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // vertices of B + for (int i = 0; i < vertex_count; i++) { + Vector3 v1 = p_transform_b.xform(vertices[i]); + Vector3 v2 = p_transform_a.origin; + + Vector3 axis = (v2 - v1).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_sphere_face(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotSphereShape3D *sphere_A = static_cast<const GodotSphereShape3D *>(p_a); + const GodotFaceShape3D *face_B = static_cast<const GodotFaceShape3D *>(p_b); + + SeparatorAxisTest<GodotSphereShape3D, GodotFaceShape3D, withMargin> separator(sphere_A, p_transform_a, face_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + Vector3 vertex[3] = { + p_transform_b.xform(face_B->vertex[0]), + p_transform_b.xform(face_B->vertex[1]), + p_transform_b.xform(face_B->vertex[2]), + }; + + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal)) { + return; + } + + // edges and points of B + for (int i = 0; i < 3; i++) { + Vector3 n1 = vertex[i] - p_transform_a.origin; + if (n1.dot(normal) < 0.0) { + n1 *= -1.0; + } + + if (!separator.test_axis(n1.normalized())) { + return; + } + + Vector3 n2 = vertex[(i + 1) % 3] - vertex[i]; + + Vector3 axis = n1.cross(n2).cross(n2).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + + if (!face_B->backface_collision) { + if (separator.best_axis.dot(normal) < _BACKFACE_NORMAL_THRESHOLD) { + if (face_B->invert_backface_collision) { + separator.best_axis = separator.best_axis.bounce(normal); + } else { + // Just ignore backface collision. + return; + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_box_box(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotBoxShape3D *box_A = static_cast<const GodotBoxShape3D *>(p_a); + const GodotBoxShape3D *box_B = static_cast<const GodotBoxShape3D *>(p_b); + + SeparatorAxisTest<GodotBoxShape3D, GodotBoxShape3D, withMargin> separator(box_A, p_transform_a, box_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + // test faces of A + + for (int i = 0; i < 3; i++) { + Vector3 axis = p_transform_a.basis.get_column(i).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // test faces of B + + for (int i = 0; i < 3; i++) { + Vector3 axis = p_transform_b.basis.get_column(i).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // test combined edges + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Vector3 axis = p_transform_a.basis.get_column(i).cross(p_transform_b.basis.get_column(j)); + + if (Math::is_zero_approx(axis.length_squared())) { + continue; + } + axis.normalize(); + + if (!separator.test_axis(axis)) { + return; + } + } + } + + if (withMargin) { + //add endpoint test between closest vertices and edges + + // calculate closest point to sphere + + Vector3 ab_vec = p_transform_b.origin - p_transform_a.origin; + + Vector3 cnormal_a = p_transform_a.basis.xform_inv(ab_vec); + + Vector3 support_a = p_transform_a.xform(Vector3( + + (cnormal_a.x < 0) ? -box_A->get_half_extents().x : box_A->get_half_extents().x, + (cnormal_a.y < 0) ? -box_A->get_half_extents().y : box_A->get_half_extents().y, + (cnormal_a.z < 0) ? -box_A->get_half_extents().z : box_A->get_half_extents().z)); + + Vector3 cnormal_b = p_transform_b.basis.xform_inv(-ab_vec); + + Vector3 support_b = p_transform_b.xform(Vector3( + + (cnormal_b.x < 0) ? -box_B->get_half_extents().x : box_B->get_half_extents().x, + (cnormal_b.y < 0) ? -box_B->get_half_extents().y : box_B->get_half_extents().y, + (cnormal_b.z < 0) ? -box_B->get_half_extents().z : box_B->get_half_extents().z)); + + Vector3 axis_ab = (support_a - support_b); + + if (!separator.test_axis(axis_ab.normalized())) { + return; + } + + //now try edges, which become cylinders! + + for (int i = 0; i < 3; i++) { + //a ->b + Vector3 axis_a = p_transform_a.basis.get_column(i); + + if (!separator.test_axis(axis_ab.cross(axis_a).cross(axis_a).normalized())) { + return; + } + + //b ->a + Vector3 axis_b = p_transform_b.basis.get_column(i); + + if (!separator.test_axis(axis_ab.cross(axis_b).cross(axis_b).normalized())) { + return; + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_box_capsule(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotBoxShape3D *box_A = static_cast<const GodotBoxShape3D *>(p_a); + const GodotCapsuleShape3D *capsule_B = static_cast<const GodotCapsuleShape3D *>(p_b); + + SeparatorAxisTest<GodotBoxShape3D, GodotCapsuleShape3D, withMargin> separator(box_A, p_transform_a, capsule_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + // faces of A + for (int i = 0; i < 3; i++) { + Vector3 axis = p_transform_a.basis.get_column(i).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + Vector3 cyl_axis = p_transform_b.basis.get_column(1).normalized(); + + // edges of A, capsule cylinder + + for (int i = 0; i < 3; i++) { + // cylinder + Vector3 box_axis = p_transform_a.basis.get_column(i); + Vector3 axis = box_axis.cross(cyl_axis); + if (Math::is_zero_approx(axis.length_squared())) { + continue; + } + + if (!separator.test_axis(axis.normalized())) { + return; + } + } + + // points of A, capsule cylinder + // this sure could be made faster somehow.. + + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 2; k++) { + Vector3 he = box_A->get_half_extents(); + he.x *= (i * 2 - 1); + he.y *= (j * 2 - 1); + he.z *= (k * 2 - 1); + Vector3 point = p_transform_a.origin; + for (int l = 0; l < 3; l++) { + point += p_transform_a.basis.get_column(l) * he[l]; + } + + //Vector3 axis = (point - cyl_axis * cyl_axis.dot(point)).normalized(); + Vector3 axis = Plane(cyl_axis).project(point).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + } + } + + // capsule balls, edges of A + + for (int i = 0; i < 2; i++) { + Vector3 capsule_axis = p_transform_b.basis.get_column(1) * (capsule_B->get_height() * 0.5 - capsule_B->get_radius()); + + Vector3 sphere_pos = p_transform_b.origin + ((i == 0) ? capsule_axis : -capsule_axis); + + Vector3 cnormal = p_transform_a.xform_inv(sphere_pos); + + Vector3 cpoint = p_transform_a.xform(Vector3( + + (cnormal.x < 0) ? -box_A->get_half_extents().x : box_A->get_half_extents().x, + (cnormal.y < 0) ? -box_A->get_half_extents().y : box_A->get_half_extents().y, + (cnormal.z < 0) ? -box_A->get_half_extents().z : box_A->get_half_extents().z)); + + // use point to test axis + Vector3 point_axis = (sphere_pos - cpoint).normalized(); + + if (!separator.test_axis(point_axis)) { + return; + } + + // test edges of A + + for (int j = 0; j < 3; j++) { + Vector3 axis = point_axis.cross(p_transform_a.basis.get_column(j)).cross(p_transform_a.basis.get_column(j)).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_box_cylinder(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotBoxShape3D *box_A = static_cast<const GodotBoxShape3D *>(p_a); + const GodotCylinderShape3D *cylinder_B = static_cast<const GodotCylinderShape3D *>(p_b); + + SeparatorAxisTest<GodotBoxShape3D, GodotCylinderShape3D, withMargin> separator(box_A, p_transform_a, cylinder_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + // Faces of A. + for (int i = 0; i < 3; i++) { + Vector3 axis = p_transform_a.basis.get_column(i).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + Vector3 cyl_axis = p_transform_b.basis.get_column(1).normalized(); + + // Cylinder end caps. + { + if (!separator.test_axis(cyl_axis)) { + return; + } + } + + // Edges of A, cylinder lateral surface. + for (int i = 0; i < 3; i++) { + Vector3 box_axis = p_transform_a.basis.get_column(i); + Vector3 axis = box_axis.cross(cyl_axis); + if (Math::is_zero_approx(axis.length_squared())) { + continue; + } + + if (!separator.test_axis(axis.normalized())) { + return; + } + } + + // Gather points of A. + Vector3 vertices_A[8]; + Vector3 box_extent = box_A->get_half_extents(); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 2; k++) { + Vector3 extent = box_extent; + extent.x *= (i * 2 - 1); + extent.y *= (j * 2 - 1); + extent.z *= (k * 2 - 1); + Vector3 &point = vertices_A[i * 2 * 2 + j * 2 + k]; + point = p_transform_a.origin; + for (int l = 0; l < 3; l++) { + point += p_transform_a.basis.get_column(l) * extent[l]; + } + } + } + } + + // Points of A, cylinder lateral surface. + for (int i = 0; i < 8; i++) { + const Vector3 &point = vertices_A[i]; + Vector3 axis = Plane(cyl_axis).project(point).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // Edges of A, cylinder end caps rim. + int edges_start_A[12] = { 0, 2, 4, 6, 0, 1, 4, 5, 0, 1, 2, 3 }; + int edges_end_A[12] = { 1, 3, 5, 7, 2, 3, 6, 7, 4, 5, 6, 7 }; + + Vector3 cap_axis = cyl_axis * (cylinder_B->get_height() * 0.5); + + for (int i = 0; i < 2; i++) { + Vector3 cap_pos = p_transform_b.origin + ((i == 0) ? cap_axis : -cap_axis); + + for (int e = 0; e < 12; e++) { + const Vector3 &edge_start = vertices_A[edges_start_A[e]]; + const Vector3 &edge_end = vertices_A[edges_end_A[e]]; + + Vector3 edge_dir = (edge_end - edge_start); + edge_dir.normalize(); + + real_t edge_dot = edge_dir.dot(cyl_axis); + if (Math::is_zero_approx(edge_dot)) { + // Edge is perpendicular to cylinder axis. + continue; + } + + // Calculate intersection between edge and circle plane. + Vector3 edge_diff = cap_pos - edge_start; + real_t diff_dot = edge_diff.dot(cyl_axis); + Vector3 intersection = edge_start + edge_dir * diff_dot / edge_dot; + + // Calculate tangent that touches intersection. + Vector3 tangent = (cap_pos - intersection).cross(cyl_axis); + + // Axis is orthogonal both to tangent and edge direction. + Vector3 axis = tangent.cross(edge_dir); + + if (!separator.test_axis(axis.normalized())) { + return; + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_box_convex_polygon(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotBoxShape3D *box_A = static_cast<const GodotBoxShape3D *>(p_a); + const GodotConvexPolygonShape3D *convex_polygon_B = static_cast<const GodotConvexPolygonShape3D *>(p_b); + + SeparatorAxisTest<GodotBoxShape3D, GodotConvexPolygonShape3D, withMargin> separator(box_A, p_transform_a, convex_polygon_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + const Geometry3D::MeshData &mesh = convex_polygon_B->get_mesh(); + + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int face_count = mesh.faces.size(); + const Geometry3D::MeshData::Edge *edges = mesh.edges.ptr(); + int edge_count = mesh.edges.size(); + const Vector3 *vertices = mesh.vertices.ptr(); + int vertex_count = mesh.vertices.size(); + + // faces of A + for (int i = 0; i < 3; i++) { + Vector3 axis = p_transform_a.basis.get_column(i).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // Precalculating this makes the transforms faster. + Basis b_xform_normal = p_transform_b.basis.inverse().transposed(); + + // faces of B + for (int i = 0; i < face_count; i++) { + Vector3 axis = b_xform_normal.xform(faces[i].plane.normal).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // A<->B edges + for (int i = 0; i < 3; i++) { + Vector3 e1 = p_transform_a.basis.get_column(i); + + for (int j = 0; j < edge_count; j++) { + Vector3 e2 = p_transform_b.basis.xform(vertices[edges[j].vertex_a]) - p_transform_b.basis.xform(vertices[edges[j].vertex_b]); + + Vector3 axis = e1.cross(e2).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + } + + if (withMargin) { + // calculate closest points between vertices and box edges + for (int v = 0; v < vertex_count; v++) { + Vector3 vtxb = p_transform_b.xform(vertices[v]); + Vector3 ab_vec = vtxb - p_transform_a.origin; + + Vector3 cnormal_a = p_transform_a.basis.xform_inv(ab_vec); + + Vector3 support_a = p_transform_a.xform(Vector3( + + (cnormal_a.x < 0) ? -box_A->get_half_extents().x : box_A->get_half_extents().x, + (cnormal_a.y < 0) ? -box_A->get_half_extents().y : box_A->get_half_extents().y, + (cnormal_a.z < 0) ? -box_A->get_half_extents().z : box_A->get_half_extents().z)); + + Vector3 axis_ab = support_a - vtxb; + + if (!separator.test_axis(axis_ab.normalized())) { + return; + } + + //now try edges, which become cylinders! + + for (int i = 0; i < 3; i++) { + //a ->b + Vector3 axis_a = p_transform_a.basis.get_column(i); + + if (!separator.test_axis(axis_ab.cross(axis_a).cross(axis_a).normalized())) { + return; + } + } + } + + //convex edges and box points + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 2; k++) { + Vector3 he = box_A->get_half_extents(); + he.x *= (i * 2 - 1); + he.y *= (j * 2 - 1); + he.z *= (k * 2 - 1); + Vector3 point = p_transform_a.origin; + for (int l = 0; l < 3; l++) { + point += p_transform_a.basis.get_column(l) * he[l]; + } + + for (int e = 0; e < edge_count; e++) { + Vector3 p1 = p_transform_b.xform(vertices[edges[e].vertex_a]); + Vector3 p2 = p_transform_b.xform(vertices[edges[e].vertex_b]); + Vector3 n = (p2 - p1); + + if (!separator.test_axis((point - p2).cross(n).cross(n).normalized())) { + return; + } + } + } + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_box_face(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotBoxShape3D *box_A = static_cast<const GodotBoxShape3D *>(p_a); + const GodotFaceShape3D *face_B = static_cast<const GodotFaceShape3D *>(p_b); + + SeparatorAxisTest<GodotBoxShape3D, GodotFaceShape3D, withMargin> separator(box_A, p_transform_a, face_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + Vector3 vertex[3] = { + p_transform_b.xform(face_B->vertex[0]), + p_transform_b.xform(face_B->vertex[1]), + p_transform_b.xform(face_B->vertex[2]), + }; + + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal)) { + return; + } + + // faces of A + for (int i = 0; i < 3; i++) { + Vector3 axis = p_transform_a.basis.get_column(i).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + + // combined edges + + for (int i = 0; i < 3; i++) { + Vector3 e = vertex[i] - vertex[(i + 1) % 3]; + + for (int j = 0; j < 3; j++) { + Vector3 axis = e.cross(p_transform_a.basis.get_column(j)).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + } + + if (withMargin) { + // calculate closest points between vertices and box edges + for (int v = 0; v < 3; v++) { + Vector3 ab_vec = vertex[v] - p_transform_a.origin; + + Vector3 cnormal_a = p_transform_a.basis.xform_inv(ab_vec); + + Vector3 support_a = p_transform_a.xform(Vector3( + + (cnormal_a.x < 0) ? -box_A->get_half_extents().x : box_A->get_half_extents().x, + (cnormal_a.y < 0) ? -box_A->get_half_extents().y : box_A->get_half_extents().y, + (cnormal_a.z < 0) ? -box_A->get_half_extents().z : box_A->get_half_extents().z)); + + Vector3 axis_ab = support_a - vertex[v]; + if (axis_ab.dot(normal) < 0.0) { + axis_ab *= -1.0; + } + + if (!separator.test_axis(axis_ab.normalized())) { + return; + } + + //now try edges, which become cylinders! + + for (int i = 0; i < 3; i++) { + //a ->b + Vector3 axis_a = p_transform_a.basis.get_column(i); + + Vector3 axis = axis_ab.cross(axis_a).cross(axis_a).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + } + + //convex edges and box points, there has to be a way to speed up this (get closest point?) + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 2; k++) { + Vector3 he = box_A->get_half_extents(); + he.x *= (i * 2 - 1); + he.y *= (j * 2 - 1); + he.z *= (k * 2 - 1); + Vector3 point = p_transform_a.origin; + for (int l = 0; l < 3; l++) { + point += p_transform_a.basis.get_column(l) * he[l]; + } + + for (int e = 0; e < 3; e++) { + Vector3 p1 = vertex[e]; + Vector3 p2 = vertex[(e + 1) % 3]; + + Vector3 n = (p2 - p1); + + Vector3 axis = (point - p2).cross(n).cross(n).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + } + } + } + } + + if (!face_B->backface_collision) { + if (separator.best_axis.dot(normal) < _BACKFACE_NORMAL_THRESHOLD) { + if (face_B->invert_backface_collision) { + separator.best_axis = separator.best_axis.bounce(normal); + } else { + // Just ignore backface collision. + return; + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_capsule_capsule(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotCapsuleShape3D *capsule_A = static_cast<const GodotCapsuleShape3D *>(p_a); + const GodotCapsuleShape3D *capsule_B = static_cast<const GodotCapsuleShape3D *>(p_b); + + real_t scale_A = p_transform_a.basis[0].length(); + real_t scale_B = p_transform_b.basis[0].length(); + + // Get the closest points between the capsule segments + Vector3 capsule_A_closest; + Vector3 capsule_B_closest; + Vector3 capsule_A_axis = p_transform_a.basis.get_column(1) * (capsule_A->get_height() * 0.5 - capsule_A->get_radius()); + Vector3 capsule_B_axis = p_transform_b.basis.get_column(1) * (capsule_B->get_height() * 0.5 - capsule_B->get_radius()); + Geometry3D::get_closest_points_between_segments( + p_transform_a.origin + capsule_A_axis, + p_transform_a.origin - capsule_A_axis, + p_transform_b.origin + capsule_B_axis, + p_transform_b.origin - capsule_B_axis, + capsule_A_closest, + capsule_B_closest); + + // Perform the analytic collision between the two closest capsule spheres + analytic_sphere_collision<withMargin>( + capsule_A_closest, + capsule_A->get_radius() * scale_A, + capsule_B_closest, + capsule_B->get_radius() * scale_B, + p_collector, + p_margin_a, + p_margin_b); +} + +template <bool withMargin> +static void _collision_capsule_cylinder(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotCapsuleShape3D *capsule_A = static_cast<const GodotCapsuleShape3D *>(p_a); + const GodotCylinderShape3D *cylinder_B = static_cast<const GodotCylinderShape3D *>(p_b); + + // Find the closest points between the axes of the two objects. + + Vector3 capsule_A_closest; + Vector3 cylinder_B_closest; + Vector3 capsule_A_axis = p_transform_a.basis.get_column(1) * (capsule_A->get_height() * 0.5 - capsule_A->get_radius()); + Vector3 cylinder_B_axis = p_transform_b.basis.get_column(1) * (cylinder_B->get_height() * 0.5); + Geometry3D::get_closest_points_between_segments( + p_transform_a.origin + capsule_A_axis, + p_transform_a.origin - capsule_A_axis, + p_transform_b.origin + cylinder_B_axis, + p_transform_b.origin - cylinder_B_axis, + capsule_A_closest, + cylinder_B_closest); + + // Perform the collision test between the cylinder and the nearest sphere on the capsule axis. + + Transform3D sphere_transform(p_transform_a.basis, capsule_A_closest); + analytic_sphere_cylinder_collision<withMargin>(capsule_A->get_radius(), cylinder_B->get_radius(), cylinder_B->get_height(), sphere_transform, p_transform_b, p_collector, p_margin_a, p_margin_b); +} + +template <bool withMargin> +static void _collision_capsule_convex_polygon(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotCapsuleShape3D *capsule_A = static_cast<const GodotCapsuleShape3D *>(p_a); + const GodotConvexPolygonShape3D *convex_polygon_B = static_cast<const GodotConvexPolygonShape3D *>(p_b); + + SeparatorAxisTest<GodotCapsuleShape3D, GodotConvexPolygonShape3D, withMargin> separator(capsule_A, p_transform_a, convex_polygon_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + const Geometry3D::MeshData &mesh = convex_polygon_B->get_mesh(); + + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int face_count = mesh.faces.size(); + const Geometry3D::MeshData::Edge *edges = mesh.edges.ptr(); + int edge_count = mesh.edges.size(); + const Vector3 *vertices = mesh.vertices.ptr(); + + // Precalculating this makes the transforms faster. + Basis b_xform_normal = p_transform_b.basis.inverse().transposed(); + + // faces of B + for (int i = 0; i < face_count; i++) { + Vector3 axis = b_xform_normal.xform(faces[i].plane.normal).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // edges of B, capsule cylinder + + for (int i = 0; i < edge_count; i++) { + // cylinder + Vector3 edge_axis = p_transform_b.basis.xform(vertices[edges[i].vertex_a]) - p_transform_b.basis.xform(vertices[edges[i].vertex_b]); + Vector3 axis = edge_axis.cross(p_transform_a.basis.get_column(1)).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // capsule balls, edges of B + + for (int i = 0; i < 2; i++) { + // edges of B, capsule cylinder + + Vector3 capsule_axis = p_transform_a.basis.get_column(1) * (capsule_A->get_height() * 0.5 - capsule_A->get_radius()); + + Vector3 sphere_pos = p_transform_a.origin + ((i == 0) ? capsule_axis : -capsule_axis); + + for (int j = 0; j < edge_count; j++) { + Vector3 n1 = sphere_pos - p_transform_b.xform(vertices[edges[j].vertex_a]); + Vector3 n2 = p_transform_b.basis.xform(vertices[edges[j].vertex_a]) - p_transform_b.basis.xform(vertices[edges[j].vertex_b]); + + Vector3 axis = n1.cross(n2).cross(n2).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_capsule_face(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotCapsuleShape3D *capsule_A = static_cast<const GodotCapsuleShape3D *>(p_a); + const GodotFaceShape3D *face_B = static_cast<const GodotFaceShape3D *>(p_b); + + SeparatorAxisTest<GodotCapsuleShape3D, GodotFaceShape3D, withMargin> separator(capsule_A, p_transform_a, face_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + Vector3 vertex[3] = { + p_transform_b.xform(face_B->vertex[0]), + p_transform_b.xform(face_B->vertex[1]), + p_transform_b.xform(face_B->vertex[2]), + }; + + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal)) { + return; + } + + // edges of B, capsule cylinder + + Vector3 capsule_axis = p_transform_a.basis.get_column(1) * (capsule_A->get_height() * 0.5 - capsule_A->get_radius()); + + for (int i = 0; i < 3; i++) { + // edge-cylinder + Vector3 edge_axis = vertex[i] - vertex[(i + 1) % 3]; + + Vector3 axis = edge_axis.cross(capsule_axis).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + + Vector3 dir_axis = (p_transform_a.origin - vertex[i]).cross(capsule_axis).cross(capsule_axis).normalized(); + if (dir_axis.dot(normal) < 0.0) { + dir_axis *= -1.0; + } + + if (!separator.test_axis(dir_axis)) { + return; + } + + for (int j = 0; j < 2; j++) { + // point-spheres + Vector3 sphere_pos = p_transform_a.origin + ((j == 0) ? capsule_axis : -capsule_axis); + + Vector3 n1 = sphere_pos - vertex[i]; + if (n1.dot(normal) < 0.0) { + n1 *= -1.0; + } + + if (!separator.test_axis(n1.normalized())) { + return; + } + + Vector3 n2 = edge_axis; + + axis = n1.cross(n2).cross(n2); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis.normalized())) { + return; + } + } + } + + if (!face_B->backface_collision) { + if (separator.best_axis.dot(normal) < _BACKFACE_NORMAL_THRESHOLD) { + if (face_B->invert_backface_collision) { + separator.best_axis = separator.best_axis.bounce(normal); + } else { + // Just ignore backface collision. + return; + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_cylinder_cylinder(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotCylinderShape3D *cylinder_A = static_cast<const GodotCylinderShape3D *>(p_a); + const GodotCylinderShape3D *cylinder_B = static_cast<const GodotCylinderShape3D *>(p_b); + + SeparatorAxisTest<GodotCylinderShape3D, GodotCylinderShape3D, withMargin> separator(cylinder_A, p_transform_a, cylinder_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + Vector3 cylinder_A_axis = p_transform_a.basis.get_column(1); + Vector3 cylinder_B_axis = p_transform_b.basis.get_column(1); + + if (!separator.test_previous_axis()) { + return; + } + + // Cylinder A end caps. + if (!separator.test_axis(cylinder_A_axis.normalized())) { + return; + } + + // Cylinder B end caps. + if (!separator.test_axis(cylinder_B_axis.normalized())) { + return; + } + + Vector3 cylinder_diff = p_transform_b.origin - p_transform_a.origin; + + // Cylinder A lateral surface. + if (!separator.test_axis(cylinder_A_axis.cross(cylinder_diff).cross(cylinder_A_axis).normalized())) { + return; + } + + // Cylinder B lateral surface. + if (!separator.test_axis(cylinder_B_axis.cross(cylinder_diff).cross(cylinder_B_axis).normalized())) { + return; + } + + real_t proj = cylinder_A_axis.cross(cylinder_B_axis).cross(cylinder_B_axis).dot(cylinder_A_axis); + if (Math::is_zero_approx(proj)) { + // Parallel cylinders, handle with specific axes only. + // Note: GJKEPA with no margin can lead to degenerate cases in this situation. + separator.generate_contacts(); + return; + } + + GodotCollisionSolver3D::CallbackResult callback = SeparatorAxisTest<GodotCylinderShape3D, GodotCylinderShape3D, withMargin>::test_contact_points; + + // Fallback to generic algorithm to find the best separating axis. + if (!fallback_collision_solver(p_a, p_transform_a, p_b, p_transform_b, callback, &separator, false, p_margin_a, p_margin_b)) { + return; + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_cylinder_convex_polygon(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotCylinderShape3D *cylinder_A = static_cast<const GodotCylinderShape3D *>(p_a); + const GodotConvexPolygonShape3D *convex_polygon_B = static_cast<const GodotConvexPolygonShape3D *>(p_b); + + SeparatorAxisTest<GodotCylinderShape3D, GodotConvexPolygonShape3D, withMargin> separator(cylinder_A, p_transform_a, convex_polygon_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + GodotCollisionSolver3D::CallbackResult callback = SeparatorAxisTest<GodotCylinderShape3D, GodotConvexPolygonShape3D, withMargin>::test_contact_points; + + // Fallback to generic algorithm to find the best separating axis. + if (!fallback_collision_solver(p_a, p_transform_a, p_b, p_transform_b, callback, &separator, false, p_margin_a, p_margin_b)) { + return; + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_cylinder_face(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotCylinderShape3D *cylinder_A = static_cast<const GodotCylinderShape3D *>(p_a); + const GodotFaceShape3D *face_B = static_cast<const GodotFaceShape3D *>(p_b); + + SeparatorAxisTest<GodotCylinderShape3D, GodotFaceShape3D, withMargin> separator(cylinder_A, p_transform_a, face_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + Vector3 vertex[3] = { + p_transform_b.xform(face_B->vertex[0]), + p_transform_b.xform(face_B->vertex[1]), + p_transform_b.xform(face_B->vertex[2]), + }; + + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + // Face B normal. + if (!separator.test_axis(normal)) { + return; + } + + Vector3 cyl_axis = p_transform_a.basis.get_column(1).normalized(); + if (cyl_axis.dot(normal) < 0.0) { + cyl_axis *= -1.0; + } + + // Cylinder end caps. + if (!separator.test_axis(cyl_axis)) { + return; + } + + // Edges of B, cylinder lateral surface. + for (int i = 0; i < 3; i++) { + Vector3 edge_axis = vertex[i] - vertex[(i + 1) % 3]; + Vector3 axis = edge_axis.cross(cyl_axis); + if (Math::is_zero_approx(axis.length_squared())) { + continue; + } + + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis.normalized())) { + return; + } + } + + // Points of B, cylinder lateral surface. + for (int i = 0; i < 3; i++) { + const Vector3 point = vertex[i] - p_transform_a.origin; + Vector3 axis = Plane(cyl_axis).project(point).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + + // Edges of B, cylinder end caps rim. + Vector3 cap_axis = cyl_axis * (cylinder_A->get_height() * 0.5); + + for (int i = 0; i < 2; i++) { + Vector3 cap_pos = p_transform_a.origin + ((i == 0) ? cap_axis : -cap_axis); + + for (int j = 0; j < 3; j++) { + const Vector3 &edge_start = vertex[j]; + const Vector3 &edge_end = vertex[(j + 1) % 3]; + Vector3 edge_dir = edge_end - edge_start; + edge_dir.normalize(); + + real_t edge_dot = edge_dir.dot(cyl_axis); + if (Math::is_zero_approx(edge_dot)) { + // Edge is perpendicular to cylinder axis. + continue; + } + + // Calculate intersection between edge and circle plane. + Vector3 edge_diff = cap_pos - edge_start; + real_t diff_dot = edge_diff.dot(cyl_axis); + Vector3 intersection = edge_start + edge_dir * diff_dot / edge_dot; + + // Calculate tangent that touches intersection. + Vector3 tangent = (cap_pos - intersection).cross(cyl_axis); + + // Axis is orthogonal both to tangent and edge direction. + Vector3 axis = tangent.cross(edge_dir); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis.normalized())) { + return; + } + } + } + + if (!face_B->backface_collision) { + if (separator.best_axis.dot(normal) < _BACKFACE_NORMAL_THRESHOLD) { + if (face_B->invert_backface_collision) { + separator.best_axis = separator.best_axis.bounce(normal); + } else { + // Just ignore backface collision. + return; + } + } + } + + separator.generate_contacts(); +} + +static _FORCE_INLINE_ bool is_minkowski_face(const Vector3 &A, const Vector3 &B, const Vector3 &B_x_A, const Vector3 &C, const Vector3 &D, const Vector3 &D_x_C) { + // Test if arcs AB and CD intersect on the unit sphere + real_t CBA = C.dot(B_x_A); + real_t DBA = D.dot(B_x_A); + real_t ADC = A.dot(D_x_C); + real_t BDC = B.dot(D_x_C); + + return (CBA * DBA < 0.0f) && (ADC * BDC < 0.0f) && (CBA * BDC > 0.0f); +} + +template <bool withMargin> +static void _collision_convex_polygon_convex_polygon(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotConvexPolygonShape3D *convex_polygon_A = static_cast<const GodotConvexPolygonShape3D *>(p_a); + const GodotConvexPolygonShape3D *convex_polygon_B = static_cast<const GodotConvexPolygonShape3D *>(p_b); + + SeparatorAxisTest<GodotConvexPolygonShape3D, GodotConvexPolygonShape3D, withMargin> separator(convex_polygon_A, p_transform_a, convex_polygon_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + if (!separator.test_previous_axis()) { + return; + } + + const Geometry3D::MeshData &mesh_A = convex_polygon_A->get_mesh(); + + const Geometry3D::MeshData::Face *faces_A = mesh_A.faces.ptr(); + int face_count_A = mesh_A.faces.size(); + const Geometry3D::MeshData::Edge *edges_A = mesh_A.edges.ptr(); + int edge_count_A = mesh_A.edges.size(); + const Vector3 *vertices_A = mesh_A.vertices.ptr(); + int vertex_count_A = mesh_A.vertices.size(); + + const Geometry3D::MeshData &mesh_B = convex_polygon_B->get_mesh(); + + const Geometry3D::MeshData::Face *faces_B = mesh_B.faces.ptr(); + int face_count_B = mesh_B.faces.size(); + const Geometry3D::MeshData::Edge *edges_B = mesh_B.edges.ptr(); + int edge_count_B = mesh_B.edges.size(); + const Vector3 *vertices_B = mesh_B.vertices.ptr(); + int vertex_count_B = mesh_B.vertices.size(); + + // Precalculating this makes the transforms faster. + Basis a_xform_normal = p_transform_a.basis.inverse().transposed(); + + // faces of A + for (int i = 0; i < face_count_A; i++) { + Vector3 axis = a_xform_normal.xform(faces_A[i].plane.normal).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // Precalculating this makes the transforms faster. + Basis b_xform_normal = p_transform_b.basis.inverse().transposed(); + + // faces of B + for (int i = 0; i < face_count_B; i++) { + Vector3 axis = b_xform_normal.xform(faces_B[i].plane.normal).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + + // A<->B edges + + for (int i = 0; i < edge_count_A; i++) { + Vector3 p1 = p_transform_a.xform(vertices_A[edges_A[i].vertex_a]); + Vector3 q1 = p_transform_a.xform(vertices_A[edges_A[i].vertex_b]); + Vector3 e1 = q1 - p1; + Vector3 u1 = p_transform_a.basis.xform(faces_A[edges_A[i].face_a].plane.normal).normalized(); + Vector3 v1 = p_transform_a.basis.xform(faces_A[edges_A[i].face_b].plane.normal).normalized(); + + for (int j = 0; j < edge_count_B; j++) { + Vector3 p2 = p_transform_b.xform(vertices_B[edges_B[j].vertex_a]); + Vector3 q2 = p_transform_b.xform(vertices_B[edges_B[j].vertex_b]); + Vector3 e2 = q2 - p2; + Vector3 u2 = p_transform_b.basis.xform(faces_B[edges_B[j].face_a].plane.normal).normalized(); + Vector3 v2 = p_transform_b.basis.xform(faces_B[edges_B[j].face_b].plane.normal).normalized(); + + if (is_minkowski_face(u1, v1, -e1, -u2, -v2, -e2)) { + Vector3 axis = e1.cross(e2).normalized(); + + if (!separator.test_axis(axis)) { + return; + } + } + } + } + + if (withMargin) { + //vertex-vertex + for (int i = 0; i < vertex_count_A; i++) { + Vector3 va = p_transform_a.xform(vertices_A[i]); + + for (int j = 0; j < vertex_count_B; j++) { + if (!separator.test_axis((va - p_transform_b.xform(vertices_B[j])).normalized())) { + return; + } + } + } + //edge-vertex (shell) + + for (int i = 0; i < edge_count_A; i++) { + Vector3 e1 = p_transform_a.basis.xform(vertices_A[edges_A[i].vertex_a]); + Vector3 e2 = p_transform_a.basis.xform(vertices_A[edges_A[i].vertex_b]); + Vector3 n = (e2 - e1); + + for (int j = 0; j < vertex_count_B; j++) { + Vector3 e3 = p_transform_b.xform(vertices_B[j]); + + if (!separator.test_axis((e1 - e3).cross(n).cross(n).normalized())) { + return; + } + } + } + + for (int i = 0; i < edge_count_B; i++) { + Vector3 e1 = p_transform_b.basis.xform(vertices_B[edges_B[i].vertex_a]); + Vector3 e2 = p_transform_b.basis.xform(vertices_B[edges_B[i].vertex_b]); + Vector3 n = (e2 - e1); + + for (int j = 0; j < vertex_count_A; j++) { + Vector3 e3 = p_transform_a.xform(vertices_A[j]); + + if (!separator.test_axis((e1 - e3).cross(n).cross(n).normalized())) { + return; + } + } + } + } + + separator.generate_contacts(); +} + +template <bool withMargin> +static void _collision_convex_polygon_face(const GodotShape3D *p_a, const Transform3D &p_transform_a, const GodotShape3D *p_b, const Transform3D &p_transform_b, _CollectorCallback *p_collector, real_t p_margin_a, real_t p_margin_b) { + const GodotConvexPolygonShape3D *convex_polygon_A = static_cast<const GodotConvexPolygonShape3D *>(p_a); + const GodotFaceShape3D *face_B = static_cast<const GodotFaceShape3D *>(p_b); + + SeparatorAxisTest<GodotConvexPolygonShape3D, GodotFaceShape3D, withMargin> separator(convex_polygon_A, p_transform_a, face_B, p_transform_b, p_collector, p_margin_a, p_margin_b); + + const Geometry3D::MeshData &mesh = convex_polygon_A->get_mesh(); + + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int face_count = mesh.faces.size(); + const Geometry3D::MeshData::Edge *edges = mesh.edges.ptr(); + int edge_count = mesh.edges.size(); + const Vector3 *vertices = mesh.vertices.ptr(); + int vertex_count = mesh.vertices.size(); + + Vector3 vertex[3] = { + p_transform_b.xform(face_B->vertex[0]), + p_transform_b.xform(face_B->vertex[1]), + p_transform_b.xform(face_B->vertex[2]), + }; + + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal)) { + return; + } + + // faces of A + for (int i = 0; i < face_count; i++) { + //Vector3 axis = p_transform_a.xform( faces[i].plane ).normal; + Vector3 axis = p_transform_a.basis.xform(faces[i].plane.normal).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + + // A<->B edges + for (int i = 0; i < edge_count; i++) { + Vector3 e1 = p_transform_a.xform(vertices[edges[i].vertex_a]) - p_transform_a.xform(vertices[edges[i].vertex_b]); + + for (int j = 0; j < 3; j++) { + Vector3 e2 = vertex[j] - vertex[(j + 1) % 3]; + + Vector3 axis = e1.cross(e2).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + } + + if (withMargin) { + //vertex-vertex + for (int i = 0; i < vertex_count; i++) { + Vector3 va = p_transform_a.xform(vertices[i]); + + for (int j = 0; j < 3; j++) { + Vector3 axis = (va - vertex[j]).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + } + //edge-vertex (shell) + + for (int i = 0; i < edge_count; i++) { + Vector3 e1 = p_transform_a.basis.xform(vertices[edges[i].vertex_a]); + Vector3 e2 = p_transform_a.basis.xform(vertices[edges[i].vertex_b]); + Vector3 n = (e2 - e1); + + for (int j = 0; j < 3; j++) { + Vector3 e3 = vertex[j]; + + Vector3 axis = (e1 - e3).cross(n).cross(n).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + } + + for (int i = 0; i < 3; i++) { + Vector3 e1 = vertex[i]; + Vector3 e2 = vertex[(i + 1) % 3]; + Vector3 n = (e2 - e1); + + for (int j = 0; j < vertex_count; j++) { + Vector3 e3 = p_transform_a.xform(vertices[j]); + + Vector3 axis = (e1 - e3).cross(n).cross(n).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis)) { + return; + } + } + } + } + + if (!face_B->backface_collision) { + if (separator.best_axis.dot(normal) < _BACKFACE_NORMAL_THRESHOLD) { + if (face_B->invert_backface_collision) { + separator.best_axis = separator.best_axis.bounce(normal); + } else { + // Just ignore backface collision. + return; + } + } + } + + separator.generate_contacts(); +} + +bool sat_calculate_penetration(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, GodotCollisionSolver3D::CallbackResult p_result_callback, void *p_userdata, bool p_swap, Vector3 *r_prev_axis, real_t p_margin_a, real_t p_margin_b) { + PhysicsServer3D::ShapeType type_A = p_shape_A->get_type(); + + ERR_FAIL_COND_V(type_A == PhysicsServer3D::SHAPE_WORLD_BOUNDARY, false); + ERR_FAIL_COND_V(type_A == PhysicsServer3D::SHAPE_SEPARATION_RAY, false); + ERR_FAIL_COND_V(p_shape_A->is_concave(), false); + + PhysicsServer3D::ShapeType type_B = p_shape_B->get_type(); + + ERR_FAIL_COND_V(type_B == PhysicsServer3D::SHAPE_WORLD_BOUNDARY, false); + ERR_FAIL_COND_V(type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY, false); + ERR_FAIL_COND_V(p_shape_B->is_concave(), false); + + static const CollisionFunc collision_table[6][6] = { + { _collision_sphere_sphere<false>, + _collision_sphere_box<false>, + _collision_sphere_capsule<false>, + _collision_sphere_cylinder<false>, + _collision_sphere_convex_polygon<false>, + _collision_sphere_face<false> }, + { nullptr, + _collision_box_box<false>, + _collision_box_capsule<false>, + _collision_box_cylinder<false>, + _collision_box_convex_polygon<false>, + _collision_box_face<false> }, + { nullptr, + nullptr, + _collision_capsule_capsule<false>, + _collision_capsule_cylinder<false>, + _collision_capsule_convex_polygon<false>, + _collision_capsule_face<false> }, + { nullptr, + nullptr, + nullptr, + _collision_cylinder_cylinder<false>, + _collision_cylinder_convex_polygon<false>, + _collision_cylinder_face<false> }, + { nullptr, + nullptr, + nullptr, + nullptr, + _collision_convex_polygon_convex_polygon<false>, + _collision_convex_polygon_face<false> }, + { nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr }, + }; + + static const CollisionFunc collision_table_margin[6][6] = { + { _collision_sphere_sphere<true>, + _collision_sphere_box<true>, + _collision_sphere_capsule<true>, + _collision_sphere_cylinder<true>, + _collision_sphere_convex_polygon<true>, + _collision_sphere_face<true> }, + { nullptr, + _collision_box_box<true>, + _collision_box_capsule<true>, + _collision_box_cylinder<true>, + _collision_box_convex_polygon<true>, + _collision_box_face<true> }, + { nullptr, + nullptr, + _collision_capsule_capsule<true>, + _collision_capsule_cylinder<true>, + _collision_capsule_convex_polygon<true>, + _collision_capsule_face<true> }, + { nullptr, + nullptr, + nullptr, + _collision_cylinder_cylinder<true>, + _collision_cylinder_convex_polygon<true>, + _collision_cylinder_face<true> }, + { nullptr, + nullptr, + nullptr, + nullptr, + _collision_convex_polygon_convex_polygon<true>, + _collision_convex_polygon_face<true> }, + { nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr }, + }; + + _CollectorCallback callback; + callback.callback = p_result_callback; + callback.swap = p_swap; + callback.userdata = p_userdata; + callback.collided = false; + callback.prev_axis = r_prev_axis; + + const GodotShape3D *A = p_shape_A; + const GodotShape3D *B = p_shape_B; + const Transform3D *transform_A = &p_transform_A; + const Transform3D *transform_B = &p_transform_B; + real_t margin_A = p_margin_a; + real_t margin_B = p_margin_b; + + if (type_A > type_B) { + SWAP(A, B); + SWAP(transform_A, transform_B); + SWAP(type_A, type_B); + SWAP(margin_A, margin_B); + callback.swap = !callback.swap; + } + + CollisionFunc collision_func; + if (margin_A != 0.0 || margin_B != 0.0) { + collision_func = collision_table_margin[type_A - 2][type_B - 2]; + + } else { + collision_func = collision_table[type_A - 2][type_B - 2]; + } + ERR_FAIL_NULL_V(collision_func, false); + + collision_func(A, *transform_A, B, *transform_B, &callback, margin_A, margin_B); + + return callback.collided; +} diff --git a/modules/godot_physics_3d/godot_collision_solver_3d_sat.h b/modules/godot_physics_3d/godot_collision_solver_3d_sat.h new file mode 100644 index 0000000000..49fcab3933 --- /dev/null +++ b/modules/godot_physics_3d/godot_collision_solver_3d_sat.h @@ -0,0 +1,38 @@ +/**************************************************************************/ +/* godot_collision_solver_3d_sat.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 GODOT_COLLISION_SOLVER_3D_SAT_H +#define GODOT_COLLISION_SOLVER_3D_SAT_H + +#include "godot_collision_solver_3d.h" + +bool sat_calculate_penetration(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, GodotCollisionSolver3D::CallbackResult p_result_callback, void *p_userdata, bool p_swap = false, Vector3 *r_prev_axis = nullptr, real_t p_margin_a = 0, real_t p_margin_b = 0); + +#endif // GODOT_COLLISION_SOLVER_3D_SAT_H diff --git a/modules/godot_physics_3d/godot_constraint_3d.h b/modules/godot_physics_3d/godot_constraint_3d.h new file mode 100644 index 0000000000..a833aba93f --- /dev/null +++ b/modules/godot_physics_3d/godot_constraint_3d.h @@ -0,0 +1,81 @@ +/**************************************************************************/ +/* godot_constraint_3d.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 GODOT_CONSTRAINT_3D_H +#define GODOT_CONSTRAINT_3D_H + +class GodotBody3D; +class GodotSoftBody3D; + +class GodotConstraint3D { + GodotBody3D **_body_ptr; + int _body_count; + uint64_t island_step; + int priority; + bool disabled_collisions_between_bodies; + + RID self; + +protected: + GodotConstraint3D(GodotBody3D **p_body_ptr = nullptr, int p_body_count = 0) { + _body_ptr = p_body_ptr; + _body_count = p_body_count; + island_step = 0; + priority = 1; + disabled_collisions_between_bodies = true; + } + +public: + _FORCE_INLINE_ void set_self(const RID &p_self) { self = p_self; } + _FORCE_INLINE_ RID get_self() const { return self; } + + _FORCE_INLINE_ uint64_t get_island_step() const { return island_step; } + _FORCE_INLINE_ void set_island_step(uint64_t p_step) { island_step = p_step; } + + _FORCE_INLINE_ GodotBody3D **get_body_ptr() const { return _body_ptr; } + _FORCE_INLINE_ int get_body_count() const { return _body_count; } + + virtual GodotSoftBody3D *get_soft_body_ptr(int p_index) const { return nullptr; } + virtual int get_soft_body_count() const { return 0; } + + _FORCE_INLINE_ void set_priority(int p_priority) { priority = p_priority; } + _FORCE_INLINE_ int get_priority() const { return priority; } + + _FORCE_INLINE_ void disable_collisions_between_bodies(const bool p_disabled) { disabled_collisions_between_bodies = p_disabled; } + _FORCE_INLINE_ bool is_disabled_collisions_between_bodies() const { return disabled_collisions_between_bodies; } + + virtual bool setup(real_t p_step) = 0; + virtual bool pre_solve(real_t p_step) = 0; + virtual void solve(real_t p_step) = 0; + + virtual ~GodotConstraint3D() {} +}; + +#endif // GODOT_CONSTRAINT_3D_H diff --git a/modules/godot_physics_3d/godot_joint_3d.h b/modules/godot_physics_3d/godot_joint_3d.h new file mode 100644 index 0000000000..3207723cb4 --- /dev/null +++ b/modules/godot_physics_3d/godot_joint_3d.h @@ -0,0 +1,101 @@ +/**************************************************************************/ +/* godot_joint_3d.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 GODOT_JOINT_3D_H +#define GODOT_JOINT_3D_H + +#include "godot_body_3d.h" +#include "godot_constraint_3d.h" + +class GodotJoint3D : public GodotConstraint3D { +protected: + bool dynamic_A = false; + bool dynamic_B = false; + + void plane_space(const Vector3 &n, Vector3 &p, Vector3 &q) { + if (Math::abs(n.z) > Math_SQRT12) { + // choose p in y-z plane + real_t a = n[1] * n[1] + n[2] * n[2]; + real_t k = 1.0 / Math::sqrt(a); + p = Vector3(0, -n[2] * k, n[1] * k); + // set q = n x p + q = Vector3(a * k, -n[0] * p[2], n[0] * p[1]); + } else { + // choose p in x-y plane + real_t a = n.x * n.x + n.y * n.y; + real_t k = 1.0 / Math::sqrt(a); + p = Vector3(-n.y * k, n.x * k, 0); + // set q = n x p + q = Vector3(-n.z * p.y, n.z * p.x, a * k); + } + } + + _FORCE_INLINE_ real_t atan2fast(real_t y, real_t x) { + real_t coeff_1 = Math_PI / 4.0f; + real_t coeff_2 = 3.0f * coeff_1; + real_t abs_y = Math::abs(y); + real_t angle; + if (x >= 0.0f) { + real_t r = (x - abs_y) / (x + abs_y); + angle = coeff_1 - coeff_1 * r; + } else { + real_t r = (x + abs_y) / (abs_y - x); + angle = coeff_2 - coeff_1 * r; + } + return (y < 0.0f) ? -angle : angle; + } + +public: + virtual bool setup(real_t p_step) override { return false; } + virtual bool pre_solve(real_t p_step) override { return true; } + virtual void solve(real_t p_step) override {} + + void copy_settings_from(GodotJoint3D *p_joint) { + set_self(p_joint->get_self()); + set_priority(p_joint->get_priority()); + disable_collisions_between_bodies(p_joint->is_disabled_collisions_between_bodies()); + } + + virtual PhysicsServer3D::JointType get_type() const { return PhysicsServer3D::JOINT_TYPE_MAX; } + _FORCE_INLINE_ GodotJoint3D(GodotBody3D **p_body_ptr = nullptr, int p_body_count = 0) : + GodotConstraint3D(p_body_ptr, p_body_count) { + } + + virtual ~GodotJoint3D() { + for (int i = 0; i < get_body_count(); i++) { + GodotBody3D *body = get_body_ptr()[i]; + if (body) { + body->remove_constraint(this); + } + } + } +}; + +#endif // GODOT_JOINT_3D_H diff --git a/modules/godot_physics_3d/godot_physics_server_3d.cpp b/modules/godot_physics_3d/godot_physics_server_3d.cpp new file mode 100644 index 0000000000..6d0949acbe --- /dev/null +++ b/modules/godot_physics_3d/godot_physics_server_3d.cpp @@ -0,0 +1,1773 @@ +/**************************************************************************/ +/* godot_physics_server_3d.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 "godot_physics_server_3d.h" + +#include "godot_body_direct_state_3d.h" +#include "godot_broad_phase_3d_bvh.h" +#include "joints/godot_cone_twist_joint_3d.h" +#include "joints/godot_generic_6dof_joint_3d.h" +#include "joints/godot_hinge_joint_3d.h" +#include "joints/godot_pin_joint_3d.h" +#include "joints/godot_slider_joint_3d.h" + +#include "core/debugger/engine_debugger.h" +#include "core/os/os.h" + +#define FLUSH_QUERY_CHECK(m_object) \ + ERR_FAIL_COND_MSG(m_object->get_space() && flushing_queries, "Can't change this state while flushing queries. Use call_deferred() or set_deferred() to change monitoring state instead."); + +RID GodotPhysicsServer3D::world_boundary_shape_create() { + GodotShape3D *shape = memnew(GodotWorldBoundaryShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::separation_ray_shape_create() { + GodotShape3D *shape = memnew(GodotSeparationRayShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::sphere_shape_create() { + GodotShape3D *shape = memnew(GodotSphereShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::box_shape_create() { + GodotShape3D *shape = memnew(GodotBoxShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::capsule_shape_create() { + GodotShape3D *shape = memnew(GodotCapsuleShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::cylinder_shape_create() { + GodotShape3D *shape = memnew(GodotCylinderShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::convex_polygon_shape_create() { + GodotShape3D *shape = memnew(GodotConvexPolygonShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::concave_polygon_shape_create() { + GodotShape3D *shape = memnew(GodotConcavePolygonShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::heightmap_shape_create() { + GodotShape3D *shape = memnew(GodotHeightMapShape3D); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} +RID GodotPhysicsServer3D::custom_shape_create() { + ERR_FAIL_V(RID()); +} + +void GodotPhysicsServer3D::shape_set_data(RID p_shape, const Variant &p_data) { + GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL(shape); + shape->set_data(p_data); +}; + +void GodotPhysicsServer3D::shape_set_custom_solver_bias(RID p_shape, real_t p_bias) { + GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL(shape); + shape->set_custom_bias(p_bias); +} + +PhysicsServer3D::ShapeType GodotPhysicsServer3D::shape_get_type(RID p_shape) const { + const GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL_V(shape, SHAPE_CUSTOM); + return shape->get_type(); +}; + +Variant GodotPhysicsServer3D::shape_get_data(RID p_shape) const { + const GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL_V(shape, Variant()); + ERR_FAIL_COND_V(!shape->is_configured(), Variant()); + return shape->get_data(); +}; + +void GodotPhysicsServer3D::shape_set_margin(RID p_shape, real_t p_margin) { +} + +real_t GodotPhysicsServer3D::shape_get_margin(RID p_shape) const { + return 0.0; +} + +real_t GodotPhysicsServer3D::shape_get_custom_solver_bias(RID p_shape) const { + const GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL_V(shape, 0); + return shape->get_custom_bias(); +} + +RID GodotPhysicsServer3D::space_create() { + GodotSpace3D *space = memnew(GodotSpace3D); + RID id = space_owner.make_rid(space); + space->set_self(id); + RID area_id = area_create(); + GodotArea3D *area = area_owner.get_or_null(area_id); + ERR_FAIL_NULL_V(area, RID()); + space->set_default_area(area); + area->set_space(space); + area->set_priority(-1); + RID sgb = body_create(); + body_set_space(sgb, id); + body_set_mode(sgb, BODY_MODE_STATIC); + space->set_static_global_body(sgb); + + return id; +}; + +void GodotPhysicsServer3D::space_set_active(RID p_space, bool p_active) { + GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL(space); + if (p_active) { + active_spaces.insert(space); + } else { + active_spaces.erase(space); + } +} + +bool GodotPhysicsServer3D::space_is_active(RID p_space) const { + const GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL_V(space, false); + + return active_spaces.has(space); +} + +void GodotPhysicsServer3D::space_set_param(RID p_space, SpaceParameter p_param, real_t p_value) { + GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL(space); + + space->set_param(p_param, p_value); +} + +real_t GodotPhysicsServer3D::space_get_param(RID p_space, SpaceParameter p_param) const { + const GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL_V(space, 0); + return space->get_param(p_param); +} + +PhysicsDirectSpaceState3D *GodotPhysicsServer3D::space_get_direct_state(RID p_space) { + GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL_V(space, nullptr); + ERR_FAIL_COND_V_MSG((using_threads && !doing_sync) || space->is_locked(), nullptr, "Space state is inaccessible right now, wait for iteration or physics process notification."); + + return space->get_direct_state(); +} + +void GodotPhysicsServer3D::space_set_debug_contacts(RID p_space, int p_max_contacts) { + GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL(space); + space->set_debug_contacts(p_max_contacts); +} + +Vector<Vector3> GodotPhysicsServer3D::space_get_contacts(RID p_space) const { + GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL_V(space, Vector<Vector3>()); + return space->get_debug_contacts(); +} + +int GodotPhysicsServer3D::space_get_contact_count(RID p_space) const { + GodotSpace3D *space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL_V(space, 0); + return space->get_debug_contact_count(); +} + +RID GodotPhysicsServer3D::area_create() { + GodotArea3D *area = memnew(GodotArea3D); + RID rid = area_owner.make_rid(area); + area->set_self(rid); + return rid; +} + +void GodotPhysicsServer3D::area_set_space(RID p_area, RID p_space) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + GodotSpace3D *space = nullptr; + if (p_space.is_valid()) { + space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL(space); + } + + if (area->get_space() == space) { + return; //pointless + } + + area->clear_constraints(); + area->set_space(space); +} + +RID GodotPhysicsServer3D::area_get_space(RID p_area) const { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, RID()); + + GodotSpace3D *space = area->get_space(); + if (!space) { + return RID(); + } + return space->get_self(); +} + +void GodotPhysicsServer3D::area_add_shape(RID p_area, RID p_shape, const Transform3D &p_transform, bool p_disabled) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL(shape); + + area->add_shape(shape, p_transform, p_disabled); +} + +void GodotPhysicsServer3D::area_set_shape(RID p_area, int p_shape_idx, RID p_shape) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL(shape); + ERR_FAIL_COND(!shape->is_configured()); + + area->set_shape(p_shape_idx, shape); +} + +void GodotPhysicsServer3D::area_set_shape_transform(RID p_area, int p_shape_idx, const Transform3D &p_transform) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + area->set_shape_transform(p_shape_idx, p_transform); +} + +int GodotPhysicsServer3D::area_get_shape_count(RID p_area) const { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, -1); + + return area->get_shape_count(); +} + +RID GodotPhysicsServer3D::area_get_shape(RID p_area, int p_shape_idx) const { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, RID()); + + GodotShape3D *shape = area->get_shape(p_shape_idx); + ERR_FAIL_NULL_V(shape, RID()); + + return shape->get_self(); +} + +Transform3D GodotPhysicsServer3D::area_get_shape_transform(RID p_area, int p_shape_idx) const { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, Transform3D()); + + return area->get_shape_transform(p_shape_idx); +} + +void GodotPhysicsServer3D::area_remove_shape(RID p_area, int p_shape_idx) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + area->remove_shape(p_shape_idx); +} + +void GodotPhysicsServer3D::area_clear_shapes(RID p_area) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + while (area->get_shape_count()) { + area->remove_shape(0); + } +} + +void GodotPhysicsServer3D::area_set_shape_disabled(RID p_area, int p_shape_idx, bool p_disabled) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + ERR_FAIL_INDEX(p_shape_idx, area->get_shape_count()); + FLUSH_QUERY_CHECK(area); + area->set_shape_disabled(p_shape_idx, p_disabled); +} + +void GodotPhysicsServer3D::area_attach_object_instance_id(RID p_area, ObjectID p_id) { + if (space_owner.owns(p_area)) { + GodotSpace3D *space = space_owner.get_or_null(p_area); + p_area = space->get_default_area()->get_self(); + } + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + area->set_instance_id(p_id); +} + +ObjectID GodotPhysicsServer3D::area_get_object_instance_id(RID p_area) const { + if (space_owner.owns(p_area)) { + GodotSpace3D *space = space_owner.get_or_null(p_area); + p_area = space->get_default_area()->get_self(); + } + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, ObjectID()); + return area->get_instance_id(); +} + +void GodotPhysicsServer3D::area_set_param(RID p_area, AreaParameter p_param, const Variant &p_value) { + if (space_owner.owns(p_area)) { + GodotSpace3D *space = space_owner.get_or_null(p_area); + p_area = space->get_default_area()->get_self(); + } + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + area->set_param(p_param, p_value); +}; + +void GodotPhysicsServer3D::area_set_transform(RID p_area, const Transform3D &p_transform) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + area->set_transform(p_transform); +}; + +Variant GodotPhysicsServer3D::area_get_param(RID p_area, AreaParameter p_param) const { + if (space_owner.owns(p_area)) { + GodotSpace3D *space = space_owner.get_or_null(p_area); + p_area = space->get_default_area()->get_self(); + } + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, Variant()); + + return area->get_param(p_param); +}; + +Transform3D GodotPhysicsServer3D::area_get_transform(RID p_area) const { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, Transform3D()); + + return area->get_transform(); +}; + +void GodotPhysicsServer3D::area_set_collision_layer(RID p_area, uint32_t p_layer) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + area->set_collision_layer(p_layer); +} + +uint32_t GodotPhysicsServer3D::area_get_collision_layer(RID p_area) const { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, 0); + + return area->get_collision_layer(); +} + +void GodotPhysicsServer3D::area_set_collision_mask(RID p_area, uint32_t p_mask) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + area->set_collision_mask(p_mask); +} + +uint32_t GodotPhysicsServer3D::area_get_collision_mask(RID p_area) const { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL_V(area, 0); + + return area->get_collision_mask(); +} + +void GodotPhysicsServer3D::area_set_monitorable(RID p_area, bool p_monitorable) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + FLUSH_QUERY_CHECK(area); + + area->set_monitorable(p_monitorable); +} + +void GodotPhysicsServer3D::area_set_monitor_callback(RID p_area, const Callable &p_callback) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + area->set_monitor_callback(p_callback.is_valid() ? p_callback : Callable()); +} + +void GodotPhysicsServer3D::area_set_ray_pickable(RID p_area, bool p_enable) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + area->set_ray_pickable(p_enable); +} + +void GodotPhysicsServer3D::area_set_area_monitor_callback(RID p_area, const Callable &p_callback) { + GodotArea3D *area = area_owner.get_or_null(p_area); + ERR_FAIL_NULL(area); + + area->set_area_monitor_callback(p_callback.is_valid() ? p_callback : Callable()); +} + +/* BODY API */ + +RID GodotPhysicsServer3D::body_create() { + GodotBody3D *body = memnew(GodotBody3D); + RID rid = body_owner.make_rid(body); + body->set_self(rid); + return rid; +}; + +void GodotPhysicsServer3D::body_set_space(RID p_body, RID p_space) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + GodotSpace3D *space = nullptr; + if (p_space.is_valid()) { + space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL(space); + } + + if (body->get_space() == space) { + return; //pointless + } + + body->clear_constraint_map(); + body->set_space(space); +}; + +RID GodotPhysicsServer3D::body_get_space(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, RID()); + + GodotSpace3D *space = body->get_space(); + if (!space) { + return RID(); + } + return space->get_self(); +}; + +void GodotPhysicsServer3D::body_set_mode(RID p_body, BodyMode p_mode) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_mode(p_mode); +}; + +PhysicsServer3D::BodyMode GodotPhysicsServer3D::body_get_mode(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, BODY_MODE_STATIC); + + return body->get_mode(); +}; + +void GodotPhysicsServer3D::body_add_shape(RID p_body, RID p_shape, const Transform3D &p_transform, bool p_disabled) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL(shape); + + body->add_shape(shape, p_transform, p_disabled); +} + +void GodotPhysicsServer3D::body_set_shape(RID p_body, int p_shape_idx, RID p_shape) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + GodotShape3D *shape = shape_owner.get_or_null(p_shape); + ERR_FAIL_NULL(shape); + ERR_FAIL_COND(!shape->is_configured()); + + body->set_shape(p_shape_idx, shape); +} +void GodotPhysicsServer3D::body_set_shape_transform(RID p_body, int p_shape_idx, const Transform3D &p_transform) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_shape_transform(p_shape_idx, p_transform); +} + +int GodotPhysicsServer3D::body_get_shape_count(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, -1); + + return body->get_shape_count(); +} + +RID GodotPhysicsServer3D::body_get_shape(RID p_body, int p_shape_idx) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, RID()); + + GodotShape3D *shape = body->get_shape(p_shape_idx); + ERR_FAIL_NULL_V(shape, RID()); + + return shape->get_self(); +} + +void GodotPhysicsServer3D::body_set_shape_disabled(RID p_body, int p_shape_idx, bool p_disabled) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + ERR_FAIL_INDEX(p_shape_idx, body->get_shape_count()); + FLUSH_QUERY_CHECK(body); + + body->set_shape_disabled(p_shape_idx, p_disabled); +} + +Transform3D GodotPhysicsServer3D::body_get_shape_transform(RID p_body, int p_shape_idx) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, Transform3D()); + + return body->get_shape_transform(p_shape_idx); +} + +void GodotPhysicsServer3D::body_remove_shape(RID p_body, int p_shape_idx) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->remove_shape(p_shape_idx); +} + +void GodotPhysicsServer3D::body_clear_shapes(RID p_body) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + while (body->get_shape_count()) { + body->remove_shape(0); + } +} + +void GodotPhysicsServer3D::body_set_enable_continuous_collision_detection(RID p_body, bool p_enable) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_continuous_collision_detection(p_enable); +} + +bool GodotPhysicsServer3D::body_is_continuous_collision_detection_enabled(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, false); + + return body->is_continuous_collision_detection_enabled(); +} + +void GodotPhysicsServer3D::body_set_collision_layer(RID p_body, uint32_t p_layer) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_collision_layer(p_layer); +} + +uint32_t GodotPhysicsServer3D::body_get_collision_layer(RID p_body) const { + const GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, 0); + + return body->get_collision_layer(); +} + +void GodotPhysicsServer3D::body_set_collision_mask(RID p_body, uint32_t p_mask) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_collision_mask(p_mask); +} + +uint32_t GodotPhysicsServer3D::body_get_collision_mask(RID p_body) const { + const GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, 0); + + return body->get_collision_mask(); +} + +void GodotPhysicsServer3D::body_set_collision_priority(RID p_body, real_t p_priority) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_collision_priority(p_priority); +} + +real_t GodotPhysicsServer3D::body_get_collision_priority(RID p_body) const { + const GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, 0); + + return body->get_collision_priority(); +} + +void GodotPhysicsServer3D::body_attach_object_instance_id(RID p_body, ObjectID p_id) { + GodotBody3D *body = body_owner.get_or_null(p_body); + if (body) { + body->set_instance_id(p_id); + return; + } + + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + if (soft_body) { + soft_body->set_instance_id(p_id); + return; + } + + ERR_FAIL_MSG("Invalid ID."); +} + +ObjectID GodotPhysicsServer3D::body_get_object_instance_id(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, ObjectID()); + + return body->get_instance_id(); +} + +void GodotPhysicsServer3D::body_set_user_flags(RID p_body, uint32_t p_flags) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); +} + +uint32_t GodotPhysicsServer3D::body_get_user_flags(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, 0); + + return 0; +} + +void GodotPhysicsServer3D::body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_param(p_param, p_value); +} + +Variant GodotPhysicsServer3D::body_get_param(RID p_body, BodyParameter p_param) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, 0); + + return body->get_param(p_param); +} + +void GodotPhysicsServer3D::body_reset_mass_properties(RID p_body) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + return body->reset_mass_properties(); +} + +void GodotPhysicsServer3D::body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_state(p_state, p_variant); +} + +Variant GodotPhysicsServer3D::body_get_state(RID p_body, BodyState p_state) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, Variant()); + + return body->get_state(p_state); +} + +void GodotPhysicsServer3D::body_apply_central_impulse(RID p_body, const Vector3 &p_impulse) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + _update_shapes(); + + body->apply_central_impulse(p_impulse); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_apply_impulse(RID p_body, const Vector3 &p_impulse, const Vector3 &p_position) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + _update_shapes(); + + body->apply_impulse(p_impulse, p_position); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_apply_torque_impulse(RID p_body, const Vector3 &p_impulse) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + _update_shapes(); + + body->apply_torque_impulse(p_impulse); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_apply_central_force(RID p_body, const Vector3 &p_force) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->apply_central_force(p_force); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_apply_force(RID p_body, const Vector3 &p_force, const Vector3 &p_position) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->apply_force(p_force, p_position); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_apply_torque(RID p_body, const Vector3 &p_torque) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->apply_torque(p_torque); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_add_constant_central_force(RID p_body, const Vector3 &p_force) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->add_constant_central_force(p_force); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_add_constant_force(RID p_body, const Vector3 &p_force, const Vector3 &p_position) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->add_constant_force(p_force, p_position); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_add_constant_torque(RID p_body, const Vector3 &p_torque) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->add_constant_torque(p_torque); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_set_constant_force(RID p_body, const Vector3 &p_force) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_constant_force(p_force); + if (!p_force.is_zero_approx()) { + body->wakeup(); + } +} + +Vector3 GodotPhysicsServer3D::body_get_constant_force(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, Vector3()); + return body->get_constant_force(); +} + +void GodotPhysicsServer3D::body_set_constant_torque(RID p_body, const Vector3 &p_torque) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_constant_torque(p_torque); + if (!p_torque.is_zero_approx()) { + body->wakeup(); + } +} + +Vector3 GodotPhysicsServer3D::body_get_constant_torque(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, Vector3()); + + return body->get_constant_torque(); +} + +void GodotPhysicsServer3D::body_set_axis_velocity(RID p_body, const Vector3 &p_axis_velocity) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + _update_shapes(); + + Vector3 v = body->get_linear_velocity(); + Vector3 axis = p_axis_velocity.normalized(); + v -= axis * axis.dot(v); + v += p_axis_velocity; + body->set_linear_velocity(v); + body->wakeup(); +} + +void GodotPhysicsServer3D::body_set_axis_lock(RID p_body, BodyAxis p_axis, bool p_lock) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_axis_lock(p_axis, p_lock); + body->wakeup(); +} + +bool GodotPhysicsServer3D::body_is_axis_locked(RID p_body, BodyAxis p_axis) const { + const GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, 0); + return body->is_axis_locked(p_axis); +} + +void GodotPhysicsServer3D::body_add_collision_exception(RID p_body, RID p_body_b) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->add_exception(p_body_b); + body->wakeup(); +}; + +void GodotPhysicsServer3D::body_remove_collision_exception(RID p_body, RID p_body_b) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->remove_exception(p_body_b); + body->wakeup(); +}; + +void GodotPhysicsServer3D::body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + for (int i = 0; i < body->get_exceptions().size(); i++) { + p_exceptions->push_back(body->get_exceptions()[i]); + } +}; + +void GodotPhysicsServer3D::body_set_contacts_reported_depth_threshold(RID p_body, real_t p_threshold) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); +}; + +real_t GodotPhysicsServer3D::body_get_contacts_reported_depth_threshold(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, 0); + return 0; +}; + +void GodotPhysicsServer3D::body_set_omit_force_integration(RID p_body, bool p_omit) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + + body->set_omit_force_integration(p_omit); +}; + +bool GodotPhysicsServer3D::body_is_omitting_force_integration(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, false); + return body->get_omit_force_integration(); +}; + +void GodotPhysicsServer3D::body_set_max_contacts_reported(RID p_body, int p_contacts) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + body->set_max_contacts_reported(p_contacts); +} + +int GodotPhysicsServer3D::body_get_max_contacts_reported(RID p_body) const { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, -1); + return body->get_max_contacts_reported(); +} + +void GodotPhysicsServer3D::body_set_state_sync_callback(RID p_body, const Callable &p_callable) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + body->set_state_sync_callback(p_callable); +} + +void GodotPhysicsServer3D::body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + body->set_force_integration_callback(p_callable, p_udata); +} + +void GodotPhysicsServer3D::body_set_ray_pickable(RID p_body, bool p_enable) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL(body); + body->set_ray_pickable(p_enable); +} + +bool GodotPhysicsServer3D::body_test_motion(RID p_body, const MotionParameters &p_parameters, MotionResult *r_result) { + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, false); + ERR_FAIL_NULL_V(body->get_space(), false); + ERR_FAIL_COND_V(body->get_space()->is_locked(), false); + + _update_shapes(); + + return body->get_space()->test_body_motion(body, p_parameters, r_result); +} + +PhysicsDirectBodyState3D *GodotPhysicsServer3D::body_get_direct_state(RID p_body) { + ERR_FAIL_COND_V_MSG((using_threads && !doing_sync), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification."); + + if (!body_owner.owns(p_body)) { + return nullptr; + } + + GodotBody3D *body = body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(body, nullptr); + + if (!body->get_space()) { + return nullptr; + } + + ERR_FAIL_COND_V_MSG(body->get_space()->is_locked(), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification."); + + return body->get_direct_state(); +} + +/* SOFT BODY */ + +RID GodotPhysicsServer3D::soft_body_create() { + GodotSoftBody3D *soft_body = memnew(GodotSoftBody3D); + RID rid = soft_body_owner.make_rid(soft_body); + soft_body->set_self(rid); + return rid; +} + +void GodotPhysicsServer3D::soft_body_update_rendering_server(RID p_body, PhysicsServer3DRenderingServerHandler *p_rendering_server_handler) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->update_rendering_server(p_rendering_server_handler); +} + +void GodotPhysicsServer3D::soft_body_set_space(RID p_body, RID p_space) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + GodotSpace3D *space = nullptr; + if (p_space.is_valid()) { + space = space_owner.get_or_null(p_space); + ERR_FAIL_NULL(space); + } + + if (soft_body->get_space() == space) { + return; + } + + soft_body->set_space(space); +} + +RID GodotPhysicsServer3D::soft_body_get_space(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, RID()); + + GodotSpace3D *space = soft_body->get_space(); + if (!space) { + return RID(); + } + return space->get_self(); +} + +void GodotPhysicsServer3D::soft_body_set_collision_layer(RID p_body, uint32_t p_layer) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_collision_layer(p_layer); +} + +uint32_t GodotPhysicsServer3D::soft_body_get_collision_layer(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0); + + return soft_body->get_collision_layer(); +} + +void GodotPhysicsServer3D::soft_body_set_collision_mask(RID p_body, uint32_t p_mask) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_collision_mask(p_mask); +} + +uint32_t GodotPhysicsServer3D::soft_body_get_collision_mask(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0); + + return soft_body->get_collision_mask(); +} + +void GodotPhysicsServer3D::soft_body_add_collision_exception(RID p_body, RID p_body_b) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->add_exception(p_body_b); +} + +void GodotPhysicsServer3D::soft_body_remove_collision_exception(RID p_body, RID p_body_b) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->remove_exception(p_body_b); +} + +void GodotPhysicsServer3D::soft_body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + for (int i = 0; i < soft_body->get_exceptions().size(); i++) { + p_exceptions->push_back(soft_body->get_exceptions()[i]); + } +} + +void GodotPhysicsServer3D::soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_state(p_state, p_variant); +} + +Variant GodotPhysicsServer3D::soft_body_get_state(RID p_body, BodyState p_state) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, Variant()); + + return soft_body->get_state(p_state); +} + +void GodotPhysicsServer3D::soft_body_set_transform(RID p_body, const Transform3D &p_transform) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_state(BODY_STATE_TRANSFORM, p_transform); +} + +void GodotPhysicsServer3D::soft_body_set_ray_pickable(RID p_body, bool p_enable) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_ray_pickable(p_enable); +} + +void GodotPhysicsServer3D::soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_iteration_count(p_simulation_precision); +} + +int GodotPhysicsServer3D::soft_body_get_simulation_precision(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0.f); + + return soft_body->get_iteration_count(); +} + +void GodotPhysicsServer3D::soft_body_set_total_mass(RID p_body, real_t p_total_mass) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_total_mass(p_total_mass); +} + +real_t GodotPhysicsServer3D::soft_body_get_total_mass(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0.f); + + return soft_body->get_total_mass(); +} + +void GodotPhysicsServer3D::soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_linear_stiffness(p_stiffness); +} + +real_t GodotPhysicsServer3D::soft_body_get_linear_stiffness(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0.f); + + return soft_body->get_linear_stiffness(); +} + +void GodotPhysicsServer3D::soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_pressure_coefficient(p_pressure_coefficient); +} + +real_t GodotPhysicsServer3D::soft_body_get_pressure_coefficient(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0.f); + + return soft_body->get_pressure_coefficient(); +} + +void GodotPhysicsServer3D::soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_damping_coefficient(p_damping_coefficient); +} + +real_t GodotPhysicsServer3D::soft_body_get_damping_coefficient(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0.f); + + return soft_body->get_damping_coefficient(); +} + +void GodotPhysicsServer3D::soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_drag_coefficient(p_drag_coefficient); +} + +real_t GodotPhysicsServer3D::soft_body_get_drag_coefficient(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, 0.f); + + return soft_body->get_drag_coefficient(); +} + +void GodotPhysicsServer3D::soft_body_set_mesh(RID p_body, RID p_mesh) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_mesh(p_mesh); +} + +AABB GodotPhysicsServer3D::soft_body_get_bounds(RID p_body) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, AABB()); + + return soft_body->get_bounds(); +} + +void GodotPhysicsServer3D::soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->set_vertex_position(p_point_index, p_global_position); +} + +Vector3 GodotPhysicsServer3D::soft_body_get_point_global_position(RID p_body, int p_point_index) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, Vector3()); + + return soft_body->get_vertex_position(p_point_index); +} + +void GodotPhysicsServer3D::soft_body_remove_all_pinned_points(RID p_body) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + soft_body->unpin_all_vertices(); +} + +void GodotPhysicsServer3D::soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL(soft_body); + + if (p_pin) { + soft_body->pin_vertex(p_point_index); + } else { + soft_body->unpin_vertex(p_point_index); + } +} + +bool GodotPhysicsServer3D::soft_body_is_point_pinned(RID p_body, int p_point_index) const { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_body); + ERR_FAIL_NULL_V(soft_body, false); + + return soft_body->is_vertex_pinned(p_point_index); +} + +/* JOINT API */ + +RID GodotPhysicsServer3D::joint_create() { + GodotJoint3D *joint = memnew(GodotJoint3D); + RID rid = joint_owner.make_rid(joint); + joint->set_self(rid); + return rid; +} + +void GodotPhysicsServer3D::joint_clear(RID p_joint) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + if (joint->get_type() != JOINT_TYPE_MAX) { + GodotJoint3D *empty_joint = memnew(GodotJoint3D); + empty_joint->copy_settings_from(joint); + + joint_owner.replace(p_joint, empty_joint); + memdelete(joint); + } +} + +void GodotPhysicsServer3D::joint_make_pin(RID p_joint, RID p_body_A, const Vector3 &p_local_A, RID p_body_B, const Vector3 &p_local_B) { + GodotBody3D *body_A = body_owner.get_or_null(p_body_A); + ERR_FAIL_NULL(body_A); + + if (!p_body_B.is_valid()) { + ERR_FAIL_NULL(body_A->get_space()); + p_body_B = body_A->get_space()->get_static_global_body(); + } + + GodotBody3D *body_B = body_owner.get_or_null(p_body_B); + ERR_FAIL_NULL(body_B); + + ERR_FAIL_COND(body_A == body_B); + + GodotJoint3D *prev_joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(prev_joint); + + GodotJoint3D *joint = memnew(GodotPinJoint3D(body_A, p_local_A, body_B, p_local_B)); + + joint->copy_settings_from(prev_joint); + joint_owner.replace(p_joint, joint); + memdelete(prev_joint); +} + +void GodotPhysicsServer3D::pin_joint_set_param(RID p_joint, PinJointParam p_param, real_t p_value) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_PIN); + GodotPinJoint3D *pin_joint = static_cast<GodotPinJoint3D *>(joint); + pin_joint->set_param(p_param, p_value); +} + +real_t GodotPhysicsServer3D::pin_joint_get_param(RID p_joint, PinJointParam p_param) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, 0); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, 0); + GodotPinJoint3D *pin_joint = static_cast<GodotPinJoint3D *>(joint); + return pin_joint->get_param(p_param); +} + +void GodotPhysicsServer3D::pin_joint_set_local_a(RID p_joint, const Vector3 &p_A) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_PIN); + GodotPinJoint3D *pin_joint = static_cast<GodotPinJoint3D *>(joint); + pin_joint->set_pos_a(p_A); +} + +Vector3 GodotPhysicsServer3D::pin_joint_get_local_a(RID p_joint) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, Vector3()); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, Vector3()); + GodotPinJoint3D *pin_joint = static_cast<GodotPinJoint3D *>(joint); + return pin_joint->get_position_a(); +} + +void GodotPhysicsServer3D::pin_joint_set_local_b(RID p_joint, const Vector3 &p_B) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_PIN); + GodotPinJoint3D *pin_joint = static_cast<GodotPinJoint3D *>(joint); + pin_joint->set_pos_b(p_B); +} + +Vector3 GodotPhysicsServer3D::pin_joint_get_local_b(RID p_joint) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, Vector3()); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, Vector3()); + GodotPinJoint3D *pin_joint = static_cast<GodotPinJoint3D *>(joint); + return pin_joint->get_position_b(); +} + +void GodotPhysicsServer3D::joint_make_hinge(RID p_joint, RID p_body_A, const Transform3D &p_frame_A, RID p_body_B, const Transform3D &p_frame_B) { + GodotBody3D *body_A = body_owner.get_or_null(p_body_A); + ERR_FAIL_NULL(body_A); + + if (!p_body_B.is_valid()) { + ERR_FAIL_NULL(body_A->get_space()); + p_body_B = body_A->get_space()->get_static_global_body(); + } + + GodotBody3D *body_B = body_owner.get_or_null(p_body_B); + ERR_FAIL_NULL(body_B); + + ERR_FAIL_COND(body_A == body_B); + + GodotJoint3D *prev_joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(prev_joint); + + GodotJoint3D *joint = memnew(GodotHingeJoint3D(body_A, body_B, p_frame_A, p_frame_B)); + + joint->copy_settings_from(prev_joint); + joint_owner.replace(p_joint, joint); + memdelete(prev_joint); +} + +void GodotPhysicsServer3D::joint_make_hinge_simple(RID p_joint, RID p_body_A, const Vector3 &p_pivot_A, const Vector3 &p_axis_A, RID p_body_B, const Vector3 &p_pivot_B, const Vector3 &p_axis_B) { + GodotBody3D *body_A = body_owner.get_or_null(p_body_A); + ERR_FAIL_NULL(body_A); + + if (!p_body_B.is_valid()) { + ERR_FAIL_NULL(body_A->get_space()); + p_body_B = body_A->get_space()->get_static_global_body(); + } + + GodotBody3D *body_B = body_owner.get_or_null(p_body_B); + ERR_FAIL_NULL(body_B); + + ERR_FAIL_COND(body_A == body_B); + + GodotJoint3D *prev_joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(prev_joint); + + GodotJoint3D *joint = memnew(GodotHingeJoint3D(body_A, body_B, p_pivot_A, p_pivot_B, p_axis_A, p_axis_B)); + + joint->copy_settings_from(prev_joint); + joint_owner.replace(p_joint, joint); + memdelete(prev_joint); +} + +void GodotPhysicsServer3D::hinge_joint_set_param(RID p_joint, HingeJointParam p_param, real_t p_value) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_HINGE); + GodotHingeJoint3D *hinge_joint = static_cast<GodotHingeJoint3D *>(joint); + hinge_joint->set_param(p_param, p_value); +} + +real_t GodotPhysicsServer3D::hinge_joint_get_param(RID p_joint, HingeJointParam p_param) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, 0); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_HINGE, 0); + GodotHingeJoint3D *hinge_joint = static_cast<GodotHingeJoint3D *>(joint); + return hinge_joint->get_param(p_param); +} + +void GodotPhysicsServer3D::hinge_joint_set_flag(RID p_joint, HingeJointFlag p_flag, bool p_enabled) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_HINGE); + GodotHingeJoint3D *hinge_joint = static_cast<GodotHingeJoint3D *>(joint); + hinge_joint->set_flag(p_flag, p_enabled); +} + +bool GodotPhysicsServer3D::hinge_joint_get_flag(RID p_joint, HingeJointFlag p_flag) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, false); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_HINGE, false); + GodotHingeJoint3D *hinge_joint = static_cast<GodotHingeJoint3D *>(joint); + return hinge_joint->get_flag(p_flag); +} + +void GodotPhysicsServer3D::joint_set_solver_priority(RID p_joint, int p_priority) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + joint->set_priority(p_priority); +} + +int GodotPhysicsServer3D::joint_get_solver_priority(RID p_joint) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, 0); + return joint->get_priority(); +} + +void GodotPhysicsServer3D::joint_disable_collisions_between_bodies(RID p_joint, bool p_disable) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + + joint->disable_collisions_between_bodies(p_disable); + + if (2 == joint->get_body_count()) { + GodotBody3D *body_a = *joint->get_body_ptr(); + GodotBody3D *body_b = *(joint->get_body_ptr() + 1); + + if (p_disable) { + body_add_collision_exception(body_a->get_self(), body_b->get_self()); + body_add_collision_exception(body_b->get_self(), body_a->get_self()); + } else { + body_remove_collision_exception(body_a->get_self(), body_b->get_self()); + body_remove_collision_exception(body_b->get_self(), body_a->get_self()); + } + } +} + +bool GodotPhysicsServer3D::joint_is_disabled_collisions_between_bodies(RID p_joint) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, true); + + return joint->is_disabled_collisions_between_bodies(); +} + +GodotPhysicsServer3D::JointType GodotPhysicsServer3D::joint_get_type(RID p_joint) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, JOINT_TYPE_PIN); + return joint->get_type(); +} + +void GodotPhysicsServer3D::joint_make_slider(RID p_joint, RID p_body_A, const Transform3D &p_local_frame_A, RID p_body_B, const Transform3D &p_local_frame_B) { + GodotBody3D *body_A = body_owner.get_or_null(p_body_A); + ERR_FAIL_NULL(body_A); + + if (!p_body_B.is_valid()) { + ERR_FAIL_NULL(body_A->get_space()); + p_body_B = body_A->get_space()->get_static_global_body(); + } + + GodotBody3D *body_B = body_owner.get_or_null(p_body_B); + ERR_FAIL_NULL(body_B); + + ERR_FAIL_COND(body_A == body_B); + + GodotJoint3D *prev_joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(prev_joint); + + GodotJoint3D *joint = memnew(GodotSliderJoint3D(body_A, body_B, p_local_frame_A, p_local_frame_B)); + + joint->copy_settings_from(prev_joint); + joint_owner.replace(p_joint, joint); + memdelete(prev_joint); +} + +void GodotPhysicsServer3D::slider_joint_set_param(RID p_joint, SliderJointParam p_param, real_t p_value) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_SLIDER); + GodotSliderJoint3D *slider_joint = static_cast<GodotSliderJoint3D *>(joint); + slider_joint->set_param(p_param, p_value); +} + +real_t GodotPhysicsServer3D::slider_joint_get_param(RID p_joint, SliderJointParam p_param) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, 0); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_CONE_TWIST, 0); + GodotSliderJoint3D *slider_joint = static_cast<GodotSliderJoint3D *>(joint); + return slider_joint->get_param(p_param); +} + +void GodotPhysicsServer3D::joint_make_cone_twist(RID p_joint, RID p_body_A, const Transform3D &p_local_frame_A, RID p_body_B, const Transform3D &p_local_frame_B) { + GodotBody3D *body_A = body_owner.get_or_null(p_body_A); + ERR_FAIL_NULL(body_A); + + if (!p_body_B.is_valid()) { + ERR_FAIL_NULL(body_A->get_space()); + p_body_B = body_A->get_space()->get_static_global_body(); + } + + GodotBody3D *body_B = body_owner.get_or_null(p_body_B); + ERR_FAIL_NULL(body_B); + + ERR_FAIL_COND(body_A == body_B); + + GodotJoint3D *prev_joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(prev_joint); + + GodotJoint3D *joint = memnew(GodotConeTwistJoint3D(body_A, body_B, p_local_frame_A, p_local_frame_B)); + + joint->copy_settings_from(prev_joint); + joint_owner.replace(p_joint, joint); + memdelete(prev_joint); +} + +void GodotPhysicsServer3D::cone_twist_joint_set_param(RID p_joint, ConeTwistJointParam p_param, real_t p_value) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_CONE_TWIST); + GodotConeTwistJoint3D *cone_twist_joint = static_cast<GodotConeTwistJoint3D *>(joint); + cone_twist_joint->set_param(p_param, p_value); +} + +real_t GodotPhysicsServer3D::cone_twist_joint_get_param(RID p_joint, ConeTwistJointParam p_param) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, 0); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_CONE_TWIST, 0); + GodotConeTwistJoint3D *cone_twist_joint = static_cast<GodotConeTwistJoint3D *>(joint); + return cone_twist_joint->get_param(p_param); +} + +void GodotPhysicsServer3D::joint_make_generic_6dof(RID p_joint, RID p_body_A, const Transform3D &p_local_frame_A, RID p_body_B, const Transform3D &p_local_frame_B) { + GodotBody3D *body_A = body_owner.get_or_null(p_body_A); + ERR_FAIL_NULL(body_A); + + if (!p_body_B.is_valid()) { + ERR_FAIL_NULL(body_A->get_space()); + p_body_B = body_A->get_space()->get_static_global_body(); + } + + GodotBody3D *body_B = body_owner.get_or_null(p_body_B); + ERR_FAIL_NULL(body_B); + + ERR_FAIL_COND(body_A == body_B); + + GodotJoint3D *prev_joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(prev_joint); + + GodotJoint3D *joint = memnew(GodotGeneric6DOFJoint3D(body_A, body_B, p_local_frame_A, p_local_frame_B, true)); + + joint->copy_settings_from(prev_joint); + joint_owner.replace(p_joint, joint); + memdelete(prev_joint); +} + +void GodotPhysicsServer3D::generic_6dof_joint_set_param(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisParam p_param, real_t p_value) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_6DOF); + GodotGeneric6DOFJoint3D *generic_6dof_joint = static_cast<GodotGeneric6DOFJoint3D *>(joint); + generic_6dof_joint->set_param(p_axis, p_param, p_value); +} + +real_t GodotPhysicsServer3D::generic_6dof_joint_get_param(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisParam p_param) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, 0); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_6DOF, 0); + GodotGeneric6DOFJoint3D *generic_6dof_joint = static_cast<GodotGeneric6DOFJoint3D *>(joint); + return generic_6dof_joint->get_param(p_axis, p_param); +} + +void GodotPhysicsServer3D::generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisFlag p_flag, bool p_enable) { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_6DOF); + GodotGeneric6DOFJoint3D *generic_6dof_joint = static_cast<GodotGeneric6DOFJoint3D *>(joint); + generic_6dof_joint->set_flag(p_axis, p_flag, p_enable); +} + +bool GodotPhysicsServer3D::generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis p_axis, G6DOFJointAxisFlag p_flag) const { + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, false); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_6DOF, false); + GodotGeneric6DOFJoint3D *generic_6dof_joint = static_cast<GodotGeneric6DOFJoint3D *>(joint); + return generic_6dof_joint->get_flag(p_axis, p_flag); +} + +void GodotPhysicsServer3D::free(RID p_rid) { + _update_shapes(); //just in case + + if (shape_owner.owns(p_rid)) { + GodotShape3D *shape = shape_owner.get_or_null(p_rid); + + while (shape->get_owners().size()) { + GodotShapeOwner3D *so = shape->get_owners().begin()->key; + so->remove_shape(shape); + } + + shape_owner.free(p_rid); + memdelete(shape); + } else if (body_owner.owns(p_rid)) { + GodotBody3D *body = body_owner.get_or_null(p_rid); + + body->set_space(nullptr); + + while (body->get_shape_count()) { + body->remove_shape(0); + } + + body_owner.free(p_rid); + memdelete(body); + } else if (soft_body_owner.owns(p_rid)) { + GodotSoftBody3D *soft_body = soft_body_owner.get_or_null(p_rid); + + soft_body->set_space(nullptr); + + soft_body_owner.free(p_rid); + memdelete(soft_body); + } else if (area_owner.owns(p_rid)) { + GodotArea3D *area = area_owner.get_or_null(p_rid); + + area->set_space(nullptr); + + while (area->get_shape_count()) { + area->remove_shape(0); + } + + area_owner.free(p_rid); + memdelete(area); + } else if (space_owner.owns(p_rid)) { + GodotSpace3D *space = space_owner.get_or_null(p_rid); + + while (space->get_objects().size()) { + GodotCollisionObject3D *co = static_cast<GodotCollisionObject3D *>(*space->get_objects().begin()); + co->set_space(nullptr); + } + + active_spaces.erase(space); + free(space->get_default_area()->get_self()); + free(space->get_static_global_body()); + + space_owner.free(p_rid); + memdelete(space); + } else if (joint_owner.owns(p_rid)) { + GodotJoint3D *joint = joint_owner.get_or_null(p_rid); + + joint_owner.free(p_rid); + memdelete(joint); + + } else { + ERR_FAIL_MSG("Invalid ID."); + } +} + +void GodotPhysicsServer3D::set_active(bool p_active) { + active = p_active; +} + +void GodotPhysicsServer3D::init() { + stepper = memnew(GodotStep3D); +} + +void GodotPhysicsServer3D::step(real_t p_step) { + if (!active) { + return; + } + + _update_shapes(); + + island_count = 0; + active_objects = 0; + collision_pairs = 0; + for (const GodotSpace3D *E : active_spaces) { + stepper->step(const_cast<GodotSpace3D *>(E), p_step); + island_count += E->get_island_count(); + active_objects += E->get_active_objects(); + collision_pairs += E->get_collision_pairs(); + } +} + +void GodotPhysicsServer3D::sync() { + doing_sync = true; +} + +void GodotPhysicsServer3D::flush_queries() { + if (!active) { + return; + } + + flushing_queries = true; + + uint64_t time_beg = OS::get_singleton()->get_ticks_usec(); + + for (const GodotSpace3D *E : active_spaces) { + GodotSpace3D *space = const_cast<GodotSpace3D *>(E); + space->call_queries(); + } + + flushing_queries = false; + + if (EngineDebugger::is_profiling("servers")) { + uint64_t total_time[GodotSpace3D::ELAPSED_TIME_MAX]; + static const char *time_name[GodotSpace3D::ELAPSED_TIME_MAX] = { + "integrate_forces", + "generate_islands", + "setup_constraints", + "solve_constraints", + "integrate_velocities" + }; + + for (int i = 0; i < GodotSpace3D::ELAPSED_TIME_MAX; i++) { + total_time[i] = 0; + } + + for (const GodotSpace3D *E : active_spaces) { + for (int i = 0; i < GodotSpace3D::ELAPSED_TIME_MAX; i++) { + total_time[i] += E->get_elapsed_time(GodotSpace3D::ElapsedTime(i)); + } + } + + Array values; + values.resize(GodotSpace3D::ELAPSED_TIME_MAX * 2); + for (int i = 0; i < GodotSpace3D::ELAPSED_TIME_MAX; i++) { + values[i * 2 + 0] = time_name[i]; + values[i * 2 + 1] = USEC_TO_SEC(total_time[i]); + } + values.push_back("flush_queries"); + values.push_back(USEC_TO_SEC(OS::get_singleton()->get_ticks_usec() - time_beg)); + + values.push_front("physics_3d"); + EngineDebugger::profiler_add_frame_data("servers", values); + } +} + +void GodotPhysicsServer3D::end_sync() { + doing_sync = false; +} + +void GodotPhysicsServer3D::finish() { + memdelete(stepper); +} + +int GodotPhysicsServer3D::get_process_info(ProcessInfo p_info) { + switch (p_info) { + case INFO_ACTIVE_OBJECTS: { + return active_objects; + } break; + case INFO_COLLISION_PAIRS: { + return collision_pairs; + } break; + case INFO_ISLAND_COUNT: { + return island_count; + } break; + } + + return 0; +} + +void GodotPhysicsServer3D::_update_shapes() { + while (pending_shape_update_list.first()) { + pending_shape_update_list.first()->self()->_shape_changed(); + pending_shape_update_list.remove(pending_shape_update_list.first()); + } +} + +void GodotPhysicsServer3D::_shape_col_cbk(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata) { + CollCbkData *cbk = static_cast<CollCbkData *>(p_userdata); + + if (cbk->max == 0) { + return; + } + + if (cbk->amount == cbk->max) { + //find least deep + real_t min_depth = 1e20; + int min_depth_idx = 0; + for (int i = 0; i < cbk->amount; i++) { + real_t d = cbk->ptr[i * 2 + 0].distance_squared_to(cbk->ptr[i * 2 + 1]); + if (d < min_depth) { + min_depth = d; + min_depth_idx = i; + } + } + + real_t d = p_point_A.distance_squared_to(p_point_B); + if (d < min_depth) { + return; + } + cbk->ptr[min_depth_idx * 2 + 0] = p_point_A; + cbk->ptr[min_depth_idx * 2 + 1] = p_point_B; + + } else { + cbk->ptr[cbk->amount * 2 + 0] = p_point_A; + cbk->ptr[cbk->amount * 2 + 1] = p_point_B; + cbk->amount++; + } +} + +GodotPhysicsServer3D *GodotPhysicsServer3D::godot_singleton = nullptr; +GodotPhysicsServer3D::GodotPhysicsServer3D(bool p_using_threads) { + godot_singleton = this; + GodotBroadPhase3D::create_func = GodotBroadPhase3DBVH::_create; + + using_threads = p_using_threads; +}; diff --git a/modules/godot_physics_3d/godot_physics_server_3d.h b/modules/godot_physics_3d/godot_physics_server_3d.h new file mode 100644 index 0000000000..040e673dcd --- /dev/null +++ b/modules/godot_physics_3d/godot_physics_server_3d.h @@ -0,0 +1,385 @@ +/**************************************************************************/ +/* godot_physics_server_3d.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 GODOT_PHYSICS_SERVER_3D_H +#define GODOT_PHYSICS_SERVER_3D_H + +#include "godot_joint_3d.h" +#include "godot_shape_3d.h" +#include "godot_space_3d.h" +#include "godot_step_3d.h" + +#include "core/templates/rid_owner.h" +#include "servers/physics_server_3d.h" + +class GodotPhysicsServer3D : public PhysicsServer3D { + GDCLASS(GodotPhysicsServer3D, PhysicsServer3D); + + friend class GodotPhysicsDirectSpaceState3D; + bool active = true; + + int island_count = 0; + int active_objects = 0; + int collision_pairs = 0; + + bool using_threads = false; + bool doing_sync = false; + bool flushing_queries = false; + + GodotStep3D *stepper = nullptr; + HashSet<const GodotSpace3D *> active_spaces; + + mutable RID_PtrOwner<GodotShape3D, true> shape_owner; + mutable RID_PtrOwner<GodotSpace3D, true> space_owner; + mutable RID_PtrOwner<GodotArea3D, true> area_owner; + mutable RID_PtrOwner<GodotBody3D, true> body_owner; + mutable RID_PtrOwner<GodotSoftBody3D, true> soft_body_owner; + mutable RID_PtrOwner<GodotJoint3D, true> joint_owner; + + //void _clear_query(QuerySW *p_query); + friend class GodotCollisionObject3D; + SelfList<GodotCollisionObject3D>::List pending_shape_update_list; + void _update_shapes(); + + static GodotPhysicsServer3D *godot_singleton; + +public: + struct CollCbkData { + int max; + int amount; + Vector3 *ptr = nullptr; + }; + + static void _shape_col_cbk(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata); + + virtual RID world_boundary_shape_create() override; + virtual RID separation_ray_shape_create() override; + virtual RID sphere_shape_create() override; + virtual RID box_shape_create() override; + virtual RID capsule_shape_create() override; + virtual RID cylinder_shape_create() override; + virtual RID convex_polygon_shape_create() override; + virtual RID concave_polygon_shape_create() override; + virtual RID heightmap_shape_create() override; + virtual RID custom_shape_create() override; + + virtual void shape_set_data(RID p_shape, const Variant &p_data) override; + virtual void shape_set_custom_solver_bias(RID p_shape, real_t p_bias) override; + + virtual ShapeType shape_get_type(RID p_shape) const override; + virtual Variant shape_get_data(RID p_shape) const override; + + virtual void shape_set_margin(RID p_shape, real_t p_margin) override; + virtual real_t shape_get_margin(RID p_shape) const override; + + virtual real_t shape_get_custom_solver_bias(RID p_shape) const override; + + /* SPACE API */ + + virtual RID space_create() override; + virtual void space_set_active(RID p_space, bool p_active) override; + virtual bool space_is_active(RID p_space) const override; + + virtual void space_set_param(RID p_space, SpaceParameter p_param, real_t p_value) override; + virtual real_t space_get_param(RID p_space, SpaceParameter p_param) const override; + + // this function only works on physics process, errors and returns null otherwise + virtual PhysicsDirectSpaceState3D *space_get_direct_state(RID p_space) override; + + virtual void space_set_debug_contacts(RID p_space, int p_max_contacts) override; + virtual Vector<Vector3> space_get_contacts(RID p_space) const override; + virtual int space_get_contact_count(RID p_space) const override; + + /* AREA API */ + + virtual RID area_create() override; + + virtual void area_set_space(RID p_area, RID p_space) override; + virtual RID area_get_space(RID p_area) const override; + + virtual void area_add_shape(RID p_area, RID p_shape, const Transform3D &p_transform = Transform3D(), bool p_disabled = false) override; + virtual void area_set_shape(RID p_area, int p_shape_idx, RID p_shape) override; + virtual void area_set_shape_transform(RID p_area, int p_shape_idx, const Transform3D &p_transform) override; + + virtual int area_get_shape_count(RID p_area) const override; + virtual RID area_get_shape(RID p_area, int p_shape_idx) const override; + virtual Transform3D area_get_shape_transform(RID p_area, int p_shape_idx) const override; + + virtual void area_remove_shape(RID p_area, int p_shape_idx) override; + virtual void area_clear_shapes(RID p_area) override; + + virtual void area_set_shape_disabled(RID p_area, int p_shape_idx, bool p_disabled) override; + + virtual void area_attach_object_instance_id(RID p_area, ObjectID p_id) override; + virtual ObjectID area_get_object_instance_id(RID p_area) const override; + + virtual void area_set_param(RID p_area, AreaParameter p_param, const Variant &p_value) override; + virtual void area_set_transform(RID p_area, const Transform3D &p_transform) override; + + virtual Variant area_get_param(RID p_area, AreaParameter p_param) const override; + virtual Transform3D area_get_transform(RID p_area) const override; + + virtual void area_set_ray_pickable(RID p_area, bool p_enable) override; + + virtual void area_set_collision_layer(RID p_area, uint32_t p_layer) override; + virtual uint32_t area_get_collision_layer(RID p_area) const override; + + virtual void area_set_collision_mask(RID p_area, uint32_t p_mask) override; + virtual uint32_t area_get_collision_mask(RID p_area) const override; + + virtual void area_set_monitorable(RID p_area, bool p_monitorable) override; + + virtual void area_set_monitor_callback(RID p_area, const Callable &p_callback) override; + virtual void area_set_area_monitor_callback(RID p_area, const Callable &p_callback) override; + + /* BODY API */ + + // create a body of a given type + virtual RID body_create() override; + + virtual void body_set_space(RID p_body, RID p_space) override; + virtual RID body_get_space(RID p_body) const override; + + virtual void body_set_mode(RID p_body, BodyMode p_mode) override; + virtual BodyMode body_get_mode(RID p_body) const override; + + virtual void body_add_shape(RID p_body, RID p_shape, const Transform3D &p_transform = Transform3D(), bool p_disabled = false) override; + virtual void body_set_shape(RID p_body, int p_shape_idx, RID p_shape) override; + virtual void body_set_shape_transform(RID p_body, int p_shape_idx, const Transform3D &p_transform) override; + + virtual int body_get_shape_count(RID p_body) const override; + virtual RID body_get_shape(RID p_body, int p_shape_idx) const override; + virtual Transform3D body_get_shape_transform(RID p_body, int p_shape_idx) const override; + + virtual void body_set_shape_disabled(RID p_body, int p_shape_idx, bool p_disabled) override; + + virtual void body_remove_shape(RID p_body, int p_shape_idx) override; + virtual void body_clear_shapes(RID p_body) override; + + virtual void body_attach_object_instance_id(RID p_body, ObjectID p_id) override; + virtual ObjectID body_get_object_instance_id(RID p_body) const override; + + virtual void body_set_enable_continuous_collision_detection(RID p_body, bool p_enable) override; + virtual bool body_is_continuous_collision_detection_enabled(RID p_body) const override; + + virtual void body_set_collision_layer(RID p_body, uint32_t p_layer) override; + virtual uint32_t body_get_collision_layer(RID p_body) const override; + + virtual void body_set_collision_mask(RID p_body, uint32_t p_mask) override; + virtual uint32_t body_get_collision_mask(RID p_body) const override; + + virtual void body_set_collision_priority(RID p_body, real_t p_priority) override; + virtual real_t body_get_collision_priority(RID p_body) const override; + + virtual void body_set_user_flags(RID p_body, uint32_t p_flags) override; + virtual uint32_t body_get_user_flags(RID p_body) const override; + + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) override; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const override; + + virtual void body_reset_mass_properties(RID p_body) override; + + virtual void body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override; + virtual Variant body_get_state(RID p_body, BodyState p_state) const override; + + virtual void body_apply_central_impulse(RID p_body, const Vector3 &p_impulse) override; + virtual void body_apply_impulse(RID p_body, const Vector3 &p_impulse, const Vector3 &p_position = Vector3()) override; + virtual void body_apply_torque_impulse(RID p_body, const Vector3 &p_impulse) override; + + virtual void body_apply_central_force(RID p_body, const Vector3 &p_force) override; + virtual void body_apply_force(RID p_body, const Vector3 &p_force, const Vector3 &p_position = Vector3()) override; + virtual void body_apply_torque(RID p_body, const Vector3 &p_torque) override; + + virtual void body_add_constant_central_force(RID p_body, const Vector3 &p_force) override; + virtual void body_add_constant_force(RID p_body, const Vector3 &p_force, const Vector3 &p_position = Vector3()) override; + virtual void body_add_constant_torque(RID p_body, const Vector3 &p_torque) override; + + virtual void body_set_constant_force(RID p_body, const Vector3 &p_force) override; + virtual Vector3 body_get_constant_force(RID p_body) const override; + + virtual void body_set_constant_torque(RID p_body, const Vector3 &p_torque) override; + virtual Vector3 body_get_constant_torque(RID p_body) const override; + + virtual void body_set_axis_velocity(RID p_body, const Vector3 &p_axis_velocity) override; + + virtual void body_set_axis_lock(RID p_body, BodyAxis p_axis, bool p_lock) override; + virtual bool body_is_axis_locked(RID p_body, BodyAxis p_axis) const override; + + virtual void body_add_collision_exception(RID p_body, RID p_body_b) override; + virtual void body_remove_collision_exception(RID p_body, RID p_body_b) override; + virtual void body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) override; + + virtual void body_set_contacts_reported_depth_threshold(RID p_body, real_t p_threshold) override; + virtual real_t body_get_contacts_reported_depth_threshold(RID p_body) const override; + + virtual void body_set_omit_force_integration(RID p_body, bool p_omit) override; + virtual bool body_is_omitting_force_integration(RID p_body) const override; + + virtual void body_set_max_contacts_reported(RID p_body, int p_contacts) override; + virtual int body_get_max_contacts_reported(RID p_body) const override; + + virtual void body_set_state_sync_callback(RID p_body, const Callable &p_callable) override; + virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) override; + + virtual void body_set_ray_pickable(RID p_body, bool p_enable) override; + + virtual bool body_test_motion(RID p_body, const MotionParameters &p_parameters, MotionResult *r_result = nullptr) override; + + // this function only works on physics process, errors and returns null otherwise + virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override; + + /* SOFT BODY */ + + virtual RID soft_body_create() override; + + virtual void soft_body_update_rendering_server(RID p_body, PhysicsServer3DRenderingServerHandler *p_rendering_server_handler) override; + + virtual void soft_body_set_space(RID p_body, RID p_space) override; + virtual RID soft_body_get_space(RID p_body) const override; + + virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer) override; + virtual uint32_t soft_body_get_collision_layer(RID p_body) const override; + + virtual void soft_body_set_collision_mask(RID p_body, uint32_t p_mask) override; + virtual uint32_t soft_body_get_collision_mask(RID p_body) const override; + + virtual void soft_body_add_collision_exception(RID p_body, RID p_body_b) override; + virtual void soft_body_remove_collision_exception(RID p_body, RID p_body_b) override; + virtual void soft_body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) override; + + virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override; + virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const override; + + virtual void soft_body_set_transform(RID p_body, const Transform3D &p_transform) override; + + virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable) override; + + virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) override; + virtual int soft_body_get_simulation_precision(RID p_body) const override; + + virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass) override; + virtual real_t soft_body_get_total_mass(RID p_body) const override; + + virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) override; + virtual real_t soft_body_get_linear_stiffness(RID p_body) const override; + + virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) override; + virtual real_t soft_body_get_pressure_coefficient(RID p_body) const override; + + virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) override; + virtual real_t soft_body_get_damping_coefficient(RID p_body) const override; + + virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) override; + virtual real_t soft_body_get_drag_coefficient(RID p_body) const override; + + virtual void soft_body_set_mesh(RID p_body, RID p_mesh) override; + + virtual AABB soft_body_get_bounds(RID p_body) const override; + + virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) override; + virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) const override; + + virtual void soft_body_remove_all_pinned_points(RID p_body) override; + virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) override; + virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) const override; + + /* JOINT API */ + + virtual RID joint_create() override; + + virtual void joint_clear(RID p_joint) override; //resets type + + virtual void joint_make_pin(RID p_joint, RID p_body_A, const Vector3 &p_local_A, RID p_body_B, const Vector3 &p_local_B) override; + + virtual void pin_joint_set_param(RID p_joint, PinJointParam p_param, real_t p_value) override; + virtual real_t pin_joint_get_param(RID p_joint, PinJointParam p_param) const override; + + virtual void pin_joint_set_local_a(RID p_joint, const Vector3 &p_A) override; + virtual Vector3 pin_joint_get_local_a(RID p_joint) const override; + + virtual void pin_joint_set_local_b(RID p_joint, const Vector3 &p_B) override; + virtual Vector3 pin_joint_get_local_b(RID p_joint) const override; + + virtual void joint_make_hinge(RID p_joint, RID p_body_A, const Transform3D &p_frame_A, RID p_body_B, const Transform3D &p_frame_B) override; + virtual void joint_make_hinge_simple(RID p_joint, RID p_body_A, const Vector3 &p_pivot_A, const Vector3 &p_axis_A, RID p_body_B, const Vector3 &p_pivot_B, const Vector3 &p_axis_B) override; + + virtual void hinge_joint_set_param(RID p_joint, HingeJointParam p_param, real_t p_value) override; + virtual real_t hinge_joint_get_param(RID p_joint, HingeJointParam p_param) const override; + + virtual void hinge_joint_set_flag(RID p_joint, HingeJointFlag p_flag, bool p_value) override; + virtual bool hinge_joint_get_flag(RID p_joint, HingeJointFlag p_flag) const override; + + virtual void joint_make_slider(RID p_joint, RID p_body_A, const Transform3D &p_local_frame_A, RID p_body_B, const Transform3D &p_local_frame_B) override; //reference frame is A + + virtual void slider_joint_set_param(RID p_joint, SliderJointParam p_param, real_t p_value) override; + virtual real_t slider_joint_get_param(RID p_joint, SliderJointParam p_param) const override; + + virtual void joint_make_cone_twist(RID p_joint, RID p_body_A, const Transform3D &p_local_frame_A, RID p_body_B, const Transform3D &p_local_frame_B) override; //reference frame is A + + virtual void cone_twist_joint_set_param(RID p_joint, ConeTwistJointParam p_param, real_t p_value) override; + virtual real_t cone_twist_joint_get_param(RID p_joint, ConeTwistJointParam p_param) const override; + + virtual void joint_make_generic_6dof(RID p_joint, RID p_body_A, const Transform3D &p_local_frame_A, RID p_body_B, const Transform3D &p_local_frame_B) override; //reference frame is A + + virtual void generic_6dof_joint_set_param(RID p_joint, Vector3::Axis, G6DOFJointAxisParam p_param, real_t p_value) override; + virtual real_t generic_6dof_joint_get_param(RID p_joint, Vector3::Axis, G6DOFJointAxisParam p_param) const override; + + virtual void generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag, bool p_enable) override; + virtual bool generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag) const override; + + virtual JointType joint_get_type(RID p_joint) const override; + + virtual void joint_set_solver_priority(RID p_joint, int p_priority) override; + virtual int joint_get_solver_priority(RID p_joint) const override; + + virtual void joint_disable_collisions_between_bodies(RID p_joint, bool p_disable) override; + virtual bool joint_is_disabled_collisions_between_bodies(RID p_joint) const override; + + /* MISC */ + + virtual void free(RID p_rid) override; + + virtual void set_active(bool p_active) override; + virtual void init() override; + virtual void step(real_t p_step) override; + virtual void sync() override; + virtual void flush_queries() override; + virtual void end_sync() override; + virtual void finish() override; + + virtual bool is_flushing_queries() const override { return flushing_queries; } + + int get_process_info(ProcessInfo p_info) override; + + GodotPhysicsServer3D(bool p_using_threads = false); + ~GodotPhysicsServer3D() {} +}; + +#endif // GODOT_PHYSICS_SERVER_3D_H diff --git a/modules/godot_physics_3d/godot_shape_3d.cpp b/modules/godot_physics_3d/godot_shape_3d.cpp new file mode 100644 index 0000000000..70b6bcf19e --- /dev/null +++ b/modules/godot_physics_3d/godot_shape_3d.cpp @@ -0,0 +1,2265 @@ +/**************************************************************************/ +/* godot_shape_3d.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 "godot_shape_3d.h" + +#include "core/io/image.h" +#include "core/math/convex_hull.h" +#include "core/math/geometry_3d.h" +#include "core/templates/sort_array.h" + +// GodotHeightMapShape3D is based on Bullet btHeightfieldTerrainShape. + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +const double edge_support_threshold = 0.99999998; +const double edge_support_threshold_lower = Math::sqrt(1.0 - edge_support_threshold * edge_support_threshold); +// For a unit normal vector n, the horizontality condition +// sqrt(n.x * n.x + n.z * n.z) > edge_support_threshold +// is equivalent to the condition +// abs(n.y) < edge_support_threshold_lower, +// which is cheaper to test. +const double face_support_threshold = 0.9998; + +const double cylinder_edge_support_threshold = 0.999998; +const double cylinder_edge_support_threshold_lower = Math::sqrt(1.0 - cylinder_edge_support_threshold * cylinder_edge_support_threshold); +const double cylinder_face_support_threshold = 0.999; + +void GodotShape3D::configure(const AABB &p_aabb) { + aabb = p_aabb; + configured = true; + for (const KeyValue<GodotShapeOwner3D *, int> &E : owners) { + GodotShapeOwner3D *co = const_cast<GodotShapeOwner3D *>(E.key); + co->_shape_changed(); + } +} + +Vector3 GodotShape3D::get_support(const Vector3 &p_normal) const { + Vector3 res; + int amnt; + FeatureType type; + get_supports(p_normal, 1, &res, amnt, type); + return res; +} + +void GodotShape3D::add_owner(GodotShapeOwner3D *p_owner) { + HashMap<GodotShapeOwner3D *, int>::Iterator E = owners.find(p_owner); + if (E) { + E->value++; + } else { + owners[p_owner] = 1; + } +} + +void GodotShape3D::remove_owner(GodotShapeOwner3D *p_owner) { + HashMap<GodotShapeOwner3D *, int>::Iterator E = owners.find(p_owner); + ERR_FAIL_COND(!E); + E->value--; + if (E->value == 0) { + owners.remove(E); + } +} + +bool GodotShape3D::is_owner(GodotShapeOwner3D *p_owner) const { + return owners.has(p_owner); +} + +const HashMap<GodotShapeOwner3D *, int> &GodotShape3D::get_owners() const { + return owners; +} + +GodotShape3D::~GodotShape3D() { + ERR_FAIL_COND(owners.size()); +} + +Plane GodotWorldBoundaryShape3D::get_plane() const { + return plane; +} + +void GodotWorldBoundaryShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + // gibberish, a plane is infinity + r_min = -1e7; + r_max = 1e7; +} + +Vector3 GodotWorldBoundaryShape3D::get_support(const Vector3 &p_normal) const { + return p_normal * 1e15; +} + +bool GodotWorldBoundaryShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + bool inters = plane.intersects_segment(p_begin, p_end, &r_result); + if (inters) { + r_normal = plane.normal; + } + return inters; +} + +bool GodotWorldBoundaryShape3D::intersect_point(const Vector3 &p_point) const { + return plane.distance_to(p_point) < 0; +} + +Vector3 GodotWorldBoundaryShape3D::get_closest_point_to(const Vector3 &p_point) const { + if (plane.is_point_over(p_point)) { + return plane.project(p_point); + } else { + return p_point; + } +} + +Vector3 GodotWorldBoundaryShape3D::get_moment_of_inertia(real_t p_mass) const { + return Vector3(); // not applicable. +} + +void GodotWorldBoundaryShape3D::_setup(const Plane &p_plane) { + plane = p_plane; + configure(AABB(Vector3(-1e15, -1e15, -1e15), Vector3(1e15 * 2, 1e15 * 2, 1e15 * 2))); +} + +void GodotWorldBoundaryShape3D::set_data(const Variant &p_data) { + _setup(p_data); +} + +Variant GodotWorldBoundaryShape3D::get_data() const { + return plane; +} + +GodotWorldBoundaryShape3D::GodotWorldBoundaryShape3D() { +} + +// + +real_t GodotSeparationRayShape3D::get_length() const { + return length; +} + +bool GodotSeparationRayShape3D::get_slide_on_slope() const { + return slide_on_slope; +} + +void GodotSeparationRayShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + // don't think this will be even used + r_min = 0; + r_max = 1; +} + +Vector3 GodotSeparationRayShape3D::get_support(const Vector3 &p_normal) const { + if (p_normal.z > 0) { + return Vector3(0, 0, length); + } else { + return Vector3(0, 0, 0); + } +} + +void GodotSeparationRayShape3D::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + if (Math::abs(p_normal.z) < edge_support_threshold_lower) { + r_amount = 2; + r_type = FEATURE_EDGE; + r_supports[0] = Vector3(0, 0, 0); + r_supports[1] = Vector3(0, 0, length); + } else if (p_normal.z > 0) { + r_amount = 1; + r_type = FEATURE_POINT; + *r_supports = Vector3(0, 0, length); + } else { + r_amount = 1; + r_type = FEATURE_POINT; + *r_supports = Vector3(0, 0, 0); + } +} + +bool GodotSeparationRayShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + return false; //simply not possible +} + +bool GodotSeparationRayShape3D::intersect_point(const Vector3 &p_point) const { + return false; //simply not possible +} + +Vector3 GodotSeparationRayShape3D::get_closest_point_to(const Vector3 &p_point) const { + Vector3 s[2] = { + Vector3(0, 0, 0), + Vector3(0, 0, length) + }; + + return Geometry3D::get_closest_point_to_segment(p_point, s); +} + +Vector3 GodotSeparationRayShape3D::get_moment_of_inertia(real_t p_mass) const { + return Vector3(); +} + +void GodotSeparationRayShape3D::_setup(real_t p_length, bool p_slide_on_slope) { + length = p_length; + slide_on_slope = p_slide_on_slope; + configure(AABB(Vector3(0, 0, 0), Vector3(0.1, 0.1, length))); +} + +void GodotSeparationRayShape3D::set_data(const Variant &p_data) { + Dictionary d = p_data; + _setup(d["length"], d["slide_on_slope"]); +} + +Variant GodotSeparationRayShape3D::get_data() const { + Dictionary d; + d["length"] = length; + d["slide_on_slope"] = slide_on_slope; + return d; +} + +GodotSeparationRayShape3D::GodotSeparationRayShape3D() {} + +/********** SPHERE *************/ + +real_t GodotSphereShape3D::get_radius() const { + return radius; +} + +void GodotSphereShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + real_t d = p_normal.dot(p_transform.origin); + + // figure out scale at point + Vector3 local_normal = p_transform.basis.xform_inv(p_normal); + real_t scale = local_normal.length(); + + r_min = d - (radius)*scale; + r_max = d + (radius)*scale; +} + +Vector3 GodotSphereShape3D::get_support(const Vector3 &p_normal) const { + return p_normal * radius; +} + +void GodotSphereShape3D::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + *r_supports = p_normal * radius; + r_amount = 1; + r_type = FEATURE_POINT; +} + +bool GodotSphereShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + return Geometry3D::segment_intersects_sphere(p_begin, p_end, Vector3(), radius, &r_result, &r_normal); +} + +bool GodotSphereShape3D::intersect_point(const Vector3 &p_point) const { + return p_point.length() < radius; +} + +Vector3 GodotSphereShape3D::get_closest_point_to(const Vector3 &p_point) const { + Vector3 p = p_point; + real_t l = p.length(); + if (l < radius) { + return p_point; + } + return (p / l) * radius; +} + +Vector3 GodotSphereShape3D::get_moment_of_inertia(real_t p_mass) const { + real_t s = 0.4 * p_mass * radius * radius; + return Vector3(s, s, s); +} + +void GodotSphereShape3D::_setup(real_t p_radius) { + radius = p_radius; + configure(AABB(Vector3(-radius, -radius, -radius), Vector3(radius * 2.0, radius * 2.0, radius * 2.0))); +} + +void GodotSphereShape3D::set_data(const Variant &p_data) { + _setup(p_data); +} + +Variant GodotSphereShape3D::get_data() const { + return radius; +} + +GodotSphereShape3D::GodotSphereShape3D() {} + +/********** BOX *************/ + +void GodotBoxShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + // no matter the angle, the box is mirrored anyway + Vector3 local_normal = p_transform.basis.xform_inv(p_normal); + + real_t length = local_normal.abs().dot(half_extents); + real_t distance = p_normal.dot(p_transform.origin); + + r_min = distance - length; + r_max = distance + length; +} + +Vector3 GodotBoxShape3D::get_support(const Vector3 &p_normal) const { + Vector3 point( + (p_normal.x < 0) ? -half_extents.x : half_extents.x, + (p_normal.y < 0) ? -half_extents.y : half_extents.y, + (p_normal.z < 0) ? -half_extents.z : half_extents.z); + + return point; +} + +void GodotBoxShape3D::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + static const int next[3] = { 1, 2, 0 }; + static const int next2[3] = { 2, 0, 1 }; + + for (int i = 0; i < 3; i++) { + Vector3 axis; + axis[i] = 1.0; + real_t dot = p_normal.dot(axis); + if (Math::abs(dot) > face_support_threshold) { + //Vector3 axis_b; + + bool neg = dot < 0; + r_amount = 4; + r_type = FEATURE_FACE; + + Vector3 point; + point[i] = half_extents[i]; + + int i_n = next[i]; + int i_n2 = next2[i]; + + static const real_t sign[4][2] = { + { -1.0, 1.0 }, + { 1.0, 1.0 }, + { 1.0, -1.0 }, + { -1.0, -1.0 }, + }; + + for (int j = 0; j < 4; j++) { + point[i_n] = sign[j][0] * half_extents[i_n]; + point[i_n2] = sign[j][1] * half_extents[i_n2]; + r_supports[j] = neg ? -point : point; + } + + if (neg) { + SWAP(r_supports[1], r_supports[2]); + SWAP(r_supports[0], r_supports[3]); + } + + return; + } + + r_amount = 0; + } + + for (int i = 0; i < 3; i++) { + Vector3 axis; + axis[i] = 1.0; + + if (Math::abs(p_normal.dot(axis)) < edge_support_threshold_lower) { + r_amount = 2; + r_type = FEATURE_EDGE; + + int i_n = next[i]; + int i_n2 = next2[i]; + + Vector3 point = half_extents; + + if (p_normal[i_n] < 0) { + point[i_n] = -point[i_n]; + } + if (p_normal[i_n2] < 0) { + point[i_n2] = -point[i_n2]; + } + + r_supports[0] = point; + point[i] = -point[i]; + r_supports[1] = point; + return; + } + } + /* USE POINT */ + + Vector3 point( + (p_normal.x < 0) ? -half_extents.x : half_extents.x, + (p_normal.y < 0) ? -half_extents.y : half_extents.y, + (p_normal.z < 0) ? -half_extents.z : half_extents.z); + + r_amount = 1; + r_type = FEATURE_POINT; + r_supports[0] = point; +} + +bool GodotBoxShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + AABB aabb_ext(-half_extents, half_extents * 2.0); + + return aabb_ext.intersects_segment(p_begin, p_end, &r_result, &r_normal); +} + +bool GodotBoxShape3D::intersect_point(const Vector3 &p_point) const { + return (Math::abs(p_point.x) < half_extents.x && Math::abs(p_point.y) < half_extents.y && Math::abs(p_point.z) < half_extents.z); +} + +Vector3 GodotBoxShape3D::get_closest_point_to(const Vector3 &p_point) const { + int outside = 0; + Vector3 min_point; + + for (int i = 0; i < 3; i++) { + if (Math::abs(p_point[i]) > half_extents[i]) { + outside++; + if (outside == 1) { + //use plane if only one side matches + Vector3 n; + n[i] = SIGN(p_point[i]); + + Plane p(n, half_extents[i]); + min_point = p.project(p_point); + } + } + } + + if (!outside) { + return p_point; //it's inside, don't do anything else + } + + if (outside == 1) { //if only above one plane, this plane clearly wins + return min_point; + } + + //check segments + real_t min_distance = 1e20; + Vector3 closest_vertex = half_extents * p_point.sign(); + Vector3 s[2] = { + closest_vertex, + closest_vertex + }; + + for (int i = 0; i < 3; i++) { + s[1] = closest_vertex; + s[1][i] = -s[1][i]; //edge + + Vector3 closest_edge = Geometry3D::get_closest_point_to_segment(p_point, s); + + real_t d = p_point.distance_to(closest_edge); + if (d < min_distance) { + min_point = closest_edge; + min_distance = d; + } + } + + return min_point; +} + +Vector3 GodotBoxShape3D::get_moment_of_inertia(real_t p_mass) const { + real_t lx = half_extents.x; + real_t ly = half_extents.y; + real_t lz = half_extents.z; + + return Vector3((p_mass / 3.0) * (ly * ly + lz * lz), (p_mass / 3.0) * (lx * lx + lz * lz), (p_mass / 3.0) * (lx * lx + ly * ly)); +} + +void GodotBoxShape3D::_setup(const Vector3 &p_half_extents) { + half_extents = p_half_extents.abs(); + + configure(AABB(-half_extents, half_extents * 2)); +} + +void GodotBoxShape3D::set_data(const Variant &p_data) { + _setup(p_data); +} + +Variant GodotBoxShape3D::get_data() const { + return half_extents; +} + +GodotBoxShape3D::GodotBoxShape3D() {} + +/********** CAPSULE *************/ + +void GodotCapsuleShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + Vector3 n = p_transform.basis.xform_inv(p_normal).normalized(); + real_t h = height * 0.5 - radius; + + n *= radius; + n.y += (n.y > 0) ? h : -h; + + r_max = p_normal.dot(p_transform.xform(n)); + r_min = p_normal.dot(p_transform.xform(-n)); +} + +Vector3 GodotCapsuleShape3D::get_support(const Vector3 &p_normal) const { + Vector3 n = p_normal; + + real_t h = height * 0.5 - radius; + + n *= radius; + n.y += (n.y > 0) ? h : -h; + return n; +} + +void GodotCapsuleShape3D::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + Vector3 n = p_normal; + + real_t d = n.y; + real_t h = height * 0.5 - radius; // half-height of the cylinder part + + if (h > 0 && Math::abs(d) < edge_support_threshold_lower) { + // make it flat + n.y = 0.0; + n.normalize(); + n *= radius; + + r_amount = 2; + r_type = FEATURE_EDGE; + r_supports[0] = n; + r_supports[0].y += h; + r_supports[1] = n; + r_supports[1].y -= h; + } else { + n *= radius; + n.y += (d > 0) ? h : -h; + r_amount = 1; + r_type = FEATURE_POINT; + *r_supports = n; + } +} + +bool GodotCapsuleShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + Vector3 norm = (p_end - p_begin).normalized(); + real_t min_d = 1e20; + + Vector3 res, n; + bool collision = false; + + Vector3 auxres, auxn; + bool collided; + + // test against cylinder and spheres :-| + + collided = Geometry3D::segment_intersects_cylinder(p_begin, p_end, height - radius * 2.0, radius, &auxres, &auxn, 1); + + if (collided) { + real_t d = norm.dot(auxres); + if (d < min_d) { + min_d = d; + res = auxres; + n = auxn; + collision = true; + } + } + + collided = Geometry3D::segment_intersects_sphere(p_begin, p_end, Vector3(0, height * 0.5 - radius, 0), radius, &auxres, &auxn); + + if (collided) { + real_t d = norm.dot(auxres); + if (d < min_d) { + min_d = d; + res = auxres; + n = auxn; + collision = true; + } + } + + collided = Geometry3D::segment_intersects_sphere(p_begin, p_end, Vector3(0, height * -0.5 + radius, 0), radius, &auxres, &auxn); + + if (collided) { + real_t d = norm.dot(auxres); + + if (d < min_d) { + min_d = d; + res = auxres; + n = auxn; + collision = true; + } + } + + if (collision) { + r_result = res; + r_normal = n; + } + return collision; +} + +bool GodotCapsuleShape3D::intersect_point(const Vector3 &p_point) const { + if (Math::abs(p_point.y) < height * 0.5 - radius) { + return Vector3(p_point.x, 0, p_point.z).length() < radius; + } else { + Vector3 p = p_point; + p.y = Math::abs(p.y) - height * 0.5 + radius; + return p.length() < radius; + } +} + +Vector3 GodotCapsuleShape3D::get_closest_point_to(const Vector3 &p_point) const { + Vector3 s[2] = { + Vector3(0, -height * 0.5 + radius, 0), + Vector3(0, height * 0.5 - radius, 0), + }; + + Vector3 p = Geometry3D::get_closest_point_to_segment(p_point, s); + + if (p.distance_to(p_point) < radius) { + return p_point; + } + + return p + (p_point - p).normalized() * radius; +} + +Vector3 GodotCapsuleShape3D::get_moment_of_inertia(real_t p_mass) const { + // use bad AABB approximation + Vector3 extents = get_aabb().size * 0.5; + + return Vector3( + (p_mass / 3.0) * (extents.y * extents.y + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.y * extents.y)); +} + +void GodotCapsuleShape3D::_setup(real_t p_height, real_t p_radius) { + height = p_height; + radius = p_radius; + configure(AABB(Vector3(-radius, -height * 0.5, -radius), Vector3(radius * 2, height, radius * 2))); +} + +void GodotCapsuleShape3D::set_data(const Variant &p_data) { + Dictionary d = p_data; + ERR_FAIL_COND(!d.has("radius")); + ERR_FAIL_COND(!d.has("height")); + _setup(d["height"], d["radius"]); +} + +Variant GodotCapsuleShape3D::get_data() const { + Dictionary d; + d["radius"] = radius; + d["height"] = height; + return d; +} + +GodotCapsuleShape3D::GodotCapsuleShape3D() {} + +/********** CYLINDER *************/ + +void GodotCylinderShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + Vector3 cylinder_axis = p_transform.basis.get_column(1).normalized(); + real_t axis_dot = cylinder_axis.dot(p_normal); + + Vector3 local_normal = p_transform.basis.xform_inv(p_normal); + real_t scale = local_normal.length(); + real_t scaled_radius = radius * scale; + real_t scaled_height = height * scale; + + real_t length; + if (Math::abs(axis_dot) > 1.0) { + length = scaled_height * 0.5; + } else { + length = Math::abs(axis_dot * scaled_height * 0.5) + scaled_radius * Math::sqrt(1.0 - axis_dot * axis_dot); + } + + real_t distance = p_normal.dot(p_transform.origin); + + r_min = distance - length; + r_max = distance + length; +} + +Vector3 GodotCylinderShape3D::get_support(const Vector3 &p_normal) const { + Vector3 n = p_normal; + real_t h = (n.y > 0) ? height : -height; + real_t s = Math::sqrt(n.x * n.x + n.z * n.z); + if (Math::is_zero_approx(s)) { + n.x = radius; + n.y = h * 0.5; + n.z = 0.0; + } else { + real_t d = radius / s; + n.x = n.x * d; + n.y = h * 0.5; + n.z = n.z * d; + } + + return n; +} + +void GodotCylinderShape3D::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + real_t d = p_normal.y; + if (Math::abs(d) > cylinder_face_support_threshold) { + real_t h = (d > 0) ? height : -height; + + Vector3 n = p_normal; + n.x = 0.0; + n.z = 0.0; + n.y = h * 0.5; + + r_amount = 3; + r_type = FEATURE_CIRCLE; + r_supports[0] = n; + r_supports[1] = n; + r_supports[1].x += radius; + r_supports[2] = n; + r_supports[2].z += radius; + } else if (Math::abs(d) < cylinder_edge_support_threshold_lower) { + // make it flat + Vector3 n = p_normal; + n.y = 0.0; + n.normalize(); + n *= radius; + + r_amount = 2; + r_type = FEATURE_EDGE; + r_supports[0] = n; + r_supports[0].y += height * 0.5; + r_supports[1] = n; + r_supports[1].y -= height * 0.5; + } else { + r_amount = 1; + r_type = FEATURE_POINT; + r_supports[0] = get_support(p_normal); + } +} + +bool GodotCylinderShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + return Geometry3D::segment_intersects_cylinder(p_begin, p_end, height, radius, &r_result, &r_normal, 1); +} + +bool GodotCylinderShape3D::intersect_point(const Vector3 &p_point) const { + if (Math::abs(p_point.y) < height * 0.5) { + return Vector3(p_point.x, 0, p_point.z).length() < radius; + } + return false; +} + +Vector3 GodotCylinderShape3D::get_closest_point_to(const Vector3 &p_point) const { + if (Math::absf(p_point.y) > height * 0.5) { + // Project point to top disk. + real_t dir = p_point.y > 0.0 ? 1.0 : -1.0; + Vector3 circle_pos(0.0, dir * height * 0.5, 0.0); + Plane circle_plane(Vector3(0.0, dir, 0.0), circle_pos); + Vector3 proj_point = circle_plane.project(p_point); + + // Clip position. + Vector3 delta_point_1 = proj_point - circle_pos; + real_t dist_point_1 = delta_point_1.length_squared(); + if (!Math::is_zero_approx(dist_point_1)) { + dist_point_1 = Math::sqrt(dist_point_1); + proj_point = circle_pos + delta_point_1 * MIN(dist_point_1, radius) / dist_point_1; + } + + return proj_point; + } else { + Vector3 s[2] = { + Vector3(0, -height * 0.5, 0), + Vector3(0, height * 0.5, 0), + }; + + Vector3 p = Geometry3D::get_closest_point_to_segment(p_point, s); + + if (p.distance_to(p_point) < radius) { + return p_point; + } + + return p + (p_point - p).normalized() * radius; + } +} + +Vector3 GodotCylinderShape3D::get_moment_of_inertia(real_t p_mass) const { + // use bad AABB approximation + Vector3 extents = get_aabb().size * 0.5; + + return Vector3( + (p_mass / 3.0) * (extents.y * extents.y + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.y * extents.y)); +} + +void GodotCylinderShape3D::_setup(real_t p_height, real_t p_radius) { + height = p_height; + radius = p_radius; + configure(AABB(Vector3(-radius, -height * 0.5, -radius), Vector3(radius * 2.0, height, radius * 2.0))); +} + +void GodotCylinderShape3D::set_data(const Variant &p_data) { + Dictionary d = p_data; + ERR_FAIL_COND(!d.has("radius")); + ERR_FAIL_COND(!d.has("height")); + _setup(d["height"], d["radius"]); +} + +Variant GodotCylinderShape3D::get_data() const { + Dictionary d; + d["radius"] = radius; + d["height"] = height; + return d; +} + +GodotCylinderShape3D::GodotCylinderShape3D() {} + +/********** CONVEX POLYGON *************/ + +void GodotConvexPolygonShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + uint32_t vertex_count = mesh.vertices.size(); + if (vertex_count == 0) { + return; + } + + const Vector3 *vrts = &mesh.vertices[0]; + + if (vertex_count > 3 * extreme_vertices.size()) { + // For a large mesh, two calls to get_support() is faster than a full + // scan over all vertices. + + Vector3 n = p_transform.basis.xform_inv(p_normal).normalized(); + r_min = p_normal.dot(p_transform.xform(get_support(-n))); + r_max = p_normal.dot(p_transform.xform(get_support(n))); + } else { + for (uint32_t i = 0; i < vertex_count; i++) { + real_t d = p_normal.dot(p_transform.xform(vrts[i])); + + if (i == 0 || d > r_max) { + r_max = d; + } + if (i == 0 || d < r_min) { + r_min = d; + } + } + } +} + +Vector3 GodotConvexPolygonShape3D::get_support(const Vector3 &p_normal) const { + // Skip if there are no vertices in the mesh + if (mesh.vertices.size() == 0) { + return Vector3(); + } + + // Get the array of vertices + const Vector3 *const vertices_array = mesh.vertices.ptr(); + + // Start with an initial assumption of the first extreme vertex. + int best_vertex = extreme_vertices[0]; + real_t max_support = p_normal.dot(vertices_array[best_vertex]); + + // Check the remaining extreme vertices for a better vertex. + for (const int &vert : extreme_vertices) { + real_t s = p_normal.dot(vertices_array[vert]); + if (s > max_support) { + best_vertex = vert; + max_support = s; + } + } + + // If we checked all vertices in the mesh then we're done. + if (extreme_vertices.size() == mesh.vertices.size()) { + return vertices_array[best_vertex]; + } + + // Move along the surface until we reach the true support vertex. + int last_vertex = -1; + while (true) { + int next_vertex = -1; + + // Iterate over all the neighbors checking for a better vertex. + for (const int &vert : vertex_neighbors[best_vertex]) { + if (vert != last_vertex) { + real_t s = p_normal.dot(vertices_array[vert]); + if (s > max_support) { + next_vertex = vert; + max_support = s; + break; + } + } + } + + // No better vertex found, we have the best + if (next_vertex == -1) { + return vertices_array[best_vertex]; + } + + // Move to the better vertex and try again + last_vertex = best_vertex; + best_vertex = next_vertex; + } +} + +void GodotConvexPolygonShape3D::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int fc = mesh.faces.size(); + + const Geometry3D::MeshData::Edge *edges = mesh.edges.ptr(); + int ec = mesh.edges.size(); + + const Vector3 *vertices = mesh.vertices.ptr(); + int vc = mesh.vertices.size(); + + r_amount = 0; + ERR_FAIL_COND_MSG(vc == 0, "Convex polygon shape has no vertices."); + + //find vertex first + real_t max = 0; + int vtx = 0; + + for (int i = 0; i < vc; i++) { + real_t d = p_normal.dot(vertices[i]); + + if (i == 0 || d > max) { + max = d; + vtx = i; + } + } + + for (int i = 0; i < fc; i++) { + if (faces[i].plane.normal.dot(p_normal) > face_support_threshold) { + int ic = faces[i].indices.size(); + const int *ind = faces[i].indices.ptr(); + + bool valid = false; + for (int j = 0; j < ic; j++) { + if (ind[j] == vtx) { + valid = true; + break; + } + } + + if (!valid) { + continue; + } + + int m = MIN(p_max, ic); + for (int j = 0; j < m; j++) { + r_supports[j] = vertices[ind[j]]; + } + r_amount = m; + r_type = FEATURE_FACE; + return; + } + } + + for (int i = 0; i < ec; i++) { + real_t dot = (vertices[edges[i].vertex_a] - vertices[edges[i].vertex_b]).normalized().dot(p_normal); + dot = ABS(dot); + if (dot < edge_support_threshold_lower && (edges[i].vertex_a == vtx || edges[i].vertex_b == vtx)) { + r_amount = 2; + r_type = FEATURE_EDGE; + r_supports[0] = vertices[edges[i].vertex_a]; + r_supports[1] = vertices[edges[i].vertex_b]; + return; + } + } + + r_supports[0] = vertices[vtx]; + r_amount = 1; + r_type = FEATURE_POINT; +} + +bool GodotConvexPolygonShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int fc = mesh.faces.size(); + + const Vector3 *vertices = mesh.vertices.ptr(); + + Vector3 n = p_end - p_begin; + real_t min = 1e20; + bool col = false; + + for (int i = 0; i < fc; i++) { + if (faces[i].plane.normal.dot(n) > 0) { + continue; //opposing face + } + + int ic = faces[i].indices.size(); + const int *ind = faces[i].indices.ptr(); + + for (int j = 1; j < ic - 1; j++) { + Face3 f(vertices[ind[0]], vertices[ind[j]], vertices[ind[j + 1]]); + Vector3 result; + if (f.intersects_segment(p_begin, p_end, &result)) { + real_t d = n.dot(result); + if (d < min) { + min = d; + r_result = result; + r_normal = faces[i].plane.normal; + col = true; + } + + break; + } + } + } + + return col; +} + +bool GodotConvexPolygonShape3D::intersect_point(const Vector3 &p_point) const { + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int fc = mesh.faces.size(); + + for (int i = 0; i < fc; i++) { + if (faces[i].plane.distance_to(p_point) >= 0) { + return false; + } + } + + return true; +} + +Vector3 GodotConvexPolygonShape3D::get_closest_point_to(const Vector3 &p_point) const { + const Geometry3D::MeshData::Face *faces = mesh.faces.ptr(); + int fc = mesh.faces.size(); + const Vector3 *vertices = mesh.vertices.ptr(); + + bool all_inside = true; + for (int i = 0; i < fc; i++) { + if (!faces[i].plane.is_point_over(p_point)) { + continue; + } + + all_inside = false; + bool is_inside = true; + int ic = faces[i].indices.size(); + const int *indices = faces[i].indices.ptr(); + + for (int j = 0; j < ic; j++) { + Vector3 a = vertices[indices[j]]; + Vector3 b = vertices[indices[(j + 1) % ic]]; + Vector3 n = (a - b).cross(faces[i].plane.normal).normalized(); + if (Plane(n, a).is_point_over(p_point)) { + is_inside = false; + break; + } + } + + if (is_inside) { + return faces[i].plane.project(p_point); + } + } + + if (all_inside) { + return p_point; + } + + real_t min_distance = 1e20; + Vector3 min_point; + + //check edges + const Geometry3D::MeshData::Edge *edges = mesh.edges.ptr(); + int ec = mesh.edges.size(); + for (int i = 0; i < ec; i++) { + Vector3 s[2] = { + vertices[edges[i].vertex_a], + vertices[edges[i].vertex_b] + }; + + Vector3 closest = Geometry3D::get_closest_point_to_segment(p_point, s); + real_t d = closest.distance_to(p_point); + if (d < min_distance) { + min_distance = d; + min_point = closest; + } + } + + return min_point; +} + +Vector3 GodotConvexPolygonShape3D::get_moment_of_inertia(real_t p_mass) const { + // use bad AABB approximation + Vector3 extents = get_aabb().size * 0.5; + + return Vector3( + (p_mass / 3.0) * (extents.y * extents.y + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.y * extents.y)); +} + +void GodotConvexPolygonShape3D::_setup(const Vector<Vector3> &p_vertices) { + Error err = ConvexHullComputer::convex_hull(p_vertices, mesh); + if (err != OK) { + ERR_PRINT("Failed to build convex hull"); + } + extreme_vertices.resize(0); + vertex_neighbors.resize(0); + + AABB _aabb; + + for (uint32_t i = 0; i < mesh.vertices.size(); i++) { + if (i == 0) { + _aabb.position = mesh.vertices[i]; + } else { + _aabb.expand_to(mesh.vertices[i]); + } + } + + configure(_aabb); + + // Pre-compute the extreme vertices in 26 directions. This will be used + // to speed up get_support() by letting us quickly get a good guess for + // the support vertex. + + for (int x = -1; x < 2; x++) { + for (int y = -1; y < 2; y++) { + for (int z = -1; z < 2; z++) { + if (x != 0 || y != 0 || z != 0) { + Vector3 dir(x, y, z); + dir.normalize(); + real_t max_support = 0.0; + int best_vertex = -1; + for (uint32_t i = 0; i < mesh.vertices.size(); i++) { + real_t s = dir.dot(mesh.vertices[i]); + if (best_vertex == -1 || s > max_support) { + best_vertex = i; + max_support = s; + } + } + if (!extreme_vertices.has(best_vertex)) + extreme_vertices.push_back(best_vertex); + } + } + } + } + + // Record all the neighbors of each vertex. This is used in get_support(). + + if (extreme_vertices.size() < mesh.vertices.size()) { + vertex_neighbors.resize(mesh.vertices.size()); + for (Geometry3D::MeshData::Edge &edge : mesh.edges) { + vertex_neighbors[edge.vertex_a].push_back(edge.vertex_b); + vertex_neighbors[edge.vertex_b].push_back(edge.vertex_a); + } + } +} + +void GodotConvexPolygonShape3D::set_data(const Variant &p_data) { + _setup(p_data); +} + +Variant GodotConvexPolygonShape3D::get_data() const { + Vector<Vector3> vertices; + vertices.resize(mesh.vertices.size()); + for (uint32_t i = 0; i < mesh.vertices.size(); i++) { + vertices.write[i] = mesh.vertices[i]; + } + return vertices; +} + +GodotConvexPolygonShape3D::GodotConvexPolygonShape3D() { +} + +/********** FACE POLYGON *************/ + +void GodotFaceShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + for (int i = 0; i < 3; i++) { + Vector3 v = p_transform.xform(vertex[i]); + real_t d = p_normal.dot(v); + + if (i == 0 || d > r_max) { + r_max = d; + } + + if (i == 0 || d < r_min) { + r_min = d; + } + } +} + +Vector3 GodotFaceShape3D::get_support(const Vector3 &p_normal) const { + int vert_support_idx = -1; + real_t support_max = 0; + + for (int i = 0; i < 3; i++) { + real_t d = p_normal.dot(vertex[i]); + + if (i == 0 || d > support_max) { + support_max = d; + vert_support_idx = i; + } + } + + return vertex[vert_support_idx]; +} + +void GodotFaceShape3D::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + Vector3 n = p_normal; + + /** TEST FACE AS SUPPORT **/ + if (Math::abs(normal.dot(n)) > face_support_threshold) { + r_amount = 3; + r_type = FEATURE_FACE; + for (int i = 0; i < 3; i++) { + r_supports[i] = vertex[i]; + } + return; + } + + /** FIND SUPPORT VERTEX **/ + + int vert_support_idx = -1; + real_t support_max = 0; + + for (int i = 0; i < 3; i++) { + real_t d = n.dot(vertex[i]); + + if (i == 0 || d > support_max) { + support_max = d; + vert_support_idx = i; + } + } + + /** TEST EDGES AS SUPPORT **/ + + for (int i = 0; i < 3; i++) { + int nx = (i + 1) % 3; + if (i != vert_support_idx && nx != vert_support_idx) { + continue; + } + + // check if edge is valid as a support + real_t dot = (vertex[i] - vertex[nx]).normalized().dot(n); + dot = ABS(dot); + if (dot < edge_support_threshold_lower) { + r_amount = 2; + r_type = FEATURE_EDGE; + r_supports[0] = vertex[i]; + r_supports[1] = vertex[nx]; + return; + } + } + + r_amount = 1; + r_type = FEATURE_POINT; + r_supports[0] = vertex[vert_support_idx]; +} + +bool GodotFaceShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + bool c = Geometry3D::segment_intersects_triangle(p_begin, p_end, vertex[0], vertex[1], vertex[2], &r_result); + if (c) { + r_normal = Plane(vertex[0], vertex[1], vertex[2]).normal; + if (r_normal.dot(p_end - p_begin) > 0) { + if (backface_collision && p_hit_back_faces) { + r_normal = -r_normal; + } else { + c = false; + } + } + } + + return c; +} + +bool GodotFaceShape3D::intersect_point(const Vector3 &p_point) const { + return false; //face is flat +} + +Vector3 GodotFaceShape3D::get_closest_point_to(const Vector3 &p_point) const { + return Face3(vertex[0], vertex[1], vertex[2]).get_closest_point_to(p_point); +} + +Vector3 GodotFaceShape3D::get_moment_of_inertia(real_t p_mass) const { + return Vector3(); // Sorry, but i don't think anyone cares, FaceShape! +} + +GodotFaceShape3D::GodotFaceShape3D() { + configure(AABB()); +} + +Vector<Vector3> GodotConcavePolygonShape3D::get_faces() const { + Vector<Vector3> rfaces; + rfaces.resize(faces.size() * 3); + + for (int i = 0; i < faces.size(); i++) { + Face f = faces.get(i); + + for (int j = 0; j < 3; j++) { + rfaces.set(i * 3 + j, vertices.get(f.indices[j])); + } + } + + return rfaces; +} + +void GodotConcavePolygonShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + int count = vertices.size(); + if (count == 0) { + r_min = 0; + r_max = 0; + return; + } + const Vector3 *vptr = vertices.ptr(); + + for (int i = 0; i < count; i++) { + real_t d = p_normal.dot(p_transform.xform(vptr[i])); + + if (i == 0 || d > r_max) { + r_max = d; + } + if (i == 0 || d < r_min) { + r_min = d; + } + } +} + +Vector3 GodotConcavePolygonShape3D::get_support(const Vector3 &p_normal) const { + int count = vertices.size(); + if (count == 0) { + return Vector3(); + } + + const Vector3 *vptr = vertices.ptr(); + + Vector3 n = p_normal; + + int vert_support_idx = -1; + real_t support_max = 0; + + for (int i = 0; i < count; i++) { + real_t d = n.dot(vptr[i]); + + if (i == 0 || d > support_max) { + support_max = d; + vert_support_idx = i; + } + } + + return vptr[vert_support_idx]; +} + +void GodotConcavePolygonShape3D::_cull_segment(int p_idx, _SegmentCullParams *p_params) const { + const BVH *params_bvh = &p_params->bvh[p_idx]; + + if (!params_bvh->aabb.intersects_segment(p_params->from, p_params->to)) { + return; + } + + if (params_bvh->face_index >= 0) { + const Face *f = &p_params->faces[params_bvh->face_index]; + GodotFaceShape3D *face = p_params->face; + face->normal = f->normal; + face->vertex[0] = p_params->vertices[f->indices[0]]; + face->vertex[1] = p_params->vertices[f->indices[1]]; + face->vertex[2] = p_params->vertices[f->indices[2]]; + + Vector3 res; + Vector3 normal; + int face_index = params_bvh->face_index; + if (face->intersect_segment(p_params->from, p_params->to, res, normal, face_index, true)) { + real_t d = p_params->dir.dot(res) - p_params->dir.dot(p_params->from); + if ((d > 0) && (d < p_params->min_d)) { + p_params->min_d = d; + p_params->result = res; + p_params->normal = normal; + p_params->face_index = face_index; + p_params->collisions++; + } + } + } else { + if (params_bvh->left >= 0) { + _cull_segment(params_bvh->left, p_params); + } + if (params_bvh->right >= 0) { + _cull_segment(params_bvh->right, p_params); + } + } +} + +bool GodotConcavePolygonShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + if (faces.size() == 0) { + return false; + } + + // unlock data + const Face *fr = faces.ptr(); + const Vector3 *vr = vertices.ptr(); + const BVH *br = bvh.ptr(); + + GodotFaceShape3D face; + face.backface_collision = backface_collision && p_hit_back_faces; + + _SegmentCullParams params; + params.from = p_begin; + params.to = p_end; + params.dir = (p_end - p_begin).normalized(); + + params.faces = fr; + params.vertices = vr; + params.bvh = br; + + params.face = &face; + + // cull + _cull_segment(0, ¶ms); + + if (params.collisions > 0) { + r_result = params.result; + r_normal = params.normal; + r_face_index = params.face_index; + return true; + } else { + return false; + } +} + +bool GodotConcavePolygonShape3D::intersect_point(const Vector3 &p_point) const { + return false; //face is flat +} + +Vector3 GodotConcavePolygonShape3D::get_closest_point_to(const Vector3 &p_point) const { + return Vector3(); +} + +bool GodotConcavePolygonShape3D::_cull(int p_idx, _CullParams *p_params) const { + const BVH *params_bvh = &p_params->bvh[p_idx]; + + if (!p_params->aabb.intersects(params_bvh->aabb)) { + return false; + } + + if (params_bvh->face_index >= 0) { + const Face *f = &p_params->faces[params_bvh->face_index]; + GodotFaceShape3D *face = p_params->face; + face->normal = f->normal; + face->vertex[0] = p_params->vertices[f->indices[0]]; + face->vertex[1] = p_params->vertices[f->indices[1]]; + face->vertex[2] = p_params->vertices[f->indices[2]]; + if (p_params->callback(p_params->userdata, face)) { + return true; + } + } else { + if (params_bvh->left >= 0) { + if (_cull(params_bvh->left, p_params)) { + return true; + } + } + + if (params_bvh->right >= 0) { + if (_cull(params_bvh->right, p_params)) { + return true; + } + } + } + + return false; +} + +void GodotConcavePolygonShape3D::cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata, bool p_invert_backface_collision) const { + // make matrix local to concave + if (faces.size() == 0) { + return; + } + + AABB local_aabb = p_local_aabb; + + // unlock data + const Face *fr = faces.ptr(); + const Vector3 *vr = vertices.ptr(); + const BVH *br = bvh.ptr(); + + GodotFaceShape3D face; // use this to send in the callback + face.backface_collision = backface_collision; + face.invert_backface_collision = p_invert_backface_collision; + + _CullParams params; + params.aabb = local_aabb; + params.face = &face; + params.faces = fr; + params.vertices = vr; + params.bvh = br; + params.callback = p_callback; + params.userdata = p_userdata; + + // cull + _cull(0, ¶ms); +} + +Vector3 GodotConcavePolygonShape3D::get_moment_of_inertia(real_t p_mass) const { + // use bad AABB approximation + Vector3 extents = get_aabb().size * 0.5; + + return Vector3( + (p_mass / 3.0) * (extents.y * extents.y + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.y * extents.y)); +} + +struct _Volume_BVH_Element { + AABB aabb; + Vector3 center; + int face_index = 0; +}; + +struct _Volume_BVH_CompareX { + _FORCE_INLINE_ bool operator()(const _Volume_BVH_Element &a, const _Volume_BVH_Element &b) const { + return a.center.x < b.center.x; + } +}; + +struct _Volume_BVH_CompareY { + _FORCE_INLINE_ bool operator()(const _Volume_BVH_Element &a, const _Volume_BVH_Element &b) const { + return a.center.y < b.center.y; + } +}; + +struct _Volume_BVH_CompareZ { + _FORCE_INLINE_ bool operator()(const _Volume_BVH_Element &a, const _Volume_BVH_Element &b) const { + return a.center.z < b.center.z; + } +}; + +struct _Volume_BVH { + AABB aabb; + _Volume_BVH *left = nullptr; + _Volume_BVH *right = nullptr; + + int face_index = 0; +}; + +_Volume_BVH *_volume_build_bvh(_Volume_BVH_Element *p_elements, int p_size, int &count) { + _Volume_BVH *bvh = memnew(_Volume_BVH); + + if (p_size == 1) { + //leaf + bvh->aabb = p_elements[0].aabb; + bvh->left = nullptr; + bvh->right = nullptr; + bvh->face_index = p_elements->face_index; + count++; + return bvh; + } else { + bvh->face_index = -1; + } + + AABB aabb; + for (int i = 0; i < p_size; i++) { + if (i == 0) { + aabb = p_elements[i].aabb; + } else { + aabb.merge_with(p_elements[i].aabb); + } + } + bvh->aabb = aabb; + switch (aabb.get_longest_axis_index()) { + case 0: { + SortArray<_Volume_BVH_Element, _Volume_BVH_CompareX> sort_x; + sort_x.sort(p_elements, p_size); + + } break; + case 1: { + SortArray<_Volume_BVH_Element, _Volume_BVH_CompareY> sort_y; + sort_y.sort(p_elements, p_size); + } break; + case 2: { + SortArray<_Volume_BVH_Element, _Volume_BVH_CompareZ> sort_z; + sort_z.sort(p_elements, p_size); + } break; + } + + int split = p_size / 2; + bvh->left = _volume_build_bvh(p_elements, split, count); + bvh->right = _volume_build_bvh(&p_elements[split], p_size - split, count); + + //printf("branch at %p - %i: %i\n",bvh,count,bvh->face_index); + count++; + return bvh; +} + +void GodotConcavePolygonShape3D::_fill_bvh(_Volume_BVH *p_bvh_tree, BVH *p_bvh_array, int &p_idx) { + int idx = p_idx; + + p_bvh_array[idx].aabb = p_bvh_tree->aabb; + p_bvh_array[idx].face_index = p_bvh_tree->face_index; + //printf("%p - %i: %i(%p) -- %p:%p\n",%p_bvh_array[idx],p_idx,p_bvh_array[i]->face_index,&p_bvh_tree->face_index,p_bvh_tree->left,p_bvh_tree->right); + + if (p_bvh_tree->left) { + p_bvh_array[idx].left = ++p_idx; + _fill_bvh(p_bvh_tree->left, p_bvh_array, p_idx); + + } else { + p_bvh_array[p_idx].left = -1; + } + + if (p_bvh_tree->right) { + p_bvh_array[idx].right = ++p_idx; + _fill_bvh(p_bvh_tree->right, p_bvh_array, p_idx); + + } else { + p_bvh_array[p_idx].right = -1; + } + + memdelete(p_bvh_tree); +} + +void GodotConcavePolygonShape3D::_setup(const Vector<Vector3> &p_faces, bool p_backface_collision) { + int src_face_count = p_faces.size(); + if (src_face_count == 0) { + configure(AABB()); + return; + } + ERR_FAIL_COND(src_face_count % 3); + src_face_count /= 3; + + const Vector3 *facesr = p_faces.ptr(); + + Vector<_Volume_BVH_Element> bvh_array; + bvh_array.resize(src_face_count); + + _Volume_BVH_Element *bvh_arrayw = bvh_array.ptrw(); + + faces.resize(src_face_count); + Face *facesw = faces.ptrw(); + + vertices.resize(src_face_count * 3); + + Vector3 *verticesw = vertices.ptrw(); + + AABB _aabb; + + for (int i = 0; i < src_face_count; i++) { + Face3 face(facesr[i * 3 + 0], facesr[i * 3 + 1], facesr[i * 3 + 2]); + + bvh_arrayw[i].aabb = face.get_aabb(); + bvh_arrayw[i].center = bvh_arrayw[i].aabb.get_center(); + bvh_arrayw[i].face_index = i; + facesw[i].indices[0] = i * 3 + 0; + facesw[i].indices[1] = i * 3 + 1; + facesw[i].indices[2] = i * 3 + 2; + facesw[i].normal = face.get_plane().normal; + verticesw[i * 3 + 0] = face.vertex[0]; + verticesw[i * 3 + 1] = face.vertex[1]; + verticesw[i * 3 + 2] = face.vertex[2]; + if (i == 0) { + _aabb = bvh_arrayw[i].aabb; + } else { + _aabb.merge_with(bvh_arrayw[i].aabb); + } + } + + int count = 0; + _Volume_BVH *bvh_tree = _volume_build_bvh(bvh_arrayw, src_face_count, count); + + bvh.resize(count + 1); + + BVH *bvh_arrayw2 = bvh.ptrw(); + + int idx = 0; + _fill_bvh(bvh_tree, bvh_arrayw2, idx); + + backface_collision = p_backface_collision; + + configure(_aabb); // this type of shape has no margin +} + +void GodotConcavePolygonShape3D::set_data(const Variant &p_data) { + Dictionary d = p_data; + ERR_FAIL_COND(!d.has("faces")); + + _setup(d["faces"], d["backface_collision"]); +} + +Variant GodotConcavePolygonShape3D::get_data() const { + Dictionary d; + d["faces"] = get_faces(); + d["backface_collision"] = backface_collision; + + return d; +} + +GodotConcavePolygonShape3D::GodotConcavePolygonShape3D() { +} + +/* HEIGHT MAP SHAPE */ + +Vector<real_t> GodotHeightMapShape3D::get_heights() const { + return heights; +} + +int GodotHeightMapShape3D::get_width() const { + return width; +} + +int GodotHeightMapShape3D::get_depth() const { + return depth; +} + +void GodotHeightMapShape3D::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + //not very useful, but not very used either + p_transform.xform(get_aabb()).project_range_in_plane(Plane(p_normal), r_min, r_max); +} + +Vector3 GodotHeightMapShape3D::get_support(const Vector3 &p_normal) const { + //not very useful, but not very used either + return get_aabb().get_support(p_normal); +} + +struct _HeightmapSegmentCullParams { + Vector3 from; + Vector3 to; + Vector3 dir; + + Vector3 result; + Vector3 normal; + + const GodotHeightMapShape3D *heightmap = nullptr; + GodotFaceShape3D *face = nullptr; +}; + +struct _HeightmapGridCullState { + real_t length = 0.0; + real_t length_flat = 0.0; + + real_t dist = 0.0; + real_t prev_dist = 0.0; + + int x = 0; + int z = 0; +}; + +_FORCE_INLINE_ bool _heightmap_face_cull_segment(_HeightmapSegmentCullParams &p_params) { + Vector3 res; + Vector3 normal; + int fi = -1; + if (p_params.face->intersect_segment(p_params.from, p_params.to, res, normal, fi, true)) { + p_params.result = res; + p_params.normal = normal; + + return true; + } + + return false; +} + +_FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_params, const _HeightmapGridCullState &p_state) { + // First triangle. + p_params.heightmap->_get_point(p_state.x, p_state.z, p_params.face->vertex[0]); + p_params.heightmap->_get_point(p_state.x + 1, p_state.z, p_params.face->vertex[1]); + p_params.heightmap->_get_point(p_state.x, p_state.z + 1, p_params.face->vertex[2]); + p_params.face->normal = Plane(p_params.face->vertex[0], p_params.face->vertex[1], p_params.face->vertex[2]).normal; + if (_heightmap_face_cull_segment(p_params)) { + return true; + } + + // Second triangle. + p_params.face->vertex[0] = p_params.face->vertex[1]; + p_params.heightmap->_get_point(p_state.x + 1, p_state.z + 1, p_params.face->vertex[1]); + p_params.face->normal = Plane(p_params.face->vertex[0], p_params.face->vertex[1], p_params.face->vertex[2]).normal; + if (_heightmap_face_cull_segment(p_params)) { + return true; + } + + return false; +} + +_FORCE_INLINE_ bool _heightmap_chunk_cull_segment(_HeightmapSegmentCullParams &p_params, const _HeightmapGridCullState &p_state) { + const GodotHeightMapShape3D::Range &chunk = p_params.heightmap->_get_bounds_chunk(p_state.x, p_state.z); + + Vector3 enter_pos; + Vector3 exit_pos; + + if (p_state.length_flat > CMP_EPSILON) { + real_t flat_to_3d = p_state.length / p_state.length_flat; + real_t enter_param = p_state.prev_dist * flat_to_3d; + real_t exit_param = p_state.dist * flat_to_3d; + enter_pos = p_params.from + p_params.dir * enter_param; + exit_pos = p_params.from + p_params.dir * exit_param; + } else { + // Consider the ray vertical. + // (though we shouldn't reach this often because there is an early check up-front) + enter_pos = p_params.from; + exit_pos = p_params.to; + } + + // Transform positions to heightmap space. + enter_pos *= GodotHeightMapShape3D::BOUNDS_CHUNK_SIZE; + exit_pos *= GodotHeightMapShape3D::BOUNDS_CHUNK_SIZE; + + // We did enter the flat projection of the AABB, + // but we have to check if we intersect it on the vertical axis. + if ((enter_pos.y > chunk.max) && (exit_pos.y > chunk.max)) { + return false; + } + if ((enter_pos.y < chunk.min) && (exit_pos.y < chunk.min)) { + return false; + } + + return p_params.heightmap->_intersect_grid_segment(_heightmap_cell_cull_segment, enter_pos, exit_pos, p_params.heightmap->width, p_params.heightmap->depth, p_params.heightmap->local_origin, p_params.result, p_params.normal); +} + +template <typename ProcessFunction> +bool GodotHeightMapShape3D::_intersect_grid_segment(ProcessFunction &p_process, const Vector3 &p_begin, const Vector3 &p_end, int p_width, int p_depth, const Vector3 &offset, Vector3 &r_point, Vector3 &r_normal) const { + Vector3 delta = (p_end - p_begin); + real_t length = delta.length(); + + if (length < CMP_EPSILON) { + return false; + } + + Vector3 local_begin = p_begin + offset; + + GodotFaceShape3D face; + face.backface_collision = false; + + _HeightmapSegmentCullParams params; + params.from = p_begin; + params.to = p_end; + params.dir = delta / length; + params.heightmap = this; + params.face = &face; + + _HeightmapGridCullState state; + + // Perform grid query from projected ray. + Vector2 ray_dir_flat(delta.x, delta.z); + state.length = length; + state.length_flat = ray_dir_flat.length(); + + if (state.length_flat < CMP_EPSILON) { + ray_dir_flat = Vector2(); + } else { + ray_dir_flat /= state.length_flat; + } + + const int x_step = (ray_dir_flat.x > CMP_EPSILON) ? 1 : ((ray_dir_flat.x < -CMP_EPSILON) ? -1 : 0); + const int z_step = (ray_dir_flat.y > CMP_EPSILON) ? 1 : ((ray_dir_flat.y < -CMP_EPSILON) ? -1 : 0); + + const real_t infinite = 1e20; + const real_t delta_x = (x_step != 0) ? 1.f / Math::abs(ray_dir_flat.x) : infinite; + const real_t delta_z = (z_step != 0) ? 1.f / Math::abs(ray_dir_flat.y) : infinite; + + real_t cross_x; // At which value of `param` we will cross a x-axis lane? + real_t cross_z; // At which value of `param` we will cross a z-axis lane? + + // X initialization. + if (x_step != 0) { + if (x_step == 1) { + cross_x = (Math::ceil(local_begin.x) - local_begin.x) * delta_x; + } else { + cross_x = (local_begin.x - Math::floor(local_begin.x)) * delta_x; + } + } else { + cross_x = infinite; // Will never cross on X. + } + + // Z initialization. + if (z_step != 0) { + if (z_step == 1) { + cross_z = (Math::ceil(local_begin.z) - local_begin.z) * delta_z; + } else { + cross_z = (local_begin.z - Math::floor(local_begin.z)) * delta_z; + } + } else { + cross_z = infinite; // Will never cross on Z. + } + + int x = Math::floor(local_begin.x); + int z = Math::floor(local_begin.z); + + // Workaround cases where the ray starts at an integer position. + if (Math::is_zero_approx(cross_x)) { + cross_x += delta_x; + // If going backwards, we should ignore the position we would get by the above flooring, + // because the ray is not heading in that direction. + if (x_step == -1) { + x -= 1; + } + } + + if (Math::is_zero_approx(cross_z)) { + cross_z += delta_z; + if (z_step == -1) { + z -= 1; + } + } + + // Start inside the grid. + int x_start = MAX(MIN(x, p_width - 2), 0); + int z_start = MAX(MIN(z, p_depth - 2), 0); + + // Adjust initial cross values. + cross_x += delta_x * x_step * (x_start - x); + cross_z += delta_z * z_step * (z_start - z); + + x = x_start; + z = z_start; + + while (true) { + state.prev_dist = state.dist; + state.x = x; + state.z = z; + + if (cross_x < cross_z) { + // X lane. + x += x_step; + // Assign before advancing the param, + // to be in sync with the initialization step. + state.dist = cross_x; + cross_x += delta_x; + } else { + // Z lane. + z += z_step; + state.dist = cross_z; + cross_z += delta_z; + } + + if (state.dist > state.length_flat) { + state.dist = state.length_flat; + if (p_process(params, state)) { + r_point = params.result; + r_normal = params.normal; + return true; + } + break; + } + + if (p_process(params, state)) { + r_point = params.result; + r_normal = params.normal; + return true; + } + + // Stop when outside the grid. + if ((x < 0) || (z < 0) || (x >= p_width - 1) || (z >= p_depth - 1)) { + break; + } + } + + return false; +} + +bool GodotHeightMapShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + if (heights.is_empty()) { + return false; + } + + Vector3 local_begin = p_begin + local_origin; + Vector3 local_end = p_end + local_origin; + + // Quantize the ray begin/end. + int begin_x = Math::floor(local_begin.x); + int begin_z = Math::floor(local_begin.z); + int end_x = Math::floor(local_end.x); + int end_z = Math::floor(local_end.z); + + if ((begin_x == end_x) && (begin_z == end_z)) { + // Simple case for rays that don't traverse the grid horizontally. + // Just perform a test on the given cell. + GodotFaceShape3D face; + face.backface_collision = p_hit_back_faces; + + _HeightmapSegmentCullParams params; + params.from = p_begin; + params.to = p_end; + params.dir = (p_end - p_begin).normalized(); + + params.heightmap = this; + params.face = &face; + + _HeightmapGridCullState state; + state.x = MAX(MIN(begin_x, width - 2), 0); + state.z = MAX(MIN(begin_z, depth - 2), 0); + if (_heightmap_cell_cull_segment(params, state)) { + r_point = params.result; + r_normal = params.normal; + return true; + } + } else if (bounds_grid.is_empty()) { + // Process all cells intersecting the flat projection of the ray. + return _intersect_grid_segment(_heightmap_cell_cull_segment, p_begin, p_end, width, depth, local_origin, r_point, r_normal); + } else { + Vector3 ray_diff = (p_end - p_begin); + real_t length_flat_sqr = ray_diff.x * ray_diff.x + ray_diff.z * ray_diff.z; + if (length_flat_sqr < BOUNDS_CHUNK_SIZE * BOUNDS_CHUNK_SIZE) { + // Don't use chunks, the ray is too short in the plane. + return _intersect_grid_segment(_heightmap_cell_cull_segment, p_begin, p_end, width, depth, local_origin, r_point, r_normal); + } else { + // The ray is long, run raycast on a higher-level grid. + Vector3 bounds_from = p_begin / BOUNDS_CHUNK_SIZE; + Vector3 bounds_to = p_end / BOUNDS_CHUNK_SIZE; + Vector3 bounds_offset = local_origin / BOUNDS_CHUNK_SIZE; + return _intersect_grid_segment(_heightmap_chunk_cull_segment, bounds_from, bounds_to, bounds_grid_width, bounds_grid_depth, bounds_offset, r_point, r_normal); + } + } + + return false; +} + +bool GodotHeightMapShape3D::intersect_point(const Vector3 &p_point) const { + return false; +} + +Vector3 GodotHeightMapShape3D::get_closest_point_to(const Vector3 &p_point) const { + return Vector3(); +} + +void GodotHeightMapShape3D::_get_cell(const Vector3 &p_point, int &r_x, int &r_y, int &r_z) const { + const AABB &shape_aabb = get_aabb(); + + Vector3 pos_local = shape_aabb.position + local_origin; + + Vector3 clamped_point(p_point); + clamped_point = p_point.clamp(pos_local, pos_local + shape_aabb.size); + + r_x = (clamped_point.x < 0.0) ? (clamped_point.x - 0.5) : (clamped_point.x + 0.5); + r_y = (clamped_point.y < 0.0) ? (clamped_point.y - 0.5) : (clamped_point.y + 0.5); + r_z = (clamped_point.z < 0.0) ? (clamped_point.z - 0.5) : (clamped_point.z + 0.5); +} + +void GodotHeightMapShape3D::cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata, bool p_invert_backface_collision) const { + if (heights.is_empty()) { + return; + } + + AABB local_aabb = p_local_aabb; + local_aabb.position += local_origin; + + // Quantize the aabb, and adjust the start/end ranges. + int aabb_min[3]; + int aabb_max[3]; + _get_cell(local_aabb.position, aabb_min[0], aabb_min[1], aabb_min[2]); + _get_cell(local_aabb.position + local_aabb.size, aabb_max[0], aabb_max[1], aabb_max[2]); + + // Expand the min/max quantized values. + // This is to catch the case where the input aabb falls between grid points. + for (int i = 0; i < 3; ++i) { + aabb_min[i]--; + aabb_max[i]++; + } + + int start_x = MAX(0, aabb_min[0]); + int end_x = MIN(width - 1, aabb_max[0]); + int start_z = MAX(0, aabb_min[2]); + int end_z = MIN(depth - 1, aabb_max[2]); + + GodotFaceShape3D face; + face.backface_collision = !p_invert_backface_collision; + face.invert_backface_collision = p_invert_backface_collision; + + for (int z = start_z; z < end_z; z++) { + for (int x = start_x; x < end_x; x++) { + // First triangle. + _get_point(x, z, face.vertex[0]); + _get_point(x + 1, z, face.vertex[1]); + _get_point(x, z + 1, face.vertex[2]); + face.normal = Plane(face.vertex[0], face.vertex[1], face.vertex[2]).normal; + if (p_callback(p_userdata, &face)) { + return; + } + + // Second triangle. + face.vertex[0] = face.vertex[1]; + _get_point(x + 1, z + 1, face.vertex[1]); + face.normal = Plane(face.vertex[0], face.vertex[1], face.vertex[2]).normal; + if (p_callback(p_userdata, &face)) { + return; + } + } + } +} + +Vector3 GodotHeightMapShape3D::get_moment_of_inertia(real_t p_mass) const { + // use bad AABB approximation + Vector3 extents = get_aabb().size * 0.5; + + return Vector3( + (p_mass / 3.0) * (extents.y * extents.y + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.z * extents.z), + (p_mass / 3.0) * (extents.x * extents.x + extents.y * extents.y)); +} + +void GodotHeightMapShape3D::_build_accelerator() { + bounds_grid.clear(); + + bounds_grid_width = width / BOUNDS_CHUNK_SIZE; + bounds_grid_depth = depth / BOUNDS_CHUNK_SIZE; + + if (width % BOUNDS_CHUNK_SIZE > 0) { + ++bounds_grid_width; // In case terrain size isn't dividable by chunk size. + } + + if (depth % BOUNDS_CHUNK_SIZE > 0) { + ++bounds_grid_depth; + } + + uint32_t bound_grid_size = (uint32_t)(bounds_grid_width * bounds_grid_depth); + + if (bound_grid_size < 2) { + // Grid is empty or just one chunk. + return; + } + + bounds_grid.resize(bound_grid_size); + + // Compute min and max height for all chunks. + for (int cz = 0; cz < bounds_grid_depth; ++cz) { + int z0 = cz * BOUNDS_CHUNK_SIZE; + + for (int cx = 0; cx < bounds_grid_width; ++cx) { + int x0 = cx * BOUNDS_CHUNK_SIZE; + + Range r; + + r.min = _get_height(x0, z0); + r.max = r.min; + + // Compute min and max height for this chunk. + // We have to include one extra cell to account for neighbors. + // Here is why: + // Say we have a flat terrain, and a plateau that fits a chunk perfectly. + // + // Left Right + // 0---0---0---1---1---1 + // | | | | | | + // 0---0---0---1---1---1 + // | | | | | | + // 0---0---0---1---1---1 + // x + // + // If the AABB for the Left chunk did not share vertices with the Right, + // then we would fail collision tests at x due to a gap. + // + int z_max = MIN(z0 + BOUNDS_CHUNK_SIZE + 1, depth); + int x_max = MIN(x0 + BOUNDS_CHUNK_SIZE + 1, width); + for (int z = z0; z < z_max; ++z) { + for (int x = x0; x < x_max; ++x) { + real_t height = _get_height(x, z); + if (height < r.min) { + r.min = height; + } else if (height > r.max) { + r.max = height; + } + } + } + + bounds_grid[cx + cz * bounds_grid_width] = r; + } + } +} + +void GodotHeightMapShape3D::_setup(const Vector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height) { + heights = p_heights; + width = p_width; + depth = p_depth; + + // Initialize aabb. + AABB aabb_new; + aabb_new.position = Vector3(0.0, p_min_height, 0.0); + aabb_new.size = Vector3(p_width - 1, p_max_height - p_min_height, p_depth - 1); + + // Initialize origin as the aabb center. + local_origin = aabb_new.position + 0.5 * aabb_new.size; + local_origin.y = 0.0; + + aabb_new.position -= local_origin; + + _build_accelerator(); + + configure(aabb_new); +} + +void GodotHeightMapShape3D::set_data(const Variant &p_data) { + ERR_FAIL_COND(p_data.get_type() != Variant::DICTIONARY); + + Dictionary d = p_data; + ERR_FAIL_COND(!d.has("width")); + ERR_FAIL_COND(!d.has("depth")); + ERR_FAIL_COND(!d.has("heights")); + + int width_new = d["width"]; + int depth_new = d["depth"]; + + ERR_FAIL_COND(width_new <= 0.0); + ERR_FAIL_COND(depth_new <= 0.0); + + Variant heights_variant = d["heights"]; + Vector<real_t> heights_buffer; +#ifdef REAL_T_IS_DOUBLE + if (heights_variant.get_type() == Variant::PACKED_FLOAT64_ARRAY) { +#else + if (heights_variant.get_type() == Variant::PACKED_FLOAT32_ARRAY) { +#endif + // Ready-to-use heights can be passed. + heights_buffer = heights_variant; + } else if (heights_variant.get_type() == Variant::OBJECT) { + // If an image is passed, we have to convert it. + // This would be expensive to do with a script, so it's nice to have it here. + Ref<Image> image = heights_variant; + ERR_FAIL_COND(image.is_null()); + ERR_FAIL_COND(image->get_format() != Image::FORMAT_RF); + + PackedByteArray im_data = image->get_data(); + heights_buffer.resize(image->get_width() * image->get_height()); + + real_t *w = heights_buffer.ptrw(); + real_t *rp = (real_t *)im_data.ptr(); + for (int i = 0; i < heights_buffer.size(); ++i) { + w[i] = rp[i]; + } + } else { +#ifdef REAL_T_IS_DOUBLE + ERR_FAIL_MSG("Expected PackedFloat64Array or float Image."); +#else + ERR_FAIL_MSG("Expected PackedFloat32Array or float Image."); +#endif + } + + // Compute min and max heights or use precomputed values. + real_t min_height = 0.0; + real_t max_height = 0.0; + if (d.has("min_height") && d.has("max_height")) { + min_height = d["min_height"]; + max_height = d["max_height"]; + } else { + int heights_size = heights.size(); + for (int i = 0; i < heights_size; ++i) { + real_t h = heights[i]; + if (h < min_height) { + min_height = h; + } else if (h > max_height) { + max_height = h; + } + } + } + + ERR_FAIL_COND(min_height > max_height); + + ERR_FAIL_COND(heights_buffer.size() != (width_new * depth_new)); + + // If specified, min and max height will be used as precomputed values. + _setup(heights_buffer, width_new, depth_new, min_height, max_height); +} + +Variant GodotHeightMapShape3D::get_data() const { + Dictionary d; + d["width"] = width; + d["depth"] = depth; + + const AABB &shape_aabb = get_aabb(); + d["min_height"] = shape_aabb.position.y; + d["max_height"] = shape_aabb.position.y + shape_aabb.size.y; + + d["heights"] = heights; + + return d; +} + +GodotHeightMapShape3D::GodotHeightMapShape3D() { +} diff --git a/modules/godot_physics_3d/godot_shape_3d.h b/modules/godot_physics_3d/godot_shape_3d.h new file mode 100644 index 0000000000..dbd58ead68 --- /dev/null +++ b/modules/godot_physics_3d/godot_shape_3d.h @@ -0,0 +1,514 @@ +/**************************************************************************/ +/* godot_shape_3d.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 GODOT_SHAPE_3D_H +#define GODOT_SHAPE_3D_H + +#include "core/math/geometry_3d.h" +#include "core/templates/local_vector.h" +#include "servers/physics_server_3d.h" + +class GodotShape3D; + +class GodotShapeOwner3D { +public: + virtual void _shape_changed() = 0; + virtual void remove_shape(GodotShape3D *p_shape) = 0; + + virtual ~GodotShapeOwner3D() {} +}; + +class GodotShape3D { + RID self; + AABB aabb; + bool configured = false; + real_t custom_bias = 0.0; + + HashMap<GodotShapeOwner3D *, int> owners; + +protected: + void configure(const AABB &p_aabb); + +public: + enum FeatureType { + FEATURE_POINT, + FEATURE_EDGE, + FEATURE_FACE, + FEATURE_CIRCLE, + }; + + virtual real_t get_volume() const { return aabb.get_volume(); } + + _FORCE_INLINE_ void set_self(const RID &p_self) { self = p_self; } + _FORCE_INLINE_ RID get_self() const { return self; } + + virtual PhysicsServer3D::ShapeType get_type() const = 0; + + _FORCE_INLINE_ const AABB &get_aabb() const { return aabb; } + _FORCE_INLINE_ bool is_configured() const { return configured; } + + virtual bool is_concave() const { return false; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const = 0; + virtual Vector3 get_support(const Vector3 &p_normal) const; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const = 0; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const = 0; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const = 0; + virtual bool intersect_point(const Vector3 &p_point) const = 0; + virtual Vector3 get_moment_of_inertia(real_t p_mass) const = 0; + + virtual void set_data(const Variant &p_data) = 0; + virtual Variant get_data() const = 0; + + _FORCE_INLINE_ void set_custom_bias(real_t p_bias) { custom_bias = p_bias; } + _FORCE_INLINE_ real_t get_custom_bias() const { return custom_bias; } + + void add_owner(GodotShapeOwner3D *p_owner); + void remove_owner(GodotShapeOwner3D *p_owner); + bool is_owner(GodotShapeOwner3D *p_owner) const; + const HashMap<GodotShapeOwner3D *, int> &get_owners() const; + + GodotShape3D() {} + virtual ~GodotShape3D(); +}; + +class GodotConcaveShape3D : public GodotShape3D { +public: + virtual bool is_concave() const override { return true; } + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; } + + // Returns true to stop the query. + typedef bool (*QueryCallback)(void *p_userdata, GodotShape3D *p_convex); + + virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata, bool p_invert_backface_collision) const = 0; + + GodotConcaveShape3D() {} +}; + +class GodotWorldBoundaryShape3D : public GodotShape3D { + Plane plane; + + void _setup(const Plane &p_plane); + +public: + Plane get_plane() const; + + virtual real_t get_volume() const override { return INFINITY; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_WORLD_BOUNDARY; } + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; } + + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotWorldBoundaryShape3D(); +}; + +class GodotSeparationRayShape3D : public GodotShape3D { + real_t length = 1.0; + bool slide_on_slope = false; + + void _setup(real_t p_length, bool p_slide_on_slope); + +public: + real_t get_length() const; + bool get_slide_on_slope() const; + + virtual real_t get_volume() const override { return 0.0; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_SEPARATION_RAY; } + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotSeparationRayShape3D(); +}; + +class GodotSphereShape3D : public GodotShape3D { + real_t radius = 0.0; + + void _setup(real_t p_radius); + +public: + real_t get_radius() const; + + virtual real_t get_volume() const override { return 4.0 / 3.0 * Math_PI * radius * radius * radius; } + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_SPHERE; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotSphereShape3D(); +}; + +class GodotBoxShape3D : public GodotShape3D { + Vector3 half_extents; + void _setup(const Vector3 &p_half_extents); + +public: + _FORCE_INLINE_ Vector3 get_half_extents() const { return half_extents; } + virtual real_t get_volume() const override { return 8 * half_extents.x * half_extents.y * half_extents.z; } + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_BOX; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotBoxShape3D(); +}; + +class GodotCapsuleShape3D : public GodotShape3D { + real_t height = 0.0; + real_t radius = 0.0; + + void _setup(real_t p_height, real_t p_radius); + +public: + _FORCE_INLINE_ real_t get_height() const { return height; } + _FORCE_INLINE_ real_t get_radius() const { return radius; } + + virtual real_t get_volume() const override { return 4.0 / 3.0 * Math_PI * radius * radius * radius + (height - radius * 2.0) * Math_PI * radius * radius; } + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CAPSULE; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotCapsuleShape3D(); +}; + +class GodotCylinderShape3D : public GodotShape3D { + real_t height = 0.0; + real_t radius = 0.0; + + void _setup(real_t p_height, real_t p_radius); + +public: + _FORCE_INLINE_ real_t get_height() const { return height; } + _FORCE_INLINE_ real_t get_radius() const { return radius; } + + virtual real_t get_volume() const override { return height * Math_PI * radius * radius; } + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CYLINDER; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotCylinderShape3D(); +}; + +struct GodotConvexPolygonShape3D : public GodotShape3D { + Geometry3D::MeshData mesh; + LocalVector<int> extreme_vertices; + LocalVector<LocalVector<int>> vertex_neighbors; + + void _setup(const Vector<Vector3> &p_vertices); + +public: + const Geometry3D::MeshData &get_mesh() const { return mesh; } + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONVEX_POLYGON; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotConvexPolygonShape3D(); +}; + +struct _Volume_BVH; +struct GodotFaceShape3D; + +struct GodotConcavePolygonShape3D : public GodotConcaveShape3D { + // always a trimesh + + struct Face { + Vector3 normal; + int indices[3] = {}; + }; + + Vector<Face> faces; + Vector<Vector3> vertices; + + struct BVH { + AABB aabb; + int left = 0; + int right = 0; + + int face_index = 0; + }; + + Vector<BVH> bvh; + + struct _CullParams { + AABB aabb; + QueryCallback callback = nullptr; + void *userdata = nullptr; + const Face *faces = nullptr; + const Vector3 *vertices = nullptr; + const BVH *bvh = nullptr; + GodotFaceShape3D *face = nullptr; + }; + + struct _SegmentCullParams { + Vector3 from; + Vector3 to; + Vector3 dir; + const Face *faces = nullptr; + const Vector3 *vertices = nullptr; + const BVH *bvh = nullptr; + GodotFaceShape3D *face = nullptr; + + Vector3 result; + Vector3 normal; + int face_index = -1; + real_t min_d = 1e20; + int collisions = 0; + }; + + bool backface_collision = false; + + void _cull_segment(int p_idx, _SegmentCullParams *p_params) const; + bool _cull(int p_idx, _CullParams *p_params) const; + + void _fill_bvh(_Volume_BVH *p_bvh_tree, BVH *p_bvh_array, int &p_idx); + + void _setup(const Vector<Vector3> &p_faces, bool p_backface_collision); + +public: + Vector<Vector3> get_faces() const; + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONCAVE_POLYGON; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata, bool p_invert_backface_collision) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotConcavePolygonShape3D(); +}; + +struct GodotHeightMapShape3D : public GodotConcaveShape3D { + Vector<real_t> heights; + int width = 0; + int depth = 0; + Vector3 local_origin; + + // Accelerator. + struct Range { + real_t min = 0.0; + real_t max = 0.0; + }; + LocalVector<Range> bounds_grid; + int bounds_grid_width = 0; + int bounds_grid_depth = 0; + + static const int BOUNDS_CHUNK_SIZE = 16; + + _FORCE_INLINE_ const Range &_get_bounds_chunk(int p_x, int p_z) const { + return bounds_grid[(p_z * bounds_grid_width) + p_x]; + } + + _FORCE_INLINE_ real_t _get_height(int p_x, int p_z) const { + return heights[(p_z * width) + p_x]; + } + + _FORCE_INLINE_ void _get_point(int p_x, int p_z, Vector3 &r_point) const { + r_point.x = p_x - 0.5 * (width - 1.0); + r_point.y = _get_height(p_x, p_z); + r_point.z = p_z - 0.5 * (depth - 1.0); + } + + void _get_cell(const Vector3 &p_point, int &r_x, int &r_y, int &r_z) const; + + void _build_accelerator(); + + template <typename ProcessFunction> + bool _intersect_grid_segment(ProcessFunction &p_process, const Vector3 &p_begin, const Vector3 &p_end, int p_width, int p_depth, const Vector3 &offset, Vector3 &r_point, Vector3 &r_normal) const; + + void _setup(const Vector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height); + +public: + Vector<real_t> get_heights() const; + int get_width() const; + int get_depth() const; + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_HEIGHTMAP; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata, bool p_invert_backface_collision) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + GodotHeightMapShape3D(); +}; + +//used internally +struct GodotFaceShape3D : public GodotShape3D { + Vector3 normal; //cache + Vector3 vertex[3]; + bool backface_collision = false; + bool invert_backface_collision = false; + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONCAVE_POLYGON; } + + const Vector3 &get_vertex(int p_idx) const { return vertex[p_idx]; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override {} + virtual Variant get_data() const override { return Variant(); } + + GodotFaceShape3D(); +}; + +struct GodotMotionShape3D : public GodotShape3D { + GodotShape3D *shape = nullptr; + Vector3 motion; + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONVEX_POLYGON; } + + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override { + Vector3 cast = p_transform.basis.xform(motion); + real_t mina, maxa; + real_t minb, maxb; + Transform3D ofsb = p_transform; + ofsb.origin += cast; + shape->project_range(p_normal, p_transform, mina, maxa); + shape->project_range(p_normal, ofsb, minb, maxb); + r_min = MIN(mina, minb); + r_max = MAX(maxa, maxb); + } + + virtual Vector3 get_support(const Vector3 &p_normal) const override { + Vector3 support = shape->get_support(p_normal); + if (p_normal.dot(motion) > 0) { + support += motion; + } + return support; + } + + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; } + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override { return false; } + virtual bool intersect_point(const Vector3 &p_point) const override { return false; } + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override { return p_point; } + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override { return Vector3(); } + + virtual void set_data(const Variant &p_data) override {} + virtual Variant get_data() const override { return Variant(); } + + GodotMotionShape3D() { configure(AABB()); } +}; + +#endif // GODOT_SHAPE_3D_H diff --git a/modules/godot_physics_3d/godot_soft_body_3d.cpp b/modules/godot_physics_3d/godot_soft_body_3d.cpp new file mode 100644 index 0000000000..7284076a47 --- /dev/null +++ b/modules/godot_physics_3d/godot_soft_body_3d.cpp @@ -0,0 +1,1295 @@ +/**************************************************************************/ +/* godot_soft_body_3d.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 "godot_soft_body_3d.h" + +#include "godot_space_3d.h" + +#include "core/math/geometry_3d.h" +#include "core/templates/rb_map.h" +#include "servers/rendering_server.h" + +// Based on Bullet soft body. + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +///btSoftBody implementation by Nathanael Presson + +GodotSoftBody3D::GodotSoftBody3D() : + GodotCollisionObject3D(TYPE_SOFT_BODY), + active_list(this) { + _set_static(false); +} + +void GodotSoftBody3D::_shapes_changed() { +} + +void GodotSoftBody3D::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant) { + switch (p_state) { + case PhysicsServer3D::BODY_STATE_TRANSFORM: { + _set_transform(p_variant); + _set_inv_transform(get_transform().inverse()); + + apply_nodes_transform(get_transform()); + + } break; + case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { + // Not supported. + ERR_FAIL_MSG("Linear velocity is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { + ERR_FAIL_MSG("Angular velocity is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_SLEEPING: { + ERR_FAIL_MSG("Sleeping state is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { + ERR_FAIL_MSG("Sleeping state is not supported for Soft bodies."); + } break; + } +} + +Variant GodotSoftBody3D::get_state(PhysicsServer3D::BodyState p_state) const { + switch (p_state) { + case PhysicsServer3D::BODY_STATE_TRANSFORM: { + return get_transform(); + } break; + case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { + ERR_FAIL_V_MSG(Vector3(), "Linear velocity is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { + ERR_FAIL_V_MSG(Vector3(), "Angular velocity is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_SLEEPING: { + ERR_FAIL_V_MSG(false, "Sleeping state is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { + ERR_FAIL_V_MSG(false, "Sleeping state is not supported for Soft bodies."); + } break; + } + + return Variant(); +} + +void GodotSoftBody3D::set_space(GodotSpace3D *p_space) { + if (get_space()) { + get_space()->soft_body_remove_from_active_list(&active_list); + + deinitialize_shape(); + } + + _set_space(p_space); + + if (get_space()) { + get_space()->soft_body_add_to_active_list(&active_list); + + if (bounds != AABB()) { + initialize_shape(true); + } + } +} + +void GodotSoftBody3D::set_mesh(RID p_mesh) { + destroy(); + + soft_mesh = p_mesh; + + if (soft_mesh.is_null()) { + return; + } + + Array arrays = RenderingServer::get_singleton()->mesh_surface_get_arrays(soft_mesh, 0); + ERR_FAIL_COND(arrays.is_empty()); + + const Vector<int> &indices = arrays[RenderingServer::ARRAY_INDEX]; + const Vector<Vector3> &vertices = arrays[RenderingServer::ARRAY_VERTEX]; + ERR_FAIL_COND_MSG(indices.is_empty(), "Soft body's mesh needs to have indices"); + ERR_FAIL_COND_MSG(vertices.is_empty(), "Soft body's mesh needs to have vertices"); + + bool success = create_from_trimesh(indices, vertices); + if (!success) { + destroy(); + } +} + +void GodotSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHandler *p_rendering_server_handler) { + if (soft_mesh.is_null()) { + return; + } + + const uint32_t vertex_count = map_visual_to_physics.size(); + for (uint32_t i = 0; i < vertex_count; ++i) { + const uint32_t node_index = map_visual_to_physics[i]; + const Node &node = nodes[node_index]; + + p_rendering_server_handler->set_vertex(i, node.x); + p_rendering_server_handler->set_normal(i, node.n); + } + + p_rendering_server_handler->set_aabb(bounds); +} + +void GodotSoftBody3D::update_normals_and_centroids() { + for (Node &node : nodes) { + node.n = Vector3(); + } + + for (Face &face : faces) { + const Vector3 n = vec3_cross(face.n[0]->x - face.n[2]->x, face.n[0]->x - face.n[1]->x); + face.n[0]->n += n; + face.n[1]->n += n; + face.n[2]->n += n; + face.normal = n; + face.normal.normalize(); + face.centroid = 0.33333333333 * (face.n[0]->x + face.n[1]->x + face.n[2]->x); + } + + for (Node &node : nodes) { + real_t len = node.n.length(); + if (len > CMP_EPSILON) { + node.n /= len; + } + } +} + +void GodotSoftBody3D::update_bounds() { + AABB prev_bounds = bounds; + prev_bounds.grow_by(collision_margin); + + bounds = AABB(); + + const uint32_t nodes_count = nodes.size(); + if (nodes_count == 0) { + deinitialize_shape(); + return; + } + + bool first = true; + bool moved = false; + for (uint32_t node_index = 0; node_index < nodes_count; ++node_index) { + const Node &node = nodes[node_index]; + if (!prev_bounds.has_point(node.x)) { + moved = true; + } + if (first) { + bounds.position = node.x; + first = false; + } else { + bounds.expand_to(node.x); + } + } + + if (get_space()) { + initialize_shape(moved); + } +} + +void GodotSoftBody3D::update_constants() { + reset_link_rest_lengths(); + update_link_constants(); + update_area(); +} + +void GodotSoftBody3D::update_area() { + int i, ni; + + // Face area. + for (Face &face : faces) { + const Vector3 &x0 = face.n[0]->x; + const Vector3 &x1 = face.n[1]->x; + const Vector3 &x2 = face.n[2]->x; + + const Vector3 a = x1 - x0; + const Vector3 b = x2 - x0; + const Vector3 cr = vec3_cross(a, b); + face.ra = cr.length() * 0.5; + } + + // Node area. + LocalVector<int> counts; + if (nodes.size() > 0) { + counts.resize(nodes.size()); + memset(counts.ptr(), 0, counts.size() * sizeof(int)); + } + + for (Node &node : nodes) { + node.area = 0.0; + } + + for (const Face &face : faces) { + for (int j = 0; j < 3; ++j) { + const int index = (int)(face.n[j] - &nodes[0]); + counts[index]++; + face.n[j]->area += Math::abs(face.ra); + } + } + + for (i = 0, ni = nodes.size(); i < ni; ++i) { + if (counts[i] > 0) { + nodes[i].area /= (real_t)counts[i]; + } else { + nodes[i].area = 0.0; + } + } +} + +void GodotSoftBody3D::reset_link_rest_lengths() { + for (Link &link : links) { + link.rl = (link.n[0]->x - link.n[1]->x).length(); + link.c1 = link.rl * link.rl; + } +} + +void GodotSoftBody3D::update_link_constants() { + real_t inv_linear_stiffness = 1.0 / linear_stiffness; + for (Link &link : links) { + link.c0 = (link.n[0]->im + link.n[1]->im) * inv_linear_stiffness; + } +} + +void GodotSoftBody3D::apply_nodes_transform(const Transform3D &p_transform) { + if (soft_mesh.is_null()) { + return; + } + + uint32_t node_count = nodes.size(); + Vector3 leaf_size = Vector3(collision_margin, collision_margin, collision_margin) * 2.0; + for (uint32_t node_index = 0; node_index < node_count; ++node_index) { + Node &node = nodes[node_index]; + + node.x = p_transform.xform(node.x); + node.q = node.x; + node.v = Vector3(); + node.bv = Vector3(); + + AABB node_aabb(node.x, leaf_size); + node_tree.update(node.leaf, node_aabb); + } + + face_tree.clear(); + + update_normals_and_centroids(); + update_bounds(); + update_constants(); +} + +Vector3 GodotSoftBody3D::get_vertex_position(int p_index) const { + ERR_FAIL_COND_V(p_index < 0, Vector3()); + + if (soft_mesh.is_null()) { + return Vector3(); + } + + ERR_FAIL_COND_V(p_index >= (int)map_visual_to_physics.size(), Vector3()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND_V(node_index >= nodes.size(), Vector3()); + return nodes[node_index].x; +} + +void GodotSoftBody3D::set_vertex_position(int p_index, const Vector3 &p_position) { + ERR_FAIL_COND(p_index < 0); + + if (soft_mesh.is_null()) { + return; + } + + ERR_FAIL_COND(p_index >= (int)map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND(node_index >= nodes.size()); + Node &node = nodes[node_index]; + node.q = node.x; + node.x = p_position; +} + +void GodotSoftBody3D::pin_vertex(int p_index) { + ERR_FAIL_COND(p_index < 0); + + if (is_vertex_pinned(p_index)) { + return; + } + + pinned_vertices.push_back(p_index); + + if (!soft_mesh.is_null()) { + ERR_FAIL_COND(p_index >= (int)map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND(node_index >= nodes.size()); + Node &node = nodes[node_index]; + node.im = 0.0; + } +} + +void GodotSoftBody3D::unpin_vertex(int p_index) { + ERR_FAIL_COND(p_index < 0); + + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + if (p_index == pinned_vertices[i]) { + pinned_vertices.remove_at(i); + + if (!soft_mesh.is_null()) { + ERR_FAIL_COND(p_index >= (int)map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND(node_index >= nodes.size()); + real_t inv_node_mass = nodes.size() * inv_total_mass; + + Node &node = nodes[node_index]; + node.im = inv_node_mass; + } + + return; + } + } +} + +void GodotSoftBody3D::unpin_all_vertices() { + if (!soft_mesh.is_null()) { + real_t inv_node_mass = nodes.size() * inv_total_mass; + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + int pinned_vertex = pinned_vertices[i]; + + ERR_CONTINUE(pinned_vertex >= (int)map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[pinned_vertex]; + + ERR_CONTINUE(node_index >= nodes.size()); + Node &node = nodes[node_index]; + node.im = inv_node_mass; + } + } + + pinned_vertices.clear(); +} + +bool GodotSoftBody3D::is_vertex_pinned(int p_index) const { + ERR_FAIL_COND_V(p_index < 0, false); + + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + if (p_index == pinned_vertices[i]) { + return true; + } + } + + return false; +} + +uint32_t GodotSoftBody3D::get_node_count() const { + return nodes.size(); +} + +real_t GodotSoftBody3D::get_node_inv_mass(uint32_t p_node_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_node_index, nodes.size(), 0.0); + return nodes[p_node_index].im; +} + +Vector3 GodotSoftBody3D::get_node_position(uint32_t p_node_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_node_index, nodes.size(), Vector3()); + return nodes[p_node_index].x; +} + +Vector3 GodotSoftBody3D::get_node_velocity(uint32_t p_node_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_node_index, nodes.size(), Vector3()); + return nodes[p_node_index].v; +} + +Vector3 GodotSoftBody3D::get_node_biased_velocity(uint32_t p_node_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_node_index, nodes.size(), Vector3()); + return nodes[p_node_index].bv; +} + +void GodotSoftBody3D::apply_node_impulse(uint32_t p_node_index, const Vector3 &p_impulse) { + ERR_FAIL_UNSIGNED_INDEX(p_node_index, nodes.size()); + Node &node = nodes[p_node_index]; + node.v += p_impulse * node.im; +} + +void GodotSoftBody3D::apply_node_bias_impulse(uint32_t p_node_index, const Vector3 &p_impulse) { + ERR_FAIL_UNSIGNED_INDEX(p_node_index, nodes.size()); + Node &node = nodes[p_node_index]; + node.bv += p_impulse * node.im; +} + +uint32_t GodotSoftBody3D::get_face_count() const { + return faces.size(); +} + +void GodotSoftBody3D::get_face_points(uint32_t p_face_index, Vector3 &r_point_1, Vector3 &r_point_2, Vector3 &r_point_3) const { + ERR_FAIL_UNSIGNED_INDEX(p_face_index, faces.size()); + const Face &face = faces[p_face_index]; + r_point_1 = face.n[0]->x; + r_point_2 = face.n[1]->x; + r_point_3 = face.n[2]->x; +} + +Vector3 GodotSoftBody3D::get_face_normal(uint32_t p_face_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_face_index, faces.size(), Vector3()); + return faces[p_face_index].normal; +} + +bool GodotSoftBody3D::create_from_trimesh(const Vector<int> &p_indices, const Vector<Vector3> &p_vertices) { + ERR_FAIL_COND_V(p_indices.is_empty(), false); + ERR_FAIL_COND_V(p_vertices.is_empty(), false); + + uint32_t node_count = 0; + LocalVector<Vector3> vertices; + const int visual_vertex_count(p_vertices.size()); + + LocalVector<int> triangles; + const uint32_t triangle_count(p_indices.size() / 3); + triangles.resize(triangle_count * 3); + + // Merge all overlapping vertices and create a map of physical vertices to visual vertices. + { + // Process vertices. + { + uint32_t vertex_count = 0; + HashMap<Vector3, uint32_t> unique_vertices; + + vertices.resize(visual_vertex_count); + map_visual_to_physics.resize(visual_vertex_count); + + for (int visual_vertex_index = 0; visual_vertex_index < visual_vertex_count; ++visual_vertex_index) { + const Vector3 &vertex = p_vertices[visual_vertex_index]; + + HashMap<Vector3, uint32_t>::Iterator e = unique_vertices.find(vertex); + uint32_t vertex_id; + if (e) { + // Already existing. + vertex_id = e->value; + } else { + // Create new one. + vertex_id = vertex_count++; + unique_vertices[vertex] = vertex_id; + vertices[vertex_id] = vertex; + } + + map_visual_to_physics[visual_vertex_index] = vertex_id; + } + + vertices.resize(vertex_count); + } + + // Process triangles. + { + for (uint32_t triangle_index = 0; triangle_index < triangle_count; ++triangle_index) { + for (int i = 0; i < 3; ++i) { + int visual_index = 3 * triangle_index + i; + int physics_index = map_visual_to_physics[p_indices[visual_index]]; + triangles[visual_index] = physics_index; + node_count = MAX((int)node_count, physics_index); + } + } + } + } + + ++node_count; + + // Create nodes from vertices. + nodes.resize(node_count); + real_t inv_node_mass = node_count * inv_total_mass; + Vector3 leaf_size = Vector3(collision_margin, collision_margin, collision_margin) * 2.0; + for (uint32_t i = 0; i < node_count; ++i) { + Node &node = nodes[i]; + node.s = vertices[i]; + node.x = node.s; + node.q = node.s; + node.im = inv_node_mass; + + AABB node_aabb(node.x, leaf_size); + node.leaf = node_tree.insert(node_aabb, &node); + + node.index = i; + } + + // Create links and faces from triangles. + LocalVector<bool> chks; + chks.resize(node_count * node_count); + memset(chks.ptr(), 0, chks.size() * sizeof(bool)); + + for (uint32_t i = 0; i < triangle_count * 3; i += 3) { + const int idx[] = { triangles[i], triangles[i + 1], triangles[i + 2] }; + + for (int j = 2, k = 0; k < 3; j = k++) { + int chk = idx[k] * node_count + idx[j]; + if (!chks[chk]) { + chks[chk] = true; + int inv_chk = idx[j] * node_count + idx[k]; + chks[inv_chk] = true; + + append_link(idx[j], idx[k]); + } + } + + append_face(idx[0], idx[1], idx[2]); + } + + // Set pinned nodes. + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + int pinned_vertex = pinned_vertices[i]; + + ERR_CONTINUE(pinned_vertex >= visual_vertex_count); + uint32_t node_index = map_visual_to_physics[pinned_vertex]; + + ERR_CONTINUE(node_index >= node_count); + Node &node = nodes[node_index]; + node.im = 0.0; + } + + generate_bending_constraints(2); + reoptimize_link_order(); + + update_constants(); + update_normals_and_centroids(); + update_bounds(); + + return true; +} + +void GodotSoftBody3D::generate_bending_constraints(int p_distance) { + uint32_t i, j; + + if (p_distance > 1) { + // Build graph. + const uint32_t n = nodes.size(); + const unsigned inf = (~(unsigned)0) >> 1; + const uint32_t adj_size = n * n; + unsigned *adj = memnew_arr(unsigned, adj_size); + +#define IDX(_x_, _y_) ((_y_) * n + (_x_)) + for (j = 0; j < n; ++j) { + for (i = 0; i < n; ++i) { + int idx_ij = j * n + i; + int idx_ji = i * n + j; + if (i != j) { + adj[idx_ij] = adj[idx_ji] = inf; + } else { + adj[idx_ij] = adj[idx_ji] = 0; + } + } + } + for (Link &link : links) { + const int ia = (int)(link.n[0] - &nodes[0]); + const int ib = (int)(link.n[1] - &nodes[0]); + int idx = ib * n + ia; + int idx_inv = ia * n + ib; + adj[idx] = 1; + adj[idx_inv] = 1; + } + + // Special optimized case for distance == 2. + if (p_distance == 2) { + LocalVector<LocalVector<int>> node_links; + + // Build node links. + node_links.resize(nodes.size()); + + for (Link &link : links) { + const int ia = (int)(link.n[0] - &nodes[0]); + const int ib = (int)(link.n[1] - &nodes[0]); + if (!node_links[ia].has(ib)) { + node_links[ia].push_back(ib); + } + + if (!node_links[ib].has(ia)) { + node_links[ib].push_back(ia); + } + } + for (uint32_t ii = 0; ii < node_links.size(); ii++) { + for (uint32_t jj = 0; jj < node_links[ii].size(); jj++) { + int k = node_links[ii][jj]; + for (const int &l : node_links[k]) { + if ((int)ii != l) { + int idx_ik = k * n + ii; + int idx_kj = l * n + k; + const unsigned sum = adj[idx_ik] + adj[idx_kj]; + ERR_FAIL_COND(sum != 2); + int idx_ij = l * n + ii; + if (adj[idx_ij] > sum) { + int idx_ji = l * n + ii; + adj[idx_ij] = adj[idx_ji] = sum; + } + } + } + } + } + } else { + // Generic Floyd's algorithm. + for (uint32_t k = 0; k < n; ++k) { + for (j = 0; j < n; ++j) { + for (i = j + 1; i < n; ++i) { + int idx_ik = k * n + i; + int idx_kj = j * n + k; + const unsigned sum = adj[idx_ik] + adj[idx_kj]; + int idx_ij = j * n + i; + if (adj[idx_ij] > sum) { + int idx_ji = j * n + i; + adj[idx_ij] = adj[idx_ji] = sum; + } + } + } + } + } + + // Build links. + for (j = 0; j < n; ++j) { + for (i = j + 1; i < n; ++i) { + int idx_ij = j * n + i; + if (adj[idx_ij] == (unsigned)p_distance) { + append_link(i, j); + } + } + } + memdelete_arr(adj); + } +} + +//=================================================================== +// +// +// This function takes in a list of interdependent Links and tries +// to maximize the distance between calculation +// of dependent links. This increases the amount of parallelism that can +// be exploited by out-of-order instruction processors with large but +// (inevitably) finite instruction windows. +// +//=================================================================== + +// A small structure to track lists of dependent link calculations. +class LinkDeps { +public: + // A link calculation that is dependent on this one. + // Positive values = "input A" while negative values = "input B". + int value; + // Next dependence in the list. + LinkDeps *next; +}; +typedef LinkDeps *LinkDepsPtr; + +void GodotSoftBody3D::reoptimize_link_order() { + const int reop_not_dependent = -1; + const int reop_node_complete = -2; + + uint32_t link_count = links.size(); + uint32_t node_count = nodes.size(); + + if (link_count < 1 || node_count < 2) { + return; + } + + uint32_t i; + Link *lr; + int ar, br; + Node *node0 = &(nodes[0]); + Node *node1 = &(nodes[1]); + LinkDepsPtr link_dep; + int ready_list_head, ready_list_tail, link_num, link_dep_frees, dep_link; + + // Allocate temporary buffers. + int *node_written_at = memnew_arr(int, node_count + 1); // What link calculation produced this node's current values? + int *link_dep_A = memnew_arr(int, link_count); // Link calculation input is dependent upon prior calculation #N + int *link_dep_B = memnew_arr(int, link_count); + int *ready_list = memnew_arr(int, link_count); // List of ready-to-process link calculations (# of links, maximum) + LinkDeps *link_dep_free_list = memnew_arr(LinkDeps, 2 * link_count); // Dependent-on-me list elements (2x# of links, maximum) + LinkDepsPtr *link_dep_list_starts = memnew_arr(LinkDepsPtr, link_count); // Start nodes of dependent-on-me lists, one for each link + + // Copy the original, unsorted links to a side buffer. + Link *link_buffer = memnew_arr(Link, link_count); + memcpy(link_buffer, &(links[0]), sizeof(Link) * link_count); + + // Clear out the node setup and ready list. + for (i = 0; i < node_count + 1; i++) { + node_written_at[i] = reop_not_dependent; + } + for (i = 0; i < link_count; i++) { + link_dep_list_starts[i] = nullptr; + } + ready_list_head = ready_list_tail = link_dep_frees = 0; + + // Initial link analysis to set up data structures. + for (i = 0; i < link_count; i++) { + // Note which prior link calculations we are dependent upon & build up dependence lists. + lr = &(links[i]); + ar = (lr->n[0] - node0) / (node1 - node0); + br = (lr->n[1] - node0) / (node1 - node0); + if (node_written_at[ar] > reop_not_dependent) { + link_dep_A[i] = node_written_at[ar]; + link_dep = &link_dep_free_list[link_dep_frees++]; + link_dep->value = i; + link_dep->next = link_dep_list_starts[node_written_at[ar]]; + link_dep_list_starts[node_written_at[ar]] = link_dep; + } else { + link_dep_A[i] = reop_not_dependent; + } + if (node_written_at[br] > reop_not_dependent) { + link_dep_B[i] = node_written_at[br]; + link_dep = &link_dep_free_list[link_dep_frees++]; + link_dep->value = -(int)(i + 1); + link_dep->next = link_dep_list_starts[node_written_at[br]]; + link_dep_list_starts[node_written_at[br]] = link_dep; + } else { + link_dep_B[i] = reop_not_dependent; + } + + // Add this link to the initial ready list, if it is not dependent on any other links. + if ((link_dep_A[i] == reop_not_dependent) && (link_dep_B[i] == reop_not_dependent)) { + ready_list[ready_list_tail++] = i; + link_dep_A[i] = link_dep_B[i] = reop_node_complete; // Probably not needed now. + } + + // Update the nodes to mark which ones are calculated by this link. + node_written_at[ar] = node_written_at[br] = i; + } + + // Process the ready list and create the sorted list of links: + // -- By treating the ready list as a queue, we maximize the distance between any + // inter-dependent node calculations. + // -- All other (non-related) nodes in the ready list will automatically be inserted + // in between each set of inter-dependent link calculations by this loop. + i = 0; + while (ready_list_head != ready_list_tail) { + // Use ready list to select the next link to process. + link_num = ready_list[ready_list_head++]; + // Copy the next-to-calculate link back into the original link array. + links[i++] = link_buffer[link_num]; + + // Free up any link inputs that are dependent on this one. + link_dep = link_dep_list_starts[link_num]; + while (link_dep) { + dep_link = link_dep->value; + if (dep_link >= 0) { + link_dep_A[dep_link] = reop_not_dependent; + } else { + dep_link = -dep_link - 1; + link_dep_B[dep_link] = reop_not_dependent; + } + // Add this dependent link calculation to the ready list if *both* inputs are clear. + if ((link_dep_A[dep_link] == reop_not_dependent) && (link_dep_B[dep_link] == reop_not_dependent)) { + ready_list[ready_list_tail++] = dep_link; + link_dep_A[dep_link] = link_dep_B[dep_link] = reop_node_complete; // Probably not needed now. + } + link_dep = link_dep->next; + } + } + + // Delete the temporary buffers. + memdelete_arr(node_written_at); + memdelete_arr(link_dep_A); + memdelete_arr(link_dep_B); + memdelete_arr(ready_list); + memdelete_arr(link_dep_free_list); + memdelete_arr(link_dep_list_starts); + memdelete_arr(link_buffer); +} + +void GodotSoftBody3D::append_link(uint32_t p_node1, uint32_t p_node2) { + if (p_node1 == p_node2) { + return; + } + + Node *node1 = &nodes[p_node1]; + Node *node2 = &nodes[p_node2]; + + Link link; + link.n[0] = node1; + link.n[1] = node2; + link.rl = (node1->x - node2->x).length(); + + links.push_back(link); +} + +void GodotSoftBody3D::append_face(uint32_t p_node1, uint32_t p_node2, uint32_t p_node3) { + if (p_node1 == p_node2) { + return; + } + if (p_node1 == p_node3) { + return; + } + if (p_node2 == p_node3) { + return; + } + + Node *node1 = &nodes[p_node1]; + Node *node2 = &nodes[p_node2]; + Node *node3 = &nodes[p_node3]; + + Face face; + face.n[0] = node1; + face.n[1] = node2; + face.n[2] = node3; + + face.index = faces.size(); + + faces.push_back(face); +} + +void GodotSoftBody3D::set_iteration_count(int p_val) { + iteration_count = p_val; +} + +void GodotSoftBody3D::set_total_mass(real_t p_val) { + ERR_FAIL_COND(p_val < 0.0); + + inv_total_mass = 1.0 / p_val; + real_t mass_factor = total_mass * inv_total_mass; + total_mass = p_val; + + uint32_t node_count = nodes.size(); + for (uint32_t node_index = 0; node_index < node_count; ++node_index) { + Node &node = nodes[node_index]; + node.im *= mass_factor; + } + + update_constants(); +} + +void GodotSoftBody3D::set_collision_margin(real_t p_val) { + collision_margin = p_val; +} + +void GodotSoftBody3D::set_linear_stiffness(real_t p_val) { + linear_stiffness = p_val; +} + +void GodotSoftBody3D::set_pressure_coefficient(real_t p_val) { + pressure_coefficient = p_val; +} + +void GodotSoftBody3D::set_damping_coefficient(real_t p_val) { + damping_coefficient = p_val; +} + +void GodotSoftBody3D::set_drag_coefficient(real_t p_val) { + drag_coefficient = p_val; +} + +void GodotSoftBody3D::add_velocity(const Vector3 &p_velocity) { + for (Node &node : nodes) { + if (node.im > 0) { + node.v += p_velocity; + } + } +} + +void GodotSoftBody3D::apply_forces(const LocalVector<GodotArea3D *> &p_wind_areas) { + if (nodes.is_empty()) { + return; + } + + int32_t j; + + real_t volume = 0.0; + const Vector3 &org = nodes[0].x; + + // Iterate over faces (try not to iterate elsewhere if possible). + for (const Face &face : faces) { + Vector3 wind_force(0, 0, 0); + + // Compute volume. + volume += vec3_dot(face.n[0]->x - org, vec3_cross(face.n[1]->x - org, face.n[2]->x - org)); + + // Compute nodal forces from area winds. + if (!p_wind_areas.is_empty()) { + for (const GodotArea3D *area : p_wind_areas) { + wind_force += _compute_area_windforce(area, &face); + } + + for (j = 0; j < 3; j++) { + Node *current_node = face.n[j]; + current_node->f += wind_force; + } + } + } + volume /= 6.0; + + // Apply nodal pressure forces. + if (pressure_coefficient > CMP_EPSILON) { + real_t ivolumetp = 1.0 / Math::abs(volume) * pressure_coefficient; + for (Node &node : nodes) { + if (node.im > 0) { + node.f += node.n * (node.area * ivolumetp); + } + } + } +} + +Vector3 GodotSoftBody3D::_compute_area_windforce(const GodotArea3D *p_area, const Face *p_face) { + real_t wfm = p_area->get_wind_force_magnitude(); + real_t waf = p_area->get_wind_attenuation_factor(); + const Vector3 &wd = p_area->get_wind_direction(); + const Vector3 &ws = p_area->get_wind_source(); + real_t projection_on_tri_normal = vec3_dot(p_face->normal, wd); + real_t projection_toward_centroid = vec3_dot(p_face->centroid - ws, wd); + real_t attenuation_over_distance = pow(projection_toward_centroid, -waf); + real_t nodal_force_magnitude = wfm * 0.33333333333 * p_face->ra * projection_on_tri_normal * attenuation_over_distance; + return nodal_force_magnitude * p_face->normal; +} + +void GodotSoftBody3D::predict_motion(real_t p_delta) { + const real_t inv_delta = 1.0 / p_delta; + + ERR_FAIL_NULL(get_space()); + + bool gravity_done = false; + Vector3 gravity; + + LocalVector<GodotArea3D *> wind_areas; + + int ac = areas.size(); + if (ac) { + areas.sort(); + const AreaCMP *aa = &areas[0]; + for (int i = ac - 1; i >= 0; i--) { + if (!gravity_done) { + PhysicsServer3D::AreaSpaceOverrideMode area_gravity_mode = (PhysicsServer3D::AreaSpaceOverrideMode)(int)aa[i].area->get_param(PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE); + if (area_gravity_mode != PhysicsServer3D::AREA_SPACE_OVERRIDE_DISABLED) { + Vector3 area_gravity; + aa[i].area->compute_gravity(get_transform().get_origin(), area_gravity); + switch (area_gravity_mode) { + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { + gravity += area_gravity; + gravity_done = area_gravity_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE; + } break; + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: { + gravity = area_gravity; + gravity_done = area_gravity_mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE; + } break; + default: { + } + } + } + } + + if (aa[i].area->get_wind_force_magnitude() > CMP_EPSILON) { + wind_areas.push_back(aa[i].area); + } + } + } + + // Add default gravity and damping from space area. + if (!gravity_done) { + GodotArea3D *default_area = get_space()->get_default_area(); + ERR_FAIL_NULL(default_area); + + Vector3 default_gravity; + default_area->compute_gravity(get_transform().get_origin(), default_gravity); + gravity += default_gravity; + } + + // Apply forces. + add_velocity(gravity * p_delta); + if (pressure_coefficient > CMP_EPSILON || !wind_areas.is_empty()) { + apply_forces(wind_areas); + } + + // Avoid soft body from 'exploding' so use some upper threshold of maximum motion + // that a node can travel per frame. + const real_t max_displacement = 1000.0; + real_t clamp_delta_v = max_displacement * inv_delta; + + // Integrate. + for (Node &node : nodes) { + node.q = node.x; + Vector3 delta_v = node.f * node.im * p_delta; + for (int c = 0; c < 3; c++) { + delta_v[c] = CLAMP(delta_v[c], -clamp_delta_v, clamp_delta_v); + } + node.v += delta_v; + node.x += node.v * p_delta; + node.f = Vector3(); + } + + // Bounds and tree update. + update_bounds(); + + // Node tree update. + for (const Node &node : nodes) { + AABB node_aabb(node.x, Vector3()); + node_aabb.expand_to(node.x + node.v * p_delta); + node_aabb.grow_by(collision_margin); + + node_tree.update(node.leaf, node_aabb); + } + + // Face tree update. + if (!face_tree.is_empty()) { + update_face_tree(p_delta); + } + + // Optimize node tree. + node_tree.optimize_incremental(1); + face_tree.optimize_incremental(1); +} + +void GodotSoftBody3D::solve_constraints(real_t p_delta) { + const real_t inv_delta = 1.0 / p_delta; + + for (Link &link : links) { + link.c3 = link.n[1]->q - link.n[0]->q; + link.c2 = 1 / (link.c3.length_squared() * link.c0); + } + + // Solve velocities. + for (Node &node : nodes) { + node.x = node.q + node.v * p_delta; + } + + // Solve positions. + for (int isolve = 0; isolve < iteration_count; ++isolve) { + const real_t ti = isolve / (real_t)iteration_count; + solve_links(1.0, ti); + } + const real_t vc = (1.0 - damping_coefficient) * inv_delta; + for (Node &node : nodes) { + node.x += node.bv * p_delta; + node.bv = Vector3(); + + node.v = (node.x - node.q) * vc; + + node.q = node.x; + } + + update_normals_and_centroids(); +} + +void GodotSoftBody3D::solve_links(real_t kst, real_t ti) { + for (Link &link : links) { + if (link.c0 > 0) { + Node &node_a = *link.n[0]; + Node &node_b = *link.n[1]; + const Vector3 del = node_b.x - node_a.x; + const real_t len = del.length_squared(); + if (link.c1 + len > CMP_EPSILON) { + const real_t k = ((link.c1 - len) / (link.c0 * (link.c1 + len))) * kst; + node_a.x -= del * (k * node_a.im); + node_b.x += del * (k * node_b.im); + } + } + } +} + +struct AABBQueryResult { + const GodotSoftBody3D *soft_body = nullptr; + void *userdata = nullptr; + GodotSoftBody3D::QueryResultCallback result_callback = nullptr; + + _FORCE_INLINE_ bool operator()(void *p_data) { + return result_callback(soft_body->get_node_index(p_data), userdata); + }; +}; + +void GodotSoftBody3D::query_aabb(const AABB &p_aabb, GodotSoftBody3D::QueryResultCallback p_result_callback, void *p_userdata) { + AABBQueryResult query_result; + query_result.soft_body = this; + query_result.result_callback = p_result_callback; + query_result.userdata = p_userdata; + + node_tree.aabb_query(p_aabb, query_result); +} + +struct RayQueryResult { + const GodotSoftBody3D *soft_body = nullptr; + void *userdata = nullptr; + GodotSoftBody3D::QueryResultCallback result_callback = nullptr; + + _FORCE_INLINE_ bool operator()(void *p_data) { + return result_callback(soft_body->get_face_index(p_data), userdata); + }; +}; + +void GodotSoftBody3D::query_ray(const Vector3 &p_from, const Vector3 &p_to, GodotSoftBody3D::QueryResultCallback p_result_callback, void *p_userdata) { + if (face_tree.is_empty()) { + initialize_face_tree(); + } + + RayQueryResult query_result; + query_result.soft_body = this; + query_result.result_callback = p_result_callback; + query_result.userdata = p_userdata; + + face_tree.ray_query(p_from, p_to, query_result); +} + +void GodotSoftBody3D::initialize_face_tree() { + face_tree.clear(); + for (Face &face : faces) { + AABB face_aabb; + + face_aabb.position = face.n[0]->x; + face_aabb.expand_to(face.n[1]->x); + face_aabb.expand_to(face.n[2]->x); + + face_aabb.grow_by(collision_margin); + + face.leaf = face_tree.insert(face_aabb, &face); + } +} + +void GodotSoftBody3D::update_face_tree(real_t p_delta) { + for (const Face &face : faces) { + AABB face_aabb; + + const Node *node0 = face.n[0]; + face_aabb.position = node0->x; + face_aabb.expand_to(node0->x + node0->v * p_delta); + + const Node *node1 = face.n[1]; + face_aabb.expand_to(node1->x); + face_aabb.expand_to(node1->x + node1->v * p_delta); + + const Node *node2 = face.n[2]; + face_aabb.expand_to(node2->x); + face_aabb.expand_to(node2->x + node2->v * p_delta); + + face_aabb.grow_by(collision_margin); + + face_tree.update(face.leaf, face_aabb); + } +} + +void GodotSoftBody3D::initialize_shape(bool p_force_move) { + if (get_shape_count() == 0) { + GodotSoftBodyShape3D *soft_body_shape = memnew(GodotSoftBodyShape3D(this)); + add_shape(soft_body_shape); + } else if (p_force_move) { + GodotSoftBodyShape3D *soft_body_shape = static_cast<GodotSoftBodyShape3D *>(get_shape(0)); + soft_body_shape->update_bounds(); + } +} + +void GodotSoftBody3D::deinitialize_shape() { + if (get_shape_count() > 0) { + GodotShape3D *shape = get_shape(0); + remove_shape(shape); + memdelete(shape); + } +} + +void GodotSoftBody3D::destroy() { + soft_mesh = RID(); + + map_visual_to_physics.clear(); + + node_tree.clear(); + face_tree.clear(); + + nodes.clear(); + links.clear(); + faces.clear(); + + bounds = AABB(); + deinitialize_shape(); +} + +void GodotSoftBodyShape3D::update_bounds() { + ERR_FAIL_NULL(soft_body); + + AABB collision_aabb = soft_body->get_bounds(); + collision_aabb.grow_by(soft_body->get_collision_margin()); + configure(collision_aabb); +} + +GodotSoftBodyShape3D::GodotSoftBodyShape3D(GodotSoftBody3D *p_soft_body) { + soft_body = p_soft_body; + update_bounds(); +} + +struct _SoftBodyIntersectSegmentInfo { + const GodotSoftBody3D *soft_body = nullptr; + Vector3 from; + Vector3 dir; + Vector3 hit_position; + uint32_t hit_face_index = -1; + real_t hit_dist_sq = INFINITY; + + static bool process_hit(uint32_t p_face_index, void *p_userdata) { + _SoftBodyIntersectSegmentInfo &query_info = *(static_cast<_SoftBodyIntersectSegmentInfo *>(p_userdata)); + + Vector3 points[3]; + query_info.soft_body->get_face_points(p_face_index, points[0], points[1], points[2]); + + Vector3 result; + if (Geometry3D::ray_intersects_triangle(query_info.from, query_info.dir, points[0], points[1], points[2], &result)) { + real_t dist_sq = query_info.from.distance_squared_to(result); + if (dist_sq < query_info.hit_dist_sq) { + query_info.hit_dist_sq = dist_sq; + query_info.hit_position = result; + query_info.hit_face_index = p_face_index; + } + } + + // Continue with the query. + return false; + } +}; + +bool GodotSoftBodyShape3D::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const { + _SoftBodyIntersectSegmentInfo query_info; + query_info.soft_body = soft_body; + query_info.from = p_begin; + query_info.dir = (p_end - p_begin).normalized(); + + soft_body->query_ray(p_begin, p_end, _SoftBodyIntersectSegmentInfo::process_hit, &query_info); + + if (query_info.hit_dist_sq != INFINITY) { + r_result = query_info.hit_position; + r_normal = soft_body->get_face_normal(query_info.hit_face_index); + return true; + } + + return false; +} + +bool GodotSoftBodyShape3D::intersect_point(const Vector3 &p_point) const { + return false; +} + +Vector3 GodotSoftBodyShape3D::get_closest_point_to(const Vector3 &p_point) const { + ERR_FAIL_V_MSG(Vector3(), "Get closest point is not supported for soft bodies."); +} diff --git a/modules/godot_physics_3d/godot_soft_body_3d.h b/modules/godot_physics_3d/godot_soft_body_3d.h new file mode 100644 index 0000000000..e23f4bb9f5 --- /dev/null +++ b/modules/godot_physics_3d/godot_soft_body_3d.h @@ -0,0 +1,276 @@ +/**************************************************************************/ +/* godot_soft_body_3d.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 GODOT_SOFT_BODY_3D_H +#define GODOT_SOFT_BODY_3D_H + +#include "godot_area_3d.h" +#include "godot_collision_object_3d.h" + +#include "core/math/aabb.h" +#include "core/math/dynamic_bvh.h" +#include "core/math/vector3.h" +#include "core/templates/hash_set.h" +#include "core/templates/local_vector.h" +#include "core/templates/vset.h" + +class GodotConstraint3D; + +class GodotSoftBody3D : public GodotCollisionObject3D { + RID soft_mesh; + + struct Node { + Vector3 s; // Source position + Vector3 x; // Position + Vector3 q; // Previous step position/Test position + Vector3 f; // Force accumulator + Vector3 v; // Velocity + Vector3 bv; // Biased Velocity + Vector3 n; // Normal + real_t area = 0.0; // Area + real_t im = 0.0; // 1/mass + DynamicBVH::ID leaf; // Leaf data + uint32_t index = 0; + }; + + struct Link { + Vector3 c3; // gradient + Node *n[2] = { nullptr, nullptr }; // Node pointers + real_t rl = 0.0; // Rest length + real_t c0 = 0.0; // (ima+imb)*kLST + real_t c1 = 0.0; // rl^2 + real_t c2 = 0.0; // |gradient|^2/c0 + }; + + struct Face { + Vector3 centroid; + Node *n[3] = { nullptr, nullptr, nullptr }; // Node pointers + Vector3 normal; // Normal + real_t ra = 0.0; // Rest area + DynamicBVH::ID leaf; // Leaf data + uint32_t index = 0; + }; + + LocalVector<Node> nodes; + LocalVector<Link> links; + LocalVector<Face> faces; + + DynamicBVH node_tree; + DynamicBVH face_tree; + + LocalVector<uint32_t> map_visual_to_physics; + + AABB bounds; + + real_t collision_margin = 0.05; + + real_t total_mass = 1.0; + real_t inv_total_mass = 1.0; + + int iteration_count = 5; + real_t linear_stiffness = 0.5; // [0,1] + real_t pressure_coefficient = 0.0; // [-inf,+inf] + real_t damping_coefficient = 0.01; // [0,1] + real_t drag_coefficient = 0.0; // [0,1] + LocalVector<int> pinned_vertices; + + SelfList<GodotSoftBody3D> active_list; + + HashSet<GodotConstraint3D *> constraints; + + Vector<AreaCMP> areas; + + VSet<RID> exceptions; + + uint64_t island_step = 0; + + _FORCE_INLINE_ Vector3 _compute_area_windforce(const GodotArea3D *p_area, const Face *p_face); + +public: + GodotSoftBody3D(); + + const AABB &get_bounds() const { return bounds; } + + void set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant); + Variant get_state(PhysicsServer3D::BodyState p_state) const; + + _FORCE_INLINE_ void add_constraint(GodotConstraint3D *p_constraint) { constraints.insert(p_constraint); } + _FORCE_INLINE_ void remove_constraint(GodotConstraint3D *p_constraint) { constraints.erase(p_constraint); } + _FORCE_INLINE_ const HashSet<GodotConstraint3D *> &get_constraints() const { return constraints; } + _FORCE_INLINE_ void clear_constraints() { constraints.clear(); } + + _FORCE_INLINE_ void add_exception(const RID &p_exception) { exceptions.insert(p_exception); } + _FORCE_INLINE_ void remove_exception(const RID &p_exception) { exceptions.erase(p_exception); } + _FORCE_INLINE_ bool has_exception(const RID &p_exception) const { return exceptions.has(p_exception); } + _FORCE_INLINE_ const VSet<RID> &get_exceptions() const { return exceptions; } + + _FORCE_INLINE_ uint64_t get_island_step() const { return island_step; } + _FORCE_INLINE_ void set_island_step(uint64_t p_step) { island_step = p_step; } + + _FORCE_INLINE_ void add_area(GodotArea3D *p_area) { + int index = areas.find(AreaCMP(p_area)); + if (index > -1) { + areas.write[index].refCount += 1; + } else { + areas.ordered_insert(AreaCMP(p_area)); + } + } + + _FORCE_INLINE_ void remove_area(GodotArea3D *p_area) { + int index = areas.find(AreaCMP(p_area)); + if (index > -1) { + areas.write[index].refCount -= 1; + if (areas[index].refCount < 1) { + areas.remove_at(index); + } + } + } + + virtual void set_space(GodotSpace3D *p_space) override; + + void set_mesh(RID p_mesh); + + void update_rendering_server(PhysicsServer3DRenderingServerHandler *p_rendering_server_handler); + + Vector3 get_vertex_position(int p_index) const; + void set_vertex_position(int p_index, const Vector3 &p_position); + + void pin_vertex(int p_index); + void unpin_vertex(int p_index); + void unpin_all_vertices(); + bool is_vertex_pinned(int p_index) const; + + uint32_t get_node_count() const; + real_t get_node_inv_mass(uint32_t p_node_index) const; + Vector3 get_node_position(uint32_t p_node_index) const; + Vector3 get_node_velocity(uint32_t p_node_index) const; + Vector3 get_node_biased_velocity(uint32_t p_node_index) const; + void apply_node_impulse(uint32_t p_node_index, const Vector3 &p_impulse); + void apply_node_bias_impulse(uint32_t p_node_index, const Vector3 &p_impulse); + + uint32_t get_face_count() const; + void get_face_points(uint32_t p_face_index, Vector3 &r_point_1, Vector3 &r_point_2, Vector3 &r_point_3) const; + Vector3 get_face_normal(uint32_t p_face_index) const; + + void set_iteration_count(int p_val); + _FORCE_INLINE_ real_t get_iteration_count() const { return iteration_count; } + + void set_total_mass(real_t p_val); + _FORCE_INLINE_ real_t get_total_mass() const { return total_mass; } + _FORCE_INLINE_ real_t get_total_inv_mass() const { return inv_total_mass; } + + void set_collision_margin(real_t p_val); + _FORCE_INLINE_ real_t get_collision_margin() const { return collision_margin; } + + void set_linear_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_linear_stiffness() const { return linear_stiffness; } + + void set_pressure_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_pressure_coefficient() const { return pressure_coefficient; } + + void set_damping_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_damping_coefficient() const { return damping_coefficient; } + + void set_drag_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_drag_coefficient() const { return drag_coefficient; } + + void predict_motion(real_t p_delta); + void solve_constraints(real_t p_delta); + + _FORCE_INLINE_ uint32_t get_node_index(void *p_node) const { return static_cast<Node *>(p_node)->index; } + _FORCE_INLINE_ uint32_t get_face_index(void *p_face) const { return static_cast<Face *>(p_face)->index; } + + // Return true to stop the query. + // p_index is the node index for AABB query, face index for Ray query. + typedef bool (*QueryResultCallback)(uint32_t p_index, void *p_userdata); + + void query_aabb(const AABB &p_aabb, QueryResultCallback p_result_callback, void *p_userdata); + void query_ray(const Vector3 &p_from, const Vector3 &p_to, QueryResultCallback p_result_callback, void *p_userdata); + +protected: + virtual void _shapes_changed() override; + +private: + void update_normals_and_centroids(); + void update_bounds(); + void update_constants(); + void update_area(); + void reset_link_rest_lengths(); + void update_link_constants(); + + void apply_nodes_transform(const Transform3D &p_transform); + + void add_velocity(const Vector3 &p_velocity); + + void apply_forces(const LocalVector<GodotArea3D *> &p_wind_areas); + + bool create_from_trimesh(const Vector<int> &p_indices, const Vector<Vector3> &p_vertices); + void generate_bending_constraints(int p_distance); + void reoptimize_link_order(); + void append_link(uint32_t p_node1, uint32_t p_node2); + void append_face(uint32_t p_node1, uint32_t p_node2, uint32_t p_node3); + + void solve_links(real_t kst, real_t ti); + + void initialize_face_tree(); + void update_face_tree(real_t p_delta); + + void initialize_shape(bool p_force_move = true); + void deinitialize_shape(); + + void destroy(); +}; + +class GodotSoftBodyShape3D : public GodotShape3D { + GodotSoftBody3D *soft_body = nullptr; + +public: + GodotSoftBody3D *get_soft_body() const { return soft_body; } + + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_SOFT_BODY; } + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override { r_min = r_max = 0.0; } + virtual Vector3 get_support(const Vector3 &p_normal) const override { return Vector3(); } + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; } + + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal, int &r_face_index, bool p_hit_back_faces) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override { return Vector3(); } + + virtual void set_data(const Variant &p_data) override {} + virtual Variant get_data() const override { return Variant(); } + + void update_bounds(); + + GodotSoftBodyShape3D(GodotSoftBody3D *p_soft_body); + ~GodotSoftBodyShape3D() {} +}; + +#endif // GODOT_SOFT_BODY_3D_H diff --git a/modules/godot_physics_3d/godot_space_3d.cpp b/modules/godot_physics_3d/godot_space_3d.cpp new file mode 100644 index 0000000000..9a6ba776b4 --- /dev/null +++ b/modules/godot_physics_3d/godot_space_3d.cpp @@ -0,0 +1,1277 @@ +/**************************************************************************/ +/* godot_space_3d.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 "godot_space_3d.h" + +#include "godot_collision_solver_3d.h" +#include "godot_physics_server_3d.h" + +#include "core/config/project_settings.h" + +#define TEST_MOTION_MARGIN_MIN_VALUE 0.0001 +#define TEST_MOTION_MIN_CONTACT_DEPTH_FACTOR 0.05 + +_FORCE_INLINE_ static bool _can_collide_with(GodotCollisionObject3D *p_object, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas) { + if (!(p_object->get_collision_layer() & p_collision_mask)) { + return false; + } + + if (p_object->get_type() == GodotCollisionObject3D::TYPE_AREA && !p_collide_with_areas) { + return false; + } + + if (p_object->get_type() == GodotCollisionObject3D::TYPE_BODY && !p_collide_with_bodies) { + return false; + } + + if (p_object->get_type() == GodotCollisionObject3D::TYPE_SOFT_BODY && !p_collide_with_bodies) { + return false; + } + + return true; +} + +int GodotPhysicsDirectSpaceState3D::intersect_point(const PointParameters &p_parameters, ShapeResult *r_results, int p_result_max) { + ERR_FAIL_COND_V(space->locked, false); + int amount = space->broadphase->cull_point(p_parameters.position, space->intersection_query_results, GodotSpace3D::INTERSECTION_QUERY_MAX, space->intersection_query_subindex_results); + int cc = 0; + + //Transform3D ai = p_xform.affine_inverse(); + + for (int i = 0; i < amount; i++) { + if (cc >= p_result_max) { + break; + } + + if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) { + continue; + } + + //area can't be picked by ray (default) + + if (p_parameters.exclude.has(space->intersection_query_results[i]->get_self())) { + continue; + } + + const GodotCollisionObject3D *col_obj = space->intersection_query_results[i]; + int shape_idx = space->intersection_query_subindex_results[i]; + + Transform3D inv_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx); + inv_xform.affine_invert(); + + if (!col_obj->get_shape(shape_idx)->intersect_point(inv_xform.xform(p_parameters.position))) { + continue; + } + + r_results[cc].collider_id = col_obj->get_instance_id(); + if (r_results[cc].collider_id.is_valid()) { + r_results[cc].collider = ObjectDB::get_instance(r_results[cc].collider_id); + } else { + r_results[cc].collider = nullptr; + } + r_results[cc].rid = col_obj->get_self(); + r_results[cc].shape = shape_idx; + + cc++; + } + + return cc; +} + +bool GodotPhysicsDirectSpaceState3D::intersect_ray(const RayParameters &p_parameters, RayResult &r_result) { + ERR_FAIL_COND_V(space->locked, false); + + Vector3 begin, end; + Vector3 normal; + begin = p_parameters.from; + end = p_parameters.to; + normal = (end - begin).normalized(); + + int amount = space->broadphase->cull_segment(begin, end, space->intersection_query_results, GodotSpace3D::INTERSECTION_QUERY_MAX, space->intersection_query_subindex_results); + + //todo, create another array that references results, compute AABBs and check closest point to ray origin, sort, and stop evaluating results when beyond first collision + + bool collided = false; + Vector3 res_point, res_normal; + int res_face_index = -1; + int res_shape = -1; + const GodotCollisionObject3D *res_obj = nullptr; + real_t min_d = 1e10; + + for (int i = 0; i < amount; i++) { + if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) { + continue; + } + + if (p_parameters.pick_ray && !(space->intersection_query_results[i]->is_ray_pickable())) { + continue; + } + + if (p_parameters.exclude.has(space->intersection_query_results[i]->get_self())) { + continue; + } + + const GodotCollisionObject3D *col_obj = space->intersection_query_results[i]; + + int shape_idx = space->intersection_query_subindex_results[i]; + Transform3D inv_xform = col_obj->get_shape_inv_transform(shape_idx) * col_obj->get_inv_transform(); + + Vector3 local_from = inv_xform.xform(begin); + Vector3 local_to = inv_xform.xform(end); + + const GodotShape3D *shape = col_obj->get_shape(shape_idx); + + Vector3 shape_point, shape_normal; + int shape_face_index = -1; + + if (shape->intersect_point(local_from)) { + if (p_parameters.hit_from_inside) { + // Hit shape at starting point. + min_d = 0; + res_point = begin; + res_normal = Vector3(); + res_shape = shape_idx; + res_obj = col_obj; + collided = true; + break; + } else { + // Ignore shape when starting inside. + continue; + } + } + + if (shape->intersect_segment(local_from, local_to, shape_point, shape_normal, shape_face_index, p_parameters.hit_back_faces)) { + Transform3D xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx); + shape_point = xform.xform(shape_point); + + real_t ld = normal.dot(shape_point); + + if (ld < min_d) { + min_d = ld; + res_point = shape_point; + res_normal = inv_xform.basis.xform_inv(shape_normal).normalized(); + res_face_index = shape_face_index; + res_shape = shape_idx; + res_obj = col_obj; + collided = true; + } + } + } + + if (!collided) { + return false; + } + ERR_FAIL_NULL_V(res_obj, false); // Shouldn't happen but silences warning. + + r_result.collider_id = res_obj->get_instance_id(); + if (r_result.collider_id.is_valid()) { + r_result.collider = ObjectDB::get_instance(r_result.collider_id); + } else { + r_result.collider = nullptr; + } + r_result.normal = res_normal; + r_result.face_index = res_face_index; + r_result.position = res_point; + r_result.rid = res_obj->get_self(); + r_result.shape = res_shape; + + return true; +} + +int GodotPhysicsDirectSpaceState3D::intersect_shape(const ShapeParameters &p_parameters, ShapeResult *r_results, int p_result_max) { + if (p_result_max <= 0) { + return 0; + } + + GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); + ERR_FAIL_NULL_V(shape, 0); + + AABB aabb = p_parameters.transform.xform(shape->get_aabb()); + + int amount = space->broadphase->cull_aabb(aabb, space->intersection_query_results, GodotSpace3D::INTERSECTION_QUERY_MAX, space->intersection_query_subindex_results); + + int cc = 0; + + //Transform3D ai = p_xform.affine_inverse(); + + for (int i = 0; i < amount; i++) { + if (cc >= p_result_max) { + break; + } + + if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) { + continue; + } + + //area can't be picked by ray (default) + + if (p_parameters.exclude.has(space->intersection_query_results[i]->get_self())) { + continue; + } + + const GodotCollisionObject3D *col_obj = space->intersection_query_results[i]; + int shape_idx = space->intersection_query_subindex_results[i]; + + if (!GodotCollisionSolver3D::solve_static(shape, p_parameters.transform, col_obj->get_shape(shape_idx), col_obj->get_transform() * col_obj->get_shape_transform(shape_idx), nullptr, nullptr, nullptr, p_parameters.margin, 0)) { + continue; + } + + if (r_results) { + r_results[cc].collider_id = col_obj->get_instance_id(); + if (r_results[cc].collider_id.is_valid()) { + r_results[cc].collider = ObjectDB::get_instance(r_results[cc].collider_id); + } else { + r_results[cc].collider = nullptr; + } + r_results[cc].rid = col_obj->get_self(); + r_results[cc].shape = shape_idx; + } + + cc++; + } + + return cc; +} + +bool GodotPhysicsDirectSpaceState3D::cast_motion(const ShapeParameters &p_parameters, real_t &p_closest_safe, real_t &p_closest_unsafe, ShapeRestInfo *r_info) { + GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); + ERR_FAIL_NULL_V(shape, false); + + AABB aabb = p_parameters.transform.xform(shape->get_aabb()); + aabb = aabb.merge(AABB(aabb.position + p_parameters.motion, aabb.size)); //motion + aabb = aabb.grow(p_parameters.margin); + + int amount = space->broadphase->cull_aabb(aabb, space->intersection_query_results, GodotSpace3D::INTERSECTION_QUERY_MAX, space->intersection_query_subindex_results); + + real_t best_safe = 1; + real_t best_unsafe = 1; + + Transform3D xform_inv = p_parameters.transform.affine_inverse(); + GodotMotionShape3D mshape; + mshape.shape = shape; + mshape.motion = xform_inv.basis.xform(p_parameters.motion); + + bool best_first = true; + + Vector3 motion_normal = p_parameters.motion.normalized(); + + Vector3 closest_A, closest_B; + + for (int i = 0; i < amount; i++) { + if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) { + continue; + } + + if (p_parameters.exclude.has(space->intersection_query_results[i]->get_self())) { + continue; //ignore excluded + } + + const GodotCollisionObject3D *col_obj = space->intersection_query_results[i]; + int shape_idx = space->intersection_query_subindex_results[i]; + + Vector3 point_A, point_B; + Vector3 sep_axis = motion_normal; + + Transform3D col_obj_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx); + //test initial overlap, does it collide if going all the way? + if (GodotCollisionSolver3D::solve_distance(&mshape, p_parameters.transform, col_obj->get_shape(shape_idx), col_obj_xform, point_A, point_B, aabb, &sep_axis)) { + continue; + } + + //test initial overlap, ignore objects it's inside of. + sep_axis = motion_normal; + + if (!GodotCollisionSolver3D::solve_distance(shape, p_parameters.transform, col_obj->get_shape(shape_idx), col_obj_xform, point_A, point_B, aabb, &sep_axis)) { + continue; + } + + //just do kinematic solving + real_t low = 0.0; + real_t hi = 1.0; + real_t fraction_coeff = 0.5; + for (int j = 0; j < 8; j++) { //steps should be customizable.. + real_t fraction = low + (hi - low) * fraction_coeff; + + mshape.motion = xform_inv.basis.xform(p_parameters.motion * fraction); + + Vector3 lA, lB; + Vector3 sep = motion_normal; //important optimization for this to work fast enough + bool collided = !GodotCollisionSolver3D::solve_distance(&mshape, p_parameters.transform, col_obj->get_shape(shape_idx), col_obj_xform, lA, lB, aabb, &sep); + + if (collided) { + hi = fraction; + if ((j == 0) || (low > 0.0)) { // Did it not collide before? + // When alternating or first iteration, use dichotomy. + fraction_coeff = 0.5; + } else { + // When colliding again, converge faster towards low fraction + // for more accurate results with long motions that collide near the start. + fraction_coeff = 0.25; + } + } else { + point_A = lA; + point_B = lB; + low = fraction; + if ((j == 0) || (hi < 1.0)) { // Did it collide before? + // When alternating or first iteration, use dichotomy. + fraction_coeff = 0.5; + } else { + // When not colliding again, converge faster towards high fraction + // for more accurate results with long motions that collide near the end. + fraction_coeff = 0.75; + } + } + } + + if (low < best_safe) { + best_first = true; //force reset + best_safe = low; + best_unsafe = hi; + } + + if (r_info && (best_first || (point_A.distance_squared_to(point_B) < closest_A.distance_squared_to(closest_B) && low <= best_safe))) { + closest_A = point_A; + closest_B = point_B; + r_info->collider_id = col_obj->get_instance_id(); + r_info->rid = col_obj->get_self(); + r_info->shape = shape_idx; + r_info->point = closest_B; + r_info->normal = (closest_A - closest_B).normalized(); + best_first = false; + if (col_obj->get_type() == GodotCollisionObject3D::TYPE_BODY) { + const GodotBody3D *body = static_cast<const GodotBody3D *>(col_obj); + Vector3 rel_vec = closest_B - (body->get_transform().origin + body->get_center_of_mass()); + r_info->linear_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(rel_vec); + } + } + } + + p_closest_safe = best_safe; + p_closest_unsafe = best_unsafe; + + return true; +} + +bool GodotPhysicsDirectSpaceState3D::collide_shape(const ShapeParameters &p_parameters, Vector3 *r_results, int p_result_max, int &r_result_count) { + if (p_result_max <= 0) { + return false; + } + + GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); + ERR_FAIL_NULL_V(shape, 0); + + AABB aabb = p_parameters.transform.xform(shape->get_aabb()); + aabb = aabb.grow(p_parameters.margin); + + int amount = space->broadphase->cull_aabb(aabb, space->intersection_query_results, GodotSpace3D::INTERSECTION_QUERY_MAX, space->intersection_query_subindex_results); + + bool collided = false; + r_result_count = 0; + + GodotPhysicsServer3D::CollCbkData cbk; + cbk.max = p_result_max; + cbk.amount = 0; + cbk.ptr = r_results; + GodotCollisionSolver3D::CallbackResult cbkres = GodotPhysicsServer3D::_shape_col_cbk; + + GodotPhysicsServer3D::CollCbkData *cbkptr = &cbk; + + for (int i = 0; i < amount; i++) { + if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) { + continue; + } + + const GodotCollisionObject3D *col_obj = space->intersection_query_results[i]; + + if (p_parameters.exclude.has(col_obj->get_self())) { + continue; + } + + int shape_idx = space->intersection_query_subindex_results[i]; + + if (GodotCollisionSolver3D::solve_static(shape, p_parameters.transform, col_obj->get_shape(shape_idx), col_obj->get_transform() * col_obj->get_shape_transform(shape_idx), cbkres, cbkptr, nullptr, p_parameters.margin)) { + collided = true; + } + } + + r_result_count = cbk.amount; + + return collided; +} + +struct _RestResultData { + const GodotCollisionObject3D *object = nullptr; + int local_shape = 0; + int shape = 0; + Vector3 contact; + Vector3 normal; + real_t len = 0.0; +}; + +struct _RestCallbackData { + const GodotCollisionObject3D *object = nullptr; + int local_shape = 0; + int shape = 0; + + real_t min_allowed_depth = 0.0; + + _RestResultData best_result; + + int max_results = 0; + int result_count = 0; + _RestResultData *other_results = nullptr; +}; + +static void _rest_cbk_result(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata) { + _RestCallbackData *rd = static_cast<_RestCallbackData *>(p_userdata); + + Vector3 contact_rel = p_point_B - p_point_A; + real_t len = contact_rel.length(); + if (len < rd->min_allowed_depth) { + return; + } + + bool is_best_result = (len > rd->best_result.len); + + if (rd->other_results && rd->result_count > 0) { + // Consider as new result by default. + int prev_result_count = rd->result_count++; + + int result_index = 0; + real_t tested_len = is_best_result ? rd->best_result.len : len; + for (; result_index < prev_result_count - 1; ++result_index) { + if (tested_len > rd->other_results[result_index].len) { + // Re-using a previous result. + rd->result_count--; + break; + } + } + + if (result_index < rd->max_results - 1) { + _RestResultData &result = rd->other_results[result_index]; + + if (is_best_result) { + // Keep the previous best result as separate result. + result = rd->best_result; + } else { + // Keep this result as separate result. + result.len = len; + result.contact = p_point_B; + result.normal = normal; + result.object = rd->object; + result.shape = rd->shape; + result.local_shape = rd->local_shape; + } + } else { + // Discarding this result. + rd->result_count--; + } + } else if (is_best_result) { + rd->result_count = 1; + } + + if (!is_best_result) { + return; + } + + rd->best_result.len = len; + rd->best_result.contact = p_point_B; + rd->best_result.normal = normal; + rd->best_result.object = rd->object; + rd->best_result.shape = rd->shape; + rd->best_result.local_shape = rd->local_shape; +} + +bool GodotPhysicsDirectSpaceState3D::rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) { + GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); + ERR_FAIL_NULL_V(shape, 0); + + real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE); + + AABB aabb = p_parameters.transform.xform(shape->get_aabb()); + aabb = aabb.grow(margin); + + int amount = space->broadphase->cull_aabb(aabb, space->intersection_query_results, GodotSpace3D::INTERSECTION_QUERY_MAX, space->intersection_query_subindex_results); + + _RestCallbackData rcd; + + // Allowed depth can't be lower than motion length, in order to handle contacts at low speed. + real_t motion_length = p_parameters.motion.length(); + real_t min_contact_depth = margin * TEST_MOTION_MIN_CONTACT_DEPTH_FACTOR; + rcd.min_allowed_depth = MIN(motion_length, min_contact_depth); + + for (int i = 0; i < amount; i++) { + if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) { + continue; + } + + const GodotCollisionObject3D *col_obj = space->intersection_query_results[i]; + + if (p_parameters.exclude.has(col_obj->get_self())) { + continue; + } + + int shape_idx = space->intersection_query_subindex_results[i]; + + rcd.object = col_obj; + rcd.shape = shape_idx; + bool sc = GodotCollisionSolver3D::solve_static(shape, p_parameters.transform, col_obj->get_shape(shape_idx), col_obj->get_transform() * col_obj->get_shape_transform(shape_idx), _rest_cbk_result, &rcd, nullptr, margin); + if (!sc) { + continue; + } + } + + if (rcd.best_result.len == 0 || !rcd.best_result.object) { + return false; + } + + r_info->collider_id = rcd.best_result.object->get_instance_id(); + r_info->shape = rcd.best_result.shape; + r_info->normal = rcd.best_result.normal; + r_info->point = rcd.best_result.contact; + r_info->rid = rcd.best_result.object->get_self(); + if (rcd.best_result.object->get_type() == GodotCollisionObject3D::TYPE_BODY) { + const GodotBody3D *body = static_cast<const GodotBody3D *>(rcd.best_result.object); + Vector3 rel_vec = rcd.best_result.contact - (body->get_transform().origin + body->get_center_of_mass()); + r_info->linear_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(rel_vec); + + } else { + r_info->linear_velocity = Vector3(); + } + + return true; +} + +Vector3 GodotPhysicsDirectSpaceState3D::get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const { + GodotCollisionObject3D *obj = GodotPhysicsServer3D::godot_singleton->area_owner.get_or_null(p_object); + if (!obj) { + obj = GodotPhysicsServer3D::godot_singleton->body_owner.get_or_null(p_object); + } + ERR_FAIL_NULL_V(obj, Vector3()); + + ERR_FAIL_COND_V(obj->get_space() != space, Vector3()); + + real_t min_distance = 1e20; + Vector3 min_point; + + bool shapes_found = false; + + for (int i = 0; i < obj->get_shape_count(); i++) { + if (obj->is_shape_disabled(i)) { + continue; + } + + Transform3D shape_xform = obj->get_transform() * obj->get_shape_transform(i); + GodotShape3D *shape = obj->get_shape(i); + + Vector3 point = shape->get_closest_point_to(shape_xform.affine_inverse().xform(p_point)); + point = shape_xform.xform(point); + + real_t dist = point.distance_to(p_point); + if (dist < min_distance) { + min_distance = dist; + min_point = point; + } + shapes_found = true; + } + + if (!shapes_found) { + return obj->get_transform().origin; //no shapes found, use distance to origin. + } else { + return min_point; + } +} + +GodotPhysicsDirectSpaceState3D::GodotPhysicsDirectSpaceState3D() { + space = nullptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int GodotSpace3D::_cull_aabb_for_body(GodotBody3D *p_body, const AABB &p_aabb) { + int amount = broadphase->cull_aabb(p_aabb, intersection_query_results, INTERSECTION_QUERY_MAX, intersection_query_subindex_results); + + for (int i = 0; i < amount; i++) { + bool keep = true; + + if (intersection_query_results[i] == p_body) { + keep = false; + } else if (intersection_query_results[i]->get_type() == GodotCollisionObject3D::TYPE_AREA) { + keep = false; + } else if (intersection_query_results[i]->get_type() == GodotCollisionObject3D::TYPE_SOFT_BODY) { + keep = false; + } else if (!p_body->collides_with(static_cast<GodotBody3D *>(intersection_query_results[i]))) { + keep = false; + } else if (static_cast<GodotBody3D *>(intersection_query_results[i])->has_exception(p_body->get_self()) || p_body->has_exception(intersection_query_results[i]->get_self())) { + keep = false; + } + + if (!keep) { + if (i < amount - 1) { + SWAP(intersection_query_results[i], intersection_query_results[amount - 1]); + SWAP(intersection_query_subindex_results[i], intersection_query_subindex_results[amount - 1]); + } + + amount--; + i--; + } + } + + return amount; +} + +bool GodotSpace3D::test_body_motion(GodotBody3D *p_body, const PhysicsServer3D::MotionParameters &p_parameters, PhysicsServer3D::MotionResult *r_result) { + //give me back regular physics engine logic + //this is madness + //and most people using this function will think + //what it does is simpler than using physics + //this took about a week to get right.. + //but is it right? who knows at this point.. + + ERR_FAIL_COND_V(p_parameters.max_collisions < 0 || p_parameters.max_collisions > PhysicsServer3D::MotionResult::MAX_COLLISIONS, false); + + if (r_result) { + *r_result = PhysicsServer3D::MotionResult(); + } + + AABB body_aabb; + bool shapes_found = false; + + for (int i = 0; i < p_body->get_shape_count(); i++) { + if (p_body->is_shape_disabled(i)) { + continue; + } + + if (!shapes_found) { + body_aabb = p_body->get_shape_aabb(i); + shapes_found = true; + } else { + body_aabb = body_aabb.merge(p_body->get_shape_aabb(i)); + } + } + + if (!shapes_found) { + if (r_result) { + r_result->travel = p_parameters.motion; + } + + return false; + } + + real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE); + + // Undo the currently transform the physics server is aware of and apply the provided one + body_aabb = p_parameters.from.xform(p_body->get_inv_transform().xform(body_aabb)); + body_aabb = body_aabb.grow(margin); + + real_t min_contact_depth = margin * TEST_MOTION_MIN_CONTACT_DEPTH_FACTOR; + + real_t motion_length = p_parameters.motion.length(); + Vector3 motion_normal = p_parameters.motion / motion_length; + + Transform3D body_transform = p_parameters.from; + + bool recovered = false; + + { + //STEP 1, FREE BODY IF STUCK + + const int max_results = 32; + int recover_attempts = 4; + Vector3 sr[max_results * 2]; + real_t priorities[max_results]; + + do { + GodotPhysicsServer3D::CollCbkData cbk; + cbk.max = max_results; + cbk.amount = 0; + cbk.ptr = sr; + + GodotPhysicsServer3D::CollCbkData *cbkptr = &cbk; + GodotCollisionSolver3D::CallbackResult cbkres = GodotPhysicsServer3D::_shape_col_cbk; + int priority_amount = 0; + + bool collided = false; + + int amount = _cull_aabb_for_body(p_body, body_aabb); + + for (int j = 0; j < p_body->get_shape_count(); j++) { + if (p_body->is_shape_disabled(j)) { + continue; + } + + Transform3D body_shape_xform = body_transform * p_body->get_shape_transform(j); + GodotShape3D *body_shape = p_body->get_shape(j); + + for (int i = 0; i < amount; i++) { + const GodotCollisionObject3D *col_obj = intersection_query_results[i]; + if (p_parameters.exclude_bodies.has(col_obj->get_self())) { + continue; + } + if (p_parameters.exclude_objects.has(col_obj->get_instance_id())) { + continue; + } + + int shape_idx = intersection_query_subindex_results[i]; + + if (GodotCollisionSolver3D::solve_static(body_shape, body_shape_xform, col_obj->get_shape(shape_idx), col_obj->get_transform() * col_obj->get_shape_transform(shape_idx), cbkres, cbkptr, nullptr, margin)) { + collided = cbk.amount > 0; + } + while (cbk.amount > priority_amount) { + priorities[priority_amount] = col_obj->get_collision_priority(); + priority_amount++; + } + } + } + + if (!collided) { + break; + } + + real_t inv_total_weight = 0.0; + for (int i = 0; i < cbk.amount; i++) { + inv_total_weight += priorities[i]; + } + inv_total_weight = Math::is_zero_approx(inv_total_weight) ? 1.0 : (real_t)cbk.amount / inv_total_weight; + + recovered = true; + + Vector3 recover_motion; + for (int i = 0; i < cbk.amount; i++) { + Vector3 a = sr[i * 2 + 0]; + Vector3 b = sr[i * 2 + 1]; + + // Compute plane on b towards a. + Vector3 n = (a - b).normalized(); + real_t d = n.dot(b); + + // Compute depth on recovered motion. + real_t depth = n.dot(a + recover_motion) - d; + if (depth > min_contact_depth + CMP_EPSILON) { + // Only recover if there is penetration. + recover_motion -= n * (depth - min_contact_depth) * 0.4 * priorities[i] * inv_total_weight; + } + } + + if (recover_motion == Vector3()) { + collided = false; + break; + } + + body_transform.origin += recover_motion; + body_aabb.position += recover_motion; + + recover_attempts--; + + } while (recover_attempts); + } + + real_t safe = 1.0; + real_t unsafe = 1.0; + int best_shape = -1; + + { + // STEP 2 ATTEMPT MOTION + + AABB motion_aabb = body_aabb; + motion_aabb.position += p_parameters.motion; + motion_aabb = motion_aabb.merge(body_aabb); + + int amount = _cull_aabb_for_body(p_body, motion_aabb); + + for (int j = 0; j < p_body->get_shape_count(); j++) { + if (p_body->is_shape_disabled(j)) { + continue; + } + + GodotShape3D *body_shape = p_body->get_shape(j); + + // Colliding separation rays allows to properly snap to the ground, + // otherwise it's not needed in regular motion. + if (!p_parameters.collide_separation_ray && (body_shape->get_type() == PhysicsServer3D::SHAPE_SEPARATION_RAY)) { + // When slide on slope is on, separation ray shape acts like a regular shape. + if (!static_cast<GodotSeparationRayShape3D *>(body_shape)->get_slide_on_slope()) { + continue; + } + } + + Transform3D body_shape_xform = body_transform * p_body->get_shape_transform(j); + + Transform3D body_shape_xform_inv = body_shape_xform.affine_inverse(); + GodotMotionShape3D mshape; + mshape.shape = body_shape; + mshape.motion = body_shape_xform_inv.basis.xform(p_parameters.motion); + + bool stuck = false; + + real_t best_safe = 1; + real_t best_unsafe = 1; + + for (int i = 0; i < amount; i++) { + const GodotCollisionObject3D *col_obj = intersection_query_results[i]; + if (p_parameters.exclude_bodies.has(col_obj->get_self())) { + continue; + } + if (p_parameters.exclude_objects.has(col_obj->get_instance_id())) { + continue; + } + + int shape_idx = intersection_query_subindex_results[i]; + + //test initial overlap, does it collide if going all the way? + Vector3 point_A, point_B; + Vector3 sep_axis = motion_normal; + + Transform3D col_obj_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx); + //test initial overlap, does it collide if going all the way? + if (GodotCollisionSolver3D::solve_distance(&mshape, body_shape_xform, col_obj->get_shape(shape_idx), col_obj_xform, point_A, point_B, motion_aabb, &sep_axis)) { + continue; + } + sep_axis = motion_normal; + + if (!GodotCollisionSolver3D::solve_distance(body_shape, body_shape_xform, col_obj->get_shape(shape_idx), col_obj_xform, point_A, point_B, motion_aabb, &sep_axis)) { + stuck = true; + break; + } + + //just do kinematic solving + real_t low = 0.0; + real_t hi = 1.0; + real_t fraction_coeff = 0.5; + for (int k = 0; k < 8; k++) { //steps should be customizable.. + real_t fraction = low + (hi - low) * fraction_coeff; + + mshape.motion = body_shape_xform_inv.basis.xform(p_parameters.motion * fraction); + + Vector3 lA, lB; + Vector3 sep = motion_normal; //important optimization for this to work fast enough + bool collided = !GodotCollisionSolver3D::solve_distance(&mshape, body_shape_xform, col_obj->get_shape(shape_idx), col_obj_xform, lA, lB, motion_aabb, &sep); + + if (collided) { + hi = fraction; + if ((k == 0) || (low > 0.0)) { // Did it not collide before? + // When alternating or first iteration, use dichotomy. + fraction_coeff = 0.5; + } else { + // When colliding again, converge faster towards low fraction + // for more accurate results with long motions that collide near the start. + fraction_coeff = 0.25; + } + } else { + point_A = lA; + point_B = lB; + low = fraction; + if ((k == 0) || (hi < 1.0)) { // Did it collide before? + // When alternating or first iteration, use dichotomy. + fraction_coeff = 0.5; + } else { + // When not colliding again, converge faster towards high fraction + // for more accurate results with long motions that collide near the end. + fraction_coeff = 0.75; + } + } + } + + if (low < best_safe) { + best_safe = low; + best_unsafe = hi; + } + } + + if (stuck) { + safe = 0; + unsafe = 0; + best_shape = j; //sadly it's the best + break; + } + if (best_safe == 1.0) { + continue; + } + if (best_safe < safe) { + safe = best_safe; + unsafe = best_unsafe; + best_shape = j; + } + } + } + + bool collided = false; + if ((p_parameters.recovery_as_collision && recovered) || (safe < 1)) { + if (safe >= 1) { + best_shape = -1; //no best shape with cast, reset to -1 + } + + //it collided, let's get the rest info in unsafe advance + Transform3D ugt = body_transform; + ugt.origin += p_parameters.motion * unsafe; + + _RestResultData results[PhysicsServer3D::MotionResult::MAX_COLLISIONS]; + + _RestCallbackData rcd; + if (p_parameters.max_collisions > 1) { + rcd.max_results = p_parameters.max_collisions; + rcd.other_results = results; + } + + // Allowed depth can't be lower than motion length, in order to handle contacts at low speed. + rcd.min_allowed_depth = MIN(motion_length, min_contact_depth); + + body_aabb.position += p_parameters.motion * unsafe; + int amount = _cull_aabb_for_body(p_body, body_aabb); + + int from_shape = best_shape != -1 ? best_shape : 0; + int to_shape = best_shape != -1 ? best_shape + 1 : p_body->get_shape_count(); + + for (int j = from_shape; j < to_shape; j++) { + if (p_body->is_shape_disabled(j)) { + continue; + } + + Transform3D body_shape_xform = ugt * p_body->get_shape_transform(j); + GodotShape3D *body_shape = p_body->get_shape(j); + + for (int i = 0; i < amount; i++) { + const GodotCollisionObject3D *col_obj = intersection_query_results[i]; + if (p_parameters.exclude_bodies.has(col_obj->get_self())) { + continue; + } + if (p_parameters.exclude_objects.has(col_obj->get_instance_id())) { + continue; + } + + int shape_idx = intersection_query_subindex_results[i]; + + rcd.object = col_obj; + rcd.shape = shape_idx; + bool sc = GodotCollisionSolver3D::solve_static(body_shape, body_shape_xform, col_obj->get_shape(shape_idx), col_obj->get_transform() * col_obj->get_shape_transform(shape_idx), _rest_cbk_result, &rcd, nullptr, margin); + if (!sc) { + continue; + } + } + } + + if (rcd.result_count > 0) { + if (r_result) { + for (int collision_index = 0; collision_index < rcd.result_count; ++collision_index) { + const _RestResultData &result = (collision_index > 0) ? rcd.other_results[collision_index - 1] : rcd.best_result; + + PhysicsServer3D::MotionCollision &collision = r_result->collisions[collision_index]; + + collision.collider = result.object->get_self(); + collision.collider_id = result.object->get_instance_id(); + collision.collider_shape = result.shape; + collision.local_shape = result.local_shape; + collision.normal = result.normal; + collision.position = result.contact; + collision.depth = result.len; + + const GodotBody3D *body = static_cast<const GodotBody3D *>(result.object); + + Vector3 rel_vec = result.contact - (body->get_transform().origin + body->get_center_of_mass()); + collision.collider_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(rel_vec); + collision.collider_angular_velocity = body->get_angular_velocity(); + } + + r_result->travel = safe * p_parameters.motion; + r_result->remainder = p_parameters.motion - safe * p_parameters.motion; + r_result->travel += (body_transform.get_origin() - p_parameters.from.get_origin()); + + r_result->collision_safe_fraction = safe; + r_result->collision_unsafe_fraction = unsafe; + + r_result->collision_count = rcd.result_count; + r_result->collision_depth = rcd.best_result.len; + } + + collided = true; + } + } + + if (!collided && r_result) { + r_result->travel = p_parameters.motion; + r_result->remainder = Vector3(); + r_result->travel += (body_transform.get_origin() - p_parameters.from.get_origin()); + + r_result->collision_safe_fraction = 1.0; + r_result->collision_unsafe_fraction = 1.0; + r_result->collision_depth = 0.0; + } + + return collided; +} + +// Assumes a valid collision pair, this should have been checked beforehand in the BVH or octree. +void *GodotSpace3D::_broadphase_pair(GodotCollisionObject3D *A, int p_subindex_A, GodotCollisionObject3D *B, int p_subindex_B, void *p_self) { + GodotCollisionObject3D::Type type_A = A->get_type(); + GodotCollisionObject3D::Type type_B = B->get_type(); + if (type_A > type_B) { + SWAP(A, B); + SWAP(p_subindex_A, p_subindex_B); + SWAP(type_A, type_B); + } + + GodotSpace3D *self = static_cast<GodotSpace3D *>(p_self); + + self->collision_pairs++; + + if (type_A == GodotCollisionObject3D::TYPE_AREA) { + GodotArea3D *area = static_cast<GodotArea3D *>(A); + if (type_B == GodotCollisionObject3D::TYPE_AREA) { + GodotArea3D *area_b = static_cast<GodotArea3D *>(B); + GodotArea2Pair3D *area2_pair = memnew(GodotArea2Pair3D(area_b, p_subindex_B, area, p_subindex_A)); + return area2_pair; + } else if (type_B == GodotCollisionObject3D::TYPE_SOFT_BODY) { + GodotSoftBody3D *softbody = static_cast<GodotSoftBody3D *>(B); + GodotAreaSoftBodyPair3D *soft_area_pair = memnew(GodotAreaSoftBodyPair3D(softbody, p_subindex_B, area, p_subindex_A)); + return soft_area_pair; + } else { + GodotBody3D *body = static_cast<GodotBody3D *>(B); + GodotAreaPair3D *area_pair = memnew(GodotAreaPair3D(body, p_subindex_B, area, p_subindex_A)); + return area_pair; + } + } else if (type_A == GodotCollisionObject3D::TYPE_BODY) { + if (type_B == GodotCollisionObject3D::TYPE_SOFT_BODY) { + GodotBodySoftBodyPair3D *soft_pair = memnew(GodotBodySoftBodyPair3D(static_cast<GodotBody3D *>(A), p_subindex_A, static_cast<GodotSoftBody3D *>(B))); + return soft_pair; + } else { + GodotBodyPair3D *b = memnew(GodotBodyPair3D(static_cast<GodotBody3D *>(A), p_subindex_A, static_cast<GodotBody3D *>(B), p_subindex_B)); + return b; + } + } else { + // Soft Body/Soft Body, not supported. + } + + return nullptr; +} + +void GodotSpace3D::_broadphase_unpair(GodotCollisionObject3D *A, int p_subindex_A, GodotCollisionObject3D *B, int p_subindex_B, void *p_data, void *p_self) { + if (!p_data) { + return; + } + + GodotSpace3D *self = static_cast<GodotSpace3D *>(p_self); + self->collision_pairs--; + GodotConstraint3D *c = static_cast<GodotConstraint3D *>(p_data); + memdelete(c); +} + +const SelfList<GodotBody3D>::List &GodotSpace3D::get_active_body_list() const { + return active_list; +} + +void GodotSpace3D::body_add_to_active_list(SelfList<GodotBody3D> *p_body) { + active_list.add(p_body); +} + +void GodotSpace3D::body_remove_from_active_list(SelfList<GodotBody3D> *p_body) { + active_list.remove(p_body); +} + +void GodotSpace3D::body_add_to_mass_properties_update_list(SelfList<GodotBody3D> *p_body) { + mass_properties_update_list.add(p_body); +} + +void GodotSpace3D::body_remove_from_mass_properties_update_list(SelfList<GodotBody3D> *p_body) { + mass_properties_update_list.remove(p_body); +} + +GodotBroadPhase3D *GodotSpace3D::get_broadphase() { + return broadphase; +} + +void GodotSpace3D::add_object(GodotCollisionObject3D *p_object) { + ERR_FAIL_COND(objects.has(p_object)); + objects.insert(p_object); +} + +void GodotSpace3D::remove_object(GodotCollisionObject3D *p_object) { + ERR_FAIL_COND(!objects.has(p_object)); + objects.erase(p_object); +} + +const HashSet<GodotCollisionObject3D *> &GodotSpace3D::get_objects() const { + return objects; +} + +void GodotSpace3D::body_add_to_state_query_list(SelfList<GodotBody3D> *p_body) { + state_query_list.add(p_body); +} + +void GodotSpace3D::body_remove_from_state_query_list(SelfList<GodotBody3D> *p_body) { + state_query_list.remove(p_body); +} + +void GodotSpace3D::area_add_to_monitor_query_list(SelfList<GodotArea3D> *p_area) { + monitor_query_list.add(p_area); +} + +void GodotSpace3D::area_remove_from_monitor_query_list(SelfList<GodotArea3D> *p_area) { + monitor_query_list.remove(p_area); +} + +void GodotSpace3D::area_add_to_moved_list(SelfList<GodotArea3D> *p_area) { + area_moved_list.add(p_area); +} + +void GodotSpace3D::area_remove_from_moved_list(SelfList<GodotArea3D> *p_area) { + area_moved_list.remove(p_area); +} + +const SelfList<GodotArea3D>::List &GodotSpace3D::get_moved_area_list() const { + return area_moved_list; +} + +const SelfList<GodotSoftBody3D>::List &GodotSpace3D::get_active_soft_body_list() const { + return active_soft_body_list; +} + +void GodotSpace3D::soft_body_add_to_active_list(SelfList<GodotSoftBody3D> *p_soft_body) { + active_soft_body_list.add(p_soft_body); +} + +void GodotSpace3D::soft_body_remove_from_active_list(SelfList<GodotSoftBody3D> *p_soft_body) { + active_soft_body_list.remove(p_soft_body); +} + +void GodotSpace3D::call_queries() { + while (state_query_list.first()) { + GodotBody3D *b = state_query_list.first()->self(); + state_query_list.remove(state_query_list.first()); + b->call_queries(); + } + + while (monitor_query_list.first()) { + GodotArea3D *a = monitor_query_list.first()->self(); + monitor_query_list.remove(monitor_query_list.first()); + a->call_queries(); + } +} + +void GodotSpace3D::setup() { + contact_debug_count = 0; + while (mass_properties_update_list.first()) { + mass_properties_update_list.first()->self()->update_mass_properties(); + mass_properties_update_list.remove(mass_properties_update_list.first()); + } +} + +void GodotSpace3D::update() { + broadphase->update(); +} + +void GodotSpace3D::set_param(PhysicsServer3D::SpaceParameter p_param, real_t p_value) { + switch (p_param) { + case PhysicsServer3D::SPACE_PARAM_CONTACT_RECYCLE_RADIUS: + contact_recycle_radius = p_value; + break; + case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_SEPARATION: + contact_max_separation = p_value; + break; + case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_ALLOWED_PENETRATION: + contact_max_allowed_penetration = p_value; + break; + case PhysicsServer3D::SPACE_PARAM_CONTACT_DEFAULT_BIAS: + contact_bias = p_value; + break; + case PhysicsServer3D::SPACE_PARAM_BODY_LINEAR_VELOCITY_SLEEP_THRESHOLD: + body_linear_velocity_sleep_threshold = p_value; + break; + case PhysicsServer3D::SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD: + body_angular_velocity_sleep_threshold = p_value; + break; + case PhysicsServer3D::SPACE_PARAM_BODY_TIME_TO_SLEEP: + body_time_to_sleep = p_value; + break; + case PhysicsServer3D::SPACE_PARAM_SOLVER_ITERATIONS: + solver_iterations = p_value; + break; + } +} + +real_t GodotSpace3D::get_param(PhysicsServer3D::SpaceParameter p_param) const { + switch (p_param) { + case PhysicsServer3D::SPACE_PARAM_CONTACT_RECYCLE_RADIUS: + return contact_recycle_radius; + case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_SEPARATION: + return contact_max_separation; + case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_ALLOWED_PENETRATION: + return contact_max_allowed_penetration; + case PhysicsServer3D::SPACE_PARAM_CONTACT_DEFAULT_BIAS: + return contact_bias; + case PhysicsServer3D::SPACE_PARAM_BODY_LINEAR_VELOCITY_SLEEP_THRESHOLD: + return body_linear_velocity_sleep_threshold; + case PhysicsServer3D::SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD: + return body_angular_velocity_sleep_threshold; + case PhysicsServer3D::SPACE_PARAM_BODY_TIME_TO_SLEEP: + return body_time_to_sleep; + case PhysicsServer3D::SPACE_PARAM_SOLVER_ITERATIONS: + return solver_iterations; + } + return 0; +} + +void GodotSpace3D::lock() { + locked = true; +} + +void GodotSpace3D::unlock() { + locked = false; +} + +bool GodotSpace3D::is_locked() const { + return locked; +} + +GodotPhysicsDirectSpaceState3D *GodotSpace3D::get_direct_state() { + return direct_access; +} + +GodotSpace3D::GodotSpace3D() { + body_linear_velocity_sleep_threshold = GLOBAL_GET("physics/3d/sleep_threshold_linear"); + body_angular_velocity_sleep_threshold = GLOBAL_GET("physics/3d/sleep_threshold_angular"); + body_time_to_sleep = GLOBAL_GET("physics/3d/time_before_sleep"); + solver_iterations = GLOBAL_GET("physics/3d/solver/solver_iterations"); + contact_recycle_radius = GLOBAL_GET("physics/3d/solver/contact_recycle_radius"); + contact_max_separation = GLOBAL_GET("physics/3d/solver/contact_max_separation"); + contact_max_allowed_penetration = GLOBAL_GET("physics/3d/solver/contact_max_allowed_penetration"); + contact_bias = GLOBAL_GET("physics/3d/solver/default_contact_bias"); + + broadphase = GodotBroadPhase3D::create_func(); + broadphase->set_pair_callback(_broadphase_pair, this); + broadphase->set_unpair_callback(_broadphase_unpair, this); + + direct_access = memnew(GodotPhysicsDirectSpaceState3D); + direct_access->space = this; +} + +GodotSpace3D::~GodotSpace3D() { + memdelete(broadphase); + memdelete(direct_access); +} diff --git a/modules/godot_physics_3d/godot_space_3d.h b/modules/godot_physics_3d/godot_space_3d.h new file mode 100644 index 0000000000..f476be5934 --- /dev/null +++ b/modules/godot_physics_3d/godot_space_3d.h @@ -0,0 +1,218 @@ +/**************************************************************************/ +/* godot_space_3d.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 GODOT_SPACE_3D_H +#define GODOT_SPACE_3D_H + +#include "godot_area_3d.h" +#include "godot_area_pair_3d.h" +#include "godot_body_3d.h" +#include "godot_body_pair_3d.h" +#include "godot_broad_phase_3d.h" +#include "godot_collision_object_3d.h" +#include "godot_soft_body_3d.h" + +#include "core/config/project_settings.h" +#include "core/templates/hash_map.h" +#include "core/typedefs.h" + +class GodotPhysicsDirectSpaceState3D : public PhysicsDirectSpaceState3D { + GDCLASS(GodotPhysicsDirectSpaceState3D, PhysicsDirectSpaceState3D); + +public: + GodotSpace3D *space = nullptr; + + virtual int intersect_point(const PointParameters &p_parameters, ShapeResult *r_results, int p_result_max) override; + virtual bool intersect_ray(const RayParameters &p_parameters, RayResult &r_result) override; + virtual int intersect_shape(const ShapeParameters &p_parameters, ShapeResult *r_results, int p_result_max) override; + virtual bool cast_motion(const ShapeParameters &p_parameters, real_t &p_closest_safe, real_t &p_closest_unsafe, ShapeRestInfo *r_info = nullptr) override; + virtual bool collide_shape(const ShapeParameters &p_parameters, Vector3 *r_results, int p_result_max, int &r_result_count) override; + virtual bool rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) override; + virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const override; + + GodotPhysicsDirectSpaceState3D(); +}; + +class GodotSpace3D { +public: + enum ElapsedTime { + ELAPSED_TIME_INTEGRATE_FORCES, + ELAPSED_TIME_GENERATE_ISLANDS, + ELAPSED_TIME_SETUP_CONSTRAINTS, + ELAPSED_TIME_SOLVE_CONSTRAINTS, + ELAPSED_TIME_INTEGRATE_VELOCITIES, + ELAPSED_TIME_MAX + + }; + +private: + uint64_t elapsed_time[ELAPSED_TIME_MAX] = {}; + + GodotPhysicsDirectSpaceState3D *direct_access = nullptr; + RID self; + + GodotBroadPhase3D *broadphase = nullptr; + SelfList<GodotBody3D>::List active_list; + SelfList<GodotBody3D>::List mass_properties_update_list; + SelfList<GodotBody3D>::List state_query_list; + SelfList<GodotArea3D>::List monitor_query_list; + SelfList<GodotArea3D>::List area_moved_list; + SelfList<GodotSoftBody3D>::List active_soft_body_list; + + static void *_broadphase_pair(GodotCollisionObject3D *A, int p_subindex_A, GodotCollisionObject3D *B, int p_subindex_B, void *p_self); + static void _broadphase_unpair(GodotCollisionObject3D *A, int p_subindex_A, GodotCollisionObject3D *B, int p_subindex_B, void *p_data, void *p_self); + + HashSet<GodotCollisionObject3D *> objects; + + GodotArea3D *area = nullptr; + + int solver_iterations = 0; + + real_t contact_recycle_radius = 0.0; + real_t contact_max_separation = 0.0; + real_t contact_max_allowed_penetration = 0.0; + real_t contact_bias = 0.0; + + enum { + INTERSECTION_QUERY_MAX = 2048 + }; + + GodotCollisionObject3D *intersection_query_results[INTERSECTION_QUERY_MAX]; + int intersection_query_subindex_results[INTERSECTION_QUERY_MAX]; + + real_t body_linear_velocity_sleep_threshold = 0.0; + real_t body_angular_velocity_sleep_threshold = 0.0; + real_t body_time_to_sleep = 0.0; + + bool locked = false; + + real_t last_step = 0.001; + + int island_count = 0; + int active_objects = 0; + int collision_pairs = 0; + + RID static_global_body; + + Vector<Vector3> contact_debug; + int contact_debug_count = 0; + + friend class GodotPhysicsDirectSpaceState3D; + + int _cull_aabb_for_body(GodotBody3D *p_body, const AABB &p_aabb); + +public: + _FORCE_INLINE_ void set_self(const RID &p_self) { self = p_self; } + _FORCE_INLINE_ RID get_self() const { return self; } + + void set_default_area(GodotArea3D *p_area) { area = p_area; } + GodotArea3D *get_default_area() const { return area; } + + const SelfList<GodotBody3D>::List &get_active_body_list() const; + void body_add_to_active_list(SelfList<GodotBody3D> *p_body); + void body_remove_from_active_list(SelfList<GodotBody3D> *p_body); + void body_add_to_mass_properties_update_list(SelfList<GodotBody3D> *p_body); + void body_remove_from_mass_properties_update_list(SelfList<GodotBody3D> *p_body); + + void body_add_to_state_query_list(SelfList<GodotBody3D> *p_body); + void body_remove_from_state_query_list(SelfList<GodotBody3D> *p_body); + + void area_add_to_monitor_query_list(SelfList<GodotArea3D> *p_area); + void area_remove_from_monitor_query_list(SelfList<GodotArea3D> *p_area); + void area_add_to_moved_list(SelfList<GodotArea3D> *p_area); + void area_remove_from_moved_list(SelfList<GodotArea3D> *p_area); + const SelfList<GodotArea3D>::List &get_moved_area_list() const; + + const SelfList<GodotSoftBody3D>::List &get_active_soft_body_list() const; + void soft_body_add_to_active_list(SelfList<GodotSoftBody3D> *p_soft_body); + void soft_body_remove_from_active_list(SelfList<GodotSoftBody3D> *p_soft_body); + + GodotBroadPhase3D *get_broadphase(); + + void add_object(GodotCollisionObject3D *p_object); + void remove_object(GodotCollisionObject3D *p_object); + const HashSet<GodotCollisionObject3D *> &get_objects() const; + + _FORCE_INLINE_ int get_solver_iterations() const { return solver_iterations; } + _FORCE_INLINE_ real_t get_contact_recycle_radius() const { return contact_recycle_radius; } + _FORCE_INLINE_ real_t get_contact_max_separation() const { return contact_max_separation; } + _FORCE_INLINE_ real_t get_contact_max_allowed_penetration() const { return contact_max_allowed_penetration; } + _FORCE_INLINE_ real_t get_contact_bias() const { return contact_bias; } + _FORCE_INLINE_ real_t get_body_linear_velocity_sleep_threshold() const { return body_linear_velocity_sleep_threshold; } + _FORCE_INLINE_ real_t get_body_angular_velocity_sleep_threshold() const { return body_angular_velocity_sleep_threshold; } + _FORCE_INLINE_ real_t get_body_time_to_sleep() const { return body_time_to_sleep; } + + void update(); + void setup(); + void call_queries(); + + bool is_locked() const; + void lock(); + void unlock(); + + real_t get_last_step() const { return last_step; } + void set_last_step(real_t p_step) { last_step = p_step; } + + void set_param(PhysicsServer3D::SpaceParameter p_param, real_t p_value); + real_t get_param(PhysicsServer3D::SpaceParameter p_param) const; + + void set_island_count(int p_island_count) { island_count = p_island_count; } + int get_island_count() const { return island_count; } + + void set_active_objects(int p_active_objects) { active_objects = p_active_objects; } + int get_active_objects() const { return active_objects; } + + int get_collision_pairs() const { return collision_pairs; } + + GodotPhysicsDirectSpaceState3D *get_direct_state(); + + void set_debug_contacts(int p_amount) { contact_debug.resize(p_amount); } + _FORCE_INLINE_ bool is_debugging_contacts() const { return !contact_debug.is_empty(); } + _FORCE_INLINE_ void add_debug_contact(const Vector3 &p_contact) { + if (contact_debug_count < contact_debug.size()) { + contact_debug.write[contact_debug_count++] = p_contact; + } + } + _FORCE_INLINE_ Vector<Vector3> get_debug_contacts() { return contact_debug; } + _FORCE_INLINE_ int get_debug_contact_count() { return contact_debug_count; } + + void set_static_global_body(RID p_body) { static_global_body = p_body; } + RID get_static_global_body() { return static_global_body; } + + void set_elapsed_time(ElapsedTime p_time, uint64_t p_msec) { elapsed_time[p_time] = p_msec; } + uint64_t get_elapsed_time(ElapsedTime p_time) const { return elapsed_time[p_time]; } + + bool test_body_motion(GodotBody3D *p_body, const PhysicsServer3D::MotionParameters &p_parameters, PhysicsServer3D::MotionResult *r_result); + + GodotSpace3D(); + ~GodotSpace3D(); +}; + +#endif // GODOT_SPACE_3D_H diff --git a/modules/godot_physics_3d/godot_step_3d.cpp b/modules/godot_physics_3d/godot_step_3d.cpp new file mode 100644 index 0000000000..d09a3b4e6d --- /dev/null +++ b/modules/godot_physics_3d/godot_step_3d.cpp @@ -0,0 +1,418 @@ +/**************************************************************************/ +/* godot_step_3d.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 "godot_step_3d.h" + +#include "godot_joint_3d.h" + +#include "core/object/worker_thread_pool.h" +#include "core/os/os.h" + +#define BODY_ISLAND_COUNT_RESERVE 128 +#define BODY_ISLAND_SIZE_RESERVE 512 +#define ISLAND_COUNT_RESERVE 128 +#define ISLAND_SIZE_RESERVE 512 +#define CONSTRAINT_COUNT_RESERVE 1024 + +void GodotStep3D::_populate_island(GodotBody3D *p_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island) { + p_body->set_island_step(_step); + + if (p_body->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC) { + // Only rigid bodies are tested for activation. + p_body_island.push_back(p_body); + } + + for (const KeyValue<GodotConstraint3D *, int> &E : p_body->get_constraint_map()) { + GodotConstraint3D *constraint = const_cast<GodotConstraint3D *>(E.key); + if (constraint->get_island_step() == _step) { + continue; // Already processed. + } + constraint->set_island_step(_step); + p_constraint_island.push_back(constraint); + + all_constraints.push_back(constraint); + + // Find connected rigid bodies. + for (int i = 0; i < constraint->get_body_count(); i++) { + if (i == E.value) { + continue; + } + GodotBody3D *other_body = constraint->get_body_ptr()[i]; + if (other_body->get_island_step() == _step) { + continue; // Already processed. + } + if (other_body->get_mode() == PhysicsServer3D::BODY_MODE_STATIC) { + continue; // Static bodies don't connect islands. + } + _populate_island(other_body, p_body_island, p_constraint_island); + } + + // Find connected soft bodies. + for (int i = 0; i < constraint->get_soft_body_count(); i++) { + GodotSoftBody3D *soft_body = constraint->get_soft_body_ptr(i); + if (soft_body->get_island_step() == _step) { + continue; // Already processed. + } + _populate_island_soft_body(soft_body, p_body_island, p_constraint_island); + } + } +} + +void GodotStep3D::_populate_island_soft_body(GodotSoftBody3D *p_soft_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island) { + p_soft_body->set_island_step(_step); + + for (const GodotConstraint3D *E : p_soft_body->get_constraints()) { + GodotConstraint3D *constraint = const_cast<GodotConstraint3D *>(E); + if (constraint->get_island_step() == _step) { + continue; // Already processed. + } + constraint->set_island_step(_step); + p_constraint_island.push_back(constraint); + + all_constraints.push_back(constraint); + + // Find connected rigid bodies. + for (int i = 0; i < constraint->get_body_count(); i++) { + GodotBody3D *body = constraint->get_body_ptr()[i]; + if (body->get_island_step() == _step) { + continue; // Already processed. + } + if (body->get_mode() == PhysicsServer3D::BODY_MODE_STATIC) { + continue; // Static bodies don't connect islands. + } + _populate_island(body, p_body_island, p_constraint_island); + } + } +} + +void GodotStep3D::_setup_constraint(uint32_t p_constraint_index, void *p_userdata) { + GodotConstraint3D *constraint = all_constraints[p_constraint_index]; + constraint->setup(delta); +} + +void GodotStep3D::_pre_solve_island(LocalVector<GodotConstraint3D *> &p_constraint_island) const { + uint32_t constraint_count = p_constraint_island.size(); + uint32_t valid_constraint_count = 0; + for (uint32_t constraint_index = 0; constraint_index < constraint_count; ++constraint_index) { + GodotConstraint3D *constraint = p_constraint_island[constraint_index]; + if (p_constraint_island[constraint_index]->pre_solve(delta)) { + // Keep this constraint for solving. + p_constraint_island[valid_constraint_count++] = constraint; + } + } + p_constraint_island.resize(valid_constraint_count); +} + +void GodotStep3D::_solve_island(uint32_t p_island_index, void *p_userdata) { + LocalVector<GodotConstraint3D *> &constraint_island = constraint_islands[p_island_index]; + + int current_priority = 1; + + uint32_t constraint_count = constraint_island.size(); + while (constraint_count > 0) { + for (int i = 0; i < iterations; i++) { + // Go through all iterations. + for (uint32_t constraint_index = 0; constraint_index < constraint_count; ++constraint_index) { + constraint_island[constraint_index]->solve(delta); + } + } + + // Check priority to keep only higher priority constraints. + uint32_t priority_constraint_count = 0; + ++current_priority; + for (uint32_t constraint_index = 0; constraint_index < constraint_count; ++constraint_index) { + GodotConstraint3D *constraint = constraint_island[constraint_index]; + if (constraint->get_priority() >= current_priority) { + // Keep this constraint for the next iteration. + constraint_island[priority_constraint_count++] = constraint; + } + } + constraint_count = priority_constraint_count; + } +} + +void GodotStep3D::_check_suspend(const LocalVector<GodotBody3D *> &p_body_island) const { + bool can_sleep = true; + + uint32_t body_count = p_body_island.size(); + for (uint32_t body_index = 0; body_index < body_count; ++body_index) { + GodotBody3D *body = p_body_island[body_index]; + + if (!body->sleep_test(delta)) { + can_sleep = false; + } + } + + // Put all to sleep or wake up everyone. + for (uint32_t body_index = 0; body_index < body_count; ++body_index) { + GodotBody3D *body = p_body_island[body_index]; + + bool active = body->is_active(); + + if (active == can_sleep) { + body->set_active(!can_sleep); + } + } +} + +void GodotStep3D::step(GodotSpace3D *p_space, real_t p_delta) { + p_space->lock(); // can't access space during this + + p_space->setup(); //update inertias, etc + + p_space->set_last_step(p_delta); + + iterations = p_space->get_solver_iterations(); + delta = p_delta; + + const SelfList<GodotBody3D>::List *body_list = &p_space->get_active_body_list(); + + const SelfList<GodotSoftBody3D>::List *soft_body_list = &p_space->get_active_soft_body_list(); + + /* INTEGRATE FORCES */ + + uint64_t profile_begtime = OS::get_singleton()->get_ticks_usec(); + uint64_t profile_endtime = 0; + + int active_count = 0; + + const SelfList<GodotBody3D> *b = body_list->first(); + while (b) { + b->self()->integrate_forces(p_delta); + b = b->next(); + active_count++; + } + + /* UPDATE SOFT BODY MOTION */ + + const SelfList<GodotSoftBody3D> *sb = soft_body_list->first(); + while (sb) { + sb->self()->predict_motion(p_delta); + sb = sb->next(); + active_count++; + } + + p_space->set_active_objects(active_count); + + // Update the broadphase to register collision pairs. + p_space->update(); + + { //profile + profile_endtime = OS::get_singleton()->get_ticks_usec(); + p_space->set_elapsed_time(GodotSpace3D::ELAPSED_TIME_INTEGRATE_FORCES, profile_endtime - profile_begtime); + profile_begtime = profile_endtime; + } + + /* GENERATE CONSTRAINT ISLANDS FOR MOVING AREAS */ + + uint32_t island_count = 0; + + const SelfList<GodotArea3D>::List &aml = p_space->get_moved_area_list(); + + while (aml.first()) { + for (GodotConstraint3D *E : aml.first()->self()->get_constraints()) { + GodotConstraint3D *constraint = E; + if (constraint->get_island_step() == _step) { + continue; + } + constraint->set_island_step(_step); + + // Each constraint can be on a separate island for areas as there's no solving phase. + ++island_count; + if (constraint_islands.size() < island_count) { + constraint_islands.resize(island_count); + } + LocalVector<GodotConstraint3D *> &constraint_island = constraint_islands[island_count - 1]; + constraint_island.clear(); + + all_constraints.push_back(constraint); + constraint_island.push_back(constraint); + } + p_space->area_remove_from_moved_list((SelfList<GodotArea3D> *)aml.first()); //faster to remove here + } + + /* GENERATE CONSTRAINT ISLANDS FOR ACTIVE RIGID BODIES */ + + b = body_list->first(); + + uint32_t body_island_count = 0; + + while (b) { + GodotBody3D *body = b->self(); + + if (body->get_island_step() != _step) { + ++body_island_count; + if (body_islands.size() < body_island_count) { + body_islands.resize(body_island_count); + } + LocalVector<GodotBody3D *> &body_island = body_islands[body_island_count - 1]; + body_island.clear(); + body_island.reserve(BODY_ISLAND_SIZE_RESERVE); + + ++island_count; + if (constraint_islands.size() < island_count) { + constraint_islands.resize(island_count); + } + LocalVector<GodotConstraint3D *> &constraint_island = constraint_islands[island_count - 1]; + constraint_island.clear(); + constraint_island.reserve(ISLAND_SIZE_RESERVE); + + _populate_island(body, body_island, constraint_island); + + if (body_island.is_empty()) { + --body_island_count; + } + + if (constraint_island.is_empty()) { + --island_count; + } + } + b = b->next(); + } + + /* GENERATE CONSTRAINT ISLANDS FOR ACTIVE SOFT BODIES */ + + sb = soft_body_list->first(); + while (sb) { + GodotSoftBody3D *soft_body = sb->self(); + + if (soft_body->get_island_step() != _step) { + ++body_island_count; + if (body_islands.size() < body_island_count) { + body_islands.resize(body_island_count); + } + LocalVector<GodotBody3D *> &body_island = body_islands[body_island_count - 1]; + body_island.clear(); + body_island.reserve(BODY_ISLAND_SIZE_RESERVE); + + ++island_count; + if (constraint_islands.size() < island_count) { + constraint_islands.resize(island_count); + } + LocalVector<GodotConstraint3D *> &constraint_island = constraint_islands[island_count - 1]; + constraint_island.clear(); + constraint_island.reserve(ISLAND_SIZE_RESERVE); + + _populate_island_soft_body(soft_body, body_island, constraint_island); + + if (body_island.is_empty()) { + --body_island_count; + } + + if (constraint_island.is_empty()) { + --island_count; + } + } + sb = sb->next(); + } + + p_space->set_island_count((int)island_count); + + { //profile + profile_endtime = OS::get_singleton()->get_ticks_usec(); + p_space->set_elapsed_time(GodotSpace3D::ELAPSED_TIME_GENERATE_ISLANDS, profile_endtime - profile_begtime); + profile_begtime = profile_endtime; + } + + /* SETUP CONSTRAINTS / PROCESS COLLISIONS */ + + uint32_t total_constraint_count = all_constraints.size(); + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_setup_constraint, nullptr, total_constraint_count, -1, true, SNAME("Physics3DConstraintSetup")); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + + { //profile + profile_endtime = OS::get_singleton()->get_ticks_usec(); + p_space->set_elapsed_time(GodotSpace3D::ELAPSED_TIME_SETUP_CONSTRAINTS, profile_endtime - profile_begtime); + profile_begtime = profile_endtime; + } + + /* PRE-SOLVE CONSTRAINT ISLANDS */ + + // Warning: This doesn't run on threads, because it involves thread-unsafe processing. + for (uint32_t island_index = 0; island_index < island_count; ++island_index) { + _pre_solve_island(constraint_islands[island_index]); + } + + /* SOLVE CONSTRAINT ISLANDS */ + + // Warning: _solve_island modifies the constraint islands for optimization purpose, + // their content is not reliable after these calls and shouldn't be used anymore. + group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_solve_island, nullptr, island_count, -1, true, SNAME("Physics3DConstraintSolveIslands")); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + + { //profile + profile_endtime = OS::get_singleton()->get_ticks_usec(); + p_space->set_elapsed_time(GodotSpace3D::ELAPSED_TIME_SOLVE_CONSTRAINTS, profile_endtime - profile_begtime); + profile_begtime = profile_endtime; + } + + /* INTEGRATE VELOCITIES */ + + b = body_list->first(); + while (b) { + const SelfList<GodotBody3D> *n = b->next(); + b->self()->integrate_velocities(p_delta); + b = n; + } + + /* SLEEP / WAKE UP ISLANDS */ + + for (uint32_t island_index = 0; island_index < body_island_count; ++island_index) { + _check_suspend(body_islands[island_index]); + } + + /* UPDATE SOFT BODY CONSTRAINTS */ + + sb = soft_body_list->first(); + while (sb) { + sb->self()->solve_constraints(p_delta); + sb = sb->next(); + } + + { //profile + profile_endtime = OS::get_singleton()->get_ticks_usec(); + p_space->set_elapsed_time(GodotSpace3D::ELAPSED_TIME_INTEGRATE_VELOCITIES, profile_endtime - profile_begtime); + profile_begtime = profile_endtime; + } + + all_constraints.clear(); + + p_space->unlock(); + _step++; +} + +GodotStep3D::GodotStep3D() { + body_islands.reserve(BODY_ISLAND_COUNT_RESERVE); + constraint_islands.reserve(ISLAND_COUNT_RESERVE); + all_constraints.reserve(CONSTRAINT_COUNT_RESERVE); +} + +GodotStep3D::~GodotStep3D() { +} diff --git a/modules/godot_physics_3d/godot_step_3d.h b/modules/godot_physics_3d/godot_step_3d.h new file mode 100644 index 0000000000..1c9b0af422 --- /dev/null +++ b/modules/godot_physics_3d/godot_step_3d.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* godot_step_3d.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 GODOT_STEP_3D_H +#define GODOT_STEP_3D_H + +#include "godot_space_3d.h" + +#include "core/templates/local_vector.h" + +class GodotStep3D { + uint64_t _step = 1; + + int iterations = 0; + real_t delta = 0.0; + + LocalVector<LocalVector<GodotBody3D *>> body_islands; + LocalVector<LocalVector<GodotConstraint3D *>> constraint_islands; + LocalVector<GodotConstraint3D *> all_constraints; + + void _populate_island(GodotBody3D *p_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island); + void _populate_island_soft_body(GodotSoftBody3D *p_soft_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island); + void _setup_constraint(uint32_t p_constraint_index, void *p_userdata = nullptr); + void _pre_solve_island(LocalVector<GodotConstraint3D *> &p_constraint_island) const; + void _solve_island(uint32_t p_island_index, void *p_userdata = nullptr); + void _check_suspend(const LocalVector<GodotBody3D *> &p_body_island) const; + +public: + void step(GodotSpace3D *p_space, real_t p_delta); + GodotStep3D(); + ~GodotStep3D(); +}; + +#endif // GODOT_STEP_3D_H diff --git a/modules/godot_physics_3d/joints/SCsub b/modules/godot_physics_3d/joints/SCsub new file mode 100644 index 0000000000..5d93da5ecf --- /dev/null +++ b/modules/godot_physics_3d/joints/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.cpp new file mode 100644 index 0000000000..4091422789 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.cpp @@ -0,0 +1,326 @@ +/**************************************************************************/ +/* godot_cone_twist_joint_3d.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. */ +/**************************************************************************/ + +/* +Adapted to Godot from the Bullet library. +*/ + +/* +Bullet Continuous Collision Detection and Physics Library +ConeTwistJointSW is Copyright (c) 2007 Starbreeze Studios + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +Written by: Marcus Hennix +*/ + +#include "godot_cone_twist_joint_3d.h" + +GodotConeTwistJoint3D::GodotConeTwistJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &rbAFrame, const Transform3D &rbBFrame) : + GodotJoint3D(_arr, 2) { + A = rbA; + B = rbB; + + m_rbAFrame = rbAFrame; + m_rbBFrame = rbBFrame; + + A->add_constraint(this, 0); + B->add_constraint(this, 1); +} + +bool GodotConeTwistJoint3D::setup(real_t p_timestep) { + dynamic_A = (A->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + dynamic_B = (B->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + + if (!dynamic_A && !dynamic_B) { + return false; + } + + m_appliedImpulse = real_t(0.); + + //set bias, sign, clear accumulator + m_swingCorrection = real_t(0.); + m_twistLimitSign = real_t(0.); + m_solveTwistLimit = false; + m_solveSwingLimit = false; + m_accTwistLimitImpulse = real_t(0.); + m_accSwingLimitImpulse = real_t(0.); + + if (!m_angularOnly) { + Vector3 pivotAInW = A->get_transform().xform(m_rbAFrame.origin); + Vector3 pivotBInW = B->get_transform().xform(m_rbBFrame.origin); + Vector3 relPos = pivotBInW - pivotAInW; + + Vector3 normal[3]; + if (Math::is_zero_approx(relPos.length_squared())) { + normal[0] = Vector3(real_t(1.0), 0, 0); + } else { + normal[0] = relPos.normalized(); + } + + plane_space(normal[0], normal[1], normal[2]); + + for (int i = 0; i < 3; i++) { + memnew_placement( + &m_jac[i], + GodotJacobianEntry3D( + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + pivotAInW - A->get_transform().origin - A->get_center_of_mass(), + pivotBInW - B->get_transform().origin - B->get_center_of_mass(), + normal[i], + A->get_inv_inertia(), + A->get_inv_mass(), + B->get_inv_inertia(), + B->get_inv_mass())); + } + } + + Vector3 b1Axis1, b1Axis2, b1Axis3; + Vector3 b2Axis1, b2Axis2; + + b1Axis1 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(0)); + b2Axis1 = B->get_transform().basis.xform(m_rbBFrame.basis.get_column(0)); + + real_t swing1 = real_t(0.), swing2 = real_t(0.); + + real_t swx = real_t(0.), swy = real_t(0.); + real_t thresh = real_t(10.); + real_t fact; + + // Get Frame into world space + if (m_swingSpan1 >= real_t(0.05f)) { + b1Axis2 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(1)); + //swing1 = btAtan2Fast( b2Axis1.dot(b1Axis2),b2Axis1.dot(b1Axis1) ); + swx = b2Axis1.dot(b1Axis1); + swy = b2Axis1.dot(b1Axis2); + swing1 = atan2fast(swy, swx); + fact = (swy * swy + swx * swx) * thresh * thresh; + fact = fact / (fact + real_t(1.0)); + swing1 *= fact; + } + + if (m_swingSpan2 >= real_t(0.05f)) { + b1Axis3 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(2)); + //swing2 = btAtan2Fast( b2Axis1.dot(b1Axis3),b2Axis1.dot(b1Axis1) ); + swx = b2Axis1.dot(b1Axis1); + swy = b2Axis1.dot(b1Axis3); + swing2 = atan2fast(swy, swx); + fact = (swy * swy + swx * swx) * thresh * thresh; + fact = fact / (fact + real_t(1.0)); + swing2 *= fact; + } + + real_t RMaxAngle1Sq = 1.0f / (m_swingSpan1 * m_swingSpan1); + real_t RMaxAngle2Sq = 1.0f / (m_swingSpan2 * m_swingSpan2); + real_t EllipseAngle = Math::abs(swing1 * swing1) * RMaxAngle1Sq + Math::abs(swing2 * swing2) * RMaxAngle2Sq; + + if (EllipseAngle > 1.0f) { + m_swingCorrection = EllipseAngle - 1.0f; + m_solveSwingLimit = true; + + // Calculate necessary axis & factors + m_swingAxis = b2Axis1.cross(b1Axis2 * b2Axis1.dot(b1Axis2) + b1Axis3 * b2Axis1.dot(b1Axis3)); + m_swingAxis.normalize(); + + real_t swingAxisSign = (b2Axis1.dot(b1Axis1) >= 0.0f) ? 1.0f : -1.0f; + m_swingAxis *= swingAxisSign; + + m_kSwing = real_t(1.) / (A->compute_angular_impulse_denominator(m_swingAxis) + B->compute_angular_impulse_denominator(m_swingAxis)); + } + + // Twist limits + if (m_twistSpan >= real_t(0.)) { + Vector3 b2Axis22 = B->get_transform().basis.xform(m_rbBFrame.basis.get_column(1)); + Quaternion rotationArc = Quaternion(b2Axis1, b1Axis1); + Vector3 TwistRef = rotationArc.xform(b2Axis22); + real_t twist = atan2fast(TwistRef.dot(b1Axis3), TwistRef.dot(b1Axis2)); + + real_t lockedFreeFactor = (m_twistSpan > real_t(0.05f)) ? m_limitSoftness : real_t(0.); + if (twist <= -m_twistSpan * lockedFreeFactor) { + m_twistCorrection = -(twist + m_twistSpan); + m_solveTwistLimit = true; + + m_twistAxis = (b2Axis1 + b1Axis1) * 0.5f; + m_twistAxis.normalize(); + m_twistAxis *= -1.0f; + + m_kTwist = real_t(1.) / (A->compute_angular_impulse_denominator(m_twistAxis) + B->compute_angular_impulse_denominator(m_twistAxis)); + + } else if (twist > m_twistSpan * lockedFreeFactor) { + m_twistCorrection = (twist - m_twistSpan); + m_solveTwistLimit = true; + + m_twistAxis = (b2Axis1 + b1Axis1) * 0.5f; + m_twistAxis.normalize(); + + m_kTwist = real_t(1.) / (A->compute_angular_impulse_denominator(m_twistAxis) + B->compute_angular_impulse_denominator(m_twistAxis)); + } + } + + return true; +} + +void GodotConeTwistJoint3D::solve(real_t p_timestep) { + Vector3 pivotAInW = A->get_transform().xform(m_rbAFrame.origin); + Vector3 pivotBInW = B->get_transform().xform(m_rbBFrame.origin); + + real_t tau = real_t(0.3); + + //linear part + if (!m_angularOnly) { + Vector3 rel_pos1 = pivotAInW - A->get_transform().origin; + Vector3 rel_pos2 = pivotBInW - B->get_transform().origin; + + Vector3 vel1 = A->get_velocity_in_local_point(rel_pos1); + Vector3 vel2 = B->get_velocity_in_local_point(rel_pos2); + Vector3 vel = vel1 - vel2; + + for (int i = 0; i < 3; i++) { + const Vector3 &normal = m_jac[i].m_linearJointAxis; + real_t jacDiagABInv = real_t(1.) / m_jac[i].getDiagonal(); + + real_t rel_vel; + rel_vel = normal.dot(vel); + //positional error (zeroth order error) + real_t depth = -(pivotAInW - pivotBInW).dot(normal); //this is the error projected on the normal + real_t impulse = depth * tau / p_timestep * jacDiagABInv - rel_vel * jacDiagABInv; + m_appliedImpulse += impulse; + Vector3 impulse_vector = normal * impulse; + if (dynamic_A) { + A->apply_impulse(impulse_vector, pivotAInW - A->get_transform().origin); + } + if (dynamic_B) { + B->apply_impulse(-impulse_vector, pivotBInW - B->get_transform().origin); + } + } + } + + { + ///solve angular part + const Vector3 &angVelA = A->get_angular_velocity(); + const Vector3 &angVelB = B->get_angular_velocity(); + + // solve swing limit + if (m_solveSwingLimit) { + real_t amplitude = ((angVelB - angVelA).dot(m_swingAxis) * m_relaxationFactor * m_relaxationFactor + m_swingCorrection * (real_t(1.) / p_timestep) * m_biasFactor); + real_t impulseMag = amplitude * m_kSwing; + + // Clamp the accumulated impulse + real_t temp = m_accSwingLimitImpulse; + m_accSwingLimitImpulse = MAX(m_accSwingLimitImpulse + impulseMag, real_t(0.0)); + impulseMag = m_accSwingLimitImpulse - temp; + + Vector3 impulse = m_swingAxis * impulseMag; + + if (dynamic_A) { + A->apply_torque_impulse(impulse); + } + if (dynamic_B) { + B->apply_torque_impulse(-impulse); + } + } + + // solve twist limit + if (m_solveTwistLimit) { + real_t amplitude = ((angVelB - angVelA).dot(m_twistAxis) * m_relaxationFactor * m_relaxationFactor + m_twistCorrection * (real_t(1.) / p_timestep) * m_biasFactor); + real_t impulseMag = amplitude * m_kTwist; + + // Clamp the accumulated impulse + real_t temp = m_accTwistLimitImpulse; + m_accTwistLimitImpulse = MAX(m_accTwistLimitImpulse + impulseMag, real_t(0.0)); + impulseMag = m_accTwistLimitImpulse - temp; + + Vector3 impulse = m_twistAxis * impulseMag; + + if (dynamic_A) { + A->apply_torque_impulse(impulse); + } + if (dynamic_B) { + B->apply_torque_impulse(-impulse); + } + } + } +} + +void GodotConeTwistJoint3D::set_param(PhysicsServer3D::ConeTwistJointParam p_param, real_t p_value) { + switch (p_param) { + case PhysicsServer3D::CONE_TWIST_JOINT_SWING_SPAN: { + m_swingSpan1 = p_value; + m_swingSpan2 = p_value; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_TWIST_SPAN: { + m_twistSpan = p_value; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_BIAS: { + m_biasFactor = p_value; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_SOFTNESS: { + m_limitSoftness = p_value; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_RELAXATION: { + m_relaxationFactor = p_value; + } break; + case PhysicsServer3D::CONE_TWIST_MAX: + break; // Can't happen, but silences warning + } +} + +real_t GodotConeTwistJoint3D::get_param(PhysicsServer3D::ConeTwistJointParam p_param) const { + switch (p_param) { + case PhysicsServer3D::CONE_TWIST_JOINT_SWING_SPAN: { + return m_swingSpan1; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_TWIST_SPAN: { + return m_twistSpan; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_BIAS: { + return m_biasFactor; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_SOFTNESS: { + return m_limitSoftness; + } break; + case PhysicsServer3D::CONE_TWIST_JOINT_RELAXATION: { + return m_relaxationFactor; + } break; + case PhysicsServer3D::CONE_TWIST_MAX: + break; // Can't happen, but silences warning + } + + return 0; +} diff --git a/modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.h b/modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.h new file mode 100644 index 0000000000..f3b683a8f3 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.h @@ -0,0 +1,142 @@ +/**************************************************************************/ +/* godot_cone_twist_joint_3d.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 GODOT_CONE_TWIST_JOINT_3D_H +#define GODOT_CONE_TWIST_JOINT_3D_H + +/* +Adapted to Godot from the Bullet library. +*/ + +/* +Bullet Continuous Collision Detection and Physics Library +GodotConeTwistJoint3D is Copyright (c) 2007 Starbreeze Studios + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +Written by: Marcus Hennix +*/ + +#include "../godot_joint_3d.h" +#include "godot_jacobian_entry_3d.h" + +// GodotConeTwistJoint3D can be used to simulate ragdoll joints (upper arm, leg etc). +class GodotConeTwistJoint3D : public GodotJoint3D { +#ifdef IN_PARALLELL_SOLVER +public: +#endif + + union { + struct { + GodotBody3D *A; + GodotBody3D *B; + }; + + GodotBody3D *_arr[2] = { nullptr, nullptr }; + }; + + GodotJacobianEntry3D m_jac[3] = {}; //3 orthogonal linear constraints + + real_t m_appliedImpulse = 0.0; + Transform3D m_rbAFrame; + Transform3D m_rbBFrame; + + real_t m_limitSoftness = 0.0; + real_t m_biasFactor = 0.3; + real_t m_relaxationFactor = 1.0; + + real_t m_swingSpan1 = Math_TAU / 8.0; + real_t m_swingSpan2 = 0.0; + real_t m_twistSpan = 0.0; + + Vector3 m_swingAxis; + Vector3 m_twistAxis; + + real_t m_kSwing = 0.0; + real_t m_kTwist = 0.0; + + real_t m_twistLimitSign = 0.0; + real_t m_swingCorrection = 0.0; + real_t m_twistCorrection = 0.0; + + real_t m_accSwingLimitImpulse = 0.0; + real_t m_accTwistLimitImpulse = 0.0; + + bool m_angularOnly = false; + bool m_solveTwistLimit = false; + bool m_solveSwingLimit = false; + +public: + virtual PhysicsServer3D::JointType get_type() const override { return PhysicsServer3D::JOINT_TYPE_CONE_TWIST; } + + virtual bool setup(real_t p_step) override; + virtual void solve(real_t p_step) override; + + GodotConeTwistJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &rbAFrame, const Transform3D &rbBFrame); + + void setAngularOnly(bool angularOnly) { + m_angularOnly = angularOnly; + } + + void setLimit(real_t _swingSpan1, real_t _swingSpan2, real_t _twistSpan, real_t _softness = 0.8f, real_t _biasFactor = 0.3f, real_t _relaxationFactor = 1.0f) { + m_swingSpan1 = _swingSpan1; + m_swingSpan2 = _swingSpan2; + m_twistSpan = _twistSpan; + + m_limitSoftness = _softness; + m_biasFactor = _biasFactor; + m_relaxationFactor = _relaxationFactor; + } + + inline int getSolveTwistLimit() { + return m_solveTwistLimit; + } + + inline int getSolveSwingLimit() { + return m_solveTwistLimit; + } + + inline real_t getTwistLimitSign() { + return m_twistLimitSign; + } + + void set_param(PhysicsServer3D::ConeTwistJointParam p_param, real_t p_value); + real_t get_param(PhysicsServer3D::ConeTwistJointParam p_param) const; +}; + +#endif // GODOT_CONE_TWIST_JOINT_3D_H diff --git a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp new file mode 100644 index 0000000000..226f8a0f7f --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp @@ -0,0 +1,675 @@ +/**************************************************************************/ +/* godot_generic_6dof_joint_3d.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. */ +/**************************************************************************/ + +/* +Adapted to Godot from the Bullet library. +*/ + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +/* +2007-09-09 +GodotGeneric6DOFJoint3D Refactored by Francisco Le?n +email: projectileman@yahoo.com +http://gimpact.sf.net +*/ + +#include "godot_generic_6dof_joint_3d.h" + +#define GENERIC_D6_DISABLE_WARMSTARTING 1 + +//////////////////////////// GodotG6DOFRotationalLimitMotor3D //////////////////////////////////// + +int GodotG6DOFRotationalLimitMotor3D::testLimitValue(real_t test_value) { + if (m_loLimit > m_hiLimit) { + m_currentLimit = 0; //Free from violation + return 0; + } + + if (test_value < m_loLimit) { + m_currentLimit = 1; //low limit violation + m_currentLimitError = test_value - m_loLimit; + return 1; + } else if (test_value > m_hiLimit) { + m_currentLimit = 2; //High limit violation + m_currentLimitError = test_value - m_hiLimit; + return 2; + }; + + m_currentLimit = 0; //Free from violation + return 0; +} + +real_t GodotG6DOFRotationalLimitMotor3D::solveAngularLimits( + real_t timeStep, Vector3 &axis, real_t jacDiagABInv, + GodotBody3D *body0, GodotBody3D *body1, bool p_body0_dynamic, bool p_body1_dynamic) { + if (!needApplyTorques()) { + return 0.0f; + } + + real_t target_velocity = m_targetVelocity; + real_t maxMotorForce = m_maxMotorForce; + + //current error correction + if (m_currentLimit != 0) { + target_velocity = -m_ERP * m_currentLimitError / (timeStep); + maxMotorForce = m_maxLimitForce; + } + + maxMotorForce *= timeStep; + + // current velocity difference + Vector3 vel_diff = body0->get_angular_velocity(); + if (body1) { + vel_diff -= body1->get_angular_velocity(); + } + + real_t rel_vel = axis.dot(vel_diff); + + // correction velocity + real_t motor_relvel = m_limitSoftness * (target_velocity - m_damping * rel_vel); + + if (Math::is_zero_approx(motor_relvel)) { + return 0.0f; //no need for applying force + } + + // correction impulse + real_t unclippedMotorImpulse = (1 + m_bounce) * motor_relvel * jacDiagABInv; + + // clip correction impulse + real_t clippedMotorImpulse; + + ///@todo: should clip against accumulated impulse + if (unclippedMotorImpulse > 0.0f) { + clippedMotorImpulse = unclippedMotorImpulse > maxMotorForce ? maxMotorForce : unclippedMotorImpulse; + } else { + clippedMotorImpulse = unclippedMotorImpulse < -maxMotorForce ? -maxMotorForce : unclippedMotorImpulse; + } + + // sort with accumulated impulses + real_t lo = real_t(-1e30); + real_t hi = real_t(1e30); + + real_t oldaccumImpulse = m_accumulatedImpulse; + real_t sum = oldaccumImpulse + clippedMotorImpulse; + m_accumulatedImpulse = sum > hi ? real_t(0.) : (sum < lo ? real_t(0.) : sum); + + clippedMotorImpulse = m_accumulatedImpulse - oldaccumImpulse; + + Vector3 motorImp = clippedMotorImpulse * axis; + + if (p_body0_dynamic) { + body0->apply_torque_impulse(motorImp); + } + if (body1 && p_body1_dynamic) { + body1->apply_torque_impulse(-motorImp); + } + + return clippedMotorImpulse; +} + +//////////////////////////// GodotG6DOFTranslationalLimitMotor3D //////////////////////////////////// + +real_t GodotG6DOFTranslationalLimitMotor3D::solveLinearAxis( + real_t timeStep, + real_t jacDiagABInv, + GodotBody3D *body1, const Vector3 &pointInA, + GodotBody3D *body2, const Vector3 &pointInB, + bool p_body1_dynamic, bool p_body2_dynamic, + int limit_index, + const Vector3 &axis_normal_on_a, + const Vector3 &anchorPos) { + ///find relative velocity + // Vector3 rel_pos1 = pointInA - body1->get_transform().origin; + // Vector3 rel_pos2 = pointInB - body2->get_transform().origin; + Vector3 rel_pos1 = anchorPos - body1->get_transform().origin; + Vector3 rel_pos2 = anchorPos - body2->get_transform().origin; + + Vector3 vel1 = body1->get_velocity_in_local_point(rel_pos1); + Vector3 vel2 = body2->get_velocity_in_local_point(rel_pos2); + Vector3 vel = vel1 - vel2; + + real_t rel_vel = axis_normal_on_a.dot(vel); + + /// apply displacement correction + + //positional error (zeroth order error) + real_t depth = -(pointInA - pointInB).dot(axis_normal_on_a); + real_t lo = real_t(-1e30); + real_t hi = real_t(1e30); + + real_t minLimit = m_lowerLimit[limit_index]; + real_t maxLimit = m_upperLimit[limit_index]; + + //handle the limits + if (minLimit < maxLimit) { + { + if (depth > maxLimit) { + depth -= maxLimit; + lo = real_t(0.); + + } else { + if (depth < minLimit) { + depth -= minLimit; + hi = real_t(0.); + } else { + return 0.0f; + } + } + } + } + + real_t normalImpulse = m_limitSoftness[limit_index] * (m_restitution[limit_index] * depth / timeStep - m_damping[limit_index] * rel_vel) * jacDiagABInv; + + real_t oldNormalImpulse = m_accumulatedImpulse[limit_index]; + real_t sum = oldNormalImpulse + normalImpulse; + m_accumulatedImpulse[limit_index] = sum > hi ? real_t(0.) : (sum < lo ? real_t(0.) : sum); + normalImpulse = m_accumulatedImpulse[limit_index] - oldNormalImpulse; + + Vector3 impulse_vector = axis_normal_on_a * normalImpulse; + if (p_body1_dynamic) { + body1->apply_impulse(impulse_vector, rel_pos1); + } + if (p_body2_dynamic) { + body2->apply_impulse(-impulse_vector, rel_pos2); + } + return normalImpulse; +} + +//////////////////////////// GodotGeneric6DOFJoint3D //////////////////////////////////// + +GodotGeneric6DOFJoint3D::GodotGeneric6DOFJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameInA, const Transform3D &frameInB, bool useLinearReferenceFrameA) : + GodotJoint3D(_arr, 2), + m_frameInA(frameInA), + m_frameInB(frameInB), + m_useLinearReferenceFrameA(useLinearReferenceFrameA) { + A = rbA; + B = rbB; + A->add_constraint(this, 0); + B->add_constraint(this, 1); +} + +void GodotGeneric6DOFJoint3D::calculateAngleInfo() { + Basis relative_frame = m_calculatedTransformB.basis.inverse() * m_calculatedTransformA.basis; + + m_calculatedAxisAngleDiff = relative_frame.get_euler(EulerOrder::XYZ); + + // in euler angle mode we do not actually constrain the angular velocity + // along the axes axis[0] and axis[2] (although we do use axis[1]) : + // + // to get constrain w2-w1 along ...not + // ------ --------------------- ------ + // d(angle[0])/dt = 0 ax[1] x ax[2] ax[0] + // d(angle[1])/dt = 0 ax[1] + // d(angle[2])/dt = 0 ax[0] x ax[1] ax[2] + // + // constraining w2-w1 along an axis 'a' means that a'*(w2-w1)=0. + // to prove the result for angle[0], write the expression for angle[0] from + // GetInfo1 then take the derivative. to prove this for angle[2] it is + // easier to take the euler rate expression for d(angle[2])/dt with respect + // to the components of w and set that to 0. + + Vector3 axis0 = m_calculatedTransformB.basis.get_column(0); + Vector3 axis2 = m_calculatedTransformA.basis.get_column(2); + + m_calculatedAxis[1] = axis2.cross(axis0); + m_calculatedAxis[0] = m_calculatedAxis[1].cross(axis2); + m_calculatedAxis[2] = axis0.cross(m_calculatedAxis[1]); + + /* + if(m_debugDrawer) + { + char buff[300]; + sprintf(buff,"\n X: %.2f ; Y: %.2f ; Z: %.2f ", + m_calculatedAxisAngleDiff[0], + m_calculatedAxisAngleDiff[1], + m_calculatedAxisAngleDiff[2]); + m_debugDrawer->reportErrorWarning(buff); + } + */ +} + +void GodotGeneric6DOFJoint3D::calculateTransforms() { + m_calculatedTransformA = A->get_transform() * m_frameInA; + m_calculatedTransformB = B->get_transform() * m_frameInB; + + calculateAngleInfo(); +} + +void GodotGeneric6DOFJoint3D::buildLinearJacobian( + GodotJacobianEntry3D &jacLinear, const Vector3 &normalWorld, + const Vector3 &pivotAInW, const Vector3 &pivotBInW) { + memnew_placement( + &jacLinear, + GodotJacobianEntry3D( + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + pivotAInW - A->get_transform().origin - A->get_center_of_mass(), + pivotBInW - B->get_transform().origin - B->get_center_of_mass(), + normalWorld, + A->get_inv_inertia(), + A->get_inv_mass(), + B->get_inv_inertia(), + B->get_inv_mass())); +} + +void GodotGeneric6DOFJoint3D::buildAngularJacobian( + GodotJacobianEntry3D &jacAngular, const Vector3 &jointAxisW) { + memnew_placement( + &jacAngular, + GodotJacobianEntry3D( + jointAxisW, + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + A->get_inv_inertia(), + B->get_inv_inertia())); +} + +bool GodotGeneric6DOFJoint3D::testAngularLimitMotor(int axis_index) { + real_t angle = m_calculatedAxisAngleDiff[axis_index]; + + //test limits + m_angularLimits[axis_index].testLimitValue(angle); + return m_angularLimits[axis_index].needApplyTorques(); +} + +bool GodotGeneric6DOFJoint3D::setup(real_t p_timestep) { + dynamic_A = (A->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + dynamic_B = (B->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + + if (!dynamic_A && !dynamic_B) { + return false; + } + + // Clear accumulated impulses for the next simulation step + m_linearLimits.m_accumulatedImpulse = Vector3(real_t(0.), real_t(0.), real_t(0.)); + int i; + for (i = 0; i < 3; i++) { + m_angularLimits[i].m_accumulatedImpulse = real_t(0.); + } + //calculates transform + calculateTransforms(); + + // const Vector3& pivotAInW = m_calculatedTransformA.origin; + // const Vector3& pivotBInW = m_calculatedTransformB.origin; + calcAnchorPos(); + Vector3 pivotAInW = m_AnchorPos; + Vector3 pivotBInW = m_AnchorPos; + + // not used here + // Vector3 rel_pos1 = pivotAInW - A->get_transform().origin; + // Vector3 rel_pos2 = pivotBInW - B->get_transform().origin; + + Vector3 normalWorld; + //linear part + for (i = 0; i < 3; i++) { + if (m_linearLimits.enable_limit[i] && m_linearLimits.isLimited(i)) { + if (m_useLinearReferenceFrameA) { + normalWorld = m_calculatedTransformA.basis.get_column(i); + } else { + normalWorld = m_calculatedTransformB.basis.get_column(i); + } + + buildLinearJacobian( + m_jacLinear[i], normalWorld, + pivotAInW, pivotBInW); + } + } + + // angular part + for (i = 0; i < 3; i++) { + //calculates error angle + if (m_angularLimits[i].m_enableLimit && testAngularLimitMotor(i)) { + normalWorld = getAxis(i); + // Create angular atom + buildAngularJacobian(m_jacAng[i], normalWorld); + } + } + + return true; +} + +void GodotGeneric6DOFJoint3D::solve(real_t p_timestep) { + m_timeStep = p_timestep; + + //calculateTransforms(); + + int i; + + // linear + + Vector3 pointInA = m_calculatedTransformA.origin; + Vector3 pointInB = m_calculatedTransformB.origin; + + real_t jacDiagABInv; + Vector3 linear_axis; + for (i = 0; i < 3; i++) { + if (m_linearLimits.enable_limit[i] && m_linearLimits.isLimited(i)) { + jacDiagABInv = real_t(1.) / m_jacLinear[i].getDiagonal(); + + if (m_useLinearReferenceFrameA) { + linear_axis = m_calculatedTransformA.basis.get_column(i); + } else { + linear_axis = m_calculatedTransformB.basis.get_column(i); + } + + m_linearLimits.solveLinearAxis( + m_timeStep, + jacDiagABInv, + A, pointInA, + B, pointInB, + dynamic_A, dynamic_B, + i, linear_axis, m_AnchorPos); + } + } + + // angular + Vector3 angular_axis; + real_t angularJacDiagABInv; + for (i = 0; i < 3; i++) { + if (m_angularLimits[i].m_enableLimit && m_angularLimits[i].needApplyTorques()) { + // get axis + angular_axis = getAxis(i); + + angularJacDiagABInv = real_t(1.) / m_jacAng[i].getDiagonal(); + + m_angularLimits[i].solveAngularLimits(m_timeStep, angular_axis, angularJacDiagABInv, A, B, dynamic_A, dynamic_B); + } + } +} + +void GodotGeneric6DOFJoint3D::updateRHS(real_t timeStep) { + (void)timeStep; +} + +Vector3 GodotGeneric6DOFJoint3D::getAxis(int axis_index) const { + return m_calculatedAxis[axis_index]; +} + +real_t GodotGeneric6DOFJoint3D::getAngle(int axis_index) const { + return m_calculatedAxisAngleDiff[axis_index]; +} + +void GodotGeneric6DOFJoint3D::calcAnchorPos() { + real_t imA = A->get_inv_mass(); + real_t imB = B->get_inv_mass(); + real_t weight; + if (imB == real_t(0.0)) { + weight = real_t(1.0); + } else { + weight = imA / (imA + imB); + } + const Vector3 &pA = m_calculatedTransformA.origin; + const Vector3 &pB = m_calculatedTransformB.origin; + m_AnchorPos = pA * weight + pB * (real_t(1.0) - weight); +} + +void GodotGeneric6DOFJoint3D::set_param(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisParam p_param, real_t p_value) { + ERR_FAIL_INDEX(p_axis, 3); + switch (p_param) { + case PhysicsServer3D::G6DOF_JOINT_LINEAR_LOWER_LIMIT: { + m_linearLimits.m_lowerLimit[p_axis] = p_value; + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_UPPER_LIMIT: { + m_linearLimits.m_upperLimit[p_axis] = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS: { + m_linearLimits.m_limitSoftness[p_axis] = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_RESTITUTION: { + m_linearLimits.m_restitution[p_axis] = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_DAMPING: { + m_linearLimits.m_damping[p_axis] = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_LOWER_LIMIT: { + m_angularLimits[p_axis].m_loLimit = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_UPPER_LIMIT: { + m_angularLimits[p_axis].m_hiLimit = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS: { + m_angularLimits[p_axis].m_limitSoftness = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_DAMPING: { + m_angularLimits[p_axis].m_damping = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_RESTITUTION: { + m_angularLimits[p_axis].m_bounce = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_FORCE_LIMIT: { + m_angularLimits[p_axis].m_maxLimitForce = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_ERP: { + m_angularLimits[p_axis].m_ERP = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_MOTOR_TARGET_VELOCITY: { + m_angularLimits[p_axis].m_targetVelocity = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_MOTOR_FORCE_LIMIT: { + m_angularLimits[p_axis].m_maxLimitForce = p_value; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_STIFFNESS: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_DAMPING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_EQUILIBRIUM_POINT: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_STIFFNESS: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_DAMPING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_MAX: + break; // Can't happen, but silences warning + } +} + +real_t GodotGeneric6DOFJoint3D::get_param(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisParam p_param) const { + ERR_FAIL_INDEX_V(p_axis, 3, 0); + switch (p_param) { + case PhysicsServer3D::G6DOF_JOINT_LINEAR_LOWER_LIMIT: { + return m_linearLimits.m_lowerLimit[p_axis]; + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_UPPER_LIMIT: { + return m_linearLimits.m_upperLimit[p_axis]; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS: { + return m_linearLimits.m_limitSoftness[p_axis]; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_RESTITUTION: { + return m_linearLimits.m_restitution[p_axis]; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_DAMPING: { + return m_linearLimits.m_damping[p_axis]; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_LOWER_LIMIT: { + return m_angularLimits[p_axis].m_loLimit; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_UPPER_LIMIT: { + return m_angularLimits[p_axis].m_hiLimit; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS: { + return m_angularLimits[p_axis].m_limitSoftness; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_DAMPING: { + return m_angularLimits[p_axis].m_damping; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_RESTITUTION: { + return m_angularLimits[p_axis].m_bounce; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_FORCE_LIMIT: { + return m_angularLimits[p_axis].m_maxLimitForce; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_ERP: { + return m_angularLimits[p_axis].m_ERP; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_MOTOR_TARGET_VELOCITY: { + return m_angularLimits[p_axis].m_targetVelocity; + + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_MOTOR_FORCE_LIMIT: { + return m_angularLimits[p_axis].m_maxMotorForce; + + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_STIFFNESS: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_DAMPING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_LINEAR_SPRING_EQUILIBRIUM_POINT: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_STIFFNESS: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_DAMPING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_MAX: + break; // Can't happen, but silences warning + } + return 0; +} + +void GodotGeneric6DOFJoint3D::set_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag, bool p_value) { + ERR_FAIL_INDEX(p_axis, 3); + + switch (p_flag) { + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT: { + m_linearLimits.enable_limit[p_axis] = p_value; + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT: { + m_angularLimits[p_axis].m_enableLimit = p_value; + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_MOTOR: { + m_angularLimits[p_axis].m_enableMotor = p_value; + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_SPRING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_SPRING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_MAX: + break; // Can't happen, but silences warning + } +} + +bool GodotGeneric6DOFJoint3D::get_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag) const { + ERR_FAIL_INDEX_V(p_axis, 3, 0); + switch (p_flag) { + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT: { + return m_linearLimits.enable_limit[p_axis]; + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT: { + return m_angularLimits[p_axis].m_enableLimit; + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_MOTOR: { + return m_angularLimits[p_axis].m_enableMotor; + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_SPRING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_SPRING: { + // Not implemented in GodotPhysics3D backend + } break; + case PhysicsServer3D::G6DOF_JOINT_FLAG_MAX: + break; // Can't happen, but silences warning + } + + return false; +} diff --git a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.h b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.h new file mode 100644 index 0000000000..9ee6dd2791 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.h @@ -0,0 +1,322 @@ +/**************************************************************************/ +/* godot_generic_6dof_joint_3d.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 GODOT_GENERIC_6DOF_JOINT_3D_H +#define GODOT_GENERIC_6DOF_JOINT_3D_H + +/* +Adapted to Godot from the Bullet library. +*/ + +#include "../godot_joint_3d.h" +#include "godot_jacobian_entry_3d.h" + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +/* +2007-09-09 +GodotGeneric6DOFJoint3D Refactored by Francisco Le?n +email: projectileman@yahoo.com +http://gimpact.sf.net +*/ + +//! Rotation Limit structure for generic joints +class GodotG6DOFRotationalLimitMotor3D { +public: + //! limit_parameters + //!@{ + real_t m_loLimit = -1e30; //!< joint limit + real_t m_hiLimit = 1e30; //!< joint limit + real_t m_targetVelocity = 0.0; //!< target motor velocity + real_t m_maxMotorForce = 0.1; //!< max force on motor + real_t m_maxLimitForce = 300.0; //!< max force on limit + real_t m_damping = 1.0; //!< Damping. + real_t m_limitSoftness = 0.5; //! Relaxation factor + real_t m_ERP = 0.5; //!< Error tolerance factor when joint is at limit + real_t m_bounce = 0.0; //!< restitution factor + bool m_enableMotor = false; + bool m_enableLimit = false; + + //!@} + + //! temp_variables + //!@{ + real_t m_currentLimitError = 0.0; //!< How much is violated this limit + int m_currentLimit = 0; //!< 0=free, 1=at lo limit, 2=at hi limit + real_t m_accumulatedImpulse = 0.0; + //!@} + + GodotG6DOFRotationalLimitMotor3D() {} + + bool isLimited() { + return (m_loLimit < m_hiLimit); + } + + // Need apply correction. + bool needApplyTorques() { + return (m_enableMotor || m_currentLimit != 0); + } + + // Calculates m_currentLimit and m_currentLimitError. + int testLimitValue(real_t test_value); + + // Apply the correction impulses for two bodies. + real_t solveAngularLimits(real_t timeStep, Vector3 &axis, real_t jacDiagABInv, GodotBody3D *body0, GodotBody3D *body1, bool p_body0_dynamic, bool p_body1_dynamic); +}; + +class GodotG6DOFTranslationalLimitMotor3D { +public: + Vector3 m_lowerLimit = Vector3(0.0, 0.0, 0.0); //!< the constraint lower limits + Vector3 m_upperLimit = Vector3(0.0, 0.0, 0.0); //!< the constraint upper limits + Vector3 m_accumulatedImpulse = Vector3(0.0, 0.0, 0.0); + //! Linear_Limit_parameters + //!@{ + Vector3 m_limitSoftness = Vector3(0.7, 0.7, 0.7); //!< Softness for linear limit + Vector3 m_damping = Vector3(1.0, 1.0, 1.0); //!< Damping for linear limit + Vector3 m_restitution = Vector3(0.5, 0.5, 0.5); //! Bounce parameter for linear limit + //!@} + bool enable_limit[3] = { true, true, true }; + + //! Test limit + /*! + * - free means upper < lower, + * - locked means upper == lower + * - limited means upper > lower + * - limitIndex: first 3 are linear, next 3 are angular + */ + inline bool isLimited(int limitIndex) { + return (m_upperLimit[limitIndex] >= m_lowerLimit[limitIndex]); + } + + real_t solveLinearAxis( + real_t timeStep, + real_t jacDiagABInv, + GodotBody3D *body1, const Vector3 &pointInA, + GodotBody3D *body2, const Vector3 &pointInB, + bool p_body1_dynamic, bool p_body2_dynamic, + int limit_index, + const Vector3 &axis_normal_on_a, + const Vector3 &anchorPos); +}; + +class GodotGeneric6DOFJoint3D : public GodotJoint3D { +protected: + union { + struct { + GodotBody3D *A; + GodotBody3D *B; + }; + + GodotBody3D *_arr[2] = { nullptr, nullptr }; + }; + + //! relative_frames + //!@{ + Transform3D m_frameInA; //!< the constraint space w.r.t body A + Transform3D m_frameInB; //!< the constraint space w.r.t body B + //!@} + + //! Jacobians + //!@{ + GodotJacobianEntry3D m_jacLinear[3]; //!< 3 orthogonal linear constraints + GodotJacobianEntry3D m_jacAng[3]; //!< 3 orthogonal angular constraints + //!@} + + //! Linear_Limit_parameters + //!@{ + GodotG6DOFTranslationalLimitMotor3D m_linearLimits; + //!@} + + //! hinge_parameters + //!@{ + GodotG6DOFRotationalLimitMotor3D m_angularLimits[3]; + //!@} + +protected: + //! temporal variables + //!@{ + real_t m_timeStep = 0.0; + Transform3D m_calculatedTransformA; + Transform3D m_calculatedTransformB; + Vector3 m_calculatedAxisAngleDiff; + Vector3 m_calculatedAxis[3]; + + Vector3 m_AnchorPos; // point between pivots of bodies A and B to solve linear axes + + bool m_useLinearReferenceFrameA = false; + + //!@} + + GodotGeneric6DOFJoint3D(GodotGeneric6DOFJoint3D const &) = delete; + void operator=(GodotGeneric6DOFJoint3D const &) = delete; + + void buildLinearJacobian( + GodotJacobianEntry3D &jacLinear, const Vector3 &normalWorld, + const Vector3 &pivotAInW, const Vector3 &pivotBInW); + + void buildAngularJacobian(GodotJacobianEntry3D &jacAngular, const Vector3 &jointAxisW); + + //! calcs the euler angles between the two bodies. + void calculateAngleInfo(); + +public: + GodotGeneric6DOFJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameInA, const Transform3D &frameInB, bool useLinearReferenceFrameA); + + virtual PhysicsServer3D::JointType get_type() const override { return PhysicsServer3D::JOINT_TYPE_6DOF; } + + virtual bool setup(real_t p_step) override; + virtual void solve(real_t p_step) override; + + // Calcs the global transform for the joint offset for body A an B, and also calcs the angle differences between the bodies. + void calculateTransforms(); + + // Gets the global transform of the offset for body A. + const Transform3D &getCalculatedTransformA() const { + return m_calculatedTransformA; + } + + // Gets the global transform of the offset for body B. + const Transform3D &getCalculatedTransformB() const { + return m_calculatedTransformB; + } + + const Transform3D &getFrameOffsetA() const { + return m_frameInA; + } + + const Transform3D &getFrameOffsetB() const { + return m_frameInB; + } + + Transform3D &getFrameOffsetA() { + return m_frameInA; + } + + Transform3D &getFrameOffsetB() { + return m_frameInB; + } + + // Performs Jacobian calculation, and also calculates angle differences and axis. + void updateRHS(real_t timeStep); + + // Get the rotation axis in global coordinates. + Vector3 getAxis(int axis_index) const; + + // Get the relative Euler angle. + real_t getAngle(int axis_index) const; + + // Calculates angular correction and returns true if limit needs to be corrected. + bool testAngularLimitMotor(int axis_index); + + void setLinearLowerLimit(const Vector3 &linearLower) { + m_linearLimits.m_lowerLimit = linearLower; + } + + void setLinearUpperLimit(const Vector3 &linearUpper) { + m_linearLimits.m_upperLimit = linearUpper; + } + + void setAngularLowerLimit(const Vector3 &angularLower) { + m_angularLimits[0].m_loLimit = angularLower.x; + m_angularLimits[1].m_loLimit = angularLower.y; + m_angularLimits[2].m_loLimit = angularLower.z; + } + + void setAngularUpperLimit(const Vector3 &angularUpper) { + m_angularLimits[0].m_hiLimit = angularUpper.x; + m_angularLimits[1].m_hiLimit = angularUpper.y; + m_angularLimits[2].m_hiLimit = angularUpper.z; + } + + // Retrieves the angular limit information. + GodotG6DOFRotationalLimitMotor3D *getRotationalLimitMotor(int index) { + return &m_angularLimits[index]; + } + + // Retrieves the limit information. + GodotG6DOFTranslationalLimitMotor3D *getTranslationalLimitMotor() { + return &m_linearLimits; + } + + // First 3 are linear, next 3 are angular. + void setLimit(int axis, real_t lo, real_t hi) { + if (axis < 3) { + m_linearLimits.m_lowerLimit[axis] = lo; + m_linearLimits.m_upperLimit[axis] = hi; + } else { + m_angularLimits[axis - 3].m_loLimit = lo; + m_angularLimits[axis - 3].m_hiLimit = hi; + } + } + + //! Test limit + /*! + * - free means upper < lower, + * - locked means upper == lower + * - limited means upper > lower + * - limitIndex: first 3 are linear, next 3 are angular + */ + bool isLimited(int limitIndex) { + if (limitIndex < 3) { + return m_linearLimits.isLimited(limitIndex); + } + return m_angularLimits[limitIndex - 3].isLimited(); + } + + const GodotBody3D *getRigidBodyA() const { + return A; + } + const GodotBody3D *getRigidBodyB() const { + return B; + } + + virtual void calcAnchorPos(); // overridable + + void set_param(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisParam p_param, real_t p_value); + real_t get_param(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisParam p_param) const; + + void set_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag, bool p_value); + bool get_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag) const; +}; + +#endif // GODOT_GENERIC_6DOF_JOINT_3D_H diff --git a/modules/godot_physics_3d/joints/godot_hinge_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_hinge_joint_3d.cpp new file mode 100644 index 0000000000..3d423f70e2 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_hinge_joint_3d.cpp @@ -0,0 +1,441 @@ +/**************************************************************************/ +/* godot_hinge_joint_3d.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. */ +/**************************************************************************/ + +/* +Adapted to Godot from the Bullet library. +*/ + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "godot_hinge_joint_3d.h" + +GodotHingeJoint3D::GodotHingeJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameA, const Transform3D &frameB) : + GodotJoint3D(_arr, 2) { + A = rbA; + B = rbB; + + m_rbAFrame = frameA; + m_rbBFrame = frameB; + // flip axis + m_rbBFrame.basis[0][2] *= real_t(-1.); + m_rbBFrame.basis[1][2] *= real_t(-1.); + m_rbBFrame.basis[2][2] *= real_t(-1.); + + A->add_constraint(this, 0); + B->add_constraint(this, 1); +} + +GodotHingeJoint3D::GodotHingeJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Vector3 &pivotInA, const Vector3 &pivotInB, + const Vector3 &axisInA, const Vector3 &axisInB) : + GodotJoint3D(_arr, 2) { + A = rbA; + B = rbB; + + m_rbAFrame.origin = pivotInA; + + // since no frame is given, assume this to be zero angle and just pick rb transform axis + Vector3 rbAxisA1 = rbA->get_transform().basis.get_column(0); + + Vector3 rbAxisA2; + real_t projection = axisInA.dot(rbAxisA1); + if (projection >= 1.0f - CMP_EPSILON) { + rbAxisA1 = -rbA->get_transform().basis.get_column(2); + rbAxisA2 = rbA->get_transform().basis.get_column(1); + } else if (projection <= -1.0f + CMP_EPSILON) { + rbAxisA1 = rbA->get_transform().basis.get_column(2); + rbAxisA2 = rbA->get_transform().basis.get_column(1); + } else { + rbAxisA2 = axisInA.cross(rbAxisA1); + rbAxisA1 = rbAxisA2.cross(axisInA); + } + + m_rbAFrame.basis = Basis(rbAxisA1.x, rbAxisA2.x, axisInA.x, + rbAxisA1.y, rbAxisA2.y, axisInA.y, + rbAxisA1.z, rbAxisA2.z, axisInA.z); + + Quaternion rotationArc = Quaternion(axisInA, axisInB); + Vector3 rbAxisB1 = rotationArc.xform(rbAxisA1); + Vector3 rbAxisB2 = axisInB.cross(rbAxisB1); + + m_rbBFrame.origin = pivotInB; + m_rbBFrame.basis = Basis(rbAxisB1.x, rbAxisB2.x, -axisInB.x, + rbAxisB1.y, rbAxisB2.y, -axisInB.y, + rbAxisB1.z, rbAxisB2.z, -axisInB.z); + + A->add_constraint(this, 0); + B->add_constraint(this, 1); +} + +bool GodotHingeJoint3D::setup(real_t p_step) { + dynamic_A = (A->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + dynamic_B = (B->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + + if (!dynamic_A && !dynamic_B) { + return false; + } + + m_appliedImpulse = real_t(0.); + + if (!m_angularOnly) { + Vector3 pivotAInW = A->get_transform().xform(m_rbAFrame.origin); + Vector3 pivotBInW = B->get_transform().xform(m_rbBFrame.origin); + Vector3 relPos = pivotBInW - pivotAInW; + + Vector3 normal[3]; + if (Math::is_zero_approx(relPos.length_squared())) { + normal[0] = Vector3(real_t(1.0), 0, 0); + } else { + normal[0] = relPos.normalized(); + } + + plane_space(normal[0], normal[1], normal[2]); + + for (int i = 0; i < 3; i++) { + memnew_placement( + &m_jac[i], + GodotJacobianEntry3D( + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + pivotAInW - A->get_transform().origin - A->get_center_of_mass(), + pivotBInW - B->get_transform().origin - B->get_center_of_mass(), + normal[i], + A->get_inv_inertia(), + A->get_inv_mass(), + B->get_inv_inertia(), + B->get_inv_mass())); + } + } + + //calculate two perpendicular jointAxis, orthogonal to hingeAxis + //these two jointAxis require equal angular velocities for both bodies + + //this is unused for now, it's a todo + Vector3 jointAxis0local; + Vector3 jointAxis1local; + + plane_space(m_rbAFrame.basis.get_column(2), jointAxis0local, jointAxis1local); + + Vector3 jointAxis0 = A->get_transform().basis.xform(jointAxis0local); + Vector3 jointAxis1 = A->get_transform().basis.xform(jointAxis1local); + Vector3 hingeAxisWorld = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(2)); + + memnew_placement( + &m_jacAng[0], + GodotJacobianEntry3D( + jointAxis0, + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + A->get_inv_inertia(), + B->get_inv_inertia())); + + memnew_placement( + &m_jacAng[1], + GodotJacobianEntry3D( + jointAxis1, + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + A->get_inv_inertia(), + B->get_inv_inertia())); + + memnew_placement( + &m_jacAng[2], + GodotJacobianEntry3D( + hingeAxisWorld, + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + A->get_inv_inertia(), + B->get_inv_inertia())); + + // Compute limit information + real_t hingeAngle = get_hinge_angle(); + + //set bias, sign, clear accumulator + m_correction = real_t(0.); + m_limitSign = real_t(0.); + m_solveLimit = false; + m_accLimitImpulse = real_t(0.); + + if (m_useLimit && m_lowerLimit <= m_upperLimit) { + if (hingeAngle <= m_lowerLimit) { + m_correction = (m_lowerLimit - hingeAngle); + m_limitSign = 1.0f; + m_solveLimit = true; + } else if (hingeAngle >= m_upperLimit) { + m_correction = m_upperLimit - hingeAngle; + m_limitSign = -1.0f; + m_solveLimit = true; + } + } + + //Compute K = J*W*J' for hinge axis + Vector3 axisA = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(2)); + m_kHinge = 1.0f / (A->compute_angular_impulse_denominator(axisA) + B->compute_angular_impulse_denominator(axisA)); + + return true; +} + +void GodotHingeJoint3D::solve(real_t p_step) { + Vector3 pivotAInW = A->get_transform().xform(m_rbAFrame.origin); + Vector3 pivotBInW = B->get_transform().xform(m_rbBFrame.origin); + + //real_t tau = real_t(0.3); + + //linear part + if (!m_angularOnly) { + Vector3 rel_pos1 = pivotAInW - A->get_transform().origin; + Vector3 rel_pos2 = pivotBInW - B->get_transform().origin; + + Vector3 vel1 = A->get_velocity_in_local_point(rel_pos1); + Vector3 vel2 = B->get_velocity_in_local_point(rel_pos2); + Vector3 vel = vel1 - vel2; + + for (int i = 0; i < 3; i++) { + const Vector3 &normal = m_jac[i].m_linearJointAxis; + real_t jacDiagABInv = real_t(1.) / m_jac[i].getDiagonal(); + + real_t rel_vel; + rel_vel = normal.dot(vel); + //positional error (zeroth order error) + real_t depth = -(pivotAInW - pivotBInW).dot(normal); //this is the error projected on the normal + real_t impulse = depth * tau / p_step * jacDiagABInv - rel_vel * jacDiagABInv; + m_appliedImpulse += impulse; + Vector3 impulse_vector = normal * impulse; + if (dynamic_A) { + A->apply_impulse(impulse_vector, pivotAInW - A->get_transform().origin); + } + if (dynamic_B) { + B->apply_impulse(-impulse_vector, pivotBInW - B->get_transform().origin); + } + } + } + + { + ///solve angular part + + // get axes in world space + Vector3 axisA = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(2)); + Vector3 axisB = B->get_transform().basis.xform(m_rbBFrame.basis.get_column(2)); + + const Vector3 &angVelA = A->get_angular_velocity(); + const Vector3 &angVelB = B->get_angular_velocity(); + + Vector3 angVelAroundHingeAxisA = axisA * axisA.dot(angVelA); + Vector3 angVelAroundHingeAxisB = axisB * axisB.dot(angVelB); + + Vector3 angAorthog = angVelA - angVelAroundHingeAxisA; + Vector3 angBorthog = angVelB - angVelAroundHingeAxisB; + Vector3 velrelOrthog = angAorthog - angBorthog; + { + //solve orthogonal angular velocity correction + real_t relaxation = real_t(1.); + real_t len = velrelOrthog.length(); + if (len > real_t(0.00001)) { + Vector3 normal = velrelOrthog.normalized(); + real_t denom = A->compute_angular_impulse_denominator(normal) + + B->compute_angular_impulse_denominator(normal); + // scale for mass and relaxation + velrelOrthog *= (real_t(1.) / denom) * m_relaxationFactor; + } + + //solve angular positional correction + Vector3 angularError = -axisA.cross(axisB) * (real_t(1.) / p_step); + real_t len2 = angularError.length(); + if (len2 > real_t(0.00001)) { + Vector3 normal2 = angularError.normalized(); + real_t denom2 = A->compute_angular_impulse_denominator(normal2) + + B->compute_angular_impulse_denominator(normal2); + angularError *= (real_t(1.) / denom2) * relaxation; + } + + if (dynamic_A) { + A->apply_torque_impulse(-velrelOrthog + angularError); + } + if (dynamic_B) { + B->apply_torque_impulse(velrelOrthog - angularError); + } + + // solve limit + if (m_solveLimit) { + real_t amplitude = ((angVelB - angVelA).dot(axisA) * m_relaxationFactor + m_correction * (real_t(1.) / p_step) * m_biasFactor) * m_limitSign; + + real_t impulseMag = amplitude * m_kHinge; + + // Clamp the accumulated impulse + real_t temp = m_accLimitImpulse; + m_accLimitImpulse = MAX(m_accLimitImpulse + impulseMag, real_t(0)); + impulseMag = m_accLimitImpulse - temp; + + Vector3 impulse = axisA * impulseMag * m_limitSign; + if (dynamic_A) { + A->apply_torque_impulse(impulse); + } + if (dynamic_B) { + B->apply_torque_impulse(-impulse); + } + } + } + + //apply motor + if (m_enableAngularMotor) { + //todo: add limits too + Vector3 angularLimit(0, 0, 0); + + Vector3 velrel = angVelAroundHingeAxisA - angVelAroundHingeAxisB; + real_t projRelVel = velrel.dot(axisA); + + real_t desiredMotorVel = m_motorTargetVelocity; + real_t motor_relvel = desiredMotorVel - projRelVel; + + real_t unclippedMotorImpulse = m_kHinge * motor_relvel; + //todo: should clip against accumulated impulse + real_t clippedMotorImpulse = unclippedMotorImpulse > m_maxMotorImpulse ? m_maxMotorImpulse : unclippedMotorImpulse; + clippedMotorImpulse = clippedMotorImpulse < -m_maxMotorImpulse ? -m_maxMotorImpulse : clippedMotorImpulse; + Vector3 motorImp = clippedMotorImpulse * axisA; + + if (dynamic_A) { + A->apply_torque_impulse(motorImp + angularLimit); + } + if (dynamic_B) { + B->apply_torque_impulse(-motorImp - angularLimit); + } + } + } +} + +/* +void HingeJointSW::updateRHS(real_t timeStep) +{ + (void)timeStep; +} + +*/ + +real_t GodotHingeJoint3D::get_hinge_angle() { + const Vector3 refAxis0 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(0)); + const Vector3 refAxis1 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(1)); + const Vector3 swingAxis = B->get_transform().basis.xform(m_rbBFrame.basis.get_column(1)); + + return atan2fast(swingAxis.dot(refAxis0), swingAxis.dot(refAxis1)); +} + +void GodotHingeJoint3D::set_param(PhysicsServer3D::HingeJointParam p_param, real_t p_value) { + switch (p_param) { + case PhysicsServer3D::HINGE_JOINT_BIAS: + tau = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_LIMIT_UPPER: + m_upperLimit = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_LIMIT_LOWER: + m_lowerLimit = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_LIMIT_BIAS: + m_biasFactor = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_LIMIT_SOFTNESS: + m_limitSoftness = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_LIMIT_RELAXATION: + m_relaxationFactor = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_MOTOR_TARGET_VELOCITY: + m_motorTargetVelocity = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_MOTOR_MAX_IMPULSE: + m_maxMotorImpulse = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_MAX: + break; // Can't happen, but silences warning + } +} + +real_t GodotHingeJoint3D::get_param(PhysicsServer3D::HingeJointParam p_param) const { + switch (p_param) { + case PhysicsServer3D::HINGE_JOINT_BIAS: + return tau; + case PhysicsServer3D::HINGE_JOINT_LIMIT_UPPER: + return m_upperLimit; + case PhysicsServer3D::HINGE_JOINT_LIMIT_LOWER: + return m_lowerLimit; + case PhysicsServer3D::HINGE_JOINT_LIMIT_BIAS: + return m_biasFactor; + case PhysicsServer3D::HINGE_JOINT_LIMIT_SOFTNESS: + return m_limitSoftness; + case PhysicsServer3D::HINGE_JOINT_LIMIT_RELAXATION: + return m_relaxationFactor; + case PhysicsServer3D::HINGE_JOINT_MOTOR_TARGET_VELOCITY: + return m_motorTargetVelocity; + case PhysicsServer3D::HINGE_JOINT_MOTOR_MAX_IMPULSE: + return m_maxMotorImpulse; + case PhysicsServer3D::HINGE_JOINT_MAX: + break; // Can't happen, but silences warning + } + + return 0; +} + +void GodotHingeJoint3D::set_flag(PhysicsServer3D::HingeJointFlag p_flag, bool p_value) { + switch (p_flag) { + case PhysicsServer3D::HINGE_JOINT_FLAG_USE_LIMIT: + m_useLimit = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_FLAG_ENABLE_MOTOR: + m_enableAngularMotor = p_value; + break; + case PhysicsServer3D::HINGE_JOINT_FLAG_MAX: + break; // Can't happen, but silences warning + } +} + +bool GodotHingeJoint3D::get_flag(PhysicsServer3D::HingeJointFlag p_flag) const { + switch (p_flag) { + case PhysicsServer3D::HINGE_JOINT_FLAG_USE_LIMIT: + return m_useLimit; + case PhysicsServer3D::HINGE_JOINT_FLAG_ENABLE_MOTOR: + return m_enableAngularMotor; + case PhysicsServer3D::HINGE_JOINT_FLAG_MAX: + break; // Can't happen, but silences warning + } + + return false; +} diff --git a/modules/godot_physics_3d/joints/godot_hinge_joint_3d.h b/modules/godot_physics_3d/joints/godot_hinge_joint_3d.h new file mode 100644 index 0000000000..7f83509468 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_hinge_joint_3d.h @@ -0,0 +1,116 @@ +/**************************************************************************/ +/* godot_hinge_joint_3d.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 GODOT_HINGE_JOINT_3D_H +#define GODOT_HINGE_JOINT_3D_H + +/* +Adapted to Godot from the Bullet library. +*/ + +#include "../godot_joint_3d.h" +#include "godot_jacobian_entry_3d.h" + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +class GodotHingeJoint3D : public GodotJoint3D { + union { + struct { + GodotBody3D *A; + GodotBody3D *B; + }; + + GodotBody3D *_arr[2] = {}; + }; + + GodotJacobianEntry3D m_jac[3]; //3 orthogonal linear constraints + GodotJacobianEntry3D m_jacAng[3]; //2 orthogonal angular constraints+ 1 for limit/motor + + Transform3D m_rbAFrame; // constraint axii. Assumes z is hinge axis. + Transform3D m_rbBFrame; + + real_t m_motorTargetVelocity = 0.0; + real_t m_maxMotorImpulse = 0.0; + + real_t m_limitSoftness = 0.9; + real_t m_biasFactor = 0.3; + real_t m_relaxationFactor = 1.0; + + real_t m_lowerLimit = Math_PI; + real_t m_upperLimit = -Math_PI; + + real_t m_kHinge = 0.0; + + real_t m_limitSign = 0.0; + real_t m_correction = 0.0; + + real_t m_accLimitImpulse = 0.0; + + real_t tau = 0.3; + + bool m_useLimit = false; + bool m_angularOnly = false; + bool m_enableAngularMotor = false; + bool m_solveLimit = false; + + real_t m_appliedImpulse = 0.0; + +public: + virtual PhysicsServer3D::JointType get_type() const override { return PhysicsServer3D::JOINT_TYPE_HINGE; } + + virtual bool setup(real_t p_step) override; + virtual void solve(real_t p_step) override; + + real_t get_hinge_angle(); + + void set_param(PhysicsServer3D::HingeJointParam p_param, real_t p_value); + real_t get_param(PhysicsServer3D::HingeJointParam p_param) const; + + void set_flag(PhysicsServer3D::HingeJointFlag p_flag, bool p_value); + bool get_flag(PhysicsServer3D::HingeJointFlag p_flag) const; + + GodotHingeJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameA, const Transform3D &frameB); + GodotHingeJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Vector3 &pivotInA, const Vector3 &pivotInB, const Vector3 &axisInA, const Vector3 &axisInB); +}; + +#endif // GODOT_HINGE_JOINT_3D_H diff --git a/modules/godot_physics_3d/joints/godot_jacobian_entry_3d.h b/modules/godot_physics_3d/joints/godot_jacobian_entry_3d.h new file mode 100644 index 0000000000..d0c3c48ae6 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_jacobian_entry_3d.h @@ -0,0 +1,169 @@ +/**************************************************************************/ +/* godot_jacobian_entry_3d.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 GODOT_JACOBIAN_ENTRY_3D_H +#define GODOT_JACOBIAN_ENTRY_3D_H + +/* +Adapted to Godot from the Bullet library. +*/ + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "core/math/transform_3d.h" + +class GodotJacobianEntry3D { +public: + GodotJacobianEntry3D() {} + //constraint between two different rigidbodies + GodotJacobianEntry3D( + const Basis &world2A, + const Basis &world2B, + const Vector3 &rel_pos1, const Vector3 &rel_pos2, + const Vector3 &jointAxis, + const Vector3 &inertiaInvA, + const real_t massInvA, + const Vector3 &inertiaInvB, + const real_t massInvB) : + m_linearJointAxis(jointAxis) { + m_aJ = world2A.xform(rel_pos1.cross(m_linearJointAxis)); + m_bJ = world2B.xform(rel_pos2.cross(-m_linearJointAxis)); + m_0MinvJt = inertiaInvA * m_aJ; + m_1MinvJt = inertiaInvB * m_bJ; + m_Adiag = massInvA + m_0MinvJt.dot(m_aJ) + massInvB + m_1MinvJt.dot(m_bJ); + + ERR_FAIL_COND(m_Adiag <= real_t(0.0)); + } + + //angular constraint between two different rigidbodies + GodotJacobianEntry3D(const Vector3 &jointAxis, + const Basis &world2A, + const Basis &world2B, + const Vector3 &inertiaInvA, + const Vector3 &inertiaInvB) : + m_linearJointAxis(Vector3(real_t(0.), real_t(0.), real_t(0.))) { + m_aJ = world2A.xform(jointAxis); + m_bJ = world2B.xform(-jointAxis); + m_0MinvJt = inertiaInvA * m_aJ; + m_1MinvJt = inertiaInvB * m_bJ; + m_Adiag = m_0MinvJt.dot(m_aJ) + m_1MinvJt.dot(m_bJ); + + ERR_FAIL_COND(m_Adiag <= real_t(0.0)); + } + + //angular constraint between two different rigidbodies + GodotJacobianEntry3D(const Vector3 &axisInA, + const Vector3 &axisInB, + const Vector3 &inertiaInvA, + const Vector3 &inertiaInvB) : + m_linearJointAxis(Vector3(real_t(0.), real_t(0.), real_t(0.))), + m_aJ(axisInA), + m_bJ(-axisInB) { + m_0MinvJt = inertiaInvA * m_aJ; + m_1MinvJt = inertiaInvB * m_bJ; + m_Adiag = m_0MinvJt.dot(m_aJ) + m_1MinvJt.dot(m_bJ); + + ERR_FAIL_COND(m_Adiag <= real_t(0.0)); + } + + //constraint on one rigidbody + GodotJacobianEntry3D( + const Basis &world2A, + const Vector3 &rel_pos1, const Vector3 &rel_pos2, + const Vector3 &jointAxis, + const Vector3 &inertiaInvA, + const real_t massInvA) : + m_linearJointAxis(jointAxis) { + m_aJ = world2A.xform(rel_pos1.cross(jointAxis)); + m_bJ = world2A.xform(rel_pos2.cross(-jointAxis)); + m_0MinvJt = inertiaInvA * m_aJ; + m_1MinvJt = Vector3(real_t(0.), real_t(0.), real_t(0.)); + m_Adiag = massInvA + m_0MinvJt.dot(m_aJ); + + ERR_FAIL_COND(m_Adiag <= real_t(0.0)); + } + + real_t getDiagonal() const { return m_Adiag; } + + // for two constraints on the same rigidbody (for example vehicle friction) + real_t getNonDiagonal(const GodotJacobianEntry3D &jacB, const real_t massInvA) const { + const GodotJacobianEntry3D &jacA = *this; + real_t lin = massInvA * jacA.m_linearJointAxis.dot(jacB.m_linearJointAxis); + real_t ang = jacA.m_0MinvJt.dot(jacB.m_aJ); + return lin + ang; + } + + // for two constraints on sharing two same rigidbodies (for example two contact points between two rigidbodies) + real_t getNonDiagonal(const GodotJacobianEntry3D &jacB, const real_t massInvA, const real_t massInvB) const { + const GodotJacobianEntry3D &jacA = *this; + Vector3 lin = jacA.m_linearJointAxis * jacB.m_linearJointAxis; + Vector3 ang0 = jacA.m_0MinvJt * jacB.m_aJ; + Vector3 ang1 = jacA.m_1MinvJt * jacB.m_bJ; + Vector3 lin0 = massInvA * lin; + Vector3 lin1 = massInvB * lin; + Vector3 sum = ang0 + ang1 + lin0 + lin1; + return sum[0] + sum[1] + sum[2]; + } + + real_t getRelativeVelocity(const Vector3 &linvelA, const Vector3 &angvelA, const Vector3 &linvelB, const Vector3 &angvelB) { + Vector3 linrel = linvelA - linvelB; + Vector3 angvela = angvelA * m_aJ; + Vector3 angvelb = angvelB * m_bJ; + linrel *= m_linearJointAxis; + angvela += angvelb; + angvela += linrel; + real_t rel_vel2 = angvela[0] + angvela[1] + angvela[2]; + return rel_vel2 + CMP_EPSILON; + } + //private: + + Vector3 m_linearJointAxis; + Vector3 m_aJ; + Vector3 m_bJ; + Vector3 m_0MinvJt; + Vector3 m_1MinvJt; + //Optimization: can be stored in the w/last component of one of the vectors + real_t m_Adiag = 1.0; +}; + +#endif // GODOT_JACOBIAN_ENTRY_3D_H diff --git a/modules/godot_physics_3d/joints/godot_pin_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_pin_joint_3d.cpp new file mode 100644 index 0000000000..05ae0839e4 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_pin_joint_3d.cpp @@ -0,0 +1,181 @@ +/**************************************************************************/ +/* godot_pin_joint_3d.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. */ +/**************************************************************************/ + +/* +Adapted to Godot from the Bullet library. +*/ + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "godot_pin_joint_3d.h" + +bool GodotPinJoint3D::setup(real_t p_step) { + dynamic_A = (A->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + dynamic_B = (B->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + + if (!dynamic_A && !dynamic_B) { + return false; + } + + m_appliedImpulse = real_t(0.); + + Vector3 normal(0, 0, 0); + + for (int i = 0; i < 3; i++) { + normal[i] = 1; + memnew_placement( + &m_jac[i], + GodotJacobianEntry3D( + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + A->get_transform().xform(m_pivotInA) - A->get_transform().origin - A->get_center_of_mass(), + B->get_transform().xform(m_pivotInB) - B->get_transform().origin - B->get_center_of_mass(), + normal, + A->get_inv_inertia(), + A->get_inv_mass(), + B->get_inv_inertia(), + B->get_inv_mass())); + normal[i] = 0; + } + + return true; +} + +void GodotPinJoint3D::solve(real_t p_step) { + Vector3 pivotAInW = A->get_transform().xform(m_pivotInA); + Vector3 pivotBInW = B->get_transform().xform(m_pivotInB); + + Vector3 normal(0, 0, 0); + + //Vector3 angvelA = A->get_transform().origin.getBasis().transpose() * A->getAngularVelocity(); + //Vector3 angvelB = B->get_transform().origin.getBasis().transpose() * B->getAngularVelocity(); + + for (int i = 0; i < 3; i++) { + normal[i] = 1; + real_t jacDiagABInv = real_t(1.) / m_jac[i].getDiagonal(); + + Vector3 rel_pos1 = pivotAInW - A->get_transform().origin; + Vector3 rel_pos2 = pivotBInW - B->get_transform().origin; + //this jacobian entry could be re-used for all iterations + + Vector3 vel1 = A->get_velocity_in_local_point(rel_pos1); + Vector3 vel2 = B->get_velocity_in_local_point(rel_pos2); + Vector3 vel = vel1 - vel2; + + real_t rel_vel; + rel_vel = normal.dot(vel); + + /* + //velocity error (first order error) + real_t rel_vel = m_jac[i].getRelativeVelocity(A->getLinearVelocity(),angvelA, + B->getLinearVelocity(),angvelB); + */ + + //positional error (zeroth order error) + real_t depth = -(pivotAInW - pivotBInW).dot(normal); //this is the error projected on the normal + + real_t impulse = depth * m_tau / p_step * jacDiagABInv - m_damping * rel_vel * jacDiagABInv; + + real_t impulseClamp = m_impulseClamp; + if (impulseClamp > 0) { + if (impulse < -impulseClamp) { + impulse = -impulseClamp; + } + if (impulse > impulseClamp) { + impulse = impulseClamp; + } + } + + m_appliedImpulse += impulse; + Vector3 impulse_vector = normal * impulse; + if (dynamic_A) { + A->apply_impulse(impulse_vector, pivotAInW - A->get_transform().origin); + } + if (dynamic_B) { + B->apply_impulse(-impulse_vector, pivotBInW - B->get_transform().origin); + } + + normal[i] = 0; + } +} + +void GodotPinJoint3D::set_param(PhysicsServer3D::PinJointParam p_param, real_t p_value) { + switch (p_param) { + case PhysicsServer3D::PIN_JOINT_BIAS: + m_tau = p_value; + break; + case PhysicsServer3D::PIN_JOINT_DAMPING: + m_damping = p_value; + break; + case PhysicsServer3D::PIN_JOINT_IMPULSE_CLAMP: + m_impulseClamp = p_value; + break; + } +} + +real_t GodotPinJoint3D::get_param(PhysicsServer3D::PinJointParam p_param) const { + switch (p_param) { + case PhysicsServer3D::PIN_JOINT_BIAS: + return m_tau; + case PhysicsServer3D::PIN_JOINT_DAMPING: + return m_damping; + case PhysicsServer3D::PIN_JOINT_IMPULSE_CLAMP: + return m_impulseClamp; + } + + return 0; +} + +GodotPinJoint3D::GodotPinJoint3D(GodotBody3D *p_body_a, const Vector3 &p_pos_a, GodotBody3D *p_body_b, const Vector3 &p_pos_b) : + GodotJoint3D(_arr, 2) { + A = p_body_a; + B = p_body_b; + m_pivotInA = p_pos_a; + m_pivotInB = p_pos_b; + + A->add_constraint(this, 0); + B->add_constraint(this, 1); +} + +GodotPinJoint3D::~GodotPinJoint3D() { +} diff --git a/modules/godot_physics_3d/joints/godot_pin_joint_3d.h b/modules/godot_physics_3d/joints/godot_pin_joint_3d.h new file mode 100644 index 0000000000..62d3068e09 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_pin_joint_3d.h @@ -0,0 +1,95 @@ +/**************************************************************************/ +/* godot_pin_joint_3d.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 GODOT_PIN_JOINT_3D_H +#define GODOT_PIN_JOINT_3D_H + +/* +Adapted to Godot from the Bullet library. +*/ + +#include "../godot_joint_3d.h" +#include "godot_jacobian_entry_3d.h" + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +class GodotPinJoint3D : public GodotJoint3D { + union { + struct { + GodotBody3D *A; + GodotBody3D *B; + }; + + GodotBody3D *_arr[2] = {}; + }; + + real_t m_tau = 0.3; //bias + real_t m_damping = 1.0; + real_t m_impulseClamp = 0.0; + real_t m_appliedImpulse = 0.0; + + GodotJacobianEntry3D m_jac[3] = {}; //3 orthogonal linear constraints + + Vector3 m_pivotInA; + Vector3 m_pivotInB; + +public: + virtual PhysicsServer3D::JointType get_type() const override { return PhysicsServer3D::JOINT_TYPE_PIN; } + + virtual bool setup(real_t p_step) override; + virtual void solve(real_t p_step) override; + + void set_param(PhysicsServer3D::PinJointParam p_param, real_t p_value); + real_t get_param(PhysicsServer3D::PinJointParam p_param) const; + + void set_pos_a(const Vector3 &p_pos) { m_pivotInA = p_pos; } + void set_pos_b(const Vector3 &p_pos) { m_pivotInB = p_pos; } + + Vector3 get_position_a() { return m_pivotInA; } + Vector3 get_position_b() { return m_pivotInB; } + + GodotPinJoint3D(GodotBody3D *p_body_a, const Vector3 &p_pos_a, GodotBody3D *p_body_b, const Vector3 &p_pos_b); + ~GodotPinJoint3D(); +}; + +#endif // GODOT_PIN_JOINT_3D_H diff --git a/modules/godot_physics_3d/joints/godot_slider_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_slider_joint_3d.cpp new file mode 100644 index 0000000000..b9dca94b37 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_slider_joint_3d.cpp @@ -0,0 +1,478 @@ +/**************************************************************************/ +/* godot_slider_joint_3d.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. */ +/**************************************************************************/ + +/* +Adapted to Godot from the Bullet library. +*/ + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +/* +Added by Roman Ponomarev (rponom@gmail.com) +April 04, 2008 + +*/ + +#include "godot_slider_joint_3d.h" + +//----------------------------------------------------------------------------- + +GodotSliderJoint3D::GodotSliderJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameInA, const Transform3D &frameInB) : + GodotJoint3D(_arr, 2), + m_frameInA(frameInA), + m_frameInB(frameInB) { + A = rbA; + B = rbB; + + A->add_constraint(this, 0); + B->add_constraint(this, 1); +} + +//----------------------------------------------------------------------------- + +bool GodotSliderJoint3D::setup(real_t p_step) { + dynamic_A = (A->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + dynamic_B = (B->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); + + if (!dynamic_A && !dynamic_B) { + return false; + } + + //calculate transforms + m_calculatedTransformA = A->get_transform() * m_frameInA; + m_calculatedTransformB = B->get_transform() * m_frameInB; + m_realPivotAInW = m_calculatedTransformA.origin; + m_realPivotBInW = m_calculatedTransformB.origin; + m_sliderAxis = m_calculatedTransformA.basis.get_column(0); // along X + m_delta = m_realPivotBInW - m_realPivotAInW; + m_projPivotInW = m_realPivotAInW + m_sliderAxis.dot(m_delta) * m_sliderAxis; + m_relPosA = m_projPivotInW - A->get_transform().origin; + m_relPosB = m_realPivotBInW - B->get_transform().origin; + Vector3 normalWorld; + int i; + //linear part + for (i = 0; i < 3; i++) { + normalWorld = m_calculatedTransformA.basis.get_column(i); + memnew_placement( + &m_jacLin[i], + GodotJacobianEntry3D( + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + m_relPosA - A->get_center_of_mass(), + m_relPosB - B->get_center_of_mass(), + normalWorld, + A->get_inv_inertia(), + A->get_inv_mass(), + B->get_inv_inertia(), + B->get_inv_mass())); + m_jacLinDiagABInv[i] = real_t(1.) / m_jacLin[i].getDiagonal(); + m_depth[i] = m_delta.dot(normalWorld); + } + testLinLimits(); + // angular part + for (i = 0; i < 3; i++) { + normalWorld = m_calculatedTransformA.basis.get_column(i); + memnew_placement( + &m_jacAng[i], + GodotJacobianEntry3D( + normalWorld, + A->get_principal_inertia_axes().transposed(), + B->get_principal_inertia_axes().transposed(), + A->get_inv_inertia(), + B->get_inv_inertia())); + } + testAngLimits(); + Vector3 axisA = m_calculatedTransformA.basis.get_column(0); + m_kAngle = real_t(1.0) / (A->compute_angular_impulse_denominator(axisA) + B->compute_angular_impulse_denominator(axisA)); + // clear accumulator for motors + m_accumulatedLinMotorImpulse = real_t(0.0); + m_accumulatedAngMotorImpulse = real_t(0.0); + + return true; +} + +//----------------------------------------------------------------------------- + +void GodotSliderJoint3D::solve(real_t p_step) { + int i; + // linear + Vector3 velA = A->get_velocity_in_local_point(m_relPosA); + Vector3 velB = B->get_velocity_in_local_point(m_relPosB); + Vector3 vel = velA - velB; + for (i = 0; i < 3; i++) { + const Vector3 &normal = m_jacLin[i].m_linearJointAxis; + real_t rel_vel = normal.dot(vel); + // calculate positional error + real_t depth = m_depth[i]; + // get parameters + real_t softness = (i) ? m_softnessOrthoLin : (m_solveLinLim ? m_softnessLimLin : m_softnessDirLin); + real_t restitution = (i) ? m_restitutionOrthoLin : (m_solveLinLim ? m_restitutionLimLin : m_restitutionDirLin); + real_t damping = (i) ? m_dampingOrthoLin : (m_solveLinLim ? m_dampingLimLin : m_dampingDirLin); + // Calculate and apply impulse. + real_t normalImpulse = softness * (restitution * depth / p_step - damping * rel_vel) * m_jacLinDiagABInv[i]; + Vector3 impulse_vector = normal * normalImpulse; + if (dynamic_A) { + A->apply_impulse(impulse_vector, m_relPosA); + } + if (dynamic_B) { + B->apply_impulse(-impulse_vector, m_relPosB); + } + if (m_poweredLinMotor && (!i)) { // apply linear motor + if (m_accumulatedLinMotorImpulse < m_maxLinMotorForce) { + real_t desiredMotorVel = m_targetLinMotorVelocity; + real_t motor_relvel = desiredMotorVel + rel_vel; + normalImpulse = -motor_relvel * m_jacLinDiagABInv[i]; + // clamp accumulated impulse + real_t new_acc = m_accumulatedLinMotorImpulse + Math::abs(normalImpulse); + if (new_acc > m_maxLinMotorForce) { + new_acc = m_maxLinMotorForce; + } + real_t del = new_acc - m_accumulatedLinMotorImpulse; + if (normalImpulse < real_t(0.0)) { + normalImpulse = -del; + } else { + normalImpulse = del; + } + m_accumulatedLinMotorImpulse = new_acc; + // apply clamped impulse + impulse_vector = normal * normalImpulse; + if (dynamic_A) { + A->apply_impulse(impulse_vector, m_relPosA); + } + if (dynamic_B) { + B->apply_impulse(-impulse_vector, m_relPosB); + } + } + } + } + // angular + // get axes in world space + Vector3 axisA = m_calculatedTransformA.basis.get_column(0); + Vector3 axisB = m_calculatedTransformB.basis.get_column(0); + + const Vector3 &angVelA = A->get_angular_velocity(); + const Vector3 &angVelB = B->get_angular_velocity(); + + Vector3 angVelAroundAxisA = axisA * axisA.dot(angVelA); + Vector3 angVelAroundAxisB = axisB * axisB.dot(angVelB); + + Vector3 angAorthog = angVelA - angVelAroundAxisA; + Vector3 angBorthog = angVelB - angVelAroundAxisB; + Vector3 velrelOrthog = angAorthog - angBorthog; + //solve orthogonal angular velocity correction + real_t len = velrelOrthog.length(); + if (len > real_t(0.00001)) { + Vector3 normal = velrelOrthog.normalized(); + real_t denom = A->compute_angular_impulse_denominator(normal) + B->compute_angular_impulse_denominator(normal); + velrelOrthog *= (real_t(1.) / denom) * m_dampingOrthoAng * m_softnessOrthoAng; + } + //solve angular positional correction + Vector3 angularError = axisA.cross(axisB) * (real_t(1.) / p_step); + real_t len2 = angularError.length(); + if (len2 > real_t(0.00001)) { + Vector3 normal2 = angularError.normalized(); + real_t denom2 = A->compute_angular_impulse_denominator(normal2) + B->compute_angular_impulse_denominator(normal2); + angularError *= (real_t(1.) / denom2) * m_restitutionOrthoAng * m_softnessOrthoAng; + } + // apply impulse + if (dynamic_A) { + A->apply_torque_impulse(-velrelOrthog + angularError); + } + if (dynamic_B) { + B->apply_torque_impulse(velrelOrthog - angularError); + } + real_t impulseMag; + //solve angular limits + if (m_solveAngLim) { + impulseMag = (angVelB - angVelA).dot(axisA) * m_dampingLimAng + m_angDepth * m_restitutionLimAng / p_step; + impulseMag *= m_kAngle * m_softnessLimAng; + } else { + impulseMag = (angVelB - angVelA).dot(axisA) * m_dampingDirAng + m_angDepth * m_restitutionDirAng / p_step; + impulseMag *= m_kAngle * m_softnessDirAng; + } + Vector3 impulse = axisA * impulseMag; + if (dynamic_A) { + A->apply_torque_impulse(impulse); + } + if (dynamic_B) { + B->apply_torque_impulse(-impulse); + } + //apply angular motor + if (m_poweredAngMotor) { + if (m_accumulatedAngMotorImpulse < m_maxAngMotorForce) { + Vector3 velrel = angVelAroundAxisA - angVelAroundAxisB; + real_t projRelVel = velrel.dot(axisA); + + real_t desiredMotorVel = m_targetAngMotorVelocity; + real_t motor_relvel = desiredMotorVel - projRelVel; + + real_t angImpulse = m_kAngle * motor_relvel; + // clamp accumulated impulse + real_t new_acc = m_accumulatedAngMotorImpulse + Math::abs(angImpulse); + if (new_acc > m_maxAngMotorForce) { + new_acc = m_maxAngMotorForce; + } + real_t del = new_acc - m_accumulatedAngMotorImpulse; + if (angImpulse < real_t(0.0)) { + angImpulse = -del; + } else { + angImpulse = del; + } + m_accumulatedAngMotorImpulse = new_acc; + // apply clamped impulse + Vector3 motorImp = angImpulse * axisA; + if (dynamic_A) { + A->apply_torque_impulse(motorImp); + } + if (dynamic_B) { + B->apply_torque_impulse(-motorImp); + } + } + } +} + +//----------------------------------------------------------------------------- + +void GodotSliderJoint3D::calculateTransforms() { + m_calculatedTransformA = A->get_transform() * m_frameInA; + m_calculatedTransformB = B->get_transform() * m_frameInB; + m_realPivotAInW = m_calculatedTransformA.origin; + m_realPivotBInW = m_calculatedTransformB.origin; + m_sliderAxis = m_calculatedTransformA.basis.get_column(0); // along X + m_delta = m_realPivotBInW - m_realPivotAInW; + m_projPivotInW = m_realPivotAInW + m_sliderAxis.dot(m_delta) * m_sliderAxis; + Vector3 normalWorld; + int i; + //linear part + for (i = 0; i < 3; i++) { + normalWorld = m_calculatedTransformA.basis.get_column(i); + m_depth[i] = m_delta.dot(normalWorld); + } +} + +//----------------------------------------------------------------------------- + +void GodotSliderJoint3D::testLinLimits() { + m_solveLinLim = false; + m_linPos = m_depth[0]; + if (m_lowerLinLimit <= m_upperLinLimit) { + if (m_depth[0] > m_upperLinLimit) { + m_depth[0] -= m_upperLinLimit; + m_solveLinLim = true; + } else if (m_depth[0] < m_lowerLinLimit) { + m_depth[0] -= m_lowerLinLimit; + m_solveLinLim = true; + } else { + m_depth[0] = real_t(0.); + } + } else { + m_depth[0] = real_t(0.); + } +} + +//----------------------------------------------------------------------------- + +void GodotSliderJoint3D::testAngLimits() { + m_angDepth = real_t(0.); + m_solveAngLim = false; + if (m_lowerAngLimit <= m_upperAngLimit) { + const Vector3 axisA0 = m_calculatedTransformA.basis.get_column(1); + const Vector3 axisA1 = m_calculatedTransformA.basis.get_column(2); + const Vector3 axisB0 = m_calculatedTransformB.basis.get_column(1); + real_t rot = atan2fast(axisB0.dot(axisA1), axisB0.dot(axisA0)); + if (rot < m_lowerAngLimit) { + m_angDepth = rot - m_lowerAngLimit; + m_solveAngLim = true; + } else if (rot > m_upperAngLimit) { + m_angDepth = rot - m_upperAngLimit; + m_solveAngLim = true; + } + } +} + +//----------------------------------------------------------------------------- + +Vector3 GodotSliderJoint3D::getAncorInA() { + Vector3 ancorInA; + ancorInA = m_realPivotAInW + (m_lowerLinLimit + m_upperLinLimit) * real_t(0.5) * m_sliderAxis; + ancorInA = A->get_transform().inverse().xform(ancorInA); + return ancorInA; +} + +//----------------------------------------------------------------------------- + +Vector3 GodotSliderJoint3D::getAncorInB() { + Vector3 ancorInB; + ancorInB = m_frameInB.origin; + return ancorInB; +} + +void GodotSliderJoint3D::set_param(PhysicsServer3D::SliderJointParam p_param, real_t p_value) { + switch (p_param) { + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: + m_upperLinLimit = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: + m_lowerLinLimit = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: + m_softnessLimLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: + m_restitutionLimLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: + m_dampingLimLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: + m_softnessDirLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: + m_restitutionDirLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: + m_dampingDirLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: + m_softnessOrthoLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: + m_restitutionOrthoLin = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: + m_dampingOrthoLin = p_value; + break; + + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: + m_upperAngLimit = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: + m_lowerAngLimit = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: + m_softnessLimAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: + m_restitutionLimAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: + m_dampingLimAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: + m_softnessDirAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: + m_restitutionDirAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: + m_dampingDirAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: + m_softnessOrthoAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: + m_restitutionOrthoAng = p_value; + break; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: + m_dampingOrthoAng = p_value; + break; + + case PhysicsServer3D::SLIDER_JOINT_MAX: + break; // Can't happen, but silences warning + } +} + +real_t GodotSliderJoint3D::get_param(PhysicsServer3D::SliderJointParam p_param) const { + switch (p_param) { + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: + return m_upperLinLimit; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: + return m_lowerLinLimit; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: + return m_softnessLimLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: + return m_restitutionLimLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: + return m_dampingLimLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: + return m_softnessDirLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: + return m_restitutionDirLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: + return m_dampingDirLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: + return m_softnessOrthoLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: + return m_restitutionOrthoLin; + case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: + return m_dampingOrthoLin; + + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: + return m_upperAngLimit; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: + return m_lowerAngLimit; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: + return m_softnessLimAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: + return m_restitutionLimAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: + return m_dampingLimAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: + return m_softnessDirAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: + return m_restitutionDirAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: + return m_dampingDirAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: + return m_softnessOrthoAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: + return m_restitutionOrthoAng; + case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: + return m_dampingOrthoAng; + + case PhysicsServer3D::SLIDER_JOINT_MAX: + break; // Can't happen, but silences warning + } + + return 0; +} diff --git a/modules/godot_physics_3d/joints/godot_slider_joint_3d.h b/modules/godot_physics_3d/joints/godot_slider_joint_3d.h new file mode 100644 index 0000000000..99fabf8638 --- /dev/null +++ b/modules/godot_physics_3d/joints/godot_slider_joint_3d.h @@ -0,0 +1,246 @@ +/**************************************************************************/ +/* godot_slider_joint_3d.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 GODOT_SLIDER_JOINT_3D_H +#define GODOT_SLIDER_JOINT_3D_H + +/* +Adapted to Godot from the Bullet library. +*/ + +#include "../godot_joint_3d.h" +#include "godot_jacobian_entry_3d.h" + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +/* +Added by Roman Ponomarev (rponom@gmail.com) +April 04, 2008 + +*/ + +#define SLIDER_CONSTRAINT_DEF_SOFTNESS (real_t(1.0)) +#define SLIDER_CONSTRAINT_DEF_DAMPING (real_t(1.0)) +#define SLIDER_CONSTRAINT_DEF_RESTITUTION (real_t(0.7)) + +//----------------------------------------------------------------------------- + +class GodotSliderJoint3D : public GodotJoint3D { +protected: + union { + struct { + GodotBody3D *A; + GodotBody3D *B; + }; + + GodotBody3D *_arr[2] = { nullptr, nullptr }; + }; + + Transform3D m_frameInA; + Transform3D m_frameInB; + + // linear limits + real_t m_lowerLinLimit = 1.0; + real_t m_upperLinLimit = -1.0; + // angular limits + real_t m_lowerAngLimit = 0.0; + real_t m_upperAngLimit = 0.0; + // softness, restitution and damping for different cases + // DirLin - moving inside linear limits + // LimLin - hitting linear limit + // DirAng - moving inside angular limits + // LimAng - hitting angular limit + // OrthoLin, OrthoAng - against constraint axis + real_t m_softnessDirLin = SLIDER_CONSTRAINT_DEF_SOFTNESS; + real_t m_restitutionDirLin = SLIDER_CONSTRAINT_DEF_RESTITUTION; + real_t m_dampingDirLin = 0.0; + real_t m_softnessDirAng = SLIDER_CONSTRAINT_DEF_SOFTNESS; + real_t m_restitutionDirAng = SLIDER_CONSTRAINT_DEF_RESTITUTION; + real_t m_dampingDirAng = 0.0; + real_t m_softnessLimLin = SLIDER_CONSTRAINT_DEF_SOFTNESS; + real_t m_restitutionLimLin = SLIDER_CONSTRAINT_DEF_RESTITUTION; + real_t m_dampingLimLin = SLIDER_CONSTRAINT_DEF_DAMPING; + real_t m_softnessLimAng = SLIDER_CONSTRAINT_DEF_SOFTNESS; + real_t m_restitutionLimAng = SLIDER_CONSTRAINT_DEF_RESTITUTION; + real_t m_dampingLimAng = SLIDER_CONSTRAINT_DEF_DAMPING; + real_t m_softnessOrthoLin = SLIDER_CONSTRAINT_DEF_SOFTNESS; + real_t m_restitutionOrthoLin = SLIDER_CONSTRAINT_DEF_RESTITUTION; + real_t m_dampingOrthoLin = SLIDER_CONSTRAINT_DEF_DAMPING; + real_t m_softnessOrthoAng = SLIDER_CONSTRAINT_DEF_SOFTNESS; + real_t m_restitutionOrthoAng = SLIDER_CONSTRAINT_DEF_RESTITUTION; + real_t m_dampingOrthoAng = SLIDER_CONSTRAINT_DEF_DAMPING; + + // for interlal use + bool m_solveLinLim = false; + bool m_solveAngLim = false; + + GodotJacobianEntry3D m_jacLin[3] = {}; + real_t m_jacLinDiagABInv[3] = {}; + + GodotJacobianEntry3D m_jacAng[3] = {}; + + real_t m_timeStep = 0.0; + Transform3D m_calculatedTransformA; + Transform3D m_calculatedTransformB; + + Vector3 m_sliderAxis; + Vector3 m_realPivotAInW; + Vector3 m_realPivotBInW; + Vector3 m_projPivotInW; + Vector3 m_delta; + Vector3 m_depth; + Vector3 m_relPosA; + Vector3 m_relPosB; + + real_t m_linPos = 0.0; + + real_t m_angDepth = 0.0; + real_t m_kAngle = 0.0; + + bool m_poweredLinMotor = false; + real_t m_targetLinMotorVelocity = 0.0; + real_t m_maxLinMotorForce = 0.0; + real_t m_accumulatedLinMotorImpulse = 0.0; + + bool m_poweredAngMotor = false; + real_t m_targetAngMotorVelocity = 0.0; + real_t m_maxAngMotorForce = 0.0; + real_t m_accumulatedAngMotorImpulse = 0.0; + +public: + // constructors + GodotSliderJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameInA, const Transform3D &frameInB); + //SliderJointSW(); + // overrides + + // access + const GodotBody3D *getRigidBodyA() const { return A; } + const GodotBody3D *getRigidBodyB() const { return B; } + const Transform3D &getCalculatedTransformA() const { return m_calculatedTransformA; } + const Transform3D &getCalculatedTransformB() const { return m_calculatedTransformB; } + const Transform3D &getFrameOffsetA() const { return m_frameInA; } + const Transform3D &getFrameOffsetB() const { return m_frameInB; } + Transform3D &getFrameOffsetA() { return m_frameInA; } + Transform3D &getFrameOffsetB() { return m_frameInB; } + real_t getLowerLinLimit() { return m_lowerLinLimit; } + void setLowerLinLimit(real_t lowerLimit) { m_lowerLinLimit = lowerLimit; } + real_t getUpperLinLimit() { return m_upperLinLimit; } + void setUpperLinLimit(real_t upperLimit) { m_upperLinLimit = upperLimit; } + real_t getLowerAngLimit() { return m_lowerAngLimit; } + void setLowerAngLimit(real_t lowerLimit) { m_lowerAngLimit = lowerLimit; } + real_t getUpperAngLimit() { return m_upperAngLimit; } + void setUpperAngLimit(real_t upperLimit) { m_upperAngLimit = upperLimit; } + + real_t getSoftnessDirLin() { return m_softnessDirLin; } + real_t getRestitutionDirLin() { return m_restitutionDirLin; } + real_t getDampingDirLin() { return m_dampingDirLin; } + real_t getSoftnessDirAng() { return m_softnessDirAng; } + real_t getRestitutionDirAng() { return m_restitutionDirAng; } + real_t getDampingDirAng() { return m_dampingDirAng; } + real_t getSoftnessLimLin() { return m_softnessLimLin; } + real_t getRestitutionLimLin() { return m_restitutionLimLin; } + real_t getDampingLimLin() { return m_dampingLimLin; } + real_t getSoftnessLimAng() { return m_softnessLimAng; } + real_t getRestitutionLimAng() { return m_restitutionLimAng; } + real_t getDampingLimAng() { return m_dampingLimAng; } + real_t getSoftnessOrthoLin() { return m_softnessOrthoLin; } + real_t getRestitutionOrthoLin() { return m_restitutionOrthoLin; } + real_t getDampingOrthoLin() { return m_dampingOrthoLin; } + real_t getSoftnessOrthoAng() { return m_softnessOrthoAng; } + real_t getRestitutionOrthoAng() { return m_restitutionOrthoAng; } + real_t getDampingOrthoAng() { return m_dampingOrthoAng; } + void setSoftnessDirLin(real_t softnessDirLin) { m_softnessDirLin = softnessDirLin; } + void setRestitutionDirLin(real_t restitutionDirLin) { m_restitutionDirLin = restitutionDirLin; } + void setDampingDirLin(real_t dampingDirLin) { m_dampingDirLin = dampingDirLin; } + void setSoftnessDirAng(real_t softnessDirAng) { m_softnessDirAng = softnessDirAng; } + void setRestitutionDirAng(real_t restitutionDirAng) { m_restitutionDirAng = restitutionDirAng; } + void setDampingDirAng(real_t dampingDirAng) { m_dampingDirAng = dampingDirAng; } + void setSoftnessLimLin(real_t softnessLimLin) { m_softnessLimLin = softnessLimLin; } + void setRestitutionLimLin(real_t restitutionLimLin) { m_restitutionLimLin = restitutionLimLin; } + void setDampingLimLin(real_t dampingLimLin) { m_dampingLimLin = dampingLimLin; } + void setSoftnessLimAng(real_t softnessLimAng) { m_softnessLimAng = softnessLimAng; } + void setRestitutionLimAng(real_t restitutionLimAng) { m_restitutionLimAng = restitutionLimAng; } + void setDampingLimAng(real_t dampingLimAng) { m_dampingLimAng = dampingLimAng; } + void setSoftnessOrthoLin(real_t softnessOrthoLin) { m_softnessOrthoLin = softnessOrthoLin; } + void setRestitutionOrthoLin(real_t restitutionOrthoLin) { m_restitutionOrthoLin = restitutionOrthoLin; } + void setDampingOrthoLin(real_t dampingOrthoLin) { m_dampingOrthoLin = dampingOrthoLin; } + void setSoftnessOrthoAng(real_t softnessOrthoAng) { m_softnessOrthoAng = softnessOrthoAng; } + void setRestitutionOrthoAng(real_t restitutionOrthoAng) { m_restitutionOrthoAng = restitutionOrthoAng; } + void setDampingOrthoAng(real_t dampingOrthoAng) { m_dampingOrthoAng = dampingOrthoAng; } + void setPoweredLinMotor(bool onOff) { m_poweredLinMotor = onOff; } + bool getPoweredLinMotor() { return m_poweredLinMotor; } + void setTargetLinMotorVelocity(real_t targetLinMotorVelocity) { m_targetLinMotorVelocity = targetLinMotorVelocity; } + real_t getTargetLinMotorVelocity() { return m_targetLinMotorVelocity; } + void setMaxLinMotorForce(real_t maxLinMotorForce) { m_maxLinMotorForce = maxLinMotorForce; } + real_t getMaxLinMotorForce() { return m_maxLinMotorForce; } + void setPoweredAngMotor(bool onOff) { m_poweredAngMotor = onOff; } + bool getPoweredAngMotor() { return m_poweredAngMotor; } + void setTargetAngMotorVelocity(real_t targetAngMotorVelocity) { m_targetAngMotorVelocity = targetAngMotorVelocity; } + real_t getTargetAngMotorVelocity() { return m_targetAngMotorVelocity; } + void setMaxAngMotorForce(real_t maxAngMotorForce) { m_maxAngMotorForce = maxAngMotorForce; } + real_t getMaxAngMotorForce() { return m_maxAngMotorForce; } + real_t getLinearPos() { return m_linPos; } + + // access for ODE solver + bool getSolveLinLimit() { return m_solveLinLim; } + real_t getLinDepth() { return m_depth[0]; } + bool getSolveAngLimit() { return m_solveAngLim; } + real_t getAngDepth() { return m_angDepth; } + // shared code used by ODE solver + void calculateTransforms(); + void testLinLimits(); + void testAngLimits(); + // access for PE Solver + Vector3 getAncorInA(); + Vector3 getAncorInB(); + + void set_param(PhysicsServer3D::SliderJointParam p_param, real_t p_value); + real_t get_param(PhysicsServer3D::SliderJointParam p_param) const; + + virtual bool setup(real_t p_step) override; + virtual void solve(real_t p_step) override; + + virtual PhysicsServer3D::JointType get_type() const override { return PhysicsServer3D::JOINT_TYPE_SLIDER; } +}; + +#endif // GODOT_SLIDER_JOINT_3D_H diff --git a/modules/godot_physics_3d/register_types.cpp b/modules/godot_physics_3d/register_types.cpp new file mode 100644 index 0000000000..1b1690cf59 --- /dev/null +++ b/modules/godot_physics_3d/register_types.cpp @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* register_types.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 "register_types.h" + +#include "godot_physics_server_3d.h" +#include "servers/physics_server_3d.h" +#include "servers/physics_server_3d_wrap_mt.h" + +static PhysicsServer3D *_createGodotPhysics3DCallback() { +#ifdef THREADS_ENABLED + bool using_threads = GLOBAL_GET("physics/3d/run_on_separate_thread"); +#else + bool using_threads = false; +#endif + + PhysicsServer3D *physics_server_3d = memnew(GodotPhysicsServer3D(using_threads)); + + return memnew(PhysicsServer3DWrapMT(physics_server_3d, using_threads)); +} + +void initialize_godot_physics_3d_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SERVERS) { + return; + } + PhysicsServer3DManager::get_singleton()->register_server("GodotPhysics3D", callable_mp_static(_createGodotPhysics3DCallback)); + PhysicsServer3DManager::get_singleton()->set_default_server("GodotPhysics3D"); +} + +void uninitialize_godot_physics_3d_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SERVERS) { + return; + } +} diff --git a/modules/godot_physics_3d/register_types.h b/modules/godot_physics_3d/register_types.h new file mode 100644 index 0000000000..998fb4a1ee --- /dev/null +++ b/modules/godot_physics_3d/register_types.h @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* register_types.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 GODOT_PHYSICS_3D_REGISTER_TYPES_H +#define GODOT_PHYSICS_3D_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_godot_physics_3d_module(ModuleInitializationLevel p_level); +void uninitialize_godot_physics_3d_module(ModuleInitializationLevel p_level); + +#endif // GODOT_PHYSICS_3D_REGISTER_TYPES_H diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 27c74421db..4c11565c51 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -34,6 +34,7 @@ #include "core/input/input.h" #include "core/os/keyboard.h" +#include "editor/editor_main_screen.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -59,10 +60,18 @@ void GridMapEditor::_menu_option(int p_option) { switch (p_option) { case MENU_OPTION_PREV_LEVEL: { floor->set_value(floor->get_value() - 1); + if (selection.active && input_action == INPUT_SELECT) { + selection.current[edit_axis]--; + _validate_selection(); + } } break; case MENU_OPTION_NEXT_LEVEL: { floor->set_value(floor->get_value() + 1); + if (selection.active && input_action == INPUT_SELECT) { + selection.current[edit_axis]++; + _validate_selection(); + } } break; case MENU_OPTION_X_AXIS: @@ -635,6 +644,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D _do_paste(); input_action = INPUT_NONE; _update_paste_indicator(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (mb->is_shift_pressed() && can_edit) { input_action = INPUT_SELECT; last_selection = selection; @@ -754,19 +764,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D } } } - - if (k->is_shift_pressed() && selection.active && input_action != INPUT_PASTE) { - if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) { - selection.click[edit_axis]--; - _validate_selection(); - return EditorPlugin::AFTER_GUI_INPUT_STOP; - } - if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL))) { - selection.click[edit_axis]++; - _validate_selection(); - return EditorPlugin::AFTER_GUI_INPUT_STOP; - } - } } } @@ -819,13 +816,14 @@ void GridMapEditor::_text_changed(const String &p_text) { update_palette(); } -void GridMapEditor::_sbox_input(const Ref<InputEvent> &p_ie) { - const Ref<InputEventKey> k = p_ie; - - if (k.is_valid() && (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::PAGEUP || k->get_keycode() == Key::PAGEDOWN)) { - // Forward the key input to the ItemList so it can be scrolled - mesh_library_palette->gui_input(k); - search_box->accept_event(); +void GridMapEditor::_sbox_input(const Ref<InputEvent> &p_event) { + // Redirect navigational key events to the item list. + Ref<InputEventKey> key = p_event; + if (key.is_valid()) { + if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) { + mesh_library_palette->gui_input(key); + search_box->accept_event(); + } } } @@ -962,7 +960,7 @@ void GridMapEditor::edit(GridMap *p_gridmap) { _update_selection_transform(); _update_paste_indicator(); - spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_plugin_screen()); + spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_selected_plugin()); if (!node) { set_process(false); @@ -1069,7 +1067,7 @@ void GridMapEditor::_update_theme() { void GridMapEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - mesh_library_palette->connect("item_selected", callable_mp(this, &GridMapEditor::_item_selected_cbk)); + mesh_library_palette->connect(SceneStringName(item_selected), callable_mp(this, &GridMapEditor::_item_selected_cbk)); for (int i = 0; i < 3; i++) { grid[i] = RS::get_singleton()->mesh_create(); grid_instance[i] = RS::get_singleton()->instance_create2(grid[i], get_tree()->get_root()->get_world_3d()->get_scenario()); @@ -1202,7 +1200,7 @@ GridMapEditor::GridMapEditor() { ED_SHORTCUT("grid_map/clear_selection", TTR("Clear Selection"), Key::KEY_DELETE); ED_SHORTCUT("grid_map/fill_selection", TTR("Fill Selection"), KeyModifierMask::CTRL + Key::F); - int mw = EDITOR_DEF("editors/grid_map/palette_min_width", 230); + int mw = EDITOR_GET("editors/grid_map/palette_min_width"); Control *ec = memnew(Control); ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE); add_child(ec); @@ -1223,7 +1221,7 @@ GridMapEditor::GridMapEditor() { floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 16); spatial_editor_hb->add_child(floor); - floor->connect("value_changed", callable_mp(this, &GridMapEditor::_floor_changed)); + floor->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_floor_changed)); floor->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited)); floor->get_line_edit()->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited)); @@ -1275,7 +1273,7 @@ GridMapEditor::GridMapEditor() { settings_pick_distance->set_value(EDITOR_GET("editors/grid_map/pick_distance")); settings_vbc->add_margin_child(TTR("Pick Distance:"), settings_pick_distance); - options->get_popup()->connect("id_pressed", callable_mp(this, &GridMapEditor::_menu_option)); + options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GridMapEditor::_menu_option)); HBoxContainer *hb = memnew(HBoxContainer); add_child(hb); @@ -1286,7 +1284,7 @@ GridMapEditor::GridMapEditor() { search_box->set_placeholder(TTR("Filter Meshes")); search_box->set_clear_button_enabled(true); hb->add_child(search_box); - search_box->connect("text_changed", callable_mp(this, &GridMapEditor::_text_changed)); + search_box->connect(SceneStringName(text_changed), callable_mp(this, &GridMapEditor::_text_changed)); search_box->connect(SceneStringName(gui_input), callable_mp(this, &GridMapEditor::_sbox_input)); mode_thumbnail = memnew(Button); @@ -1309,11 +1307,9 @@ GridMapEditor::GridMapEditor() { size_slider->set_max(4.0f); size_slider->set_step(0.1f); size_slider->set_value(1.0f); - size_slider->connect("value_changed", callable_mp(this, &GridMapEditor::_icon_size_changed)); + size_slider->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_icon_size_changed)); add_child(size_slider); - EDITOR_DEF("editors/grid_map/preview_size", 64); - mesh_library_palette = memnew(ItemList); mesh_library_palette->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); add_child(mesh_library_palette); @@ -1536,9 +1532,6 @@ void GridMapEditorPlugin::make_visible(bool p_visible) { } GridMapEditorPlugin::GridMapEditorPlugin() { - EDITOR_DEF("editors/grid_map/editor_side", 1); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/grid_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right")); - grid_map_editor = memnew(GridMapEditor); switch ((int)EDITOR_GET("editors/grid_map/editor_side")) { case 0: { // Left. diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h index cfa0f0c35c..4294c93c93 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.h +++ b/modules/gridmap/editor/grid_map_editor_plugin.h @@ -199,7 +199,7 @@ class GridMapEditor : public VBoxContainer { void _update_theme(); void _text_changed(const String &p_text); - void _sbox_input(const Ref<InputEvent> &p_ie); + void _sbox_input(const Ref<InputEvent> &p_event); void _mesh_library_palette_input(const Ref<InputEvent> &p_ie); void _icon_size_changed(float p_value); diff --git a/modules/gridmap/icons/GridMap.svg b/modules/gridmap/icons/GridMap.svg index 38e48a2707..eddeadbcac 100644 --- a/modules/gridmap/icons/GridMap.svg +++ b/modules/gridmap/icons/GridMap.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1-6 3v8l6 3 6-3v-2l-2-1-4 2-2-1v-4l2-1v-2l2-1zm4 2-2 1v2l2 1 2-1v-2z" fill="#fc7f7f"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fc7f7f" d="m8 1-6 3v8l6 3 6-3v-2l-2-1-4 2-2-1v-4l2-1v-2l2-1zm4 2-2 1v2l2 1 2-1v-2z"/></svg>
\ No newline at end of file diff --git a/modules/hdr/image_loader_hdr.cpp b/modules/hdr/image_loader_hdr.cpp index c49c62a08b..ba59bb25ee 100644 --- a/modules/hdr/image_loader_hdr.cpp +++ b/modules/hdr/image_loader_hdr.cpp @@ -68,9 +68,11 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField imgdata.resize(height * width * (int)sizeof(uint32_t)); { - uint8_t *w = imgdata.ptrw(); + uint8_t *ptr = imgdata.ptrw(); - uint8_t *ptr = (uint8_t *)w; + Vector<uint8_t> temp_read_data; + temp_read_data.resize(128); + uint8_t *temp_read_ptr = temp_read_data.ptrw(); if (width < 8 || width >= 32768) { // Read flat data @@ -113,8 +115,9 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField } } else { // Dump + f->get_buffer(temp_read_ptr, count); for (int z = 0; z < count; ++z) { - ptr[(j * width + i++) * 4 + k] = f->get_8(); + ptr[(j * width + i++) * 4 + k] = temp_read_ptr[z]; } } } @@ -122,20 +125,27 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField } } + const bool force_linear = p_flags & FLAG_FORCE_LINEAR; + //convert for (int i = 0; i < width * height; i++) { - float exp = pow(2.0f, ptr[3] - 128.0f); + int e = ptr[3] - 128; + + if (force_linear || (e < -15 || e > 15)) { + float exp = pow(2.0f, e); + Color c(ptr[0] * exp / 255.0, ptr[1] * exp / 255.0, ptr[2] * exp / 255.0); - Color c( - ptr[0] * exp / 255.0, - ptr[1] * exp / 255.0, - ptr[2] * exp / 255.0); + if (force_linear) { + c = c.srgb_to_linear(); + } - if (p_flags & FLAG_FORCE_LINEAR) { - c = c.srgb_to_linear(); + *(uint32_t *)ptr = c.to_rgbe9995(); + } else { + // https://github.com/george-steel/rgbe-rs/blob/e7cc33b7f42b4eb3272c166dac75385e48687c92/src/types.rs#L123-L129 + uint32_t e5 = (uint32_t)(e + 15); + *(uint32_t *)ptr = ((e5 << 27) | ((uint32_t)ptr[2] << 19) | ((uint32_t)ptr[1] << 10) | ((uint32_t)ptr[0] << 1)); } - *(uint32_t *)ptr = c.to_rgbe9995(); ptr += 4; } } diff --git a/modules/hdr/image_loader_hdr.h b/modules/hdr/image_loader_hdr.h index 9821db059e..0a8e91fb9e 100644 --- a/modules/hdr/image_loader_hdr.h +++ b/modules/hdr/image_loader_hdr.h @@ -37,6 +37,7 @@ class ImageLoaderHDR : public ImageFormatLoader { public: virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale); virtual void get_recognized_extensions(List<String> *p_extensions) const; + ImageLoaderHDR(); }; diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index 01764d66ed..8656be988d 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -777,7 +777,7 @@ void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_ if (stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED) { int next_clip = stream->clips[p_to_clip_index].auto_advance_next_clip; - if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && next_clip != playback_current && (!transition.use_filler_clip || next_clip != transition.filler_clip)) { + if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && (!transition.use_filler_clip || next_clip != transition.filler_clip)) { auto_advance_to = next_clip; } } @@ -858,10 +858,7 @@ int AudioStreamPlaybackInteractive::mix(AudioFrame *p_buffer, float p_rate_scale } if (!active) { - for (int i = 0; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0.0, 0.0); - } - return p_frames; + return 0; } int todo = p_frames; @@ -908,7 +905,9 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ // time to start! from_frame = state.fade_wait * mix_rate; state.fade_wait = 0; - queue_next = state.auto_advance; + if (state.fade_speed == 0.0) { + queue_next = state.auto_advance; + } playback_current = p_state_idx; state.first_mix = false; } else { @@ -922,7 +921,6 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ state.playback->mix(temp_buffer + from_frame, 1.0, p_frames - from_frame); double frame_fade_inc = state.fade_speed * frame_inc; - for (int i = from_frame; i < p_frames; i++) { if (state.fade_wait) { // This is for fade out of existing stream; @@ -936,6 +934,7 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ state.fade_speed = 0.0; frame_fade_inc = 0.0; state.fade_volume = 1.0; + queue_next = state.auto_advance; } } else if (frame_fade_inc < 0.0) { state.fade_volume += frame_fade_inc; @@ -976,6 +975,8 @@ void AudioStreamPlaybackInteractive::switch_to_clip_by_name(const StringName &p_ return; } + ERR_FAIL_COND_MSG(stream.is_null(), "Attempted to switch while not playing back any stream."); + for (int i = 0; i < stream->get_clip_count(); i++) { if (stream->get_clip_name(i) == p_name) { switch_request = i; diff --git a/modules/interactive_music/audio_stream_playlist.cpp b/modules/interactive_music/audio_stream_playlist.cpp index f47035b30c..eb77a4a8d3 100644 --- a/modules/interactive_music/audio_stream_playlist.cpp +++ b/modules/interactive_music/audio_stream_playlist.cpp @@ -259,10 +259,7 @@ void AudioStreamPlaybackPlaylist::seek(double p_time) { int AudioStreamPlaybackPlaylist::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (!active) { - for (int i = 0; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0.0, 0.0); - } - return p_frames; + return 0; } double time_dec = (1.0 / AudioServer::get_singleton()->get_mix_rate()); diff --git a/modules/interactive_music/audio_stream_synchronized.cpp b/modules/interactive_music/audio_stream_synchronized.cpp index d0d58fac16..e38a57ba75 100644 --- a/modules/interactive_music/audio_stream_synchronized.cpp +++ b/modules/interactive_music/audio_stream_synchronized.cpp @@ -204,11 +204,8 @@ void AudioStreamPlaybackSynchronized::seek(double p_time) { } int AudioStreamPlaybackSynchronized::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { - if (active != true) { - for (int i = 0; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0.0, 0.0); - } - return p_frames; + if (!active) { + return 0; } int todo = p_frames; diff --git a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml index e8f8e7b760..17448724d1 100644 --- a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml +++ b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml @@ -4,7 +4,7 @@ Audio stream that can playback music interactively, combining clips and a transition table. </brief_description> <description> - This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and the transition rules via the [method add_transition]. Additionally, this stream export a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D]. + This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and then the transition rules via the [method add_transition]. Additionally, this stream exports a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D]. The way this is used is by filling a number of clips, then configuring the transition table. From there, clips are selected for playback and the music will smoothly go from the current to the new one while using the corresponding transition rule defined in the transition table. </description> <tutorials> diff --git a/modules/interactive_music/doc_classes/AudioStreamPlaylist.xml b/modules/interactive_music/doc_classes/AudioStreamPlaylist.xml index 1d429a932f..eb7e1806db 100644 --- a/modules/interactive_music/doc_classes/AudioStreamPlaylist.xml +++ b/modules/interactive_music/doc_classes/AudioStreamPlaylist.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AudioStreamPlaylist" inherits="AudioStream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - [AudioStream] that includes sub-streams and plays them back like a playslit. + [AudioStream] that includes sub-streams and plays them back like a playlist. </brief_description> <description> </description> @@ -11,14 +11,14 @@ <method name="get_bpm" qualifiers="const"> <return type="float" /> <description> - Return the bpm of the playlist, which can vary depending on the clip being played. + Returns the BPM of the playlist, which can vary depending on the clip being played. </description> </method> <method name="get_list_stream" qualifiers="const"> <return type="AudioStream" /> <param index="0" name="stream_index" type="int" /> <description> - Get the stream at playback position index. + Returns the stream at playback position index. </description> </method> <method name="set_list_stream"> @@ -26,7 +26,7 @@ <param index="0" name="stream_index" type="int" /> <param index="1" name="audio_stream" type="AudioStream" /> <description> - Set the stream at playback position index. + Sets the stream at playback position index. </description> </method> </methods> @@ -35,10 +35,10 @@ Fade time used when a stream ends, when going to the next one. Streams are expected to have an extra bit of audio after the end to help with fading. </member> <member name="loop" type="bool" setter="set_loop" getter="has_loop" default="true"> - If true, the playlist will loop, otherwise the playlist when end when the last stream is played. + If [code]true[/code], the playlist will loop, otherwise the playlist will end when the last stream is finished. </member> <member name="shuffle" type="bool" setter="set_shuffle" getter="get_shuffle" default="false"> - Shuffle the playlist. Streams are played in random order. + If [code]true[/code], the playlist will shuffle each time playback starts and each time it loops. </member> <member name="stream_count" type="int" setter="set_stream_count" getter="get_stream_count" default="0"> Amount of streams in the playlist. diff --git a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml index ea914715a3..5353dc7376 100644 --- a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml +++ b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml @@ -47,7 +47,7 @@ </members> <constants> <constant name="MAX_STREAMS" value="32"> - Maximum amount of streams that can be synchrohized. + Maximum amount of streams that can be synchronized. </constant> </constants> </class> diff --git a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp index cd3814879a..fcb477995f 100644 --- a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp +++ b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp @@ -34,6 +34,7 @@ #include "editor/editor_node.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/themes/editor_scale.h" #include "scene/gui/check_box.h" #include "scene/gui/option_button.h" #include "scene/gui/spin_box.h" @@ -161,7 +162,7 @@ void AudioStreamInteractiveTransitionEditor::_update_transitions() { return; } int clip_count = audio_stream_interactive->get_clip_count(); - Color font_color = tree->get_theme_color("font_color", "Tree"); + Color font_color = tree->get_theme_color(SceneStringName(font_color), "Tree"); Color font_color_default = font_color; font_color_default.a *= 0.5; Ref<Texture> fade_icons[5] = { @@ -290,7 +291,6 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) { } int min_w = header_font->get_string_size(name + "XX").width; - tree->set_column_expand(cell_index, false); tree->set_column_custom_minimum_width(cell_index, min_w); max_w = MAX(max_w, min_w); @@ -314,11 +314,10 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) { } } - tree->set_column_expand(header_index, false); tree->set_column_custom_minimum_width(header_index, max_w); selection_order.clear(); _update_selection(); - popup_centered_ratio(0.6); + popup_centered_clamped(Size2(900, 450) * EDSCALE); updating = false; _update_transitions(); } @@ -332,6 +331,7 @@ AudioStreamInteractiveTransitionEditor::AudioStreamInteractiveTransitionEditor() tree->set_hide_root(true); tree->add_theme_constant_override("draw_guides", 1); tree->set_select_mode(Tree::SELECT_MULTI); + tree->set_custom_minimum_size(Size2(400, 0) * EDSCALE); split->add_child(tree); tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -351,29 +351,29 @@ AudioStreamInteractiveTransitionEditor::AudioStreamInteractiveTransitionEditor() transition_from->add_item(TTR("Next Bar"), AudioStreamInteractive::TRANSITION_FROM_TIME_NEXT_BAR); transition_from->add_item(TTR("Clip End"), AudioStreamInteractive::TRANSITION_FROM_TIME_END); - transition_from->connect("item_selected", callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); + transition_from->connect(SceneStringName(item_selected), callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); transition_to = memnew(OptionButton); edit_vb->add_margin_child(TTR("Transition To:"), transition_to); transition_to->add_item(TTR("Same Position"), AudioStreamInteractive::TRANSITION_TO_TIME_SAME_POSITION); transition_to->add_item(TTR("Clip Start"), AudioStreamInteractive::TRANSITION_TO_TIME_START); transition_to->add_item(TTR("Prev Position"), AudioStreamInteractive::TRANSITION_TO_TIME_PREVIOUS_POSITION); - transition_to->connect("item_selected", callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); + transition_to->connect(SceneStringName(item_selected), callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); fade_mode = memnew(OptionButton); edit_vb->add_margin_child(TTR("Fade Mode:"), fade_mode); - fade_mode->connect("item_selected", callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); + fade_mode->connect(SceneStringName(item_selected), callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); fade_beats = memnew(SpinBox); edit_vb->add_margin_child(TTR("Fade Beats:"), fade_beats); fade_beats->set_max(16); fade_beats->set_step(0.1); - fade_beats->connect("value_changed", callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); + fade_beats->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); filler_clip = memnew(OptionButton); edit_vb->add_margin_child(TTR("Filler Clip:"), filler_clip); filler_clip->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); - filler_clip->connect("item_selected", callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); + filler_clip->connect(SceneStringName(item_selected), callable_mp(this, &AudioStreamInteractiveTransitionEditor::_edited).unbind(1)); hold_previous = memnew(CheckBox); hold_previous->set_text(TTR("Enabled")); diff --git a/modules/interactive_music/register_types.cpp b/modules/interactive_music/register_types.cpp index 5baea13f81..6175ea6493 100644 --- a/modules/interactive_music/register_types.cpp +++ b/modules/interactive_music/register_types.cpp @@ -42,11 +42,11 @@ void initialize_interactive_music_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { GDREGISTER_CLASS(AudioStreamPlaylist); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackPlaylist); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackPlaylist); GDREGISTER_CLASS(AudioStreamInteractive); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackInteractive); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackInteractive); GDREGISTER_CLASS(AudioStreamSynchronized); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackSynchronized); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackSynchronized); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index ada0cd01fa..53046de740 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -162,7 +162,7 @@ static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, c ERR_FAIL_COND_V_MSG(error != OK, error, "Couldn't decompress image."); } if (image->get_format() != Image::FORMAT_RGB8) { - image = p_img->duplicate(); + image = image->duplicate(); image->convert(Image::FORMAT_RGB8); } diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 835fb3e59d..8ba6f9e2ba 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -44,6 +44,9 @@ #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_context_driver_vulkan.h" #endif +#if defined(METAL_ENABLED) +#include "drivers/metal/rendering_context_driver_metal.h" +#endif //uncomment this if you want to see textures from all the process saved //#define DEBUG_TEXTURES @@ -226,21 +229,21 @@ void LightmapperRD::_sort_triangle_clusters(uint32_t p_cluster_size, uint32_t p_ } } -Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) { +Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, int p_denoiser_range, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) { Vector<Size2i> sizes; for (int m_i = 0; m_i < mesh_instances.size(); m_i++) { MeshInstance &mi = mesh_instances.write[m_i]; Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height()); sizes.push_back(s); - atlas_size = atlas_size.max(s + Size2i(2, 2)); + atlas_size = atlas_size.max(s + Size2i(2, 2).maxi(p_denoiser_range)); } int max = nearest_power_of_2_templated(atlas_size.width); max = MAX(max, nearest_power_of_2_templated(atlas_size.height)); if (max > p_max_texture_size) { - return BAKE_ERROR_LIGHTMAP_TOO_SMALL; + return BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE; } if (p_step_function) { @@ -254,19 +257,27 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ int best_atlas_memory = 0x7FFFFFFF; Vector<Vector3i> best_atlas_offsets; - //determine best texture array atlas size by bruteforce fitting + // Determine best texture array atlas size by bruteforce fitting. while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) { Vector<Vector2i> source_sizes; Vector<int> source_indices; source_sizes.resize(sizes.size()); source_indices.resize(sizes.size()); for (int i = 0; i < source_indices.size(); i++) { - source_sizes.write[i] = sizes[i] + Vector2i(2, 2); // Add padding between lightmaps + source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps. source_indices.write[i] = i; } Vector<Vector3i> atlas_offsets; atlas_offsets.resize(source_sizes.size()); + // Ensure the sizes can all fit into a single atlas layer. + // This should always happen, and this check is only in place to prevent an infinite loop. + for (int i = 0; i < source_sizes.size(); i++) { + if (source_sizes[i] > atlas_size) { + return BAKE_ERROR_ATLAS_TOO_SMALL; + } + } + int slices = 0; while (source_sizes.size() > 0) { @@ -428,6 +439,7 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i SWAP(edge.a, edge.b); SWAP(edge.na, edge.nb); SWAP(uv2.a, uv2.b); + SWAP(uv2.indices.x, uv2.indices.y); SWAP(edge_indices.x, edge_indices.y); } @@ -706,7 +718,7 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x); raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y); - RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 1.0, 0, Rect2(), RDD::BreadcrumbMarker::LIGHTMAPPER_PASS); //draw opaque rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline); rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); @@ -781,6 +793,35 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade return BAKE_OK; } +LightmapperRD::BakeError LightmapperRD::_pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) { + Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(source_light_tex, dest_light_tex); + + RID compute_shader_pack = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("pack_coeffs")); + ERR_FAIL_COND_V(compute_shader_pack.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen + RID compute_shader_pack_pipeline = rd->compute_pipeline_create(compute_shader_pack); + + RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_pack, 1); + + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_pack_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1); + push_constant.region_ofs[0] = 0; + push_constant.region_ofs[1] = 0; + Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1); //restore group size + + for (int i = 0; i < atlas_slices; i++) { + push_constant.atlas_slice = i; + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); + //no barrier, let them run all together + } + rd->compute_list_end(); + rd->free(compute_shader_pack); + + return BAKE_OK; +} + Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) { Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index); Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data); @@ -906,7 +947,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID return BAKE_OK; } -LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function) { +LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata) { RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams)); DenoiseParams denoise_params; denoise_params.spatial_bandwidth = 5.0f; @@ -914,6 +955,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh denoise_params.albedo_bandwidth = 1.0f; denoise_params.normal_bandwidth = 0.1f; denoise_params.filter_strength = 10.0f; + denoise_params.half_search_window = p_denoiser_range; p_rd->buffer_update(denoise_params_buffer, 0, sizeof(DenoiseParams), &denoise_params); Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(p_source_light_tex, p_dest_light_tex); @@ -968,6 +1010,11 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh p_rd->sync(); } } + if (p_step_function) { + int percent = (s + 1) * 100 / p_atlas_slices; + float p = float(s) / p_atlas_slices * 0.1; + p_step_function(0.8 + p, vformat(RTR("Denoising %d%%"), percent), p_bake_userdata, false); + } } p_rd->free(compute_shader_denoise); @@ -976,7 +1023,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh return BAKE_OK; } -LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { +LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser"); String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path"); @@ -1008,7 +1055,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Vector<Ref<Image>> albedo_images; Vector<Ref<Image>> emission_images; - BakeError bake_error = _blit_meshes_into_atlas(p_max_texture_size, albedo_images, emission_images, bounds, atlas_size, atlas_slices, p_step_function, p_bake_userdata); + BakeError bake_error = _blit_meshes_into_atlas(p_max_texture_size, p_denoiser_range, albedo_images, emission_images, bounds, atlas_size, atlas_slices, p_step_function, p_bake_userdata); if (bake_error != BAKE_OK) { return bake_error; } @@ -1028,10 +1075,16 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device(); if (rd == nullptr) { #if defined(RD_ENABLED) -#if defined(VULKAN_ENABLED) - rcd = memnew(RenderingContextDriverVulkan); +#if defined(METAL_ENABLED) + rcd = memnew(RenderingContextDriverMetal); rd = memnew(RenderingDevice); #endif +#if defined(VULKAN_ENABLED) + if (rcd == nullptr) { + rcd = memnew(RenderingContextDriverVulkan); + rd = memnew(RenderingDevice); + } +#endif #endif if (rcd != nullptr && rd != nullptr) { err = rcd->initialize(); @@ -1571,12 +1624,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); img->save_exr("res://2_light_primary_" + itos(i) + ".exr", false); } + + if (p_bake_sh) { + for (int i = 0; i < atlas_slices * 4; i++) { + Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->save_exr("res://2_light_primary_accum_" + itos(i) + ".exr", false); + } + } #endif /* SECONDARY (indirect) LIGHT PASS(ES) */ - if (p_step_function) { - p_step_function(0.6, RTR("Integrate indirect lighting"), p_bake_userdata, true); - } if (p_bounces > 0) { Vector<RD::Uniform> uniforms; @@ -1640,6 +1698,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d rd->submit(); rd->sync(); + if (p_step_function) { + p_step_function(0.6, RTR("Integrate indirect lighting"), p_bake_userdata, true); + } + int count = 0; for (int s = 0; s < atlas_slices; s++) { push_constant.atlas_slice = s; @@ -1692,7 +1754,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d light_probe_buffer = rd->storage_buffer_create(sizeof(float) * 4 * 9 * probe_positions.size()); if (p_step_function) { - p_step_function(0.7, RTR("Baking lightprobes"), p_bake_userdata, true); + p_step_function(0.7, RTR("Baking light probes"), p_bake_userdata, true); } Vector<RD::Uniform> uniforms; @@ -1793,7 +1855,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } else { // JNLM (built-in). SWAP(light_accum_tex, light_accum_tex2); - error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, atlas_size, atlas_slices, p_bake_sh, p_step_function); + error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, p_bake_sh, p_step_function, p_bake_userdata); } if (unlikely(error != BAKE_OK)) { return error; @@ -1908,7 +1970,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d seams_push_constant.slice = uint32_t(i * subslices + k); seams_push_constant.debug = debug; - RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + // Store the current subslice in the breadcrumb. + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 1.0, 0, Rect2(), RDD::BreadcrumbMarker::LIGHTMAPPER_PASS | seams_push_constant.slice); rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1); @@ -1968,6 +2031,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } } + if (p_bake_sh) { + SWAP(light_accum_tex, light_accum_tex2); + BakeError error = _pack_l1(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices); + if (unlikely(error != BAKE_OK)) { + return error; + } + } + #ifdef DEBUG_TEXTURES for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 5414048ddc..f43da39670 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -262,16 +262,18 @@ class LightmapperRD : public Lightmapper { float albedo_bandwidth; float normal_bandwidth; + int half_search_window; float filter_strength; - float pad[3]; + float pad[2]; }; - BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); + BakeError _blit_meshes_into_atlas(int p_max_texture_size, int p_denoiser_range, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, uint32_t p_cluster_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &r_triangle_indices_buffer, RID &r_cluster_indices_buffer, RID &r_cluster_aabbs_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); - BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function); + BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata); + BakeError _pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name); Ref<Image> _read_pfm(const String &p_name); @@ -283,7 +285,7 @@ public: virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override; virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override; virtual void add_probe(const Vector3 &p_position) override; - virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; int get_bake_texture_count() const override; Ref<Image> get_bake_texture(int p_index) const override; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 1d088450e9..2c85fff6f3 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -6,6 +6,7 @@ dilate = "#define MODE_DILATE"; unocclude = "#define MODE_UNOCCLUDE"; light_probes = "#define MODE_LIGHT_PROBES"; denoise = "#define MODE_DENOISE"; +pack_coeffs = "#define MODE_PACK_L1_COEFFS"; #[compute] @@ -63,7 +64,7 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light; layout(set = 1, binding = 5) uniform texture2D environment; #endif -#if defined(MODE_DILATE) || defined(MODE_DENOISE) +#if defined(MODE_DILATE) || defined(MODE_DENOISE) || defined(MODE_PACK_L1_COEFFS) layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light; layout(set = 1, binding = 1) uniform texture2DArray source_light; #endif @@ -76,6 +77,7 @@ layout(set = 1, binding = 3) uniform DenoiseParams { float albedo_bandwidth; float normal_bandwidth; + int half_search_window; float filter_strength; } denoise_params; @@ -358,7 +360,36 @@ float get_omni_attenuation(float distance, float inv_range, float decay) { return nd * pow(max(distance, 0.0001), -decay); } -void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise) { +const int AA_SAMPLES = 16; + +const vec2 halton_map[AA_SAMPLES] = vec2[]( + vec2(0.5, 0.33333333), + vec2(0.25, 0.66666667), + vec2(0.75, 0.11111111), + vec2(0.125, 0.44444444), + vec2(0.625, 0.77777778), + vec2(0.375, 0.22222222), + vec2(0.875, 0.55555556), + vec2(0.0625, 0.88888889), + vec2(0.5625, 0.03703704), + vec2(0.3125, 0.37037037), + vec2(0.8125, 0.7037037), + vec2(0.1875, 0.14814815), + vec2(0.6875, 0.48148148), + vec2(0.4375, 0.81481481), + vec2(0.9375, 0.25925926), + vec2(0.03125, 0.59259259)); + +vec2 get_vogel_disk(float p_i, float p_rotation, float p_sample_count_sqrt) { + const float golden_angle = 2.4; + + float r = sqrt(p_i + 0.5) / p_sample_count_sqrt; + float theta = p_i * golden_angle + p_rotation; + + return vec2(cos(theta), sin(theta)) * r; +} + +void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise, float p_texel_size) { r_light = vec3(0.0f); vec3 light_pos; @@ -406,46 +437,70 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool } float penumbra = 0.0; - if ((light_data.size > 0.0) && p_soft_shadowing) { + if (p_soft_shadowing) { + const bool use_soft_shadows = (light_data.size > 0.0); + const uint ray_count = AA_SAMPLES; + const uint total_ray_count = use_soft_shadows ? params.ray_count : ray_count; + const uint shadowing_rays_check_penumbra_denom = 2; + const uint shadowing_ray_count = max(1, params.ray_count / ray_count); + const float shadowing_ray_count_sqrt = sqrt(float(total_ray_count)); + + // Setup tangent pass to calculate AA samples over the current texel. + vec3 aux = p_normal.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(p_normal, aux)); + vec3 bitan = normalize(cross(p_normal, tangent)); + + // Setup light tangent pass to calculate samples over disk aligned towards the light vec3 light_to_point = -r_light_dir; - vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); - vec3 light_to_point_tan = normalize(cross(light_to_point, aux)); + vec3 light_aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 light_to_point_tan = normalize(cross(light_to_point, light_aux)); vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan)); - const uint shadowing_rays_check_penumbra_denom = 2; - uint shadowing_ray_count = p_soft_shadowing ? params.ray_count : 1; - uint hits = 0; - vec3 light_disk_to_point = light_to_point; - for (uint j = 0; j < shadowing_ray_count; j++) { - // Optimization: - // Once already traced an important proportion of rays, if all are hits or misses, - // assume we're not in the penumbra so we can infer the rest would have the same result - if (p_soft_shadowing) { - if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) { - if (hits == j) { - // Assume totally lit - hits = shadowing_ray_count; - break; - } else if (hits == 0) { - // Assume totally dark - hits = 0; - break; + for (uint i = 0; i < ray_count; i++) { + // Create a random sample within the texel. + vec2 disk_sample = (halton_map[i] - vec2(0.5)) * p_texel_size * light_data.shadow_blur; + // Align the sample to world space. + vec3 disk_aligned = (disk_sample.x * tangent + disk_sample.y * bitan); + vec3 origin = p_position - disk_aligned; + vec3 light_dir = normalize(light_pos - origin); + + if (use_soft_shadows) { + uint soft_shadow_hits = 0; + for (uint j = 0; j < shadowing_ray_count; j++) { + // Optimization: + // Once already traced an important proportion of rays, if all are hits or misses, + // assume we're not in the penumbra so we can infer the rest would have the same result. + if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) { + if (soft_shadow_hits == j) { + // Assume totally lit + soft_shadow_hits = shadowing_ray_count; + break; + } else if (soft_shadow_hits == 0) { + // Assume totally dark + soft_shadow_hits = 0; + break; + } } - } - } - - float r = randomize(r_noise); - float a = randomize(r_noise) * 2.0 * PI; - vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * light_data.shadow_blur; - light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan); - if (trace_ray_any_hit(p_position - light_disk_to_point * bake_params.bias, p_position - light_disk_to_point * dist) == RAY_MISS) { - hits++; + float a = randomize(r_noise) * 2.0 * PI; + float vogel_index = float(total_ray_count - 1 - (i * shadowing_ray_count + j)); // Start from (total_ray_count - 1) so we check the outer points first. + vec2 light_disk_sample = (get_vogel_disk(vogel_index, a, shadowing_ray_count_sqrt)) * soft_shadowing_disk_size * light_data.shadow_blur; + vec3 light_disk_to_point = normalize(light_to_point + light_disk_sample.x * light_to_point_tan + light_disk_sample.y * light_to_point_bitan); + // Offset the ray origin for AA, offset the light position for soft shadows. + if (trace_ray_any_hit(origin - light_disk_to_point * (bake_params.bias + length(disk_sample)), p_position - light_disk_to_point * dist) == RAY_MISS) { + soft_shadow_hits++; + } + } + hits += soft_shadow_hits; + } else { + // Offset the ray origin based on the disk. Also increase the bias for further samples to avoid bleeding. + if (trace_ray_any_hit(origin + light_dir * (bake_params.bias + length(disk_sample)), light_pos) == RAY_MISS) { + hits++; + } } } - - penumbra = float(hits) / float(shadowing_ray_count); + penumbra = float(hits) / float(total_ray_count); } else { if (trace_ray_any_hit(p_position + r_light_dir * bake_params.bias, light_pos) == RAY_MISS) { penumbra = 1.0; @@ -469,7 +524,7 @@ vec3 trace_environment_color(vec3 ray_dir) { return textureLod(sampler2D(environment, linear_sampler), st / vec2(PI * 2.0, PI), 0.0).rgb; } -vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) { +vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, float p_texel_size) { // The lower limit considers the case where the lightmapper might have bounces disabled but light probes are requested. vec3 position = p_position; vec3 ray_dir = p_ray_dir; @@ -501,7 +556,7 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) { for (uint i = 0; i < bake_params.light_count; i++) { vec3 light; vec3 light_dir; - trace_direct_light(position, normal, i, false, light, light_dir, r_noise); + trace_direct_light(position, normal, i, false, light, light_dir, r_noise, p_texel_size); direct_light += light * lights.data[i].indirect_energy; } @@ -565,6 +620,14 @@ void main() { return; //empty texel, no process } vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + vec4 neighbor_position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(1, 0), params.atlas_slice), 0).xyzw; + + if (neighbor_position.w < 0.001) { + // Empty texel, try again. + neighbor_position.xyz = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(-1, 0), params.atlas_slice), 0).xyz; + } + float texel_size_world_space = distance(position, neighbor_position.xyz); + vec3 light_for_texture = vec3(0.0); vec3 light_for_bounces = vec3(0.0); @@ -581,21 +644,26 @@ void main() { for (uint i = 0; i < bake_params.light_count; i++) { vec3 light; vec3 light_dir; - trace_direct_light(position, normal, i, true, light, light_dir, noise); + trace_direct_light(position, normal, i, true, light, light_dir, noise, texel_size_world_space); if (lights.data[i].static_bake) { light_for_texture += light; #ifdef USE_SH_LIGHTMAPS + // These coefficients include the factored out SH evaluation, diffuse convolution, and final application, as well as the BRDF 1/PI and the spherical monte carlo factor. + // LO: 1/(2*sqrtPI) * 1/(2*sqrtPI) * PI * PI * 1/PI = 0.25 + // L1: sqrt(3/(4*pi)) * sqrt(3/(4*pi)) * (PI*2/3) * (2 * PI) * 1/PI = 1.0 + // Note: This only works because we aren't scaling, rotating, or combing harmonics, we are just directing applying them in the shader. + float c[4] = float[]( - 0.282095, //l0 - 0.488603 * light_dir.y, //l1n1 - 0.488603 * light_dir.z, //l1n0 - 0.488603 * light_dir.x //l1p1 + 0.25, //l0 + light_dir.y, //l1n1 + light_dir.z, //l1n0 + light_dir.x //l1p1 ); for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * 8.0; + sh_accum[j].rgb += light * c[j] * bake_params.exposure_normalization; } #endif } @@ -639,21 +707,29 @@ void main() { } vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + int neighbor_offset = atlas_pos.x < bake_params.atlas_size.x - 1 ? 1 : -1; + vec3 neighbor_position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(neighbor_offset, 0), params.atlas_slice), 0).xyz; + float texel_size_world_space = distance(position, neighbor_position); uint noise = random_seed(ivec3(params.ray_from, atlas_pos)); for (uint i = params.ray_from; i < params.ray_to; i++) { vec3 ray_dir = generate_ray_dir_from_normal(normal, noise); - vec3 light = trace_indirect_light(position, ray_dir, noise); + vec3 light = trace_indirect_light(position, ray_dir, noise, texel_size_world_space); #ifdef USE_SH_LIGHTMAPS + // These coefficients include the factored out SH evaluation, diffuse convolution, and final application, as well as the BRDF 1/PI and the spherical monte carlo factor. + // LO: 1/(2*sqrtPI) * 1/(2*sqrtPI) * PI * PI * 1/PI = 0.25 + // L1: sqrt(3/(4*pi)) * sqrt(3/(4*pi)) * (PI*2/3) * (2 * PI) * 1/PI = 1.0 + // Note: This only works because we aren't scaling, rotating, or combing harmonics, we are just directing applying them in the shader. + float c[4] = float[]( - 0.282095, //l0 - 0.488603 * ray_dir.y, //l1n1 - 0.488603 * ray_dir.z, //l1n0 - 0.488603 * ray_dir.x //l1p1 + 0.25, //l0 + ray_dir.y, //l1n1 + ray_dir.z, //l1n0 + ray_dir.x //l1p1 ); for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * 8.0; + sh_accum[j].rgb += light * c[j]; } #else light_accum += light; @@ -736,7 +812,7 @@ void main() { uint noise = random_seed(ivec3(params.ray_from, probe_index, 49502741 /* some prime */)); for (uint i = params.ray_from; i < params.ray_to; i++) { vec3 ray_dir = generate_sphere_uniform_direction(noise); - vec3 light = trace_indirect_light(position, ray_dir, noise); + vec3 light = trace_indirect_light(position, ray_dir, noise, 0.0); float c[9] = float[]( 0.282095, //l0 @@ -849,10 +925,10 @@ void main() { // Half the size of the patch window around each pixel that is weighted to compute the denoised pixel. // A value of 1 represents a 3x3 window, a value of 2 a 5x5 window, etc. - const int HALF_PATCH_WINDOW = 4; + const int HALF_PATCH_WINDOW = 3; // Half the size of the search window around each pixel that is denoised and weighted to compute the denoised pixel. - const int HALF_SEARCH_WINDOW = 10; + const int HALF_SEARCH_WINDOW = denoise_params.half_search_window; // For all of the following sigma values, smaller values will give less weight to pixels that have a bigger distance // in the feature being evaluated. Therefore, smaller values are likely to cause more noise to appear, but will also @@ -962,4 +1038,28 @@ void main() { imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a)); } #endif + +#ifdef MODE_PACK_L1_COEFFS + vec4 base_coeff = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4), 0); + + for (int i = 1; i < 4; i++) { + vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4 + i), 0); + + if (abs(base_coeff.r) > 0.0) { + c.r /= (base_coeff.r * 8); + } + + if (abs(base_coeff.g) > 0.0) { + c.g /= (base_coeff.g * 8); + } + + if (abs(base_coeff.b) > 0.0) { + c.b /= (base_coeff.b * 8); + } + + c.rgb += vec3(0.5); + c.rgb = clamp(c.rgb, vec3(0.0), vec3(1.0)); + imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), c); + } +#endif } diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index e910627b32..0d97b5fc1a 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -49,8 +49,8 @@ #define PEM_END_CRT "-----END CERTIFICATE-----\n" #define PEM_MIN_SIZE 54 -CryptoKey *CryptoKeyMbedTLS::create() { - return memnew(CryptoKeyMbedTLS); +CryptoKey *CryptoKeyMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<CryptoKey *>(ClassDB::creator<CryptoKeyMbedTLS>(p_notify_postinitialize)); } Error CryptoKeyMbedTLS::load(const String &p_path, bool p_public_only) { @@ -153,8 +153,8 @@ int CryptoKeyMbedTLS::_parse_key(const uint8_t *p_buf, int p_size) { #endif } -X509Certificate *X509CertificateMbedTLS::create() { - return memnew(X509CertificateMbedTLS); +X509Certificate *X509CertificateMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<X509Certificate *>(ClassDB::creator<X509CertificateMbedTLS>(p_notify_postinitialize)); } Error X509CertificateMbedTLS::load(const String &p_path) { @@ -250,8 +250,8 @@ bool HMACContextMbedTLS::is_md_type_allowed(mbedtls_md_type_t p_md_type) { } } -HMACContext *HMACContextMbedTLS::create() { - return memnew(HMACContextMbedTLS); +HMACContext *HMACContextMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<HMACContext *>(ClassDB::creator<HMACContextMbedTLS>(p_notify_postinitialize)); } Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, const PackedByteArray &p_key) { @@ -309,8 +309,8 @@ HMACContextMbedTLS::~HMACContextMbedTLS() { } } -Crypto *CryptoMbedTLS::create() { - return memnew(CryptoMbedTLS); +Crypto *CryptoMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<Crypto *>(ClassDB::creator<CryptoMbedTLS>(p_notify_postinitialize)); } void CryptoMbedTLS::initialize_crypto() { diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h index 52918cedf0..5e1da550d7 100644 --- a/modules/mbedtls/crypto_mbedtls.h +++ b/modules/mbedtls/crypto_mbedtls.h @@ -49,7 +49,7 @@ private: int _parse_key(const uint8_t *p_buf, int p_size); public: - static CryptoKey *create(); + static CryptoKey *create(bool p_notify_postinitialize = true); static void make_default() { CryptoKey::_create = create; } static void finalize() { CryptoKey::_create = nullptr; } @@ -80,7 +80,7 @@ private: int locks; public: - static X509Certificate *create(); + static X509Certificate *create(bool p_notify_postinitialize = true); static void make_default() { X509Certificate::_create = create; } static void finalize() { X509Certificate::_create = nullptr; } @@ -112,7 +112,7 @@ private: void *ctx = nullptr; public: - static HMACContext *create(); + static HMACContext *create(bool p_notify_postinitialize = true); static void make_default() { HMACContext::_create = create; } static void finalize() { HMACContext::_create = nullptr; } @@ -133,7 +133,7 @@ private: static X509CertificateMbedTLS *default_certs; public: - static Crypto *create(); + static Crypto *create(bool p_notify_postinitialize = true); static void initialize_crypto(); static void finalize_crypto(); static X509CertificateMbedTLS *get_default_certificates(); diff --git a/modules/mbedtls/dtls_server_mbedtls.cpp b/modules/mbedtls/dtls_server_mbedtls.cpp index e466fe15d6..b64bdcb192 100644 --- a/modules/mbedtls/dtls_server_mbedtls.cpp +++ b/modules/mbedtls/dtls_server_mbedtls.cpp @@ -54,8 +54,8 @@ Ref<PacketPeerDTLS> DTLSServerMbedTLS::take_connection(Ref<PacketPeerUDP> p_udp_ return out; } -DTLSServer *DTLSServerMbedTLS::_create_func() { - return memnew(DTLSServerMbedTLS); +DTLSServer *DTLSServerMbedTLS::_create_func(bool p_notify_postinitialize) { + return static_cast<DTLSServer *>(ClassDB::creator<DTLSServerMbedTLS>(p_notify_postinitialize)); } void DTLSServerMbedTLS::initialize() { diff --git a/modules/mbedtls/dtls_server_mbedtls.h b/modules/mbedtls/dtls_server_mbedtls.h index 59befecf43..18661bf505 100644 --- a/modules/mbedtls/dtls_server_mbedtls.h +++ b/modules/mbedtls/dtls_server_mbedtls.h @@ -37,7 +37,7 @@ class DTLSServerMbedTLS : public DTLSServer { private: - static DTLSServer *_create_func(); + static DTLSServer *_create_func(bool p_notify_postinitialize); Ref<TLSOptions> tls_options; Ref<CookieContextMbedTLS> cookies; diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index c7373481ca..62d27405d8 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -270,8 +270,8 @@ PacketPeerMbedDTLS::Status PacketPeerMbedDTLS::get_status() const { return status; } -PacketPeerDTLS *PacketPeerMbedDTLS::_create_func() { - return memnew(PacketPeerMbedDTLS); +PacketPeerDTLS *PacketPeerMbedDTLS::_create_func(bool p_notify_postinitialize) { + return static_cast<PacketPeerDTLS *>(ClassDB::creator<PacketPeerMbedDTLS>(p_notify_postinitialize)); } void PacketPeerMbedDTLS::initialize_dtls() { diff --git a/modules/mbedtls/packet_peer_mbed_dtls.h b/modules/mbedtls/packet_peer_mbed_dtls.h index 2cff7a3589..881a5fdd0e 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.h +++ b/modules/mbedtls/packet_peer_mbed_dtls.h @@ -50,7 +50,7 @@ private: Ref<PacketPeerUDP> base; - static PacketPeerDTLS *_create_func(); + static PacketPeerDTLS *_create_func(bool p_notify_postinitialize); static int bio_recv(void *ctx, unsigned char *buf, size_t len); static int bio_send(void *ctx, const unsigned char *buf, size_t len); diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index a359b42041..b4200410fb 100644 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -295,8 +295,8 @@ Ref<StreamPeer> StreamPeerMbedTLS::get_stream() const { return base; } -StreamPeerTLS *StreamPeerMbedTLS::_create_func() { - return memnew(StreamPeerMbedTLS); +StreamPeerTLS *StreamPeerMbedTLS::_create_func(bool p_notify_postinitialize) { + return static_cast<StreamPeerTLS *>(ClassDB::creator<StreamPeerMbedTLS>(p_notify_postinitialize)); } void StreamPeerMbedTLS::initialize_tls() { diff --git a/modules/mbedtls/stream_peer_mbedtls.h b/modules/mbedtls/stream_peer_mbedtls.h index a8080f0960..b4f80b614c 100644 --- a/modules/mbedtls/stream_peer_mbedtls.h +++ b/modules/mbedtls/stream_peer_mbedtls.h @@ -42,7 +42,7 @@ private: Ref<StreamPeer> base; - static StreamPeerTLS *_create_func(); + static StreamPeerTLS *_create_func(bool p_notify_postinitialize); static int bio_recv(void *ctx, unsigned char *buf, size_t len); static int bio_send(void *ctx, const unsigned char *buf, size_t len); diff --git a/modules/mbedtls/tls_context_mbedtls.cpp b/modules/mbedtls/tls_context_mbedtls.cpp index aab082f488..f5c196596e 100644 --- a/modules/mbedtls/tls_context_mbedtls.cpp +++ b/modules/mbedtls/tls_context_mbedtls.cpp @@ -152,21 +152,23 @@ Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname, ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER); int authmode = MBEDTLS_SSL_VERIFY_REQUIRED; - if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_NONE) { + bool unsafe = p_options->is_unsafe_client(); + if (unsafe && p_options->get_trusted_ca_chain().is_null()) { authmode = MBEDTLS_SSL_VERIFY_NONE; } Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, authmode); ERR_FAIL_COND_V(err != OK, err); - if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_FULL) { - String cn = p_options->get_common_name(); + if (unsafe) { + // No hostname verification for unsafe clients. + mbedtls_ssl_set_hostname(&tls, nullptr); + } else { + String cn = p_options->get_common_name_override(); if (cn.is_empty()) { cn = p_hostname; } mbedtls_ssl_set_hostname(&tls, cn.utf8().get_data()); - } else { - mbedtls_ssl_set_hostname(&tls, nullptr); } X509CertificateMbedTLS *cas = nullptr; diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index a46a1c93b5..4963cfdf1a 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -57,7 +57,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { mp3dec_frame_info_t frame_info; mp3d_sample_t *buf_frame = nullptr; - int samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels); + int samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels); if (samples_mixed) { p_buffer[p_frames - todo] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]); @@ -70,7 +70,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { if (beat_loop && (int)frames_mixed >= beat_length_frames) { for (int i = 0; i < FADE_SIZE; i++) { - samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels); + samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels); loop_fade[i] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]); if (!samples_mixed) { break; @@ -138,13 +138,32 @@ void AudioStreamPlaybackMP3::seek(double p_time) { } frames_mixed = uint32_t(mp3_stream->sample_rate * p_time); - mp3dec_ex_seek(mp3d, (uint64_t)frames_mixed * mp3_stream->channels); + mp3dec_ex_seek(&mp3d, (uint64_t)frames_mixed * mp3_stream->channels); } void AudioStreamPlaybackMP3::tag_used_streams() { mp3_stream->tag_used(get_playback_position()); } +void AudioStreamPlaybackMP3::set_is_sample(bool p_is_sample) { + _is_sample = p_is_sample; +} + +bool AudioStreamPlaybackMP3::get_is_sample() const { + return _is_sample; +} + +Ref<AudioSamplePlayback> AudioStreamPlaybackMP3::get_sample_playback() const { + return sample_playback; +} + +void AudioStreamPlaybackMP3::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { + sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } +} + void AudioStreamPlaybackMP3::set_parameter(const StringName &p_name, const Variant &p_value) { if (p_name == SNAME("looping")) { if (p_value == Variant()) { @@ -165,10 +184,7 @@ Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const { } AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() { - if (mp3d) { - mp3dec_ex_close(mp3d); - memfree(mp3d); - } + mp3dec_ex_close(&mp3d); } Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() { @@ -181,9 +197,8 @@ Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() { mp3s.instantiate(); mp3s->mp3_stream = Ref<AudioStreamMP3>(this); - mp3s->mp3d = (mp3dec_ex_t *)memalloc(sizeof(mp3dec_ex_t)); - int errorcode = mp3dec_ex_open_buf(mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE); + int errorcode = mp3dec_ex_open_buf(&mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE); mp3s->frames_mixed = 0; mp3s->active = false; @@ -206,22 +221,22 @@ void AudioStreamMP3::clear_data() { void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) { int src_data_len = p_data.size(); - const uint8_t *src_datar = p_data.ptr(); - mp3dec_ex_t mp3d; - int err = mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); - ERR_FAIL_COND_MSG(err || mp3d.info.hz == 0, "Failed to decode mp3 file. Make sure it is a valid mp3 audio file."); - - channels = mp3d.info.channels; - sample_rate = mp3d.info.hz; - length = float(mp3d.samples) / (sample_rate * float(channels)); + mp3dec_ex_t *mp3d = memnew(mp3dec_ex_t); + int err = mp3dec_ex_open_buf(mp3d, p_data.ptr(), src_data_len, MP3D_SEEK_TO_SAMPLE); + if (err || mp3d->info.hz == 0) { + memdelete(mp3d); + ERR_FAIL_MSG("Failed to decode mp3 file. Make sure it is a valid mp3 audio file."); + } - mp3dec_ex_close(&mp3d); + channels = mp3d->info.channels; + sample_rate = mp3d->info.hz; + length = float(mp3d->samples) / (sample_rate * float(channels)); - clear_data(); + mp3dec_ex_close(mp3d); + memdelete(mp3d); - data.resize(src_data_len); - memcpy(data.ptrw(), src_datar, src_data_len); + data = p_data; data_len = src_data_len; } @@ -287,6 +302,18 @@ int AudioStreamMP3::get_bar_beats() const { return bar_beats; } +Ref<AudioSample> AudioStreamMP3::generate_sample() const { + Ref<AudioSample> sample; + sample.instantiate(); + sample->stream = this; + sample->loop_mode = loop + ? AudioSample::LoopMode::LOOP_FORWARD + : AudioSample::LoopMode::LOOP_DISABLED; + sample->loop_begin = loop_offset; + sample->loop_end = 0; + return sample; +} + void AudioStreamMP3::_bind_methods() { ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamMP3::set_data); ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamMP3::get_data); diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index 7d85e0a321..bb4c7f524c 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -49,7 +49,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled { bool looping_override = false; bool looping = false; - mp3dec_ex_t *mp3d = nullptr; + mp3dec_ex_t mp3d = {}; uint32_t frames_mixed = 0; bool active = false; int loops = 0; @@ -58,6 +58,9 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled { Ref<AudioStreamMP3> mp3_stream; + bool _is_sample = false; + Ref<AudioSamplePlayback> sample_playback; + protected: virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override; virtual float get_stream_sampling_rate() override; @@ -74,6 +77,11 @@ public: virtual void tag_used_streams() override; + virtual void set_is_sample(bool p_is_sample) override; + virtual bool get_is_sample() const override; + virtual Ref<AudioSamplePlayback> get_sample_playback() const override; + virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override; + virtual void set_parameter(const StringName &p_name, const Variant &p_value) override; virtual Variant get_parameter(const StringName &p_name) const override; @@ -88,7 +96,7 @@ class AudioStreamMP3 : public AudioStream { friend class AudioStreamPlaybackMP3; - PackedByteArray data; + LocalVector<uint8_t> data; uint32_t data_len = 0; float sample_rate = 1.0; @@ -131,6 +139,11 @@ public: virtual bool is_monophonic() const override; + virtual bool can_be_sampled() const override { + return true; + } + virtual Ref<AudioSample> generate_sample() const override; + virtual void get_parameter_list(List<Parameter> *r_parameters) override; AudioStreamMP3(); diff --git a/modules/mobile_vr/doc_classes/MobileVRInterface.xml b/modules/mobile_vr/doc_classes/MobileVRInterface.xml index 0dbe06d220..61d802aea3 100644 --- a/modules/mobile_vr/doc_classes/MobileVRInterface.xml +++ b/modules/mobile_vr/doc_classes/MobileVRInterface.xml @@ -10,7 +10,7 @@ [codeblock] var interface = XRServer.find_interface("Native mobile") if interface and interface.initialize(): - get_viewport().xr = true + get_viewport().use_xr = true [/codeblock] </description> <tutorials> diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index f9709362eb..9f88b0575e 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -5,7 +5,7 @@ import os.path import shlex import subprocess from dataclasses import dataclass -from typing import Optional, List +from typing import List, Optional def find_dotnet_cli(): @@ -194,7 +194,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, chdir_to: str, msbuild_args: Opt return subprocess.call(args, env=msbuild_env, cwd=chdir_to) -def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision): +def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated): target_filenames = [ "GodotSharp.dll", "GodotSharp.pdb", @@ -217,6 +217,8 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, pre args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] if precision == "double": args += ["/p:GodotFloat64=true"] + if no_deprecated: + args += ["/p:GodotNoDeprecated=true"] sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args) @@ -304,9 +306,7 @@ def generate_sdk_package_versions(): <GodotVersionConstants>{1}</GodotVersionConstants> </PropertyGroup> </Project> -""".format( - version_str, ";".join(version_defines) - ) +""".format(version_str, ";".join(version_defines)) # We write in ../SdkPackageVersions.props. with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w", encoding="utf-8", newline="\n") as f: @@ -323,9 +323,7 @@ def generate_sdk_package_versions(): public const string VersionDocsUrl = "https://docs.godotengine.org/en/{docs_branch}"; }} }} -""".format( - **version_info - ) +""".format(**version_info) generators_dir = os.path.join( dirname(script_path), @@ -340,12 +338,14 @@ def generate_sdk_package_versions(): f.write(constants) -def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision): +def build_all( + msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision, no_deprecated +): # Generate SdkPackageVersions.props and VersionDocsUrl constant generate_sdk_package_versions() # Godot API - exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision) + exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated) if exit_code != 0: return exit_code @@ -368,6 +368,8 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] if precision == "double": args += ["/p:GodotFloat64=true"] + if no_deprecated: + args += ["/p:GodotNoDeprecated=true"] sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args) if exit_code != 0: @@ -394,6 +396,12 @@ def main(): parser.add_argument( "--precision", type=str, default="single", choices=["single", "double"], help="Floating-point precision level" ) + parser.add_argument( + "--no-deprecated", + action="store_true", + default=False, + help="Build GodotSharp without using deprecated features. This is required, if the engine was built with 'deprecated=no'.", + ) args = parser.parse_args() @@ -418,6 +426,7 @@ def main(): args.dev_debug, push_nupkgs_local, args.precision, + args.no_deprecated, ) sys.exit(exit_code) diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index e5469c4980..98c50dcc22 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -1,7 +1,3 @@ -import os -import os.path - - def is_desktop(platform): return platform in ["windows", "macos", "linuxbsd"] diff --git a/modules/mono/config.py b/modules/mono/config.py index 3d087c9e27..0573fc662a 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -11,9 +11,11 @@ def can_build(env, platform): def configure(env): # Check if the platform has marked mono as supported. supported = env.get("supported", []) + if "mono" not in supported: + import sys - if not "mono" in supported: - raise RuntimeError("This module does not currently support building for this platform") + print("The 'mono' module does not currently support building for this platform. Aborting.") + sys.exit(255) env.add_module_version_string("mono") diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 36c8a40ed9..3d12994469 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -148,7 +148,7 @@ void CSharpLanguage::finalize() { finalizing = true; - // Make sure all script binding gchandles are released before finalizing GDMono + // Make sure all script binding gchandles are released before finalizing GDMono. for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) { CSharpScriptBinding &script_binding = E.value; @@ -156,6 +156,10 @@ void CSharpLanguage::finalize() { script_binding.gchandle.release(); script_binding.inited = false; } + + // Make sure we clear all the instance binding callbacks so they don't get called + // after finalizing the C# language. + script_binding.owner->free_instance_binding(this); } if (gdmono) { @@ -1227,6 +1231,11 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin } GDExtensionBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, void *p_binding, GDExtensionBool p_reference) { + // Instance bindings callbacks can only be called if the C# language is available. + // Failing this assert usually means that we didn't clear the instance binding in some Object + // and the C# language has already been finalized. + DEV_ASSERT(CSharpLanguage::get_singleton() != nullptr); + CRASH_COND(!p_binding); CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)p_binding)->get(); @@ -1488,11 +1497,23 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { List<PropertyInfo> props; - script->get_script_property_list(&props); + ERR_FAIL_COND(!script.is_valid()); +#ifdef TOOLS_ENABLED + for (const PropertyInfo &prop : script->exported_members_cache) { + props.push_back(prop); + } +#else + for (const KeyValue<StringName, PropertyInfo> &E : script->member_info) { + props.push_front(E.value); + } +#endif - // Call _get_property_list + for (PropertyInfo &prop : props) { + validate_property(prop); + p_properties->push_back(prop); + } - ERR_FAIL_COND(!script.is_valid()); + // Call _get_property_list StringName method = SNAME("_get_property_list"); @@ -1515,9 +1536,25 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { } } - for (PropertyInfo &prop : props) { - validate_property(prop); - p_properties->push_back(prop); + CSharpScript *top = script.ptr()->base_script.ptr(); + while (top != nullptr) { + props.clear(); +#ifdef TOOLS_ENABLED + for (const PropertyInfo &prop : top->exported_members_cache) { + props.push_back(prop); + } +#else + for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) { + props.push_front(E.value); + } +#endif + + for (PropertyInfo &prop : props) { + validate_property(prop); + p_properties->push_back(prop); + } + + top = top->base_script.ptr(); } } @@ -1662,7 +1699,7 @@ bool CSharpInstance::_reference_owner_unsafe() { // but the managed instance is alive, the refcount will be 1 instead of 0. // See: _unreference_owner_unsafe() - // May not me referenced yet, so we must use init_ref() instead of reference() + // May not be referenced yet, so we must use init_ref() instead of reference() if (static_cast<RefCounted *>(owner)->init_ref()) { CSharpLanguage::get_singleton()->post_unsafe_reference(owner); unsafe_referenced = true; @@ -2351,8 +2388,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg if (!ok) { // Important to clear this before destroying the script instance here instance->script = Ref<CSharpScript>(); - instance->owner = nullptr; p_owner->set_script_instance(nullptr); + instance->owner = nullptr; return nullptr; } @@ -2707,7 +2744,7 @@ int CSharpScript::get_member_line(const StringName &p_member) const { return -1; } -const Variant CSharpScript::get_rpc_config() const { +Variant CSharpScript::get_rpc_config() const { return rpc_config; } @@ -2787,7 +2824,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const if (GDMonoCache::godot_api_cache_updated) { GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr); - ERR_FAIL_NULL_V_MSG(scr, Ref<Resource>(), "Could not create C# script '" + real_path + "'."); + ERR_FAIL_COND_V_MSG(scr.is_null(), Ref<Resource>(), "Could not create C# script '" + real_path + "'."); } else { scr = Ref<CSharpScript>(memnew(CSharpScript)); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index c48e1a95c9..ec7328be4a 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -284,7 +284,7 @@ public: int get_member_line(const StringName &p_member) const override; - const Variant get_rpc_config() const override; + Variant get_rpc_config() const override; #ifdef TOOLS_ENABLED bool is_placeholder_fallback_enabled() const override { diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml index b559ca20b2..7e14259d5a 100644 --- a/modules/mono/doc_classes/CSharpScript.xml +++ b/modules/mono/doc_classes/CSharpScript.xml @@ -5,7 +5,6 @@ </brief_description> <description> This class represents a C# script. It is the C# equivalent of the [GDScript] class and is only available in Mono-enabled Godot builds. - See also [GodotSharp]. </description> <tutorials> <link title="C# documentation index">$DOCS_URL/tutorials/scripting/c_sharp/index.html</link> diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml deleted file mode 100644 index 969ca14350..0000000000 --- a/modules/mono/doc_classes/GodotSharp.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="GodotSharp" inherits="Object" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> - <brief_description> - Bridge between Godot and the Mono runtime (Mono-enabled builds only). - </brief_description> - <description> - This class is a bridge between Godot and the Mono runtime. It exposes several low-level operations and is only available in Mono-enabled Godot builds. - See also [CSharpScript]. - </description> - <tutorials> - </tutorials> - <methods> - <method name="is_runtime_initialized"> - <return type="bool" /> - <description> - Returns [code]true[/code] if the .NET runtime is initialized, [code]false[/code] otherwise. - </description> - </method> - </methods> -</class> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 74623a60ba..c5f2dfee4b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -7,7 +7,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>Godot.NET.Sdk</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> @@ -30,6 +30,7 @@ <None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk"> <Link>Sdk\SdkPackageVersions.props</Link> </None> + <None Include="Sdk\Android.props" Pack="true" PackagePath="Sdk" /> <None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" /> <None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" /> </ItemGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props new file mode 100644 index 0000000000..3926a4b22a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props @@ -0,0 +1,5 @@ +<Project> + <PropertyGroup> + <UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' and '$(PublishAot)' != 'true' ">true</UseMonoRuntime> + </PropertyGroup> +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index c4034f1f9f..d10f9ae0ab 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -112,5 +112,6 @@ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> </PropertyGroup> + <Import Project="$(MSBuildThisFileDirectory)\Android.props" Condition=" '$(GodotTargetPlatform)' == 'android' " /> <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " /> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AbstractGenericNode(Of T)_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AbstractGenericNode(Of T)_ScriptProperties.generated.cs index a561c5fc0d..024d4b7fbe 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AbstractGenericNode(Of T)_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AbstractGenericNode(Of T)_ScriptProperties.generated.cs @@ -11,14 +11,14 @@ partial class AbstractGenericNode<T> /// <summary> /// Cached name for the 'MyArray' property. /// </summary> - public new static readonly global::Godot.StringName MyArray = "MyArray"; + public new static readonly global::Godot.StringName @MyArray = "MyArray"; } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { - if (name == PropertyName.MyArray) { - this.MyArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<T>(value); + if (name == PropertyName.@MyArray) { + this.@MyArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<T>(value); return true; } return base.SetGodotClassPropertyValue(name, value); @@ -27,8 +27,8 @@ partial class AbstractGenericNode<T> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - if (name == PropertyName.MyArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.MyArray); + if (name == PropertyName.@MyArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.@MyArray); return true; } return base.GetGodotClassPropertyValue(name, out value); @@ -42,7 +42,7 @@ partial class AbstractGenericNode<T> internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() { var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.MyArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@MyArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); return properties; } #pragma warning restore CS0109 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs index 825daffe80..dbcefbbcbd 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllReadOnly_ScriptProperties.generated.cs @@ -11,38 +11,38 @@ partial class AllReadOnly /// <summary> /// Cached name for the 'ReadOnlyAutoProperty' property. /// </summary> - public new static readonly global::Godot.StringName ReadOnlyAutoProperty = "ReadOnlyAutoProperty"; + public new static readonly global::Godot.StringName @ReadOnlyAutoProperty = "ReadOnlyAutoProperty"; /// <summary> /// Cached name for the 'ReadOnlyProperty' property. /// </summary> - public new static readonly global::Godot.StringName ReadOnlyProperty = "ReadOnlyProperty"; + public new static readonly global::Godot.StringName @ReadOnlyProperty = "ReadOnlyProperty"; /// <summary> /// Cached name for the 'InitOnlyAutoProperty' property. /// </summary> - public new static readonly global::Godot.StringName InitOnlyAutoProperty = "InitOnlyAutoProperty"; + public new static readonly global::Godot.StringName @InitOnlyAutoProperty = "InitOnlyAutoProperty"; /// <summary> /// Cached name for the 'ReadOnlyField' field. /// </summary> - public new static readonly global::Godot.StringName ReadOnlyField = "ReadOnlyField"; + public new static readonly global::Godot.StringName @ReadOnlyField = "ReadOnlyField"; } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - if (name == PropertyName.ReadOnlyAutoProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.ReadOnlyAutoProperty); + if (name == PropertyName.@ReadOnlyAutoProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@ReadOnlyAutoProperty); return true; } - if (name == PropertyName.ReadOnlyProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.ReadOnlyProperty); + if (name == PropertyName.@ReadOnlyProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@ReadOnlyProperty); return true; } - if (name == PropertyName.InitOnlyAutoProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.InitOnlyAutoProperty); + if (name == PropertyName.@InitOnlyAutoProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@InitOnlyAutoProperty); return true; } - if (name == PropertyName.ReadOnlyField) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.ReadOnlyField); + if (name == PropertyName.@ReadOnlyField) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@ReadOnlyField); return true; } return base.GetGodotClassPropertyValue(name, out value); @@ -56,10 +56,10 @@ partial class AllReadOnly internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() { var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.ReadOnlyField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.ReadOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.ReadOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.InitOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@ReadOnlyField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@ReadOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@ReadOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@InitOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); return properties; } #pragma warning restore CS0109 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs index 615450efe8..0d559132ac 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AllWriteOnly_ScriptProperties.generated.cs @@ -11,22 +11,22 @@ partial class AllWriteOnly /// <summary> /// Cached name for the 'WriteOnlyProperty' property. /// </summary> - public new static readonly global::Godot.StringName WriteOnlyProperty = "WriteOnlyProperty"; + public new static readonly global::Godot.StringName @WriteOnlyProperty = "WriteOnlyProperty"; /// <summary> /// Cached name for the '_writeOnlyBackingField' field. /// </summary> - public new static readonly global::Godot.StringName _writeOnlyBackingField = "_writeOnlyBackingField"; + public new static readonly global::Godot.StringName @_writeOnlyBackingField = "_writeOnlyBackingField"; } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { - if (name == PropertyName.WriteOnlyProperty) { - this.WriteOnlyProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); + if (name == PropertyName.@WriteOnlyProperty) { + this.@WriteOnlyProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); return true; } - if (name == PropertyName._writeOnlyBackingField) { - this._writeOnlyBackingField = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); + if (name == PropertyName.@_writeOnlyBackingField) { + this.@_writeOnlyBackingField = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); return true; } return base.SetGodotClassPropertyValue(name, value); @@ -35,8 +35,8 @@ partial class AllWriteOnly [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - if (name == PropertyName._writeOnlyBackingField) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this._writeOnlyBackingField); + if (name == PropertyName.@_writeOnlyBackingField) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.@_writeOnlyBackingField); return true; } return base.GetGodotClassPropertyValue(name, out value); @@ -50,8 +50,8 @@ partial class AllWriteOnly internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() { var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); - properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName._writeOnlyBackingField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.WriteOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.@_writeOnlyBackingField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.@WriteOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); return properties; } #pragma warning restore CS0109 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs index b1c57e6b26..cc45e5746f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs @@ -11,7 +11,7 @@ partial class EventSignals /// <summary> /// Cached name for the 'MySignal' signal. /// </summary> - public new static readonly global::Godot.StringName MySignal = "MySignal"; + public new static readonly global::Godot.StringName @MySignal = "MySignal"; } /// <summary> /// Get the signal information for all the signals declared in this class. @@ -22,13 +22,13 @@ partial class EventSignals internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotSignalList() { var signals = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(1); - signals.Add(new(name: SignalName.MySignal, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)4, name: "str", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), new(type: (global::Godot.Variant.Type)2, name: "num", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); + signals.Add(new(name: SignalName.@MySignal, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)4, name: "str", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), new(type: (global::Godot.Variant.Type)2, name: "num", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); return signals; } #pragma warning restore CS0109 private global::EventSignals.MySignalEventHandler backing_MySignal; /// <inheritdoc cref="global::EventSignals.MySignalEventHandler"/> - public event global::EventSignals.MySignalEventHandler MySignal { + public event global::EventSignals.MySignalEventHandler @MySignal { add => backing_MySignal += value; remove => backing_MySignal -= value; } @@ -36,7 +36,7 @@ partial class EventSignals [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, NativeVariantPtrArgs args) { - if (signal == SignalName.MySignal && args.Count == 2) { + if (signal == SignalName.@MySignal && args.Count == 2) { backing_MySignal?.Invoke(global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(args[0]), global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[1])); return; } @@ -46,7 +46,7 @@ partial class EventSignals [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool HasGodotClassSignal(in godot_string_name signal) { - if (signal == SignalName.MySignal) { + if (signal == SignalName.@MySignal) { return true; } return base.HasGodotClassSignal(signal); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0106_OK_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0106_OK_ScriptPropertyDefVal.generated.cs index f9dc4003e7..8ff51265c1 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0106_OK_ScriptPropertyDefVal.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0106_OK_ScriptPropertyDefVal.generated.cs @@ -13,7 +13,7 @@ partial class ExportDiagnostics_GD0106_OK { var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(1); int __MyProperty_default_value = default; - values.Add(PropertyName.MyProperty, global::Godot.Variant.From<int>(__MyProperty_default_value)); + values.Add(PropertyName.@MyProperty, global::Godot.Variant.From<int>(__MyProperty_default_value)); return values; } #endif // TOOLS diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs index 8b823d52c1..9a8b3ea846 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs @@ -11,11 +11,27 @@ partial class ExportDiagnostics_GD0107_OK [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] internal new static global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant> GetGodotPropertyDefaultValues() { - var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(2); + var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(10); global::Godot.Node __NodeProperty_default_value = default; - values.Add(PropertyName.NodeProperty, global::Godot.Variant.From<global::Godot.Node>(__NodeProperty_default_value)); + values.Add(PropertyName.@NodeProperty, global::Godot.Variant.From<global::Godot.Node>(__NodeProperty_default_value)); + global::Godot.Node[] __SystemArrayOfNodesProperty_default_value = default; + values.Add(PropertyName.@SystemArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesProperty_default_value)); + global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesProperty_default_value = default; + values.Add(PropertyName.@GodotArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesProperty_default_value)); + global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyProperty_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyProperty_default_value)); + global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueProperty_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsValueProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueProperty_default_value)); global::Godot.Node __NodeField_default_value = default; - values.Add(PropertyName.NodeField, global::Godot.Variant.From<global::Godot.Node>(__NodeField_default_value)); + values.Add(PropertyName.@NodeField, global::Godot.Variant.From<global::Godot.Node>(__NodeField_default_value)); + global::Godot.Node[] __SystemArrayOfNodesField_default_value = default; + values.Add(PropertyName.@SystemArrayOfNodesField, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesField_default_value)); + global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesField_default_value = default; + values.Add(PropertyName.@GodotArrayOfNodesField, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesField_default_value)); + global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyField_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyField_default_value)); + global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueField_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsValueField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueField_default_value)); return values; } #endif // TOOLS diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedComplexStrings_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedComplexStrings_ScriptPropertyDefVal.generated.cs index 69e85b4467..2fc0ca50d1 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedComplexStrings_ScriptPropertyDefVal.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedComplexStrings_ScriptPropertyDefVal.generated.cs @@ -13,15 +13,15 @@ partial class ExportedComplexStrings { var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(5); string __PropertyInterpolated1_default_value = $"The quick brown fox jumps over {(global::Godot.GD.VarToStr($"the lazy {(global::Godot.Engine.GetVersionInfo())} do"))}g."; - values.Add(PropertyName.PropertyInterpolated1, global::Godot.Variant.From<string>(__PropertyInterpolated1_default_value)); + values.Add(PropertyName.@PropertyInterpolated1, global::Godot.Variant.From<string>(__PropertyInterpolated1_default_value)); string ___fieldInterpolated1_default_value = $"The quick brown fox jumps over ({(global::Godot.Engine.GetVersionInfo())})"; - values.Add(PropertyName._fieldInterpolated1, global::Godot.Variant.From<string>(___fieldInterpolated1_default_value)); + values.Add(PropertyName.@_fieldInterpolated1, global::Godot.Variant.From<string>(___fieldInterpolated1_default_value)); string ___fieldInterpolated2_default_value = $"The quick brown fox jumps over ({(global::Godot.Engine.GetVersionInfo()["major"]),0:G}) the lazy dog."; - values.Add(PropertyName._fieldInterpolated2, global::Godot.Variant.From<string>(___fieldInterpolated2_default_value)); + values.Add(PropertyName.@_fieldInterpolated2, global::Godot.Variant.From<string>(___fieldInterpolated2_default_value)); string ___fieldInterpolated3_default_value = $"{(((int)global::Godot.Engine.GetVersionInfo()["major"]) * -1 * -1):G} the lazy dog."; - values.Add(PropertyName._fieldInterpolated3, global::Godot.Variant.From<string>(___fieldInterpolated3_default_value)); + values.Add(PropertyName.@_fieldInterpolated3, global::Godot.Variant.From<string>(___fieldInterpolated3_default_value)); string ___fieldInterpolated4_default_value = $"{(":::fff,,}<,<}},,}]")}"; - values.Add(PropertyName._fieldInterpolated4, global::Godot.Variant.From<string>(___fieldInterpolated4_default_value)); + values.Add(PropertyName.@_fieldInterpolated4, global::Godot.Variant.From<string>(___fieldInterpolated4_default_value)); return values; } #endif // TOOLS diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs index 67ec4fa883..c734dc7be1 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs @@ -11,486 +11,486 @@ partial class ExportedFields /// <summary> /// Cached name for the '_fieldBoolean' field. /// </summary> - public new static readonly global::Godot.StringName _fieldBoolean = "_fieldBoolean"; + public new static readonly global::Godot.StringName @_fieldBoolean = "_fieldBoolean"; /// <summary> /// Cached name for the '_fieldChar' field. /// </summary> - public new static readonly global::Godot.StringName _fieldChar = "_fieldChar"; + public new static readonly global::Godot.StringName @_fieldChar = "_fieldChar"; /// <summary> /// Cached name for the '_fieldSByte' field. /// </summary> - public new static readonly global::Godot.StringName _fieldSByte = "_fieldSByte"; + public new static readonly global::Godot.StringName @_fieldSByte = "_fieldSByte"; /// <summary> /// Cached name for the '_fieldInt16' field. /// </summary> - public new static readonly global::Godot.StringName _fieldInt16 = "_fieldInt16"; + public new static readonly global::Godot.StringName @_fieldInt16 = "_fieldInt16"; /// <summary> /// Cached name for the '_fieldInt32' field. /// </summary> - public new static readonly global::Godot.StringName _fieldInt32 = "_fieldInt32"; + public new static readonly global::Godot.StringName @_fieldInt32 = "_fieldInt32"; /// <summary> /// Cached name for the '_fieldInt64' field. /// </summary> - public new static readonly global::Godot.StringName _fieldInt64 = "_fieldInt64"; + public new static readonly global::Godot.StringName @_fieldInt64 = "_fieldInt64"; /// <summary> /// Cached name for the '_fieldByte' field. /// </summary> - public new static readonly global::Godot.StringName _fieldByte = "_fieldByte"; + public new static readonly global::Godot.StringName @_fieldByte = "_fieldByte"; /// <summary> /// Cached name for the '_fieldUInt16' field. /// </summary> - public new static readonly global::Godot.StringName _fieldUInt16 = "_fieldUInt16"; + public new static readonly global::Godot.StringName @_fieldUInt16 = "_fieldUInt16"; /// <summary> /// Cached name for the '_fieldUInt32' field. /// </summary> - public new static readonly global::Godot.StringName _fieldUInt32 = "_fieldUInt32"; + public new static readonly global::Godot.StringName @_fieldUInt32 = "_fieldUInt32"; /// <summary> /// Cached name for the '_fieldUInt64' field. /// </summary> - public new static readonly global::Godot.StringName _fieldUInt64 = "_fieldUInt64"; + public new static readonly global::Godot.StringName @_fieldUInt64 = "_fieldUInt64"; /// <summary> /// Cached name for the '_fieldSingle' field. /// </summary> - public new static readonly global::Godot.StringName _fieldSingle = "_fieldSingle"; + public new static readonly global::Godot.StringName @_fieldSingle = "_fieldSingle"; /// <summary> /// Cached name for the '_fieldDouble' field. /// </summary> - public new static readonly global::Godot.StringName _fieldDouble = "_fieldDouble"; + public new static readonly global::Godot.StringName @_fieldDouble = "_fieldDouble"; /// <summary> /// Cached name for the '_fieldString' field. /// </summary> - public new static readonly global::Godot.StringName _fieldString = "_fieldString"; + public new static readonly global::Godot.StringName @_fieldString = "_fieldString"; /// <summary> /// Cached name for the '_fieldVector2' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector2 = "_fieldVector2"; + public new static readonly global::Godot.StringName @_fieldVector2 = "_fieldVector2"; /// <summary> /// Cached name for the '_fieldVector2I' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector2I = "_fieldVector2I"; + public new static readonly global::Godot.StringName @_fieldVector2I = "_fieldVector2I"; /// <summary> /// Cached name for the '_fieldRect2' field. /// </summary> - public new static readonly global::Godot.StringName _fieldRect2 = "_fieldRect2"; + public new static readonly global::Godot.StringName @_fieldRect2 = "_fieldRect2"; /// <summary> /// Cached name for the '_fieldRect2I' field. /// </summary> - public new static readonly global::Godot.StringName _fieldRect2I = "_fieldRect2I"; + public new static readonly global::Godot.StringName @_fieldRect2I = "_fieldRect2I"; /// <summary> /// Cached name for the '_fieldTransform2D' field. /// </summary> - public new static readonly global::Godot.StringName _fieldTransform2D = "_fieldTransform2D"; + public new static readonly global::Godot.StringName @_fieldTransform2D = "_fieldTransform2D"; /// <summary> /// Cached name for the '_fieldVector3' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector3 = "_fieldVector3"; + public new static readonly global::Godot.StringName @_fieldVector3 = "_fieldVector3"; /// <summary> /// Cached name for the '_fieldVector3I' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector3I = "_fieldVector3I"; + public new static readonly global::Godot.StringName @_fieldVector3I = "_fieldVector3I"; /// <summary> /// Cached name for the '_fieldBasis' field. /// </summary> - public new static readonly global::Godot.StringName _fieldBasis = "_fieldBasis"; + public new static readonly global::Godot.StringName @_fieldBasis = "_fieldBasis"; /// <summary> /// Cached name for the '_fieldQuaternion' field. /// </summary> - public new static readonly global::Godot.StringName _fieldQuaternion = "_fieldQuaternion"; + public new static readonly global::Godot.StringName @_fieldQuaternion = "_fieldQuaternion"; /// <summary> /// Cached name for the '_fieldTransform3D' field. /// </summary> - public new static readonly global::Godot.StringName _fieldTransform3D = "_fieldTransform3D"; + public new static readonly global::Godot.StringName @_fieldTransform3D = "_fieldTransform3D"; /// <summary> /// Cached name for the '_fieldVector4' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector4 = "_fieldVector4"; + public new static readonly global::Godot.StringName @_fieldVector4 = "_fieldVector4"; /// <summary> /// Cached name for the '_fieldVector4I' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector4I = "_fieldVector4I"; + public new static readonly global::Godot.StringName @_fieldVector4I = "_fieldVector4I"; /// <summary> /// Cached name for the '_fieldProjection' field. /// </summary> - public new static readonly global::Godot.StringName _fieldProjection = "_fieldProjection"; + public new static readonly global::Godot.StringName @_fieldProjection = "_fieldProjection"; /// <summary> /// Cached name for the '_fieldAabb' field. /// </summary> - public new static readonly global::Godot.StringName _fieldAabb = "_fieldAabb"; + public new static readonly global::Godot.StringName @_fieldAabb = "_fieldAabb"; /// <summary> /// Cached name for the '_fieldColor' field. /// </summary> - public new static readonly global::Godot.StringName _fieldColor = "_fieldColor"; + public new static readonly global::Godot.StringName @_fieldColor = "_fieldColor"; /// <summary> /// Cached name for the '_fieldPlane' field. /// </summary> - public new static readonly global::Godot.StringName _fieldPlane = "_fieldPlane"; + public new static readonly global::Godot.StringName @_fieldPlane = "_fieldPlane"; /// <summary> /// Cached name for the '_fieldCallable' field. /// </summary> - public new static readonly global::Godot.StringName _fieldCallable = "_fieldCallable"; + public new static readonly global::Godot.StringName @_fieldCallable = "_fieldCallable"; /// <summary> /// Cached name for the '_fieldSignal' field. /// </summary> - public new static readonly global::Godot.StringName _fieldSignal = "_fieldSignal"; + public new static readonly global::Godot.StringName @_fieldSignal = "_fieldSignal"; /// <summary> /// Cached name for the '_fieldEnum' field. /// </summary> - public new static readonly global::Godot.StringName _fieldEnum = "_fieldEnum"; + public new static readonly global::Godot.StringName @_fieldEnum = "_fieldEnum"; /// <summary> /// Cached name for the '_fieldFlagsEnum' field. /// </summary> - public new static readonly global::Godot.StringName _fieldFlagsEnum = "_fieldFlagsEnum"; + public new static readonly global::Godot.StringName @_fieldFlagsEnum = "_fieldFlagsEnum"; /// <summary> /// Cached name for the '_fieldByteArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldByteArray = "_fieldByteArray"; + public new static readonly global::Godot.StringName @_fieldByteArray = "_fieldByteArray"; /// <summary> /// Cached name for the '_fieldInt32Array' field. /// </summary> - public new static readonly global::Godot.StringName _fieldInt32Array = "_fieldInt32Array"; + public new static readonly global::Godot.StringName @_fieldInt32Array = "_fieldInt32Array"; /// <summary> /// Cached name for the '_fieldInt64Array' field. /// </summary> - public new static readonly global::Godot.StringName _fieldInt64Array = "_fieldInt64Array"; + public new static readonly global::Godot.StringName @_fieldInt64Array = "_fieldInt64Array"; /// <summary> /// Cached name for the '_fieldSingleArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldSingleArray = "_fieldSingleArray"; + public new static readonly global::Godot.StringName @_fieldSingleArray = "_fieldSingleArray"; /// <summary> /// Cached name for the '_fieldDoubleArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldDoubleArray = "_fieldDoubleArray"; + public new static readonly global::Godot.StringName @_fieldDoubleArray = "_fieldDoubleArray"; /// <summary> /// Cached name for the '_fieldStringArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldStringArray = "_fieldStringArray"; + public new static readonly global::Godot.StringName @_fieldStringArray = "_fieldStringArray"; /// <summary> /// Cached name for the '_fieldStringArrayEnum' field. /// </summary> - public new static readonly global::Godot.StringName _fieldStringArrayEnum = "_fieldStringArrayEnum"; + public new static readonly global::Godot.StringName @_fieldStringArrayEnum = "_fieldStringArrayEnum"; /// <summary> /// Cached name for the '_fieldVector2Array' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector2Array = "_fieldVector2Array"; + public new static readonly global::Godot.StringName @_fieldVector2Array = "_fieldVector2Array"; /// <summary> /// Cached name for the '_fieldVector3Array' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVector3Array = "_fieldVector3Array"; + public new static readonly global::Godot.StringName @_fieldVector3Array = "_fieldVector3Array"; /// <summary> /// Cached name for the '_fieldColorArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldColorArray = "_fieldColorArray"; + public new static readonly global::Godot.StringName @_fieldColorArray = "_fieldColorArray"; /// <summary> /// Cached name for the '_fieldGodotObjectOrDerivedArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldGodotObjectOrDerivedArray = "_fieldGodotObjectOrDerivedArray"; + public new static readonly global::Godot.StringName @_fieldGodotObjectOrDerivedArray = "_fieldGodotObjectOrDerivedArray"; /// <summary> /// Cached name for the '_fieldStringNameArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldStringNameArray = "_fieldStringNameArray"; + public new static readonly global::Godot.StringName @_fieldStringNameArray = "_fieldStringNameArray"; /// <summary> /// Cached name for the '_fieldNodePathArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldNodePathArray = "_fieldNodePathArray"; + public new static readonly global::Godot.StringName @_fieldNodePathArray = "_fieldNodePathArray"; /// <summary> /// Cached name for the '_fieldRidArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldRidArray = "_fieldRidArray"; + public new static readonly global::Godot.StringName @_fieldRidArray = "_fieldRidArray"; /// <summary> /// Cached name for the '_fieldEmptyInt32Array' field. /// </summary> - public new static readonly global::Godot.StringName _fieldEmptyInt32Array = "_fieldEmptyInt32Array"; + public new static readonly global::Godot.StringName @_fieldEmptyInt32Array = "_fieldEmptyInt32Array"; /// <summary> /// Cached name for the '_fieldArrayFromList' field. /// </summary> - public new static readonly global::Godot.StringName _fieldArrayFromList = "_fieldArrayFromList"; + public new static readonly global::Godot.StringName @_fieldArrayFromList = "_fieldArrayFromList"; /// <summary> /// Cached name for the '_fieldVariant' field. /// </summary> - public new static readonly global::Godot.StringName _fieldVariant = "_fieldVariant"; + public new static readonly global::Godot.StringName @_fieldVariant = "_fieldVariant"; /// <summary> /// Cached name for the '_fieldGodotObjectOrDerived' field. /// </summary> - public new static readonly global::Godot.StringName _fieldGodotObjectOrDerived = "_fieldGodotObjectOrDerived"; + public new static readonly global::Godot.StringName @_fieldGodotObjectOrDerived = "_fieldGodotObjectOrDerived"; /// <summary> /// Cached name for the '_fieldGodotResourceTexture' field. /// </summary> - public new static readonly global::Godot.StringName _fieldGodotResourceTexture = "_fieldGodotResourceTexture"; + public new static readonly global::Godot.StringName @_fieldGodotResourceTexture = "_fieldGodotResourceTexture"; /// <summary> /// Cached name for the '_fieldStringName' field. /// </summary> - public new static readonly global::Godot.StringName _fieldStringName = "_fieldStringName"; + public new static readonly global::Godot.StringName @_fieldStringName = "_fieldStringName"; /// <summary> /// Cached name for the '_fieldNodePath' field. /// </summary> - public new static readonly global::Godot.StringName _fieldNodePath = "_fieldNodePath"; + public new static readonly global::Godot.StringName @_fieldNodePath = "_fieldNodePath"; /// <summary> /// Cached name for the '_fieldRid' field. /// </summary> - public new static readonly global::Godot.StringName _fieldRid = "_fieldRid"; + public new static readonly global::Godot.StringName @_fieldRid = "_fieldRid"; /// <summary> /// Cached name for the '_fieldGodotDictionary' field. /// </summary> - public new static readonly global::Godot.StringName _fieldGodotDictionary = "_fieldGodotDictionary"; + public new static readonly global::Godot.StringName @_fieldGodotDictionary = "_fieldGodotDictionary"; /// <summary> /// Cached name for the '_fieldGodotArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldGodotArray = "_fieldGodotArray"; + public new static readonly global::Godot.StringName @_fieldGodotArray = "_fieldGodotArray"; /// <summary> /// Cached name for the '_fieldGodotGenericDictionary' field. /// </summary> - public new static readonly global::Godot.StringName _fieldGodotGenericDictionary = "_fieldGodotGenericDictionary"; + public new static readonly global::Godot.StringName @_fieldGodotGenericDictionary = "_fieldGodotGenericDictionary"; /// <summary> /// Cached name for the '_fieldGodotGenericArray' field. /// </summary> - public new static readonly global::Godot.StringName _fieldGodotGenericArray = "_fieldGodotGenericArray"; + public new static readonly global::Godot.StringName @_fieldGodotGenericArray = "_fieldGodotGenericArray"; /// <summary> /// Cached name for the '_fieldEmptyInt64Array' field. /// </summary> - public new static readonly global::Godot.StringName _fieldEmptyInt64Array = "_fieldEmptyInt64Array"; + public new static readonly global::Godot.StringName @_fieldEmptyInt64Array = "_fieldEmptyInt64Array"; } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { - if (name == PropertyName._fieldBoolean) { - this._fieldBoolean = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); + if (name == PropertyName.@_fieldBoolean) { + this.@_fieldBoolean = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); return true; } - if (name == PropertyName._fieldChar) { - this._fieldChar = global::Godot.NativeInterop.VariantUtils.ConvertTo<char>(value); + if (name == PropertyName.@_fieldChar) { + this.@_fieldChar = global::Godot.NativeInterop.VariantUtils.ConvertTo<char>(value); return true; } - if (name == PropertyName._fieldSByte) { - this._fieldSByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<sbyte>(value); + if (name == PropertyName.@_fieldSByte) { + this.@_fieldSByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<sbyte>(value); return true; } - if (name == PropertyName._fieldInt16) { - this._fieldInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<short>(value); + if (name == PropertyName.@_fieldInt16) { + this.@_fieldInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<short>(value); return true; } - if (name == PropertyName._fieldInt32) { - this._fieldInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); + if (name == PropertyName.@_fieldInt32) { + this.@_fieldInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); return true; } - if (name == PropertyName._fieldInt64) { - this._fieldInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<long>(value); + if (name == PropertyName.@_fieldInt64) { + this.@_fieldInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<long>(value); return true; } - if (name == PropertyName._fieldByte) { - this._fieldByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte>(value); + if (name == PropertyName.@_fieldByte) { + this.@_fieldByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte>(value); return true; } - if (name == PropertyName._fieldUInt16) { - this._fieldUInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ushort>(value); + if (name == PropertyName.@_fieldUInt16) { + this.@_fieldUInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ushort>(value); return true; } - if (name == PropertyName._fieldUInt32) { - this._fieldUInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<uint>(value); + if (name == PropertyName.@_fieldUInt32) { + this.@_fieldUInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<uint>(value); return true; } - if (name == PropertyName._fieldUInt64) { - this._fieldUInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ulong>(value); + if (name == PropertyName.@_fieldUInt64) { + this.@_fieldUInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ulong>(value); return true; } - if (name == PropertyName._fieldSingle) { - this._fieldSingle = global::Godot.NativeInterop.VariantUtils.ConvertTo<float>(value); + if (name == PropertyName.@_fieldSingle) { + this.@_fieldSingle = global::Godot.NativeInterop.VariantUtils.ConvertTo<float>(value); return true; } - if (name == PropertyName._fieldDouble) { - this._fieldDouble = global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(value); + if (name == PropertyName.@_fieldDouble) { + this.@_fieldDouble = global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(value); return true; } - if (name == PropertyName._fieldString) { - this._fieldString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@_fieldString) { + this.@_fieldString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName._fieldVector2) { - this._fieldVector2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2>(value); + if (name == PropertyName.@_fieldVector2) { + this.@_fieldVector2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2>(value); return true; } - if (name == PropertyName._fieldVector2I) { - this._fieldVector2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2I>(value); + if (name == PropertyName.@_fieldVector2I) { + this.@_fieldVector2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2I>(value); return true; } - if (name == PropertyName._fieldRect2) { - this._fieldRect2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2>(value); + if (name == PropertyName.@_fieldRect2) { + this.@_fieldRect2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2>(value); return true; } - if (name == PropertyName._fieldRect2I) { - this._fieldRect2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2I>(value); + if (name == PropertyName.@_fieldRect2I) { + this.@_fieldRect2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2I>(value); return true; } - if (name == PropertyName._fieldTransform2D) { - this._fieldTransform2D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform2D>(value); + if (name == PropertyName.@_fieldTransform2D) { + this.@_fieldTransform2D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform2D>(value); return true; } - if (name == PropertyName._fieldVector3) { - this._fieldVector3 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3>(value); + if (name == PropertyName.@_fieldVector3) { + this.@_fieldVector3 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3>(value); return true; } - if (name == PropertyName._fieldVector3I) { - this._fieldVector3I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3I>(value); + if (name == PropertyName.@_fieldVector3I) { + this.@_fieldVector3I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3I>(value); return true; } - if (name == PropertyName._fieldBasis) { - this._fieldBasis = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Basis>(value); + if (name == PropertyName.@_fieldBasis) { + this.@_fieldBasis = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Basis>(value); return true; } - if (name == PropertyName._fieldQuaternion) { - this._fieldQuaternion = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Quaternion>(value); + if (name == PropertyName.@_fieldQuaternion) { + this.@_fieldQuaternion = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Quaternion>(value); return true; } - if (name == PropertyName._fieldTransform3D) { - this._fieldTransform3D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform3D>(value); + if (name == PropertyName.@_fieldTransform3D) { + this.@_fieldTransform3D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform3D>(value); return true; } - if (name == PropertyName._fieldVector4) { - this._fieldVector4 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4>(value); + if (name == PropertyName.@_fieldVector4) { + this.@_fieldVector4 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4>(value); return true; } - if (name == PropertyName._fieldVector4I) { - this._fieldVector4I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4I>(value); + if (name == PropertyName.@_fieldVector4I) { + this.@_fieldVector4I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4I>(value); return true; } - if (name == PropertyName._fieldProjection) { - this._fieldProjection = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Projection>(value); + if (name == PropertyName.@_fieldProjection) { + this.@_fieldProjection = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Projection>(value); return true; } - if (name == PropertyName._fieldAabb) { - this._fieldAabb = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Aabb>(value); + if (name == PropertyName.@_fieldAabb) { + this.@_fieldAabb = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Aabb>(value); return true; } - if (name == PropertyName._fieldColor) { - this._fieldColor = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color>(value); + if (name == PropertyName.@_fieldColor) { + this.@_fieldColor = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color>(value); return true; } - if (name == PropertyName._fieldPlane) { - this._fieldPlane = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Plane>(value); + if (name == PropertyName.@_fieldPlane) { + this.@_fieldPlane = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Plane>(value); return true; } - if (name == PropertyName._fieldCallable) { - this._fieldCallable = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); + if (name == PropertyName.@_fieldCallable) { + this.@_fieldCallable = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); return true; } - if (name == PropertyName._fieldSignal) { - this._fieldSignal = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Signal>(value); + if (name == PropertyName.@_fieldSignal) { + this.@_fieldSignal = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Signal>(value); return true; } - if (name == PropertyName._fieldEnum) { - this._fieldEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedFields.MyEnum>(value); + if (name == PropertyName.@_fieldEnum) { + this.@_fieldEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedFields.MyEnum>(value); return true; } - if (name == PropertyName._fieldFlagsEnum) { - this._fieldFlagsEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedFields.MyFlagsEnum>(value); + if (name == PropertyName.@_fieldFlagsEnum) { + this.@_fieldFlagsEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedFields.MyFlagsEnum>(value); return true; } - if (name == PropertyName._fieldByteArray) { - this._fieldByteArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte[]>(value); + if (name == PropertyName.@_fieldByteArray) { + this.@_fieldByteArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte[]>(value); return true; } - if (name == PropertyName._fieldInt32Array) { - this._fieldInt32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); + if (name == PropertyName.@_fieldInt32Array) { + this.@_fieldInt32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); return true; } - if (name == PropertyName._fieldInt64Array) { - this._fieldInt64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value); + if (name == PropertyName.@_fieldInt64Array) { + this.@_fieldInt64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value); return true; } - if (name == PropertyName._fieldSingleArray) { - this._fieldSingleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<float[]>(value); + if (name == PropertyName.@_fieldSingleArray) { + this.@_fieldSingleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<float[]>(value); return true; } - if (name == PropertyName._fieldDoubleArray) { - this._fieldDoubleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<double[]>(value); + if (name == PropertyName.@_fieldDoubleArray) { + this.@_fieldDoubleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<double[]>(value); return true; } - if (name == PropertyName._fieldStringArray) { - this._fieldStringArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); + if (name == PropertyName.@_fieldStringArray) { + this.@_fieldStringArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); return true; } - if (name == PropertyName._fieldStringArrayEnum) { - this._fieldStringArrayEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); + if (name == PropertyName.@_fieldStringArrayEnum) { + this.@_fieldStringArrayEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); return true; } - if (name == PropertyName._fieldVector2Array) { - this._fieldVector2Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2[]>(value); + if (name == PropertyName.@_fieldVector2Array) { + this.@_fieldVector2Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2[]>(value); return true; } - if (name == PropertyName._fieldVector3Array) { - this._fieldVector3Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3[]>(value); + if (name == PropertyName.@_fieldVector3Array) { + this.@_fieldVector3Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3[]>(value); return true; } - if (name == PropertyName._fieldColorArray) { - this._fieldColorArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color[]>(value); + if (name == PropertyName.@_fieldColorArray) { + this.@_fieldColorArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color[]>(value); return true; } - if (name == PropertyName._fieldGodotObjectOrDerivedArray) { - this._fieldGodotObjectOrDerivedArray = global::Godot.NativeInterop.VariantUtils.ConvertToSystemArrayOfGodotObject<global::Godot.GodotObject>(value); + if (name == PropertyName.@_fieldGodotObjectOrDerivedArray) { + this.@_fieldGodotObjectOrDerivedArray = global::Godot.NativeInterop.VariantUtils.ConvertToSystemArrayOfGodotObject<global::Godot.GodotObject>(value); return true; } - if (name == PropertyName._fieldStringNameArray) { - this._fieldStringNameArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName[]>(value); + if (name == PropertyName.@_fieldStringNameArray) { + this.@_fieldStringNameArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName[]>(value); return true; } - if (name == PropertyName._fieldNodePathArray) { - this._fieldNodePathArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath[]>(value); + if (name == PropertyName.@_fieldNodePathArray) { + this.@_fieldNodePathArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath[]>(value); return true; } - if (name == PropertyName._fieldRidArray) { - this._fieldRidArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid[]>(value); + if (name == PropertyName.@_fieldRidArray) { + this.@_fieldRidArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid[]>(value); return true; } - if (name == PropertyName._fieldEmptyInt32Array) { - this._fieldEmptyInt32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); + if (name == PropertyName.@_fieldEmptyInt32Array) { + this.@_fieldEmptyInt32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); return true; } - if (name == PropertyName._fieldArrayFromList) { - this._fieldArrayFromList = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); + if (name == PropertyName.@_fieldArrayFromList) { + this.@_fieldArrayFromList = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); return true; } - if (name == PropertyName._fieldVariant) { - this._fieldVariant = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Variant>(value); + if (name == PropertyName.@_fieldVariant) { + this.@_fieldVariant = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Variant>(value); return true; } - if (name == PropertyName._fieldGodotObjectOrDerived) { - this._fieldGodotObjectOrDerived = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.GodotObject>(value); + if (name == PropertyName.@_fieldGodotObjectOrDerived) { + this.@_fieldGodotObjectOrDerived = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.GodotObject>(value); return true; } - if (name == PropertyName._fieldGodotResourceTexture) { - this._fieldGodotResourceTexture = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Texture>(value); + if (name == PropertyName.@_fieldGodotResourceTexture) { + this.@_fieldGodotResourceTexture = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Texture>(value); return true; } - if (name == PropertyName._fieldStringName) { - this._fieldStringName = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(value); + if (name == PropertyName.@_fieldStringName) { + this.@_fieldStringName = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(value); return true; } - if (name == PropertyName._fieldNodePath) { - this._fieldNodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value); + if (name == PropertyName.@_fieldNodePath) { + this.@_fieldNodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value); return true; } - if (name == PropertyName._fieldRid) { - this._fieldRid = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid>(value); + if (name == PropertyName.@_fieldRid) { + this.@_fieldRid = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid>(value); return true; } - if (name == PropertyName._fieldGodotDictionary) { - this._fieldGodotDictionary = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Dictionary>(value); + if (name == PropertyName.@_fieldGodotDictionary) { + this.@_fieldGodotDictionary = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Dictionary>(value); return true; } - if (name == PropertyName._fieldGodotArray) { - this._fieldGodotArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Array>(value); + if (name == PropertyName.@_fieldGodotArray) { + this.@_fieldGodotArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Array>(value); return true; } - if (name == PropertyName._fieldGodotGenericDictionary) { - this._fieldGodotGenericDictionary = global::Godot.NativeInterop.VariantUtils.ConvertToDictionary<string, bool>(value); + if (name == PropertyName.@_fieldGodotGenericDictionary) { + this.@_fieldGodotGenericDictionary = global::Godot.NativeInterop.VariantUtils.ConvertToDictionary<string, bool>(value); return true; } - if (name == PropertyName._fieldGodotGenericArray) { - this._fieldGodotGenericArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<int>(value); + if (name == PropertyName.@_fieldGodotGenericArray) { + this.@_fieldGodotGenericArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<int>(value); return true; } - if (name == PropertyName._fieldEmptyInt64Array) { - this._fieldEmptyInt64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value); + if (name == PropertyName.@_fieldEmptyInt64Array) { + this.@_fieldEmptyInt64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value); return true; } return base.SetGodotClassPropertyValue(name, value); @@ -499,244 +499,244 @@ partial class ExportedFields [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - if (name == PropertyName._fieldBoolean) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this._fieldBoolean); + if (name == PropertyName.@_fieldBoolean) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.@_fieldBoolean); return true; } - if (name == PropertyName._fieldChar) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<char>(this._fieldChar); + if (name == PropertyName.@_fieldChar) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<char>(this.@_fieldChar); return true; } - if (name == PropertyName._fieldSByte) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<sbyte>(this._fieldSByte); + if (name == PropertyName.@_fieldSByte) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<sbyte>(this.@_fieldSByte); return true; } - if (name == PropertyName._fieldInt16) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<short>(this._fieldInt16); + if (name == PropertyName.@_fieldInt16) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<short>(this.@_fieldInt16); return true; } - if (name == PropertyName._fieldInt32) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this._fieldInt32); + if (name == PropertyName.@_fieldInt32) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@_fieldInt32); return true; } - if (name == PropertyName._fieldInt64) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long>(this._fieldInt64); + if (name == PropertyName.@_fieldInt64) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long>(this.@_fieldInt64); return true; } - if (name == PropertyName._fieldByte) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte>(this._fieldByte); + if (name == PropertyName.@_fieldByte) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte>(this.@_fieldByte); return true; } - if (name == PropertyName._fieldUInt16) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ushort>(this._fieldUInt16); + if (name == PropertyName.@_fieldUInt16) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ushort>(this.@_fieldUInt16); return true; } - if (name == PropertyName._fieldUInt32) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<uint>(this._fieldUInt32); + if (name == PropertyName.@_fieldUInt32) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<uint>(this.@_fieldUInt32); return true; } - if (name == PropertyName._fieldUInt64) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ulong>(this._fieldUInt64); + if (name == PropertyName.@_fieldUInt64) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ulong>(this.@_fieldUInt64); return true; } - if (name == PropertyName._fieldSingle) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float>(this._fieldSingle); + if (name == PropertyName.@_fieldSingle) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float>(this.@_fieldSingle); return true; } - if (name == PropertyName._fieldDouble) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double>(this._fieldDouble); + if (name == PropertyName.@_fieldDouble) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double>(this.@_fieldDouble); return true; } - if (name == PropertyName._fieldString) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._fieldString); + if (name == PropertyName.@_fieldString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@_fieldString); return true; } - if (name == PropertyName._fieldVector2) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2>(this._fieldVector2); + if (name == PropertyName.@_fieldVector2) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2>(this.@_fieldVector2); return true; } - if (name == PropertyName._fieldVector2I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2I>(this._fieldVector2I); + if (name == PropertyName.@_fieldVector2I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2I>(this.@_fieldVector2I); return true; } - if (name == PropertyName._fieldRect2) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2>(this._fieldRect2); + if (name == PropertyName.@_fieldRect2) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2>(this.@_fieldRect2); return true; } - if (name == PropertyName._fieldRect2I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2I>(this._fieldRect2I); + if (name == PropertyName.@_fieldRect2I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2I>(this.@_fieldRect2I); return true; } - if (name == PropertyName._fieldTransform2D) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform2D>(this._fieldTransform2D); + if (name == PropertyName.@_fieldTransform2D) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform2D>(this.@_fieldTransform2D); return true; } - if (name == PropertyName._fieldVector3) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3>(this._fieldVector3); + if (name == PropertyName.@_fieldVector3) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3>(this.@_fieldVector3); return true; } - if (name == PropertyName._fieldVector3I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3I>(this._fieldVector3I); + if (name == PropertyName.@_fieldVector3I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3I>(this.@_fieldVector3I); return true; } - if (name == PropertyName._fieldBasis) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Basis>(this._fieldBasis); + if (name == PropertyName.@_fieldBasis) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Basis>(this.@_fieldBasis); return true; } - if (name == PropertyName._fieldQuaternion) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Quaternion>(this._fieldQuaternion); + if (name == PropertyName.@_fieldQuaternion) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Quaternion>(this.@_fieldQuaternion); return true; } - if (name == PropertyName._fieldTransform3D) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform3D>(this._fieldTransform3D); + if (name == PropertyName.@_fieldTransform3D) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform3D>(this.@_fieldTransform3D); return true; } - if (name == PropertyName._fieldVector4) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4>(this._fieldVector4); + if (name == PropertyName.@_fieldVector4) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4>(this.@_fieldVector4); return true; } - if (name == PropertyName._fieldVector4I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4I>(this._fieldVector4I); + if (name == PropertyName.@_fieldVector4I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4I>(this.@_fieldVector4I); return true; } - if (name == PropertyName._fieldProjection) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Projection>(this._fieldProjection); + if (name == PropertyName.@_fieldProjection) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Projection>(this.@_fieldProjection); return true; } - if (name == PropertyName._fieldAabb) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Aabb>(this._fieldAabb); + if (name == PropertyName.@_fieldAabb) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Aabb>(this.@_fieldAabb); return true; } - if (name == PropertyName._fieldColor) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color>(this._fieldColor); + if (name == PropertyName.@_fieldColor) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color>(this.@_fieldColor); return true; } - if (name == PropertyName._fieldPlane) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Plane>(this._fieldPlane); + if (name == PropertyName.@_fieldPlane) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Plane>(this.@_fieldPlane); return true; } - if (name == PropertyName._fieldCallable) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this._fieldCallable); + if (name == PropertyName.@_fieldCallable) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@_fieldCallable); return true; } - if (name == PropertyName._fieldSignal) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Signal>(this._fieldSignal); + if (name == PropertyName.@_fieldSignal) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Signal>(this.@_fieldSignal); return true; } - if (name == PropertyName._fieldEnum) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedFields.MyEnum>(this._fieldEnum); + if (name == PropertyName.@_fieldEnum) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedFields.MyEnum>(this.@_fieldEnum); return true; } - if (name == PropertyName._fieldFlagsEnum) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedFields.MyFlagsEnum>(this._fieldFlagsEnum); + if (name == PropertyName.@_fieldFlagsEnum) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedFields.MyFlagsEnum>(this.@_fieldFlagsEnum); return true; } - if (name == PropertyName._fieldByteArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte[]>(this._fieldByteArray); + if (name == PropertyName.@_fieldByteArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte[]>(this.@_fieldByteArray); return true; } - if (name == PropertyName._fieldInt32Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this._fieldInt32Array); + if (name == PropertyName.@_fieldInt32Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.@_fieldInt32Array); return true; } - if (name == PropertyName._fieldInt64Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this._fieldInt64Array); + if (name == PropertyName.@_fieldInt64Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this.@_fieldInt64Array); return true; } - if (name == PropertyName._fieldSingleArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float[]>(this._fieldSingleArray); + if (name == PropertyName.@_fieldSingleArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float[]>(this.@_fieldSingleArray); return true; } - if (name == PropertyName._fieldDoubleArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double[]>(this._fieldDoubleArray); + if (name == PropertyName.@_fieldDoubleArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double[]>(this.@_fieldDoubleArray); return true; } - if (name == PropertyName._fieldStringArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this._fieldStringArray); + if (name == PropertyName.@_fieldStringArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.@_fieldStringArray); return true; } - if (name == PropertyName._fieldStringArrayEnum) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this._fieldStringArrayEnum); + if (name == PropertyName.@_fieldStringArrayEnum) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.@_fieldStringArrayEnum); return true; } - if (name == PropertyName._fieldVector2Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2[]>(this._fieldVector2Array); + if (name == PropertyName.@_fieldVector2Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2[]>(this.@_fieldVector2Array); return true; } - if (name == PropertyName._fieldVector3Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3[]>(this._fieldVector3Array); + if (name == PropertyName.@_fieldVector3Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3[]>(this.@_fieldVector3Array); return true; } - if (name == PropertyName._fieldColorArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color[]>(this._fieldColorArray); + if (name == PropertyName.@_fieldColorArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color[]>(this.@_fieldColorArray); return true; } - if (name == PropertyName._fieldGodotObjectOrDerivedArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFromSystemArrayOfGodotObject(this._fieldGodotObjectOrDerivedArray); + if (name == PropertyName.@_fieldGodotObjectOrDerivedArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromSystemArrayOfGodotObject(this.@_fieldGodotObjectOrDerivedArray); return true; } - if (name == PropertyName._fieldStringNameArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName[]>(this._fieldStringNameArray); + if (name == PropertyName.@_fieldStringNameArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName[]>(this.@_fieldStringNameArray); return true; } - if (name == PropertyName._fieldNodePathArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath[]>(this._fieldNodePathArray); + if (name == PropertyName.@_fieldNodePathArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath[]>(this.@_fieldNodePathArray); return true; } - if (name == PropertyName._fieldRidArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid[]>(this._fieldRidArray); + if (name == PropertyName.@_fieldRidArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid[]>(this.@_fieldRidArray); return true; } - if (name == PropertyName._fieldEmptyInt32Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this._fieldEmptyInt32Array); + if (name == PropertyName.@_fieldEmptyInt32Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.@_fieldEmptyInt32Array); return true; } - if (name == PropertyName._fieldArrayFromList) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this._fieldArrayFromList); + if (name == PropertyName.@_fieldArrayFromList) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.@_fieldArrayFromList); return true; } - if (name == PropertyName._fieldVariant) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(this._fieldVariant); + if (name == PropertyName.@_fieldVariant) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(this.@_fieldVariant); return true; } - if (name == PropertyName._fieldGodotObjectOrDerived) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.GodotObject>(this._fieldGodotObjectOrDerived); + if (name == PropertyName.@_fieldGodotObjectOrDerived) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.GodotObject>(this.@_fieldGodotObjectOrDerived); return true; } - if (name == PropertyName._fieldGodotResourceTexture) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Texture>(this._fieldGodotResourceTexture); + if (name == PropertyName.@_fieldGodotResourceTexture) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Texture>(this.@_fieldGodotResourceTexture); return true; } - if (name == PropertyName._fieldStringName) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName>(this._fieldStringName); + if (name == PropertyName.@_fieldStringName) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName>(this.@_fieldStringName); return true; } - if (name == PropertyName._fieldNodePath) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this._fieldNodePath); + if (name == PropertyName.@_fieldNodePath) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this.@_fieldNodePath); return true; } - if (name == PropertyName._fieldRid) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid>(this._fieldRid); + if (name == PropertyName.@_fieldRid) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid>(this.@_fieldRid); return true; } - if (name == PropertyName._fieldGodotDictionary) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Dictionary>(this._fieldGodotDictionary); + if (name == PropertyName.@_fieldGodotDictionary) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Dictionary>(this.@_fieldGodotDictionary); return true; } - if (name == PropertyName._fieldGodotArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Array>(this._fieldGodotArray); + if (name == PropertyName.@_fieldGodotArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Array>(this.@_fieldGodotArray); return true; } - if (name == PropertyName._fieldGodotGenericDictionary) { - value = global::Godot.NativeInterop.VariantUtils.CreateFromDictionary(this._fieldGodotGenericDictionary); + if (name == PropertyName.@_fieldGodotGenericDictionary) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromDictionary(this.@_fieldGodotGenericDictionary); return true; } - if (name == PropertyName._fieldGodotGenericArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this._fieldGodotGenericArray); + if (name == PropertyName.@_fieldGodotGenericArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.@_fieldGodotGenericArray); return true; } - if (name == PropertyName._fieldEmptyInt64Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this._fieldEmptyInt64Array); + if (name == PropertyName.@_fieldEmptyInt64Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this.@_fieldEmptyInt64Array); return true; } return base.GetGodotClassPropertyValue(name, out value); @@ -750,66 +750,66 @@ partial class ExportedFields internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() { var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); - properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName._fieldBoolean, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldChar, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldSByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldUInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldUInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldUInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName._fieldSingle, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName._fieldDouble, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._fieldString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)5, name: PropertyName._fieldVector2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)6, name: PropertyName._fieldVector2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)7, name: PropertyName._fieldRect2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)8, name: PropertyName._fieldRect2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)11, name: PropertyName._fieldTransform2D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)9, name: PropertyName._fieldVector3, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)10, name: PropertyName._fieldVector3I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)17, name: PropertyName._fieldBasis, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)15, name: PropertyName._fieldQuaternion, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)18, name: PropertyName._fieldTransform3D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)12, name: PropertyName._fieldVector4, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)13, name: PropertyName._fieldVector4I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)19, name: PropertyName._fieldProjection, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)16, name: PropertyName._fieldAabb, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)20, name: PropertyName._fieldColor, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)14, name: PropertyName._fieldPlane, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName._fieldCallable, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)26, name: PropertyName._fieldSignal, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldEnum, hint: (global::Godot.PropertyHint)2, hintString: "A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._fieldFlagsEnum, hint: (global::Godot.PropertyHint)6, hintString: "A:0,B:1,C:2", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)29, name: PropertyName._fieldByteArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName._fieldInt32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName._fieldInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)32, name: PropertyName._fieldSingleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)33, name: PropertyName._fieldDoubleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName._fieldStringArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName._fieldStringArrayEnum, hint: (global::Godot.PropertyHint)23, hintString: "4/2:A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)35, name: PropertyName._fieldVector2Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)36, name: PropertyName._fieldVector3Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)37, name: PropertyName._fieldColorArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName._fieldGodotObjectOrDerivedArray, hint: (global::Godot.PropertyHint)23, hintString: "24/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName._fieldStringNameArray, hint: (global::Godot.PropertyHint)23, hintString: "21/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName._fieldNodePathArray, hint: (global::Godot.PropertyHint)23, hintString: "22/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName._fieldRidArray, hint: (global::Godot.PropertyHint)23, hintString: "23/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName._fieldEmptyInt32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName._fieldArrayFromList, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)0, name: PropertyName._fieldVariant, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)135174, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName._fieldGodotObjectOrDerived, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName._fieldGodotResourceTexture, hint: (global::Godot.PropertyHint)17, hintString: "Texture", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)21, name: PropertyName._fieldStringName, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName._fieldNodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName._fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName._fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName._fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName._fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName._fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName._fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.@_fieldBoolean, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldChar, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldSByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldUInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldUInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldUInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.@_fieldSingle, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.@_fieldDouble, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@_fieldString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)5, name: PropertyName.@_fieldVector2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)6, name: PropertyName.@_fieldVector2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)7, name: PropertyName.@_fieldRect2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)8, name: PropertyName.@_fieldRect2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)11, name: PropertyName.@_fieldTransform2D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)9, name: PropertyName.@_fieldVector3, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)10, name: PropertyName.@_fieldVector3I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)17, name: PropertyName.@_fieldBasis, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)15, name: PropertyName.@_fieldQuaternion, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)18, name: PropertyName.@_fieldTransform3D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)12, name: PropertyName.@_fieldVector4, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)13, name: PropertyName.@_fieldVector4I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)19, name: PropertyName.@_fieldProjection, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)16, name: PropertyName.@_fieldAabb, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)20, name: PropertyName.@_fieldColor, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)14, name: PropertyName.@_fieldPlane, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@_fieldCallable, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)26, name: PropertyName.@_fieldSignal, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldEnum, hint: (global::Godot.PropertyHint)2, hintString: "A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_fieldFlagsEnum, hint: (global::Godot.PropertyHint)6, hintString: "A:0,B:1,C:2", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)29, name: PropertyName.@_fieldByteArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.@_fieldInt32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)32, name: PropertyName.@_fieldSingleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)33, name: PropertyName.@_fieldDoubleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.@_fieldStringArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.@_fieldStringArrayEnum, hint: (global::Godot.PropertyHint)23, hintString: "4/2:A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)35, name: PropertyName.@_fieldVector2Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)36, name: PropertyName.@_fieldVector3Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)37, name: PropertyName.@_fieldColorArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotObjectOrDerivedArray, hint: (global::Godot.PropertyHint)23, hintString: "24/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldStringNameArray, hint: (global::Godot.PropertyHint)23, hintString: "21/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldNodePathArray, hint: (global::Godot.PropertyHint)23, hintString: "22/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldRidArray, hint: (global::Godot.PropertyHint)23, hintString: "23/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.@_fieldEmptyInt32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.@_fieldArrayFromList, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)0, name: PropertyName.@_fieldVariant, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)135174, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.@_fieldGodotObjectOrDerived, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.@_fieldGodotResourceTexture, hint: (global::Godot.PropertyHint)17, hintString: "Texture", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)21, name: PropertyName.@_fieldStringName, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName.@_fieldNodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); return properties; } #pragma warning restore CS0109 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs index 367cb073c0..f201b7c8d8 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptPropertyDefVal.generated.cs @@ -13,125 +13,125 @@ partial class ExportedFields { var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(60); bool ___fieldBoolean_default_value = true; - values.Add(PropertyName._fieldBoolean, global::Godot.Variant.From<bool>(___fieldBoolean_default_value)); + values.Add(PropertyName.@_fieldBoolean, global::Godot.Variant.From<bool>(___fieldBoolean_default_value)); char ___fieldChar_default_value = 'f'; - values.Add(PropertyName._fieldChar, global::Godot.Variant.From<char>(___fieldChar_default_value)); + values.Add(PropertyName.@_fieldChar, global::Godot.Variant.From<char>(___fieldChar_default_value)); sbyte ___fieldSByte_default_value = 10; - values.Add(PropertyName._fieldSByte, global::Godot.Variant.From<sbyte>(___fieldSByte_default_value)); + values.Add(PropertyName.@_fieldSByte, global::Godot.Variant.From<sbyte>(___fieldSByte_default_value)); short ___fieldInt16_default_value = 10; - values.Add(PropertyName._fieldInt16, global::Godot.Variant.From<short>(___fieldInt16_default_value)); + values.Add(PropertyName.@_fieldInt16, global::Godot.Variant.From<short>(___fieldInt16_default_value)); int ___fieldInt32_default_value = 10; - values.Add(PropertyName._fieldInt32, global::Godot.Variant.From<int>(___fieldInt32_default_value)); + values.Add(PropertyName.@_fieldInt32, global::Godot.Variant.From<int>(___fieldInt32_default_value)); long ___fieldInt64_default_value = 10; - values.Add(PropertyName._fieldInt64, global::Godot.Variant.From<long>(___fieldInt64_default_value)); + values.Add(PropertyName.@_fieldInt64, global::Godot.Variant.From<long>(___fieldInt64_default_value)); byte ___fieldByte_default_value = 10; - values.Add(PropertyName._fieldByte, global::Godot.Variant.From<byte>(___fieldByte_default_value)); + values.Add(PropertyName.@_fieldByte, global::Godot.Variant.From<byte>(___fieldByte_default_value)); ushort ___fieldUInt16_default_value = 10; - values.Add(PropertyName._fieldUInt16, global::Godot.Variant.From<ushort>(___fieldUInt16_default_value)); + values.Add(PropertyName.@_fieldUInt16, global::Godot.Variant.From<ushort>(___fieldUInt16_default_value)); uint ___fieldUInt32_default_value = 10; - values.Add(PropertyName._fieldUInt32, global::Godot.Variant.From<uint>(___fieldUInt32_default_value)); + values.Add(PropertyName.@_fieldUInt32, global::Godot.Variant.From<uint>(___fieldUInt32_default_value)); ulong ___fieldUInt64_default_value = 10; - values.Add(PropertyName._fieldUInt64, global::Godot.Variant.From<ulong>(___fieldUInt64_default_value)); + values.Add(PropertyName.@_fieldUInt64, global::Godot.Variant.From<ulong>(___fieldUInt64_default_value)); float ___fieldSingle_default_value = 10; - values.Add(PropertyName._fieldSingle, global::Godot.Variant.From<float>(___fieldSingle_default_value)); + values.Add(PropertyName.@_fieldSingle, global::Godot.Variant.From<float>(___fieldSingle_default_value)); double ___fieldDouble_default_value = 10; - values.Add(PropertyName._fieldDouble, global::Godot.Variant.From<double>(___fieldDouble_default_value)); + values.Add(PropertyName.@_fieldDouble, global::Godot.Variant.From<double>(___fieldDouble_default_value)); string ___fieldString_default_value = "foo"; - values.Add(PropertyName._fieldString, global::Godot.Variant.From<string>(___fieldString_default_value)); + values.Add(PropertyName.@_fieldString, global::Godot.Variant.From<string>(___fieldString_default_value)); global::Godot.Vector2 ___fieldVector2_default_value = new(10f, 10f); - values.Add(PropertyName._fieldVector2, global::Godot.Variant.From<global::Godot.Vector2>(___fieldVector2_default_value)); + values.Add(PropertyName.@_fieldVector2, global::Godot.Variant.From<global::Godot.Vector2>(___fieldVector2_default_value)); global::Godot.Vector2I ___fieldVector2I_default_value = global::Godot.Vector2I.Up; - values.Add(PropertyName._fieldVector2I, global::Godot.Variant.From<global::Godot.Vector2I>(___fieldVector2I_default_value)); + values.Add(PropertyName.@_fieldVector2I, global::Godot.Variant.From<global::Godot.Vector2I>(___fieldVector2I_default_value)); global::Godot.Rect2 ___fieldRect2_default_value = new(new global::Godot.Vector2(10f, 10f), new global::Godot.Vector2(10f, 10f)); - values.Add(PropertyName._fieldRect2, global::Godot.Variant.From<global::Godot.Rect2>(___fieldRect2_default_value)); + values.Add(PropertyName.@_fieldRect2, global::Godot.Variant.From<global::Godot.Rect2>(___fieldRect2_default_value)); global::Godot.Rect2I ___fieldRect2I_default_value = new(new global::Godot.Vector2I(10, 10), new global::Godot.Vector2I(10, 10)); - values.Add(PropertyName._fieldRect2I, global::Godot.Variant.From<global::Godot.Rect2I>(___fieldRect2I_default_value)); + values.Add(PropertyName.@_fieldRect2I, global::Godot.Variant.From<global::Godot.Rect2I>(___fieldRect2I_default_value)); global::Godot.Transform2D ___fieldTransform2D_default_value = global::Godot.Transform2D.Identity; - values.Add(PropertyName._fieldTransform2D, global::Godot.Variant.From<global::Godot.Transform2D>(___fieldTransform2D_default_value)); + values.Add(PropertyName.@_fieldTransform2D, global::Godot.Variant.From<global::Godot.Transform2D>(___fieldTransform2D_default_value)); global::Godot.Vector3 ___fieldVector3_default_value = new(10f, 10f, 10f); - values.Add(PropertyName._fieldVector3, global::Godot.Variant.From<global::Godot.Vector3>(___fieldVector3_default_value)); + values.Add(PropertyName.@_fieldVector3, global::Godot.Variant.From<global::Godot.Vector3>(___fieldVector3_default_value)); global::Godot.Vector3I ___fieldVector3I_default_value = global::Godot.Vector3I.Back; - values.Add(PropertyName._fieldVector3I, global::Godot.Variant.From<global::Godot.Vector3I>(___fieldVector3I_default_value)); + values.Add(PropertyName.@_fieldVector3I, global::Godot.Variant.From<global::Godot.Vector3I>(___fieldVector3I_default_value)); global::Godot.Basis ___fieldBasis_default_value = new global::Godot.Basis(global::Godot.Quaternion.Identity); - values.Add(PropertyName._fieldBasis, global::Godot.Variant.From<global::Godot.Basis>(___fieldBasis_default_value)); + values.Add(PropertyName.@_fieldBasis, global::Godot.Variant.From<global::Godot.Basis>(___fieldBasis_default_value)); global::Godot.Quaternion ___fieldQuaternion_default_value = new global::Godot.Quaternion(global::Godot.Basis.Identity); - values.Add(PropertyName._fieldQuaternion, global::Godot.Variant.From<global::Godot.Quaternion>(___fieldQuaternion_default_value)); + values.Add(PropertyName.@_fieldQuaternion, global::Godot.Variant.From<global::Godot.Quaternion>(___fieldQuaternion_default_value)); global::Godot.Transform3D ___fieldTransform3D_default_value = global::Godot.Transform3D.Identity; - values.Add(PropertyName._fieldTransform3D, global::Godot.Variant.From<global::Godot.Transform3D>(___fieldTransform3D_default_value)); + values.Add(PropertyName.@_fieldTransform3D, global::Godot.Variant.From<global::Godot.Transform3D>(___fieldTransform3D_default_value)); global::Godot.Vector4 ___fieldVector4_default_value = new(10f, 10f, 10f, 10f); - values.Add(PropertyName._fieldVector4, global::Godot.Variant.From<global::Godot.Vector4>(___fieldVector4_default_value)); + values.Add(PropertyName.@_fieldVector4, global::Godot.Variant.From<global::Godot.Vector4>(___fieldVector4_default_value)); global::Godot.Vector4I ___fieldVector4I_default_value = global::Godot.Vector4I.One; - values.Add(PropertyName._fieldVector4I, global::Godot.Variant.From<global::Godot.Vector4I>(___fieldVector4I_default_value)); + values.Add(PropertyName.@_fieldVector4I, global::Godot.Variant.From<global::Godot.Vector4I>(___fieldVector4I_default_value)); global::Godot.Projection ___fieldProjection_default_value = global::Godot.Projection.Identity; - values.Add(PropertyName._fieldProjection, global::Godot.Variant.From<global::Godot.Projection>(___fieldProjection_default_value)); + values.Add(PropertyName.@_fieldProjection, global::Godot.Variant.From<global::Godot.Projection>(___fieldProjection_default_value)); global::Godot.Aabb ___fieldAabb_default_value = new global::Godot.Aabb(10f, 10f, 10f, new global::Godot.Vector3(1f, 1f, 1f)); - values.Add(PropertyName._fieldAabb, global::Godot.Variant.From<global::Godot.Aabb>(___fieldAabb_default_value)); + values.Add(PropertyName.@_fieldAabb, global::Godot.Variant.From<global::Godot.Aabb>(___fieldAabb_default_value)); global::Godot.Color ___fieldColor_default_value = global::Godot.Colors.Aquamarine; - values.Add(PropertyName._fieldColor, global::Godot.Variant.From<global::Godot.Color>(___fieldColor_default_value)); + values.Add(PropertyName.@_fieldColor, global::Godot.Variant.From<global::Godot.Color>(___fieldColor_default_value)); global::Godot.Plane ___fieldPlane_default_value = global::Godot.Plane.PlaneXZ; - values.Add(PropertyName._fieldPlane, global::Godot.Variant.From<global::Godot.Plane>(___fieldPlane_default_value)); + values.Add(PropertyName.@_fieldPlane, global::Godot.Variant.From<global::Godot.Plane>(___fieldPlane_default_value)); global::Godot.Callable ___fieldCallable_default_value = new global::Godot.Callable(global::Godot.Engine.GetMainLoop(), "_process"); - values.Add(PropertyName._fieldCallable, global::Godot.Variant.From<global::Godot.Callable>(___fieldCallable_default_value)); + values.Add(PropertyName.@_fieldCallable, global::Godot.Variant.From<global::Godot.Callable>(___fieldCallable_default_value)); global::Godot.Signal ___fieldSignal_default_value = new global::Godot.Signal(global::Godot.Engine.GetMainLoop(), "property_list_changed"); - values.Add(PropertyName._fieldSignal, global::Godot.Variant.From<global::Godot.Signal>(___fieldSignal_default_value)); + values.Add(PropertyName.@_fieldSignal, global::Godot.Variant.From<global::Godot.Signal>(___fieldSignal_default_value)); global::ExportedFields.MyEnum ___fieldEnum_default_value = global::ExportedFields.MyEnum.C; - values.Add(PropertyName._fieldEnum, global::Godot.Variant.From<global::ExportedFields.MyEnum>(___fieldEnum_default_value)); + values.Add(PropertyName.@_fieldEnum, global::Godot.Variant.From<global::ExportedFields.MyEnum>(___fieldEnum_default_value)); global::ExportedFields.MyFlagsEnum ___fieldFlagsEnum_default_value = global::ExportedFields.MyFlagsEnum.C; - values.Add(PropertyName._fieldFlagsEnum, global::Godot.Variant.From<global::ExportedFields.MyFlagsEnum>(___fieldFlagsEnum_default_value)); + values.Add(PropertyName.@_fieldFlagsEnum, global::Godot.Variant.From<global::ExportedFields.MyFlagsEnum>(___fieldFlagsEnum_default_value)); byte[] ___fieldByteArray_default_value = { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName._fieldByteArray, global::Godot.Variant.From<byte[]>(___fieldByteArray_default_value)); + values.Add(PropertyName.@_fieldByteArray, global::Godot.Variant.From<byte[]>(___fieldByteArray_default_value)); int[] ___fieldInt32Array_default_value = { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName._fieldInt32Array, global::Godot.Variant.From<int[]>(___fieldInt32Array_default_value)); + values.Add(PropertyName.@_fieldInt32Array, global::Godot.Variant.From<int[]>(___fieldInt32Array_default_value)); long[] ___fieldInt64Array_default_value = { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName._fieldInt64Array, global::Godot.Variant.From<long[]>(___fieldInt64Array_default_value)); + values.Add(PropertyName.@_fieldInt64Array, global::Godot.Variant.From<long[]>(___fieldInt64Array_default_value)); float[] ___fieldSingleArray_default_value = { 0f, 1f, 2f, 3f, 4f, 5f, 6f }; - values.Add(PropertyName._fieldSingleArray, global::Godot.Variant.From<float[]>(___fieldSingleArray_default_value)); + values.Add(PropertyName.@_fieldSingleArray, global::Godot.Variant.From<float[]>(___fieldSingleArray_default_value)); double[] ___fieldDoubleArray_default_value = { 0d, 1d, 2d, 3d, 4d, 5d, 6d }; - values.Add(PropertyName._fieldDoubleArray, global::Godot.Variant.From<double[]>(___fieldDoubleArray_default_value)); + values.Add(PropertyName.@_fieldDoubleArray, global::Godot.Variant.From<double[]>(___fieldDoubleArray_default_value)); string[] ___fieldStringArray_default_value = { "foo", "bar" }; - values.Add(PropertyName._fieldStringArray, global::Godot.Variant.From<string[]>(___fieldStringArray_default_value)); + values.Add(PropertyName.@_fieldStringArray, global::Godot.Variant.From<string[]>(___fieldStringArray_default_value)); string[] ___fieldStringArrayEnum_default_value = { "foo", "bar" }; - values.Add(PropertyName._fieldStringArrayEnum, global::Godot.Variant.From<string[]>(___fieldStringArrayEnum_default_value)); + values.Add(PropertyName.@_fieldStringArrayEnum, global::Godot.Variant.From<string[]>(___fieldStringArrayEnum_default_value)); global::Godot.Vector2[] ___fieldVector2Array_default_value = { global::Godot.Vector2.Up, global::Godot.Vector2.Down, global::Godot.Vector2.Left, global::Godot.Vector2.Right }; - values.Add(PropertyName._fieldVector2Array, global::Godot.Variant.From<global::Godot.Vector2[]>(___fieldVector2Array_default_value)); + values.Add(PropertyName.@_fieldVector2Array, global::Godot.Variant.From<global::Godot.Vector2[]>(___fieldVector2Array_default_value)); global::Godot.Vector3[] ___fieldVector3Array_default_value = { global::Godot.Vector3.Up, global::Godot.Vector3.Down, global::Godot.Vector3.Left, global::Godot.Vector3.Right }; - values.Add(PropertyName._fieldVector3Array, global::Godot.Variant.From<global::Godot.Vector3[]>(___fieldVector3Array_default_value)); + values.Add(PropertyName.@_fieldVector3Array, global::Godot.Variant.From<global::Godot.Vector3[]>(___fieldVector3Array_default_value)); global::Godot.Color[] ___fieldColorArray_default_value = { global::Godot.Colors.Aqua, global::Godot.Colors.Aquamarine, global::Godot.Colors.Azure, global::Godot.Colors.Beige }; - values.Add(PropertyName._fieldColorArray, global::Godot.Variant.From<global::Godot.Color[]>(___fieldColorArray_default_value)); + values.Add(PropertyName.@_fieldColorArray, global::Godot.Variant.From<global::Godot.Color[]>(___fieldColorArray_default_value)); global::Godot.GodotObject[] ___fieldGodotObjectOrDerivedArray_default_value = { null }; - values.Add(PropertyName._fieldGodotObjectOrDerivedArray, global::Godot.Variant.CreateFrom(___fieldGodotObjectOrDerivedArray_default_value)); + values.Add(PropertyName.@_fieldGodotObjectOrDerivedArray, global::Godot.Variant.CreateFrom(___fieldGodotObjectOrDerivedArray_default_value)); global::Godot.StringName[] ___fieldStringNameArray_default_value = { "foo", "bar" }; - values.Add(PropertyName._fieldStringNameArray, global::Godot.Variant.From<global::Godot.StringName[]>(___fieldStringNameArray_default_value)); + values.Add(PropertyName.@_fieldStringNameArray, global::Godot.Variant.From<global::Godot.StringName[]>(___fieldStringNameArray_default_value)); global::Godot.NodePath[] ___fieldNodePathArray_default_value = { "foo", "bar" }; - values.Add(PropertyName._fieldNodePathArray, global::Godot.Variant.From<global::Godot.NodePath[]>(___fieldNodePathArray_default_value)); + values.Add(PropertyName.@_fieldNodePathArray, global::Godot.Variant.From<global::Godot.NodePath[]>(___fieldNodePathArray_default_value)); global::Godot.Rid[] ___fieldRidArray_default_value = { default, default, default }; - values.Add(PropertyName._fieldRidArray, global::Godot.Variant.From<global::Godot.Rid[]>(___fieldRidArray_default_value)); + values.Add(PropertyName.@_fieldRidArray, global::Godot.Variant.From<global::Godot.Rid[]>(___fieldRidArray_default_value)); int[] ___fieldEmptyInt32Array_default_value = global::System.Array.Empty<int>(); - values.Add(PropertyName._fieldEmptyInt32Array, global::Godot.Variant.From<int[]>(___fieldEmptyInt32Array_default_value)); + values.Add(PropertyName.@_fieldEmptyInt32Array, global::Godot.Variant.From<int[]>(___fieldEmptyInt32Array_default_value)); int[] ___fieldArrayFromList_default_value = new global::System.Collections.Generic.List<int>(global::System.Array.Empty<int>()).ToArray(); - values.Add(PropertyName._fieldArrayFromList, global::Godot.Variant.From<int[]>(___fieldArrayFromList_default_value)); + values.Add(PropertyName.@_fieldArrayFromList, global::Godot.Variant.From<int[]>(___fieldArrayFromList_default_value)); global::Godot.Variant ___fieldVariant_default_value = "foo"; - values.Add(PropertyName._fieldVariant, global::Godot.Variant.From<global::Godot.Variant>(___fieldVariant_default_value)); + values.Add(PropertyName.@_fieldVariant, global::Godot.Variant.From<global::Godot.Variant>(___fieldVariant_default_value)); global::Godot.GodotObject ___fieldGodotObjectOrDerived_default_value = default; - values.Add(PropertyName._fieldGodotObjectOrDerived, global::Godot.Variant.From<global::Godot.GodotObject>(___fieldGodotObjectOrDerived_default_value)); + values.Add(PropertyName.@_fieldGodotObjectOrDerived, global::Godot.Variant.From<global::Godot.GodotObject>(___fieldGodotObjectOrDerived_default_value)); global::Godot.Texture ___fieldGodotResourceTexture_default_value = default; - values.Add(PropertyName._fieldGodotResourceTexture, global::Godot.Variant.From<global::Godot.Texture>(___fieldGodotResourceTexture_default_value)); + values.Add(PropertyName.@_fieldGodotResourceTexture, global::Godot.Variant.From<global::Godot.Texture>(___fieldGodotResourceTexture_default_value)); global::Godot.StringName ___fieldStringName_default_value = new global::Godot.StringName("foo"); - values.Add(PropertyName._fieldStringName, global::Godot.Variant.From<global::Godot.StringName>(___fieldStringName_default_value)); + values.Add(PropertyName.@_fieldStringName, global::Godot.Variant.From<global::Godot.StringName>(___fieldStringName_default_value)); global::Godot.NodePath ___fieldNodePath_default_value = new global::Godot.NodePath("foo"); - values.Add(PropertyName._fieldNodePath, global::Godot.Variant.From<global::Godot.NodePath>(___fieldNodePath_default_value)); + values.Add(PropertyName.@_fieldNodePath, global::Godot.Variant.From<global::Godot.NodePath>(___fieldNodePath_default_value)); global::Godot.Rid ___fieldRid_default_value = default; - values.Add(PropertyName._fieldRid, global::Godot.Variant.From<global::Godot.Rid>(___fieldRid_default_value)); + values.Add(PropertyName.@_fieldRid, global::Godot.Variant.From<global::Godot.Rid>(___fieldRid_default_value)); global::Godot.Collections.Dictionary ___fieldGodotDictionary_default_value = new() { { "foo", 10 }, { global::Godot.Vector2.Up, global::Godot.Colors.Chocolate } }; - values.Add(PropertyName._fieldGodotDictionary, global::Godot.Variant.From<global::Godot.Collections.Dictionary>(___fieldGodotDictionary_default_value)); + values.Add(PropertyName.@_fieldGodotDictionary, global::Godot.Variant.From<global::Godot.Collections.Dictionary>(___fieldGodotDictionary_default_value)); global::Godot.Collections.Array ___fieldGodotArray_default_value = new() { "foo", 10, global::Godot.Vector2.Up, global::Godot.Colors.Chocolate }; - values.Add(PropertyName._fieldGodotArray, global::Godot.Variant.From<global::Godot.Collections.Array>(___fieldGodotArray_default_value)); + values.Add(PropertyName.@_fieldGodotArray, global::Godot.Variant.From<global::Godot.Collections.Array>(___fieldGodotArray_default_value)); global::Godot.Collections.Dictionary<string, bool> ___fieldGodotGenericDictionary_default_value = new() { { "foo", true }, { "bar", false } }; - values.Add(PropertyName._fieldGodotGenericDictionary, global::Godot.Variant.CreateFrom(___fieldGodotGenericDictionary_default_value)); + values.Add(PropertyName.@_fieldGodotGenericDictionary, global::Godot.Variant.CreateFrom(___fieldGodotGenericDictionary_default_value)); global::Godot.Collections.Array<int> ___fieldGodotGenericArray_default_value = new() { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName._fieldGodotGenericArray, global::Godot.Variant.CreateFrom(___fieldGodotGenericArray_default_value)); + values.Add(PropertyName.@_fieldGodotGenericArray, global::Godot.Variant.CreateFrom(___fieldGodotGenericArray_default_value)); long[] ___fieldEmptyInt64Array_default_value = global::System.Array.Empty<long>(); - values.Add(PropertyName._fieldEmptyInt64Array, global::Godot.Variant.From<long[]>(___fieldEmptyInt64Array_default_value)); + values.Add(PropertyName.@_fieldEmptyInt64Array, global::Godot.Variant.From<long[]>(___fieldEmptyInt64Array_default_value)); return values; } #endif // TOOLS diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs index aa876d8d7d..0de840aa34 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs @@ -11,558 +11,558 @@ partial class ExportedProperties /// <summary> /// Cached name for the 'NotGenerateComplexLamdaProperty' property. /// </summary> - public new static readonly global::Godot.StringName NotGenerateComplexLamdaProperty = "NotGenerateComplexLamdaProperty"; + public new static readonly global::Godot.StringName @NotGenerateComplexLamdaProperty = "NotGenerateComplexLamdaProperty"; /// <summary> /// Cached name for the 'NotGenerateLamdaNoFieldProperty' property. /// </summary> - public new static readonly global::Godot.StringName NotGenerateLamdaNoFieldProperty = "NotGenerateLamdaNoFieldProperty"; + public new static readonly global::Godot.StringName @NotGenerateLamdaNoFieldProperty = "NotGenerateLamdaNoFieldProperty"; /// <summary> /// Cached name for the 'NotGenerateComplexReturnProperty' property. /// </summary> - public new static readonly global::Godot.StringName NotGenerateComplexReturnProperty = "NotGenerateComplexReturnProperty"; + public new static readonly global::Godot.StringName @NotGenerateComplexReturnProperty = "NotGenerateComplexReturnProperty"; /// <summary> /// Cached name for the 'NotGenerateReturnsProperty' property. /// </summary> - public new static readonly global::Godot.StringName NotGenerateReturnsProperty = "NotGenerateReturnsProperty"; + public new static readonly global::Godot.StringName @NotGenerateReturnsProperty = "NotGenerateReturnsProperty"; /// <summary> /// Cached name for the 'FullPropertyString' property. /// </summary> - public new static readonly global::Godot.StringName FullPropertyString = "FullPropertyString"; + public new static readonly global::Godot.StringName @FullPropertyString = "FullPropertyString"; /// <summary> /// Cached name for the 'FullPropertyString_Complex' property. /// </summary> - public new static readonly global::Godot.StringName FullPropertyString_Complex = "FullPropertyString_Complex"; + public new static readonly global::Godot.StringName @FullPropertyString_Complex = "FullPropertyString_Complex"; /// <summary> /// Cached name for the 'LamdaPropertyString' property. /// </summary> - public new static readonly global::Godot.StringName LamdaPropertyString = "LamdaPropertyString"; + public new static readonly global::Godot.StringName @LamdaPropertyString = "LamdaPropertyString"; /// <summary> /// Cached name for the 'PropertyBoolean' property. /// </summary> - public new static readonly global::Godot.StringName PropertyBoolean = "PropertyBoolean"; + public new static readonly global::Godot.StringName @PropertyBoolean = "PropertyBoolean"; /// <summary> /// Cached name for the 'PropertyChar' property. /// </summary> - public new static readonly global::Godot.StringName PropertyChar = "PropertyChar"; + public new static readonly global::Godot.StringName @PropertyChar = "PropertyChar"; /// <summary> /// Cached name for the 'PropertySByte' property. /// </summary> - public new static readonly global::Godot.StringName PropertySByte = "PropertySByte"; + public new static readonly global::Godot.StringName @PropertySByte = "PropertySByte"; /// <summary> /// Cached name for the 'PropertyInt16' property. /// </summary> - public new static readonly global::Godot.StringName PropertyInt16 = "PropertyInt16"; + public new static readonly global::Godot.StringName @PropertyInt16 = "PropertyInt16"; /// <summary> /// Cached name for the 'PropertyInt32' property. /// </summary> - public new static readonly global::Godot.StringName PropertyInt32 = "PropertyInt32"; + public new static readonly global::Godot.StringName @PropertyInt32 = "PropertyInt32"; /// <summary> /// Cached name for the 'PropertyInt64' property. /// </summary> - public new static readonly global::Godot.StringName PropertyInt64 = "PropertyInt64"; + public new static readonly global::Godot.StringName @PropertyInt64 = "PropertyInt64"; /// <summary> /// Cached name for the 'PropertyByte' property. /// </summary> - public new static readonly global::Godot.StringName PropertyByte = "PropertyByte"; + public new static readonly global::Godot.StringName @PropertyByte = "PropertyByte"; /// <summary> /// Cached name for the 'PropertyUInt16' property. /// </summary> - public new static readonly global::Godot.StringName PropertyUInt16 = "PropertyUInt16"; + public new static readonly global::Godot.StringName @PropertyUInt16 = "PropertyUInt16"; /// <summary> /// Cached name for the 'PropertyUInt32' property. /// </summary> - public new static readonly global::Godot.StringName PropertyUInt32 = "PropertyUInt32"; + public new static readonly global::Godot.StringName @PropertyUInt32 = "PropertyUInt32"; /// <summary> /// Cached name for the 'PropertyUInt64' property. /// </summary> - public new static readonly global::Godot.StringName PropertyUInt64 = "PropertyUInt64"; + public new static readonly global::Godot.StringName @PropertyUInt64 = "PropertyUInt64"; /// <summary> /// Cached name for the 'PropertySingle' property. /// </summary> - public new static readonly global::Godot.StringName PropertySingle = "PropertySingle"; + public new static readonly global::Godot.StringName @PropertySingle = "PropertySingle"; /// <summary> /// Cached name for the 'PropertyDouble' property. /// </summary> - public new static readonly global::Godot.StringName PropertyDouble = "PropertyDouble"; + public new static readonly global::Godot.StringName @PropertyDouble = "PropertyDouble"; /// <summary> /// Cached name for the 'PropertyString' property. /// </summary> - public new static readonly global::Godot.StringName PropertyString = "PropertyString"; + public new static readonly global::Godot.StringName @PropertyString = "PropertyString"; /// <summary> /// Cached name for the 'PropertyVector2' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector2 = "PropertyVector2"; + public new static readonly global::Godot.StringName @PropertyVector2 = "PropertyVector2"; /// <summary> /// Cached name for the 'PropertyVector2I' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector2I = "PropertyVector2I"; + public new static readonly global::Godot.StringName @PropertyVector2I = "PropertyVector2I"; /// <summary> /// Cached name for the 'PropertyRect2' property. /// </summary> - public new static readonly global::Godot.StringName PropertyRect2 = "PropertyRect2"; + public new static readonly global::Godot.StringName @PropertyRect2 = "PropertyRect2"; /// <summary> /// Cached name for the 'PropertyRect2I' property. /// </summary> - public new static readonly global::Godot.StringName PropertyRect2I = "PropertyRect2I"; + public new static readonly global::Godot.StringName @PropertyRect2I = "PropertyRect2I"; /// <summary> /// Cached name for the 'PropertyTransform2D' property. /// </summary> - public new static readonly global::Godot.StringName PropertyTransform2D = "PropertyTransform2D"; + public new static readonly global::Godot.StringName @PropertyTransform2D = "PropertyTransform2D"; /// <summary> /// Cached name for the 'PropertyVector3' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector3 = "PropertyVector3"; + public new static readonly global::Godot.StringName @PropertyVector3 = "PropertyVector3"; /// <summary> /// Cached name for the 'PropertyVector3I' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector3I = "PropertyVector3I"; + public new static readonly global::Godot.StringName @PropertyVector3I = "PropertyVector3I"; /// <summary> /// Cached name for the 'PropertyBasis' property. /// </summary> - public new static readonly global::Godot.StringName PropertyBasis = "PropertyBasis"; + public new static readonly global::Godot.StringName @PropertyBasis = "PropertyBasis"; /// <summary> /// Cached name for the 'PropertyQuaternion' property. /// </summary> - public new static readonly global::Godot.StringName PropertyQuaternion = "PropertyQuaternion"; + public new static readonly global::Godot.StringName @PropertyQuaternion = "PropertyQuaternion"; /// <summary> /// Cached name for the 'PropertyTransform3D' property. /// </summary> - public new static readonly global::Godot.StringName PropertyTransform3D = "PropertyTransform3D"; + public new static readonly global::Godot.StringName @PropertyTransform3D = "PropertyTransform3D"; /// <summary> /// Cached name for the 'PropertyVector4' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector4 = "PropertyVector4"; + public new static readonly global::Godot.StringName @PropertyVector4 = "PropertyVector4"; /// <summary> /// Cached name for the 'PropertyVector4I' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector4I = "PropertyVector4I"; + public new static readonly global::Godot.StringName @PropertyVector4I = "PropertyVector4I"; /// <summary> /// Cached name for the 'PropertyProjection' property. /// </summary> - public new static readonly global::Godot.StringName PropertyProjection = "PropertyProjection"; + public new static readonly global::Godot.StringName @PropertyProjection = "PropertyProjection"; /// <summary> /// Cached name for the 'PropertyAabb' property. /// </summary> - public new static readonly global::Godot.StringName PropertyAabb = "PropertyAabb"; + public new static readonly global::Godot.StringName @PropertyAabb = "PropertyAabb"; /// <summary> /// Cached name for the 'PropertyColor' property. /// </summary> - public new static readonly global::Godot.StringName PropertyColor = "PropertyColor"; + public new static readonly global::Godot.StringName @PropertyColor = "PropertyColor"; /// <summary> /// Cached name for the 'PropertyPlane' property. /// </summary> - public new static readonly global::Godot.StringName PropertyPlane = "PropertyPlane"; + public new static readonly global::Godot.StringName @PropertyPlane = "PropertyPlane"; /// <summary> /// Cached name for the 'PropertyCallable' property. /// </summary> - public new static readonly global::Godot.StringName PropertyCallable = "PropertyCallable"; + public new static readonly global::Godot.StringName @PropertyCallable = "PropertyCallable"; /// <summary> /// Cached name for the 'PropertySignal' property. /// </summary> - public new static readonly global::Godot.StringName PropertySignal = "PropertySignal"; + public new static readonly global::Godot.StringName @PropertySignal = "PropertySignal"; /// <summary> /// Cached name for the 'PropertyEnum' property. /// </summary> - public new static readonly global::Godot.StringName PropertyEnum = "PropertyEnum"; + public new static readonly global::Godot.StringName @PropertyEnum = "PropertyEnum"; /// <summary> /// Cached name for the 'PropertyFlagsEnum' property. /// </summary> - public new static readonly global::Godot.StringName PropertyFlagsEnum = "PropertyFlagsEnum"; + public new static readonly global::Godot.StringName @PropertyFlagsEnum = "PropertyFlagsEnum"; /// <summary> /// Cached name for the 'PropertyByteArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertyByteArray = "PropertyByteArray"; + public new static readonly global::Godot.StringName @PropertyByteArray = "PropertyByteArray"; /// <summary> /// Cached name for the 'PropertyInt32Array' property. /// </summary> - public new static readonly global::Godot.StringName PropertyInt32Array = "PropertyInt32Array"; + public new static readonly global::Godot.StringName @PropertyInt32Array = "PropertyInt32Array"; /// <summary> /// Cached name for the 'PropertyInt64Array' property. /// </summary> - public new static readonly global::Godot.StringName PropertyInt64Array = "PropertyInt64Array"; + public new static readonly global::Godot.StringName @PropertyInt64Array = "PropertyInt64Array"; /// <summary> /// Cached name for the 'PropertySingleArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertySingleArray = "PropertySingleArray"; + public new static readonly global::Godot.StringName @PropertySingleArray = "PropertySingleArray"; /// <summary> /// Cached name for the 'PropertyDoubleArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertyDoubleArray = "PropertyDoubleArray"; + public new static readonly global::Godot.StringName @PropertyDoubleArray = "PropertyDoubleArray"; /// <summary> /// Cached name for the 'PropertyStringArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertyStringArray = "PropertyStringArray"; + public new static readonly global::Godot.StringName @PropertyStringArray = "PropertyStringArray"; /// <summary> /// Cached name for the 'PropertyStringArrayEnum' property. /// </summary> - public new static readonly global::Godot.StringName PropertyStringArrayEnum = "PropertyStringArrayEnum"; + public new static readonly global::Godot.StringName @PropertyStringArrayEnum = "PropertyStringArrayEnum"; /// <summary> /// Cached name for the 'PropertyVector2Array' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector2Array = "PropertyVector2Array"; + public new static readonly global::Godot.StringName @PropertyVector2Array = "PropertyVector2Array"; /// <summary> /// Cached name for the 'PropertyVector3Array' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVector3Array = "PropertyVector3Array"; + public new static readonly global::Godot.StringName @PropertyVector3Array = "PropertyVector3Array"; /// <summary> /// Cached name for the 'PropertyColorArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertyColorArray = "PropertyColorArray"; + public new static readonly global::Godot.StringName @PropertyColorArray = "PropertyColorArray"; /// <summary> /// Cached name for the 'PropertyGodotObjectOrDerivedArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertyGodotObjectOrDerivedArray = "PropertyGodotObjectOrDerivedArray"; + public new static readonly global::Godot.StringName @PropertyGodotObjectOrDerivedArray = "PropertyGodotObjectOrDerivedArray"; /// <summary> /// Cached name for the 'field_StringNameArray' property. /// </summary> - public new static readonly global::Godot.StringName field_StringNameArray = "field_StringNameArray"; + public new static readonly global::Godot.StringName @field_StringNameArray = "field_StringNameArray"; /// <summary> /// Cached name for the 'field_NodePathArray' property. /// </summary> - public new static readonly global::Godot.StringName field_NodePathArray = "field_NodePathArray"; + public new static readonly global::Godot.StringName @field_NodePathArray = "field_NodePathArray"; /// <summary> /// Cached name for the 'field_RidArray' property. /// </summary> - public new static readonly global::Godot.StringName field_RidArray = "field_RidArray"; + public new static readonly global::Godot.StringName @field_RidArray = "field_RidArray"; /// <summary> /// Cached name for the 'PropertyVariant' property. /// </summary> - public new static readonly global::Godot.StringName PropertyVariant = "PropertyVariant"; + public new static readonly global::Godot.StringName @PropertyVariant = "PropertyVariant"; /// <summary> /// Cached name for the 'PropertyGodotObjectOrDerived' property. /// </summary> - public new static readonly global::Godot.StringName PropertyGodotObjectOrDerived = "PropertyGodotObjectOrDerived"; + public new static readonly global::Godot.StringName @PropertyGodotObjectOrDerived = "PropertyGodotObjectOrDerived"; /// <summary> /// Cached name for the 'PropertyGodotResourceTexture' property. /// </summary> - public new static readonly global::Godot.StringName PropertyGodotResourceTexture = "PropertyGodotResourceTexture"; + public new static readonly global::Godot.StringName @PropertyGodotResourceTexture = "PropertyGodotResourceTexture"; /// <summary> /// Cached name for the 'PropertyStringName' property. /// </summary> - public new static readonly global::Godot.StringName PropertyStringName = "PropertyStringName"; + public new static readonly global::Godot.StringName @PropertyStringName = "PropertyStringName"; /// <summary> /// Cached name for the 'PropertyNodePath' property. /// </summary> - public new static readonly global::Godot.StringName PropertyNodePath = "PropertyNodePath"; + public new static readonly global::Godot.StringName @PropertyNodePath = "PropertyNodePath"; /// <summary> /// Cached name for the 'PropertyRid' property. /// </summary> - public new static readonly global::Godot.StringName PropertyRid = "PropertyRid"; + public new static readonly global::Godot.StringName @PropertyRid = "PropertyRid"; /// <summary> /// Cached name for the 'PropertyGodotDictionary' property. /// </summary> - public new static readonly global::Godot.StringName PropertyGodotDictionary = "PropertyGodotDictionary"; + public new static readonly global::Godot.StringName @PropertyGodotDictionary = "PropertyGodotDictionary"; /// <summary> /// Cached name for the 'PropertyGodotArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertyGodotArray = "PropertyGodotArray"; + public new static readonly global::Godot.StringName @PropertyGodotArray = "PropertyGodotArray"; /// <summary> /// Cached name for the 'PropertyGodotGenericDictionary' property. /// </summary> - public new static readonly global::Godot.StringName PropertyGodotGenericDictionary = "PropertyGodotGenericDictionary"; + public new static readonly global::Godot.StringName @PropertyGodotGenericDictionary = "PropertyGodotGenericDictionary"; /// <summary> /// Cached name for the 'PropertyGodotGenericArray' property. /// </summary> - public new static readonly global::Godot.StringName PropertyGodotGenericArray = "PropertyGodotGenericArray"; + public new static readonly global::Godot.StringName @PropertyGodotGenericArray = "PropertyGodotGenericArray"; /// <summary> /// Cached name for the '_notGeneratePropertyString' field. /// </summary> - public new static readonly global::Godot.StringName _notGeneratePropertyString = "_notGeneratePropertyString"; + public new static readonly global::Godot.StringName @_notGeneratePropertyString = "_notGeneratePropertyString"; /// <summary> /// Cached name for the '_notGeneratePropertyInt' field. /// </summary> - public new static readonly global::Godot.StringName _notGeneratePropertyInt = "_notGeneratePropertyInt"; + public new static readonly global::Godot.StringName @_notGeneratePropertyInt = "_notGeneratePropertyInt"; /// <summary> /// Cached name for the '_fullPropertyString' field. /// </summary> - public new static readonly global::Godot.StringName _fullPropertyString = "_fullPropertyString"; + public new static readonly global::Godot.StringName @_fullPropertyString = "_fullPropertyString"; /// <summary> /// Cached name for the '_fullPropertyStringComplex' field. /// </summary> - public new static readonly global::Godot.StringName _fullPropertyStringComplex = "_fullPropertyStringComplex"; + public new static readonly global::Godot.StringName @_fullPropertyStringComplex = "_fullPropertyStringComplex"; /// <summary> /// Cached name for the '_lamdaPropertyString' field. /// </summary> - public new static readonly global::Godot.StringName _lamdaPropertyString = "_lamdaPropertyString"; + public new static readonly global::Godot.StringName @_lamdaPropertyString = "_lamdaPropertyString"; } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { - if (name == PropertyName.NotGenerateComplexLamdaProperty) { - this.NotGenerateComplexLamdaProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@NotGenerateComplexLamdaProperty) { + this.@NotGenerateComplexLamdaProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.NotGenerateLamdaNoFieldProperty) { - this.NotGenerateLamdaNoFieldProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@NotGenerateLamdaNoFieldProperty) { + this.@NotGenerateLamdaNoFieldProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.NotGenerateComplexReturnProperty) { - this.NotGenerateComplexReturnProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@NotGenerateComplexReturnProperty) { + this.@NotGenerateComplexReturnProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.NotGenerateReturnsProperty) { - this.NotGenerateReturnsProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@NotGenerateReturnsProperty) { + this.@NotGenerateReturnsProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.FullPropertyString) { - this.FullPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@FullPropertyString) { + this.@FullPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.FullPropertyString_Complex) { - this.FullPropertyString_Complex = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@FullPropertyString_Complex) { + this.@FullPropertyString_Complex = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.LamdaPropertyString) { - this.LamdaPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@LamdaPropertyString) { + this.@LamdaPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.PropertyBoolean) { - this.PropertyBoolean = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); + if (name == PropertyName.@PropertyBoolean) { + this.@PropertyBoolean = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); return true; } - if (name == PropertyName.PropertyChar) { - this.PropertyChar = global::Godot.NativeInterop.VariantUtils.ConvertTo<char>(value); + if (name == PropertyName.@PropertyChar) { + this.@PropertyChar = global::Godot.NativeInterop.VariantUtils.ConvertTo<char>(value); return true; } - if (name == PropertyName.PropertySByte) { - this.PropertySByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<sbyte>(value); + if (name == PropertyName.@PropertySByte) { + this.@PropertySByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<sbyte>(value); return true; } - if (name == PropertyName.PropertyInt16) { - this.PropertyInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<short>(value); + if (name == PropertyName.@PropertyInt16) { + this.@PropertyInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<short>(value); return true; } - if (name == PropertyName.PropertyInt32) { - this.PropertyInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); + if (name == PropertyName.@PropertyInt32) { + this.@PropertyInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); return true; } - if (name == PropertyName.PropertyInt64) { - this.PropertyInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<long>(value); + if (name == PropertyName.@PropertyInt64) { + this.@PropertyInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<long>(value); return true; } - if (name == PropertyName.PropertyByte) { - this.PropertyByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte>(value); + if (name == PropertyName.@PropertyByte) { + this.@PropertyByte = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte>(value); return true; } - if (name == PropertyName.PropertyUInt16) { - this.PropertyUInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ushort>(value); + if (name == PropertyName.@PropertyUInt16) { + this.@PropertyUInt16 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ushort>(value); return true; } - if (name == PropertyName.PropertyUInt32) { - this.PropertyUInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<uint>(value); + if (name == PropertyName.@PropertyUInt32) { + this.@PropertyUInt32 = global::Godot.NativeInterop.VariantUtils.ConvertTo<uint>(value); return true; } - if (name == PropertyName.PropertyUInt64) { - this.PropertyUInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ulong>(value); + if (name == PropertyName.@PropertyUInt64) { + this.@PropertyUInt64 = global::Godot.NativeInterop.VariantUtils.ConvertTo<ulong>(value); return true; } - if (name == PropertyName.PropertySingle) { - this.PropertySingle = global::Godot.NativeInterop.VariantUtils.ConvertTo<float>(value); + if (name == PropertyName.@PropertySingle) { + this.@PropertySingle = global::Godot.NativeInterop.VariantUtils.ConvertTo<float>(value); return true; } - if (name == PropertyName.PropertyDouble) { - this.PropertyDouble = global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(value); + if (name == PropertyName.@PropertyDouble) { + this.@PropertyDouble = global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(value); return true; } - if (name == PropertyName.PropertyString) { - this.PropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@PropertyString) { + this.@PropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName.PropertyVector2) { - this.PropertyVector2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2>(value); + if (name == PropertyName.@PropertyVector2) { + this.@PropertyVector2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2>(value); return true; } - if (name == PropertyName.PropertyVector2I) { - this.PropertyVector2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2I>(value); + if (name == PropertyName.@PropertyVector2I) { + this.@PropertyVector2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2I>(value); return true; } - if (name == PropertyName.PropertyRect2) { - this.PropertyRect2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2>(value); + if (name == PropertyName.@PropertyRect2) { + this.@PropertyRect2 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2>(value); return true; } - if (name == PropertyName.PropertyRect2I) { - this.PropertyRect2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2I>(value); + if (name == PropertyName.@PropertyRect2I) { + this.@PropertyRect2I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rect2I>(value); return true; } - if (name == PropertyName.PropertyTransform2D) { - this.PropertyTransform2D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform2D>(value); + if (name == PropertyName.@PropertyTransform2D) { + this.@PropertyTransform2D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform2D>(value); return true; } - if (name == PropertyName.PropertyVector3) { - this.PropertyVector3 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3>(value); + if (name == PropertyName.@PropertyVector3) { + this.@PropertyVector3 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3>(value); return true; } - if (name == PropertyName.PropertyVector3I) { - this.PropertyVector3I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3I>(value); + if (name == PropertyName.@PropertyVector3I) { + this.@PropertyVector3I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3I>(value); return true; } - if (name == PropertyName.PropertyBasis) { - this.PropertyBasis = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Basis>(value); + if (name == PropertyName.@PropertyBasis) { + this.@PropertyBasis = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Basis>(value); return true; } - if (name == PropertyName.PropertyQuaternion) { - this.PropertyQuaternion = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Quaternion>(value); + if (name == PropertyName.@PropertyQuaternion) { + this.@PropertyQuaternion = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Quaternion>(value); return true; } - if (name == PropertyName.PropertyTransform3D) { - this.PropertyTransform3D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform3D>(value); + if (name == PropertyName.@PropertyTransform3D) { + this.@PropertyTransform3D = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Transform3D>(value); return true; } - if (name == PropertyName.PropertyVector4) { - this.PropertyVector4 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4>(value); + if (name == PropertyName.@PropertyVector4) { + this.@PropertyVector4 = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4>(value); return true; } - if (name == PropertyName.PropertyVector4I) { - this.PropertyVector4I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4I>(value); + if (name == PropertyName.@PropertyVector4I) { + this.@PropertyVector4I = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector4I>(value); return true; } - if (name == PropertyName.PropertyProjection) { - this.PropertyProjection = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Projection>(value); + if (name == PropertyName.@PropertyProjection) { + this.@PropertyProjection = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Projection>(value); return true; } - if (name == PropertyName.PropertyAabb) { - this.PropertyAabb = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Aabb>(value); + if (name == PropertyName.@PropertyAabb) { + this.@PropertyAabb = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Aabb>(value); return true; } - if (name == PropertyName.PropertyColor) { - this.PropertyColor = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color>(value); + if (name == PropertyName.@PropertyColor) { + this.@PropertyColor = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color>(value); return true; } - if (name == PropertyName.PropertyPlane) { - this.PropertyPlane = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Plane>(value); + if (name == PropertyName.@PropertyPlane) { + this.@PropertyPlane = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Plane>(value); return true; } - if (name == PropertyName.PropertyCallable) { - this.PropertyCallable = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); + if (name == PropertyName.@PropertyCallable) { + this.@PropertyCallable = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); return true; } - if (name == PropertyName.PropertySignal) { - this.PropertySignal = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Signal>(value); + if (name == PropertyName.@PropertySignal) { + this.@PropertySignal = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Signal>(value); return true; } - if (name == PropertyName.PropertyEnum) { - this.PropertyEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedProperties.MyEnum>(value); + if (name == PropertyName.@PropertyEnum) { + this.@PropertyEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedProperties.MyEnum>(value); return true; } - if (name == PropertyName.PropertyFlagsEnum) { - this.PropertyFlagsEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedProperties.MyFlagsEnum>(value); + if (name == PropertyName.@PropertyFlagsEnum) { + this.@PropertyFlagsEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::ExportedProperties.MyFlagsEnum>(value); return true; } - if (name == PropertyName.PropertyByteArray) { - this.PropertyByteArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte[]>(value); + if (name == PropertyName.@PropertyByteArray) { + this.@PropertyByteArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<byte[]>(value); return true; } - if (name == PropertyName.PropertyInt32Array) { - this.PropertyInt32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); + if (name == PropertyName.@PropertyInt32Array) { + this.@PropertyInt32Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<int[]>(value); return true; } - if (name == PropertyName.PropertyInt64Array) { - this.PropertyInt64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value); + if (name == PropertyName.@PropertyInt64Array) { + this.@PropertyInt64Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<long[]>(value); return true; } - if (name == PropertyName.PropertySingleArray) { - this.PropertySingleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<float[]>(value); + if (name == PropertyName.@PropertySingleArray) { + this.@PropertySingleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<float[]>(value); return true; } - if (name == PropertyName.PropertyDoubleArray) { - this.PropertyDoubleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<double[]>(value); + if (name == PropertyName.@PropertyDoubleArray) { + this.@PropertyDoubleArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<double[]>(value); return true; } - if (name == PropertyName.PropertyStringArray) { - this.PropertyStringArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); + if (name == PropertyName.@PropertyStringArray) { + this.@PropertyStringArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); return true; } - if (name == PropertyName.PropertyStringArrayEnum) { - this.PropertyStringArrayEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); + if (name == PropertyName.@PropertyStringArrayEnum) { + this.@PropertyStringArrayEnum = global::Godot.NativeInterop.VariantUtils.ConvertTo<string[]>(value); return true; } - if (name == PropertyName.PropertyVector2Array) { - this.PropertyVector2Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2[]>(value); + if (name == PropertyName.@PropertyVector2Array) { + this.@PropertyVector2Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector2[]>(value); return true; } - if (name == PropertyName.PropertyVector3Array) { - this.PropertyVector3Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3[]>(value); + if (name == PropertyName.@PropertyVector3Array) { + this.@PropertyVector3Array = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Vector3[]>(value); return true; } - if (name == PropertyName.PropertyColorArray) { - this.PropertyColorArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color[]>(value); + if (name == PropertyName.@PropertyColorArray) { + this.@PropertyColorArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Color[]>(value); return true; } - if (name == PropertyName.PropertyGodotObjectOrDerivedArray) { - this.PropertyGodotObjectOrDerivedArray = global::Godot.NativeInterop.VariantUtils.ConvertToSystemArrayOfGodotObject<global::Godot.GodotObject>(value); + if (name == PropertyName.@PropertyGodotObjectOrDerivedArray) { + this.@PropertyGodotObjectOrDerivedArray = global::Godot.NativeInterop.VariantUtils.ConvertToSystemArrayOfGodotObject<global::Godot.GodotObject>(value); return true; } - if (name == PropertyName.field_StringNameArray) { - this.field_StringNameArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName[]>(value); + if (name == PropertyName.@field_StringNameArray) { + this.@field_StringNameArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName[]>(value); return true; } - if (name == PropertyName.field_NodePathArray) { - this.field_NodePathArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath[]>(value); + if (name == PropertyName.@field_NodePathArray) { + this.@field_NodePathArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath[]>(value); return true; } - if (name == PropertyName.field_RidArray) { - this.field_RidArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid[]>(value); + if (name == PropertyName.@field_RidArray) { + this.@field_RidArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid[]>(value); return true; } - if (name == PropertyName.PropertyVariant) { - this.PropertyVariant = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Variant>(value); + if (name == PropertyName.@PropertyVariant) { + this.@PropertyVariant = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Variant>(value); return true; } - if (name == PropertyName.PropertyGodotObjectOrDerived) { - this.PropertyGodotObjectOrDerived = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.GodotObject>(value); + if (name == PropertyName.@PropertyGodotObjectOrDerived) { + this.@PropertyGodotObjectOrDerived = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.GodotObject>(value); return true; } - if (name == PropertyName.PropertyGodotResourceTexture) { - this.PropertyGodotResourceTexture = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Texture>(value); + if (name == PropertyName.@PropertyGodotResourceTexture) { + this.@PropertyGodotResourceTexture = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Texture>(value); return true; } - if (name == PropertyName.PropertyStringName) { - this.PropertyStringName = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(value); + if (name == PropertyName.@PropertyStringName) { + this.@PropertyStringName = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(value); return true; } - if (name == PropertyName.PropertyNodePath) { - this.PropertyNodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value); + if (name == PropertyName.@PropertyNodePath) { + this.@PropertyNodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value); return true; } - if (name == PropertyName.PropertyRid) { - this.PropertyRid = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid>(value); + if (name == PropertyName.@PropertyRid) { + this.@PropertyRid = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Rid>(value); return true; } - if (name == PropertyName.PropertyGodotDictionary) { - this.PropertyGodotDictionary = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Dictionary>(value); + if (name == PropertyName.@PropertyGodotDictionary) { + this.@PropertyGodotDictionary = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Dictionary>(value); return true; } - if (name == PropertyName.PropertyGodotArray) { - this.PropertyGodotArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Array>(value); + if (name == PropertyName.@PropertyGodotArray) { + this.@PropertyGodotArray = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Collections.Array>(value); return true; } - if (name == PropertyName.PropertyGodotGenericDictionary) { - this.PropertyGodotGenericDictionary = global::Godot.NativeInterop.VariantUtils.ConvertToDictionary<string, bool>(value); + if (name == PropertyName.@PropertyGodotGenericDictionary) { + this.@PropertyGodotGenericDictionary = global::Godot.NativeInterop.VariantUtils.ConvertToDictionary<string, bool>(value); return true; } - if (name == PropertyName.PropertyGodotGenericArray) { - this.PropertyGodotGenericArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<int>(value); + if (name == PropertyName.@PropertyGodotGenericArray) { + this.@PropertyGodotGenericArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<int>(value); return true; } - if (name == PropertyName._notGeneratePropertyString) { - this._notGeneratePropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@_notGeneratePropertyString) { + this.@_notGeneratePropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName._notGeneratePropertyInt) { - this._notGeneratePropertyInt = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); + if (name == PropertyName.@_notGeneratePropertyInt) { + this.@_notGeneratePropertyInt = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); return true; } - if (name == PropertyName._fullPropertyString) { - this._fullPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@_fullPropertyString) { + this.@_fullPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName._fullPropertyStringComplex) { - this._fullPropertyStringComplex = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@_fullPropertyStringComplex) { + this.@_fullPropertyStringComplex = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } - if (name == PropertyName._lamdaPropertyString) { - this._lamdaPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + if (name == PropertyName.@_lamdaPropertyString) { + this.@_lamdaPropertyString = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); return true; } return base.SetGodotClassPropertyValue(name, value); @@ -571,280 +571,280 @@ partial class ExportedProperties [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - if (name == PropertyName.NotGenerateComplexLamdaProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerateComplexLamdaProperty); + if (name == PropertyName.@NotGenerateComplexLamdaProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@NotGenerateComplexLamdaProperty); return true; } - if (name == PropertyName.NotGenerateLamdaNoFieldProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerateLamdaNoFieldProperty); + if (name == PropertyName.@NotGenerateLamdaNoFieldProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@NotGenerateLamdaNoFieldProperty); return true; } - if (name == PropertyName.NotGenerateComplexReturnProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerateComplexReturnProperty); + if (name == PropertyName.@NotGenerateComplexReturnProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@NotGenerateComplexReturnProperty); return true; } - if (name == PropertyName.NotGenerateReturnsProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.NotGenerateReturnsProperty); + if (name == PropertyName.@NotGenerateReturnsProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@NotGenerateReturnsProperty); return true; } - if (name == PropertyName.FullPropertyString) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.FullPropertyString); + if (name == PropertyName.@FullPropertyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@FullPropertyString); return true; } - if (name == PropertyName.FullPropertyString_Complex) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.FullPropertyString_Complex); + if (name == PropertyName.@FullPropertyString_Complex) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@FullPropertyString_Complex); return true; } - if (name == PropertyName.LamdaPropertyString) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.LamdaPropertyString); + if (name == PropertyName.@LamdaPropertyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@LamdaPropertyString); return true; } - if (name == PropertyName.PropertyBoolean) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.PropertyBoolean); + if (name == PropertyName.@PropertyBoolean) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.@PropertyBoolean); return true; } - if (name == PropertyName.PropertyChar) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<char>(this.PropertyChar); + if (name == PropertyName.@PropertyChar) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<char>(this.@PropertyChar); return true; } - if (name == PropertyName.PropertySByte) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<sbyte>(this.PropertySByte); + if (name == PropertyName.@PropertySByte) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<sbyte>(this.@PropertySByte); return true; } - if (name == PropertyName.PropertyInt16) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<short>(this.PropertyInt16); + if (name == PropertyName.@PropertyInt16) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<short>(this.@PropertyInt16); return true; } - if (name == PropertyName.PropertyInt32) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.PropertyInt32); + if (name == PropertyName.@PropertyInt32) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@PropertyInt32); return true; } - if (name == PropertyName.PropertyInt64) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long>(this.PropertyInt64); + if (name == PropertyName.@PropertyInt64) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long>(this.@PropertyInt64); return true; } - if (name == PropertyName.PropertyByte) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte>(this.PropertyByte); + if (name == PropertyName.@PropertyByte) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte>(this.@PropertyByte); return true; } - if (name == PropertyName.PropertyUInt16) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ushort>(this.PropertyUInt16); + if (name == PropertyName.@PropertyUInt16) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ushort>(this.@PropertyUInt16); return true; } - if (name == PropertyName.PropertyUInt32) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<uint>(this.PropertyUInt32); + if (name == PropertyName.@PropertyUInt32) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<uint>(this.@PropertyUInt32); return true; } - if (name == PropertyName.PropertyUInt64) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ulong>(this.PropertyUInt64); + if (name == PropertyName.@PropertyUInt64) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<ulong>(this.@PropertyUInt64); return true; } - if (name == PropertyName.PropertySingle) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float>(this.PropertySingle); + if (name == PropertyName.@PropertySingle) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float>(this.@PropertySingle); return true; } - if (name == PropertyName.PropertyDouble) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double>(this.PropertyDouble); + if (name == PropertyName.@PropertyDouble) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double>(this.@PropertyDouble); return true; } - if (name == PropertyName.PropertyString) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.PropertyString); + if (name == PropertyName.@PropertyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@PropertyString); return true; } - if (name == PropertyName.PropertyVector2) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2>(this.PropertyVector2); + if (name == PropertyName.@PropertyVector2) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2>(this.@PropertyVector2); return true; } - if (name == PropertyName.PropertyVector2I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2I>(this.PropertyVector2I); + if (name == PropertyName.@PropertyVector2I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2I>(this.@PropertyVector2I); return true; } - if (name == PropertyName.PropertyRect2) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2>(this.PropertyRect2); + if (name == PropertyName.@PropertyRect2) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2>(this.@PropertyRect2); return true; } - if (name == PropertyName.PropertyRect2I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2I>(this.PropertyRect2I); + if (name == PropertyName.@PropertyRect2I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rect2I>(this.@PropertyRect2I); return true; } - if (name == PropertyName.PropertyTransform2D) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform2D>(this.PropertyTransform2D); + if (name == PropertyName.@PropertyTransform2D) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform2D>(this.@PropertyTransform2D); return true; } - if (name == PropertyName.PropertyVector3) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3>(this.PropertyVector3); + if (name == PropertyName.@PropertyVector3) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3>(this.@PropertyVector3); return true; } - if (name == PropertyName.PropertyVector3I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3I>(this.PropertyVector3I); + if (name == PropertyName.@PropertyVector3I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3I>(this.@PropertyVector3I); return true; } - if (name == PropertyName.PropertyBasis) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Basis>(this.PropertyBasis); + if (name == PropertyName.@PropertyBasis) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Basis>(this.@PropertyBasis); return true; } - if (name == PropertyName.PropertyQuaternion) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Quaternion>(this.PropertyQuaternion); + if (name == PropertyName.@PropertyQuaternion) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Quaternion>(this.@PropertyQuaternion); return true; } - if (name == PropertyName.PropertyTransform3D) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform3D>(this.PropertyTransform3D); + if (name == PropertyName.@PropertyTransform3D) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Transform3D>(this.@PropertyTransform3D); return true; } - if (name == PropertyName.PropertyVector4) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4>(this.PropertyVector4); + if (name == PropertyName.@PropertyVector4) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4>(this.@PropertyVector4); return true; } - if (name == PropertyName.PropertyVector4I) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4I>(this.PropertyVector4I); + if (name == PropertyName.@PropertyVector4I) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector4I>(this.@PropertyVector4I); return true; } - if (name == PropertyName.PropertyProjection) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Projection>(this.PropertyProjection); + if (name == PropertyName.@PropertyProjection) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Projection>(this.@PropertyProjection); return true; } - if (name == PropertyName.PropertyAabb) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Aabb>(this.PropertyAabb); + if (name == PropertyName.@PropertyAabb) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Aabb>(this.@PropertyAabb); return true; } - if (name == PropertyName.PropertyColor) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color>(this.PropertyColor); + if (name == PropertyName.@PropertyColor) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color>(this.@PropertyColor); return true; } - if (name == PropertyName.PropertyPlane) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Plane>(this.PropertyPlane); + if (name == PropertyName.@PropertyPlane) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Plane>(this.@PropertyPlane); return true; } - if (name == PropertyName.PropertyCallable) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.PropertyCallable); + if (name == PropertyName.@PropertyCallable) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@PropertyCallable); return true; } - if (name == PropertyName.PropertySignal) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Signal>(this.PropertySignal); + if (name == PropertyName.@PropertySignal) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Signal>(this.@PropertySignal); return true; } - if (name == PropertyName.PropertyEnum) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedProperties.MyEnum>(this.PropertyEnum); + if (name == PropertyName.@PropertyEnum) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedProperties.MyEnum>(this.@PropertyEnum); return true; } - if (name == PropertyName.PropertyFlagsEnum) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedProperties.MyFlagsEnum>(this.PropertyFlagsEnum); + if (name == PropertyName.@PropertyFlagsEnum) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::ExportedProperties.MyFlagsEnum>(this.@PropertyFlagsEnum); return true; } - if (name == PropertyName.PropertyByteArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte[]>(this.PropertyByteArray); + if (name == PropertyName.@PropertyByteArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<byte[]>(this.@PropertyByteArray); return true; } - if (name == PropertyName.PropertyInt32Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.PropertyInt32Array); + if (name == PropertyName.@PropertyInt32Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int[]>(this.@PropertyInt32Array); return true; } - if (name == PropertyName.PropertyInt64Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this.PropertyInt64Array); + if (name == PropertyName.@PropertyInt64Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<long[]>(this.@PropertyInt64Array); return true; } - if (name == PropertyName.PropertySingleArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float[]>(this.PropertySingleArray); + if (name == PropertyName.@PropertySingleArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<float[]>(this.@PropertySingleArray); return true; } - if (name == PropertyName.PropertyDoubleArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double[]>(this.PropertyDoubleArray); + if (name == PropertyName.@PropertyDoubleArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<double[]>(this.@PropertyDoubleArray); return true; } - if (name == PropertyName.PropertyStringArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.PropertyStringArray); + if (name == PropertyName.@PropertyStringArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.@PropertyStringArray); return true; } - if (name == PropertyName.PropertyStringArrayEnum) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.PropertyStringArrayEnum); + if (name == PropertyName.@PropertyStringArrayEnum) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string[]>(this.@PropertyStringArrayEnum); return true; } - if (name == PropertyName.PropertyVector2Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2[]>(this.PropertyVector2Array); + if (name == PropertyName.@PropertyVector2Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector2[]>(this.@PropertyVector2Array); return true; } - if (name == PropertyName.PropertyVector3Array) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3[]>(this.PropertyVector3Array); + if (name == PropertyName.@PropertyVector3Array) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Vector3[]>(this.@PropertyVector3Array); return true; } - if (name == PropertyName.PropertyColorArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color[]>(this.PropertyColorArray); + if (name == PropertyName.@PropertyColorArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Color[]>(this.@PropertyColorArray); return true; } - if (name == PropertyName.PropertyGodotObjectOrDerivedArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFromSystemArrayOfGodotObject(this.PropertyGodotObjectOrDerivedArray); + if (name == PropertyName.@PropertyGodotObjectOrDerivedArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromSystemArrayOfGodotObject(this.@PropertyGodotObjectOrDerivedArray); return true; } - if (name == PropertyName.field_StringNameArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName[]>(this.field_StringNameArray); + if (name == PropertyName.@field_StringNameArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName[]>(this.@field_StringNameArray); return true; } - if (name == PropertyName.field_NodePathArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath[]>(this.field_NodePathArray); + if (name == PropertyName.@field_NodePathArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath[]>(this.@field_NodePathArray); return true; } - if (name == PropertyName.field_RidArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid[]>(this.field_RidArray); + if (name == PropertyName.@field_RidArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid[]>(this.@field_RidArray); return true; } - if (name == PropertyName.PropertyVariant) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(this.PropertyVariant); + if (name == PropertyName.@PropertyVariant) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(this.@PropertyVariant); return true; } - if (name == PropertyName.PropertyGodotObjectOrDerived) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.GodotObject>(this.PropertyGodotObjectOrDerived); + if (name == PropertyName.@PropertyGodotObjectOrDerived) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.GodotObject>(this.@PropertyGodotObjectOrDerived); return true; } - if (name == PropertyName.PropertyGodotResourceTexture) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Texture>(this.PropertyGodotResourceTexture); + if (name == PropertyName.@PropertyGodotResourceTexture) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Texture>(this.@PropertyGodotResourceTexture); return true; } - if (name == PropertyName.PropertyStringName) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName>(this.PropertyStringName); + if (name == PropertyName.@PropertyStringName) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.StringName>(this.@PropertyStringName); return true; } - if (name == PropertyName.PropertyNodePath) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this.PropertyNodePath); + if (name == PropertyName.@PropertyNodePath) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this.@PropertyNodePath); return true; } - if (name == PropertyName.PropertyRid) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid>(this.PropertyRid); + if (name == PropertyName.@PropertyRid) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Rid>(this.@PropertyRid); return true; } - if (name == PropertyName.PropertyGodotDictionary) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Dictionary>(this.PropertyGodotDictionary); + if (name == PropertyName.@PropertyGodotDictionary) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Dictionary>(this.@PropertyGodotDictionary); return true; } - if (name == PropertyName.PropertyGodotArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Array>(this.PropertyGodotArray); + if (name == PropertyName.@PropertyGodotArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Collections.Array>(this.@PropertyGodotArray); return true; } - if (name == PropertyName.PropertyGodotGenericDictionary) { - value = global::Godot.NativeInterop.VariantUtils.CreateFromDictionary(this.PropertyGodotGenericDictionary); + if (name == PropertyName.@PropertyGodotGenericDictionary) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromDictionary(this.@PropertyGodotGenericDictionary); return true; } - if (name == PropertyName.PropertyGodotGenericArray) { - value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.PropertyGodotGenericArray); + if (name == PropertyName.@PropertyGodotGenericArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.@PropertyGodotGenericArray); return true; } - if (name == PropertyName._notGeneratePropertyString) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._notGeneratePropertyString); + if (name == PropertyName.@_notGeneratePropertyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@_notGeneratePropertyString); return true; } - if (name == PropertyName._notGeneratePropertyInt) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this._notGeneratePropertyInt); + if (name == PropertyName.@_notGeneratePropertyInt) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@_notGeneratePropertyInt); return true; } - if (name == PropertyName._fullPropertyString) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._fullPropertyString); + if (name == PropertyName.@_fullPropertyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@_fullPropertyString); return true; } - if (name == PropertyName._fullPropertyStringComplex) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._fullPropertyStringComplex); + if (name == PropertyName.@_fullPropertyStringComplex) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@_fullPropertyStringComplex); return true; } - if (name == PropertyName._lamdaPropertyString) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this._lamdaPropertyString); + if (name == PropertyName.@_lamdaPropertyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@_lamdaPropertyString); return true; } return base.GetGodotClassPropertyValue(name, out value); @@ -858,75 +858,75 @@ partial class ExportedProperties internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() { var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._notGeneratePropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerateComplexLamdaProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerateLamdaNoFieldProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerateComplexReturnProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._notGeneratePropertyInt, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.NotGenerateReturnsProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._fullPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.FullPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._fullPropertyStringComplex, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.FullPropertyString_Complex, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName._lamdaPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.LamdaPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.PropertyBoolean, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyChar, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertySByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyUInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyUInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyUInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.PropertySingle, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.PropertyDouble, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.PropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)5, name: PropertyName.PropertyVector2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)6, name: PropertyName.PropertyVector2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)7, name: PropertyName.PropertyRect2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)8, name: PropertyName.PropertyRect2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)11, name: PropertyName.PropertyTransform2D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)9, name: PropertyName.PropertyVector3, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)10, name: PropertyName.PropertyVector3I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)17, name: PropertyName.PropertyBasis, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)15, name: PropertyName.PropertyQuaternion, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)18, name: PropertyName.PropertyTransform3D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)12, name: PropertyName.PropertyVector4, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)13, name: PropertyName.PropertyVector4I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)19, name: PropertyName.PropertyProjection, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)16, name: PropertyName.PropertyAabb, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)20, name: PropertyName.PropertyColor, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)14, name: PropertyName.PropertyPlane, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.PropertyCallable, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)26, name: PropertyName.PropertySignal, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyEnum, hint: (global::Godot.PropertyHint)2, hintString: "A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.PropertyFlagsEnum, hint: (global::Godot.PropertyHint)6, hintString: "A:0,B:1,C:2", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)29, name: PropertyName.PropertyByteArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.PropertyInt32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.PropertyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)32, name: PropertyName.PropertySingleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)33, name: PropertyName.PropertyDoubleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.PropertyStringArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.PropertyStringArrayEnum, hint: (global::Godot.PropertyHint)23, hintString: "4/2:A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)35, name: PropertyName.PropertyVector2Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)36, name: PropertyName.PropertyVector3Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)37, name: PropertyName.PropertyColorArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.PropertyGodotObjectOrDerivedArray, hint: (global::Godot.PropertyHint)23, hintString: "24/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_StringNameArray, hint: (global::Godot.PropertyHint)23, hintString: "21/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_NodePathArray, hint: (global::Godot.PropertyHint)23, hintString: "22/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.field_RidArray, hint: (global::Godot.PropertyHint)23, hintString: "23/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)0, name: PropertyName.PropertyVariant, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)135174, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.PropertyGodotObjectOrDerived, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.PropertyGodotResourceTexture, hint: (global::Godot.PropertyHint)17, hintString: "Texture", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)21, name: PropertyName.PropertyStringName, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName.PropertyNodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@_notGeneratePropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@NotGenerateComplexLamdaProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@NotGenerateLamdaNoFieldProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@NotGenerateComplexReturnProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_notGeneratePropertyInt, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@NotGenerateReturnsProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@_fullPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@FullPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@_fullPropertyStringComplex, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@FullPropertyString_Complex, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@_lamdaPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@LamdaPropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.@PropertyBoolean, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyChar, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertySByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyByte, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyUInt16, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyUInt32, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyUInt64, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.@PropertySingle, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)3, name: PropertyName.@PropertyDouble, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@PropertyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)5, name: PropertyName.@PropertyVector2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)6, name: PropertyName.@PropertyVector2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)7, name: PropertyName.@PropertyRect2, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)8, name: PropertyName.@PropertyRect2I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)11, name: PropertyName.@PropertyTransform2D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)9, name: PropertyName.@PropertyVector3, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)10, name: PropertyName.@PropertyVector3I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)17, name: PropertyName.@PropertyBasis, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)15, name: PropertyName.@PropertyQuaternion, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)18, name: PropertyName.@PropertyTransform3D, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)12, name: PropertyName.@PropertyVector4, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)13, name: PropertyName.@PropertyVector4I, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)19, name: PropertyName.@PropertyProjection, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)16, name: PropertyName.@PropertyAabb, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)20, name: PropertyName.@PropertyColor, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)14, name: PropertyName.@PropertyPlane, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@PropertyCallable, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)26, name: PropertyName.@PropertySignal, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyEnum, hint: (global::Godot.PropertyHint)2, hintString: "A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@PropertyFlagsEnum, hint: (global::Godot.PropertyHint)6, hintString: "A:0,B:1,C:2", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)29, name: PropertyName.@PropertyByteArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)30, name: PropertyName.@PropertyInt32Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@PropertyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)32, name: PropertyName.@PropertySingleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)33, name: PropertyName.@PropertyDoubleArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.@PropertyStringArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)34, name: PropertyName.@PropertyStringArrayEnum, hint: (global::Godot.PropertyHint)23, hintString: "4/2:A,B,C", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)35, name: PropertyName.@PropertyVector2Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)36, name: PropertyName.@PropertyVector3Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)37, name: PropertyName.@PropertyColorArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotObjectOrDerivedArray, hint: (global::Godot.PropertyHint)23, hintString: "24/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@field_StringNameArray, hint: (global::Godot.PropertyHint)23, hintString: "21/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@field_NodePathArray, hint: (global::Godot.PropertyHint)23, hintString: "22/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@field_RidArray, hint: (global::Godot.PropertyHint)23, hintString: "23/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)0, name: PropertyName.@PropertyVariant, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)135174, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.@PropertyGodotObjectOrDerived, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)24, name: PropertyName.@PropertyGodotResourceTexture, hint: (global::Godot.PropertyHint)17, hintString: "Texture", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)21, name: PropertyName.@PropertyStringName, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName.@PropertyNodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); return properties; } #pragma warning restore CS0109 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs index a1b01aed4f..ce154cad8e 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptPropertyDefVal.generated.cs @@ -13,133 +13,133 @@ partial class ExportedProperties { var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(64); string __NotGenerateComplexLamdaProperty_default_value = default; - values.Add(PropertyName.NotGenerateComplexLamdaProperty, global::Godot.Variant.From<string>(__NotGenerateComplexLamdaProperty_default_value)); + values.Add(PropertyName.@NotGenerateComplexLamdaProperty, global::Godot.Variant.From<string>(__NotGenerateComplexLamdaProperty_default_value)); string __NotGenerateLamdaNoFieldProperty_default_value = default; - values.Add(PropertyName.NotGenerateLamdaNoFieldProperty, global::Godot.Variant.From<string>(__NotGenerateLamdaNoFieldProperty_default_value)); + values.Add(PropertyName.@NotGenerateLamdaNoFieldProperty, global::Godot.Variant.From<string>(__NotGenerateLamdaNoFieldProperty_default_value)); string __NotGenerateComplexReturnProperty_default_value = default; - values.Add(PropertyName.NotGenerateComplexReturnProperty, global::Godot.Variant.From<string>(__NotGenerateComplexReturnProperty_default_value)); + values.Add(PropertyName.@NotGenerateComplexReturnProperty, global::Godot.Variant.From<string>(__NotGenerateComplexReturnProperty_default_value)); string __NotGenerateReturnsProperty_default_value = default; - values.Add(PropertyName.NotGenerateReturnsProperty, global::Godot.Variant.From<string>(__NotGenerateReturnsProperty_default_value)); + values.Add(PropertyName.@NotGenerateReturnsProperty, global::Godot.Variant.From<string>(__NotGenerateReturnsProperty_default_value)); string __FullPropertyString_default_value = "FullPropertyString"; - values.Add(PropertyName.FullPropertyString, global::Godot.Variant.From<string>(__FullPropertyString_default_value)); + values.Add(PropertyName.@FullPropertyString, global::Godot.Variant.From<string>(__FullPropertyString_default_value)); string __FullPropertyString_Complex_default_value = new string("FullPropertyString_Complex") + global::System.Convert.ToInt32("1"); - values.Add(PropertyName.FullPropertyString_Complex, global::Godot.Variant.From<string>(__FullPropertyString_Complex_default_value)); + values.Add(PropertyName.@FullPropertyString_Complex, global::Godot.Variant.From<string>(__FullPropertyString_Complex_default_value)); string __LamdaPropertyString_default_value = "LamdaPropertyString"; - values.Add(PropertyName.LamdaPropertyString, global::Godot.Variant.From<string>(__LamdaPropertyString_default_value)); + values.Add(PropertyName.@LamdaPropertyString, global::Godot.Variant.From<string>(__LamdaPropertyString_default_value)); bool __PropertyBoolean_default_value = true; - values.Add(PropertyName.PropertyBoolean, global::Godot.Variant.From<bool>(__PropertyBoolean_default_value)); + values.Add(PropertyName.@PropertyBoolean, global::Godot.Variant.From<bool>(__PropertyBoolean_default_value)); char __PropertyChar_default_value = 'f'; - values.Add(PropertyName.PropertyChar, global::Godot.Variant.From<char>(__PropertyChar_default_value)); + values.Add(PropertyName.@PropertyChar, global::Godot.Variant.From<char>(__PropertyChar_default_value)); sbyte __PropertySByte_default_value = 10; - values.Add(PropertyName.PropertySByte, global::Godot.Variant.From<sbyte>(__PropertySByte_default_value)); + values.Add(PropertyName.@PropertySByte, global::Godot.Variant.From<sbyte>(__PropertySByte_default_value)); short __PropertyInt16_default_value = 10; - values.Add(PropertyName.PropertyInt16, global::Godot.Variant.From<short>(__PropertyInt16_default_value)); + values.Add(PropertyName.@PropertyInt16, global::Godot.Variant.From<short>(__PropertyInt16_default_value)); int __PropertyInt32_default_value = 10; - values.Add(PropertyName.PropertyInt32, global::Godot.Variant.From<int>(__PropertyInt32_default_value)); + values.Add(PropertyName.@PropertyInt32, global::Godot.Variant.From<int>(__PropertyInt32_default_value)); long __PropertyInt64_default_value = 10; - values.Add(PropertyName.PropertyInt64, global::Godot.Variant.From<long>(__PropertyInt64_default_value)); + values.Add(PropertyName.@PropertyInt64, global::Godot.Variant.From<long>(__PropertyInt64_default_value)); byte __PropertyByte_default_value = 10; - values.Add(PropertyName.PropertyByte, global::Godot.Variant.From<byte>(__PropertyByte_default_value)); + values.Add(PropertyName.@PropertyByte, global::Godot.Variant.From<byte>(__PropertyByte_default_value)); ushort __PropertyUInt16_default_value = 10; - values.Add(PropertyName.PropertyUInt16, global::Godot.Variant.From<ushort>(__PropertyUInt16_default_value)); + values.Add(PropertyName.@PropertyUInt16, global::Godot.Variant.From<ushort>(__PropertyUInt16_default_value)); uint __PropertyUInt32_default_value = 10; - values.Add(PropertyName.PropertyUInt32, global::Godot.Variant.From<uint>(__PropertyUInt32_default_value)); + values.Add(PropertyName.@PropertyUInt32, global::Godot.Variant.From<uint>(__PropertyUInt32_default_value)); ulong __PropertyUInt64_default_value = 10; - values.Add(PropertyName.PropertyUInt64, global::Godot.Variant.From<ulong>(__PropertyUInt64_default_value)); + values.Add(PropertyName.@PropertyUInt64, global::Godot.Variant.From<ulong>(__PropertyUInt64_default_value)); float __PropertySingle_default_value = 10; - values.Add(PropertyName.PropertySingle, global::Godot.Variant.From<float>(__PropertySingle_default_value)); + values.Add(PropertyName.@PropertySingle, global::Godot.Variant.From<float>(__PropertySingle_default_value)); double __PropertyDouble_default_value = 10; - values.Add(PropertyName.PropertyDouble, global::Godot.Variant.From<double>(__PropertyDouble_default_value)); + values.Add(PropertyName.@PropertyDouble, global::Godot.Variant.From<double>(__PropertyDouble_default_value)); string __PropertyString_default_value = "foo"; - values.Add(PropertyName.PropertyString, global::Godot.Variant.From<string>(__PropertyString_default_value)); + values.Add(PropertyName.@PropertyString, global::Godot.Variant.From<string>(__PropertyString_default_value)); global::Godot.Vector2 __PropertyVector2_default_value = new(10f, 10f); - values.Add(PropertyName.PropertyVector2, global::Godot.Variant.From<global::Godot.Vector2>(__PropertyVector2_default_value)); + values.Add(PropertyName.@PropertyVector2, global::Godot.Variant.From<global::Godot.Vector2>(__PropertyVector2_default_value)); global::Godot.Vector2I __PropertyVector2I_default_value = global::Godot.Vector2I.Up; - values.Add(PropertyName.PropertyVector2I, global::Godot.Variant.From<global::Godot.Vector2I>(__PropertyVector2I_default_value)); + values.Add(PropertyName.@PropertyVector2I, global::Godot.Variant.From<global::Godot.Vector2I>(__PropertyVector2I_default_value)); global::Godot.Rect2 __PropertyRect2_default_value = new(new global::Godot.Vector2(10f, 10f), new global::Godot.Vector2(10f, 10f)); - values.Add(PropertyName.PropertyRect2, global::Godot.Variant.From<global::Godot.Rect2>(__PropertyRect2_default_value)); + values.Add(PropertyName.@PropertyRect2, global::Godot.Variant.From<global::Godot.Rect2>(__PropertyRect2_default_value)); global::Godot.Rect2I __PropertyRect2I_default_value = new(new global::Godot.Vector2I(10, 10), new global::Godot.Vector2I(10, 10)); - values.Add(PropertyName.PropertyRect2I, global::Godot.Variant.From<global::Godot.Rect2I>(__PropertyRect2I_default_value)); + values.Add(PropertyName.@PropertyRect2I, global::Godot.Variant.From<global::Godot.Rect2I>(__PropertyRect2I_default_value)); global::Godot.Transform2D __PropertyTransform2D_default_value = global::Godot.Transform2D.Identity; - values.Add(PropertyName.PropertyTransform2D, global::Godot.Variant.From<global::Godot.Transform2D>(__PropertyTransform2D_default_value)); + values.Add(PropertyName.@PropertyTransform2D, global::Godot.Variant.From<global::Godot.Transform2D>(__PropertyTransform2D_default_value)); global::Godot.Vector3 __PropertyVector3_default_value = new(10f, 10f, 10f); - values.Add(PropertyName.PropertyVector3, global::Godot.Variant.From<global::Godot.Vector3>(__PropertyVector3_default_value)); + values.Add(PropertyName.@PropertyVector3, global::Godot.Variant.From<global::Godot.Vector3>(__PropertyVector3_default_value)); global::Godot.Vector3I __PropertyVector3I_default_value = global::Godot.Vector3I.Back; - values.Add(PropertyName.PropertyVector3I, global::Godot.Variant.From<global::Godot.Vector3I>(__PropertyVector3I_default_value)); + values.Add(PropertyName.@PropertyVector3I, global::Godot.Variant.From<global::Godot.Vector3I>(__PropertyVector3I_default_value)); global::Godot.Basis __PropertyBasis_default_value = new global::Godot.Basis(global::Godot.Quaternion.Identity); - values.Add(PropertyName.PropertyBasis, global::Godot.Variant.From<global::Godot.Basis>(__PropertyBasis_default_value)); + values.Add(PropertyName.@PropertyBasis, global::Godot.Variant.From<global::Godot.Basis>(__PropertyBasis_default_value)); global::Godot.Quaternion __PropertyQuaternion_default_value = new global::Godot.Quaternion(global::Godot.Basis.Identity); - values.Add(PropertyName.PropertyQuaternion, global::Godot.Variant.From<global::Godot.Quaternion>(__PropertyQuaternion_default_value)); + values.Add(PropertyName.@PropertyQuaternion, global::Godot.Variant.From<global::Godot.Quaternion>(__PropertyQuaternion_default_value)); global::Godot.Transform3D __PropertyTransform3D_default_value = global::Godot.Transform3D.Identity; - values.Add(PropertyName.PropertyTransform3D, global::Godot.Variant.From<global::Godot.Transform3D>(__PropertyTransform3D_default_value)); + values.Add(PropertyName.@PropertyTransform3D, global::Godot.Variant.From<global::Godot.Transform3D>(__PropertyTransform3D_default_value)); global::Godot.Vector4 __PropertyVector4_default_value = new(10f, 10f, 10f, 10f); - values.Add(PropertyName.PropertyVector4, global::Godot.Variant.From<global::Godot.Vector4>(__PropertyVector4_default_value)); + values.Add(PropertyName.@PropertyVector4, global::Godot.Variant.From<global::Godot.Vector4>(__PropertyVector4_default_value)); global::Godot.Vector4I __PropertyVector4I_default_value = global::Godot.Vector4I.One; - values.Add(PropertyName.PropertyVector4I, global::Godot.Variant.From<global::Godot.Vector4I>(__PropertyVector4I_default_value)); + values.Add(PropertyName.@PropertyVector4I, global::Godot.Variant.From<global::Godot.Vector4I>(__PropertyVector4I_default_value)); global::Godot.Projection __PropertyProjection_default_value = global::Godot.Projection.Identity; - values.Add(PropertyName.PropertyProjection, global::Godot.Variant.From<global::Godot.Projection>(__PropertyProjection_default_value)); + values.Add(PropertyName.@PropertyProjection, global::Godot.Variant.From<global::Godot.Projection>(__PropertyProjection_default_value)); global::Godot.Aabb __PropertyAabb_default_value = new global::Godot.Aabb(10f, 10f, 10f, new global::Godot.Vector3(1f, 1f, 1f)); - values.Add(PropertyName.PropertyAabb, global::Godot.Variant.From<global::Godot.Aabb>(__PropertyAabb_default_value)); + values.Add(PropertyName.@PropertyAabb, global::Godot.Variant.From<global::Godot.Aabb>(__PropertyAabb_default_value)); global::Godot.Color __PropertyColor_default_value = global::Godot.Colors.Aquamarine; - values.Add(PropertyName.PropertyColor, global::Godot.Variant.From<global::Godot.Color>(__PropertyColor_default_value)); + values.Add(PropertyName.@PropertyColor, global::Godot.Variant.From<global::Godot.Color>(__PropertyColor_default_value)); global::Godot.Plane __PropertyPlane_default_value = global::Godot.Plane.PlaneXZ; - values.Add(PropertyName.PropertyPlane, global::Godot.Variant.From<global::Godot.Plane>(__PropertyPlane_default_value)); + values.Add(PropertyName.@PropertyPlane, global::Godot.Variant.From<global::Godot.Plane>(__PropertyPlane_default_value)); global::Godot.Callable __PropertyCallable_default_value = new global::Godot.Callable(global::Godot.Engine.GetMainLoop(), "_process"); - values.Add(PropertyName.PropertyCallable, global::Godot.Variant.From<global::Godot.Callable>(__PropertyCallable_default_value)); + values.Add(PropertyName.@PropertyCallable, global::Godot.Variant.From<global::Godot.Callable>(__PropertyCallable_default_value)); global::Godot.Signal __PropertySignal_default_value = new global::Godot.Signal(global::Godot.Engine.GetMainLoop(), "Propertylist_changed"); - values.Add(PropertyName.PropertySignal, global::Godot.Variant.From<global::Godot.Signal>(__PropertySignal_default_value)); + values.Add(PropertyName.@PropertySignal, global::Godot.Variant.From<global::Godot.Signal>(__PropertySignal_default_value)); global::ExportedProperties.MyEnum __PropertyEnum_default_value = global::ExportedProperties.MyEnum.C; - values.Add(PropertyName.PropertyEnum, global::Godot.Variant.From<global::ExportedProperties.MyEnum>(__PropertyEnum_default_value)); + values.Add(PropertyName.@PropertyEnum, global::Godot.Variant.From<global::ExportedProperties.MyEnum>(__PropertyEnum_default_value)); global::ExportedProperties.MyFlagsEnum __PropertyFlagsEnum_default_value = global::ExportedProperties.MyFlagsEnum.C; - values.Add(PropertyName.PropertyFlagsEnum, global::Godot.Variant.From<global::ExportedProperties.MyFlagsEnum>(__PropertyFlagsEnum_default_value)); + values.Add(PropertyName.@PropertyFlagsEnum, global::Godot.Variant.From<global::ExportedProperties.MyFlagsEnum>(__PropertyFlagsEnum_default_value)); byte[] __PropertyByteArray_default_value = { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName.PropertyByteArray, global::Godot.Variant.From<byte[]>(__PropertyByteArray_default_value)); + values.Add(PropertyName.@PropertyByteArray, global::Godot.Variant.From<byte[]>(__PropertyByteArray_default_value)); int[] __PropertyInt32Array_default_value = { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName.PropertyInt32Array, global::Godot.Variant.From<int[]>(__PropertyInt32Array_default_value)); + values.Add(PropertyName.@PropertyInt32Array, global::Godot.Variant.From<int[]>(__PropertyInt32Array_default_value)); long[] __PropertyInt64Array_default_value = { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName.PropertyInt64Array, global::Godot.Variant.From<long[]>(__PropertyInt64Array_default_value)); + values.Add(PropertyName.@PropertyInt64Array, global::Godot.Variant.From<long[]>(__PropertyInt64Array_default_value)); float[] __PropertySingleArray_default_value = { 0f, 1f, 2f, 3f, 4f, 5f, 6f }; - values.Add(PropertyName.PropertySingleArray, global::Godot.Variant.From<float[]>(__PropertySingleArray_default_value)); + values.Add(PropertyName.@PropertySingleArray, global::Godot.Variant.From<float[]>(__PropertySingleArray_default_value)); double[] __PropertyDoubleArray_default_value = { 0d, 1d, 2d, 3d, 4d, 5d, 6d }; - values.Add(PropertyName.PropertyDoubleArray, global::Godot.Variant.From<double[]>(__PropertyDoubleArray_default_value)); + values.Add(PropertyName.@PropertyDoubleArray, global::Godot.Variant.From<double[]>(__PropertyDoubleArray_default_value)); string[] __PropertyStringArray_default_value = { "foo", "bar" }; - values.Add(PropertyName.PropertyStringArray, global::Godot.Variant.From<string[]>(__PropertyStringArray_default_value)); + values.Add(PropertyName.@PropertyStringArray, global::Godot.Variant.From<string[]>(__PropertyStringArray_default_value)); string[] __PropertyStringArrayEnum_default_value = { "foo", "bar" }; - values.Add(PropertyName.PropertyStringArrayEnum, global::Godot.Variant.From<string[]>(__PropertyStringArrayEnum_default_value)); + values.Add(PropertyName.@PropertyStringArrayEnum, global::Godot.Variant.From<string[]>(__PropertyStringArrayEnum_default_value)); global::Godot.Vector2[] __PropertyVector2Array_default_value = { global::Godot.Vector2.Up, global::Godot.Vector2.Down, global::Godot.Vector2.Left, global::Godot.Vector2.Right }; - values.Add(PropertyName.PropertyVector2Array, global::Godot.Variant.From<global::Godot.Vector2[]>(__PropertyVector2Array_default_value)); + values.Add(PropertyName.@PropertyVector2Array, global::Godot.Variant.From<global::Godot.Vector2[]>(__PropertyVector2Array_default_value)); global::Godot.Vector3[] __PropertyVector3Array_default_value = { global::Godot.Vector3.Up, global::Godot.Vector3.Down, global::Godot.Vector3.Left, global::Godot.Vector3.Right }; - values.Add(PropertyName.PropertyVector3Array, global::Godot.Variant.From<global::Godot.Vector3[]>(__PropertyVector3Array_default_value)); + values.Add(PropertyName.@PropertyVector3Array, global::Godot.Variant.From<global::Godot.Vector3[]>(__PropertyVector3Array_default_value)); global::Godot.Color[] __PropertyColorArray_default_value = { global::Godot.Colors.Aqua, global::Godot.Colors.Aquamarine, global::Godot.Colors.Azure, global::Godot.Colors.Beige }; - values.Add(PropertyName.PropertyColorArray, global::Godot.Variant.From<global::Godot.Color[]>(__PropertyColorArray_default_value)); + values.Add(PropertyName.@PropertyColorArray, global::Godot.Variant.From<global::Godot.Color[]>(__PropertyColorArray_default_value)); global::Godot.GodotObject[] __PropertyGodotObjectOrDerivedArray_default_value = { null }; - values.Add(PropertyName.PropertyGodotObjectOrDerivedArray, global::Godot.Variant.CreateFrom(__PropertyGodotObjectOrDerivedArray_default_value)); + values.Add(PropertyName.@PropertyGodotObjectOrDerivedArray, global::Godot.Variant.CreateFrom(__PropertyGodotObjectOrDerivedArray_default_value)); global::Godot.StringName[] __field_StringNameArray_default_value = { "foo", "bar" }; - values.Add(PropertyName.field_StringNameArray, global::Godot.Variant.From<global::Godot.StringName[]>(__field_StringNameArray_default_value)); + values.Add(PropertyName.@field_StringNameArray, global::Godot.Variant.From<global::Godot.StringName[]>(__field_StringNameArray_default_value)); global::Godot.NodePath[] __field_NodePathArray_default_value = { "foo", "bar" }; - values.Add(PropertyName.field_NodePathArray, global::Godot.Variant.From<global::Godot.NodePath[]>(__field_NodePathArray_default_value)); + values.Add(PropertyName.@field_NodePathArray, global::Godot.Variant.From<global::Godot.NodePath[]>(__field_NodePathArray_default_value)); global::Godot.Rid[] __field_RidArray_default_value = { default, default, default }; - values.Add(PropertyName.field_RidArray, global::Godot.Variant.From<global::Godot.Rid[]>(__field_RidArray_default_value)); + values.Add(PropertyName.@field_RidArray, global::Godot.Variant.From<global::Godot.Rid[]>(__field_RidArray_default_value)); global::Godot.Variant __PropertyVariant_default_value = "foo"; - values.Add(PropertyName.PropertyVariant, global::Godot.Variant.From<global::Godot.Variant>(__PropertyVariant_default_value)); + values.Add(PropertyName.@PropertyVariant, global::Godot.Variant.From<global::Godot.Variant>(__PropertyVariant_default_value)); global::Godot.GodotObject __PropertyGodotObjectOrDerived_default_value = default; - values.Add(PropertyName.PropertyGodotObjectOrDerived, global::Godot.Variant.From<global::Godot.GodotObject>(__PropertyGodotObjectOrDerived_default_value)); + values.Add(PropertyName.@PropertyGodotObjectOrDerived, global::Godot.Variant.From<global::Godot.GodotObject>(__PropertyGodotObjectOrDerived_default_value)); global::Godot.Texture __PropertyGodotResourceTexture_default_value = default; - values.Add(PropertyName.PropertyGodotResourceTexture, global::Godot.Variant.From<global::Godot.Texture>(__PropertyGodotResourceTexture_default_value)); + values.Add(PropertyName.@PropertyGodotResourceTexture, global::Godot.Variant.From<global::Godot.Texture>(__PropertyGodotResourceTexture_default_value)); global::Godot.StringName __PropertyStringName_default_value = new global::Godot.StringName("foo"); - values.Add(PropertyName.PropertyStringName, global::Godot.Variant.From<global::Godot.StringName>(__PropertyStringName_default_value)); + values.Add(PropertyName.@PropertyStringName, global::Godot.Variant.From<global::Godot.StringName>(__PropertyStringName_default_value)); global::Godot.NodePath __PropertyNodePath_default_value = new global::Godot.NodePath("foo"); - values.Add(PropertyName.PropertyNodePath, global::Godot.Variant.From<global::Godot.NodePath>(__PropertyNodePath_default_value)); + values.Add(PropertyName.@PropertyNodePath, global::Godot.Variant.From<global::Godot.NodePath>(__PropertyNodePath_default_value)); global::Godot.Rid __PropertyRid_default_value = default; - values.Add(PropertyName.PropertyRid, global::Godot.Variant.From<global::Godot.Rid>(__PropertyRid_default_value)); + values.Add(PropertyName.@PropertyRid, global::Godot.Variant.From<global::Godot.Rid>(__PropertyRid_default_value)); global::Godot.Collections.Dictionary __PropertyGodotDictionary_default_value = new() { { "foo", 10 }, { global::Godot.Vector2.Up, global::Godot.Colors.Chocolate } }; - values.Add(PropertyName.PropertyGodotDictionary, global::Godot.Variant.From<global::Godot.Collections.Dictionary>(__PropertyGodotDictionary_default_value)); + values.Add(PropertyName.@PropertyGodotDictionary, global::Godot.Variant.From<global::Godot.Collections.Dictionary>(__PropertyGodotDictionary_default_value)); global::Godot.Collections.Array __PropertyGodotArray_default_value = new() { "foo", 10, global::Godot.Vector2.Up, global::Godot.Colors.Chocolate }; - values.Add(PropertyName.PropertyGodotArray, global::Godot.Variant.From<global::Godot.Collections.Array>(__PropertyGodotArray_default_value)); + values.Add(PropertyName.@PropertyGodotArray, global::Godot.Variant.From<global::Godot.Collections.Array>(__PropertyGodotArray_default_value)); global::Godot.Collections.Dictionary<string, bool> __PropertyGodotGenericDictionary_default_value = new() { { "foo", true }, { "bar", false } }; - values.Add(PropertyName.PropertyGodotGenericDictionary, global::Godot.Variant.CreateFrom(__PropertyGodotGenericDictionary_default_value)); + values.Add(PropertyName.@PropertyGodotGenericDictionary, global::Godot.Variant.CreateFrom(__PropertyGodotGenericDictionary_default_value)); global::Godot.Collections.Array<int> __PropertyGodotGenericArray_default_value = new() { 0, 1, 2, 3, 4, 5, 6 }; - values.Add(PropertyName.PropertyGodotGenericArray, global::Godot.Variant.CreateFrom(__PropertyGodotGenericArray_default_value)); + values.Add(PropertyName.@PropertyGodotGenericArray, global::Godot.Variant.CreateFrom(__PropertyGodotGenericArray_default_value)); return values; } #endif // TOOLS diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs index f757497618..fe54e2cb70 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Methods_ScriptMethods.generated.cs @@ -11,7 +11,7 @@ partial class Methods /// <summary> /// Cached name for the 'MethodWithOverload' method. /// </summary> - public new static readonly global::Godot.StringName MethodWithOverload = "MethodWithOverload"; + public new static readonly global::Godot.StringName @MethodWithOverload = "MethodWithOverload"; } /// <summary> /// Get the method information for all the methods declared in this class. @@ -22,9 +22,9 @@ partial class Methods internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotMethodList() { var methods = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(3); - methods.Add(new(name: MethodName.MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: null, defaultArguments: null)); - methods.Add(new(name: MethodName.MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)2, name: "a", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); - methods.Add(new(name: MethodName.MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)2, name: "a", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), new(type: (global::Godot.Variant.Type)2, name: "b", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); + methods.Add(new(name: MethodName.@MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: null, defaultArguments: null)); + methods.Add(new(name: MethodName.@MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)2, name: "a", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); + methods.Add(new(name: MethodName.@MethodWithOverload, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)2, name: "a", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), new(type: (global::Godot.Variant.Type)2, name: "b", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); return methods; } #pragma warning restore CS0109 @@ -32,18 +32,18 @@ partial class Methods [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool InvokeGodotClassMethod(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret) { - if (method == MethodName.MethodWithOverload && args.Count == 0) { - MethodWithOverload(); + if (method == MethodName.@MethodWithOverload && args.Count == 0) { + @MethodWithOverload(); ret = default; return true; } - if (method == MethodName.MethodWithOverload && args.Count == 1) { - MethodWithOverload(global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[0])); + if (method == MethodName.@MethodWithOverload && args.Count == 1) { + @MethodWithOverload(global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[0])); ret = default; return true; } - if (method == MethodName.MethodWithOverload && args.Count == 2) { - MethodWithOverload(global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[0]), global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[1])); + if (method == MethodName.@MethodWithOverload && args.Count == 2) { + @MethodWithOverload(global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[0]), global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(args[1])); ret = default; return true; } @@ -53,7 +53,7 @@ partial class Methods [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool HasGodotClassMethod(in godot_string_name method) { - if (method == MethodName.MethodWithOverload) { + if (method == MethodName.@MethodWithOverload) { return true; } return base.HasGodotClassMethod(method); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs index cabdbe8d99..6be6296d3b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/MixedReadOnlyWriteOnly_ScriptProperties.generated.cs @@ -11,38 +11,38 @@ partial class MixedReadOnlyWriteOnly /// <summary> /// Cached name for the 'ReadOnlyAutoProperty' property. /// </summary> - public new static readonly global::Godot.StringName ReadOnlyAutoProperty = "ReadOnlyAutoProperty"; + public new static readonly global::Godot.StringName @ReadOnlyAutoProperty = "ReadOnlyAutoProperty"; /// <summary> /// Cached name for the 'ReadOnlyProperty' property. /// </summary> - public new static readonly global::Godot.StringName ReadOnlyProperty = "ReadOnlyProperty"; + public new static readonly global::Godot.StringName @ReadOnlyProperty = "ReadOnlyProperty"; /// <summary> /// Cached name for the 'InitOnlyAutoProperty' property. /// </summary> - public new static readonly global::Godot.StringName InitOnlyAutoProperty = "InitOnlyAutoProperty"; + public new static readonly global::Godot.StringName @InitOnlyAutoProperty = "InitOnlyAutoProperty"; /// <summary> /// Cached name for the 'WriteOnlyProperty' property. /// </summary> - public new static readonly global::Godot.StringName WriteOnlyProperty = "WriteOnlyProperty"; + public new static readonly global::Godot.StringName @WriteOnlyProperty = "WriteOnlyProperty"; /// <summary> /// Cached name for the 'ReadOnlyField' field. /// </summary> - public new static readonly global::Godot.StringName ReadOnlyField = "ReadOnlyField"; + public new static readonly global::Godot.StringName @ReadOnlyField = "ReadOnlyField"; /// <summary> /// Cached name for the '_writeOnlyBackingField' field. /// </summary> - public new static readonly global::Godot.StringName _writeOnlyBackingField = "_writeOnlyBackingField"; + public new static readonly global::Godot.StringName @_writeOnlyBackingField = "_writeOnlyBackingField"; } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { - if (name == PropertyName.WriteOnlyProperty) { - this.WriteOnlyProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); + if (name == PropertyName.@WriteOnlyProperty) { + this.@WriteOnlyProperty = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); return true; } - if (name == PropertyName._writeOnlyBackingField) { - this._writeOnlyBackingField = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); + if (name == PropertyName.@_writeOnlyBackingField) { + this.@_writeOnlyBackingField = global::Godot.NativeInterop.VariantUtils.ConvertTo<bool>(value); return true; } return base.SetGodotClassPropertyValue(name, value); @@ -51,24 +51,24 @@ partial class MixedReadOnlyWriteOnly [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - if (name == PropertyName.ReadOnlyAutoProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.ReadOnlyAutoProperty); + if (name == PropertyName.@ReadOnlyAutoProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@ReadOnlyAutoProperty); return true; } - if (name == PropertyName.ReadOnlyProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.ReadOnlyProperty); + if (name == PropertyName.@ReadOnlyProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@ReadOnlyProperty); return true; } - if (name == PropertyName.InitOnlyAutoProperty) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.InitOnlyAutoProperty); + if (name == PropertyName.@InitOnlyAutoProperty) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@InitOnlyAutoProperty); return true; } - if (name == PropertyName.ReadOnlyField) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.ReadOnlyField); + if (name == PropertyName.@ReadOnlyField) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@ReadOnlyField); return true; } - if (name == PropertyName._writeOnlyBackingField) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this._writeOnlyBackingField); + if (name == PropertyName.@_writeOnlyBackingField) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<bool>(this.@_writeOnlyBackingField); return true; } return base.GetGodotClassPropertyValue(name, out value); @@ -82,12 +82,12 @@ partial class MixedReadOnlyWriteOnly internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() { var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.ReadOnlyField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.ReadOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.ReadOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.InitOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName._writeOnlyBackingField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.WriteOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@ReadOnlyField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@ReadOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@ReadOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@InitOnlyAutoProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.@_writeOnlyBackingField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)1, name: PropertyName.@WriteOnlyProperty, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); return properties; } #pragma warning restore CS0109 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs index 328107a237..2cc8f82f73 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/OuterClass.NestedClass_ScriptMethods.generated.cs @@ -13,7 +13,7 @@ partial class NestedClass /// <summary> /// Cached name for the '_Get' method. /// </summary> - public new static readonly global::Godot.StringName _Get = "_Get"; + public new static readonly global::Godot.StringName @_Get = "_Get"; } /// <summary> /// Get the method information for all the methods declared in this class. @@ -24,7 +24,7 @@ partial class NestedClass internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotMethodList() { var methods = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(1); - methods.Add(new(name: MethodName._Get, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)131078, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)21, name: "property", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); + methods.Add(new(name: MethodName.@_Get, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)131078, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)21, name: "property", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); return methods; } #pragma warning restore CS0109 @@ -32,8 +32,8 @@ partial class NestedClass [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool InvokeGodotClassMethod(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret) { - if (method == MethodName._Get && args.Count == 1) { - var callRet = _Get(global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(args[0])); + if (method == MethodName.@_Get && args.Count == 1) { + var callRet = @_Get(global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(args[0])); ret = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Variant>(callRet); return true; } @@ -43,7 +43,7 @@ partial class NestedClass [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool HasGodotClassMethod(in godot_string_name method) { - if (method == MethodName._Get) { + if (method == MethodName.@_Get) { return true; } return base.HasGodotClassMethod(method); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs index a6e58bf27d..3b06c95a9a 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptMethods.generated.cs @@ -11,11 +11,11 @@ partial class ScriptBoilerplate /// <summary> /// Cached name for the '_Process' method. /// </summary> - public new static readonly global::Godot.StringName _Process = "_Process"; + public new static readonly global::Godot.StringName @_Process = "_Process"; /// <summary> /// Cached name for the 'Bazz' method. /// </summary> - public new static readonly global::Godot.StringName Bazz = "Bazz"; + public new static readonly global::Godot.StringName @Bazz = "Bazz"; } /// <summary> /// Get the method information for all the methods declared in this class. @@ -26,8 +26,8 @@ partial class ScriptBoilerplate internal new static global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo> GetGodotMethodList() { var methods = new global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>(2); - methods.Add(new(name: MethodName._Process, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)3, name: "delta", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); - methods.Add(new(name: MethodName.Bazz, returnVal: new(type: (global::Godot.Variant.Type)2, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)21, name: "name", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); + methods.Add(new(name: MethodName.@_Process, returnVal: new(type: (global::Godot.Variant.Type)0, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)3, name: "delta", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); + methods.Add(new(name: MethodName.@Bazz, returnVal: new(type: (global::Godot.Variant.Type)2, name: "", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), flags: (global::Godot.MethodFlags)1, arguments: new() { new(type: (global::Godot.Variant.Type)21, name: "name", hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)6, exported: false), }, defaultArguments: null)); return methods; } #pragma warning restore CS0109 @@ -35,13 +35,13 @@ partial class ScriptBoilerplate [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool InvokeGodotClassMethod(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret) { - if (method == MethodName._Process && args.Count == 1) { - _Process(global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(args[0])); + if (method == MethodName.@_Process && args.Count == 1) { + @_Process(global::Godot.NativeInterop.VariantUtils.ConvertTo<double>(args[0])); ret = default; return true; } - if (method == MethodName.Bazz && args.Count == 1) { - var callRet = Bazz(global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(args[0])); + if (method == MethodName.@Bazz && args.Count == 1) { + var callRet = @Bazz(global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.StringName>(args[0])); ret = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(callRet); return true; } @@ -51,10 +51,10 @@ partial class ScriptBoilerplate [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool HasGodotClassMethod(in godot_string_name method) { - if (method == MethodName._Process) { + if (method == MethodName.@_Process) { return true; } - if (method == MethodName.Bazz) { + if (method == MethodName.@Bazz) { return true; } return base.HasGodotClassMethod(method); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs index 81cc27502f..ef0eba5e09 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptProperties.generated.cs @@ -11,22 +11,22 @@ partial class ScriptBoilerplate /// <summary> /// Cached name for the '_nodePath' field. /// </summary> - public new static readonly global::Godot.StringName _nodePath = "_nodePath"; + public new static readonly global::Godot.StringName @_nodePath = "_nodePath"; /// <summary> /// Cached name for the '_velocity' field. /// </summary> - public new static readonly global::Godot.StringName _velocity = "_velocity"; + public new static readonly global::Godot.StringName @_velocity = "_velocity"; } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { - if (name == PropertyName._nodePath) { - this._nodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value); + if (name == PropertyName.@_nodePath) { + this.@_nodePath = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.NodePath>(value); return true; } - if (name == PropertyName._velocity) { - this._velocity = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); + if (name == PropertyName.@_velocity) { + this.@_velocity = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value); return true; } return base.SetGodotClassPropertyValue(name, value); @@ -35,12 +35,12 @@ partial class ScriptBoilerplate [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - if (name == PropertyName._nodePath) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this._nodePath); + if (name == PropertyName.@_nodePath) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.NodePath>(this.@_nodePath); return true; } - if (name == PropertyName._velocity) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this._velocity); + if (name == PropertyName.@_velocity) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@_velocity); return true; } return base.GetGodotClassPropertyValue(name, out value); @@ -54,8 +54,8 @@ partial class ScriptBoilerplate internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() { var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); - properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName._nodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); - properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName._velocity, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)22, name: PropertyName.@_nodePath, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@_velocity, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); return properties; } #pragma warning restore CS0109 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs index 28bc863b0a..38f532c502 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ScriptBoilerplate_ScriptSerialization.generated.cs @@ -8,17 +8,17 @@ partial class ScriptBoilerplate protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info) { base.SaveGodotObjectData(info); - info.AddProperty(PropertyName._nodePath, global::Godot.Variant.From<global::Godot.NodePath>(this._nodePath)); - info.AddProperty(PropertyName._velocity, global::Godot.Variant.From<int>(this._velocity)); + info.AddProperty(PropertyName.@_nodePath, global::Godot.Variant.From<global::Godot.NodePath>(this.@_nodePath)); + info.AddProperty(PropertyName.@_velocity, global::Godot.Variant.From<int>(this.@_velocity)); } /// <inheritdoc/> [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info) { base.RestoreGodotObjectData(info); - if (info.TryGetProperty(PropertyName._nodePath, out var _value__nodePath)) - this._nodePath = _value__nodePath.As<global::Godot.NodePath>(); - if (info.TryGetProperty(PropertyName._velocity, out var _value__velocity)) - this._velocity = _value__velocity.As<int>(); + if (info.TryGetProperty(PropertyName.@_nodePath, out var _value__nodePath)) + this.@_nodePath = _value__nodePath.As<global::Godot.NodePath>(); + if (info.TryGetProperty(PropertyName.@_velocity, out var _value__velocity)) + this.@_velocity = _value__velocity.As<int>(); } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs index 067783ea66..4613d883c2 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs @@ -1,4 +1,5 @@ using Godot; +using Godot.Collections; public partial class ExportDiagnostics_GD0107_OK : Node { @@ -6,7 +7,31 @@ public partial class ExportDiagnostics_GD0107_OK : Node public Node NodeField; [Export] + public Node[] SystemArrayOfNodesField; + + [Export] + public Array<Node> GodotArrayOfNodesField; + + [Export] + public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyField; + + [Export] + public Dictionary<string, Node> GodotDictionaryWithNodeAsValueField; + + [Export] public Node NodeProperty { get; set; } + + [Export] + public Node[] SystemArrayOfNodesProperty { get; set; } + + [Export] + public Array<Node> GodotArrayOfNodesProperty { get; set; } + + [Export] + public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyProperty { get; set; } + + [Export] + public Dictionary<string, Node> GodotDictionaryWithNodeAsValueProperty { get; set; } } public partial class ExportDiagnostics_GD0107_KO : Resource @@ -15,5 +40,29 @@ public partial class ExportDiagnostics_GD0107_KO : Resource public Node {|GD0107:NodeField|}; [Export] + public Node[] {|GD0107:SystemArrayOfNodesField|}; + + [Export] + public Array<Node> {|GD0107:GodotArrayOfNodesField|}; + + [Export] + public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyField|}; + + [Export] + public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueField|}; + + [Export] public Node {|GD0107:NodeProperty|} { get; set; } + + [Export] + public Node[] {|GD0107:SystemArrayOfNodesProperty|} { get; set; } + + [Export] + public Array<Node> {|GD0107:GodotArrayOfNodesProperty|} { get; set; } + + [Export] + public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyProperty|} { get; set; } + + [Export] + public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueProperty|} { get; set; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj index 1aa2979e76..8e407da7a6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -9,7 +9,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>Godot.SourceGenerators</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs index 4cf6a9f431..bb4c4824e7 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -88,7 +88,8 @@ namespace Godot.SourceGenerators HideQuaternionEdit = 35, Password = 36, LayersAvoidance = 37, - Max = 38 + DictionaryType = 38, + Max = 39 } [Flags] diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs index d272832950..f5f51722b4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -274,6 +274,14 @@ namespace Godot.SourceGenerators return null; } + public static ITypeSymbol[]? GetGenericElementTypes(ITypeSymbol typeSymbol) + { + if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType) + return genericType.TypeArguments.ToArray(); + + return null; + } + private static StringBuilder Append(this StringBuilder source, string a, string b) => source.Append(a).Append(b); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 39d3a6f94e..e482d9ca79 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -158,7 +158,7 @@ namespace Godot.SourceGenerators .Append("' method.\n") .Append(" /// </summary>\n"); - source.Append(" public new static readonly global::Godot.StringName "); + source.Append(" public new static readonly global::Godot.StringName @"); source.Append(methodName); source.Append(" = \""); source.Append(methodName); @@ -287,7 +287,7 @@ namespace Godot.SourceGenerators private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) { - source.Append(" methods.Add(new(name: MethodName.") + source.Append(" methods.Add(new(name: MethodName.@") .Append(methodInfo.Name) .Append(", returnVal: "); @@ -414,7 +414,7 @@ namespace Godot.SourceGenerators ) { source.Append(" "); - source.Append("if (method == MethodName."); + source.Append("if (method == MethodName.@"); source.Append(methodName); source.Append(") {\n return true;\n }\n"); } @@ -426,7 +426,7 @@ namespace Godot.SourceGenerators { string methodName = method.Method.Name; - source.Append(" if (method == MethodName."); + source.Append(" if (method == MethodName.@"); source.Append(methodName); source.Append(" && args.Count == "); source.Append(method.ParamTypes.Length); @@ -437,6 +437,7 @@ namespace Godot.SourceGenerators else source.Append(" "); + source.Append("@"); source.Append(methodName); source.Append("("); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 02c2cd4034..0f86b3b91c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -144,7 +145,7 @@ namespace Godot.SourceGenerators .Append("' property.\n") .Append(" /// </summary>\n"); - source.Append(" public new static readonly global::Godot.StringName "); + source.Append(" public new static readonly global::Godot.StringName @"); source.Append(propertyName); source.Append(" = \""); source.Append(propertyName); @@ -161,7 +162,7 @@ namespace Godot.SourceGenerators .Append("' field.\n") .Append(" /// </summary>\n"); - source.Append(" public new static readonly global::Godot.StringName "); + source.Append(" public new static readonly global::Godot.StringName @"); source.Append(fieldName); source.Append(" = \""); source.Append(fieldName); @@ -316,10 +317,10 @@ namespace Godot.SourceGenerators { source.Append(" "); - source.Append("if (name == PropertyName.") + source.Append("if (name == PropertyName.@") .Append(propertyMemberName) .Append(") {\n") - .Append(" this.") + .Append(" this.@") .Append(propertyMemberName) .Append(" = ") .AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType) @@ -337,11 +338,11 @@ namespace Godot.SourceGenerators { source.Append(" "); - source.Append("if (name == PropertyName.") + source.Append("if (name == PropertyName.@") .Append(propertyMemberName) .Append(") {\n") .Append(" value = ") - .AppendManagedToNativeVariantExpr("this." + propertyMemberName, + .AppendManagedToNativeVariantExpr("this.@" + propertyMemberName, propertyTypeSymbol, propertyMarshalType) .Append(";\n") .Append(" return true;\n") @@ -367,7 +368,7 @@ namespace Godot.SourceGenerators { source.Append(" properties.Add(new(type: (global::Godot.Variant.Type)") .Append((int)propertyInfo.Type) - .Append(", name: PropertyName.") + .Append(", name: PropertyName.@") .Append(propertyInfo.Name) .Append(", hint: (global::Godot.PropertyHint)") .Append((int)propertyInfo.Hint) @@ -728,8 +729,72 @@ namespace Godot.SourceGenerators if (!isTypeArgument && variantType == VariantType.Dictionary) { - // TODO: Dictionaries are not supported in the inspector - return false; + var elementTypes = MarshalUtils.GetGenericElementTypes(type); + + if (elementTypes == null) + return false; // Non-generic Dictionary, so there's no hint to add + Debug.Assert(elementTypes.Length == 2); + + var keyElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[0], typeCache)!.Value; + var keyElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(keyElementMarshalType)!.Value; + var keyIsPresetHint = false; + var keyHintString = (string?)null; + + if (keyElementVariantType == VariantType.String || keyElementVariantType == VariantType.StringName) + keyIsPresetHint = GetStringArrayEnumHint(keyElementVariantType, exportAttr, out keyHintString); + + if (!keyIsPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[0], + exportAttr, keyElementVariantType, isTypeArgument: true, + out var keyElementHint, out var keyElementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + keyHintString = (int)keyElementVariantType + "/" + (int)keyElementHint + ":"; + + if (keyElementHintString != null) + keyHintString += keyElementHintString; + } + else + { + keyHintString = (int)keyElementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + var valueElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[1], typeCache)!.Value; + var valueElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(valueElementMarshalType)!.Value; + var valueIsPresetHint = false; + var valueHintString = (string?)null; + + if (valueElementVariantType == VariantType.String || valueElementVariantType == VariantType.StringName) + valueIsPresetHint = GetStringArrayEnumHint(valueElementVariantType, exportAttr, out valueHintString); + + if (!valueIsPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[1], + exportAttr, valueElementVariantType, isTypeArgument: true, + out var valueElementHint, out var valueElementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + valueHintString = (int)valueElementVariantType + "/" + (int)valueElementHint + ":"; + + if (valueElementHintString != null) + valueHintString += valueElementHintString; + } + else + { + valueHintString = (int)valueElementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + hint = PropertyHint.DictionaryType; + + hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null; + return hintString != null; } return false; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index d13a828875..626f51ecae 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -196,16 +196,13 @@ namespace Godot.SourceGenerators continue; } - if (marshalType == MarshalType.GodotObjectOrDerived) + if (!isNode && MemberHasNodeType(propertyType, marshalType.Value)) { - if (!isNode && propertyType.InheritsFrom("GodotSharp", GodotClasses.Node)) - { - context.ReportDiagnostic(Diagnostic.Create( - Common.OnlyNodesShouldExportNodesRule, - property.Locations.FirstLocationWithSourceTreeOrDefault() - )); - continue; - } + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyNodesShouldExportNodesRule, + property.Locations.FirstLocationWithSourceTreeOrDefault() + )); + continue; } var propertyDeclarationSyntax = property.DeclaringSyntaxReferences @@ -315,16 +312,13 @@ namespace Godot.SourceGenerators continue; } - if (marshalType == MarshalType.GodotObjectOrDerived) + if (!isNode && MemberHasNodeType(fieldType, marshalType.Value)) { - if (!isNode && fieldType.InheritsFrom("GodotSharp", GodotClasses.Node)) - { - context.ReportDiagnostic(Diagnostic.Create( - Common.OnlyNodesShouldExportNodesRule, - field.Locations.FirstLocationWithSourceTreeOrDefault() - )); - continue; - } + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyNodesShouldExportNodesRule, + field.Locations.FirstLocationWithSourceTreeOrDefault() + )); + continue; } EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences @@ -386,7 +380,7 @@ namespace Godot.SourceGenerators source.Append(" = "); source.Append(exportedMember.Value ?? "default"); source.Append(";\n"); - source.Append(" values.Add(PropertyName."); + source.Append(" values.Add(PropertyName.@"); source.Append(exportedMember.Name); source.Append(", "); source.AppendManagedToVariantExpr(defaultValueLocalName, @@ -424,6 +418,27 @@ namespace Godot.SourceGenerators context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); } + private static bool MemberHasNodeType(ITypeSymbol memberType, MarshalType marshalType) + { + if (marshalType == MarshalType.GodotObjectOrDerived) + { + return memberType.InheritsFrom("GodotSharp", GodotClasses.Node); + } + if (marshalType == MarshalType.GodotObjectOrDerivedArray) + { + var elementType = ((IArrayTypeSymbol)memberType).ElementType; + return elementType.InheritsFrom("GodotSharp", GodotClasses.Node); + } + if (memberType is INamedTypeSymbol { IsGenericType: true } genericType) + { + return genericType.TypeArguments + .Any(static typeArgument + => typeArgument.InheritsFrom("GodotSharp", GodotClasses.Node)); + } + + return false; + } + private struct ExportedPropertyMetadata { public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index df0484333a..937da76335 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -169,10 +169,10 @@ namespace Godot.SourceGenerators { string propertyName = property.PropertySymbol.Name; - source.Append(" info.AddProperty(PropertyName.") + source.Append(" info.AddProperty(PropertyName.@") .Append(propertyName) .Append(", ") - .AppendManagedToVariantExpr(string.Concat("this.", propertyName), + .AppendManagedToVariantExpr(string.Concat("this.@", propertyName), property.PropertySymbol.Type, property.Type) .Append(");\n"); } @@ -183,10 +183,10 @@ namespace Godot.SourceGenerators { string fieldName = field.FieldSymbol.Name; - source.Append(" info.AddProperty(PropertyName.") + source.Append(" info.AddProperty(PropertyName.@") .Append(fieldName) .Append(", ") - .AppendManagedToVariantExpr(string.Concat("this.", fieldName), + .AppendManagedToVariantExpr(string.Concat("this.@", fieldName), field.FieldSymbol.Type, field.Type) .Append(");\n"); } @@ -197,7 +197,7 @@ namespace Godot.SourceGenerators { string signalName = signalDelegate.Name; - source.Append(" info.AddSignalEventDelegate(SignalName.") + source.Append(" info.AddSignalEventDelegate(SignalName.@") .Append(signalName) .Append(", this.backing_") .Append(signalName) @@ -218,12 +218,12 @@ namespace Godot.SourceGenerators { string propertyName = property.PropertySymbol.Name; - source.Append(" if (info.TryGetProperty(PropertyName.") + source.Append(" if (info.TryGetProperty(PropertyName.@") .Append(propertyName) .Append(", out var _value_") .Append(propertyName) .Append("))\n") - .Append(" this.") + .Append(" this.@") .Append(propertyName) .Append(" = ") .AppendVariantToManagedExpr(string.Concat("_value_", propertyName), @@ -237,12 +237,12 @@ namespace Godot.SourceGenerators { string fieldName = field.FieldSymbol.Name; - source.Append(" if (info.TryGetProperty(PropertyName.") + source.Append(" if (info.TryGetProperty(PropertyName.@") .Append(fieldName) .Append(", out var _value_") .Append(fieldName) .Append("))\n") - .Append(" this.") + .Append(" this.@") .Append(fieldName) .Append(" = ") .AppendVariantToManagedExpr(string.Concat("_value_", fieldName), @@ -259,7 +259,7 @@ namespace Godot.SourceGenerators source.Append(" if (info.TryGetSignalEventDelegate<") .Append(signalDelegateQualifiedName) - .Append(">(SignalName.") + .Append(">(SignalName.@") .Append(signalName) .Append(", out var _value_") .Append(signalName) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index deac5f2bcf..54f2212339 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -212,7 +212,7 @@ namespace Godot.SourceGenerators .Append("' signal.\n") .Append(" /// </summary>\n"); - source.Append(" public new static readonly global::Godot.StringName "); + source.Append(" public new static readonly global::Godot.StringName @"); source.Append(signalName); source.Append(" = \""); source.Append(signalName); @@ -278,7 +278,7 @@ namespace Godot.SourceGenerators source.Append(" public event ") .Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()) - .Append(" ") + .Append(" @") .Append(signalName) .Append(" {\n") .Append(" add => backing_") @@ -353,7 +353,7 @@ namespace Godot.SourceGenerators private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) { - source.Append(" signals.Add(new(name: SignalName.") + source.Append(" signals.Add(new(name: SignalName.@") .Append(methodInfo.Name) .Append(", returnVal: "); @@ -475,7 +475,7 @@ namespace Godot.SourceGenerators ) { source.Append(" "); - source.Append("if (signal == SignalName."); + source.Append("if (signal == SignalName.@"); source.Append(signalName); source.Append(") {\n return true;\n }\n"); } @@ -488,7 +488,7 @@ namespace Godot.SourceGenerators string signalName = signal.Name; var invokeMethodData = signal.InvokeMethodData; - source.Append(" if (signal == SignalName."); + source.Append(" if (signal == SignalName.@"); source.Append(signalName); source.Append(" && args.Count == "); source.Append(invokeMethodData.ParamTypes.Length); diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index b699765b8e..032d067ae4 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -86,7 +86,7 @@ namespace GodotTools.BuildLogger WriteLine(line); - string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + string errorLine = $@"error,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; _issuesStreamWriter.WriteLine(errorLine); @@ -101,7 +101,7 @@ namespace GodotTools.BuildLogger WriteLine(line); - string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + string warningLine = $@"warning,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; _issuesStreamWriter.WriteLine(warningLine); diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj index f23f2b9a8c..208e6d8f41 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -5,7 +5,6 @@ <TargetFramework>net6.0-windows</TargetFramework> <LangVersion>10</LangVersion> <Nullable>enable</Nullable> - <RuntimeIdentifier>win-x86</RuntimeIdentifier> <SelfContained>False</SelfContained> <RollForward>LatestMajor</RollForward> </PropertyGroup> diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index ebb2677361..d7877fa5fc 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -265,11 +265,6 @@ namespace GodotTools.Build success = Publish(buildInfo); } - if (!success) - { - ShowBuildErrorDialog("Failed to publish .NET project"); - } - return success; } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs index b6d6d9ebf8..35a62a0eab 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs @@ -280,7 +280,7 @@ namespace GodotTools.Build if (_problemsContextMenu.ItemCount > 0) { - _problemsContextMenu.Position = (Vector2I)(_problemsTree.GlobalPosition + position); + _problemsContextMenu.Position = (Vector2I)(GetScreenPosition() + position); _problemsContextMenu.Popup(); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index d3720dcb72..6fd84d3834 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -75,7 +75,19 @@ namespace GodotTools.Export }; } - private string? _maybeLastExportError; + private void AddExceptionMessage(EditorExportPlatform platform, Exception exception) + { + string? exceptionMessage = exception.Message; + if (string.IsNullOrEmpty(exceptionMessage)) + { + exceptionMessage = $"Exception thrown: {exception.GetType().Name}"; + } + + platform.AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", exceptionMessage); + + // We also print exceptions as we receive them to stderr. + Console.Error.WriteLine(exception); + } // With this method we can override how a file is exported in the PCK public override void _ExportFile(string path, string type, string[] features) @@ -92,8 +104,8 @@ namespace GodotTools.Export if (!ProjectContainsDotNet()) { - _maybeLastExportError = $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" + - "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again."; + GetExportPlatform().AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" + + "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again."); throw new InvalidOperationException($"{path} is a C# file but no solution file exists."); } @@ -124,16 +136,7 @@ namespace GodotTools.Export } catch (Exception e) { - _maybeLastExportError = e.Message; - - // 'maybeLastExportError' cannot be null or empty if there was an error, so we - // must consider the possibility of exceptions being thrown without a message. - if (string.IsNullOrEmpty(_maybeLastExportError)) - _maybeLastExportError = $"Exception thrown: {e.GetType().Name}"; - - GD.PushError($"Failed to export project: {_maybeLastExportError}"); - Console.Error.WriteLine(e); - // TODO: Do something on error once _ExportBegin supports failing. + AddExceptionMessage(GetExportPlatform(), e); } } @@ -144,7 +147,9 @@ namespace GodotTools.Export if (!ProjectContainsDotNet()) return; - if (!DeterminePlatformFromFeatures(features, out string? platform)) + string osName = GetExportPlatform().GetOsName(); + + if (!TryDeterminePlatformFromOSName(osName, out string? platform)) throw new NotSupportedException("Target platform not supported."); if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS } @@ -240,7 +245,6 @@ namespace GodotTools.Export { publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet", $"{buildConfig}-{runtimeIdentifier}"); - } outputPaths.Add(publishOutputDir); @@ -317,6 +321,30 @@ namespace GodotTools.Export { if (embedBuildResults) { + if (platform == OS.Platforms.Android) + { + if (IsSharedObject(Path.GetFileName(path))) + { + AddSharedObject(path, tags: new string[] { arch }, + Path.Join(projectDataDirName, + Path.GetRelativePath(publishOutputDir, + Path.GetDirectoryName(path)!))); + + return; + } + + static bool IsSharedObject(string fileName) + { + if (fileName.EndsWith(".so") || fileName.EndsWith(".a") + || fileName.EndsWith(".jar") || fileName.EndsWith(".dex")) + { + return true; + } + + return false; + } + } + string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path)); byte[] fileData = File.ReadAllBytes(path); string hash = Convert.ToBase64String(SHA512.HashData(fileData)); @@ -355,24 +383,23 @@ namespace GodotTools.Export if (outputPaths.Count > 2) { // lipo the simulator binaries together - // TODO: Move this to the native lipo implementation we have in the macos export plugin. - var lipoArgs = new List<string>(); - lipoArgs.Add("-create"); - lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib"))); - lipoArgs.Add("-output"); - lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib")); - int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs); - if (lipoExitCode != 0) - throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}."); + string outputPath = Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib"); + string[] files = outputPaths + .Skip(1) + .Select(path => Path.Combine(path, $"{GodotSharpDirs.ProjectAssemblyName}.dylib")) + .ToArray(); + + if (!Internal.LipOCreateFile(outputPath, files)) + { + throw new InvalidOperationException($"Failed to 'lipo' simulator binaries."); + } outputPaths.RemoveRange(2, outputPaths.Count - 2); } - var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, - $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework"); - if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, - Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath))) + string xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework"); + if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, xcFrameworkPath)) { throw new InvalidOperationException("Failed to generate xcframework."); } @@ -446,25 +473,22 @@ namespace GodotTools.Export Directory.Delete(folder, recursive: true); } _tempFolders.Clear(); - - // TODO: The following is just a workaround until the export plugins can be made to abort with errors - - // We check for empty as well, because it's set to empty after hot-reloading - if (!string.IsNullOrEmpty(_maybeLastExportError)) - { - string lastExportError = _maybeLastExportError; - _maybeLastExportError = null; - - GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project"); - } } - private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform) + /// <summary> + /// Tries to determine the platform from the export preset's platform OS name. + /// </summary> + /// <param name="osName">Name of the export operating system.</param> + /// <param name="platform">Platform name for the recognized supported platform.</param> + /// <returns> + /// <see langword="true"/> when the platform OS name is recognized as a supported platform, + /// <see langword="false"/> otherwise. + /// </returns> + private static bool TryDeterminePlatformFromOSName(string osName, [NotNullWhen(true)] out string? platform) { - foreach (var feature in features) + if (OS.PlatformFeatureMap.TryGetValue(osName, out platform)) { - if (OS.PlatformFeatureMap.TryGetValue(feature, out platform)) - return true; + return true; } platform = null; diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs deleted file mode 100644 index 023f46b685..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.IO; - -namespace GodotTools.Export -{ - public static class XcodeHelper - { - private static string? _XcodePath = null; - - public static string XcodePath - { - get - { - if (_XcodePath == null) - { - _XcodePath = FindXcode(); - - if (_XcodePath == null) - throw new FileNotFoundException("Could not find Xcode."); - } - - return _XcodePath; - } - } - - private static string? FindSelectedXcode() - { - var outputWrapper = new Godot.Collections.Array(); - - int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, output: outputWrapper); - - if (exitCode == 0) - { - string output = (string)outputWrapper[0]; - return output.Trim(); - } - - Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}"); - - return null; - } - - public static string? FindXcode() - { - string? selectedXcode = FindSelectedXcode(); - if (selectedXcode != null) - { - if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer"))) - return selectedXcode; - - // The path already pointed to Contents/Developer - var dirInfo = new DirectoryInfo(selectedXcode); - if (dirInfo is not { Parent.Name: "Contents", Name: "Developer" }) - { - Console.WriteLine(Path.GetDirectoryName(selectedXcode)); - Console.WriteLine(System.IO.Directory.GetParent(selectedXcode)?.Name); - Console.Error.WriteLine("Unrecognized path for selected Xcode"); - } - else - { - return System.IO.Path.GetFullPath($"{selectedXcode}/../.."); - } - } - else - { - Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path"); - } - - const string XcodeHintPath = "/Applications/Xcode.app"; - - if (Directory.Exists(XcodeHintPath)) - { - if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer"))) - return XcodeHintPath; - - Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory"); - } - - return null; - } - - public static string FindXcodeTool(string toolName) - { - string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain"); - - string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName); - if (File.Exists(path)) - return path; - - throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}"); - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index d3899c809a..788b46ab9a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Reflection; using GodotTools.Build; using GodotTools.Ides; using GodotTools.Ides.Rider; @@ -259,11 +260,12 @@ namespace GodotTools var args = new List<string> { + Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.dll"), GodotSharpDirs.ProjectSlnPath, line >= 0 ? $"{scriptPath};{line + 1};{col + 1}" : scriptPath }; - string command = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.exe"); + string command = DotNetFinder.FindDotNetExe() ?? "dotnet"; try { @@ -700,6 +702,23 @@ namespace GodotTools private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) { Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); + + var populateConstructorMethod = + AppDomain.CurrentDomain + .GetAssemblies() + .First(x => x.GetName().Name == "GodotSharpEditor") + .GetType("Godot.EditorConstructors")? + .GetMethod("AddEditorConstructors", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + if (populateConstructorMethod == null) + { + throw new MissingMethodException("Godot.EditorConstructors", + "AddEditorConstructors"); + } + + populateConstructorMethod.Invoke(null, null); + return new GodotSharpEditor().NativeInstance; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 175bb78051..225ac4073b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -35,6 +35,13 @@ namespace GodotTools.Internals return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn); } + public static bool LipOCreateFile(string outputPath, string[] files) + { + using godot_string outputPathIn = Marshaling.ConvertStringToNative(outputPath); + using godot_packed_string_array filesIn = Marshaling.ConvertSystemArrayToNativePackedStringArray(files); + return godot_icall_Internal_LipOCreateFile(outputPathIn, filesIn); + } + public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits(); public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble(); @@ -121,6 +128,8 @@ namespace GodotTools.Internals private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId); + private static partial bool godot_icall_Internal_LipOCreateFile(in godot_string outputPath, in godot_packed_string_array files); + private static partial bool godot_icall_Internal_GodotIs32Bits(); private static partial bool godot_icall_Internal_GodotIsRealTDouble(); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 9a76a25639..a467aae2e9 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -77,6 +77,10 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define BINDINGS_GLOBAL_SCOPE_CLASS "GD" #define BINDINGS_NATIVE_NAME_FIELD "NativeName" +#define BINDINGS_CLASS_CONSTRUCTOR "Constructors" +#define BINDINGS_CLASS_CONSTRUCTOR_EDITOR "EditorConstructors" +#define BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY "BuiltInMethodConstructors" + #define CS_PARAM_MEMORYOWN "memoryOwn" #define CS_PARAM_METHODBIND "method" #define CS_PARAM_INSTANCE "ptr" @@ -1452,7 +1456,7 @@ Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_it } const TypeInterface *return_type = _get_type_or_null(imethod.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found."); String im_unique_sig = get_ret_unique_sig(return_type) + ",CallMethodBind"; @@ -1463,7 +1467,7 @@ Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_it // Get arguments information for (const ArgumentInterface &iarg : imethod.arguments) { const TypeInterface *arg_type = _get_type_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); im_unique_sig += ","; im_unique_sig += get_arg_unique_sig(*arg_type); @@ -1737,6 +1741,69 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { compile_items.push_back(output_file); } + // Generate source file for built-in type constructor dictionary. + + { + StringBuilder cs_built_in_ctors_content; + + cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n"); + cs_built_in_ctors_content.append("using System;\n" + "using System.Collections.Generic;\n" + "\n"); + cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR "\n{"); + + cs_built_in_ctors_content.append(MEMBER_BEGIN "internal static readonly Dictionary<string, Func<IntPtr, GodotObject>> " BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n"); + + cs_built_in_ctors_content.append(MEMBER_BEGIN "public static GodotObject Invoke(string nativeTypeNameStr, IntPtr nativeObjectPtr)\n"); + cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK); + cs_built_in_ctors_content.append(INDENT2 "if (!" BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".TryGetValue(nativeTypeNameStr, out var constructor))\n"); + cs_built_in_ctors_content.append(INDENT3 "throw new InvalidOperationException(\"Wrapper class not found for type: \" + nativeTypeNameStr);\n"); + cs_built_in_ctors_content.append(INDENT2 "return constructor(nativeObjectPtr);\n"); + cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK); + + cs_built_in_ctors_content.append(MEMBER_BEGIN "static " BINDINGS_CLASS_CONSTRUCTOR "()\n"); + cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK); + cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY " = new();\n"); + + for (const KeyValue<StringName, TypeInterface> &E : obj_types) { + const TypeInterface &itype = E.value; + + if (itype.api_type != ClassDB::API_CORE || itype.is_singleton_instance) { + continue; + } + + if (itype.is_deprecated) { + cs_built_in_ctors_content.append("#pragma warning disable CS0618\n"); + } + + cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".Add(\""); + cs_built_in_ctors_content.append(itype.name); + cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new "); + cs_built_in_ctors_content.append(itype.proxy_name); + if (itype.is_singleton && !itype.is_compat_singleton) { + cs_built_in_ctors_content.append("Instance"); + } + cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n"); + + if (itype.is_deprecated) { + cs_built_in_ctors_content.append("#pragma warning restore CS0618\n"); + } + } + + cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK); + + cs_built_in_ctors_content.append(CLOSE_BLOCK); + + String constructors_file = path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR ".cs"); + Error err = _save_file(constructors_file, cs_built_in_ctors_content); + + if (err != OK) { + return err; + } + + compile_items.push_back(constructors_file); + } + // Generate native calls StringBuilder cs_icalls_content; @@ -1844,6 +1911,57 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) { compile_items.push_back(output_file); } + // Generate source file for editor type constructor dictionary. + + { + StringBuilder cs_built_in_ctors_content; + + cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n"); + cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR_EDITOR "\n{"); + + cs_built_in_ctors_content.append(MEMBER_BEGIN "private static void AddEditorConstructors()\n"); + cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK); + cs_built_in_ctors_content.append(INDENT2 "var builtInMethodConstructors = " BINDINGS_CLASS_CONSTRUCTOR "." BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n"); + + for (const KeyValue<StringName, TypeInterface> &E : obj_types) { + const TypeInterface &itype = E.value; + + if (itype.api_type != ClassDB::API_EDITOR || itype.is_singleton_instance) { + continue; + } + + if (itype.is_deprecated) { + cs_built_in_ctors_content.append("#pragma warning disable CS0618\n"); + } + + cs_built_in_ctors_content.append(INDENT2 "builtInMethodConstructors.Add(\""); + cs_built_in_ctors_content.append(itype.name); + cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new "); + cs_built_in_ctors_content.append(itype.proxy_name); + if (itype.is_singleton && !itype.is_compat_singleton) { + cs_built_in_ctors_content.append("Instance"); + } + cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n"); + + if (itype.is_deprecated) { + cs_built_in_ctors_content.append("#pragma warning restore CS0618\n"); + } + } + + cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK); + + cs_built_in_ctors_content.append(CLOSE_BLOCK); + + String constructors_file = path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR_EDITOR ".cs"); + Error err = _save_file(constructors_file, cs_built_in_ctors_content); + + if (err != OK) { + return err; + } + + compile_items.push_back(constructors_file); + } + // Generate native calls StringBuilder cs_icalls_content; @@ -2184,7 +2302,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Add native constructor static field output << MEMBER_BEGIN << "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" - << INDENT1 "private static readonly unsafe delegate* unmanaged<IntPtr> " + << INDENT1 "private static readonly unsafe delegate* unmanaged<godot_bool, IntPtr> " << CS_STATIC_FIELD_NATIVE_CTOR " = " ICALL_CLASSDB_GET_CONSTRUCTOR << "(" BINDINGS_NATIVE_NAME_FIELD ");\n"; } @@ -2210,6 +2328,15 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1; } + output << MEMBER_BEGIN "internal " << itype.proxy_name << "(IntPtr " CS_PARAM_INSTANCE ") : this(" + << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1 + << INDENT2 "NativePtr = " CS_PARAM_INSTANCE ";\n" + << INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK + << INDENT3 "ConstructAndInitialize(null, " + << BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: " + << (itype.is_ref_counted ? "true" : "false") << ");\n" + << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1; + // Add.. em.. trick constructor. Sort of. output.append(MEMBER_BEGIN "internal "); output.append(itype.proxy_name); @@ -2313,7 +2440,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str const ArgumentInterface &iarg = *itr; const TypeInterface *arg_type = _get_type_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); if (i != 0) { output << ", "; @@ -2333,7 +2460,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str if (imethod.return_type.cname != name_cache.type_void) { const TypeInterface *return_type = _get_type_or_null(imethod.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found."); output << INDENT3 "ret = " << sformat(return_type->cs_managed_to_variant, "callRet", return_type->cs_type, return_type->name) @@ -2552,7 +2679,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type; const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name); - ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found + ERR_FAIL_NULL_V_MSG(prop_itype, ERR_BUG, "Property type '" + proptype_name.cname + "' was not found."); ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG, "Property type is a singleton: '" + p_itype.name + "." + String(p_iprop.cname) + "'."); @@ -2651,7 +2778,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) { const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG, "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'."); @@ -2690,7 +2817,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf const ArgumentInterface &first = p_imethod.arguments.front()->get(); for (const ArgumentInterface &iarg : p_imethod.arguments) { const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'."); @@ -2934,17 +3061,12 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) { String arguments_sig; - String delegate_type_params; - - if (!p_isignal.arguments.is_empty()) { - delegate_type_params += "<"; - } // Retrieve information from the arguments const ArgumentInterface &first = p_isignal.arguments.front()->get(); for (const ArgumentInterface &iarg : p_isignal.arguments) { const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'."); @@ -2959,18 +3081,13 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf if (&iarg != &first) { arguments_sig += ", "; - delegate_type_params += ", "; } - arguments_sig += arg_type->cs_type; + String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters); + + arguments_sig += arg_cs_type; arguments_sig += " "; arguments_sig += iarg.name; - - delegate_type_params += arg_type->cs_type; - } - - if (!p_isignal.arguments.is_empty()) { - delegate_type_params += ">"; } // Generate signal @@ -3013,14 +3130,20 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf int idx = 0; for (const ArgumentInterface &iarg : p_isignal.arguments) { const TypeInterface *arg_type = _get_type_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); if (idx != 0) { p_output << ", "; } - p_output << sformat(arg_type->cs_variant_to_managed, - "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name); + if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) { + String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters); + + p_output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name) << ")"; + } else { + p_output << sformat(arg_type->cs_variant_to_managed, + "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name); + } idx++; } @@ -3113,7 +3236,7 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, bool ret_void = p_icall.return_type.cname == name_cache.type_void; const TypeInterface *return_type = _get_type_or_null(p_icall.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_icall.return_type.cname + "' was not found."); StringBuilder c_func_sig; StringBuilder c_in_statements; @@ -3129,7 +3252,7 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, int i = 0; for (const TypeReference &arg_type_ref : p_icall.argument_types) { const TypeInterface *arg_type = _get_type_or_null(arg_type_ref); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + arg_type_ref.cname + "' was not found."); String c_param_name = "arg" + itos(i + 1); @@ -3389,7 +3512,7 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface String params = "<"; for (const TypeReference ¶m_type : p_generic_type_parameters) { const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type); - ERR_FAIL_NULL_V(param_itype, ""); // Parameter type not found + ERR_FAIL_NULL_V_MSG(param_itype, "", "Parameter type '" + param_type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "", "Generic type parameter is a singleton: '" + param_itype->name + "'."); @@ -3448,6 +3571,12 @@ StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metada case GodotTypeInfo::METADATA_INT_IS_UINT64: return "ulong"; break; + case GodotTypeInfo::METADATA_INT_IS_CHAR16: + return "char"; + break; + case GodotTypeInfo::METADATA_INT_IS_CHAR32: + // To prevent breaking compatibility, C# bindings need to keep using `long`. + return "long"; default: // Assume INT64 return "long"; @@ -3809,6 +3938,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) { imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string)); + } else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; + Vector<String> split = return_info.hint_string.split(";"); + imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0))); + imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -3836,6 +3970,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); + } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; + Vector<String> split = arginfo.hint_string.split(";"); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0))); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { @@ -3963,6 +4102,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); + } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; + Vector<String> split = arginfo.hint_string.split(";"); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0))); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 05dacd28fb..df240a5965 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -41,9 +41,11 @@ #include "core/os/os.h" #include "core/version.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/editor_main_screen.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" +#include "editor/export/lipo.h" #include "editor/gui/editor_run_bar.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" @@ -117,6 +119,13 @@ bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle #endif } +bool godot_icall_Internal_LipOCreateFile(const godot_string *p_output_path, const godot_packed_array *p_files) { + String output_path = *reinterpret_cast<const String *>(p_output_path); + PackedStringArray files = *reinterpret_cast<const PackedStringArray *>(p_files); + LipO lip; + return lip.create_file(output_path, files); +} + bool godot_icall_Internal_GodotIs32Bits() { return sizeof(void *) == 4; } @@ -143,7 +152,7 @@ bool godot_icall_Internal_IsAssembliesReloadingNeeded() { void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD - mono_bind::GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload); + callable_mp(mono_bind::GodotSharp::get_singleton(), &mono_bind::GodotSharp::reload_assemblies).call_deferred(p_soft_reload); #endif } @@ -157,7 +166,7 @@ bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, } void godot_icall_Internal_EditorNodeShowScriptScreen() { - EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT); + EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT); } void godot_icall_Internal_EditorRunPlay() { @@ -258,6 +267,7 @@ static const void *unmanaged_callbacks[]{ (void *)godot_icall_EditorProgress_Step, (void *)godot_icall_Internal_FullExportTemplatesDir, (void *)godot_icall_Internal_IsMacOSAppBundleInstalled, + (void *)godot_icall_Internal_LipOCreateFile, (void *)godot_icall_Internal_GodotIs32Bits, (void *)godot_icall_Internal_GodotIsRealTDouble, (void *)godot_icall_Internal_GodotMainIteration, diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp index 7fa482969e..9c37ac810a 100644 --- a/modules/mono/editor/hostfxr_resolver.cpp +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -216,6 +216,7 @@ bool get_default_installation_dir(String &r_dotnet_root) { #endif } +#ifndef WINDOWS_ENABLED bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) { Error err = OK; Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err); @@ -233,6 +234,7 @@ bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_ r_dotnet_root = line; return true; } +#endif bool get_dotnet_self_registered_dir(String &r_dotnet_root) { #if defined(WINDOWS_ENABLED) @@ -260,7 +262,7 @@ bool get_dotnet_self_registered_dir(String &r_dotnet_root) { return false; } - r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()); + r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()).replace("\\", "/"); RegCloseKey(hkey); return true; #else diff --git a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs index cd335934db..ece1ab44a2 100644 --- a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs +++ b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs @@ -3,6 +3,8 @@ using _BINDINGS_NAMESPACE_; using System; +[Tool] +[GlobalClass] public partial class VisualShaderNode_CLASS_ : _BASE_ { public override string _GetName() @@ -20,37 +22,37 @@ public partial class VisualShaderNode_CLASS_ : _BASE_ return ""; } - public override long _GetReturnIconType() + public override VisualShaderNode.PortType _GetReturnIconType() { return 0; } - public override long _GetInputPortCount() + public override int _GetInputPortCount() { return 0; } - public override string _GetInputPortName(long port) + public override string _GetInputPortName(int port) { return ""; } - public override long _GetInputPortType(long port) + public override VisualShaderNode.PortType _GetInputPortType(int port) { return 0; } - public override long _GetOutputPortCount() + public override int _GetOutputPortCount() { return 1; } - public override string _GetOutputPortName(long port) + public override string _GetOutputPortName(int port) { return "result"; } - public override long _GetOutputPortType(long port) + public override VisualShaderNode.PortType _GetOutputPortType(int port) { return 0; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index ab7f8ede44..ef550eab0d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -314,13 +314,20 @@ namespace Godot /// <returns>A vector representing the support.</returns> public readonly Vector3 GetSupport(Vector3 dir) { - Vector3 halfExtents = _size * 0.5f; - Vector3 ofs = _position + halfExtents; - - return ofs + new Vector3( - dir.X > 0f ? -halfExtents.X : halfExtents.X, - dir.Y > 0f ? -halfExtents.Y : halfExtents.Y, - dir.Z > 0f ? -halfExtents.Z : halfExtents.Z); + Vector3 support = _position; + if (dir.X > 0.0f) + { + support.X += _size.X; + } + if (dir.Y > 0.0f) + { + support.Y += _size.Y; + } + if (dir.Z > 0.0f) + { + support.Z += _size.Z; + } + return support; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 01d9103221..91d49854c7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -93,27 +93,15 @@ namespace Godot.Bridge internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName, IntPtr godotObject) { - // TODO: Optimize with source generators and delegate pointers. - try { using var stringName = StringName.CreateTakingOwnershipOfDisposableValue( NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName))); string nativeTypeNameStr = stringName.ToString(); - Type nativeType = TypeGetProxyClass(nativeTypeNameStr) ?? throw new InvalidOperationException( - "Wrapper class not found for type: " + nativeTypeNameStr); - var obj = (GodotObject)FormatterServices.GetUninitializedObject(nativeType); - - var ctor = nativeType.GetConstructor( - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, - null, Type.EmptyTypes, null); - - obj.NativePtr = godotObject; - - _ = ctor!.Invoke(obj, null); + var instance = Constructors.Invoke(nativeTypeNameStr, godotObject); - return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj)); + return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(instance)); } catch (Exception e) { @@ -241,11 +229,17 @@ namespace Godot.Bridge if (outIconPath != null) { - var iconAttr = scriptType.GetCustomAttributes(inherit: false) + IconAttribute? iconAttr = scriptType.GetCustomAttributes(inherit: false) .OfType<IconAttribute>() .FirstOrDefault(); - *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path); + if (!string.IsNullOrEmpty(iconAttr?.Path)) + { + string iconPath = iconAttr.Path.IsAbsolutePath() + ? iconAttr.Path.SimplifyPath() + : scriptPathStr.GetBaseDir().PathJoin(iconAttr.Path).SimplifyPath(); + *outIconPath = Marshaling.ConvertStringToNative(iconPath); + } } if (outBaseType != null) @@ -302,66 +296,6 @@ namespace Godot.Bridge } } - private static Type? TypeGetProxyClass(string nativeTypeNameStr) - { - // Performance is not critical here as this will be replaced with a generated dictionary. - - if (nativeTypeNameStr[0] == '_') - nativeTypeNameStr = nativeTypeNameStr.Substring(1); - - Type? wrapperType = typeof(GodotObject).Assembly.GetType("Godot." + nativeTypeNameStr); - - if (wrapperType == null) - { - wrapperType = GetTypeByGodotClassAttr(typeof(GodotObject).Assembly, nativeTypeNameStr); - } - - if (wrapperType == null) - { - var editorAssembly = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor"); - - if (editorAssembly != null) - { - wrapperType = editorAssembly.GetType("Godot." + nativeTypeNameStr); - - if (wrapperType == null) - { - wrapperType = GetTypeByGodotClassAttr(editorAssembly, nativeTypeNameStr); - } - } - } - - static Type? GetTypeByGodotClassAttr(Assembly assembly, string nativeTypeNameStr) - { - var types = assembly.GetTypes(); - foreach (var type in types) - { - var attr = type.GetCustomAttribute<GodotClassNameAttribute>(); - if (attr?.Name == nativeTypeNameStr) - { - return type; - } - } - return null; - } - - static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed; - - if (wrapperType != null && IsStatic(wrapperType)) - { - // A static class means this is a Godot singleton class. Try to get the Instance proxy type. - wrapperType = TypeGetProxyClass($"{wrapperType.Name}Instance"); - if (wrapperType == null) - { - // Otherwise, fallback to GodotObject. - return typeof(GodotObject); - } - } - - return wrapperType; - } - // Called from GodotPlugins // ReSharper disable once UnusedMember.Local public static void LookupScriptsInAssembly(Assembly assembly) @@ -427,10 +361,11 @@ namespace Godot.Bridge // This method may be called before initialization. if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint()) { - foreach (var scriptPath in _pathTypeBiMap.Paths) + if (_pathTypeBiMap.Paths.Count > 0) { - using godot_string nativeScriptPath = Marshaling.ConvertStringToNative(scriptPath); - NativeFuncs.godotsharp_internal_editor_file_system_update_file(nativeScriptPath); + string[] scriptPaths = _pathTypeBiMap.Paths.ToArray(); + using godot_packed_string_array scriptPathsNative = Marshaling.ConvertSystemArrayToNativePackedStringArray(scriptPaths); + NativeFuncs.godotsharp_internal_editor_file_system_update_files(scriptPathsNative); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs index 1ec1a75516..29fa13d625 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -64,7 +65,7 @@ public static partial class ScriptManagerBridge private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new(); private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new(); - public System.Collections.Generic.IEnumerable<string> Paths => _pathTypeMap.Keys; + public IReadOnlyCollection<string> Paths => _pathTypeMap.Keys; public void Add(string scriptPath, Type scriptType) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs index 563a6abe9b..1fc6e54e09 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Godot.NativeInterop; #nullable enable @@ -51,7 +52,7 @@ namespace Godot /// </summary> /// <param name="instance">The instance to check.</param> /// <returns>If the instance is a valid object.</returns> - public static bool IsInstanceValid(GodotObject? instance) + public static bool IsInstanceValid([NotNullWhen(true)] GodotObject? instance) { return instance != null && instance.NativeInstance != IntPtr.Zero; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index 0be9cdc953..a429931399 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -29,8 +29,19 @@ namespace Godot } } + internal GodotObject(IntPtr nativePtr) : this(false) + { + // NativePtr must be non-zero before calling ConstructAndInitialize to avoid invoking the constructor NativeCtor. + // We don't want to invoke the constructor, because we already have a constructed instance in nativePtr. + NativePtr = nativePtr; + unsafe + { + ConstructAndInitialize(NativeCtor, NativeName, _cachedType, refCounted: false); + } + } + internal unsafe void ConstructAndInitialize( - delegate* unmanaged<IntPtr> nativeCtor, + delegate* unmanaged<godot_bool, IntPtr> nativeCtor, StringName nativeName, Type cachedType, bool refCounted @@ -40,7 +51,8 @@ namespace Godot { Debug.Assert(nativeCtor != null); - NativePtr = nativeCtor(); + // Need postinitialization. + NativePtr = nativeCtor(godot_bool.True); InteropUtils.TieManagedToUnmanaged(this, NativePtr, nativeName, refCounted, GetType(), cachedType); @@ -260,7 +272,7 @@ namespace Godot return methodBind; } - internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) + internal static unsafe delegate* unmanaged<godot_bool, IntPtr> ClassDB_get_constructor(StringName type) { // for some reason the '??' operator doesn't support 'delegate*' var typeSelf = (godot_string_name)type.NativeValue; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index 04b6c2e743..6b5d7fed78 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -143,7 +143,7 @@ namespace Godot.NativeInterop if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK) { using godot_variant instanceVariant = VariantUtils.CreateFromGodotObjectPtr(instance); - string where = GetCallErrorWhere(method, &instanceVariant, args, argCount); + string where = GetCallErrorWhere(ref error, method, &instanceVariant, args, argCount); string errorText = GetCallErrorMessage(error, where, args); GD.PushError(errorText); } @@ -161,7 +161,7 @@ namespace Godot.NativeInterop } } - private unsafe static string GetCallErrorWhere(godot_string_name method, godot_variant* instance, godot_variant** args, int argCount) + private unsafe static string GetCallErrorWhere(ref godot_variant_call_error error, godot_string_name method, godot_variant* instance, godot_variant** args, int argCount) { string? methodstr = null; string basestr = GetVariantTypeName(instance); @@ -171,6 +171,10 @@ namespace Godot.NativeInterop if (argCount >= 1) { methodstr = VariantUtils.ConvertToString(*args[0]); + if (error.Error == godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT) + { + error.Argument += 1; + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index c4fd639cce..6a643833f6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -47,7 +47,7 @@ namespace Godot.NativeInterop public static partial IntPtr godotsharp_method_bind_get_method_with_compatibility( in godot_string_name p_classname, in godot_string_name p_methodname, ulong p_hash); - public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor( + public static partial delegate* unmanaged<godot_bool, IntPtr> godotsharp_get_class_constructor( in godot_string_name p_classname); public static partial IntPtr godotsharp_engine_get_singleton(in godot_string p_name); @@ -59,7 +59,7 @@ namespace Godot.NativeInterop internal static partial void godotsharp_stack_info_vector_destroy( ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector); - internal static partial void godotsharp_internal_editor_file_system_update_file(in godot_string p_script_path); + internal static partial void godotsharp_internal_editor_file_system_update_files(in godot_packed_string_array p_script_paths); internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func, in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 19721b6cca..4f606cc6b3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -172,6 +172,26 @@ namespace Godot } /// <summary> + /// Returns the support point in a given direction. + /// This is useful for collision detection algorithms. + /// </summary> + /// <param name="direction">The direction to find support for.</param> + /// <returns>A vector representing the support.</returns> + public readonly Vector2 GetSupport(Vector2 direction) + { + Vector2 support = _position; + if (direction.X > 0.0f) + { + support.X += _size.X; + } + if (direction.Y > 0.0f) + { + support.Y += _size.Y; + } + return support; + } + + /// <summary> /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount /// on all sides. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index a5fa89d3bf..f943a3049d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -47,7 +47,7 @@ namespace Godot { get { - real_t detSign = Mathf.Sign(BasisDeterminant()); + real_t detSign = Mathf.Sign(Determinant()); return new Vector2(X.Length(), detSign * Y.Length()); } } @@ -59,7 +59,7 @@ namespace Godot { get { - real_t detSign = Mathf.Sign(BasisDeterminant()); + real_t detSign = Mathf.Sign(Determinant()); return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f; } } @@ -135,7 +135,7 @@ namespace Godot /// <returns>The inverse transformation matrix.</returns> public readonly Transform2D AffineInverse() { - real_t det = BasisDeterminant(); + real_t det = Determinant(); if (det == 0) throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted."); @@ -157,15 +157,16 @@ namespace Godot /// <summary> /// Returns the determinant of the basis matrix. If the basis is - /// uniformly scaled, its determinant is the square of the scale. + /// uniformly scaled, then its determinant equals the square of the + /// scale factor. /// - /// A negative determinant means the Y scale is negative. - /// A zero determinant means the basis isn't invertible, - /// and is usually considered invalid. + /// A negative determinant means the basis was flipped, so one part of + /// the scale is negative. A zero determinant means the basis isn't + /// invertible, and is usually considered invalid. /// </summary> /// <returns>The determinant of the basis matrix.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly real_t BasisDeterminant() + public readonly real_t Determinant() { return (X.X * Y.Y) - (X.Y * Y.X); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 0f534d477f..0dc143edea 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -468,8 +468,8 @@ namespace Godot { for (int j = 0; j < 3; j++) { - real_t e = transform.Basis[i][j] * min[j]; - real_t f = transform.Basis[i][j] * max[j]; + real_t e = transform.Basis[j][i] * min[j]; + real_t f = transform.Basis[j][i] * max[j]; if (e < f) { tmin[i] += e; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 50bf56d832..f5b64ff81b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -869,7 +869,7 @@ namespace Godot } /// <summary> - /// Multiplies each component of the <see cref="Vector2"/> + /// Divides each component of the <see cref="Vector2"/> /// by the given <see cref="real_t"/>. /// </summary> /// <param name="vec">The dividend vector.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 27f2713efa..ef66d5bbe0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -692,10 +692,18 @@ namespace Godot // Zero length vectors have no angle, so the best we can do is either lerp or throw an error. return Lerp(to, weight); } + Vector3 axis = Cross(to); + real_t axisLengthSquared = axis.LengthSquared(); + if (axisLengthSquared == 0.0) + { + // Colinear vectors have no rotation axis or angle between them, so the best we can do is lerp. + return Lerp(to, weight); + } + axis /= Mathf.Sqrt(axisLengthSquared); real_t startLength = Mathf.Sqrt(startLengthSquared); real_t resultLength = Mathf.Lerp(startLength, Mathf.Sqrt(endLengthSquared), weight); real_t angle = AngleTo(to); - return Rotated(Cross(to).Normalized(), angle * weight) * (resultLength / startLength); + return Rotated(axis, angle * weight) * (resultLength / startLength); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 6b25087c93..3a3134d160 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -17,7 +17,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>GodotSharp</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> @@ -135,7 +135,7 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <!-- Compat Sources --> - <ItemGroup> + <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' "> <Compile Include="Compat.cs" /> </ItemGroup> <!-- diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index 4561fdaf2b..715c1a4d51 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -15,7 +15,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>GodotSharpEditor</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharpEditor</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> @@ -36,7 +36,7 @@ </ProjectReference> </ItemGroup> <!-- Compat Sources --> - <ItemGroup> + <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' "> <Compile Include="Compat.cs" /> </ItemGroup> <!-- diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 1af462dafd..73c10eba83 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -58,7 +58,7 @@ extern "C" { // For ArrayPrivate and DictionaryPrivate static_assert(sizeof(SafeRefCount) == sizeof(uint32_t)); -typedef Object *(*godotsharp_class_creation_func)(); +typedef Object *(*godotsharp_class_creation_func)(bool); bool godotsharp_dotnet_module_is_initialized() { return GDMono::get_singleton()->is_initialized(); @@ -315,13 +315,13 @@ void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) { memnew_placement(r_dest, Ref<CSharpScript>(memnew(CSharpScript))); } -void godotsharp_internal_editor_file_system_update_file(const String *p_script_path) { +void godotsharp_internal_editor_file_system_update_files(const PackedStringArray &p_script_paths) { #ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); if (efs) { - efs->update_file(*p_script_path); + efs->update_files(p_script_paths); } #else // EditorFileSystem is only available when running in the Godot editor. @@ -1450,7 +1450,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_engine_get_singleton, (void *)godotsharp_stack_info_vector_resize, (void *)godotsharp_stack_info_vector_destroy, - (void *)godotsharp_internal_editor_file_system_update_file, + (void *)godotsharp_internal_editor_file_system_update_files, (void *)godotsharp_internal_script_debugger_send_error, (void *)godotsharp_internal_script_debugger_is_active, (void *)godotsharp_internal_object_get_associated_gchandle, diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 80e44011be..039263b405 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -192,9 +192,15 @@ private: } } if (!has_data) { - // 3. Extract the data to a temporary location to load from there. - Ref<DirAccess> da = DirAccess::create_for_path(packed_path); - ERR_FAIL_NULL(da); + // 3. Extract the data to a temporary location to load from there, delete old data if it exists but is not up-to-date. + Ref<DirAccess> da; + if (DirAccess::exists(data_dir_root)) { + da = DirAccess::open(data_dir_root); + ERR_FAIL_COND(da.is_null()); + ERR_FAIL_COND(da->erase_contents_recursive() != OK); + } + da = DirAccess::create_for_path(packed_path); + ERR_FAIL_COND(da.is_null()); ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK); } api_assemblies_dir = data_dir_root; diff --git a/modules/mono/icons/BuildCSharp.svg b/modules/mono/icons/BuildCSharp.svg index d4ba5bc293..430433dc38 100644 --- a/modules/mono/icons/BuildCSharp.svg +++ b/modules/mono/icons/BuildCSharp.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2.256 4.85 1.7 2.945 2.598-1.5 4.5 7.794a1 1-30 0 0 2.598-1.5l-4.65-8.054 3.464-2-.25-.433s-1.25-2.165-3.848-.665l-3.464 2-.31.063z" fill="#e0e0e0"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m2.256 4.85 1.7 2.945 2.598-1.5 4.5 7.794a1 1-30 0 0 2.598-1.5l-4.65-8.054 3.464-2-.25-.433s-1.25-2.165-3.848-.665l-3.464 2-.31.063z"/></svg>
\ No newline at end of file diff --git a/modules/mono/icons/CSharpScript.svg b/modules/mono/icons/CSharpScript.svg index 1e1ec96857..7f309fe5fa 100644 --- a/modules/mono/icons/CSharpScript.svg +++ b/modules/mono/icons/CSharpScript.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M6 10a3 3 0 1 0 0 6h1v-2H6a1 1 0 0 1 0-2h1v-2zm1-9-.564 2.258a4.91 4.91 0 0 0-.69.28L3.758 2.343 2.344 3.758l1.195 1.994-.285.685L1 7v2h5.27a2 2 0 1 1 3.46 0H15V7l-2.258-.565a5.007 5.007 0 0 0-.28-.687l1.194-1.99-1.414-1.414-1.994 1.195a4.998 4.998 0 0 0-.686-.285L9 1zm4 9a2 2 0 1 0 0 4H9v2h2a2 2 0 1 0 0-4h2v-2z" fill="#e0e0e0"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M6 10a3 3 0 1 0 0 6h1v-2H6a1 1 0 0 1 0-2h1v-2zm1-9-.564 2.258a4.91 4.91 0 0 0-.69.28L3.758 2.343 2.344 3.758l1.195 1.994-.285.685L1 7v2h5.27a2 2 0 1 1 3.46 0H15V7l-2.258-.565a5.007 5.007 0 0 0-.28-.687l1.194-1.99-1.414-1.414-1.994 1.195a4.998 4.998 0 0 0-.686-.285L9 1zm4 9a2 2 0 1 0 0 4H9v2h2a2 2 0 1 0 0-4h2v-2z"/></svg>
\ No newline at end of file diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 0e34616951..6abf0193cf 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -61,6 +61,14 @@ hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr; hostfxr_close_fn hostfxr_close = nullptr; +#ifndef TOOLS_ENABLED +typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_create_delegate_fn)(void *hostHandle, unsigned int domainId, const char *entryPointAssemblyName, const char *entryPointTypeName, const char *entryPointMethodName, void **delegate); +typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_initialize_fn)(const char *exePath, const char *appDomainFriendlyName, int propertyCount, const char **propertyKeys, const char **propertyValues, void **hostHandle, unsigned int *domainId); + +coreclr_create_delegate_fn coreclr_create_delegate = nullptr; +coreclr_initialize_fn coreclr_initialize = nullptr; +#endif + #ifdef _WIN32 static_assert(sizeof(char_t) == sizeof(char16_t)); using HostFxrCharString = Char16String; @@ -142,6 +150,56 @@ String find_hostfxr() { #endif } +#ifndef TOOLS_ENABLED +String find_monosgen() { +#if defined(ANDROID_ENABLED) + // Android includes all native libraries in the libs directory of the APK + // so we assume it exists and use only the name to dlopen it. + return "libmonosgen-2.0.so"; +#else +#if defined(WINDOWS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("monosgen-2.0.dll"); +#elif defined(MACOS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libmonosgen-2.0.dylib"); +#elif defined(UNIX_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libmonosgen-2.0.so"); +#else +#error "Platform not supported (yet?)" +#endif + + if (FileAccess::exists(probe_path)) { + return probe_path; + } + + return String(); +#endif +} + +String find_coreclr() { +#if defined(WINDOWS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("coreclr.dll"); +#elif defined(MACOS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libcoreclr.dylib"); +#elif defined(UNIX_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libcoreclr.so"); +#else +#error "Platform not supported (yet?)" +#endif + + if (FileAccess::exists(probe_path)) { + return probe_path; + } + + return String(); +} +#endif + bool load_hostfxr(void *&r_hostfxr_dll_handle) { String hostfxr_path = find_hostfxr(); @@ -182,6 +240,47 @@ bool load_hostfxr(void *&r_hostfxr_dll_handle) { hostfxr_close); } +#ifndef TOOLS_ENABLED +bool load_coreclr(void *&r_coreclr_dll_handle) { + String coreclr_path = find_coreclr(); + + bool is_monovm = false; + if (coreclr_path.is_empty()) { + // Fallback to MonoVM (should have the same API as CoreCLR). + coreclr_path = find_monosgen(); + is_monovm = true; + } + + if (coreclr_path.is_empty()) { + return false; + } + + const String coreclr_name = is_monovm ? "monosgen" : "coreclr"; + print_verbose("Found " + coreclr_name + ": " + coreclr_path); + + Error err = OS::get_singleton()->open_dynamic_library(coreclr_path, r_coreclr_dll_handle); + + if (err != OK) { + return false; + } + + void *lib = r_coreclr_dll_handle; + + void *symbol = nullptr; + + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_initialize", symbol); + ERR_FAIL_COND_V(err != OK, false); + coreclr_initialize = (coreclr_initialize_fn)symbol; + + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_create_delegate", symbol); + ERR_FAIL_COND_V(err != OK, false); + coreclr_create_delegate = (coreclr_create_delegate_fn)symbol; + + return (coreclr_initialize && + coreclr_create_delegate); +} +#endif + #ifdef TOOLS_ENABLED load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) { hostfxr_handle cxt = nullptr; @@ -339,6 +438,56 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) } #endif +#ifndef TOOLS_ENABLED +String make_tpa_list() { + String tpa_list; + +#if defined(WINDOWS_ENABLED) + String separator = ";"; +#else + String separator = ":"; +#endif + + String assemblies_dir = GodotSharpDirs::get_api_assemblies_dir(); + PackedStringArray files = DirAccess::get_files_at(assemblies_dir); + for (const String &file : files) { + tpa_list += assemblies_dir.path_join(file); + tpa_list += separator; + } + + return tpa_list; +} + +godot_plugins_initialize_fn initialize_coreclr_and_godot_plugins(bool &r_runtime_initialized) { + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; + + String assembly_name = path::get_csharp_project_name(); + + String tpa_list = make_tpa_list(); + const char *prop_keys[] = { "TRUSTED_PLATFORM_ASSEMBLIES" }; + const char *prop_values[] = { tpa_list.utf8().get_data() }; + int nprops = sizeof(prop_keys) / sizeof(prop_keys[0]); + + void *coreclr_handle = nullptr; + unsigned int domain_id = 0; + int rc = coreclr_initialize(nullptr, nullptr, nprops, (const char **)&prop_keys, (const char **)&prop_values, &coreclr_handle, &domain_id); + ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to initialize CoreCLR."); + + r_runtime_initialized = true; + + print_verbose(".NET: CoreCLR initialized"); + + coreclr_create_delegate(coreclr_handle, domain_id, + assembly_name.utf8().get_data(), + "GodotPlugins.Game.Main", + "InitializeFromGameProject", + (void **)&godot_plugins_initialize); + ERR_FAIL_NULL_V_MSG(godot_plugins_initialize, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer"); + + return godot_plugins_initialize; +} +#endif + } // namespace bool GDMono::should_initialize() { @@ -382,14 +531,21 @@ void GDMono::initialize() { } #endif - if (!load_hostfxr(hostfxr_dll_handle)) { + if (load_hostfxr(hostfxr_dll_handle)) { + godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized); + ERR_FAIL_NULL(godot_plugins_initialize); + } else { #if !defined(TOOLS_ENABLED) - godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle); - - if (godot_plugins_initialize != nullptr) { - is_native_aot = true; - runtime_initialized = true; + if (load_coreclr(coreclr_dll_handle)) { + godot_plugins_initialize = initialize_coreclr_and_godot_plugins(runtime_initialized); } else { + godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle); + if (godot_plugins_initialize != nullptr) { + runtime_initialized = true; + } + } + + if (godot_plugins_initialize == nullptr) { ERR_FAIL_MSG(".NET: Failed to load hostfxr"); } #else @@ -400,11 +556,6 @@ void GDMono::initialize() { #endif } - if (!is_native_aot) { - godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized); - ERR_FAIL_NULL(godot_plugins_initialize); - } - int32_t interop_funcs_size = 0; const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size); @@ -553,6 +704,9 @@ GDMono::~GDMono() { if (hostfxr_dll_handle) { OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle); } + if (coreclr_dll_handle) { + OS::get_singleton()->close_dynamic_library(coreclr_dll_handle); + } finalizing_scripts_domain = false; runtime_initialized = false; @@ -564,11 +718,7 @@ namespace mono_bind { GodotSharp *GodotSharp::singleton = nullptr; -bool GodotSharp::_is_runtime_initialized() { - return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized(); -} - -void GodotSharp::_reload_assemblies(bool p_soft_reload) { +void GodotSharp::reload_assemblies(bool p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD CRASH_COND(CSharpLanguage::get_singleton() == nullptr); // This method may be called more than once with `call_deferred`, so we need to check @@ -579,11 +729,6 @@ void GodotSharp::_reload_assemblies(bool p_soft_reload) { #endif } -void GodotSharp::_bind_methods() { - ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::_is_runtime_initialized); - ClassDB::bind_method(D_METHOD("_reload_assemblies"), &GodotSharp::_reload_assemblies); -} - GodotSharp::GodotSharp() { singleton = this; } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 0cb087db57..fae3421ac9 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -64,7 +64,7 @@ class GDMono { bool finalizing_scripts_domain = false; void *hostfxr_dll_handle = nullptr; - bool is_native_aot = false; + void *coreclr_dll_handle = nullptr; String project_assembly_path; uint64_t project_assembly_modified_time = 0; @@ -167,18 +167,14 @@ namespace mono_bind { class GodotSharp : public Object { GDCLASS(GodotSharp, Object); - friend class GDMono; - - void _reload_assemblies(bool p_soft_reload); - bool _is_runtime_initialized(); - protected: static GodotSharp *singleton; - static void _bind_methods(); public: static GodotSharp *get_singleton() { return singleton; } + void reload_assemblies(bool p_soft_reload); + GodotSharp(); ~GodotSharp(); }; diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp index beaa50ecb2..4d5426d96f 100644 --- a/modules/mono/register_types.cpp +++ b/modules/mono/register_types.cpp @@ -49,9 +49,6 @@ void initialize_mono_module(ModuleInitializationLevel p_level) { _godotsharp = memnew(mono_bind::GodotSharp); - GDREGISTER_CLASS(mono_bind::GodotSharp); - Engine::get_singleton()->add_singleton(Engine::Singleton("GodotSharp", mono_bind::GodotSharp::get_singleton())); - script_language_cs = memnew(CSharpLanguage); script_language_cs->set_language_index(ScriptServer::get_language_count()); ScriptServer::register_language(script_language_cs); diff --git a/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar b/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar Binary files differnew file mode 100755 index 0000000000..7366030881 --- /dev/null +++ b/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml index 3da245f806..b620292519 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml @@ -48,11 +48,11 @@ </methods> <members> <member name="spawn_function" type="Callable" setter="set_spawn_function" getter="get_spawn_function"> - Method called on all peers when for every custom [method spawn] requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree. + Method called on all peers when a custom [method spawn] is requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree. [b]Note:[/b] The returned node should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically. </member> <member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0"> - Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns. + Maximum number of nodes allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns. When set to [code]0[/code] (the default), there is no limit. </member> <member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath("")"> diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml index 7abee8e2c8..42f32d4848 100644 --- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml +++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml @@ -68,7 +68,7 @@ The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect. </member> <member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0"> - If set to a value greater than [code]0.0[/code], the maximum amount of time peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals. + If set to a value greater than [code]0.0[/code], the maximum duration in seconds peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals. </member> <member name="max_delta_packet_size" type="int" setter="set_max_delta_packet_size" getter="get_max_delta_packet_size" default="65535"> Maximum size of each delta packet. Higher values increase the chance of receiving full updates in a single frame, but also the chance of causing networking congestion (higher latency, disconnections). See [MultiplayerSynchronizer]. diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp index 75941207c7..3a51712c70 100644 --- a/modules/multiplayer/editor/editor_network_profiler.cpp +++ b/modules/multiplayer/editor/editor_network_profiler.cpp @@ -34,6 +34,7 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" +#include "scene/gui/check_box.h" void EditorNetworkProfiler::_bind_methods() { ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable"))); @@ -74,8 +75,8 @@ void EditorNetworkProfiler::_update_theme_item_cache() { theme_cache.incoming_bandwidth_icon = get_theme_icon(SNAME("ArrowDown"), EditorStringName(EditorIcons)); theme_cache.outgoing_bandwidth_icon = get_theme_icon(SNAME("ArrowUp"), EditorStringName(EditorIcons)); - theme_cache.incoming_bandwidth_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor)); - theme_cache.outgoing_bandwidth_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor)); + theme_cache.incoming_bandwidth_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)); + theme_cache.outgoing_bandwidth_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)); } void EditorNetworkProfiler::_refresh() { @@ -170,15 +171,42 @@ void EditorNetworkProfiler::add_node_data(const NodeInfo &p_info) { } void EditorNetworkProfiler::_activate_pressed() { + _update_button_text(); + if (activate->is_pressed()) { refresh_timer->start(); + } else { + refresh_timer->stop(); + } + + emit_signal(SNAME("enable_profiling"), activate->is_pressed()); +} + +void EditorNetworkProfiler::_update_button_text() { + if (activate->is_pressed()) { activate->set_icon(theme_cache.stop_icon); activate->set_text(TTR("Stop")); } else { - refresh_timer->stop(); activate->set_icon(theme_cache.play_icon); activate->set_text(TTR("Start")); } +} + +void EditorNetworkProfiler::started() { + if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false)) { + set_profiling(true); + refresh_timer->start(); + } +} + +void EditorNetworkProfiler::stopped() { + set_profiling(false); + refresh_timer->stop(); +} + +void EditorNetworkProfiler::set_profiling(bool p_pressed) { + activate->set_pressed(p_pressed); + _update_button_text(); emit_signal(SNAME("enable_profiling"), activate->is_pressed()); } @@ -192,6 +220,10 @@ void EditorNetworkProfiler::_clear_pressed() { refresh_replication_data(); } +void EditorNetworkProfiler::_autostart_toggled(bool p_toggled_on) { + EditorSettings::get_singleton()->set_project_metadata("debug_options", "autostart_network_profiler", p_toggled_on); +} + void EditorNetworkProfiler::_replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button) { if (!p_item) { return; @@ -227,10 +259,10 @@ void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) { sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs; } SyncInfo &info = sync_data[p_frame.synchronizer]; - if (info.incoming_syncs) { + if (p_frame.incoming_syncs) { info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs; } - if (info.outgoing_syncs) { + if (p_frame.outgoing_syncs) { info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs; } } @@ -268,6 +300,12 @@ EditorNetworkProfiler::EditorNetworkProfiler() { clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorNetworkProfiler::_clear_pressed)); hb->add_child(clear_button); + CheckBox *autostart_checkbox = memnew(CheckBox); + autostart_checkbox->set_text(TTR("Autostart")); + autostart_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false)); + autostart_checkbox->connect(SceneStringName(toggled), callable_mp(this, &EditorNetworkProfiler::_autostart_toggled)); + hb->add_child(autostart_checkbox); + hb->add_spacer(); Label *lb = memnew(Label); diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h index b4f8ffa724..46931c9fc9 100644 --- a/modules/multiplayer/editor/editor_network_profiler.h +++ b/modules/multiplayer/editor/editor_network_profiler.h @@ -92,7 +92,9 @@ private: void _activate_pressed(); void _clear_pressed(); + void _autostart_toggled(bool p_toggled_on); void _refresh(); + void _update_button_text(); void _replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button); protected: @@ -112,6 +114,10 @@ public: void set_bandwidth(int p_incoming, int p_outgoing); bool is_profiling(); + void set_profiling(bool p_pressed); + void started(); + void stopped(); + EditorNetworkProfiler(); }; diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp index a496f5dfa2..817d503aec 100644 --- a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp @@ -106,6 +106,8 @@ void MultiplayerEditorDebugger::setup_session(int p_session_id) { profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id)); profiler->connect("open_request", callable_mp(this, &MultiplayerEditorDebugger::_open_request)); profiler->set_name(TTR("Network Profiler")); + session->connect("started", callable_mp(profiler, &EditorNetworkProfiler::started)); + session->connect("stopped", callable_mp(profiler, &EditorNetworkProfiler::stopped)); session->add_session_tab(profiler); profilers[p_session_id] = profiler; } diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index 3cc0a5ae53..386feae4f9 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -93,24 +93,6 @@ void ReplicationEditor::_pick_node_select_recursive(TreeItem *p_item, const Stri } } -void ReplicationEditor::_pick_node_filter_input(const Ref<InputEvent> &p_ie) { - Ref<InputEventKey> k = p_ie; - - if (k.is_valid()) { - switch (k->get_keycode()) { - case Key::UP: - case Key::DOWN: - case Key::PAGEUP: - case Key::PAGEDOWN: { - pick_node->get_scene_tree()->get_scene_tree()->gui_input(k); - pick_node->get_filter_line_edit()->accept_event(); - } break; - default: - break; - } - } -} - void ReplicationEditor::_pick_node_selected(NodePath p_path) { Node *root = current->get_node(current->get_root_path()); ERR_FAIL_NULL(root); @@ -175,7 +157,7 @@ ReplicationEditor::ReplicationEditor() { delete_dialog = memnew(ConfirmationDialog); delete_dialog->connect("canceled", callable_mp(this, &ReplicationEditor::_dialog_closed).bind(false)); - delete_dialog->connect("confirmed", callable_mp(this, &ReplicationEditor::_dialog_closed).bind(true)); + delete_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ReplicationEditor::_dialog_closed).bind(true)); add_child(delete_dialog); VBoxContainer *vb = memnew(VBoxContainer); @@ -184,11 +166,9 @@ ReplicationEditor::ReplicationEditor() { pick_node = memnew(SceneTreeDialog); add_child(pick_node); - pick_node->register_text_enter(pick_node->get_filter_line_edit()); pick_node->set_title(TTR("Pick a node to synchronize:")); pick_node->connect("selected", callable_mp(this, &ReplicationEditor::_pick_node_selected)); - pick_node->get_filter_line_edit()->connect("text_changed", callable_mp(this, &ReplicationEditor::_pick_node_filter_text_changed)); - pick_node->get_filter_line_edit()->connect("gui_input", callable_mp(this, &ReplicationEditor::_pick_node_filter_input)); + pick_node->get_filter_line_edit()->connect(SceneStringName(text_changed), callable_mp(this, &ReplicationEditor::_pick_node_filter_text_changed)); prop_selector = memnew(PropertySelector); add_child(prop_selector); @@ -372,7 +352,7 @@ void ReplicationEditor::_notification(int p_what) { [[fallthrough]]; } case NOTIFICATION_ENTER_TREE: { - add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("panel"), SNAME("Panel"))); + add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SceneStringName(panel), SNAME("Panel"))); add_pick_button->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons))); pin->set_icon(get_theme_icon(SNAME("Pin"), EditorStringName(EditorIcons))); } break; @@ -430,7 +410,7 @@ void ReplicationEditor::_tree_item_edited() { undo_redo->add_do_method(config.ptr(), "property_set_spawn", prop, value); undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, !value); undo_redo->add_do_method(this, "_update_value", prop, column, value ? 1 : 0); - undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 1 : 0); + undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 0 : 1); undo_redo->commit_action(); } else if (column == 2) { undo_redo->create_action(TTR("Set sync property")); diff --git a/modules/multiplayer/editor/replication_editor.h b/modules/multiplayer/editor/replication_editor.h index 8f11774292..017fa73967 100644 --- a/modules/multiplayer/editor/replication_editor.h +++ b/modules/multiplayer/editor/replication_editor.h @@ -81,7 +81,6 @@ private: void _pick_node_filter_text_changed(const String &p_newtext); void _pick_node_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates); - void _pick_node_filter_input(const Ref<InputEvent> &p_ie); void _pick_node_selected(NodePath p_path); void _pick_new_property(); diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index 2408d7231b..682d20022f 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -99,7 +99,7 @@ void MultiplayerSpawner::add_spawnable_scene(const String &p_path) { SpawnableScene sc; sc.path = p_path; if (Engine::get_singleton()->is_editor_hint()) { - ERR_FAIL_COND(!FileAccess::exists(p_path)); + ERR_FAIL_COND(!ResourceLoader::exists(p_path)); } spawnable_scenes.push_back(sc); #ifdef TOOLS_ENABLED diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index ca3fa43207..852975b8eb 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -270,7 +270,7 @@ void MultiplayerSynchronizer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s"), "set_replication_interval", "get_replication_interval"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "delta_interval", PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s"), "set_delta_interval", "get_delta_interval"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig", PROPERTY_USAGE_NO_EDITOR), "set_replication_config", "get_replication_config"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_replication_config", "get_replication_config"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_update_mode", PROPERTY_HINT_ENUM, "Idle,Physics,None"), "set_visibility_update_mode", "get_visibility_update_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "public_visibility"), "set_visibility_public", "is_visibility_public"); diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index 2ea9ce8819..99c8930e92 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -54,11 +54,14 @@ void SceneCacheInterface::_remove_node_cache(ObjectID p_oid) { if (nc->cache_id) { assigned_ids.erase(nc->cache_id); } +#if 0 + // TODO: Find a way to cleanup recv_nodes without breaking visibility and RPCs interactions. for (KeyValue<int, int> &E : nc->recv_ids) { PeerInfo *pinfo = peers_info.getptr(E.key); ERR_CONTINUE(!pinfo); pinfo->recv_nodes.erase(E.value); } +#endif for (KeyValue<int, bool> &E : nc->confirmed_peers) { PeerInfo *pinfo = peers_info.getptr(E.key); ERR_CONTINUE(!pinfo); @@ -73,10 +76,13 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) { } else { PeerInfo *pinfo = peers_info.getptr(p_id); ERR_FAIL_NULL(pinfo); // Bug. - for (KeyValue<int, ObjectID> E : pinfo->recv_nodes) { - NodeCache *nc = nodes_cache.getptr(E.value); - ERR_CONTINUE(!nc); - nc->recv_ids.erase(E.key); + for (KeyValue<int, RecvNode> E : pinfo->recv_nodes) { + NodeCache *nc = nodes_cache.getptr(E.value.oid); + if (!nc) { + // Node might have already been deleted locally. + continue; + } + nc->recv_ids.erase(p_id); } for (const ObjectID &oid : pinfo->sent_nodes) { NodeCache *nc = nodes_cache.getptr(oid); @@ -115,7 +121,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); } - peers_info[p_from].recv_nodes.insert(id, node->get_instance_id()); + peers_info[p_from].recv_nodes.insert(id, RecvNode(node->get_instance_id(), path)); NodeCache &cache = _track(node); cache.recv_ids.insert(p_from, id); @@ -269,14 +275,21 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r } Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) { - Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_NULL_V(root_node, nullptr); PeerInfo *pinfo = peers_info.getptr(p_from); ERR_FAIL_NULL_V(pinfo, nullptr); - const ObjectID *oid = pinfo->recv_nodes.getptr(p_cache_id); - ERR_FAIL_NULL_V_MSG(oid, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from)); - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid)); + RecvNode *recv_node = pinfo->recv_nodes.getptr(p_cache_id); + ERR_FAIL_NULL_V_MSG(recv_node, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from)); + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(recv_node->oid)); + if (!node) { + // Fallback to path lookup. + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_NULL_V(root_node, nullptr); + node = root_node->get_node(recv_node->path); + if (node) { + recv_node->oid = node->get_instance_id(); + } + } ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to get cached node from peer %d with cache ID %d.", p_from, p_cache_id)); return node; } diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h index 73d6bde6ef..fbe618f4ad 100644 --- a/modules/multiplayer/scene_cache_interface.h +++ b/modules/multiplayer/scene_cache_interface.h @@ -49,8 +49,18 @@ private: HashMap<int, bool> confirmed_peers; // peer id, confirmed }; + struct RecvNode { + ObjectID oid; + NodePath path; + + RecvNode(const ObjectID &p_oid, const NodePath &p_path) { + oid = p_oid; + path = p_path; + } + }; + struct PeerInfo { - HashMap<int, ObjectID> recv_nodes; // remote cache id, ObjectID + HashMap<int, RecvNode> recv_nodes; // remote cache id, (ObjectID, NodePath) HashSet<ObjectID> sent_nodes; }; diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index 99aba680cc..e245101eeb 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -307,8 +307,10 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p int len = p_packet_len - SYS_CMD_SIZE; bool should_process = false; if (get_unique_id() == 1) { // I am the server. - // Direct messages to server should not go through relay. - ERR_FAIL_COND(peer > 0 && !connected_peers.has(peer)); + // The requested target might have disconnected while the packet was in transit. + if (unlikely(peer > 0 && !connected_peers.has(peer))) { + return; + } // Send relay packet. relay_buffer->seek(0); relay_buffer->put_u8(NETWORK_COMMAND_SYS); @@ -319,21 +321,24 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p multiplayer_peer->set_transfer_mode(p_mode); multiplayer_peer->set_transfer_channel(p_channel); if (peer > 0) { + // Single destination. multiplayer_peer->set_target_peer(peer); _send(data.ptr(), relay_buffer->get_position()); } else { + // Multiple destinations. for (const int &P : connected_peers) { // Not to sender, nor excluded. - if (P == p_from || (peer < 0 && P != -peer)) { + if (P == p_from || P == -peer) { continue; } multiplayer_peer->set_target_peer(P); _send(data.ptr(), relay_buffer->get_position()); } - } - if (peer == 0 || peer == -1) { - should_process = true; - peer = p_from; // Process as the source. + if (peer != -1) { + // The server is one of the targets, process the packet with sender as source. + should_process = true; + peer = p_from; + } } } else { ERR_FAIL_COND(p_from != 1); // Bug. @@ -425,11 +430,11 @@ void SceneMultiplayer::_del_peer(int p_id) { void SceneMultiplayer::disconnect_peer(int p_id) { ERR_FAIL_COND(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED); - if (pending_peers.has(p_id)) { - pending_peers.erase(p_id); - } else if (connected_peers.has(p_id)) { - connected_peers.erase(p_id); - } + // Block signals to avoid emitting peer_disconnected. + bool blocking = is_blocking_signals(); + set_block_signals(true); + _del_peer(p_id); + set_block_signals(blocking); multiplayer_peer->disconnect_peer(p_id); } diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index 1463598ddc..0938d7ef99 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -73,6 +73,16 @@ int get_packet_len(uint32_t p_node_target, int p_packet_len) { } } +bool SceneRPCInterface::_sort_rpc_names(const Variant &p_l, const Variant &p_r) { + if (likely(p_l.is_string() && p_r.is_string())) { + return p_l.operator String() < p_r.operator String(); + } + bool valid = false; + Variant res; + Variant::evaluate(Variant::OP_LESS, p_l, p_r, res, valid); + return valid ? res.operator bool() : false; +} + void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_node, RPCConfigCache &r_cache) { if (p_config.get_type() == Variant::NIL) { return; @@ -80,9 +90,9 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no ERR_FAIL_COND(p_config.get_type() != Variant::DICTIONARY); const Dictionary config = p_config; Array names = config.keys(); - names.sort(); // Ensure ID order + names.sort_custom(callable_mp_static(&SceneRPCInterface::_sort_rpc_names)); // Ensure ID order for (int i = 0; i < names.size(); i++) { - ERR_CONTINUE(names[i].get_type() != Variant::STRING && names[i].get_type() != Variant::STRING_NAME); + ERR_CONTINUE(!names[i].is_string()); String name = names[i].operator String(); ERR_CONTINUE(config[name].get_type() != Variant::DICTIONARY); ERR_CONTINUE(!config[name].operator Dictionary().has("rpc_mode")); @@ -108,7 +118,7 @@ const SceneRPCInterface::RPCConfigCache &SceneRPCInterface::_get_node_config(con return rpc_cache[oid]; } RPCConfigCache cache; - _parse_rpc_config(p_node->get_node_rpc_config(), true, cache); + _parse_rpc_config(p_node->get_rpc_config(), true, cache); if (p_node->get_script_instance()) { _parse_rpc_config(p_node->get_script_instance()->get_rpc_config(), false, cache); } diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h index 5c9b66d5f5..852cef7830 100644 --- a/modules/multiplayer/scene_rpc_interface.h +++ b/modules/multiplayer/scene_rpc_interface.h @@ -91,6 +91,8 @@ private: #endif protected: + static bool _sort_rpc_names(const Variant &p_l, const Variant &p_r); + void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp index bf69adc14c..2af125d434 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.cpp +++ b/modules/navigation/2d/godot_navigation_server_2d.cpp @@ -318,6 +318,11 @@ int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid); Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); +Vector2 GodotNavigationServer2D::region_get_closest_point(RID p_region, const Vector2 &p_point) const { + Vector3 result = NavigationServer3D::get_singleton()->region_get_closest_point(p_region, v2_to_v3(p_point)); + return v3_to_v2(result); +} + Vector2 GodotNavigationServer2D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const { Vector3 result = NavigationServer3D::get_singleton()->region_get_random_point(p_region, p_navigation_layers, p_uniformly); return v3_to_v2(result); diff --git a/modules/navigation/2d/godot_navigation_server_2d.h b/modules/navigation/2d/godot_navigation_server_2d.h index ea77fa5e6e..1579ca2907 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.h +++ b/modules/navigation/2d/godot_navigation_server_2d.h @@ -101,6 +101,7 @@ public: virtual int region_get_connections_count(RID p_region) const override; virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override; virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; + virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override; virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override; virtual RID link_create() override; diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 2198158f9c..78983187c7 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -87,57 +87,55 @@ void NavMeshGenerator2D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask2D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask2D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser2D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser2D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::finish() { @@ -212,7 +210,7 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly baking_navmeshes.insert(p_navigation_mesh); baking_navmesh_mutex.unlock(); - generator_task_mutex.lock(); + MutexLock generator_task_lock(generator_task_mutex); NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D); generator_task->navigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -220,14 +218,11 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D"); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_polygon); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_polygon); } void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { @@ -263,7 +258,7 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_ // Special case for TileMap, so that internal layer get parsed even if p_recurse_children is false. for (int i = 0; i < p_node->get_child_count(); i++) { TileMapLayer *tile_map_layer = Object::cast_to<TileMapLayer>(p_node->get_child(i)); - if (tile_map_layer->get_index_in_tile_map() >= 0) { + if (tile_map_layer && tile_map_layer->get_index_in_tile_map() >= 0) { generator_parse_tile_map_layer_node(p_navigation_mesh, p_source_geometry_data, tile_map_layer); } } @@ -852,8 +847,15 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation } int outline_count = p_navigation_mesh->get_outline_count(); - const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines(); - const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->_get_obstruction_outlines(); + + Vector<Vector<Vector2>> traversable_outlines; + Vector<Vector<Vector2>> obstruction_outlines; + Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> projected_obstructions; + + p_source_geometry_data->get_data( + traversable_outlines, + obstruction_outlines, + projected_obstructions); if (outline_count == 0 && traversable_outlines.size() == 0) { return; @@ -898,8 +900,6 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation obstruction_polygon_paths.push_back(clip_path); } - const Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_get_projected_obstructions(); - if (!projected_obstructions.is_empty()) { for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { if (projected_obstruction.carve) { @@ -1005,8 +1005,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation } if (new_baked_outlines.size() == 0) { - p_navigation_mesh->set_vertices(Vector<Vector2>()); - p_navigation_mesh->clear_polygons(); + p_navigation_mesh->clear(); return; } @@ -1038,11 +1037,32 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation } TPPLPartition tpart; - if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed! - ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths."); - p_navigation_mesh->set_vertices(Vector<Vector2>()); - p_navigation_mesh->clear_polygons(); - return; + + NavigationPolygon::SamplePartitionType sample_partition_type = p_navigation_mesh->get_sample_partition_type(); + + switch (sample_partition_type) { + case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_CONVEX_PARTITION: + if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { + ERR_PRINT("NavigationPolygon polygon convex partition failed. Unable to create a valid navigation mesh polygon layout from provided source geometry."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + break; + case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_TRIANGULATE: + if (tpart.Triangulate_EC(&tppl_in_polygon, &tppl_out_polygon) == 0) { + ERR_PRINT("NavigationPolygon polygon triangulation failed. Unable to create a valid navigation mesh polygon layout from provided source geometry."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + break; + default: { + ERR_PRINT("NavigationPolygon polygon partitioning failed. Unrecognized partition type."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } } Vector<Vector2> new_vertices; @@ -1066,11 +1086,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation new_polygons.push_back(new_polygon); } - p_navigation_mesh->set_vertices(new_vertices); - p_navigation_mesh->clear_polygons(); - for (int i = 0; i < new_polygons.size(); i++) { - p_navigation_mesh->add_polygon(new_polygons[i]); - } + p_navigation_mesh->set_data(new_vertices, new_polygons); } #endif // CLIPPER2_ENABLED diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp index 6cbfd93088..5dfc39f6f5 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.cpp +++ b/modules/navigation/3d/godot_navigation_server_3d.cpp @@ -486,7 +486,7 @@ COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navi NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_NULL(region); - region->set_mesh(p_navigation_mesh); + region->set_navigation_mesh(p_navigation_mesh); } #ifndef DISABLE_DEPRECATED @@ -509,22 +509,52 @@ void GodotNavigationServer3D::region_bake_navigation_mesh(Ref<NavigationMesh> p_ int GodotNavigationServer3D::region_get_connections_count(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_NULL_V(region, 0); - - return region->get_connections_count(); + NavMap *map = region->get_map(); + if (map) { + return map->get_region_connections_count(region); + } + return 0; } Vector3 GodotNavigationServer3D::region_get_connection_pathway_start(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_NULL_V(region, Vector3()); - - return region->get_connection_pathway_start(p_connection_id); + NavMap *map = region->get_map(); + if (map) { + return map->get_region_connection_pathway_start(region, p_connection_id); + } + return Vector3(); } Vector3 GodotNavigationServer3D::region_get_connection_pathway_end(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_NULL_V(region, Vector3()); + NavMap *map = region->get_map(); + if (map) { + return map->get_region_connection_pathway_end(region, p_connection_id); + } + return Vector3(); +} - return region->get_connection_pathway_end(p_connection_id); +Vector3 GodotNavigationServer3D::region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const { + const NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_NULL_V(region, Vector3()); + + return region->get_closest_point_to_segment(p_from, p_to, p_use_collision); +} + +Vector3 GodotNavigationServer3D::region_get_closest_point(RID p_region, const Vector3 &p_point) const { + const NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_NULL_V(region, Vector3()); + + return region->get_closest_point_info(p_point).point; +} + +Vector3 GodotNavigationServer3D::region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const { + const NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_NULL_V(region, Vector3()); + + return region->get_closest_point_info(p_point).normal; } Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const { @@ -1298,6 +1328,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) { int _new_pm_edge_merge_count = 0; int _new_pm_edge_connection_count = 0; int _new_pm_edge_free_count = 0; + int _new_pm_obstacle_count = 0; // In c++ we can't be sure that this is performed in the main thread // even with mutable functions. @@ -1315,6 +1346,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) { _new_pm_edge_merge_count += active_maps[i]->get_pm_edge_merge_count(); _new_pm_edge_connection_count += active_maps[i]->get_pm_edge_connection_count(); _new_pm_edge_free_count += active_maps[i]->get_pm_edge_free_count(); + _new_pm_obstacle_count += active_maps[i]->get_pm_obstacle_count(); // Emit a signal if a map changed. const uint32_t new_map_iteration_id = active_maps[i]->get_iteration_id(); @@ -1332,6 +1364,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) { pm_edge_merge_count = _new_pm_edge_merge_count; pm_edge_connection_count = _new_pm_edge_connection_count; pm_edge_free_count = _new_pm_edge_free_count; + pm_obstacle_count = _new_pm_obstacle_count; } void GodotNavigationServer3D::init() { @@ -1566,6 +1599,9 @@ int GodotNavigationServer3D::get_process_info(ProcessInfo p_info) const { case INFO_EDGE_FREE_COUNT: { return pm_edge_free_count; } break; + case INFO_OBSTACLE_COUNT: { + return pm_obstacle_count; + } break; } return 0; diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h index 5ba7ed1088..eae6ea2860 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.h +++ b/modules/navigation/3d/godot_navigation_server_3d.h @@ -95,6 +95,7 @@ class GodotNavigationServer3D : public NavigationServer3D { int pm_edge_merge_count = 0; int pm_edge_connection_count = 0; int pm_edge_free_count = 0; + int pm_obstacle_count = 0; public: GodotNavigationServer3D(); @@ -177,6 +178,9 @@ public: virtual int region_get_connections_count(RID p_region) const override; virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override; virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; + virtual Vector3 region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision = false) const override; + virtual Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override; + virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override; virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override; virtual RID link_create() override; diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index cc3bbdbf01..e92a9d304b 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -100,57 +100,55 @@ void NavMeshGenerator3D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask3D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask3D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser3D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser3D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::finish() { @@ -226,7 +224,7 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh baking_navmeshes.insert(p_navigation_mesh); baking_navmesh_mutex.unlock(); - generator_task_mutex.lock(); + MutexLock generator_task_lock(generator_task_mutex); NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D); generator_task->navigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -234,14 +232,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D")); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_mesh); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_mesh); } void NavMeshGenerator3D::generator_thread_bake(void *p_arg) { @@ -672,10 +667,16 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation return; } - const Vector<float> &vertices = p_source_geometry_data->get_vertices(); - const Vector<int> &indices = p_source_geometry_data->get_indices(); + Vector<float> source_geometry_vertices; + Vector<int> source_geometry_indices; + Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> projected_obstructions; - if (vertices.size() < 3 || indices.size() < 3) { + p_source_geometry_data->get_data( + source_geometry_vertices, + source_geometry_indices, + projected_obstructions); + + if (source_geometry_vertices.size() < 3 || source_geometry_indices.size() < 3) { return; } @@ -691,10 +692,10 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation bake_state = "Setting up Configuration..."; // step #1 - const float *verts = vertices.ptr(); - const int nverts = vertices.size() / 3; - const int *tris = indices.ptr(); - const int ntris = indices.size() / 3; + const float *verts = source_geometry_vertices.ptr(); + const int nverts = source_geometry_vertices.size() / 3; + const int *tris = source_geometry_indices.ptr(); + const int ntris = source_geometry_indices.size() / 3; float bmin[3], bmax[3]; rcCalcBounds(verts, nverts, bmin, bmax); @@ -818,8 +819,6 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation rcFreeHeightField(hf); hf = nullptr; - const Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_get_projected_obstructions(); - // Add obstacles to the source geometry. Those will be affected by e.g. agent_radius. if (!projected_obstructions.is_empty()) { for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) { @@ -894,13 +893,25 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation bake_state = "Converting to native navigation mesh..."; // step #10 Vector<Vector3> nav_vertices; + Vector<Vector<int>> nav_polygons; + + HashMap<Vector3, int> recast_vertex_to_native_index; + LocalVector<int> recast_index_to_native_index; + recast_index_to_native_index.resize(detail_mesh->nverts); for (int i = 0; i < detail_mesh->nverts; i++) { const float *v = &detail_mesh->verts[i * 3]; - nav_vertices.push_back(Vector3(v[0], v[1], v[2])); + const Vector3 vertex = Vector3(v[0], v[1], v[2]); + int *existing_index_ptr = recast_vertex_to_native_index.getptr(vertex); + if (!existing_index_ptr) { + int new_index = recast_vertex_to_native_index.size(); + recast_index_to_native_index[i] = new_index; + recast_vertex_to_native_index[vertex] = new_index; + nav_vertices.push_back(vertex); + } else { + recast_index_to_native_index[i] = *existing_index_ptr; + } } - p_navigation_mesh->set_vertices(nav_vertices); - p_navigation_mesh->clear_polygons(); for (int i = 0; i < detail_mesh->nmeshes; i++) { const unsigned int *detail_mesh_m = &detail_mesh->meshes[i * 4]; @@ -912,13 +923,20 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation Vector<int> nav_indices; nav_indices.resize(3); // Polygon order in recast is opposite than godot's - nav_indices.write[0] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0])); - nav_indices.write[1] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2])); - nav_indices.write[2] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1])); - p_navigation_mesh->add_polygon(nav_indices); + int index1 = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0])); + int index2 = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2])); + int index3 = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1])); + + nav_indices.write[0] = recast_index_to_native_index[index1]; + nav_indices.write[1] = recast_index_to_native_index[index2]; + nav_indices.write[2] = recast_index_to_native_index[index3]; + + nav_polygons.push_back(nav_indices); } } + p_navigation_mesh->set_data(nav_vertices, nav_polygons); + bake_state = "Cleanup..."; // step #11 rcFreePolyMesh(poly_mesh); diff --git a/modules/navigation/3d/nav_mesh_queries_3d.cpp b/modules/navigation/3d/nav_mesh_queries_3d.cpp new file mode 100644 index 0000000000..70207f86ce --- /dev/null +++ b/modules/navigation/3d/nav_mesh_queries_3d.cpp @@ -0,0 +1,715 @@ +/**************************************************************************/ +/* nav_mesh_queries_3d.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. */ +/**************************************************************************/ + +#ifndef _3D_DISABLED + +#include "nav_mesh_queries_3d.h" + +#include "../nav_base.h" + +#include "core/math/geometry_3d.h" + +#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a))) + +#define APPEND_METADATA(poly) \ + if (r_path_types) { \ + r_path_types->push_back(poly->owner->get_type()); \ + } \ + if (r_path_rids) { \ + r_path_rids->push_back(poly->owner->get_self()); \ + } \ + if (r_path_owners) { \ + r_path_owners->push_back(poly->owner->get_owner_id()); \ + } + +Vector3 NavMeshQueries3D::polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly) { + const LocalVector<gd::Polygon> ®ion_polygons = p_polygons; + + if (region_polygons.is_empty()) { + return Vector3(); + } + + if (p_uniformly) { + real_t accumulated_area = 0; + RBMap<real_t, uint32_t> region_area_map; + + for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) { + const gd::Polygon ®ion_polygon = region_polygons[rp_index]; + real_t polyon_area = region_polygon.surface_area; + + if (polyon_area == 0.0) { + continue; + } + region_area_map[accumulated_area] = rp_index; + accumulated_area += polyon_area; + } + if (region_area_map.is_empty() || accumulated_area == 0) { + // All polygons have no real surface / no area. + return Vector3(); + } + + real_t region_area_map_pos = Math::random(real_t(0), accumulated_area); + + RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos); + ERR_FAIL_COND_V(!region_E, Vector3()); + uint32_t rrp_polygon_index = region_E->value; + ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3()); + + const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; + + real_t accumulated_polygon_area = 0; + RBMap<real_t, uint32_t> polygon_area_map; + + for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) { + real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area(); + + if (face_area == 0.0) { + continue; + } + polygon_area_map[accumulated_polygon_area] = rpp_index; + accumulated_polygon_area += face_area; + } + if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) { + // All faces have no real surface / no area. + return Vector3(); + } + + real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area); + + RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos); + ERR_FAIL_COND_V(!polygon_E, Vector3()); + uint32_t rrp_face_index = polygon_E->value; + ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3()); + + const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); + + Vector3 face_random_position = face.get_random_point_inside(); + return face_random_position; + + } else { + uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1); + + const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; + + uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1); + + const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); + + Vector3 face_random_position = face.get_random_point_inside(); + return face_random_position; + } +} + +Vector<Vector3> NavMeshQueries3D::polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size) { + // Clear metadata outputs. + if (r_path_types) { + r_path_types->clear(); + } + if (r_path_rids) { + r_path_rids->clear(); + } + if (r_path_owners) { + r_path_owners->clear(); + } + + // Find the start poly and the end poly on this map. + const gd::Polygon *begin_poly = nullptr; + const gd::Polygon *end_poly = nullptr; + Vector3 begin_point; + Vector3 end_point; + real_t begin_d = FLT_MAX; + real_t end_d = FLT_MAX; + // Find the initial poly and the end poly on this map. + for (const gd::Polygon &p : p_polygons) { + // Only consider the polygon if it in a region with compatible layers. + if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) { + continue; + } + + // For each face check the distance between the origin/destination + for (size_t point_id = 2; point_id < p.points.size(); point_id++) { + const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); + + Vector3 point = face.get_closest_point_to(p_origin); + real_t distance_to_point = point.distance_to(p_origin); + if (distance_to_point < begin_d) { + begin_d = distance_to_point; + begin_poly = &p; + begin_point = point; + } + + point = face.get_closest_point_to(p_destination); + distance_to_point = point.distance_to(p_destination); + if (distance_to_point < end_d) { + end_d = distance_to_point; + end_poly = &p; + end_point = point; + } + } + } + + // Check for trivial cases + if (!begin_poly || !end_poly) { + return Vector<Vector3>(); + } + if (begin_poly == end_poly) { + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = end_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = end_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = end_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; + } + + // List of all reachable navigation polys. + LocalVector<gd::NavigationPoly> navigation_polys; + navigation_polys.resize(p_polygons.size() + p_link_polygons_size); + + // Initialize the matching navigation polygon. + gd::NavigationPoly &begin_navigation_poly = navigation_polys[begin_poly->id]; + begin_navigation_poly.poly = begin_poly; + begin_navigation_poly.entry = begin_point; + begin_navigation_poly.back_navigation_edge_pathway_start = begin_point; + begin_navigation_poly.back_navigation_edge_pathway_end = begin_point; + + // Heap of polygons to travel next. + gd::Heap<gd::NavigationPoly *, gd::NavPolyTravelCostGreaterThan, gd::NavPolyHeapIndexer> + traversable_polys; + traversable_polys.reserve(p_polygons.size() * 0.25); + + // This is an implementation of the A* algorithm. + int least_cost_id = begin_poly->id; + int prev_least_cost_id = -1; + bool found_route = false; + + const gd::Polygon *reachable_end = nullptr; + real_t distance_to_reachable_end = FLT_MAX; + bool is_reachable = true; + + while (true) { + // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance. + for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) { + // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon. + for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) { + const gd::Edge::Connection &connection = edge.connections[connection_index]; + + // Only consider the connection to another polygon if this polygon is in a region with compatible layers. + if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) { + continue; + } + + const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id]; + real_t poly_enter_cost = 0.0; + real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost(); + + if (prev_least_cost_id != -1 && navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self()) { + poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost(); + } + prev_least_cost_id = least_cost_id; + + Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end }; + const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway); + const real_t new_traveled_distance = least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost + poly_enter_cost + least_cost_poly.traveled_distance; + + // Check if the neighbor polygon has already been processed. + gd::NavigationPoly &neighbor_poly = navigation_polys[connection.polygon->id]; + if (neighbor_poly.poly != nullptr) { + // If the neighbor polygon hasn't been traversed yet and the new path leading to + // it is shorter, update the polygon. + if (neighbor_poly.traversable_poly_index < traversable_polys.size() && + new_traveled_distance < neighbor_poly.traveled_distance) { + neighbor_poly.back_navigation_poly_id = least_cost_id; + neighbor_poly.back_navigation_edge = connection.edge; + neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start; + neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end; + neighbor_poly.traveled_distance = new_traveled_distance; + neighbor_poly.distance_to_destination = + new_entry.distance_to(end_point) * + neighbor_poly.poly->owner->get_travel_cost(); + neighbor_poly.entry = new_entry; + + // Update the priority of the polygon in the heap. + traversable_polys.shift(neighbor_poly.traversable_poly_index); + } + } else { + // Initialize the matching navigation polygon. + neighbor_poly.poly = connection.polygon; + neighbor_poly.back_navigation_poly_id = least_cost_id; + neighbor_poly.back_navigation_edge = connection.edge; + neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start; + neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end; + neighbor_poly.traveled_distance = new_traveled_distance; + neighbor_poly.distance_to_destination = + new_entry.distance_to(end_point) * + neighbor_poly.poly->owner->get_travel_cost(); + neighbor_poly.entry = new_entry; + + // Add the polygon to the heap of polygons to traverse next. + traversable_polys.push(&neighbor_poly); + } + } + } + + // When the heap of traversable polygons is empty at this point it means the end polygon is + // unreachable. + if (traversable_polys.is_empty()) { + // Thus use the further reachable polygon + ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons"); + is_reachable = false; + if (reachable_end == nullptr) { + // The path is not found and there is not a way out. + break; + } + + // Set as end point the furthest reachable point. + end_poly = reachable_end; + end_d = FLT_MAX; + for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) { + Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + } + } + + // Search all faces of start polygon as well. + bool closest_point_on_start_poly = false; + for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { + Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + closest_point_on_start_poly = true; + } + } + + if (closest_point_on_start_poly) { + // No point to run PostProcessing when start and end convex polygon is the same. + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = begin_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = begin_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = begin_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; + } + + for (gd::NavigationPoly &nav_poly : navigation_polys) { + nav_poly.poly = nullptr; + } + navigation_polys[begin_poly->id].poly = begin_poly; + + least_cost_id = begin_poly->id; + prev_least_cost_id = -1; + + reachable_end = nullptr; + + continue; + } + + // Pop the polygon with the lowest travel cost from the heap of traversable polygons. + least_cost_id = traversable_polys.pop()->poly->id; + + // Store the farthest reachable end polygon in case our goal is not reachable. + if (is_reachable) { + real_t distance = navigation_polys[least_cost_id].entry.distance_to(p_destination); + if (distance_to_reachable_end > distance) { + distance_to_reachable_end = distance; + reachable_end = navigation_polys[least_cost_id].poly; + } + } + + // Check if we reached the end + if (navigation_polys[least_cost_id].poly == end_poly) { + found_route = true; + break; + } + } + + // We did not find a route but we have both a start polygon and an end polygon at this point. + // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon. + if (!found_route) { + end_d = FLT_MAX; + // Search all faces of the start polygon for the closest point to our target position. + for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { + Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + } + } + + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = begin_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = begin_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = begin_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; + } + + Vector<Vector3> path; + // Optimize the path. + if (p_optimize) { + // Set the apex poly/point to the end point + gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id]; + + Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end }; + const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway); + if (end_point.is_equal_approx(back_edge_closest_point)) { + // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing. + // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon. + if (apex_poly->back_navigation_poly_id != -1) { + apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id]; + } + } + + Vector3 apex_point = end_point; + + gd::NavigationPoly *left_poly = apex_poly; + Vector3 left_portal = apex_point; + gd::NavigationPoly *right_poly = apex_poly; + Vector3 right_portal = apex_point; + + gd::NavigationPoly *p = apex_poly; + + path.push_back(end_point); + APPEND_METADATA(end_poly); + + while (p) { + // Set left and right points of the pathway between polygons. + Vector3 left = p->back_navigation_edge_pathway_start; + Vector3 right = p->back_navigation_edge_pathway_end; + if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(p_map_up) < 0) { + SWAP(left, right); + } + + bool skip = false; + if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(p_map_up) >= 0) { + //process + if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(p_map_up) > 0) { + left_poly = p; + left_portal = left; + } else { + clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners, p_map_up); + + apex_point = right_portal; + p = right_poly; + left_poly = p; + apex_poly = p; + left_portal = apex_point; + right_portal = apex_point; + + path.push_back(apex_point); + APPEND_METADATA(apex_poly->poly); + skip = true; + } + } + + if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(p_map_up) <= 0) { + //process + if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(p_map_up) < 0) { + right_poly = p; + right_portal = right; + } else { + clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners, p_map_up); + + apex_point = left_portal; + p = left_poly; + right_poly = p; + apex_poly = p; + right_portal = apex_point; + left_portal = apex_point; + + path.push_back(apex_point); + APPEND_METADATA(apex_poly->poly); + } + } + + // Go to the previous polygon. + if (p->back_navigation_poly_id != -1) { + p = &navigation_polys[p->back_navigation_poly_id]; + } else { + // The end + p = nullptr; + } + } + + // If the last point is not the begin point, add it to the list. + if (path[path.size() - 1] != begin_point) { + path.push_back(begin_point); + APPEND_METADATA(begin_poly); + } + + path.reverse(); + if (r_path_types) { + r_path_types->reverse(); + } + if (r_path_rids) { + r_path_rids->reverse(); + } + if (r_path_owners) { + r_path_owners->reverse(); + } + + } else { + path.push_back(end_point); + APPEND_METADATA(end_poly); + + // Add mid points + int np_id = least_cost_id; + while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) { + if (navigation_polys[np_id].back_navigation_edge != -1) { + int prev = navigation_polys[np_id].back_navigation_edge; + int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size(); + Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5; + + path.push_back(point); + APPEND_METADATA(navigation_polys[np_id].poly); + } else { + path.push_back(navigation_polys[np_id].entry); + APPEND_METADATA(navigation_polys[np_id].poly); + } + + np_id = navigation_polys[np_id].back_navigation_poly_id; + } + + path.push_back(begin_point); + APPEND_METADATA(begin_poly); + + path.reverse(); + if (r_path_types) { + r_path_types->reverse(); + } + if (r_path_rids) { + r_path_rids->reverse(); + } + if (r_path_owners) { + r_path_owners->reverse(); + } + } + + // Ensure post conditions (path arrays MUST match in size). + CRASH_COND(r_path_types && path.size() != r_path_types->size()); + CRASH_COND(r_path_rids && path.size() != r_path_rids->size()); + CRASH_COND(r_path_owners && path.size() != r_path_owners->size()); + + return path; +} + +Vector3 NavMeshQueries3D::polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) { + bool use_collision = p_use_collision; + Vector3 closest_point; + real_t closest_point_distance = FLT_MAX; + + for (const gd::Polygon &polygon : p_polygons) { + // For each face check the distance to the segment. + for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) { + const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos); + Vector3 intersection_point; + if (face.intersects_segment(p_from, p_to, &intersection_point)) { + const real_t d = p_from.distance_to(intersection_point); + if (!use_collision) { + closest_point = intersection_point; + use_collision = true; + closest_point_distance = d; + } else if (closest_point_distance > d) { + closest_point = intersection_point; + closest_point_distance = d; + } + } + // If segment does not itersect face, check the distance from segment's endpoints. + else if (!use_collision) { + const Vector3 p_from_closest = face.get_closest_point_to(p_from); + const real_t d_p_from = p_from.distance_to(p_from_closest); + if (closest_point_distance > d_p_from) { + closest_point = p_from_closest; + closest_point_distance = d_p_from; + } + + const Vector3 p_to_closest = face.get_closest_point_to(p_to); + const real_t d_p_to = p_to.distance_to(p_to_closest); + if (closest_point_distance > d_p_to) { + closest_point = p_to_closest; + closest_point_distance = d_p_to; + } + } + } + // Finally, check for a case when shortest distance is between some point located on a face's edge and some point located on a line segment. + if (!use_collision) { + for (size_t point_id = 0; point_id < polygon.points.size(); point_id += 1) { + Vector3 a, b; + + Geometry3D::get_closest_points_between_segments( + p_from, + p_to, + polygon.points[point_id].pos, + polygon.points[(point_id + 1) % polygon.points.size()].pos, + a, + b); + + const real_t d = a.distance_to(b); + if (d < closest_point_distance) { + closest_point_distance = d; + closest_point = b; + } + } + } + } + + return closest_point; +} + +Vector3 NavMeshQueries3D::polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point); + return cp.point; +} + +Vector3 NavMeshQueries3D::polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point); + return cp.normal; +} + +gd::ClosestPointQueryResult NavMeshQueries3D::polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult result; + real_t closest_point_distance_squared = FLT_MAX; + + for (const gd::Polygon &polygon : p_polygons) { + for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) { + const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos); + const Vector3 closest_point_on_face = face.get_closest_point_to(p_point); + const real_t distance_squared_to_point = closest_point_on_face.distance_squared_to(p_point); + if (distance_squared_to_point < closest_point_distance_squared) { + result.point = closest_point_on_face; + result.normal = face.get_plane().normal; + result.owner = polygon.owner->get_self(); + closest_point_distance_squared = distance_squared_to_point; + } + } + } + + return result; +} + +RID NavMeshQueries3D::polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point); + return cp.owner; +} + +void NavMeshQueries3D::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up) { + Vector3 from = path[path.size() - 1]; + + if (from.is_equal_approx(p_to_point)) { + return; + } + + Plane cut_plane; + cut_plane.normal = (from - p_to_point).cross(p_map_up); + if (cut_plane.normal == Vector3()) { + return; + } + cut_plane.normal.normalize(); + cut_plane.d = cut_plane.normal.dot(from); + + while (from_poly != p_to_poly) { + Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start; + Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end; + + ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1); + from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id]; + + if (!pathway_start.is_equal_approx(pathway_end)) { + Vector3 inters; + if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) { + if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) { + path.push_back(inters); + APPEND_METADATA(from_poly->poly); + } + } + } + } +} + +#endif // _3D_DISABLED diff --git a/modules/navigation/3d/nav_mesh_queries_3d.h b/modules/navigation/3d/nav_mesh_queries_3d.h new file mode 100644 index 0000000000..109bb2f971 --- /dev/null +++ b/modules/navigation/3d/nav_mesh_queries_3d.h @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* nav_mesh_queries_3d.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 NAV_MESH_QUERIES_3D_H +#define NAV_MESH_QUERIES_3D_H + +#ifndef _3D_DISABLED + +#include "../nav_map.h" + +class NavMeshQueries3D { +public: + static Vector3 polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly); + + static Vector<Vector3> polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size); + static Vector3 polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision); + static Vector3 polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + static Vector3 polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + static gd::ClosestPointQueryResult polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + static RID polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + + static void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up); +}; + +#endif // _3D_DISABLED + +#endif // NAV_MESH_QUERIES_3D_H diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp index d07d3cdff5..7f0cbc7b5e 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp @@ -126,9 +126,6 @@ void NavigationMeshEditor::edit(NavigationRegion3D *p_nav_region) { node = p_nav_region; } -void NavigationMeshEditor::_bind_methods() { -} - NavigationMeshEditor::NavigationMeshEditor() { bake_hbox = memnew(HBoxContainer); @@ -179,7 +176,7 @@ void NavigationMeshEditorPlugin::make_visible(bool p_visible) { NavigationMeshEditorPlugin::NavigationMeshEditorPlugin() { navigation_mesh_editor = memnew(NavigationMeshEditor); - EditorNode::get_singleton()->get_main_screen_control()->add_child(navigation_mesh_editor); + EditorNode::get_singleton()->get_gui_base()->add_child(navigation_mesh_editor); add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, navigation_mesh_editor->bake_hbox); navigation_mesh_editor->hide(); navigation_mesh_editor->bake_hbox->hide(); diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.h b/modules/navigation/editor/navigation_mesh_editor_plugin.h index 6114c62ebf..f5a471d531 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.h +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.h @@ -60,7 +60,6 @@ class NavigationMeshEditor : public Control { protected: void _node_removed(Node *p_node); - static void _bind_methods(); void _notification(int p_what); public: diff --git a/modules/navigation/nav_base.h b/modules/navigation/nav_base.h index c28392acf7..d2308abfaf 100644 --- a/modules/navigation/nav_base.h +++ b/modules/navigation/nav_base.h @@ -64,7 +64,7 @@ public: void set_owner_id(ObjectID p_owner_id) { owner_id = p_owner_id; } ObjectID get_owner_id() const { return owner_id; } - virtual ~NavBase(){}; + virtual ~NavBase() {} }; #endif // NAV_BASE_H diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index dfbc92a919..dd77e81b45 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -35,25 +35,13 @@ #include "nav_obstacle.h" #include "nav_region.h" +#include "3d/nav_mesh_queries_3d.h" + #include "core/config/project_settings.h" #include "core/object/worker_thread_pool.h" #include <Obstacle2d.h> -#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a))) - -// Helper macro -#define APPEND_METADATA(poly) \ - if (r_path_types) { \ - r_path_types->push_back(poly->owner->get_type()); \ - } \ - if (r_path_rids) { \ - r_path_rids->push_back(poly->owner->get_self()); \ - } \ - if (r_path_owners) { \ - r_path_owners->push_back(poly->owner->get_owner_id()); \ - } - #ifdef DEBUG_ENABLED #define NAVMAP_ITERATION_ZERO_ERROR_MSG() \ ERR_PRINT_ONCE("NavigationServer navigation map query failed because it was made before first map synchronization.\n\ @@ -142,462 +130,9 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p return Vector<Vector3>(); } - // Clear metadata outputs. - if (r_path_types) { - r_path_types->clear(); - } - if (r_path_rids) { - r_path_rids->clear(); - } - if (r_path_owners) { - r_path_owners->clear(); - } - - // Find the start poly and the end poly on this map. - const gd::Polygon *begin_poly = nullptr; - const gd::Polygon *end_poly = nullptr; - Vector3 begin_point; - Vector3 end_point; - real_t begin_d = FLT_MAX; - real_t end_d = FLT_MAX; - // Find the initial poly and the end poly on this map. - for (const gd::Polygon &p : polygons) { - // Only consider the polygon if it in a region with compatible layers. - if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) { - continue; - } - - // For each face check the distance between the origin/destination - for (size_t point_id = 2; point_id < p.points.size(); point_id++) { - const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - - Vector3 point = face.get_closest_point_to(p_origin); - real_t distance_to_point = point.distance_to(p_origin); - if (distance_to_point < begin_d) { - begin_d = distance_to_point; - begin_poly = &p; - begin_point = point; - } - - point = face.get_closest_point_to(p_destination); - distance_to_point = point.distance_to(p_destination); - if (distance_to_point < end_d) { - end_d = distance_to_point; - end_poly = &p; - end_point = point; - } - } - } - - // Check for trivial cases - if (!begin_poly || !end_poly) { - return Vector<Vector3>(); - } - if (begin_poly == end_poly) { - if (r_path_types) { - r_path_types->resize(2); - r_path_types->write[0] = begin_poly->owner->get_type(); - r_path_types->write[1] = end_poly->owner->get_type(); - } - - if (r_path_rids) { - r_path_rids->resize(2); - (*r_path_rids)[0] = begin_poly->owner->get_self(); - (*r_path_rids)[1] = end_poly->owner->get_self(); - } - - if (r_path_owners) { - r_path_owners->resize(2); - r_path_owners->write[0] = begin_poly->owner->get_owner_id(); - r_path_owners->write[1] = end_poly->owner->get_owner_id(); - } - - Vector<Vector3> path; - path.resize(2); - path.write[0] = begin_point; - path.write[1] = end_point; - return path; - } - - // List of all reachable navigation polys. - LocalVector<gd::NavigationPoly> navigation_polys; - navigation_polys.reserve(polygons.size() * 0.75); - - // Add the start polygon to the reachable navigation polygons. - gd::NavigationPoly begin_navigation_poly = gd::NavigationPoly(begin_poly); - begin_navigation_poly.self_id = 0; - begin_navigation_poly.entry = begin_point; - begin_navigation_poly.back_navigation_edge_pathway_start = begin_point; - begin_navigation_poly.back_navigation_edge_pathway_end = begin_point; - navigation_polys.push_back(begin_navigation_poly); - - // List of polygon IDs to visit. - List<uint32_t> to_visit; - to_visit.push_back(0); - - // This is an implementation of the A* algorithm. - int least_cost_id = 0; - int prev_least_cost_id = -1; - bool found_route = false; - - const gd::Polygon *reachable_end = nullptr; - real_t reachable_d = FLT_MAX; - bool is_reachable = true; - - while (true) { - // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance. - for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) { - // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon. - for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) { - const gd::Edge::Connection &connection = edge.connections[connection_index]; - - // Only consider the connection to another polygon if this polygon is in a region with compatible layers. - if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) { - continue; - } - - const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id]; - real_t poly_enter_cost = 0.0; - real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost(); - - if (prev_least_cost_id != -1 && (navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self())) { - poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost(); - } - prev_least_cost_id = least_cost_id; - - Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end }; - const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway); - const real_t new_distance = (least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost) + poly_enter_cost + least_cost_poly.traveled_distance; - - int64_t already_visited_polygon_index = navigation_polys.find(gd::NavigationPoly(connection.polygon)); - - if (already_visited_polygon_index != -1) { - // Polygon already visited, check if we can reduce the travel cost. - gd::NavigationPoly &avp = navigation_polys[already_visited_polygon_index]; - if (new_distance < avp.traveled_distance) { - avp.back_navigation_poly_id = least_cost_id; - avp.back_navigation_edge = connection.edge; - avp.back_navigation_edge_pathway_start = connection.pathway_start; - avp.back_navigation_edge_pathway_end = connection.pathway_end; - avp.traveled_distance = new_distance; - avp.entry = new_entry; - } - } else { - // Add the neighbor polygon to the reachable ones. - gd::NavigationPoly new_navigation_poly = gd::NavigationPoly(connection.polygon); - new_navigation_poly.self_id = navigation_polys.size(); - new_navigation_poly.back_navigation_poly_id = least_cost_id; - new_navigation_poly.back_navigation_edge = connection.edge; - new_navigation_poly.back_navigation_edge_pathway_start = connection.pathway_start; - new_navigation_poly.back_navigation_edge_pathway_end = connection.pathway_end; - new_navigation_poly.traveled_distance = new_distance; - new_navigation_poly.entry = new_entry; - navigation_polys.push_back(new_navigation_poly); - - // Add the neighbor polygon to the polygons to visit. - to_visit.push_back(navigation_polys.size() - 1); - } - } - } - - // Removes the least cost polygon from the list of polygons to visit so we can advance. - to_visit.erase(least_cost_id); - - // When the list of polygons to visit is empty at this point it means the End Polygon is not reachable - if (to_visit.size() == 0) { - // Thus use the further reachable polygon - ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons"); - is_reachable = false; - if (reachable_end == nullptr) { - // The path is not found and there is not a way out. - break; - } - - // Set as end point the furthest reachable point. - end_poly = reachable_end; - end_d = FLT_MAX; - for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) { - Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos); - Vector3 spoint = f.get_closest_point_to(p_destination); - real_t dpoint = spoint.distance_to(p_destination); - if (dpoint < end_d) { - end_point = spoint; - end_d = dpoint; - } - } - - // Search all faces of start polygon as well. - bool closest_point_on_start_poly = false; - for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { - Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); - Vector3 spoint = f.get_closest_point_to(p_destination); - real_t dpoint = spoint.distance_to(p_destination); - if (dpoint < end_d) { - end_point = spoint; - end_d = dpoint; - closest_point_on_start_poly = true; - } - } - - if (closest_point_on_start_poly) { - // No point to run PostProcessing when start and end convex polygon is the same. - if (r_path_types) { - r_path_types->resize(2); - r_path_types->write[0] = begin_poly->owner->get_type(); - r_path_types->write[1] = begin_poly->owner->get_type(); - } - - if (r_path_rids) { - r_path_rids->resize(2); - (*r_path_rids)[0] = begin_poly->owner->get_self(); - (*r_path_rids)[1] = begin_poly->owner->get_self(); - } - - if (r_path_owners) { - r_path_owners->resize(2); - r_path_owners->write[0] = begin_poly->owner->get_owner_id(); - r_path_owners->write[1] = begin_poly->owner->get_owner_id(); - } - - Vector<Vector3> path; - path.resize(2); - path.write[0] = begin_point; - path.write[1] = end_point; - return path; - } - - // Reset open and navigation_polys - gd::NavigationPoly np = navigation_polys[0]; - navigation_polys.clear(); - navigation_polys.push_back(np); - to_visit.clear(); - to_visit.push_back(0); - least_cost_id = 0; - prev_least_cost_id = -1; - - reachable_end = nullptr; - - continue; - } - - // Find the polygon with the minimum cost from the list of polygons to visit. - least_cost_id = -1; - real_t least_cost = FLT_MAX; - for (List<uint32_t>::Element *element = to_visit.front(); element != nullptr; element = element->next()) { - gd::NavigationPoly *np = &navigation_polys[element->get()]; - real_t cost = np->traveled_distance; - cost += (np->entry.distance_to(end_point) * np->poly->owner->get_travel_cost()); - if (cost < least_cost) { - least_cost_id = np->self_id; - least_cost = cost; - } - } - - ERR_BREAK(least_cost_id == -1); - - // Stores the further reachable end polygon, in case our goal is not reachable. - if (is_reachable) { - real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination); - if (reachable_d > d) { - reachable_d = d; - reachable_end = navigation_polys[least_cost_id].poly; - } - } - - // Check if we reached the end - if (navigation_polys[least_cost_id].poly == end_poly) { - found_route = true; - break; - } - } - - // We did not find a route but we have both a start polygon and an end polygon at this point. - // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon. - if (!found_route) { - end_d = FLT_MAX; - // Search all faces of the start polygon for the closest point to our target position. - for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { - Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); - Vector3 spoint = f.get_closest_point_to(p_destination); - real_t dpoint = spoint.distance_to(p_destination); - if (dpoint < end_d) { - end_point = spoint; - end_d = dpoint; - } - } - - if (r_path_types) { - r_path_types->resize(2); - r_path_types->write[0] = begin_poly->owner->get_type(); - r_path_types->write[1] = begin_poly->owner->get_type(); - } - - if (r_path_rids) { - r_path_rids->resize(2); - (*r_path_rids)[0] = begin_poly->owner->get_self(); - (*r_path_rids)[1] = begin_poly->owner->get_self(); - } - - if (r_path_owners) { - r_path_owners->resize(2); - r_path_owners->write[0] = begin_poly->owner->get_owner_id(); - r_path_owners->write[1] = begin_poly->owner->get_owner_id(); - } - - Vector<Vector3> path; - path.resize(2); - path.write[0] = begin_point; - path.write[1] = end_point; - return path; - } - - Vector<Vector3> path; - // Optimize the path. - if (p_optimize) { - // Set the apex poly/point to the end point - gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id]; - - Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end }; - const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway); - if (end_point.is_equal_approx(back_edge_closest_point)) { - // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing. - // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon. - if (apex_poly->back_navigation_poly_id != -1) { - apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id]; - } - } - - Vector3 apex_point = end_point; - - gd::NavigationPoly *left_poly = apex_poly; - Vector3 left_portal = apex_point; - gd::NavigationPoly *right_poly = apex_poly; - Vector3 right_portal = apex_point; - - gd::NavigationPoly *p = apex_poly; - - path.push_back(end_point); - APPEND_METADATA(end_poly); - - while (p) { - // Set left and right points of the pathway between polygons. - Vector3 left = p->back_navigation_edge_pathway_start; - Vector3 right = p->back_navigation_edge_pathway_end; - if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(up) < 0) { - SWAP(left, right); - } - - bool skip = false; - if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(up) >= 0) { - //process - if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(up) > 0) { - left_poly = p; - left_portal = left; - } else { - clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners); - - apex_point = right_portal; - p = right_poly; - left_poly = p; - apex_poly = p; - left_portal = apex_point; - right_portal = apex_point; - - path.push_back(apex_point); - APPEND_METADATA(apex_poly->poly); - skip = true; - } - } - - if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(up) <= 0) { - //process - if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(up) < 0) { - right_poly = p; - right_portal = right; - } else { - clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners); - - apex_point = left_portal; - p = left_poly; - right_poly = p; - apex_poly = p; - right_portal = apex_point; - left_portal = apex_point; - - path.push_back(apex_point); - APPEND_METADATA(apex_poly->poly); - } - } - - // Go to the previous polygon. - if (p->back_navigation_poly_id != -1) { - p = &navigation_polys[p->back_navigation_poly_id]; - } else { - // The end - p = nullptr; - } - } - - // If the last point is not the begin point, add it to the list. - if (path[path.size() - 1] != begin_point) { - path.push_back(begin_point); - APPEND_METADATA(begin_poly); - } - - path.reverse(); - if (r_path_types) { - r_path_types->reverse(); - } - if (r_path_rids) { - r_path_rids->reverse(); - } - if (r_path_owners) { - r_path_owners->reverse(); - } - - } else { - path.push_back(end_point); - APPEND_METADATA(end_poly); - - // Add mid points - int np_id = least_cost_id; - while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) { - if (navigation_polys[np_id].back_navigation_edge != -1) { - int prev = navigation_polys[np_id].back_navigation_edge; - int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size(); - Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5; - - path.push_back(point); - APPEND_METADATA(navigation_polys[np_id].poly); - } else { - path.push_back(navigation_polys[np_id].entry); - APPEND_METADATA(navigation_polys[np_id].poly); - } - - np_id = navigation_polys[np_id].back_navigation_poly_id; - } - - path.push_back(begin_point); - APPEND_METADATA(begin_poly); - - path.reverse(); - if (r_path_types) { - r_path_types->reverse(); - } - if (r_path_rids) { - r_path_rids->reverse(); - } - if (r_path_owners) { - r_path_owners->reverse(); - } - } - - // Ensure post conditions (path arrays MUST match in size). - CRASH_COND(r_path_types && path.size() != r_path_types->size()); - CRASH_COND(r_path_rids && path.size() != r_path_rids->size()); - CRASH_COND(r_path_owners && path.size() != r_path_owners->size()); - - return path; + return NavMeshQueries3D::polygons_get_path( + polygons, p_origin, p_destination, p_optimize, p_navigation_layers, + r_path_types, r_path_rids, r_path_owners, up, link_polygons.size()); } Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const { @@ -607,50 +142,7 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector return Vector3(); } - bool use_collision = p_use_collision; - Vector3 closest_point; - real_t closest_point_d = FLT_MAX; - - for (const gd::Polygon &p : polygons) { - // For each face check the distance to the segment - for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - Vector3 inters; - if (f.intersects_segment(p_from, p_to, &inters)) { - const real_t d = closest_point_d = p_from.distance_to(inters); - if (use_collision == false) { - closest_point = inters; - use_collision = true; - closest_point_d = d; - } else if (closest_point_d > d) { - closest_point = inters; - closest_point_d = d; - } - } - } - - if (use_collision == false) { - for (size_t point_id = 0; point_id < p.points.size(); point_id += 1) { - Vector3 a, b; - - Geometry3D::get_closest_points_between_segments( - p_from, - p_to, - p.points[point_id].pos, - p.points[(point_id + 1) % p.points.size()].pos, - a, - b); - - const real_t d = a.distance_to(b); - if (d < closest_point_d) { - closest_point_d = d; - closest_point = b; - } - } - } - } - - return closest_point; + return NavMeshQueries3D::polygons_get_closest_point_to_segment(polygons, p_from, p_to, p_use_collision); } Vector3 NavMap::get_closest_point(const Vector3 &p_point) const { @@ -659,8 +151,8 @@ Vector3 NavMap::get_closest_point(const Vector3 &p_point) const { NAVMAP_ITERATION_ZERO_ERROR_MSG(); return Vector3(); } - gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); - return cp.point; + + return NavMeshQueries3D::polygons_get_closest_point(polygons, p_point); } Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const { @@ -669,8 +161,8 @@ Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const { NAVMAP_ITERATION_ZERO_ERROR_MSG(); return Vector3(); } - gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); - return cp.normal; + + return NavMeshQueries3D::polygons_get_closest_point_normal(polygons, p_point); } RID NavMap::get_closest_point_owner(const Vector3 &p_point) const { @@ -679,32 +171,14 @@ RID NavMap::get_closest_point_owner(const Vector3 &p_point) const { NAVMAP_ITERATION_ZERO_ERROR_MSG(); return RID(); } - gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); - return cp.owner; + + return NavMeshQueries3D::polygons_get_closest_point_owner(polygons, p_point); } gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_point) const { RWLockRead read_lock(map_rwlock); - gd::ClosestPointQueryResult result; - real_t closest_point_ds = FLT_MAX; - - for (const gd::Polygon &p : polygons) { - // For each face check the distance to the point - for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - const Vector3 inters = f.get_closest_point_to(p_point); - const real_t ds = inters.distance_squared_to(p_point); - if (ds < closest_point_ds) { - result.point = inters; - result.normal = f.get_plane().normal; - result.owner = p.owner->get_self(); - closest_point_ds = ds; - } - } - } - - return result; + return NavMeshQueries3D::polygons_get_closest_point_info(polygons, p_point); } void NavMap::add_region(NavRegion *p_region) { @@ -891,6 +365,7 @@ void NavMap::sync() { int _new_pm_edge_merge_count = pm_edge_merge_count; int _new_pm_edge_connection_count = pm_edge_connection_count; int _new_pm_edge_free_count = pm_edge_free_count; + int _new_pm_obstacle_count = obstacles.size(); // Check if we need to update the links. if (regenerate_polygons) { @@ -920,34 +395,36 @@ void NavMap::sync() { _new_pm_edge_free_count = 0; // Remove regions connections. + region_external_connections.clear(); for (NavRegion *region : regions) { - region->get_connections().clear(); + region_external_connections[region] = LocalVector<gd::Edge::Connection>(); } // Resize the polygon count. - int count = 0; + int polygon_count = 0; for (const NavRegion *region : regions) { if (!region->get_enabled()) { continue; } - count += region->get_polygons().size(); + polygon_count += region->get_polygons().size(); } - polygons.resize(count); + polygons.resize(polygon_count); // Copy all region polygons in the map. - count = 0; + polygon_count = 0; for (const NavRegion *region : regions) { if (!region->get_enabled()) { continue; } const LocalVector<gd::Polygon> &polygons_source = region->get_polygons(); for (uint32_t n = 0; n < polygons_source.size(); n++) { - polygons[count + n] = polygons_source[n]; + polygons[polygon_count] = polygons_source[n]; + polygons[polygon_count].id = polygon_count; + polygon_count++; } - count += region->get_polygons().size(); } - _new_pm_polygon_count = polygons.size(); + _new_pm_polygon_count = polygon_count; // Group all edges per key. HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections; @@ -1055,7 +532,7 @@ void NavMap::sync() { free_edge.polygon->edges[free_edge.edge].connections.push_back(new_connection); // Add the connection to the region_connection map. - ((NavRegion *)free_edge.polygon->owner)->get_connections().push_back(new_connection); + region_external_connections[(NavRegion *)free_edge.polygon->owner].push_back(new_connection); _new_pm_edge_connection_count += 1; } } @@ -1118,6 +595,7 @@ void NavMap::sync() { // If we have both a start and end point, then create a synthetic polygon to route through. if (closest_start_polygon && closest_end_polygon) { gd::Polygon &new_polygon = link_polygons[link_poly_idx++]; + new_polygon.id = polygon_count++; new_polygon.owner = link; new_polygon.edges.clear(); @@ -1131,13 +609,6 @@ void NavMap::sync() { new_polygon.points.push_back({ closest_end_point, get_point_key(closest_end_point) }); new_polygon.points.push_back({ closest_end_point, get_point_key(closest_end_point) }); - Vector3 center; - for (int p = 0; p < 4; ++p) { - center += new_polygon.points[p].pos; - } - new_polygon.center = center / real_t(new_polygon.points.size()); - new_polygon.clockwise = true; - // Setup connections to go forward in the link. { gd::Edge::Connection entry_connection; @@ -1210,6 +681,7 @@ void NavMap::sync() { pm_edge_merge_count = _new_pm_edge_merge_count; pm_edge_connection_count = _new_pm_edge_connection_count; pm_edge_free_count = _new_pm_edge_free_count; + pm_obstacle_count = _new_pm_obstacle_count; } void NavMap::_update_rvo_obstacles_tree_2d() { @@ -1379,42 +851,43 @@ void NavMap::dispatch_callbacks() { } } -void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const { - Vector3 from = path[path.size() - 1]; +void NavMap::_update_merge_rasterizer_cell_dimensions() { + merge_rasterizer_cell_size = cell_size * merge_rasterizer_cell_scale; + merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale; +} + +int NavMap::get_region_connections_count(NavRegion *p_region) const { + ERR_FAIL_NULL_V(p_region, 0); - if (from.is_equal_approx(p_to_point)) { - return; + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region); + if (found_connections) { + return found_connections->value.size(); } - Plane cut_plane; - cut_plane.normal = (from - p_to_point).cross(up); - if (cut_plane.normal == Vector3()) { - return; - } - cut_plane.normal.normalize(); - cut_plane.d = cut_plane.normal.dot(from); - - while (from_poly != p_to_poly) { - Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start; - Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end; - - ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1); - from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id]; - - if (!pathway_start.is_equal_approx(pathway_end)) { - Vector3 inters; - if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) { - if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) { - path.push_back(inters); - APPEND_METADATA(from_poly->poly); - } - } - } + return 0; +} + +Vector3 NavMap::get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const { + ERR_FAIL_NULL_V(p_region, Vector3()); + + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region); + if (found_connections) { + ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3()); + return found_connections->value[p_connection_id].pathway_start; } + + return Vector3(); } -void NavMap::_update_merge_rasterizer_cell_dimensions() { - merge_rasterizer_cell_size = cell_size * merge_rasterizer_cell_scale; - merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale; +Vector3 NavMap::get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const { + ERR_FAIL_NULL_V(p_region, Vector3()); + + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region); + if (found_connections) { + ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3()); + return found_connections->value[p_connection_id].pathway_end; + } + + return Vector3(); } NavMap::NavMap() { diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index d6215ea57f..b9120c04d9 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -36,6 +36,7 @@ #include "core/math/math_defs.h" #include "core/object/worker_thread_pool.h" +#include "servers/navigation/navigation_globals.h" #include <KdTree2d.h> #include <KdTree3d.h> @@ -55,21 +56,21 @@ class NavMap : public NavRid { /// To find the polygons edges the vertices are displaced in a grid where /// each cell has the following cell_size and cell_height. - real_t cell_size = 0.25; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size. - real_t cell_height = 0.25; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height. + real_t cell_size = NavigationDefaults3D::navmesh_cell_size; + real_t cell_height = NavigationDefaults3D::navmesh_cell_height; // For the inter-region merging to work, internal rasterization is performed. - float merge_rasterizer_cell_size = 0.25; - float merge_rasterizer_cell_height = 0.25; + float merge_rasterizer_cell_size = NavigationDefaults3D::navmesh_cell_size; + float merge_rasterizer_cell_height = NavigationDefaults3D::navmesh_cell_height; // This value is used to control sensitivity of internal rasterizer. float merge_rasterizer_cell_scale = 1.0; bool use_edge_connections = true; /// This value is used to detect the near edges to connect. - real_t edge_connection_margin = 0.25; + real_t edge_connection_margin = NavigationDefaults3D::edge_connection_margin; /// This value is used to limit how far links search to find polygons to connect to. - real_t link_connection_radius = 1.0; + real_t link_connection_radius = NavigationDefaults3D::link_connection_radius; bool regenerate_polygons = true; bool regenerate_links = true; @@ -123,6 +124,9 @@ class NavMap : public NavRid { int pm_edge_merge_count = 0; int pm_edge_connection_count = 0; int pm_edge_free_count = 0; + int pm_obstacle_count = 0; + + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>> region_external_connections; public: NavMap(); @@ -216,6 +220,11 @@ public: int get_pm_edge_merge_count() const { return pm_edge_merge_count; } int get_pm_edge_connection_count() const { return pm_edge_connection_count; } int get_pm_edge_free_count() const { return pm_edge_free_count; } + int get_pm_obstacle_count() const { return pm_obstacle_count; } + + int get_region_connections_count(NavRegion *p_region) const; + Vector3 get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const; + Vector3 get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const; private: void compute_single_step(uint32_t index, NavAgent **agent); @@ -223,7 +232,6 @@ private: void compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent); void compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent); - void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const; void _update_rvo_simulation(); void _update_rvo_obstacles_tree_2d(); void _update_rvo_agents_tree_2d(); diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index 9cb235d79f..2c91b80af2 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -32,6 +32,8 @@ #include "nav_map.h" +#include "3d/nav_mesh_queries_3d.h" + void NavRegion::set_map(NavMap *p_map) { if (map == p_map) { return; @@ -44,8 +46,6 @@ void NavRegion::set_map(NavMap *p_map) { map = p_map; polygons_dirty = true; - connections.clear(); - if (map) { map->add_region(this); } @@ -74,115 +74,63 @@ void NavRegion::set_transform(Transform3D p_transform) { } transform = p_transform; polygons_dirty = true; -} - -void NavRegion::set_mesh(Ref<NavigationMesh> p_mesh) { - mesh = p_mesh; - polygons_dirty = true; -} -int NavRegion::get_connections_count() const { - if (!map) { - return 0; +#ifdef DEBUG_ENABLED + if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) { + ERR_PRINT_ONCE("Attempted to update a navigation region transform rotated 90 degrees or more away from the current navigation map UP orientation."); } - return connections.size(); -} - -Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const { - ERR_FAIL_NULL_V(map, Vector3()); - ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); - return connections[p_connection_id].pathway_start; -} - -Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const { - ERR_FAIL_NULL_V(map, Vector3()); - ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); - return connections[p_connection_id].pathway_end; +#endif // DEBUG_ENABLED } -Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const { - if (!get_enabled()) { - return Vector3(); +void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) { +#ifdef DEBUG_ENABLED + if (map && p_navigation_mesh.is_valid() && !Math::is_equal_approx(double(map->get_cell_size()), double(p_navigation_mesh->get_cell_size()))) { + ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_size()), double(map->get_cell_size()))); } - const LocalVector<gd::Polygon> ®ion_polygons = get_polygons(); - - if (region_polygons.is_empty()) { - return Vector3(); + if (map && p_navigation_mesh.is_valid() && !Math::is_equal_approx(double(map->get_cell_height()), double(p_navigation_mesh->get_cell_height()))) { + ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_height()), double(map->get_cell_height()))); } +#endif // DEBUG_ENABLED - if (p_uniformly) { - real_t accumulated_area = 0; - RBMap<real_t, uint32_t> region_area_map; - - for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) { - const gd::Polygon ®ion_polygon = region_polygons[rp_index]; - real_t polyon_area = region_polygon.surface_area; - - if (polyon_area == 0.0) { - continue; - } - region_area_map[accumulated_area] = rp_index; - accumulated_area += polyon_area; - } - if (region_area_map.is_empty() || accumulated_area == 0) { - // All polygons have no real surface / no area. - return Vector3(); - } - - real_t region_area_map_pos = Math::random(real_t(0), accumulated_area); - - RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos); - ERR_FAIL_COND_V(!region_E, Vector3()); - uint32_t rrp_polygon_index = region_E->value; - ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3()); - - const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; - - real_t accumulated_polygon_area = 0; - RBMap<real_t, uint32_t> polygon_area_map; + RWLockWrite write_lock(navmesh_rwlock); - for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) { - real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area(); + pending_navmesh_vertices.clear(); + pending_navmesh_polygons.clear(); - if (face_area == 0.0) { - continue; - } - polygon_area_map[accumulated_polygon_area] = rpp_index; - accumulated_polygon_area += face_area; - } - if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) { - // All faces have no real surface / no area. - return Vector3(); - } - - real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area); - - RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos); - ERR_FAIL_COND_V(!polygon_E, Vector3()); - uint32_t rrp_face_index = polygon_E->value; - ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3()); + if (p_navigation_mesh.is_valid()) { + p_navigation_mesh->get_data(pending_navmesh_vertices, pending_navmesh_polygons); + } - const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); + polygons_dirty = true; +} - Vector3 face_random_position = face.get_random_point_inside(); - return face_random_position; +Vector3 NavRegion::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const { + RWLockRead read_lock(region_rwlock); - } else { - uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1); + return NavMeshQueries3D::polygons_get_closest_point_to_segment( + get_polygons(), p_from, p_to, p_use_collision); +} - const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; +gd::ClosestPointQueryResult NavRegion::get_closest_point_info(const Vector3 &p_point) const { + RWLockRead read_lock(region_rwlock); - uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1); + return NavMeshQueries3D::polygons_get_closest_point_info(get_polygons(), p_point); +} - const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); +Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const { + RWLockRead read_lock(region_rwlock); - Vector3 face_random_position = face.get_random_point_inside(); - return face_random_position; + if (!get_enabled()) { + return Vector3(); } + + return NavMeshQueries3D::polygons_get_random_point(get_polygons(), p_navigation_layers, p_uniformly); } bool NavRegion::sync() { + RWLockWrite write_lock(region_rwlock); + bool something_changed = polygons_dirty /* || something_dirty? */; update_polygons(); @@ -202,33 +150,20 @@ void NavRegion::update_polygons() { return; } - if (mesh.is_null()) { - return; - } - -#ifdef DEBUG_ENABLED - if (!Math::is_equal_approx(double(map->get_cell_size()), double(mesh->get_cell_size()))) { - ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_size()), double(map->get_cell_size()))); - } - - if (!Math::is_equal_approx(double(map->get_cell_height()), double(mesh->get_cell_height()))) { - ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_height()), double(map->get_cell_height()))); - } + RWLockRead read_lock(navmesh_rwlock); - if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) { - ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to update a navigation region transform rotated 90 degrees or more away from the current navigation map UP orientation."); + if (pending_navmesh_vertices.is_empty() || pending_navmesh_polygons.is_empty()) { + return; } -#endif // DEBUG_ENABLED - Vector<Vector3> vertices = mesh->get_vertices(); - int len = vertices.size(); + int len = pending_navmesh_vertices.size(); if (len == 0) { return; } - const Vector3 *vertices_r = vertices.ptr(); + const Vector3 *vertices_r = pending_navmesh_vertices.ptr(); - polygons.resize(mesh->get_polygon_count()); + polygons.resize(pending_navmesh_polygons.size()); real_t _new_region_surface_area = 0.0; @@ -238,7 +173,7 @@ void NavRegion::update_polygons() { polygon.owner = this; polygon.surface_area = 0.0; - Vector<int> navigation_mesh_polygon = mesh->get_polygon(navigation_mesh_polygon_index); + Vector<int> navigation_mesh_polygon = pending_navmesh_polygons[navigation_mesh_polygon_index]; navigation_mesh_polygon_index += 1; int navigation_mesh_polygon_size = navigation_mesh_polygon.size(); @@ -266,9 +201,6 @@ void NavRegion::update_polygons() { polygon.surface_area = _new_polygon_surface_area; _new_region_surface_area += _new_polygon_surface_area; - Vector3 polygon_center; - real_t sum(0); - for (int j(0); j < navigation_mesh_polygon_size; j++) { int idx = indices[j]; if (idx < 0 || idx >= len) { @@ -279,25 +211,11 @@ void NavRegion::update_polygons() { Vector3 point_position = transform.xform(vertices_r[idx]); polygon.points[j].pos = point_position; polygon.points[j].key = map->get_point_key(point_position); - - polygon_center += point_position; // Composing the center of the polygon - - if (j >= 2) { - Vector3 epa = transform.xform(vertices_r[indices[j - 2]]); - Vector3 epb = transform.xform(vertices_r[indices[j - 1]]); - - sum += map->get_up().dot((epb - epa).cross(point_position - epa)); - } } if (!valid) { ERR_BREAK_MSG(!valid, "The navigation mesh set in this region is not valid!"); } - - polygon.clockwise = sum > 0; - if (!navigation_mesh_polygon.is_empty()) { - polygon.center = polygon_center / real_t(navigation_mesh_polygon.size()); - } } surface_area = _new_region_surface_area; diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h index a9cfc53c7e..c015802b92 100644 --- a/modules/navigation/nav_region.h +++ b/modules/navigation/nav_region.h @@ -34,13 +34,14 @@ #include "nav_base.h" #include "nav_utils.h" +#include "core/os/rw_lock.h" #include "scene/resources/navigation_mesh.h" class NavRegion : public NavBase { + RWLock region_rwlock; + NavMap *map = nullptr; Transform3D transform; - Ref<NavigationMesh> mesh; - Vector<gd::Edge::Connection> connections; bool enabled = true; bool use_edge_connections = true; @@ -52,6 +53,10 @@ class NavRegion : public NavBase { real_t surface_area = 0.0; + RWLock navmesh_rwlock; + Vector<Vector3> pending_navmesh_vertices; + Vector<Vector<int>> pending_navmesh_polygons; + public: NavRegion() { type = NavigationUtilities::PathSegmentType::PATH_SEGMENT_TYPE_REGION; @@ -79,22 +84,14 @@ public: return transform; } - void set_mesh(Ref<NavigationMesh> p_mesh); - const Ref<NavigationMesh> get_mesh() const { - return mesh; - } - - Vector<gd::Edge::Connection> &get_connections() { - return connections; - } - int get_connections_count() const; - Vector3 get_connection_pathway_start(int p_connection_id) const; - Vector3 get_connection_pathway_end(int p_connection_id) const; + void set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh); LocalVector<gd::Polygon> const &get_polygons() const { return polygons; } + Vector3 get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const; + gd::ClosestPointQueryResult get_closest_point_info(const Vector3 &p_point) const; Vector3 get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const; real_t get_surface_area() const { return surface_area; }; diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h index 175d08ca6d..ba4c44b748 100644 --- a/modules/navigation/nav_utils.h +++ b/modules/navigation/nav_utils.h @@ -98,28 +98,27 @@ struct Edge { }; struct Polygon { + /// Id of the polygon in the map. + uint32_t id = UINT32_MAX; + /// Navigation region or link that contains this polygon. const NavBase *owner = nullptr; /// The points of this `Polygon` LocalVector<Point> points; - /// Are the points clockwise? - bool clockwise; - /// The edges of this `Polygon` LocalVector<Edge> edges; - /// The center of this `Polygon` - Vector3 center; - real_t surface_area = 0.0; }; struct NavigationPoly { - uint32_t self_id = 0; /// This poly. - const Polygon *poly; + const Polygon *poly = nullptr; + + /// Index in the heap of traversable polygons. + uint32_t traversable_poly_index = UINT32_MAX; /// Those 4 variables are used to travel the path backwards. int back_navigation_poly_id = -1; @@ -129,20 +128,44 @@ struct NavigationPoly { /// The entry position of this poly. Vector3 entry; - /// The distance to the destination. + /// The distance traveled until now (g cost). real_t traveled_distance = 0.0; + /// The distance to the destination (h cost). + real_t distance_to_destination = 0.0; - NavigationPoly() { poly = nullptr; } + /// The total travel cost (f cost). + real_t total_travel_cost() const { + return traveled_distance + distance_to_destination; + } - NavigationPoly(const Polygon *p_poly) : - poly(p_poly) {} + bool operator==(const NavigationPoly &p_other) const { + return poly == p_other.poly; + } - bool operator==(const NavigationPoly &other) const { - return poly == other.poly; + bool operator!=(const NavigationPoly &p_other) const { + return !(*this == p_other); } +}; + +struct NavPolyTravelCostGreaterThan { + // Returns `true` if the travel cost of `a` is higher than that of `b`. + bool operator()(const NavigationPoly *p_poly_a, const NavigationPoly *p_poly_b) const { + real_t f_cost_a = p_poly_a->total_travel_cost(); + real_t h_cost_a = p_poly_a->distance_to_destination; + real_t f_cost_b = p_poly_b->total_travel_cost(); + real_t h_cost_b = p_poly_b->distance_to_destination; + + if (f_cost_a != f_cost_b) { + return f_cost_a > f_cost_b; + } else { + return h_cost_a > h_cost_b; + } + } +}; - bool operator!=(const NavigationPoly &other) const { - return !operator==(other); +struct NavPolyHeapIndexer { + void operator()(NavigationPoly *p_poly, uint32_t p_heap_index) const { + p_poly->traversable_poly_index = p_heap_index; } }; @@ -152,6 +175,129 @@ struct ClosestPointQueryResult { RID owner; }; +template <typename T> +struct NoopIndexer { + void operator()(const T &p_value, uint32_t p_index) {} +}; + +/** + * A max-heap implementation that notifies of element index changes. + */ +template <typename T, typename LessThan = Comparator<T>, typename Indexer = NoopIndexer<T>> +class Heap { + LocalVector<T> _buffer; + + LessThan _less_than; + Indexer _indexer; + +public: + void reserve(uint32_t p_size) { + _buffer.reserve(p_size); + } + + uint32_t size() const { + return _buffer.size(); + } + + bool is_empty() const { + return _buffer.is_empty(); + } + + void push(const T &p_element) { + _buffer.push_back(p_element); + _indexer(p_element, _buffer.size() - 1); + _shift_up(_buffer.size() - 1); + } + + T pop() { + ERR_FAIL_COND_V_MSG(_buffer.is_empty(), T(), "Can't pop an empty heap."); + T value = _buffer[0]; + _indexer(value, UINT32_MAX); + if (_buffer.size() > 1) { + _buffer[0] = _buffer[_buffer.size() - 1]; + _indexer(_buffer[0], 0); + _buffer.remove_at(_buffer.size() - 1); + _shift_down(0); + } else { + _buffer.remove_at(_buffer.size() - 1); + } + return value; + } + + /** + * Update the position of the element in the heap if necessary. + */ + void shift(uint32_t p_index) { + ERR_FAIL_UNSIGNED_INDEX_MSG(p_index, _buffer.size(), "Heap element index is out of range."); + if (!_shift_up(p_index)) { + _shift_down(p_index); + } + } + + void clear() { + for (const T &value : _buffer) { + _indexer(value, UINT32_MAX); + } + _buffer.clear(); + } + + Heap() {} + + Heap(const LessThan &p_less_than) : + _less_than(p_less_than) {} + + Heap(const Indexer &p_indexer) : + _indexer(p_indexer) {} + + Heap(const LessThan &p_less_than, const Indexer &p_indexer) : + _less_than(p_less_than), _indexer(p_indexer) {} + +private: + bool _shift_up(uint32_t p_index) { + T value = _buffer[p_index]; + uint32_t current_index = p_index; + uint32_t parent_index = (current_index - 1) / 2; + while (current_index > 0 && _less_than(_buffer[parent_index], value)) { + _buffer[current_index] = _buffer[parent_index]; + _indexer(_buffer[current_index], current_index); + current_index = parent_index; + parent_index = (current_index - 1) / 2; + } + if (current_index != p_index) { + _buffer[current_index] = value; + _indexer(value, current_index); + return true; + } else { + return false; + } + } + + bool _shift_down(uint32_t p_index) { + T value = _buffer[p_index]; + uint32_t current_index = p_index; + uint32_t child_index = 2 * current_index + 1; + while (child_index < _buffer.size()) { + if (child_index + 1 < _buffer.size() && + _less_than(_buffer[child_index], _buffer[child_index + 1])) { + child_index++; + } + if (_less_than(_buffer[child_index], value)) { + break; + } + _buffer[current_index] = _buffer[child_index]; + _indexer(_buffer[current_index], current_index); + current_index = child_index; + child_index = 2 * current_index + 1; + } + if (current_index != p_index) { + _buffer[current_index] = value; + _indexer(value, current_index); + return true; + } else { + return false; + } + } +}; } // namespace gd #endif // NAV_UTILS_H diff --git a/modules/noise/icons/NoiseTexture2D.svg b/modules/noise/icons/NoiseTexture2D.svg index 0c22cfdcc6..94d550d141 100644 --- a/modules/noise/icons/NoiseTexture2D.svg +++ b/modules/noise/icons/NoiseTexture2D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm1 2h10v8H3zm3 1v2h2V4zm2 2v2h2v2h2V4h-2v2zm0 2H6V6H4v4h4z" fill="#e0e0e0"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm1 2h10v8H3zm3 1v2h2V4zm2 2v2h2v2h2V4h-2v2zm0 2H6V6H4v4h4z"/></svg>
\ No newline at end of file diff --git a/modules/noise/icons/NoiseTexture3D.svg b/modules/noise/icons/NoiseTexture3D.svg index 92da633dce..178bac2dd3 100644 --- a/modules/noise/icons/NoiseTexture3D.svg +++ b/modules/noise/icons/NoiseTexture3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M1 14a1 1 0 0 0 1 1h9.5a1 1 0 0 0 .707-.293l2.5-2.5A1 1 0 0 0 15 11.5V2a1 1 0 0 0-1-1H4.5a1 1 0 0 0-.707.293l-2.5 2.5A1 1 0 0 0 1 4.5zm1.25-9H11v7H2.25zm10 6.25v-6.5L14 3v6.5zm-1-7.5H3L4.75 2H13zM3 11h4l1.25-1.25V9H9l1.25-1.25v-2h-2L7 7v.75h-.75v-2h-2L3 7z" fill="#e0e0e0"/><path d="M3 7h2l1.25-1.25h-2zm2 2h2V7.75h-.75zm2-2h2l1.25-1.25H8z" fill="#000" fill-opacity=".4"/><path d="M5 7v2l1.25-1.25v-2zm2 2v2l1.25-1.25V9zm2 0V7l1.25-1.25v2z" fill="#000" fill-opacity=".2"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M1 14a1 1 0 0 0 1 1h9.5a1 1 0 0 0 .707-.293l2.5-2.5A1 1 0 0 0 15 11.5V2a1 1 0 0 0-1-1H4.5a1 1 0 0 0-.707.293l-2.5 2.5A1 1 0 0 0 1 4.5zm1.25-9H11v7H2.25zm10 6.25v-6.5L14 3v6.5zm-1-7.5H3L4.75 2H13zM3 11h4l1.25-1.25V9H9l1.25-1.25v-2h-2L7 7v.75h-.75v-2h-2L3 7z"/><path fill-opacity=".4" d="M3 7h2l1.25-1.25h-2zm2 2h2V7.75h-.75zm2-2h2l1.25-1.25H8z"/><path fill-opacity=".2" d="M5 7v2l1.25-1.25v-2zm2 2v2l1.25-1.25V9zm2 0V7l1.25-1.25v2z"/></svg>
\ No newline at end of file diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp index 0443fec4a0..b55b1141e1 100644 --- a/modules/noise/noise_texture_2d.cpp +++ b/modules/noise/noise_texture_2d.cpp @@ -119,6 +119,7 @@ void NoiseTexture2D::_set_texture_image(const Ref<Image> &p_image) { } else { texture = RS::get_singleton()->texture_2d_create(p_image); } + RS::get_singleton()->texture_set_path(texture, get_path()); } emit_changed(); } @@ -193,6 +194,9 @@ Ref<Image> NoiseTexture2D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi void NoiseTexture2D::_update_texture() { bool use_thread = true; +#ifndef THREADS_ENABLED + use_thread = false; +#endif if (first_time) { use_thread = false; first_time = false; diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp index 1e929e6f63..e3cca8a09f 100644 --- a/modules/noise/noise_texture_3d.cpp +++ b/modules/noise/noise_texture_3d.cpp @@ -187,6 +187,9 @@ Ref<Image> NoiseTexture3D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi void NoiseTexture3D::_update_texture() { bool use_thread = true; +#ifndef THREADS_ENABLED + use_thread = false; +#endif if (first_time) { use_thread = false; first_time = false; @@ -331,6 +334,10 @@ int NoiseTexture3D::get_depth() const { return depth; } +bool NoiseTexture3D::has_mipmaps() const { + return false; +} + RID NoiseTexture3D::get_rid() const { if (!texture.is_valid()) { texture = RS::get_singleton()->texture_3d_placeholder_create(); diff --git a/modules/noise/noise_texture_3d.h b/modules/noise/noise_texture_3d.h index 13125efe7f..d55b78a2ba 100644 --- a/modules/noise/noise_texture_3d.h +++ b/modules/noise/noise_texture_3d.h @@ -103,6 +103,8 @@ public: virtual int get_height() const override; virtual int get_depth() const override; + virtual bool has_mipmaps() const override; + virtual RID get_rid() const override; virtual Vector<Ref<Image>> get_data() const override; diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h index 938e8fd6ab..0d18d66e74 100644 --- a/modules/noise/tests/test_noise_texture_2d.h +++ b/modules/noise/tests/test_noise_texture_2d.h @@ -135,7 +135,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_noise(noise); CHECK(noise_texture->get_noise() == noise); noise_texture->set_noise(nullptr); - CHECK(noise_texture->get_noise() == nullptr); + CHECK(noise_texture->get_noise().is_null()); noise_texture->set_width(8); noise_texture->set_height(4); @@ -190,7 +190,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_color_ramp(gradient); CHECK(noise_texture->get_color_ramp() == gradient); noise_texture->set_color_ramp(nullptr); - CHECK(noise_texture->get_color_ramp() == nullptr); + CHECK(noise_texture->get_color_ramp().is_null()); } TEST_CASE("[NoiseTexture2D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") { diff --git a/modules/noise/tests/test_noise_texture_3d.h b/modules/noise/tests/test_noise_texture_3d.h index b708eac43b..434cd20a08 100644 --- a/modules/noise/tests/test_noise_texture_3d.h +++ b/modules/noise/tests/test_noise_texture_3d.h @@ -133,7 +133,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_noise(noise); CHECK(noise_texture->get_noise() == noise); noise_texture->set_noise(nullptr); - CHECK(noise_texture->get_noise() == nullptr); + CHECK(noise_texture->get_noise().is_null()); noise_texture->set_width(8); noise_texture->set_height(4); @@ -174,7 +174,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_color_ramp(gradient); CHECK(noise_texture->get_color_ramp() == gradient); noise_texture->set_color_ramp(nullptr); - CHECK(noise_texture->get_color_ramp() == nullptr); + CHECK(noise_texture->get_color_ramp().is_null()); } TEST_CASE("[NoiseTexture3D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") { diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp index 2579697d05..1266457113 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -127,9 +127,12 @@ Ref<OpenXRInteractionProfile> OpenXRInteractionProfile::new_profile(const char * void OpenXRInteractionProfile::set_interaction_profile_path(const String p_input_profile_path) { OpenXRInteractionProfileMetadata *pmd = OpenXRInteractionProfileMetadata::get_singleton(); - ERR_FAIL_NULL(pmd); - - interaction_profile_path = pmd->check_profile_name(p_input_profile_path); + if (pmd) { + interaction_profile_path = pmd->check_profile_name(p_input_profile_path); + } else { + // OpenXR module not enabled, ignore checks. + interaction_profile_path = p_input_profile_path; + } emit_changed(); } diff --git a/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp b/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp index 75cfb095bb..6315c95a03 100644 --- a/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile_metadata.cpp @@ -225,21 +225,6 @@ void OpenXRInteractionProfileMetadata::_register_core_metadata() { register_top_level_path("Gamepad", "/user/gamepad", ""); register_top_level_path("Treadmill", "/user/treadmill", ""); - // TODO move this into OpenXRHTCViveTrackerExtension once this is supported. - // register_top_level_path("Handheld object tracker", "/user/vive_tracker_htcx/role/handheld_object", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Left foot tracker", "/user/vive_tracker_htcx/role/left_foot", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Right foot tracker", "/user/vive_tracker_htcx/role/right_foot", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Left shoulder tracker", "/user/vive_tracker_htcx/role/left_shoulder", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Right shoulder tracker", "/user/vive_tracker_htcx/role/right_shoulder", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Left elbow tracker", "/user/vive_tracker_htcx/role/left_elbow", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Right elbow tracker", "/user/vive_tracker_htcx/role/right_elbow", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Left knee tracker", "/user/vive_tracker_htcx/role/left_knee", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Right knee tracker", "/user/vive_tracker_htcx/role/right_knee", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Waist tracker", "/user/vive_tracker_htcx/role/waist", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Chest tracker", "/user/vive_tracker_htcx/role/chest", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Camera tracker", "/user/vive_tracker_htcx/role/camera", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - register_top_level_path("Keyboard tracker", "/user/vive_tracker_htcx/role/keyboard", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); - // Fallback Khronos simple controller register_interaction_profile("Simple controller", "/interaction_profiles/khr/simple_controller", ""); register_io_path("/interaction_profiles/khr/simple_controller", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); diff --git a/modules/openxr/config.py b/modules/openxr/config.py index 6c6b9fdb8b..559aa2acf6 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -22,6 +22,7 @@ def get_doc_classes(): "OpenXRInteractionProfileMetadata", "OpenXRIPBinding", "OpenXRHand", + "OpenXRVisibilityMask", "OpenXRCompositionLayer", "OpenXRCompositionLayerQuad", "OpenXRCompositionLayerCylinder", diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml index 4419d24dd3..432b331eec 100644 --- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml +++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml @@ -17,12 +17,25 @@ <link title="XrPosef documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html</link> </tutorials> <methods> + <method name="begin_debug_label_region"> + <return type="void" /> + <param index="0" name="label_name" type="String" /> + <description> + Begins a new debug label region, this label will be reported in debug messages for any calls following this until [method end_debug_label_region] is called. Debug labels can be stacked. + </description> + </method> <method name="can_render"> <return type="bool" /> <description> Returns [code]true[/code] if OpenXR is initialized for rendering with an XR viewport. </description> </method> + <method name="end_debug_label_region"> + <return type="void" /> + <description> + Marks the end of a debug label region. Removes the latest debug label region added by calling [method begin_debug_label_region]. + </description> + </method> <method name="get_error_string"> <return type="String" /> <param index="0" name="result" type="int" /> @@ -88,6 +101,13 @@ Returns the id of the system, which is a [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSystemId.html]XrSystemId[/url] cast to an integer. </description> </method> + <method name="insert_debug_label"> + <return type="void" /> + <param index="0" name="label_name" type="String" /> + <description> + Inserts a debug label, this label is reported in any debug message resulting from the OpenXR calls that follows, until any of [method begin_debug_label_region], [method end_debug_label_region], or [method insert_debug_label] is called. + </description> + </method> <method name="is_environment_blend_mode_alpha_supported"> <return type="int" enum="OpenXRAPIExtension.OpenXRAlphaBlendModeSupport" /> <description> @@ -127,6 +147,15 @@ If set to [code]true[/code], an OpenXR extension is loaded which is capable of emulating the [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] blend mode. </description> </method> + <method name="set_object_name"> + <return type="void" /> + <param index="0" name="object_type" type="int" /> + <param index="1" name="object_handle" type="int" /> + <param index="2" name="object_name" type="String" /> + <description> + Set the object name of an OpenXR object, used for debug output. [param object_type] must be a valid OpenXR [code]XrObjectType[/code] enum and [param object_handle] must be a valid OpenXR object handle. + </description> + </method> <method name="transform_from_pose"> <return type="Transform3D" /> <param index="0" name="pose" type="const void*" /> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml index 6f4ac00f3e..341b50065c 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml @@ -10,6 +10,13 @@ <tutorials> </tutorials> <methods> + <method name="get_android_surface"> + <return type="JavaObject" /> + <description> + Returns a [JavaObject] representing an [code]android.view.Surface[/code] if [member use_android_surface] is enabled and OpenXR has created the surface. Otherwise, this will return [code]null[/code]. + [b]Note:[/b] The surface can only be created during an active OpenXR session. So, if [member use_android_surface] is enabled outside of an OpenXR session, it won't be created until a new session fully starts. + </description> + </method> <method name="intersects_ray" qualifiers="const"> <return type="Vector2" /> <param index="0" name="origin" type="Vector3" /> @@ -32,6 +39,9 @@ Enables the blending the layer using its alpha channel. Can be combined with [member Viewport.transparent_bg] to give the layer a transparent background. </member> + <member name="android_surface_size" type="Vector2i" setter="set_android_surface_size" getter="get_android_surface_size" default="Vector2i(1024, 1024)"> + The size of the Android surface to create if [member use_android_surface] is enabled. + </member> <member name="enable_hole_punch" type="bool" setter="set_enable_hole_punch" getter="get_enable_hole_punch" default="false"> Enables a technique called "hole punching", which allows putting the composition layer behind the main projection layer (i.e. setting [member sort_order] to a negative value) while "punching a hole" through everything rendered by Godot so that the layer is still visible. This can be used to create the illusion that the composition layer exists in the same 3D space as everything rendered by Godot, allowing objects to appear to pass both behind or in front of the composition layer. @@ -43,5 +53,10 @@ The sort order for this composition layer. Higher numbers will be shown in front of lower numbers. [b]Note:[/b] This will have no effect if a fallback mesh is being used. </member> + <member name="use_android_surface" type="bool" setter="set_use_android_surface" getter="get_use_android_surface" default="false"> + If enabled, an Android surface will be created (with the dimensions from [member android_surface_size]) which will provide the 2D content for the composition layer, rather than using [member layer_viewport]. + See [method get_android_surface] for information about how to get the surface so that your application can draw to it. + [b]Note:[/b] This will only work in Android builds. + </member> </members> </class> diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml index 79aa547c52..813c9d582e 100644 --- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml +++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml @@ -84,6 +84,12 @@ Called right before the OpenXR instance is destroyed. </description> </method> + <method name="_on_main_swapchains_created" qualifiers="virtual"> + <return type="void" /> + <description> + Called right after the main swapchains are (re)created. + </description> + </method> <method name="_on_pre_render" qualifiers="virtual"> <return type="void" /> <description> @@ -172,6 +178,15 @@ [param layer] is a pointer to an [code]XrCompositionLayerBaseHeader[/code] struct. </description> </method> + <method name="_set_android_surface_swapchain_create_info_and_get_next_pointer" qualifiers="virtual"> + <return type="int" /> + <param index="0" name="property_values" type="Dictionary" /> + <param index="1" name="next_pointer" type="void*" /> + <description> + Adds additional data structures to Android surface swapchains created by [OpenXRCompositionLayer]. + [param property_values] contains the values of the properties returned by [method _get_viewport_composition_layer_extension_properties]. + </description> + </method> <method name="_set_hand_joint_locations_and_get_next_pointer" qualifiers="virtual"> <return type="int" /> <param index="0" name="hand_index" type="int" /> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 309cbe0d72..ed5810da3c 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -176,7 +176,7 @@ <param index="0" name="refresh_rate" type="float" /> <description> Informs the user the HMD refresh rate has changed. - [b]Node:[/b] Only emitted if XR runtime supports the refresh rate extension. + [b]Note:[/b] Only emitted if XR runtime supports the refresh rate extension. </description> </signal> <signal name="session_begun"> diff --git a/modules/openxr/doc_classes/OpenXRVisibilityMask.xml b/modules/openxr/doc_classes/OpenXRVisibilityMask.xml new file mode 100644 index 0000000000..90ee889c7c --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRVisibilityMask.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRVisibilityMask" inherits="VisualInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Draws a stereo correct visibility mask. + </brief_description> + <description> + The visibility mask allows us to black out the part of the render result that is invisible due to lens distortion. + As this is rendered first, it prevents fragments with expensive lighting calculations to be processed as they are discarded through z-checking. + </description> + <tutorials> + </tutorials> +</class> diff --git a/modules/openxr/editor/openxr_action_editor.cpp b/modules/openxr/editor/openxr_action_editor.cpp index 06d2e8dcc9..63162ba3dc 100644 --- a/modules/openxr/editor/openxr_action_editor.cpp +++ b/modules/openxr/editor/openxr_action_editor.cpp @@ -134,14 +134,14 @@ OpenXRActionEditor::OpenXRActionEditor(Ref<OpenXRAction> p_action) { action_name = memnew(LineEdit); action_name->set_text(action->get_name()); action_name->set_custom_minimum_size(Size2(150.0, 0.0)); - action_name->connect("text_changed", callable_mp(this, &OpenXRActionEditor::_on_action_name_changed)); + action_name->connect(SceneStringName(text_changed), callable_mp(this, &OpenXRActionEditor::_on_action_name_changed)); add_child(action_name); action_localized_name = memnew(LineEdit); action_localized_name->set_text(action->get_localized_name()); action_localized_name->set_custom_minimum_size(Size2(150.0, 0.0)); action_localized_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); - action_localized_name->connect("text_changed", callable_mp(this, &OpenXRActionEditor::_on_action_localized_name_changed)); + action_localized_name->connect(SceneStringName(text_changed), callable_mp(this, &OpenXRActionEditor::_on_action_localized_name_changed)); add_child(action_localized_name); action_type_button = memnew(OptionButton); @@ -152,7 +152,7 @@ OpenXRActionEditor::OpenXRActionEditor(Ref<OpenXRAction> p_action) { action_type_button->add_item("Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC); action_type_button->select(int(action->get_action_type())); action_type_button->set_custom_minimum_size(Size2(100.0, 0.0)); - action_type_button->connect("item_selected", callable_mp(this, &OpenXRActionEditor::_on_item_selected)); + action_type_button->connect(SceneStringName(item_selected), callable_mp(this, &OpenXRActionEditor::_on_item_selected)); add_child(action_type_button); // maybe add dropdown to edit our toplevel paths, or do we deduce them from our suggested bindings? diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index 62b4a427b9..a353073f21 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -59,7 +59,7 @@ void OpenXRActionMapEditor::_notification(int p_what) { for (int i = 0; i < tabs->get_child_count(); i++) { Control *tab = Object::cast_to<Control>(tabs->get_child(i)); if (tab) { - tab->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + tab->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); } } } break; @@ -110,7 +110,7 @@ OpenXRInteractionProfileEditorBase *OpenXRActionMapEditor::_add_interaction_prof // now add it in.. ERR_FAIL_NULL_V(new_profile_editor, nullptr); tabs->add_child(new_profile_editor); - new_profile_editor->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + new_profile_editor->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); tabs->set_tab_button_icon(tabs->get_tab_count() - 1, get_theme_icon(SNAME("close"), SNAME("TabBar"))); return new_profile_editor; @@ -248,26 +248,27 @@ void OpenXRActionMapEditor::_on_interaction_profile_selected(const String p_path void OpenXRActionMapEditor::_load_action_map(const String p_path, bool p_create_new_if_missing) { Error err = OK; - action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err); - if (err != OK) { - if ((err == ERR_FILE_NOT_FOUND || err == ERR_CANT_OPEN) && p_create_new_if_missing) { - action_map.instantiate(); - action_map->create_default_action_sets(); - - // Save it immediately - err = ResourceSaver::save(action_map, p_path); - if (err != OK) { - // show warning but continue - EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), edited_path, error_names[err])); - } - - } else { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da->file_exists(p_path)) { + action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + if (err != OK) { EditorNode::get_singleton()->show_warning(vformat(TTR("Error loading %s: %s."), edited_path, error_names[err])); edited_path = ""; header_label->set_text(""); return; } + } else if (p_create_new_if_missing) { + action_map.instantiate(); + action_map->create_default_action_sets(); + action_map->set_path(p_path); + + // Save it immediately + err = ResourceSaver::save(action_map, p_path); + if (err != OK) { + // Show warning but continue. + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), edited_path, error_names[err])); + } } edited_path = p_path; diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp index 5d9a3155fb..0c55592707 100644 --- a/modules/openxr/editor/openxr_action_set_editor.cpp +++ b/modules/openxr/editor/openxr_action_set_editor.cpp @@ -63,7 +63,7 @@ void OpenXRActionSetEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { _theme_changed(); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("TabContainer"))); } break; } } @@ -244,20 +244,20 @@ OpenXRActionSetEditor::OpenXRActionSetEditor(Ref<OpenXRActionMap> p_action_map, action_set_name = memnew(LineEdit); action_set_name->set_text(action_set->get_name()); action_set_name->set_custom_minimum_size(Size2(150.0, 0.0)); - action_set_name->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_name_changed)); + action_set_name->connect(SceneStringName(text_changed), callable_mp(this, &OpenXRActionSetEditor::_on_action_set_name_changed)); action_set_hb->add_child(action_set_name); action_set_localized_name = memnew(LineEdit); action_set_localized_name->set_text(action_set->get_localized_name()); action_set_localized_name->set_custom_minimum_size(Size2(150.0, 0.0)); action_set_localized_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); - action_set_localized_name->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_localized_name_changed)); + action_set_localized_name->connect(SceneStringName(text_changed), callable_mp(this, &OpenXRActionSetEditor::_on_action_set_localized_name_changed)); action_set_hb->add_child(action_set_localized_name); action_set_priority = memnew(TextEdit); action_set_priority->set_text(itos(action_set->get_priority())); action_set_priority->set_custom_minimum_size(Size2(50.0, 0.0)); - action_set_priority->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_priority_changed)); + action_set_priority->connect(SceneStringName(text_changed), callable_mp(this, &OpenXRActionSetEditor::_on_action_set_priority_changed)); action_set_hb->add_child(action_set_priority); add_action = memnew(Button); diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp index ab36c0744e..651171358c 100644 --- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp +++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp @@ -285,7 +285,7 @@ void OpenXRInteractionProfileEditor::_update_interaction_profile() { PanelContainer *panel = memnew(PanelContainer); panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); main_hb->add_child(panel); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("TabContainer"))); VBoxContainer *container = memnew(VBoxContainer); panel->add_child(container); @@ -310,7 +310,7 @@ void OpenXRInteractionProfileEditor::_theme_changed() { for (int i = 0; i < main_hb->get_child_count(); i++) { Control *panel = Object::cast_to<Control>(main_hb->get_child(i)); if (panel) { - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("TabContainer"))); } } } diff --git a/modules/openxr/editor/openxr_select_action_dialog.cpp b/modules/openxr/editor/openxr_select_action_dialog.cpp index de0ab40f9e..a4ccc98408 100644 --- a/modules/openxr/editor/openxr_select_action_dialog.cpp +++ b/modules/openxr/editor/openxr_select_action_dialog.cpp @@ -38,7 +38,7 @@ void OpenXRSelectActionDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { - scroll->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + scroll->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); } break; } } diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp index e6705d5c82..53b8cbd401 100644 --- a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp +++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp @@ -38,7 +38,7 @@ void OpenXRSelectInteractionProfileDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { - scroll->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + scroll->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); } break; } } diff --git a/modules/openxr/editor/openxr_select_runtime.cpp b/modules/openxr/editor/openxr_select_runtime.cpp index f6aa157907..4a2a87cb88 100644 --- a/modules/openxr/editor/openxr_select_runtime.cpp +++ b/modules/openxr/editor/openxr_select_runtime.cpp @@ -35,9 +35,6 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" -void OpenXRSelectRuntime::_bind_methods() { -} - void OpenXRSelectRuntime::_update_items() { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); OS *os = OS::get_singleton(); @@ -98,11 +95,11 @@ void OpenXRSelectRuntime::_notification(int p_notification) { _update_items(); // Connect signal - connect("item_selected", callable_mp(this, &OpenXRSelectRuntime::_item_selected)); + connect(SceneStringName(item_selected), callable_mp(this, &OpenXRSelectRuntime::_item_selected)); } break; case NOTIFICATION_EXIT_TREE: { // Disconnect signal - disconnect("item_selected", callable_mp(this, &OpenXRSelectRuntime::_item_selected)); + disconnect(SceneStringName(item_selected), callable_mp(this, &OpenXRSelectRuntime::_item_selected)); } break; } } @@ -122,6 +119,7 @@ OpenXRSelectRuntime::OpenXRSelectRuntime() { default_runtimes["SteamVR"] = "~/.steam/steam/steamapps/common/SteamVR/steamxr_linux64.json"; #endif + // TODO: Move to editor_settings.cpp EDITOR_DEF_RST("xr/openxr/runtime_paths", default_runtimes); set_flat(true); diff --git a/modules/openxr/editor/openxr_select_runtime.h b/modules/openxr/editor/openxr_select_runtime.h index 60b5137f67..9a3487439c 100644 --- a/modules/openxr/editor/openxr_select_runtime.h +++ b/modules/openxr/editor/openxr_select_runtime.h @@ -40,7 +40,6 @@ public: OpenXRSelectRuntime(); protected: - static void _bind_methods(); void _notification(int p_notification); private: diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp index 994b08af53..dc30b95b27 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -30,6 +30,12 @@ #include "openxr_composition_layer_extension.h" +#ifdef ANDROID_ENABLED +#include <openxr/openxr.h> +#include <openxr/openxr_platform.h> +#endif + +#include "platform/android/api/java_class_wrapper.h" #include "servers/rendering/rendering_server_globals.h" //////////////////////////////////////////////////////////////////////////// @@ -55,18 +61,37 @@ HashMap<String, bool *> OpenXRCompositionLayerExtension::get_requested_extension request_extensions[XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME] = &cylinder_ext_available; request_extensions[XR_KHR_COMPOSITION_LAYER_EQUIRECT2_EXTENSION_NAME] = &equirect_ext_available; +#ifdef ANDROID_ENABLED + request_extensions[XR_KHR_ANDROID_SURFACE_SWAPCHAIN_EXTENSION_NAME] = &android_surface_ext_available; +#endif + return request_extensions; } -void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_instance) { +void OpenXRCompositionLayerExtension::on_instance_created(const XrInstance p_instance) { +#ifdef ANDROID_ENABLED + EXT_INIT_XR_FUNC(xrDestroySwapchain); + EXT_INIT_XR_FUNC(xrCreateSwapchainAndroidSurfaceKHR); +#endif +} + +void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_session) { OpenXRAPI::get_singleton()->register_composition_layer_provider(this); } void OpenXRCompositionLayerExtension::on_session_destroyed() { OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this); + +#ifdef ANDROID_ENABLED + free_queued_android_surface_swapchains(); +#endif } void OpenXRCompositionLayerExtension::on_pre_render() { +#ifdef ANDROID_ENABLED + free_queued_android_surface_swapchains(); +#endif + for (OpenXRViewportCompositionLayerProvider *composition_layer : composition_layers) { composition_layer->on_pre_render(); } @@ -113,6 +138,36 @@ bool OpenXRCompositionLayerExtension::is_available(XrStructureType p_which) { } } +#ifdef ANDROID_ENABLED +bool OpenXRCompositionLayerExtension::create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface) { + if (android_surface_ext_available) { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrResult result = xrCreateSwapchainAndroidSurfaceKHR(openxr_api->get_session(), p_info, r_swapchain, r_surface); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create Android surface swapchain [", openxr_api->get_error_string(result), "]"); + return false; + } + + return true; + } + + return false; +} + +void OpenXRCompositionLayerExtension::free_android_surface_swapchain(XrSwapchain p_swapchain) { + android_surface_swapchain_free_queue.push_back(p_swapchain); +} + +void OpenXRCompositionLayerExtension::free_queued_android_surface_swapchains() { + for (XrSwapchain swapchain : android_surface_swapchain_free_queue) { + xrDestroySwapchain(swapchain); + } + android_surface_swapchain_free_queue.clear(); +} +#endif + //////////////////////////////////////////////////////////////////////////// // OpenXRViewportCompositionLayerProvider @@ -127,8 +182,12 @@ OpenXRViewportCompositionLayerProvider::~OpenXRViewportCompositionLayerProvider( extension->on_viewport_composition_layer_destroyed(composition_layer); } - // This will reset the viewport and free the swapchain too. - set_viewport(RID(), Size2i()); + if (use_android_surface) { + free_swapchain(); + } else { + // This will reset the viewport and free the swapchain too. + set_viewport(RID(), Size2i()); + } } void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend) { @@ -143,25 +202,101 @@ void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend) } void OpenXRViewportCompositionLayerProvider::set_viewport(RID p_viewport, Size2i p_size) { + ERR_FAIL_COND(use_android_surface); + RenderingServer *rs = RenderingServer::get_singleton(); ERR_FAIL_NULL(rs); - if (viewport != p_viewport) { - if (viewport.is_valid()) { - RID rt = rs->viewport_get_render_target(viewport); + if (subviewport.viewport != p_viewport) { + if (subviewport.viewport.is_valid()) { + RID rt = rs->viewport_get_render_target(subviewport.viewport); RSG::texture_storage->render_target_set_override(rt, RID(), RID(), RID()); } - viewport = p_viewport; + subviewport.viewport = p_viewport; - if (viewport.is_valid()) { - viewport_size = p_size; + if (subviewport.viewport.is_valid()) { + subviewport.viewport_size = p_size; } else { free_swapchain(); - viewport_size = Size2i(); + subviewport.viewport_size = Size2i(); + } + } +} + +void OpenXRViewportCompositionLayerProvider::set_use_android_surface(bool p_use_android_surface, Size2i p_size) { +#ifdef ANDROID_ENABLED + if (p_use_android_surface == use_android_surface) { + return; + } + + use_android_surface = p_use_android_surface; + + if (use_android_surface) { + if (!composition_layer_extension->is_android_surface_swapchain_available()) { + ERR_PRINT_ONCE("OpenXR: Cannot use Android surface for composition layer because the extension isn't available"); + } + + if (subviewport.viewport.is_valid()) { + set_viewport(RID(), Size2i()); } + + swapchain_size = p_size; + } else { + free_swapchain(); + } +#endif +} + +#ifdef ANDROID_ENABLED +void OpenXRViewportCompositionLayerProvider::create_android_surface() { + ERR_FAIL_COND(android_surface.swapchain != XR_NULL_HANDLE || android_surface.surface.is_valid()); + ERR_FAIL_COND(!openxr_api || !openxr_api->is_running()); + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : openxr_api->get_registered_extension_wrappers()) { + void *np = wrapper->set_android_surface_swapchain_create_info_and_get_next_pointer(extension_property_values, next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + // The XR_FB_android_surface_swapchain_create extension mandates that format, sampleCount, + // faceCount, arraySize, and mipCount must be zero. + XrSwapchainCreateInfo info = { + XR_TYPE_SWAPCHAIN_CREATE_INFO, // type + next_pointer, // next + 0, // createFlags + XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, // usageFlags + 0, // format + 0, // sampleCount + (uint32_t)swapchain_size.x, // width + (uint32_t)swapchain_size.y, // height + 0, // faceCount + 0, // arraySize + 0, // mipCount + }; + + jobject surface; + composition_layer_extension->create_android_surface_swapchain(&info, &android_surface.swapchain, &surface); + + if (surface) { + android_surface.surface = Ref<JavaObject>(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface))); } } +#endif + +Ref<JavaObject> OpenXRViewportCompositionLayerProvider::get_android_surface() { +#ifdef ANDROID_ENABLED + if (use_android_surface) { + if (android_surface.surface.is_null()) { + create_android_surface(); + } + return android_surface.surface; + } +#endif + return Ref<JavaObject>(); +} void OpenXRViewportCompositionLayerProvider::set_extension_property_values(const Dictionary &p_extension_property_values) { extension_property_values = p_extension_property_values; @@ -169,16 +304,25 @@ void OpenXRViewportCompositionLayerProvider::set_extension_property_values(const } void OpenXRViewportCompositionLayerProvider::on_pre_render() { +#ifdef ANDROID_ENABLED + if (use_android_surface) { + if (android_surface.surface.is_null()) { + create_android_surface(); + } + return; + } +#endif + RenderingServer *rs = RenderingServer::get_singleton(); ERR_FAIL_NULL(rs); - if (viewport.is_valid() && openxr_api && openxr_api->is_running()) { - RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(viewport); + if (subviewport.viewport.is_valid() && openxr_api && openxr_api->is_running()) { + RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(subviewport.viewport); if (update_mode == RS::VIEWPORT_UPDATE_ONCE || update_mode == RS::VIEWPORT_UPDATE_ALWAYS) { // Update our XR swapchain if (update_and_acquire_swapchain(update_mode == RS::VIEWPORT_UPDATE_ONCE)) { // Render to our XR swapchain image. - RID rt = rs->viewport_get_render_target(viewport); + RID rt = rs->viewport_get_render_target(subviewport.viewport); RSG::texture_storage->render_target_set_override(rt, get_current_swapchain_texture(), RID(), RID()); } } @@ -196,48 +340,36 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos return nullptr; } - if (swapchain_info.get_swapchain() == XR_NULL_HANDLE) { + XrSwapchainSubImage subimage = { + 0, // swapchain + { { 0, 0 }, { 0, 0 } }, // imageRect + 0, // imageArrayIndex + }; + update_swapchain_sub_image(subimage); + + if (subimage.swapchain == XR_NULL_HANDLE) { // Don't have a swapchain to display? Ignore our layer. return nullptr; } - if (swapchain_info.is_image_acquired()) { - swapchain_info.release(); - } - // Update the layer struct for the swapchain. switch (composition_layer->type) { case XR_TYPE_COMPOSITION_LAYER_QUAD: { XrCompositionLayerQuad *quad_layer = (XrCompositionLayerQuad *)composition_layer; quad_layer->space = openxr_api->get_play_space(); - quad_layer->subImage.swapchain = swapchain_info.get_swapchain(); - quad_layer->subImage.imageArrayIndex = 0; - quad_layer->subImage.imageRect.offset.x = 0; - quad_layer->subImage.imageRect.offset.y = 0; - quad_layer->subImage.imageRect.extent.width = swapchain_size.width; - quad_layer->subImage.imageRect.extent.height = swapchain_size.height; + quad_layer->subImage = subimage; } break; case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: { XrCompositionLayerCylinderKHR *cylinder_layer = (XrCompositionLayerCylinderKHR *)composition_layer; cylinder_layer->space = openxr_api->get_play_space(); - cylinder_layer->subImage.swapchain = swapchain_info.get_swapchain(); - cylinder_layer->subImage.imageArrayIndex = 0; - cylinder_layer->subImage.imageRect.offset.x = 0; - cylinder_layer->subImage.imageRect.offset.y = 0; - cylinder_layer->subImage.imageRect.extent.width = swapchain_size.width; - cylinder_layer->subImage.imageRect.extent.height = swapchain_size.height; + cylinder_layer->subImage = subimage; } break; case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: { XrCompositionLayerEquirect2KHR *equirect_layer = (XrCompositionLayerEquirect2KHR *)composition_layer; equirect_layer->space = openxr_api->get_play_space(); - equirect_layer->subImage.swapchain = swapchain_info.get_swapchain(); - equirect_layer->subImage.imageArrayIndex = 0; - equirect_layer->subImage.imageRect.offset.x = 0; - equirect_layer->subImage.imageRect.offset.y = 0; - equirect_layer->subImage.imageRect.extent.width = swapchain_size.width; - equirect_layer->subImage.imageRect.extent.height = swapchain_size.height; + equirect_layer->subImage = subimage; } break; default: { @@ -261,27 +393,49 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos return composition_layer; } +void OpenXRViewportCompositionLayerProvider::update_swapchain_sub_image(XrSwapchainSubImage &r_subimage) { +#ifdef ANDROID_ENABLED + if (use_android_surface) { + r_subimage.swapchain = android_surface.swapchain; + } else +#endif + { + XrSwapchain swapchain = subviewport.swapchain_info.get_swapchain(); + + if (swapchain && subviewport.swapchain_info.is_image_acquired()) { + subviewport.swapchain_info.release(); + } + + r_subimage.swapchain = swapchain; + } + + r_subimage.imageRect.extent.width = swapchain_size.width; + r_subimage.imageRect.extent.height = swapchain_size.height; +} + bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p_static_image) { + ERR_FAIL_COND_V(use_android_surface, false); + if (openxr_api == nullptr || composition_layer_extension == nullptr) { // OpenXR not initialized or we're in the editor? return false; } - if (!composition_layer_extension->is_available(composition_layer->type)) { + if (!composition_layer_extension->is_available(get_openxr_type())) { // Selected type is not supported? return false; } // See if our current swapchain is outdated. - if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) { + if (subviewport.swapchain_info.get_swapchain() != XR_NULL_HANDLE) { // If this swap chain, or the previous one, were static, then we can't reuse it. - if (swapchain_size == viewport_size && !p_static_image && !static_image) { + if (swapchain_size == subviewport.viewport_size && !p_static_image && !subviewport.static_image) { // We're all good! Just acquire it. // We can ignore should_render here, return will be false. bool should_render = true; - return swapchain_info.acquire(should_render); + return subviewport.swapchain_info.acquire(should_render); } - swapchain_info.queue_free(); + subviewport.swapchain_info.queue_free(); } // Create our new swap chain @@ -292,7 +446,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p if (p_static_image) { create_flags |= XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT; } - if (!swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size)) { + if (!subviewport.swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, subviewport.viewport_size.width, subviewport.viewport_size.height, sample_count, array_size)) { swapchain_size = Size2i(); return false; } @@ -300,26 +454,40 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p // Acquire our image so we can start rendering into it, // we can ignore should_render here, ret will be false. bool should_render = true; - bool ret = swapchain_info.acquire(should_render); + bool ret = subviewport.swapchain_info.acquire(should_render); - swapchain_size = viewport_size; - static_image = p_static_image; + swapchain_size = subviewport.viewport_size; + subviewport.static_image = p_static_image; return ret; } void OpenXRViewportCompositionLayerProvider::free_swapchain() { - if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) { - swapchain_info.queue_free(); +#ifdef ANDROID_ENABLED + if (use_android_surface) { + if (android_surface.swapchain != XR_NULL_HANDLE) { + composition_layer_extension->free_android_surface_swapchain(android_surface.swapchain); + + android_surface.swapchain = XR_NULL_HANDLE; + android_surface.surface.unref(); + } + } else +#endif + { + if (subviewport.swapchain_info.get_swapchain() != XR_NULL_HANDLE) { + subviewport.swapchain_info.queue_free(); + } + subviewport.static_image = false; } swapchain_size = Size2i(); - static_image = false; } RID OpenXRViewportCompositionLayerProvider::get_current_swapchain_texture() { + ERR_FAIL_COND_V(use_android_surface, RID()); + if (openxr_api == nullptr) { return RID(); } - return swapchain_info.get_image(); + return subviewport.swapchain_info.get_image(); } diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.h b/modules/openxr/extensions/openxr_composition_layer_extension.h index 4fefc416e6..bce34f098c 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.h +++ b/modules/openxr/extensions/openxr_composition_layer_extension.h @@ -36,6 +36,15 @@ #include "../openxr_api.h" +#ifdef ANDROID_ENABLED +#include <jni.h> + +// Copied here from openxr_platform.h, in order to avoid including that whole header, +// which can cause compilation issues on some platforms. +typedef XrResult(XRAPI_PTR *PFN_xrCreateSwapchainAndroidSurfaceKHR)(XrSession session, const XrSwapchainCreateInfo *info, XrSwapchain *swapchain, jobject *surface); +#endif + +class JavaObject; class OpenXRViewportCompositionLayerProvider; // This extension provides access to composition layers for displaying 2D content through the XR compositor. @@ -49,7 +58,8 @@ public: virtual ~OpenXRCompositionLayerExtension() override; virtual HashMap<String, bool *> get_requested_extensions() override; - virtual void on_session_created(const XrSession p_instance) override; + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_session_created(const XrSession p_session) override; virtual void on_session_destroyed() override; virtual void on_pre_render() override; @@ -61,6 +71,12 @@ public: void unregister_viewport_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer); bool is_available(XrStructureType p_which); + bool is_android_surface_swapchain_available() { return android_surface_ext_available; } + +#ifdef ANDROID_ENABLED + bool create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface); + void free_android_surface_swapchain(XrSwapchain p_swapchain); +#endif private: static OpenXRCompositionLayerExtension *singleton; @@ -69,6 +85,15 @@ private: bool cylinder_ext_available = false; bool equirect_ext_available = false; + bool android_surface_ext_available = false; + +#ifdef ANDROID_ENABLED + Vector<XrSwapchain> android_surface_swapchain_free_queue; + void free_queued_android_surface_swapchains(); + + EXT_PROTO_XRRESULT_FUNC1(xrDestroySwapchain, (XrSwapchain), swapchain) + EXT_PROTO_XRRESULT_FUNC4(xrCreateSwapchainAndroidSurfaceKHR, (XrSession), session, (const XrSwapchainCreateInfo *), info, (XrSwapchain *), swapchain, (jobject *), surface) +#endif }; class OpenXRViewportCompositionLayerProvider { @@ -78,20 +103,37 @@ class OpenXRViewportCompositionLayerProvider { Dictionary extension_property_values; bool extension_property_values_changed = true; - RID viewport; - Size2i viewport_size; - - OpenXRAPI::OpenXRSwapChainInfo swapchain_info; + struct { + RID viewport; + Size2i viewport_size; + OpenXRAPI::OpenXRSwapChainInfo swapchain_info; + bool static_image = false; + } subviewport; + +#ifdef ANDROID_ENABLED + struct { + XrSwapchain swapchain = XR_NULL_HANDLE; + Ref<JavaObject> surface; + } android_surface; +#endif + + bool use_android_surface = false; Size2i swapchain_size; - bool static_image = false; OpenXRAPI *openxr_api = nullptr; OpenXRCompositionLayerExtension *composition_layer_extension = nullptr; + // Only for SubViewports. bool update_and_acquire_swapchain(bool p_static_image); - void free_swapchain(); RID get_current_swapchain_texture(); + void update_swapchain_sub_image(XrSwapchainSubImage &r_swapchain_sub_image); + void free_swapchain(); + +#ifdef ANDROID_ENABLED + void create_android_surface(); +#endif + public: XrStructureType get_openxr_type() { return composition_layer->type; } @@ -102,7 +144,12 @@ public: bool get_alpha_blend() const { return alpha_blend; } void set_viewport(RID p_viewport, Size2i p_size); - RID get_viewport() const { return viewport; } + RID get_viewport() const { return subviewport.viewport; } + + void set_use_android_surface(bool p_enable, Size2i p_size); + bool get_use_android_surface() const { return use_android_surface; } + + Ref<JavaObject> get_android_surface(); void set_extension_property_values(const Dictionary &p_property_values); diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.cpp b/modules/openxr/extensions/openxr_debug_utils_extension.cpp new file mode 100644 index 0000000000..10dbe629f7 --- /dev/null +++ b/modules/openxr/extensions/openxr_debug_utils_extension.cpp @@ -0,0 +1,287 @@ +/**************************************************************************/ +/* openxr_debug_utils_extension.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 "openxr_debug_utils_extension.h" + +#include "../openxr_api.h" +#include "core/config/project_settings.h" +#include "core/string/print_string.h" + +#include <openxr/openxr.h> + +OpenXRDebugUtilsExtension *OpenXRDebugUtilsExtension::singleton = nullptr; + +OpenXRDebugUtilsExtension *OpenXRDebugUtilsExtension::get_singleton() { + return singleton; +} + +OpenXRDebugUtilsExtension::OpenXRDebugUtilsExtension() { + singleton = this; +} + +OpenXRDebugUtilsExtension::~OpenXRDebugUtilsExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRDebugUtilsExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_EXT_DEBUG_UTILS_EXTENSION_NAME] = &debug_utils_ext; + + return request_extensions; +} + +void OpenXRDebugUtilsExtension::on_instance_created(const XrInstance p_instance) { + if (debug_utils_ext) { + EXT_INIT_XR_FUNC(xrCreateDebugUtilsMessengerEXT); + EXT_INIT_XR_FUNC(xrDestroyDebugUtilsMessengerEXT); + EXT_INIT_XR_FUNC(xrSetDebugUtilsObjectNameEXT); + EXT_INIT_XR_FUNC(xrSessionBeginDebugUtilsLabelRegionEXT); + EXT_INIT_XR_FUNC(xrSessionEndDebugUtilsLabelRegionEXT); + EXT_INIT_XR_FUNC(xrSessionInsertDebugUtilsLabelEXT); + + debug_utils_ext = xrCreateDebugUtilsMessengerEXT_ptr && xrDestroyDebugUtilsMessengerEXT_ptr && xrSetDebugUtilsObjectNameEXT_ptr && xrSessionBeginDebugUtilsLabelRegionEXT_ptr && xrSessionEndDebugUtilsLabelRegionEXT_ptr && xrSessionInsertDebugUtilsLabelEXT_ptr; + } else { + WARN_PRINT("OpenXR: The debug utils extension is not available on this runtime. Debug logging is not enabled!"); + } + + // On successful init, setup our default messenger. + if (debug_utils_ext) { + int max_severity = GLOBAL_GET("xr/openxr/extensions/debug_utils"); + int types = GLOBAL_GET("xr/openxr/extensions/debug_message_types"); + + XrDebugUtilsMessageSeverityFlagsEXT message_severities = 0; + + if (max_severity >= 1) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + } + if (max_severity >= 2) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + } + if (max_severity >= 3) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; + } + if (max_severity >= 4) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + } + + XrDebugUtilsMessageTypeFlagsEXT message_types = 0; + + // These should match up but just to be safe and future proof... + if (types & 1) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT; + } + if (types & 2) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; + } + if (types & 4) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + } + if (types & 8) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT; + } + + XrDebugUtilsMessengerCreateInfoEXT callback_info = { + XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, // type + nullptr, // next + message_severities, // messageSeverities + message_types, // messageTypes + &OpenXRDebugUtilsExtension::_debug_callback, // userCallback + nullptr, // userData + }; + + XrResult result = xrCreateDebugUtilsMessengerEXT(p_instance, &callback_info, &default_messenger); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to create debug callback [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + set_object_name(XR_OBJECT_TYPE_INSTANCE, uint64_t(p_instance), "Main Godot OpenXR Instance"); + } +} + +void OpenXRDebugUtilsExtension::on_instance_destroyed() { + if (default_messenger != XR_NULL_HANDLE) { + XrResult result = xrDestroyDebugUtilsMessengerEXT(default_messenger); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to destroy debug callback [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + default_messenger = XR_NULL_HANDLE; + } + + xrCreateDebugUtilsMessengerEXT_ptr = nullptr; + xrDestroyDebugUtilsMessengerEXT_ptr = nullptr; + xrSetDebugUtilsObjectNameEXT_ptr = nullptr; + xrSessionBeginDebugUtilsLabelRegionEXT_ptr = nullptr; + xrSessionEndDebugUtilsLabelRegionEXT_ptr = nullptr; + xrSessionInsertDebugUtilsLabelEXT_ptr = nullptr; + debug_utils_ext = false; +} + +bool OpenXRDebugUtilsExtension::get_active() { + return debug_utils_ext; +} + +void OpenXRDebugUtilsExtension::set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const char *p_object_name) { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSetDebugUtilsObjectNameEXT_ptr); + + const XrDebugUtilsObjectNameInfoEXT space_name_info = { + XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, // type + nullptr, // next + p_object_type, // objectType + p_object_handle, // objectHandle + p_object_name, // objectName + }; + + XrResult result = xrSetDebugUtilsObjectNameEXT_ptr(OpenXRAPI::get_singleton()->get_instance(), &space_name_info); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to set object name [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +void OpenXRDebugUtilsExtension::begin_debug_label_region(const char *p_label_name) { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSessionBeginDebugUtilsLabelRegionEXT_ptr); + + const XrDebugUtilsLabelEXT session_active_region_label = { + XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type + NULL, // next + p_label_name, // labelName + }; + + XrResult result = xrSessionBeginDebugUtilsLabelRegionEXT_ptr(OpenXRAPI::get_singleton()->get_session(), &session_active_region_label); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to begin label region [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +void OpenXRDebugUtilsExtension::end_debug_label_region() { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSessionEndDebugUtilsLabelRegionEXT_ptr); + + XrResult result = xrSessionEndDebugUtilsLabelRegionEXT_ptr(OpenXRAPI::get_singleton()->get_session()); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to end label region [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +void OpenXRDebugUtilsExtension::insert_debug_label(const char *p_label_name) { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSessionInsertDebugUtilsLabelEXT_ptr); + + const XrDebugUtilsLabelEXT session_active_region_label = { + XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type + NULL, // next + p_label_name, // labelName + }; + + XrResult result = xrSessionInsertDebugUtilsLabelEXT_ptr(OpenXRAPI::get_singleton()->get_session(), &session_active_region_label); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to insert label [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +XrBool32 XRAPI_PTR OpenXRDebugUtilsExtension::_debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + + if (debug_utils) { + return debug_utils->debug_callback(p_message_severity, p_message_types, p_callback_data, p_user_data); + } + + return XR_FALSE; +} + +XrBool32 OpenXRDebugUtilsExtension::debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data) { + String msg; + + ERR_FAIL_NULL_V(p_callback_data, XR_FALSE); + + if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { + msg = ", type: General"; + } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + msg = ", type: Validation"; + } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { + msg = ", type: Performance"; + } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT) { + msg = ", type: Conformance"; + } else { + msg = ", type: Unknown (" + String::num_uint64(p_message_types) + ")"; + } + + if (p_callback_data->functionName) { + msg += ", function Name: " + String(p_callback_data->functionName); + } + if (p_callback_data->messageId) { + msg += "\nMessage ID: " + String(p_callback_data->messageId); + } + if (p_callback_data->message) { + msg += "\nMessage: " + String(p_callback_data->message); + } + + if (p_callback_data->objectCount > 0) { + String objects; + + for (uint32_t i = 0; i < p_callback_data->objectCount; i++) { + if (!objects.is_empty()) { + objects += ", "; + } + objects += p_callback_data->objects[i].objectName; + } + + msg += "\nObjects: " + objects; + } + + if (p_callback_data->sessionLabelCount > 0) { + String labels; + + for (uint32_t i = 0; i < p_callback_data->sessionLabelCount; i++) { + if (!labels.is_empty()) { + labels += ", "; + } + labels += p_callback_data->sessionLabels[i].labelName; + } + + msg += "\nLabels: " + labels; + } + + if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + ERR_PRINT("OpenXR: Severity: Error" + msg); + } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + WARN_PRINT("OpenXR: Severity: Warning" + msg); + } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + print_line("OpenXR: Severity: Info" + msg); + } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + // This is a bit double because we won't output this unless verbose messaging in Godot is on. + print_verbose("OpenXR: Severity: Verbose" + msg); + } + + return XR_FALSE; +} diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.h b/modules/openxr/extensions/openxr_debug_utils_extension.h new file mode 100644 index 0000000000..1ee4c2a101 --- /dev/null +++ b/modules/openxr/extensions/openxr_debug_utils_extension.h @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* openxr_debug_utils_extension.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 OPENXR_DEBUG_UTILS_EXTENSION_H +#define OPENXR_DEBUG_UTILS_EXTENSION_H + +#include "../util.h" +#include "openxr_extension_wrapper.h" + +class OpenXRDebugUtilsExtension : public OpenXRExtensionWrapper { +public: + static OpenXRDebugUtilsExtension *get_singleton(); + + OpenXRDebugUtilsExtension(); + virtual ~OpenXRDebugUtilsExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + bool get_active(); + + void set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const char *p_object_name); + void begin_debug_label_region(const char *p_label_name); + void end_debug_label_region(); + void insert_debug_label(const char *p_label_name); + +private: + static OpenXRDebugUtilsExtension *singleton; + + // related extensions + bool debug_utils_ext = false; + + // debug handlers + XrDebugUtilsMessengerEXT default_messenger = XR_NULL_HANDLE; + + static XrBool32 XRAPI_PTR _debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data); + XrBool32 debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data); + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC3(xrCreateDebugUtilsMessengerEXT, (XrInstance), p_instance, (const XrDebugUtilsMessengerCreateInfoEXT *), p_create_info, (XrDebugUtilsMessengerEXT *), p_messenger) + EXT_PROTO_XRRESULT_FUNC1(xrDestroyDebugUtilsMessengerEXT, (XrDebugUtilsMessengerEXT), p_messenger) + EXT_PROTO_XRRESULT_FUNC2(xrSetDebugUtilsObjectNameEXT, (XrInstance), p_instance, (const XrDebugUtilsObjectNameInfoEXT *), p_name_info) + EXT_PROTO_XRRESULT_FUNC2(xrSessionBeginDebugUtilsLabelRegionEXT, (XrSession), p_session, (const XrDebugUtilsLabelEXT *), p_label_info) + EXT_PROTO_XRRESULT_FUNC1(xrSessionEndDebugUtilsLabelRegionEXT, (XrSession), p_session) + EXT_PROTO_XRRESULT_FUNC2(xrSessionInsertDebugUtilsLabelEXT, (XrSession), p_session, (const XrDebugUtilsLabelEXT *), p_label_info) +}; + +#endif // OPENXR_DEBUG_UTILS_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index ce03df0b30..95b537d1b4 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -76,7 +76,7 @@ public: virtual void on_before_instance_created() {} // `on_before_instance_created` is called before we create our OpenXR instance. virtual void on_instance_created(const XrInstance p_instance) {} // `on_instance_created` is called right after we've successfully created our OpenXR instance. virtual void on_instance_destroyed() {} // `on_instance_destroyed` is called right before we destroy our OpenXR instance. - virtual void on_session_created(const XrSession p_instance) {} // `on_session_created` is called right after we've successfully created our OpenXR session. + virtual void on_session_created(const XrSession p_session) {} // `on_session_created` is called right after we've successfully created our OpenXR session. virtual void on_session_destroyed() {} // `on_session_destroyed` is called right before we destroy our OpenXR session. // `on_process` is called as part of our OpenXR process handling, @@ -84,6 +84,7 @@ public: // This is when controller data is queried and made available to game logic. virtual void on_process() {} virtual void on_pre_render() {} // `on_pre_render` is called right before we start rendering our XR viewports. + virtual void on_main_swapchains_created() {} // `on_main_swapchains_created` is called right after our main swapchains are (re)created. virtual void on_pre_draw_viewport(RID p_render_target) {} // `on_pre_draw_viewport` is called right before we start rendering this viewport virtual void on_post_draw_viewport(RID p_render_target) {} // `on_port_draw_viewport` is called right after we start rendering this viewport (note that on Vulkan draw commands may only be queued) @@ -96,10 +97,11 @@ public: virtual void on_state_loss_pending() {} // `on_state_loss_pending` is called when the OpenXR session state is changed to loss pending. virtual void on_state_exiting() {} // `on_state_exiting` is called when the OpenXR session state is changed to exiting. - virtual void *set_viewport_composition_layer_and_get_next_pointer(const XrCompositionLayerBaseHeader *p_layer, Dictionary p_property_values, void *p_next_pointer) { return p_next_pointer; } // Add additional data structures to composition layers created via OpenXRCompositionLayer. + virtual void *set_viewport_composition_layer_and_get_next_pointer(const XrCompositionLayerBaseHeader *p_layer, const Dictionary &p_property_values, void *p_next_pointer) { return p_next_pointer; } // Add additional data structures to composition layers created via OpenXRCompositionLayer. virtual void on_viewport_composition_layer_destroyed(const XrCompositionLayerBaseHeader *p_layer) {} // `on_viewport_composition_layer_destroyed` is called when a composition layer created via OpenXRCompositionLayer is destroyed. virtual void get_viewport_composition_layer_extension_properties(List<PropertyInfo> *p_property_list) {} // Get additional property definitions for OpenXRCompositionLayer. virtual Dictionary get_viewport_composition_layer_extension_property_defaults() { return Dictionary(); } // Get the default values for the additional property definitions for OpenXRCompositionLayer. + virtual void *set_android_surface_swapchain_create_info_and_get_next_pointer(const Dictionary &p_property_values, void *p_next_pointer) { return p_next_pointer; } // `on_event_polled` is called when there is an OpenXR event to process. // Should return true if the event was handled, false otherwise. diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp index 3b31e1b1f6..07ca476421 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp @@ -50,6 +50,7 @@ void OpenXRExtensionWrapperExtension::_bind_methods() { GDVIRTUAL_BIND(_on_session_created, "session"); GDVIRTUAL_BIND(_on_process); GDVIRTUAL_BIND(_on_pre_render); + GDVIRTUAL_BIND(_on_main_swapchains_created); GDVIRTUAL_BIND(_on_session_destroyed); GDVIRTUAL_BIND(_on_state_idle); GDVIRTUAL_BIND(_on_state_ready); @@ -64,6 +65,7 @@ void OpenXRExtensionWrapperExtension::_bind_methods() { GDVIRTUAL_BIND(_get_viewport_composition_layer_extension_properties); GDVIRTUAL_BIND(_get_viewport_composition_layer_extension_property_defaults); GDVIRTUAL_BIND(_on_viewport_composition_layer_destroyed, "layer"); + GDVIRTUAL_BIND(_set_android_surface_swapchain_create_info_and_get_next_pointer, "property_values", "next_pointer"); ClassDB::bind_method(D_METHOD("get_openxr_api"), &OpenXRExtensionWrapperExtension::get_openxr_api); ClassDB::bind_method(D_METHOD("register_extension_wrapper"), &OpenXRExtensionWrapperExtension::register_extension_wrapper); @@ -198,6 +200,10 @@ void OpenXRExtensionWrapperExtension::on_pre_render() { GDVIRTUAL_CALL(_on_pre_render); } +void OpenXRExtensionWrapperExtension::on_main_swapchains_created() { + GDVIRTUAL_CALL(_on_main_swapchains_created); +} + void OpenXRExtensionWrapperExtension::on_session_destroyed() { GDVIRTUAL_CALL(_on_session_destroyed); } @@ -244,7 +250,7 @@ bool OpenXRExtensionWrapperExtension::on_event_polled(const XrEventDataBuffer &p return false; } -void *OpenXRExtensionWrapperExtension::set_viewport_composition_layer_and_get_next_pointer(const XrCompositionLayerBaseHeader *p_layer, Dictionary p_property_values, void *p_next_pointer) { +void *OpenXRExtensionWrapperExtension::set_viewport_composition_layer_and_get_next_pointer(const XrCompositionLayerBaseHeader *p_layer, const Dictionary &p_property_values, void *p_next_pointer) { uint64_t pointer = 0; if (GDVIRTUAL_CALL(_set_viewport_composition_layer_and_get_next_pointer, GDExtensionConstPtr<void>(p_layer), p_property_values, GDExtensionPtr<void>(p_next_pointer), pointer)) { @@ -274,6 +280,16 @@ Dictionary OpenXRExtensionWrapperExtension::get_viewport_composition_layer_exten return property_defaults; } +void *OpenXRExtensionWrapperExtension::set_android_surface_swapchain_create_info_and_get_next_pointer(const Dictionary &p_property_values, void *p_next_pointer) { + uint64_t pointer = 0; + + if (GDVIRTUAL_CALL(_set_android_surface_swapchain_create_info_and_get_next_pointer, p_property_values, GDExtensionPtr<void>(p_next_pointer), pointer)) { + return reinterpret_cast<void *>(pointer); + } + + return p_next_pointer; +} + Ref<OpenXRAPIExtension> OpenXRExtensionWrapperExtension::get_openxr_api() { return openxr_api; } diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h index 71d2a57ff8..5cdf288c93 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h @@ -86,6 +86,7 @@ public: virtual void on_session_created(const XrSession p_session) override; virtual void on_process() override; virtual void on_pre_render() override; + virtual void on_main_swapchains_created() override; virtual void on_session_destroyed() override; GDVIRTUAL0(_on_register_metadata); @@ -95,6 +96,7 @@ public: GDVIRTUAL1(_on_session_created, uint64_t); GDVIRTUAL0(_on_process); GDVIRTUAL0(_on_pre_render); + GDVIRTUAL0(_on_main_swapchains_created); GDVIRTUAL0(_on_session_destroyed); virtual void on_state_idle() override; @@ -119,15 +121,17 @@ public: GDVIRTUAL1R(bool, _on_event_polled, GDExtensionConstPtr<void>); - virtual void *set_viewport_composition_layer_and_get_next_pointer(const XrCompositionLayerBaseHeader *p_layer, Dictionary p_property_values, void *p_next_pointer) override; + virtual void *set_viewport_composition_layer_and_get_next_pointer(const XrCompositionLayerBaseHeader *p_layer, const Dictionary &p_property_values, void *p_next_pointer) override; virtual void on_viewport_composition_layer_destroyed(const XrCompositionLayerBaseHeader *p_layer) override; virtual void get_viewport_composition_layer_extension_properties(List<PropertyInfo> *p_property_list) override; virtual Dictionary get_viewport_composition_layer_extension_property_defaults() override; + virtual void *set_android_surface_swapchain_create_info_and_get_next_pointer(const Dictionary &p_property_values, void *p_next_pointer) override; GDVIRTUAL3R(uint64_t, _set_viewport_composition_layer_and_get_next_pointer, GDExtensionConstPtr<void>, Dictionary, GDExtensionPtr<void>); GDVIRTUAL1(_on_viewport_composition_layer_destroyed, GDExtensionConstPtr<void>); GDVIRTUAL0R(TypedArray<Dictionary>, _get_viewport_composition_layer_extension_properties); GDVIRTUAL0R(Dictionary, _get_viewport_composition_layer_extension_property_defaults); + GDVIRTUAL2R(uint64_t, _set_android_surface_swapchain_create_info_and_get_next_pointer, Dictionary, GDExtensionPtr<void>); Ref<OpenXRAPIExtension> get_openxr_api(); diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.cpp b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp index bbdd2e3c8a..8ce808dd3c 100644 --- a/modules/openxr/extensions/openxr_fb_foveation_extension.cpp +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp @@ -101,7 +101,7 @@ void *OpenXRFBFoveationExtension::set_swapchain_create_info_and_get_next_pointer } } -void OpenXRFBFoveationExtension::on_state_ready() { +void OpenXRFBFoveationExtension::on_main_swapchains_created() { update_profile(); } @@ -127,26 +127,41 @@ void OpenXRFBFoveationExtension::set_foveation_dynamic(XrFoveationDynamicFB p_fo update_profile(); } -void OpenXRFBFoveationExtension::update_profile() { - if (!is_enabled()) { +void OpenXRFBFoveationExtension::_update_profile() { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + ERR_FAIL_NULL(fov_ext); + + if (!fov_ext->is_enabled()) { + return; + } + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + XrSwapchain main_color_swapchain = openxr_api->get_color_swapchain(); + if (main_color_swapchain == XR_NULL_HANDLE) { + // Our swapchain hasn't been created yet, we'll call this again once it has. return; } XrFoveationLevelProfileCreateInfoFB level_profile_create_info; level_profile_create_info.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB; level_profile_create_info.next = nullptr; - level_profile_create_info.level = foveation_level; + level_profile_create_info.level = fov_ext->foveation_level; level_profile_create_info.verticalOffset = 0.0f; - level_profile_create_info.dynamic = foveation_dynamic; + level_profile_create_info.dynamic = fov_ext->foveation_dynamic; XrFoveationProfileCreateInfoFB profile_create_info; profile_create_info.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB; profile_create_info.next = &level_profile_create_info; XrFoveationProfileFB foveation_profile; - XrResult result = xrCreateFoveationProfileFB(OpenXRAPI::get_singleton()->get_session(), &profile_create_info, &foveation_profile); + XrResult result = fov_ext->xrCreateFoveationProfileFB(openxr_api->get_session(), &profile_create_info, &foveation_profile); if (XR_FAILED(result)) { - print_line("OpenXR: Unable to create the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + print_line("OpenXR: Unable to create the foveation profile [", openxr_api->get_error_string(result), "]"); return; } @@ -154,15 +169,15 @@ void OpenXRFBFoveationExtension::update_profile() { foveation_update_state.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB; foveation_update_state.profile = foveation_profile; - result = swapchain_update_state_ext->xrUpdateSwapchainFB(OpenXRAPI::get_singleton()->get_color_swapchain(), (XrSwapchainStateBaseHeaderFB *)&foveation_update_state); + result = fov_ext->swapchain_update_state_ext->xrUpdateSwapchainFB(main_color_swapchain, (XrSwapchainStateBaseHeaderFB *)&foveation_update_state); if (XR_FAILED(result)) { - print_line("OpenXR: Unable to update the swapchain [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + print_line("OpenXR: Unable to update the swapchain [", openxr_api->get_error_string(result), "]"); // We still want to destroy our profile so keep going... } - result = xrDestroyFoveationProfileFB(foveation_profile); + result = fov_ext->xrDestroyFoveationProfileFB(foveation_profile); if (XR_FAILED(result)) { - print_line("OpenXR: Unable to destroy the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + print_line("OpenXR: Unable to destroy the foveation profile [", openxr_api->get_error_string(result), "]"); } } diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.h b/modules/openxr/extensions/openxr_fb_foveation_extension.h index 1c5e722731..84bd7011b5 100644 --- a/modules/openxr/extensions/openxr_fb_foveation_extension.h +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.h @@ -60,7 +60,7 @@ public: virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override; - virtual void on_state_ready() override; + virtual void on_main_swapchains_created() override; bool is_enabled() const; @@ -82,7 +82,15 @@ private: XrFoveationLevelFB foveation_level = XR_FOVEATION_LEVEL_NONE_FB; XrFoveationDynamicFB foveation_dynamic = XR_FOVEATION_DYNAMIC_DISABLED_FB; - void update_profile(); + static void _update_profile(); + + void update_profile() { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&OpenXRFBFoveationExtension::_update_profile)); + } // Enable foveation on this swapchain XrSwapchainCreateInfoFoveationFB swapchain_create_info_foveation_fb; diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index b8a2f58935..ea64f077c5 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -58,9 +58,14 @@ OpenXRHandTrackingExtension::~OpenXRHandTrackingExtension() { HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; + unobstructed_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_unobstructed_data_source"); + controller_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_controller_data_source"); + request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext; request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext; - request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext; + if (unobstructed_data_source || controller_data_source) { + request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext; + } return request_extensions; } @@ -141,10 +146,18 @@ void OpenXRHandTrackingExtension::on_process() { void *next_pointer = nullptr; // Originally not all XR runtimes supported hand tracking data sourced both from controllers and normal hand tracking. - // With this extension we can indicate we accept input from both sources so hand tracking data is consistently provided - // on runtimes that support this. - XrHandTrackingDataSourceEXT data_sources[2] = { XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT, XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT }; - XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, 2, data_sources }; + // With this extension we can indicate we wish to accept input from either or both sources. + // This functionality is subject to the abilities of the XR runtime and requires the data source extension. + // Note: If the data source extension is not available, no guarantees can be made on what the XR runtime supports. + uint32_t data_source_count = 0; + XrHandTrackingDataSourceEXT data_sources[2]; + if (unobstructed_data_source) { + data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT; + } + if (controller_data_source) { + data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT; + } + XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, data_source_count, data_sources }; if (hand_tracking_source_ext) { // If supported include this info next_pointer = &data_source_info; @@ -224,7 +237,9 @@ void OpenXRHandTrackingExtension::on_process() { if (XR_FAILED(result)) { // not successful? then we do nothing. print_line("OpenXR: Failed to get tracking for hand", i, "[", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + godot_tracker->set_hand_tracking_source(XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN); godot_tracker->set_has_tracking_data(false); + godot_tracker->invalidate_pose("default"); continue; } @@ -235,8 +250,6 @@ void OpenXRHandTrackingExtension::on_process() { } if (hand_trackers[i].locations.isActive) { - godot_tracker->set_has_tracking_data(true); - // SKELETON_RIG_HUMANOID bone adjustment. This rotation performs: // OpenXR Z+ -> Godot Humanoid Y- (Back along the bone) // OpenXR Y+ -> Godot Humanoid Z- (Out the back of the hand) @@ -245,7 +258,6 @@ void OpenXRHandTrackingExtension::on_process() { for (int joint = 0; joint < XR_HAND_JOINT_COUNT_EXT; joint++) { const XrHandJointLocationEXT &location = hand_trackers[i].joint_locations[joint]; const XrHandJointVelocityEXT &velocity = hand_trackers[i].joint_velocities[joint]; - const XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source; const XrPosef &pose = location.pose; Transform3D transform; @@ -285,18 +297,35 @@ void OpenXRHandTrackingExtension::on_process() { godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius); if (joint == XR_HAND_JOINT_PALM_EXT) { - XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; - if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; - } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + if (location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source; + + XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; + if (hand_tracking_source_ext) { + if (!data_source.isActive) { + source = XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED; + } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; + } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + } else { + // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them. + WARN_PRINT_ONCE("Unknown active data source found!"); + source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; + } + } + godot_tracker->set_hand_tracking_source(source); + godot_tracker->set_has_tracking_data(true); + godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity); + } else { + godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN); + godot_tracker->set_has_tracking_data(false); + godot_tracker->invalidate_pose("default"); } - - godot_tracker->set_hand_tracking_source(source); - godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity); } } } else { + godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN); godot_tracker->set_has_tracking_data(false); godot_tracker->invalidate_pose("default"); } @@ -344,16 +373,17 @@ XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTra OpenXRHandTrackingExtension::HandTrackedSource OpenXRHandTrackingExtension::get_hand_tracking_source(HandTrackedHands p_hand) const { ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, OPENXR_SOURCE_UNKNOWN); - if (hand_tracking_source_ext && hand_trackers[p_hand].data_source.isActive) { - switch (hand_trackers[p_hand].data_source.dataSource) { - case XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT: - return OPENXR_SOURCE_UNOBSTRUCTED; - - case XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT: - return OPENXR_SOURCE_CONTROLLER; - - default: - return OPENXR_SOURCE_UNKNOWN; + if (hand_tracking_source_ext) { + if (!hand_trackers[p_hand].data_source.isActive) { + return OPENXR_SOURCE_NOT_TRACKED; + } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { + return OPENXR_SOURCE_UNOBSTRUCTED; + } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { + return OPENXR_SOURCE_CONTROLLER; + } else { + // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them. + WARN_PRINT_ONCE("Unknown active data source found!"); + return OPENXR_SOURCE_UNKNOWN; } } diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h index f709bc05c2..2c34ff7f21 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.h +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h @@ -48,6 +48,7 @@ public: OPENXR_SOURCE_UNKNOWN, OPENXR_SOURCE_UNOBSTRUCTED, OPENXR_SOURCE_CONTROLLER, + OPENXR_SOURCE_NOT_TRACKED, OPENXR_SOURCE_MAX }; @@ -110,6 +111,8 @@ private: bool hand_tracking_ext = false; bool hand_motion_range_ext = false; bool hand_tracking_source_ext = false; + bool unobstructed_data_source = false; + bool controller_data_source = false; // functions void cleanup_hand_tracking(); diff --git a/modules/openxr/extensions/openxr_htc_controller_extension.cpp b/modules/openxr/extensions/openxr_htc_controller_extension.cpp index c4ecb4e57c..416934fa47 100644 --- a/modules/openxr/extensions/openxr_htc_controller_extension.cpp +++ b/modules/openxr/extensions/openxr_htc_controller_extension.cpp @@ -37,10 +37,19 @@ HashMap<String, bool *> OpenXRHTCControllerExtension::get_requested_extensions() request_extensions[XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME] = &available[HTC_VIVE_COSMOS]; request_extensions[XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME] = &available[HTC_VIVE_FOCUS3]; + request_extensions[XR_HTC_HAND_INTERACTION_EXTENSION_NAME] = &available[HTC_HAND_INTERACTION]; return request_extensions; } +PackedStringArray OpenXRHTCControllerExtension::get_suggested_tracker_names() { + PackedStringArray arr = { + "/user/hand_htc/left", + "/user/hand_htc/right", + }; + return arr; +} + bool OpenXRHTCControllerExtension::is_available(HTCControllers p_type) { return available[p_type]; } @@ -49,6 +58,9 @@ void OpenXRHTCControllerExtension::on_register_metadata() { OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton(); ERR_FAIL_NULL(metadata); + metadata->register_top_level_path("HTC left hand tracker", "/user/hand_htc/left", XR_HTC_HAND_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("HTC right hand tracker", "/user/hand_htc/right", XR_HTC_HAND_INTERACTION_EXTENSION_NAME); + // HTC Vive Cosmos controller metadata->register_interaction_profile("Vive Cosmos controller", "/interaction_profiles/htc/vive_cosmos_controller", XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME); metadata->register_io_path("/interaction_profiles/htc/vive_cosmos_controller", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); @@ -128,4 +140,17 @@ void OpenXRHTCControllerExtension::on_register_metadata() { metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); + + // HTC Hand interaction profile + metadata->register_interaction_profile("HTC Hand interaction", "/interaction_profiles/htc/hand_interaction", XR_HTC_HAND_INTERACTION_EXTENSION_NAME); + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Grip pose", "/user/hand_htc/left", "/user/hand_htc/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Grip pose", "/user/hand_htc/right", "/user/hand_htc/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Aim pose", "/user/hand_htc/left", "/user/hand_htc/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Aim pose", "/user/hand_htc/right", "/user/hand_htc/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Select (pinch)", "/user/hand_htc/left", "/user/hand_htc/left/input/select/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Select (pinch)", "/user/hand_htc/right", "/user/hand_htc/right/input/select/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Squeeze (grab)", "/user/hand_htc/left", "/user/hand_htc/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/htc/hand_interaction", "Squeeze (grab)", "/user/hand_htc/right", "/user/hand_htc/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); } diff --git a/modules/openxr/extensions/openxr_htc_controller_extension.h b/modules/openxr/extensions/openxr_htc_controller_extension.h index 2aaeac2577..6f2b2ceec3 100644 --- a/modules/openxr/extensions/openxr_htc_controller_extension.h +++ b/modules/openxr/extensions/openxr_htc_controller_extension.h @@ -39,11 +39,14 @@ public: // Note, HTC Vive Wand controllers are part of the core spec and not part of our extension. HTC_VIVE_COSMOS, HTC_VIVE_FOCUS3, + HTC_HAND_INTERACTION, HTC_MAX_CONTROLLERS }; virtual HashMap<String, bool *> get_requested_extensions() override; + PackedStringArray get_suggested_tracker_names() override; + bool is_available(HTCControllers p_type); virtual void on_register_metadata() override; diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp index bb60f7adef..b2ddf71f5e 100644 --- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp +++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp @@ -69,6 +69,20 @@ void OpenXRHTCViveTrackerExtension::on_register_metadata() { OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton(); ERR_FAIL_NULL(metadata); + // register_top_level_path("Handheld object tracker", "/user/vive_tracker_htcx/role/handheld_object", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Left foot tracker", "/user/vive_tracker_htcx/role/left_foot", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Right foot tracker", "/user/vive_tracker_htcx/role/right_foot", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Left shoulder tracker", "/user/vive_tracker_htcx/role/left_shoulder", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Right shoulder tracker", "/user/vive_tracker_htcx/role/right_shoulder", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Left elbow tracker", "/user/vive_tracker_htcx/role/left_elbow", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Right elbow tracker", "/user/vive_tracker_htcx/role/right_elbow", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Left knee tracker", "/user/vive_tracker_htcx/role/left_knee", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Right knee tracker", "/user/vive_tracker_htcx/role/right_knee", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Waist tracker", "/user/vive_tracker_htcx/role/waist", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Chest tracker", "/user/vive_tracker_htcx/role/chest", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Camera tracker", "/user/vive_tracker_htcx/role/camera", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + metadata->register_top_level_path("Keyboard tracker", "/user/vive_tracker_htcx/role/keyboard", XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME); + // HTC Vive tracker // Interestingly enough trackers don't have buttons or inputs, yet these are defined in the spec. // I think this can be supported through attachments on the trackers. diff --git a/modules/openxr/extensions/openxr_mxink_extension.cpp b/modules/openxr/extensions/openxr_mxink_extension.cpp new file mode 100644 index 0000000000..fe48583c27 --- /dev/null +++ b/modules/openxr/extensions/openxr_mxink_extension.cpp @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* openxr_mxink_extension.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 "openxr_mxink_extension.h" + +#include "../action_map/openxr_interaction_profile_metadata.h" + +// Not in base XR libs needs def +#define XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME "XR_LOGITECH_mx_ink_stylus_interaction" + +HashMap<String, bool *> OpenXRMxInkExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME] = &available; + + return request_extensions; +} + +bool OpenXRMxInkExtension::is_available() { + return available; +} + +void OpenXRMxInkExtension::on_register_metadata() { + OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton(); + ERR_FAIL_NULL(metadata); + + // Logitech MX Ink Stylus + metadata->register_interaction_profile("Logitech MX Ink Stylus", "/interaction_profiles/logitech/mx_ink_stylus_logitech", XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME); + + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/left", "/user/hand/left/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/left", "/user/hand/left/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/left", "/user/hand/left/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/left", "/user/hand/left/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/right", "/user/hand/right/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/right", "/user/hand/right/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/right", "/user/hand/right/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/right", "/user/hand/right/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); +} diff --git a/modules/openxr/extensions/openxr_mxink_extension.h b/modules/openxr/extensions/openxr_mxink_extension.h new file mode 100644 index 0000000000..fe0cf866aa --- /dev/null +++ b/modules/openxr/extensions/openxr_mxink_extension.h @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* openxr_mxink_extension.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 OPENXR_MXINK_EXTENSION_H +#define OPENXR_MXINK_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRMxInkExtension : public OpenXRExtensionWrapper { +public: + virtual HashMap<String, bool *> get_requested_extensions() override; + + bool is_available(); + + virtual void on_register_metadata() override; + +private: + bool available = false; +}; + +#endif // OPENXR_MXINK_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_visibility_mask_extension.cpp b/modules/openxr/extensions/openxr_visibility_mask_extension.cpp new file mode 100644 index 0000000000..3e193d7d50 --- /dev/null +++ b/modules/openxr/extensions/openxr_visibility_mask_extension.cpp @@ -0,0 +1,279 @@ +/**************************************************************************/ +/* openxr_visibility_mask_extension.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 "openxr_visibility_mask_extension.h" + +#include "../openxr_api.h" +#include "core/string/print_string.h" +#include "core/variant/array.h" +#include "core/variant/variant.h" +#include "servers/rendering_server.h" + +static const char *VISIBILITY_MASK_SHADER_CODE = + "shader_type spatial;\n" + "render_mode unshaded, shadows_disabled, cull_disabled;\n" + "void vertex() {\n" + "\tif (int(VERTEX.z) == VIEW_INDEX) {\n" + "\t\tVERTEX.z = -1.0;\n" + "\t\tVERTEX += EYE_OFFSET;\n" + "\t\tPOSITION = PROJECTION_MATRIX * vec4(VERTEX, 1.0);\n" + "\t\tPOSITION.xy /= POSITION.w;\n" + "\t\tPOSITION.z = 1.0;\n" + "\t\tPOSITION.w = 1.0;\n" + "\t} else {\n" + "\t\tPOSITION = vec4(2.0, 2.0, 2.0, 1.0);\n" + "\t}\n" + "}\n" + "void fragment() {\n" + "\tALBEDO = vec3(0.0, 0.0, 0.0);\n" + "}\n"; + +OpenXRVisibilityMaskExtension *OpenXRVisibilityMaskExtension::singleton = nullptr; + +OpenXRVisibilityMaskExtension *OpenXRVisibilityMaskExtension::get_singleton() { + return singleton; +} + +OpenXRVisibilityMaskExtension::OpenXRVisibilityMaskExtension() { + singleton = this; +} + +OpenXRVisibilityMaskExtension::~OpenXRVisibilityMaskExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRVisibilityMaskExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_KHR_VISIBILITY_MASK_EXTENSION_NAME] = &available; + + return request_extensions; +} + +void OpenXRVisibilityMaskExtension::on_instance_created(const XrInstance p_instance) { + if (available) { + EXT_INIT_XR_FUNC(xrGetVisibilityMaskKHR); + } +} + +void OpenXRVisibilityMaskExtension::on_session_created(const XrSession p_instance) { + if (available) { + RS *rendering_server = RS::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + OpenXRAPI *openxr_api = (OpenXRAPI *)OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + // Create our shader. + shader = rendering_server->shader_create(); + rendering_server->shader_set_code(shader, VISIBILITY_MASK_SHADER_CODE); + + // Create our material. + material = rendering_server->material_create(); + rendering_server->material_set_shader(material, shader); + rendering_server->material_set_render_priority(material, 99); + + // Create our mesh. + mesh = rendering_server->mesh_create(); + + // Get our initial mesh data. + mesh_count = openxr_api->get_view_count(); // We need a mesh for each view. + for (uint32_t i = 0; i < mesh_count; i++) { + _update_mesh_data(i); + } + + // And update our mesh + _update_mesh(); + } +} + +void OpenXRVisibilityMaskExtension::on_session_destroyed() { + RS *rendering_server = RS::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + // Free our mesh. + if (mesh.is_valid()) { + rendering_server->free(mesh); + mesh = RID(); + } + + // Free our material. + if (material.is_valid()) { + rendering_server->free(material); + material = RID(); + } + + // Free our shader. + if (shader.is_valid()) { + rendering_server->free(shader); + shader = RID(); + } + + mesh_count = 0; +} + +void OpenXRVisibilityMaskExtension::on_pre_render() { + // Update mesh data if its dirty. + // Here we call this from the rendering thread however as we're going through the rendering server this is safe. + _update_mesh(); +} + +bool OpenXRVisibilityMaskExtension::on_event_polled(const XrEventDataBuffer &event) { + if (event.type == XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR) { + XrEventDataVisibilityMaskChangedKHR *vismask_event = (XrEventDataVisibilityMaskChangedKHR *)&event; + + print_verbose("OpenXR EVENT: Visibility mask changed for view " + String::num_uint64(vismask_event->viewIndex)); + + if (available) { // This event won't be called if this extension is not available but better safe than sorry. + _update_mesh_data(vismask_event->viewIndex); + } + + return true; + } + + return false; +} + +bool OpenXRVisibilityMaskExtension::is_available() { + return available; +} + +RID OpenXRVisibilityMaskExtension::get_mesh() { + return mesh; +} + +void OpenXRVisibilityMaskExtension::_update_mesh_data(uint32_t p_view) { + if (available) { + ERR_FAIL_UNSIGNED_INDEX(p_view, 4); + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + XrSession session = openxr_api->get_session(); + XrViewConfigurationType view_configuration_type = openxr_api->get_view_configuration(); + + // Figure out how much data we're getting. + XrVisibilityMaskKHR visibility_mask_data = { + XR_TYPE_VISIBILITY_MASK_KHR, + nullptr, + 0, + 0, + nullptr, + 0, + 0, + nullptr, + }; + + XrResult result = xrGetVisibilityMaskKHR(session, view_configuration_type, p_view, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask_data); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to obtain visibility mask metrics [", openxr_api->get_error_string(result), "]"); + return; + } + + // Resize buffers + mesh_data[p_view].vertices.resize(visibility_mask_data.vertexCountOutput); + mesh_data[p_view].indices.resize(visibility_mask_data.indexCountOutput); + + visibility_mask_data.vertexCapacityInput = visibility_mask_data.vertexCountOutput; + visibility_mask_data.vertices = mesh_data[p_view].vertices.ptrw(); + visibility_mask_data.indexCapacityInput = visibility_mask_data.indexCountOutput; + visibility_mask_data.indices = mesh_data[p_view].indices.ptrw(); + + result = xrGetVisibilityMaskKHR(session, view_configuration_type, p_view, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask_data); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to obtain visibility mask data [", openxr_api->get_error_string(result), "]"); + return; + } + + // Mark as dirty, we have updated mesh data. + is_dirty = true; + } +} + +void OpenXRVisibilityMaskExtension::_update_mesh() { + if (available && is_dirty && mesh_count > 0) { + RS *rendering_server = RS::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + OpenXRAPI *openxr_api = (OpenXRAPI *)OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + // Combine all vertex and index buffers into one. + PackedVector3Array vertices; + PackedInt32Array indices; + + uint64_t vertice_count = 0; + uint64_t index_count = 0; + + for (uint32_t i = 0; i < mesh_count; i++) { + vertice_count += mesh_data[i].vertices.size(); + index_count += mesh_data[i].indices.size(); + } + + vertices.resize(vertice_count); + indices.resize(index_count); + uint64_t offset = 0; + + Vector3 *v_out = vertices.ptrw(); + int32_t *i_out = indices.ptrw(); + + for (uint32_t i = 0; i < mesh_count; i++) { + const XrVector2f *v_in = mesh_data[i].vertices.ptr(); + for (uint32_t j = 0; j < mesh_data[i].vertices.size(); j++) { + v_out->x = v_in->x; + v_out->y = v_in->y; + v_out->z = float(i); // We store our view in our Z component, our shader will filter the right faces out. + v_out++; + v_in++; + } + const uint32_t *i_in = mesh_data[i].indices.ptr(); + for (uint32_t j = 0; j < mesh_data[i].indices.size(); j++) { + *i_out = offset + *i_in; + i_out++; + i_in++; + } + + offset += mesh_data[i].vertices.size(); + } + + // Update our mesh. + Array arr; + arr.resize(RS::ARRAY_MAX); + arr[RS::ARRAY_VERTEX] = vertices; + arr[RS::ARRAY_INDEX] = indices; + + rendering_server->mesh_clear(mesh); + rendering_server->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr); + rendering_server->mesh_surface_set_material(mesh, 0, material); + + // Set no longer dirty. + is_dirty = false; + } +} diff --git a/modules/openxr/extensions/openxr_visibility_mask_extension.h b/modules/openxr/extensions/openxr_visibility_mask_extension.h new file mode 100644 index 0000000000..ee80dd02ff --- /dev/null +++ b/modules/openxr/extensions/openxr_visibility_mask_extension.h @@ -0,0 +1,95 @@ +/**************************************************************************/ +/* openxr_visibility_mask_extension.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 OPENXR_VISIBILITY_MASK_EXTENSION_H +#define OPENXR_VISIBILITY_MASK_EXTENSION_H + +#include "../util.h" + +#include "core/templates/vector.h" +#include "openxr_extension_wrapper.h" +#include "scene/resources/mesh.h" + +// The OpenXR visibility mask extension provides a mesh for each eye that +// can be used as a mask to determine which part of our rendered result +// is actually visible to the user. Due to lens distortion the edges of +// the rendered image are never used in the final result output on the HMD. +// +// Blacking out this are of the render result can remove a fair amount of +// overhead in rendering part of the screen that is unused. +// +// https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_KHR_visibility_mask + +class OpenXRVisibilityMaskExtension : public OpenXRExtensionWrapper { +public: + static OpenXRVisibilityMaskExtension *get_singleton(); + + OpenXRVisibilityMaskExtension(); + virtual ~OpenXRVisibilityMaskExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + + virtual void on_session_created(const XrSession p_instance) override; + virtual void on_session_destroyed() override; + + virtual void on_pre_render() override; + virtual bool on_event_polled(const XrEventDataBuffer &event) override; + + bool is_available(); + RID get_mesh(); + +private: + static OpenXRVisibilityMaskExtension *singleton; + + bool available = false; + bool is_dirty = false; + + RID shader; + RID material; + RID mesh; + + struct MeshData { + Vector<XrVector2f> vertices; + Vector<uint32_t> indices; + }; + + uint32_t mesh_count = 0; + MeshData mesh_data[4]; + + void _update_mesh_data(uint32_t p_view); + void _update_mesh(); + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC5(xrGetVisibilityMaskKHR, (XrSession), session, (XrViewConfigurationType), viewConfigurationType, (uint32_t), viewIndex, (XrVisibilityMaskTypeKHR), visibilityMaskType, (XrVisibilityMaskKHR *), visibilityMask); +}; + +#endif // OPENXR_VISIBILITY_MASK_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_wmr_controller_extension.cpp b/modules/openxr/extensions/openxr_wmr_controller_extension.cpp index 7d43d2e304..3437320452 100644 --- a/modules/openxr/extensions/openxr_wmr_controller_extension.cpp +++ b/modules/openxr/extensions/openxr_wmr_controller_extension.cpp @@ -38,6 +38,7 @@ HashMap<String, bool *> OpenXRWMRControllerExtension::get_requested_extensions() // Note HP G2 is available on WMR and SteamVR, but Odessey is only available on WMR request_extensions[XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME] = &available[WMR_HPMR]; request_extensions[XR_EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME] = &available[WMR_SAMSUNG_ODESSY]; + request_extensions[XR_MSFT_HAND_INTERACTION_EXTENSION_NAME] = &available[WMR_HAND_INTERACTION]; return request_extensions; } @@ -117,4 +118,23 @@ void OpenXRWMRControllerExtension::on_register_metadata() { metadata->register_io_path("/interaction_profiles/samsung/odyssey_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); metadata->register_io_path("/interaction_profiles/samsung/odyssey_controller", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); + + // MSFT Hand interaction profile, also supported by other headsets + metadata->register_interaction_profile("MSFT Hand interaction", "/interaction_profiles/microsoft/hand_interaction", XR_MSFT_HAND_INTERACTION_EXTENSION_NAME); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Pinch pose", "/user/hand/left", "/user/hand/left/input/pinch_ext/pose", XR_EXT_HAND_INTERACTION_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Pinch pose", "/user/hand/right", "/user/hand/right/input/pinch_ext/pose", XR_EXT_HAND_INTERACTION_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Poke pose", "/user/hand/left", "/user/hand/left/input/poke_ext/pose", XR_EXT_HAND_INTERACTION_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Poke pose", "/user/hand/right", "/user/hand/right/input/poke_ext/pose", XR_EXT_HAND_INTERACTION_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Palm pose", "/user/hand/left", "/user/hand/left/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Palm pose", "/user/hand/right", "/user/hand/right/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Select (pinch)", "/user/hand/left", "/user/hand/left/input/select/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Select (pinch)", "/user/hand/right", "/user/hand/right/input/select/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Squeeze (grab)", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/microsoft/hand_interaction", "Squeeze (grab)", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); } diff --git a/modules/openxr/extensions/openxr_wmr_controller_extension.h b/modules/openxr/extensions/openxr_wmr_controller_extension.h index 3bbdff586e..c8b50f1bbf 100644 --- a/modules/openxr/extensions/openxr_wmr_controller_extension.h +++ b/modules/openxr/extensions/openxr_wmr_controller_extension.h @@ -38,6 +38,7 @@ public: enum WMRControllers { WMR_HPMR, WMR_SAMSUNG_ODESSY, + WMR_HAND_INTERACTION, WMR_MAX_CONTROLLERS }; diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index d92084a220..caded14ca7 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -56,11 +56,6 @@ // feature off. // See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_sRGB_write_control.txt -// On OpenGLES this is not defined in our standard headers.. -#ifndef GL_FRAMEBUFFER_SRGB -#define GL_FRAMEBUFFER_SRGB 0x8DB9 -#endif - HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; @@ -196,23 +191,6 @@ void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_d p_usable_depth_formats.push_back(GL_DEPTH_COMPONENT24); } -void OpenXROpenGLExtension::on_pre_draw_viewport(RID p_render_target) { - if (srgb_ext_is_available) { - hw_linear_to_srgb_is_enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB); - if (hw_linear_to_srgb_is_enabled) { - // Disable this. - glDisable(GL_FRAMEBUFFER_SRGB); - } - } -} - -void OpenXROpenGLExtension::on_post_draw_viewport(RID p_render_target) { - if (srgb_ext_is_available && hw_linear_to_srgb_is_enabled) { - // Re-enable this. - glEnable(GL_FRAMEBUFFER_SRGB); - } -} - bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); ERR_FAIL_NULL_V(texture_storage, false); @@ -262,8 +240,8 @@ bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in Vector<RID> texture_rids; for (uint64_t i = 0; i < swapchain_length; i++) { - RID texture_rid = texture_storage->texture_create_external( - p_array_size == 1 ? GLES3::Texture::TYPE_2D : GLES3::Texture::TYPE_LAYERED, + RID texture_rid = texture_storage->texture_create_from_native_handle( + p_array_size == 1 ? RS::TEXTURE_TYPE_2D : RS::TEXTURE_TYPE_LAYERED, format, images[i].image, p_width, diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h index a3052d3f53..8da3ca48f4 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.h +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h @@ -49,9 +49,6 @@ public: virtual void on_instance_created(const XrInstance p_instance) override; virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; - virtual void on_pre_draw_viewport(RID p_render_target) override; - virtual void on_post_draw_viewport(RID p_render_target) override; - virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; @@ -76,9 +73,6 @@ private: Vector<RID> texture_rids; }; - bool srgb_ext_is_available = true; - bool hw_linear_to_srgb_is_enabled = false; - bool check_graphics_api_support(XrVersion p_desired_version); #ifdef ANDROID_ENABLED diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 32512070d6..c67be5a2b3 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -54,6 +54,7 @@ #endif #include "extensions/openxr_composition_layer_depth_extension.h" +#include "extensions/openxr_debug_utils_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_fb_foveation_extension.h" @@ -270,17 +271,16 @@ OpenXRAPI *OpenXRAPI::singleton = nullptr; Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers; bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) { - // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled" - - if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) { - // Disabled for now, using XR inside of the editor we'll be working on during the coming months. - return false; - } else { - if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) { - return GLOBAL_GET("xr/openxr/enabled"); + if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) { + if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) { + // For now, don't start OpenXR when the editor starts up. In the future, this may change + // if we want to integrate more XR features into the editor experience. + return false; } else { - return XRServer::get_xr_mode() == XRServer::XRMODE_ON; + return GLOBAL_GET("xr/openxr/enabled"); } + } else { + return XRServer::get_xr_mode() == XRServer::XRMODE_ON; } } @@ -316,6 +316,46 @@ String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const { return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); } +void OpenXRAPI::set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const String &p_object_name) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->set_object_name(p_object_type, p_object_handle, p_object_name.utf8().get_data()); +} + +void OpenXRAPI::begin_debug_label_region(const String &p_label_name) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->begin_debug_label_region(p_label_name.utf8().get_data()); +} + +void OpenXRAPI::end_debug_label_region() { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->end_debug_label_region(); +} + +void OpenXRAPI::insert_debug_label(const String &p_label_name) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->insert_debug_label(p_label_name.utf8().get_data()); +} + bool OpenXRAPI::load_layer_properties() { // This queries additional layers that are available and can be initialized when we create our OpenXR instance if (layer_properties != nullptr) { @@ -516,16 +556,13 @@ bool OpenXRAPI::create_instance() { extension_ptrs.push_back(enabled_extensions[i].get_data()); } - // Get our project name - String project_name = GLOBAL_GET("application/config/name"); - // Create our OpenXR instance XrApplicationInfo application_info{ - "", // applicationName, we'll set this down below + "Godot Engine", // applicationName, if we're running a game we'll update this down below. 1, // applicationVersion, we don't currently have this - "Godot Game Engine", // engineName + "Godot Engine", // engineName VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc. - XR_CURRENT_API_VERSION // apiVersion + XR_API_VERSION_1_0 // apiVersion }; void *next_pointer = nullptr; @@ -547,7 +584,11 @@ bool OpenXRAPI::create_instance() { extension_ptrs.ptr() // enabledExtensionNames }; - copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); + // Get our project name + String project_name = GLOBAL_GET("application/config/name"); + if (!project_name.is_empty()) { + copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); + } XrResult result = xrCreateInstance(&instance_create_info, &instance); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance."); @@ -707,13 +748,6 @@ bool OpenXRAPI::load_supported_environmental_blend_modes() { print_verbose(String("OpenXR: Found environmental blend mode ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[i])); } - // Check value we loaded at startup... - if (!is_environment_blend_mode_supported(environment_blend_mode)) { - print_verbose(String("OpenXR: ") + OpenXRUtil::get_environment_blend_mode_name(environment_blend_mode) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[0])); - - environment_blend_mode = supported_environment_blend_modes[0]; - } - return true; } @@ -833,10 +867,21 @@ bool OpenXRAPI::create_session() { return false; } + set_object_name(XR_OBJECT_TYPE_SESSION, uint64_t(session), "Main Godot OpenXR Session"); + + begin_debug_label_region("Godot session active"); + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_session_created(session); } + // Check our environment blend mode. This needs to happen after we call `on_session_created()` + // on the extension wrappers, so they can emulate alpha blend mode. + if (!set_environment_blend_mode(environment_blend_mode)) { + print_verbose(String("OpenXR: ") + OpenXRUtil::get_environment_blend_mode_name(environment_blend_mode) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[0])); + set_environment_blend_mode(supported_environment_blend_modes[0]); + } + return true; } @@ -902,6 +947,51 @@ bool OpenXRAPI::setup_play_space() { new_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; will_emulate_local_floor = true; + + if (local_floor_emulation.local_space == XR_NULL_HANDLE) { + XrReferenceSpaceCreateInfo create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType + identityPose, // poseInReferenceSpace + }; + + XrResult result = xrCreateReferenceSpace(session, &create_info, &local_floor_emulation.local_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + will_emulate_local_floor = false; + } + + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(local_floor_emulation.local_space), "Emulation local space"); + } + + if (local_floor_emulation.stage_space == XR_NULL_HANDLE) { + XrReferenceSpaceCreateInfo create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_STAGE, // referenceSpaceType + identityPose, // poseInReferenceSpace + }; + + XrResult result = xrCreateReferenceSpace(session, &create_info, &local_floor_emulation.stage_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + will_emulate_local_floor = false; + } + + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(local_floor_emulation.stage_space), "Emulation stage space"); + } + + if (!will_emulate_local_floor) { + if (local_floor_emulation.local_space != XR_NULL_HANDLE) { + xrDestroySpace(local_floor_emulation.local_space); + local_floor_emulation.local_space = XR_NULL_HANDLE; + } + if (local_floor_emulation.stage_space != XR_NULL_HANDLE) { + xrDestroySpace(local_floor_emulation.stage_space); + local_floor_emulation.stage_space = XR_NULL_HANDLE; + } + } } else { // Fallback on LOCAL, which all OpenXR runtimes are required to support. print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space.")); @@ -931,16 +1021,13 @@ bool OpenXRAPI::setup_play_space() { play_space = new_play_space; reference_space = new_reference_space; - emulating_local_floor = will_emulate_local_floor; - if (emulating_local_floor) { - // We'll use the STAGE space to get the floor height, but we can't do that until - // after xrWaitFrame(), so just set this flag for now. - // Render state will be updated then. - should_reset_emulated_floor_height = true; - } else { - // Update render state so this play space is used rendering the upcoming frame. - set_render_play_space(play_space); - } + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(play_space), "Play space"); + + local_floor_emulation.enabled = will_emulate_local_floor; + local_floor_emulation.should_reset_floor_height = will_emulate_local_floor; + + // Update render state so this play space is used rendering the upcoming frame. + set_render_play_space(play_space); return true; } @@ -971,67 +1058,45 @@ bool OpenXRAPI::setup_view_space() { return false; } + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(view_space), "View space"); + return true; } bool OpenXRAPI::reset_emulated_floor_height() { - ERR_FAIL_COND_V(!emulating_local_floor, false); - - // This is based on the example code in the OpenXR spec which shows how to - // emulate LOCAL_FLOOR if it's not supported. - // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor + ERR_FAIL_COND_V(!local_floor_emulation.enabled, false); + ERR_FAIL_COND_V(local_floor_emulation.local_space == XR_NULL_HANDLE, false); + ERR_FAIL_COND_V(local_floor_emulation.stage_space == XR_NULL_HANDLE, false); XrResult result; - XrPosef identityPose = { - { 0.0, 0.0, 0.0, 1.0 }, - { 0.0, 0.0, 0.0 } - }; - - XrSpace local_space = XR_NULL_HANDLE; - XrSpace stage_space = XR_NULL_HANDLE; - - XrReferenceSpaceCreateInfo create_info = { - XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type - nullptr, // next - XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType - identityPose, // poseInReferenceSpace - }; - - result = xrCreateReferenceSpace(session, &create_info, &local_space); - if (XR_FAILED(result)) { - print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); - return false; - } - - create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; - result = xrCreateReferenceSpace(session, &create_info, &stage_space); - if (XR_FAILED(result)) { - print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); - xrDestroySpace(local_space); - return false; - } - XrSpaceLocation stage_location = { XR_TYPE_SPACE_LOCATION, // type nullptr, // next 0, // locationFlags - identityPose, // pose + { { 0.0, 0.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0 } }, // pose }; - result = xrLocateSpace(stage_space, local_space, get_predicted_display_time(), &stage_location); - - xrDestroySpace(local_space); - xrDestroySpace(stage_space); + result = xrLocateSpace(local_floor_emulation.stage_space, local_floor_emulation.local_space, get_predicted_display_time(), &stage_location); if (XR_FAILED(result)) { print_line("OpenXR: Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); return false; } + XrPosef pose = { + { 0.0, 0.0, 0.0, 1.0 }, + { 0.0, stage_location.pose.position.y, 0.0 } + }; + + XrReferenceSpaceCreateInfo create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType + pose, // poseInReferenceSpace + }; + XrSpace new_play_space; - create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; - create_info.poseInReferenceSpace.position.y = stage_location.pose.position.y; result = xrCreateReferenceSpace(session, &create_info, &new_play_space); if (XR_FAILED(result)) { print_line("OpenXR: Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate [", get_error_string(result), "]"); @@ -1169,6 +1234,8 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { return false; } + + set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain()), "Main color swapchain"); } // We create our depth swapchain if: @@ -1179,6 +1246,8 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { return false; } + + set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain()), "Main depth swapchain"); } // We create our velocity swapchain if: @@ -1220,6 +1289,10 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { } }; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_main_swapchains_created(); + } + return true; }; @@ -1271,6 +1344,16 @@ void OpenXRAPI::destroy_session() { xrDestroySpace(view_space); view_space = XR_NULL_HANDLE; } + if (local_floor_emulation.local_space != XR_NULL_HANDLE) { + xrDestroySpace(local_floor_emulation.local_space); + local_floor_emulation.local_space = XR_NULL_HANDLE; + } + if (local_floor_emulation.stage_space != XR_NULL_HANDLE) { + xrDestroySpace(local_floor_emulation.stage_space); + local_floor_emulation.stage_space = XR_NULL_HANDLE; + } + local_floor_emulation.enabled = false; + local_floor_emulation.should_reset_floor_height = false; if (supported_reference_spaces != nullptr) { // free previous results @@ -1283,6 +1366,8 @@ void OpenXRAPI::destroy_session() { wrapper->on_session_destroyed(); } + end_debug_label_region(); + xrDestroySession(session); session = XR_NULL_HANDLE; } @@ -1433,6 +1518,10 @@ void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) { form_factor = p_form_factor; } +uint32_t OpenXRAPI::get_view_count() { + return view_count; +} + void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) { ERR_FAIL_COND(is_initialized()); @@ -1707,8 +1796,12 @@ void OpenXRAPI::cleanup_extension_wrappers() { XrHandTrackerEXT OpenXRAPI::get_hand_tracker(int p_hand_index) { ERR_FAIL_INDEX_V(p_hand_index, OpenXRHandTrackingExtension::HandTrackedHands::OPENXR_MAX_TRACKED_HANDS, XR_NULL_HANDLE); + + OpenXRHandTrackingExtension *hand_tracking = OpenXRHandTrackingExtension::get_singleton(); + ERR_FAIL_NULL_V(hand_tracking, XR_NULL_HANDLE); + OpenXRHandTrackingExtension::HandTrackedHands hand = static_cast<OpenXRHandTrackingExtension::HandTrackedHands>(p_hand_index); - return OpenXRHandTrackingExtension::get_singleton()->get_hand_tracker(hand)->hand_tracker; + return hand_tracking->get_hand_tracker(hand)->hand_tracker; } Size2 OpenXRAPI::get_recommended_target_size() { @@ -1888,15 +1981,6 @@ bool OpenXRAPI::poll_events() { // We probably didn't poll fast enough, just output warning WARN_PRINT("OpenXR EVENT: " + itos(event->lostEventCount) + " event data lost!"); } break; - case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { - // XrEventDataVisibilityMaskChangedKHR *event = (XrEventDataVisibilityMaskChangedKHR *)&runtimeEvent; - - // TODO implement this in the future, we should call xrGetVisibilityMaskKHR to obtain a mask, - // this will allow us to prevent rendering the part of our view which is never displayed giving us - // a decent performance improvement. - - print_verbose("OpenXR EVENT: STUB: visibility mask changed"); - } break; case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { XrEventDataInstanceLossPending *event = (XrEventDataInstanceLossPending *)&runtimeEvent; @@ -1949,8 +2033,8 @@ bool OpenXRAPI::poll_events() { XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent; print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); - if (emulating_local_floor) { - should_reset_emulated_floor_height = true; + if (local_floor_emulation.enabled) { + local_floor_emulation.should_reset_floor_height = true; } if (event->poseValid && xr_interface) { xr_interface->on_pose_recentered(); @@ -2093,16 +2177,18 @@ bool OpenXRAPI::process() { set_render_display_info(frame_state.predictedDisplayTime, frame_state.shouldRender); + // This is before setup_play_space() to ensure that it happens on the frame after + // the play space has been created. + if (unlikely(local_floor_emulation.should_reset_floor_height && !play_space_is_dirty)) { + reset_emulated_floor_height(); + local_floor_emulation.should_reset_floor_height = false; + } + if (unlikely(play_space_is_dirty)) { setup_play_space(); play_space_is_dirty = false; } - if (unlikely(should_reset_emulated_floor_height)) { - reset_emulated_floor_height(); - should_reset_emulated_floor_height = false; - } - for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_process(); } @@ -2188,6 +2274,9 @@ void OpenXRAPI::pre_render() { } } + // We should get our frame no from the rendering server, but this will do. + begin_debug_label_region(String("Session Frame ") + String::num_uint64(++render_state.frame)); + // let's start our frame.. XrFrameBeginInfo frame_begin_info = { XR_TYPE_FRAME_BEGIN_INFO, // type @@ -2306,6 +2395,8 @@ void OpenXRAPI::end_frame() { return; } + end_debug_label_region(); // Session frame # + // neither eye is rendered return; } @@ -2380,6 +2471,8 @@ void OpenXRAPI::end_frame() { print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); return; } + + end_debug_label_region(); // Session frame # } float OpenXRAPI::get_display_refresh_rate() const { @@ -2490,7 +2583,6 @@ OpenXRAPI::OpenXRAPI() { if (Engine::get_singleton()->is_editor_hint()) { // Enabled OpenXR in the editor? Adjust our settings for the editor - } else { // Load settings from project settings int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor"); @@ -2795,6 +2887,8 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n return RID(); } + set_object_name(XR_OBJECT_TYPE_ACTION_SET, uint64_t(action_set.handle), p_name); + return action_set_owner.make_rid(action_set); } @@ -2970,6 +3064,8 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String return RID(); } + set_object_name(XR_OBJECT_TYPE_ACTION, uint64_t(action.handle), p_name); + return action_owner.make_rid(action); } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index f9d2e60148..0d1e4eb414 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -165,8 +165,16 @@ private: XrSpace view_space = XR_NULL_HANDLE; XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; - bool emulating_local_floor = false; - bool should_reset_emulated_floor_height = false; + // When LOCAL_FLOOR isn't supported, we use an approach based on the example code in the + // OpenXR spec in order to emulate it. + // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor + struct LocalFloorEmulation { + bool enabled = false; + XrSpace local_space = XR_NULL_HANDLE; + XrSpace stage_space = XR_NULL_HANDLE; + bool should_reset_floor_height = false; + } local_floor_emulation; + bool reset_emulated_floor_height(); bool load_layer_properties(); @@ -328,6 +336,7 @@ private: XrTime predicted_display_time = 0; XrSpace play_space = XR_NULL_HANDLE; double render_target_size_multiplier = 1.0; + uint64_t frame = 0; uint32_t view_count = 0; XrView *views = nullptr; @@ -414,6 +423,10 @@ public: XrResult get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr); String get_error_string(XrResult result) const; String get_swapchain_format_name(int64_t p_swapchain_format) const; + void set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const String &p_object_name); + void begin_debug_label_region(const String &p_label_name); + void end_debug_label_region(); + void insert_debug_label(const String &p_label_name); OpenXRInterface *get_xr_interface() const { return xr_interface; } void set_xr_interface(OpenXRInterface *p_xr_interface); @@ -426,6 +439,7 @@ public: void set_form_factor(XrFormFactor p_form_factor); XrFormFactor get_form_factor() const { return form_factor; } + uint32_t get_view_count(); void set_view_configuration(XrViewConfigurationType p_view_configuration); XrViewConfigurationType get_view_configuration() const { return view_configuration; } @@ -446,7 +460,6 @@ public: _FORCE_INLINE_ XrTime get_predicted_display_time() { return frame_state.predictedDisplayTime; } _FORCE_INLINE_ XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; } _FORCE_INLINE_ bool can_render() { - ERR_ON_RENDER_THREAD_V(false); return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && frame_state.shouldRender; } diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp index a1744fa1db..f3bc178d3a 100644 --- a/modules/openxr/openxr_api_extension.cpp +++ b/modules/openxr/openxr_api_extension.cpp @@ -43,6 +43,10 @@ void OpenXRAPIExtension::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_proc_addr", "name"), &OpenXRAPIExtension::get_instance_proc_addr); ClassDB::bind_method(D_METHOD("get_error_string", "result"), &OpenXRAPIExtension::get_error_string); ClassDB::bind_method(D_METHOD("get_swapchain_format_name", "swapchain_format"), &OpenXRAPIExtension::get_swapchain_format_name); + ClassDB::bind_method(D_METHOD("set_object_name", "object_type", "object_handle", "object_name"), &OpenXRAPIExtension::set_object_name); + ClassDB::bind_method(D_METHOD("begin_debug_label_region", "label_name"), &OpenXRAPIExtension::begin_debug_label_region); + ClassDB::bind_method(D_METHOD("end_debug_label_region"), &OpenXRAPIExtension::end_debug_label_region); + ClassDB::bind_method(D_METHOD("insert_debug_label", "label_name"), &OpenXRAPIExtension::insert_debug_label); ClassDB::bind_method(D_METHOD("is_initialized"), &OpenXRAPIExtension::is_initialized); ClassDB::bind_method(D_METHOD("is_running"), &OpenXRAPIExtension::is_running); @@ -116,6 +120,30 @@ String OpenXRAPIExtension::get_swapchain_format_name(int64_t p_swapchain_format) return OpenXRAPI::get_singleton()->get_swapchain_format_name(p_swapchain_format); } +void OpenXRAPIExtension::set_object_name(int64_t p_object_type, uint64_t p_object_handle, const String &p_object_name) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->set_object_name(XrObjectType(p_object_type), p_object_handle, p_object_name); +} + +void OpenXRAPIExtension::begin_debug_label_region(const String &p_label_name) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->begin_debug_label_region(p_label_name); +} + +void OpenXRAPIExtension::end_debug_label_region() { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->end_debug_label_region(); +} + +void OpenXRAPIExtension::insert_debug_label(const String &p_label_name) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->insert_debug_label(p_label_name); +} + bool OpenXRAPIExtension::is_initialized() { ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false); return OpenXRAPI::get_singleton()->is_initialized(); diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h index cff2c4738e..1b88b418f6 100644 --- a/modules/openxr/openxr_api_extension.h +++ b/modules/openxr/openxr_api_extension.h @@ -64,6 +64,10 @@ public: uint64_t get_instance_proc_addr(String p_name); String get_error_string(uint64_t result); String get_swapchain_format_name(int64_t p_swapchain_format); + void set_object_name(int64_t p_object_type, uint64_t p_object_handle, const String &p_object_name); + void begin_debug_label_region(const String &p_label_name); + void end_debug_label_region(); + void insert_debug_label(const String &p_label_name); bool is_initialized(); bool is_running(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index fbbc61a91c..73ac529537 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -36,7 +36,8 @@ #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_hand_interaction_extension.h" -#include "thirdparty/openxr/include/openxr/openxr.h" + +#include <openxr/openxr.h> void OpenXRInterface::_bind_methods() { // lifecycle signals @@ -651,6 +652,10 @@ bool OpenXRInterface::initialize() { // make this our primary interface xr_server->set_primary_interface(this); + // Register an additional output with the display server, so rendering won't + // be skipped if no windows are visible. + DisplayServer::get_singleton()->register_additional_output(this); + initialized = true; return initialized; @@ -674,6 +679,8 @@ void OpenXRInterface::uninitialize() { } } + DisplayServer::get_singleton()->unregister_additional_output(this); + initialized = false; } diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 85514737f2..f3fda2517c 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -44,9 +44,11 @@ #include "scene/openxr_composition_layer_equirect.h" #include "scene/openxr_composition_layer_quad.h" #include "scene/openxr_hand.h" +#include "scene/openxr_visibility_mask.h" #include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_composition_layer_extension.h" +#include "extensions/openxr_debug_utils_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_hand_interaction_extension.h" @@ -57,8 +59,10 @@ #include "extensions/openxr_local_floor_extension.h" #include "extensions/openxr_meta_controller_extension.h" #include "extensions/openxr_ml2_controller_extension.h" +#include "extensions/openxr_mxink_extension.h" #include "extensions/openxr_palm_pose_extension.h" #include "extensions/openxr_pico_controller_extension.h" +#include "extensions/openxr_visibility_mask_extension.h" #include "extensions/openxr_wmr_controller_extension.h" #ifdef TOOLS_ENABLED @@ -126,8 +130,13 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRMetaControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandInteractionExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRMxInkExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRVisibilityMaskExtension)); // register gated extensions + if (int(GLOBAL_GET("xr/openxr/extensions/debug_utils")) > 0) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXRDebugUtilsExtension)); + } if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension)); } @@ -179,6 +188,8 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(OpenXRHand); + GDREGISTER_CLASS(OpenXRVisibilityMask); + XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { openxr_interface.instantiate(); diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp index 50cc7b9e7b..697369d516 100644 --- a/modules/openxr/scene/openxr_composition_layer.cpp +++ b/modules/openxr/scene/openxr_composition_layer.cpp @@ -38,6 +38,8 @@ #include "scene/3d/xr_nodes.h" #include "scene/main/viewport.h" +#include "platform/android/api/java_class_wrapper.h" + Vector<OpenXRCompositionLayer *> OpenXRCompositionLayer::composition_layer_nodes; static const char *HOLE_PUNCH_SHADER_CODE = @@ -47,7 +49,10 @@ static const char *HOLE_PUNCH_SHADER_CODE = "\tALBEDO = vec3(0.0, 0.0, 0.0);\n" "}\n"; -OpenXRCompositionLayer::OpenXRCompositionLayer() { +OpenXRCompositionLayer::OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer) { + composition_layer_base_header = p_composition_layer; + openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider(composition_layer_base_header)); + openxr_api = OpenXRAPI::get_singleton(); composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton(); @@ -85,6 +90,12 @@ void OpenXRCompositionLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer_viewport", "viewport"), &OpenXRCompositionLayer::set_layer_viewport); ClassDB::bind_method(D_METHOD("get_layer_viewport"), &OpenXRCompositionLayer::get_layer_viewport); + ClassDB::bind_method(D_METHOD("set_use_android_surface", "enable"), &OpenXRCompositionLayer::set_use_android_surface); + ClassDB::bind_method(D_METHOD("get_use_android_surface"), &OpenXRCompositionLayer::get_use_android_surface); + + ClassDB::bind_method(D_METHOD("set_android_surface_size", "size"), &OpenXRCompositionLayer::set_android_surface_size); + ClassDB::bind_method(D_METHOD("get_android_surface_size"), &OpenXRCompositionLayer::get_android_surface_size); + ClassDB::bind_method(D_METHOD("set_enable_hole_punch", "enable"), &OpenXRCompositionLayer::set_enable_hole_punch); ClassDB::bind_method(D_METHOD("get_enable_hole_punch"), &OpenXRCompositionLayer::get_enable_hole_punch); @@ -94,11 +105,14 @@ void OpenXRCompositionLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_alpha_blend", "enabled"), &OpenXRCompositionLayer::set_alpha_blend); ClassDB::bind_method(D_METHOD("get_alpha_blend"), &OpenXRCompositionLayer::get_alpha_blend); + ClassDB::bind_method(D_METHOD("get_android_surface"), &OpenXRCompositionLayer::get_android_surface); ClassDB::bind_method(D_METHOD("is_natively_supported"), &OpenXRCompositionLayer::is_natively_supported); ClassDB::bind_method(D_METHOD("intersects_ray", "origin", "direction"), &OpenXRCompositionLayer::intersects_ray); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "layer_viewport", PROPERTY_HINT_NODE_TYPE, "SubViewport"), "set_layer_viewport", "get_layer_viewport"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_android_surface", PROPERTY_HINT_NONE, ""), "set_use_android_surface", "get_use_android_surface"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "android_surface_size", PROPERTY_HINT_NONE, ""), "set_android_surface_size", "get_android_surface_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sort_order", PROPERTY_HINT_NONE, ""), "set_sort_order", "get_sort_order"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "alpha_blend", PROPERTY_HINT_NONE, ""), "set_alpha_blend", "get_alpha_blend"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_hole_punch", PROPERTY_HINT_NONE, ""), "set_enable_hole_punch", "get_enable_hole_punch"); @@ -108,7 +122,7 @@ bool OpenXRCompositionLayer::_should_use_fallback_node() { if (Engine::get_singleton()->is_editor_hint()) { return true; } else if (openxr_session_running) { - return enable_hole_punch || !is_natively_supported(); + return enable_hole_punch || (!is_natively_supported() && !use_android_surface); } return false; } @@ -128,10 +142,36 @@ void OpenXRCompositionLayer::_remove_fallback_node() { fallback = nullptr; } +void OpenXRCompositionLayer::_setup_composition_layer_provider() { + if (use_android_surface || layer_viewport) { + if (composition_layer_extension) { + composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider); + } + + // NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly. + if (layer_viewport) { + // Set our properties on the layer provider, which will create all the necessary resources (ex swap chains). + openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + } + } +} + +void OpenXRCompositionLayer::_clear_composition_layer_provider() { + if (composition_layer_extension) { + composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider); + } + + // NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly. + if (!use_android_surface) { + // This will reset the viewport and free all the resources (ex swap chains) used by the layer. + openxr_layer_provider->set_viewport(RID(), Size2i()); + } +} + void OpenXRCompositionLayer::_on_openxr_session_begun() { openxr_session_running = true; - if (layer_viewport && is_natively_supported() && is_visible() && is_inside_tree()) { - openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + if (is_natively_supported() && is_visible() && is_inside_tree()) { + _setup_composition_layer_provider(); } if (!fallback && _should_use_fallback_node()) { _create_fallback_node(); @@ -142,16 +182,26 @@ void OpenXRCompositionLayer::_on_openxr_session_stopping() { openxr_session_running = false; if (fallback && !_should_use_fallback_node()) { _remove_fallback_node(); - } else { - openxr_layer_provider->set_viewport(RID(), Size2i()); } + _clear_composition_layer_provider(); } void OpenXRCompositionLayer::update_fallback_mesh() { should_update_fallback_mesh = true; } +XrPosef OpenXRCompositionLayer::get_openxr_pose() { + Transform3D reference_frame = XRServer::get_singleton()->get_reference_frame(); + Transform3D transform = reference_frame.inverse() * get_transform(); + Quaternion quat(transform.basis.orthonormalized()); + return { + { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }, + { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z } + }; +} + bool OpenXRCompositionLayer::is_viewport_in_use(SubViewport *p_viewport) { + ERR_FAIL_NULL_V(p_viewport, false); for (const OpenXRCompositionLayer *other_composition_layer : composition_layer_nodes) { if (other_composition_layer != this && other_composition_layer->is_inside_tree() && other_composition_layer->get_layer_viewport() == p_viewport) { return true; @@ -165,7 +215,12 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) { return; } - ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first.")); + if (p_viewport != nullptr) { + ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first.")); + } + if (use_android_surface) { + ERR_FAIL_COND_MSG(p_viewport != nullptr, RTR("Cannot set SubViewport on an OpenXR composition layer when using an Android surface.")); + } layer_viewport = p_viewport; @@ -188,6 +243,41 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) { } } +void OpenXRCompositionLayer::set_use_android_surface(bool p_use_android_surface) { + if (use_android_surface == p_use_android_surface) { + return; + } + + use_android_surface = p_use_android_surface; + if (use_android_surface) { + set_layer_viewport(nullptr); + openxr_layer_provider->set_use_android_surface(true, android_surface_size); + } else { + openxr_layer_provider->set_use_android_surface(false, Size2i()); + } + + notify_property_list_changed(); +} + +bool OpenXRCompositionLayer::get_use_android_surface() const { + return use_android_surface; +} + +void OpenXRCompositionLayer::set_android_surface_size(Size2i p_size) { + if (android_surface_size == p_size) { + return; + } + + android_surface_size = p_size; + if (use_android_surface) { + openxr_layer_provider->set_use_android_surface(true, android_surface_size); + } +} + +Size2i OpenXRCompositionLayer::get_android_surface_size() const { + return android_surface_size; +} + SubViewport *OpenXRCompositionLayer::get_layer_viewport() const { return layer_viewport; } @@ -216,33 +306,23 @@ bool OpenXRCompositionLayer::get_enable_hole_punch() const { } void OpenXRCompositionLayer::set_sort_order(int p_order) { - if (openxr_layer_provider) { - openxr_layer_provider->set_sort_order(p_order); - update_configuration_warnings(); - } + openxr_layer_provider->set_sort_order(p_order); + update_configuration_warnings(); } int OpenXRCompositionLayer::get_sort_order() const { - if (openxr_layer_provider) { - return openxr_layer_provider->get_sort_order(); - } - return 1; + return openxr_layer_provider->get_sort_order(); } void OpenXRCompositionLayer::set_alpha_blend(bool p_alpha_blend) { - if (openxr_layer_provider) { - openxr_layer_provider->set_alpha_blend(p_alpha_blend); - if (fallback) { - _reset_fallback_material(); - } + openxr_layer_provider->set_alpha_blend(p_alpha_blend); + if (fallback) { + _reset_fallback_material(); } } bool OpenXRCompositionLayer::get_alpha_blend() const { - if (openxr_layer_provider) { - return openxr_layer_provider->get_alpha_blend(); - } - return false; + return openxr_layer_provider->get_alpha_blend(); } bool OpenXRCompositionLayer::is_natively_supported() const { @@ -252,6 +332,10 @@ bool OpenXRCompositionLayer::is_natively_supported() const { return false; } +Ref<JavaObject> OpenXRCompositionLayer::get_android_surface() { + return openxr_layer_provider->get_android_surface(); +} + Vector2 OpenXRCompositionLayer::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const { return Vector2(-1.0, -1.0); } @@ -289,10 +373,7 @@ void OpenXRCompositionLayer::_reset_fallback_material() { Ref<ViewportTexture> texture = material->get_texture(StandardMaterial3D::TEXTURE_ALBEDO); if (texture.is_null()) { - texture.instantiate(); - // ViewportTexture can't be configured without a local scene, so use this hack to set it. - HashMap<Ref<Resource>, Ref<Resource>> remap_cache; - texture->configure_for_local_scene(this, remap_cache); + texture = layer_viewport->get_texture(); } Node *loc_scene = texture->get_local_scene(); @@ -309,12 +390,10 @@ void OpenXRCompositionLayer::_notification(int p_what) { case NOTIFICATION_POSTINITIALIZE: { composition_layer_nodes.push_back(this); - if (openxr_layer_provider) { - for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) { - extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults()); - } - openxr_layer_provider->set_extension_property_values(extension_property_values); + for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) { + extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults()); } + openxr_layer_provider->set_extension_property_values(extension_property_values); } break; case NOTIFICATION_INTERNAL_PROCESS: { if (fallback) { @@ -327,10 +406,10 @@ void OpenXRCompositionLayer::_notification(int p_what) { } break; case NOTIFICATION_VISIBILITY_CHANGED: { if (!fallback && openxr_session_running && is_inside_tree()) { - if (layer_viewport && is_visible()) { - openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + if (is_visible()) { + _setup_composition_layer_provider(); } else { - openxr_layer_provider->set_viewport(RID(), Size2i()); + _clear_composition_layer_provider(); } } update_configuration_warnings(); @@ -339,25 +418,15 @@ void OpenXRCompositionLayer::_notification(int p_what) { update_configuration_warnings(); } break; case NOTIFICATION_ENTER_TREE: { - if (composition_layer_extension) { - composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider); - } - - if (is_viewport_in_use(layer_viewport)) { - set_layer_viewport(nullptr); - } else if (!fallback && layer_viewport && openxr_session_running && is_visible()) { - openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + if (layer_viewport && is_viewport_in_use(layer_viewport)) { + _clear_composition_layer_provider(); + } else if (openxr_session_running && is_visible()) { + _setup_composition_layer_provider(); } } break; case NOTIFICATION_EXIT_TREE: { - if (composition_layer_extension) { - composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider); - } - - if (!fallback) { - // This will clean up existing resources. - openxr_layer_provider->set_viewport(RID(), Size2i()); - } + // This will clean up existing resources. + _clear_composition_layer_provider(); } break; } } @@ -389,13 +458,27 @@ bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value bool OpenXRCompositionLayer::_set(const StringName &p_property, const Variant &p_value) { extension_property_values[p_property] = p_value; - if (openxr_layer_provider) { - openxr_layer_provider->set_extension_property_values(extension_property_values); - } + openxr_layer_provider->set_extension_property_values(extension_property_values); return true; } +void OpenXRCompositionLayer::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "layer_viewport") { + if (use_android_surface) { + p_property.usage &= ~PROPERTY_USAGE_EDITOR; + } else { + p_property.usage |= PROPERTY_USAGE_EDITOR; + } + } else if (p_property.name == "android_surface_size") { + if (use_android_surface) { + p_property.usage |= PROPERTY_USAGE_EDITOR; + } else { + p_property.usage &= ~PROPERTY_USAGE_EDITOR; + } + } +} + PackedStringArray OpenXRCompositionLayer::get_configuration_warnings() const { PackedStringArray warnings = Node3D::get_configuration_warnings(); diff --git a/modules/openxr/scene/openxr_composition_layer.h b/modules/openxr/scene/openxr_composition_layer.h index 6792364295..26b40236d2 100644 --- a/modules/openxr/scene/openxr_composition_layer.h +++ b/modules/openxr/scene/openxr_composition_layer.h @@ -35,6 +35,7 @@ #include "scene/3d/node_3d.h" +class JavaObject; class MeshInstance3D; class Mesh; class OpenXRAPI; @@ -45,7 +46,12 @@ class SubViewport; class OpenXRCompositionLayer : public Node3D { GDCLASS(OpenXRCompositionLayer, Node3D); + XrCompositionLayerBaseHeader *composition_layer_base_header = nullptr; + OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr; + SubViewport *layer_viewport = nullptr; + bool use_android_surface = false; + Size2i android_surface_size = Size2i(1024, 1024); bool enable_hole_punch = false; MeshInstance3D *fallback = nullptr; bool should_update_fallback_mesh = false; @@ -58,10 +64,12 @@ class OpenXRCompositionLayer : public Node3D { void _reset_fallback_material(); void _remove_fallback_node(); + void _setup_composition_layer_provider(); + void _clear_composition_layer_provider(); + protected: OpenXRAPI *openxr_api = nullptr; OpenXRCompositionLayerExtension *composition_layer_extension = nullptr; - OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr; static void _bind_methods(); @@ -69,6 +77,7 @@ protected: void _get_property_list(List<PropertyInfo> *p_property_list) const; bool _get(const StringName &p_property, Variant &r_value) const; bool _set(const StringName &p_property, const Variant &p_value); + void _validate_property(PropertyInfo &p_property) const; virtual void _on_openxr_session_begun(); virtual void _on_openxr_session_stopping(); @@ -77,13 +86,23 @@ protected: void update_fallback_mesh(); + XrPosef get_openxr_pose(); + static Vector<OpenXRCompositionLayer *> composition_layer_nodes; bool is_viewport_in_use(SubViewport *p_viewport); + OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer); + public: void set_layer_viewport(SubViewport *p_viewport); SubViewport *get_layer_viewport() const; + void set_use_android_surface(bool p_use_android_surface); + bool get_use_android_surface() const; + + void set_android_surface_size(Size2i p_size); + Size2i get_android_surface_size() const; + void set_enable_hole_punch(bool p_enable); bool get_enable_hole_punch() const; @@ -93,13 +112,13 @@ public: void set_alpha_blend(bool p_alpha_blend); bool get_alpha_blend() const; + Ref<JavaObject> get_android_surface(); bool is_natively_supported() const; virtual PackedStringArray get_configuration_warnings() const override; virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const; - OpenXRCompositionLayer(); ~OpenXRCompositionLayer(); }; diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp index 728ba71006..727586467a 100644 --- a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp @@ -38,20 +38,9 @@ #include "scene/main/viewport.h" #include "scene/resources/mesh.h" -OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() { - composition_layer = { - XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, // type - nullptr, // next - 0, // layerFlags - XR_NULL_HANDLE, // space - XR_EYE_VISIBILITY_BOTH, // eyeVisibility - {}, // subImage - { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose - radius, // radius - central_angle, // centralAngle - aspect_ratio, // aspectRatio - }; - openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); +OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() : + OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) { + XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerCylinder::update_transform)); } OpenXRCompositionLayerCylinder::~OpenXRCompositionLayerCylinder() { @@ -131,14 +120,15 @@ Ref<Mesh> OpenXRCompositionLayerCylinder::_create_fallback_mesh() { void OpenXRCompositionLayerCylinder::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - Transform3D transform = get_transform(); - Quaternion quat(transform.basis.orthonormalized()); - composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; - composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + update_transform(); } break; } } +void OpenXRCompositionLayerCylinder::update_transform() { + composition_layer.pose = get_openxr_pose(); +} + void OpenXRCompositionLayerCylinder::set_radius(float p_radius) { ERR_FAIL_COND(p_radius <= 0); radius = p_radius; diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.h b/modules/openxr/scene/openxr_composition_layer_cylinder.h index bb1d242267..a701575972 100644 --- a/modules/openxr/scene/openxr_composition_layer_cylinder.h +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.h @@ -38,7 +38,18 @@ class OpenXRCompositionLayerCylinder : public OpenXRCompositionLayer { GDCLASS(OpenXRCompositionLayerCylinder, OpenXRCompositionLayer); - XrCompositionLayerCylinderKHR composition_layer; + XrCompositionLayerCylinderKHR composition_layer = { + XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, // type + nullptr, // next + 0, // layerFlags + XR_NULL_HANDLE, // space + XR_EYE_VISIBILITY_BOTH, // eyeVisibility + {}, // subImage + { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose + 1.0, // radius + Math_PI / 2.0, // centralAngle + 1.0, // aspectRatio + }; float radius = 1.0; float aspect_ratio = 1.0; @@ -50,6 +61,8 @@ protected: void _notification(int p_what); + void update_transform(); + virtual Ref<Mesh> _create_fallback_mesh() override; public: diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.cpp b/modules/openxr/scene/openxr_composition_layer_equirect.cpp index 14cfea8da6..2fce26c965 100644 --- a/modules/openxr/scene/openxr_composition_layer_equirect.cpp +++ b/modules/openxr/scene/openxr_composition_layer_equirect.cpp @@ -38,21 +38,9 @@ #include "scene/main/viewport.h" #include "scene/resources/mesh.h" -OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() { - composition_layer = { - XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, // type - nullptr, // next - 0, // layerFlags - XR_NULL_HANDLE, // space - XR_EYE_VISIBILITY_BOTH, // eyeVisibility - {}, // subImage - { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose - radius, // radius - central_horizontal_angle, // centralHorizontalAngle - upper_vertical_angle, // upperVerticalAngle - -lower_vertical_angle, // lowerVerticalAngle - }; - openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); +OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() : + OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) { + XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerEquirect::update_transform)); } OpenXRCompositionLayerEquirect::~OpenXRCompositionLayerEquirect() { @@ -139,14 +127,15 @@ Ref<Mesh> OpenXRCompositionLayerEquirect::_create_fallback_mesh() { void OpenXRCompositionLayerEquirect::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - Transform3D transform = get_transform(); - Quaternion quat(transform.basis.orthonormalized()); - composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; - composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + update_transform(); } break; } } +void OpenXRCompositionLayerEquirect::update_transform() { + composition_layer.pose = get_openxr_pose(); +} + void OpenXRCompositionLayerEquirect::set_radius(float p_radius) { ERR_FAIL_COND(p_radius <= 0); radius = p_radius; diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.h b/modules/openxr/scene/openxr_composition_layer_equirect.h index 66f8b0a91c..45a65fe5aa 100644 --- a/modules/openxr/scene/openxr_composition_layer_equirect.h +++ b/modules/openxr/scene/openxr_composition_layer_equirect.h @@ -38,7 +38,19 @@ class OpenXRCompositionLayerEquirect : public OpenXRCompositionLayer { GDCLASS(OpenXRCompositionLayerEquirect, OpenXRCompositionLayer); - XrCompositionLayerEquirect2KHR composition_layer; + XrCompositionLayerEquirect2KHR composition_layer = { + XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, // type + nullptr, // next + 0, // layerFlags + XR_NULL_HANDLE, // space + XR_EYE_VISIBILITY_BOTH, // eyeVisibility + {}, // subImage + { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose + 1.0, // radius + Math_PI / 2.0, // centralHorizontalAngle + Math_PI / 4.0, // upperVerticalAngle + -Math_PI / 4.0, // lowerVerticalAngle + }; float radius = 1.0; float central_horizontal_angle = Math_PI / 2.0; @@ -51,6 +63,8 @@ protected: void _notification(int p_what); + void update_transform(); + virtual Ref<Mesh> _create_fallback_mesh() override; public: diff --git a/modules/openxr/scene/openxr_composition_layer_quad.cpp b/modules/openxr/scene/openxr_composition_layer_quad.cpp index 8c5b8ec26b..4a00fd371e 100644 --- a/modules/openxr/scene/openxr_composition_layer_quad.cpp +++ b/modules/openxr/scene/openxr_composition_layer_quad.cpp @@ -38,18 +38,9 @@ #include "scene/main/viewport.h" #include "scene/resources/3d/primitive_meshes.h" -OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() { - composition_layer = { - XR_TYPE_COMPOSITION_LAYER_QUAD, // type - nullptr, // next - 0, // layerFlags - XR_NULL_HANDLE, // space - XR_EYE_VISIBILITY_BOTH, // eyeVisibility - {}, // subImage - { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose - { (float)quad_size.x, (float)quad_size.y }, // size - }; - openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); +OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() : + OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) { + XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerQuad::update_transform)); } OpenXRCompositionLayerQuad::~OpenXRCompositionLayerQuad() { @@ -72,14 +63,15 @@ Ref<Mesh> OpenXRCompositionLayerQuad::_create_fallback_mesh() { void OpenXRCompositionLayerQuad::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - Transform3D transform = get_transform(); - Quaternion quat(transform.basis.orthonormalized()); - composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; - composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + update_transform(); } break; } } +void OpenXRCompositionLayerQuad::update_transform() { + composition_layer.pose = get_openxr_pose(); +} + void OpenXRCompositionLayerQuad::set_quad_size(const Size2 &p_size) { quad_size = p_size; composition_layer.size = { (float)quad_size.x, (float)quad_size.y }; diff --git a/modules/openxr/scene/openxr_composition_layer_quad.h b/modules/openxr/scene/openxr_composition_layer_quad.h index 21bb9b2d85..a4ccfc6d8e 100644 --- a/modules/openxr/scene/openxr_composition_layer_quad.h +++ b/modules/openxr/scene/openxr_composition_layer_quad.h @@ -38,7 +38,16 @@ class OpenXRCompositionLayerQuad : public OpenXRCompositionLayer { GDCLASS(OpenXRCompositionLayerQuad, OpenXRCompositionLayer); - XrCompositionLayerQuad composition_layer; + XrCompositionLayerQuad composition_layer = { + XR_TYPE_COMPOSITION_LAYER_QUAD, // type + nullptr, // next + 0, // layerFlags + XR_NULL_HANDLE, // space + XR_EYE_VISIBILITY_BOTH, // eyeVisibility + {}, // subImage + { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose + { 1.0, 1.0 }, // size + }; Size2 quad_size = Size2(1.0, 1.0); @@ -47,6 +56,8 @@ protected: void _notification(int p_what); + void update_transform(); + virtual Ref<Mesh> _create_fallback_mesh() override; public: diff --git a/modules/openxr/scene/openxr_visibility_mask.cpp b/modules/openxr/scene/openxr_visibility_mask.cpp new file mode 100644 index 0000000000..7214fac327 --- /dev/null +++ b/modules/openxr/scene/openxr_visibility_mask.cpp @@ -0,0 +1,106 @@ +/**************************************************************************/ +/* openxr_visibility_mask.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 "openxr_visibility_mask.h" + +#include "../extensions/openxr_visibility_mask_extension.h" +#include "../openxr_interface.h" +#include "scene/3d/xr_nodes.h" + +void OpenXRVisibilityMask::_bind_methods() { +} + +void OpenXRVisibilityMask::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + OpenXRVisibilityMaskExtension *vis_mask_ext = OpenXRVisibilityMaskExtension::get_singleton(); + if (vis_mask_ext && vis_mask_ext->is_available()) { + set_base(vis_mask_ext->get_mesh()); + } + } break; + case NOTIFICATION_EXIT_TREE: { + set_base(RID()); + } break; + } +} + +void OpenXRVisibilityMask::_on_openxr_session_begun() { + if (is_inside_tree()) { + OpenXRVisibilityMaskExtension *vis_mask_ext = OpenXRVisibilityMaskExtension::get_singleton(); + if (vis_mask_ext && vis_mask_ext->is_available()) { + set_base(vis_mask_ext->get_mesh()); + } + } +} + +void OpenXRVisibilityMask::_on_openxr_session_stopping() { + set_base(RID()); +} + +PackedStringArray OpenXRVisibilityMask::get_configuration_warnings() const { + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); + + if (is_visible() && is_inside_tree()) { + XRCamera3D *camera = Object::cast_to<XRCamera3D>(get_parent()); + if (camera == nullptr) { + warnings.push_back(RTR("OpenXR visibility mask must have an XRCamera3D node as their parent.")); + } + } + + return warnings; +} + +AABB OpenXRVisibilityMask::get_aabb() const { + AABB ret; + + // Make sure it's always visible, this is positioned through its shader. + ret.position = Vector3(-1000.0, -1000.0, -1000.0); + ret.size = Vector3(2000.0, 2000.0, 2000.0); + + return ret; +} + +OpenXRVisibilityMask::OpenXRVisibilityMask() { + Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->connect("session_begun", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_begun)); + openxr_interface->connect("session_stopping", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_stopping)); + } + + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(get_instance(), RS::SHADOW_CASTING_SETTING_OFF); +} + +OpenXRVisibilityMask::~OpenXRVisibilityMask() { + Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->disconnect("session_begun", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_begun)); + openxr_interface->disconnect("session_stopping", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_stopping)); + } +} diff --git a/modules/openxr/scene/openxr_visibility_mask.h b/modules/openxr/scene/openxr_visibility_mask.h new file mode 100644 index 0000000000..0acb30fc0a --- /dev/null +++ b/modules/openxr/scene/openxr_visibility_mask.h @@ -0,0 +1,56 @@ +/**************************************************************************/ +/* openxr_visibility_mask.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 OPENXR_VISIBILITY_MASK_H +#define OPENXR_VISIBILITY_MASK_H + +#include "scene/3d/visual_instance_3d.h" + +class OpenXRVisibilityMask : public VisualInstance3D { + GDCLASS(OpenXRVisibilityMask, VisualInstance3D); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + void _on_openxr_session_begun(); + void _on_openxr_session_stopping(); + +public: + virtual PackedStringArray get_configuration_warnings() const override; + + virtual AABB get_aabb() const override; + + OpenXRVisibilityMask(); + ~OpenXRVisibilityMask(); +}; + +#endif // OPENXR_VISIBILITY_MASK_H diff --git a/modules/raycast/config.py b/modules/raycast/config.py index 0fd35af528..2fa0058f88 100644 --- a/modules/raycast/config.py +++ b/modules/raycast/config.py @@ -1,6 +1,6 @@ def can_build(env, platform): # Supported architectures and platforms depend on the Embree library. - if env["arch"] == "arm64" and platform == "windows": + if env["arch"] == "arm64" and platform == "windows" and env.msvc: return False if env["arch"] in ["x86_64", "arm64", "wasm32"]: return True diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py index c179060365..c4fff330c9 100644 --- a/modules/raycast/godot_update_embree.py +++ b/modules/raycast/godot_update_embree.py @@ -4,8 +4,8 @@ import re import shutil import stat import subprocess -from types import TracebackType -from typing import Any, Callable, Tuple, Type +import sys +from typing import Any, Callable git_tag = "v4.3.1" @@ -100,9 +100,7 @@ subprocess.run(["git", "checkout", git_tag]) commit_hash = str(subprocess.check_output(["git", "rev-parse", "HEAD"], universal_newlines=True)).strip() -def on_rm_error( - function: Callable[..., Any], path: str, excinfo: Tuple[Type[Exception], Exception, TracebackType] -) -> None: +def on_rm_error(function: Callable[..., Any], path: str, excinfo: Exception) -> None: """ Error handler for `shutil.rmtree()`. @@ -113,10 +111,12 @@ def on_rm_error( os.unlink(path) -# 3.12 Python and beyond should replace `onerror` with `onexc`. # We remove the .git directory because it contains # a lot of read-only files that are problematic on Windows. -shutil.rmtree(".git", onerror=on_rm_error) +if sys.version_info >= (3, 12): + shutil.rmtree(".git", onexc=on_rm_error) +else: + shutil.rmtree(".git", onerror=on_rm_error) # type: ignore all_files = set(cpp_files) diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml index ab74fce3a9..e12dc43b6f 100644 --- a/modules/regex/doc_classes/RegEx.xml +++ b/modules/regex/doc_classes/RegEx.xml @@ -58,15 +58,17 @@ <method name="compile"> <return type="int" enum="Error" /> <param index="0" name="pattern" type="String" /> + <param index="1" name="show_error" type="bool" default="true" /> <description> - Compiles and assign the search pattern to use. Returns [constant OK] if the compilation is successful. If an error is encountered, details are printed to standard output and an error is returned. + Compiles and assign the search pattern to use. Returns [constant OK] if the compilation is successful. If compilation fails, returns [constant FAILED] and when [param show_error] is [code]true[/code], details are printed to standard output. </description> </method> <method name="create_from_string" qualifiers="static"> <return type="RegEx" /> <param index="0" name="pattern" type="String" /> + <param index="1" name="show_error" type="bool" default="true" /> <description> - Creates and compiles a new [RegEx] object. + Creates and compiles a new [RegEx] object. See also [method compile]. </description> </method> <method name="get_group_count" qualifiers="const"> diff --git a/modules/regex/icons/RegEx.svg b/modules/regex/icons/RegEx.svg index 4df26f41c0..ba232f6f0a 100644 --- a/modules/regex/icons/RegEx.svg +++ b/modules/regex/icons/RegEx.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 14h3v-3H2zM6.561 2.855a21 21 0 0 1 2.82 1.185A21 21 0 0 1 9.137 1h1.77a21 21 0 0 1-.28 3.027 21 21 0 0 1 2.88-1.171l.562 1.733a21 21 0 0 1-3.04.684 21 21 0 0 1 2.1 2.307l-1.465 1.037a21 21 0 0 1-1.672-2.624 21 21 0 0 1-1.587 2.624L6.965 7.58a21 21 0 0 1 2.026-2.308A21 21 0 0 1 6 4.59z" fill="#e0e0e0"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M2 14h3v-3H2zM6.561 2.855a21 21 0 0 1 2.82 1.185A21 21 0 0 1 9.137 1h1.77a21 21 0 0 1-.28 3.027 21 21 0 0 1 2.88-1.171l.562 1.733a21 21 0 0 1-3.04.684 21 21 0 0 1 2.1 2.307l-1.465 1.037a21 21 0 0 1-1.672-2.624 21 21 0 0 1-1.587 2.624L6.965 7.58a21 21 0 0 1 2.026-2.308A21 21 0 0 1 6 4.59z"/></svg>
\ No newline at end of file diff --git a/modules/regex/icons/RegExMatch.svg b/modules/regex/icons/RegExMatch.svg index 889cf6cc8a..626ff36691 100644 --- a/modules/regex/icons/RegExMatch.svg +++ b/modules/regex/icons/RegExMatch.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M5 13h2v-2H5zm2.5-8a14 14 0 0 1 1.88.79 14 14 0 0 1-.163-2.027h1.18a14 14 0 0 1-.186 2.018 14 14 0 0 1 1.92-.78l.374 1.155a14 14 0 0 1-2.026.456 14 14 0 0 1 1.4 1.538l-.977.691a14 14 0 0 1-1.115-1.75 14 14 0 0 1-1.058 1.75l-.96-.691A14 14 0 0 1 9.12 6.61a14 14 0 0 1-1.993-.454zM1.67 2C0 5 0 11 1.67 14h2C2 11 2 5 3.67 2zm10.66 0c1.67 3 1.67 9 0 12h2c1.67-3 1.67-9 0-12z" fill="#e0e0e0"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M5 13h2v-2H5zm2.5-8a14 14 0 0 1 1.88.79 14 14 0 0 1-.163-2.027h1.18a14 14 0 0 1-.186 2.018 14 14 0 0 1 1.92-.78l.374 1.155a14 14 0 0 1-2.026.456 14 14 0 0 1 1.4 1.538l-.977.691a14 14 0 0 1-1.115-1.75 14 14 0 0 1-1.058 1.75l-.96-.691A14 14 0 0 1 9.12 6.61a14 14 0 0 1-1.993-.454zM1.67 2C0 5 0 11 1.67 14h2C2 11 2 5 3.67 2zm10.66 0c1.67 3 1.67 9 0 12h2c1.67-3 1.67-9 0-12z"/></svg>
\ No newline at end of file diff --git a/modules/regex/regex.compat.inc b/modules/regex/regex.compat.inc new file mode 100644 index 0000000000..0c380655a4 --- /dev/null +++ b/modules/regex/regex.compat.inc @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* regex.compat.inc */ +/**************************************************************************/ +/* 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 DISABLE_DEPRECATED + +Ref<RegEx> RegEx::_create_from_string_bind_compat_95212(const String &p_pattern) { + return create_from_string(p_pattern, true); +} + +Error RegEx::_compile_bind_compat_95212(const String &p_pattern) { + return compile(p_pattern, true); +} + +void RegEx::_bind_compatibility_methods() { + ClassDB::bind_compatibility_static_method("RegEx", D_METHOD("create_from_string", "pattern"), &RegEx::_create_from_string_bind_compat_95212); + ClassDB::bind_compatibility_method(D_METHOD("compile", "pattern"), &RegEx::_compile_bind_compat_95212); +} + +#endif diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index 9f34a6ca6a..9c366408a0 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "regex.h" +#include "regex.compat.inc" #include "core/os/memory.h" @@ -53,8 +54,8 @@ int RegExMatch::_find(const Variant &p_name) const { return -1; } return i; - } else if (p_name.get_type() == Variant::STRING || p_name.get_type() == Variant::STRING_NAME) { - HashMap<String, int>::ConstIterator found = names.find((String)p_name); + } else if (p_name.is_string()) { + HashMap<String, int>::ConstIterator found = names.find(p_name); if (found) { return found->value; } @@ -161,10 +162,10 @@ void RegEx::_pattern_info(uint32_t what, void *where) const { pcre2_pattern_info_32((pcre2_code_32 *)code, what, where); } -Ref<RegEx> RegEx::create_from_string(const String &p_pattern) { +Ref<RegEx> RegEx::create_from_string(const String &p_pattern, bool p_show_error) { Ref<RegEx> ret; ret.instantiate(); - ret->compile(p_pattern); + ret->compile(p_pattern, p_show_error); return ret; } @@ -175,7 +176,7 @@ void RegEx::clear() { } } -Error RegEx::compile(const String &p_pattern) { +Error RegEx::compile(const String &p_pattern, bool p_show_error) { pattern = p_pattern; clear(); @@ -192,10 +193,12 @@ Error RegEx::compile(const String &p_pattern) { pcre2_compile_context_free_32(cctx); if (!code) { - PCRE2_UCHAR32 buf[256]; - pcre2_get_error_message_32(err, buf, 256); - String message = String::num(offset) + ": " + String((const char32_t *)buf); - ERR_PRINT(message.utf8()); + if (p_show_error) { + PCRE2_UCHAR32 buf[256]; + pcre2_get_error_message_32(err, buf, 256); + String message = String::num(offset) + ": " + String((const char32_t *)buf); + ERR_PRINT(message.utf8()); + } return FAILED; } return OK; @@ -395,10 +398,10 @@ RegEx::~RegEx() { } void RegEx::_bind_methods() { - ClassDB::bind_static_method("RegEx", D_METHOD("create_from_string", "pattern"), &RegEx::create_from_string); + ClassDB::bind_static_method("RegEx", D_METHOD("create_from_string", "pattern", "show_error"), &RegEx::create_from_string, DEFVAL(true)); ClassDB::bind_method(D_METHOD("clear"), &RegEx::clear); - ClassDB::bind_method(D_METHOD("compile", "pattern"), &RegEx::compile); + ClassDB::bind_method(D_METHOD("compile", "pattern", "show_error"), &RegEx::compile, DEFVAL(true)); ClassDB::bind_method(D_METHOD("search", "subject", "offset", "end"), &RegEx::search, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("search_all", "subject", "offset", "end"), &RegEx::search_all, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("sub", "subject", "replacement", "all", "offset", "end"), &RegEx::sub, DEFVAL(false), DEFVAL(0), DEFVAL(-1)); diff --git a/modules/regex/regex.h b/modules/regex/regex.h index 13476d69de..cb8b0459ad 100644 --- a/modules/regex/regex.h +++ b/modules/regex/regex.h @@ -81,11 +81,17 @@ class RegEx : public RefCounted { protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + static Ref<RegEx> _create_from_string_bind_compat_95212(const String &p_pattern); + Error _compile_bind_compat_95212(const String &p_pattern); + static void _bind_compatibility_methods(); +#endif + public: - static Ref<RegEx> create_from_string(const String &p_pattern); + static Ref<RegEx> create_from_string(const String &p_pattern, bool p_show_error = true); void clear(); - Error compile(const String &p_pattern); + Error compile(const String &p_pattern, bool p_show_error = true); Ref<RegExMatch> search(const String &p_subject, int p_offset = 0, int p_end = -1) const; TypedArray<RegExMatch> search_all(const String &p_subject, int p_offset = 0, int p_end = -1) const; diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h index af58e2487b..7e8e456341 100644 --- a/modules/regex/tests/test_regex.h +++ b/modules/regex/tests/test_regex.h @@ -80,32 +80,32 @@ TEST_CASE("[RegEx] Searching") { REQUIRE(re.is_valid()); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "ea"); match = re.search(s, 1, 2); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "e"); match = re.search(s, 2, 4); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "a"); match = re.search(s, 3, 5); - CHECK(match == nullptr); + CHECK(match.is_null()); match = re.search(s, 6, 2); - CHECK(match == nullptr); + CHECK(match.is_null()); const Array all_results = re.search_all(s); CHECK(all_results.size() == 2); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "ea"); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "i"); CHECK(re.compile(numerics) == OK); CHECK(re.is_valid()); - CHECK(re.search(s) == nullptr); + CHECK(re.search(s).is_null()); CHECK(re.search_all(s).size() == 0); } @@ -168,7 +168,7 @@ TEST_CASE("[RegEx] Uninitialized use") { RegEx re; ERR_PRINT_OFF; - CHECK(re.search(s) == nullptr); + CHECK(re.search(s).is_null()); CHECK(re.search_all(s).size() == 0); CHECK(re.sub(s, "") == ""); CHECK(re.get_group_count() == 0); @@ -237,10 +237,10 @@ TEST_CASE("[RegEx] Invalid end position") { const Array all_results = re.search_all(s, 0, 10); CHECK(all_results.size() == 2); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("o")); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("o")); CHECK(re.sub(s, "", true, 0, 10) == "Gdt"); @@ -251,7 +251,7 @@ TEST_CASE("[RegEx] Get match string list") { RegEx re("(Go)(dot)"); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); PackedStringArray result; result.append("Godot"); result.append("Go"); @@ -265,14 +265,14 @@ TEST_CASE("[RegEx] Match start and end positions") { RegEx re1("pattern"); REQUIRE(re1.is_valid()); Ref<RegExMatch> match = re1.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 6); CHECK(match->get_end(0) == 13); RegEx re2("(?<vowel>[aeiou])"); REQUIRE(re2.is_valid()); match = re2.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start("vowel") == 2); CHECK(match->get_end("vowel") == 3); } @@ -307,7 +307,7 @@ TEST_CASE("[RegEx] Simple lookahead") { RegEx re("o(?=t)"); REQUIRE(re.is_valid()); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 3); CHECK(match->get_end(0) == 4); } @@ -325,12 +325,12 @@ TEST_CASE("[RegEx] Lookahead groups empty matches") { CHECK(all_results.size() == 2); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("")); CHECK(match->get_string(1) == String("12")); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("")); CHECK(match->get_string(1) == String("2")); } @@ -341,7 +341,7 @@ TEST_CASE("[RegEx] Simple lookbehind") { RegEx re("(?<=d)o"); REQUIRE(re.is_valid()); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 3); CHECK(match->get_end(0) == 4); } @@ -355,22 +355,22 @@ TEST_CASE("[RegEx] Simple lookbehind search all") { CHECK(all_results.size() == 4); Ref<RegExMatch> match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 1); CHECK(match->get_end(0) == 2); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 3); CHECK(match->get_end(0) == 4); match = all_results[2]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 7); CHECK(match->get_end(0) == 8); match = all_results[3]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 9); CHECK(match->get_end(0) == 10); } @@ -386,7 +386,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") { CHECK(all_results.size() == 3); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 2); CHECK(match->get_end(0) == 2); CHECK(match->get_start(1) == 1); @@ -395,7 +395,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") { CHECK(match->get_string(1) == String("b")); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 6); CHECK(match->get_end(0) == 6); CHECK(match->get_start(1) == 5); @@ -404,7 +404,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") { CHECK(match->get_string(1) == String("b")); match = all_results[2]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 8); CHECK(match->get_end(0) == 8); CHECK(match->get_start(1) == 7); diff --git a/modules/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp index fba76621d6..3841ba8db1 100644 --- a/modules/squish/image_decompress_squish.cpp +++ b/modules/squish/image_decompress_squish.cpp @@ -40,7 +40,7 @@ void image_decompress_squish(Image *p_image) { Image::Format target_format = Image::FORMAT_RGBA8; Vector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->get_mipmap_count(); data.resize(target_size); @@ -77,10 +77,11 @@ void image_decompress_squish(Image *p_image) { } for (int i = 0; i <= mm_count; i++) { - int src_ofs = 0, mipmap_size = 0, mipmap_w = 0, mipmap_h = 0; + 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); - int dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); + int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags); w >>= 1; diff --git a/modules/svg/SCsub b/modules/svg/SCsub index 0e21c1e6d7..a32be0e41a 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -26,7 +26,6 @@ thirdparty_sources = [ "src/loaders/raw/tvgRawLoader.cpp", # image loaders "src/loaders/external_png/tvgPngLoader.cpp", - "src/loaders/external_webp/tvgWebpLoader.cpp", "src/loaders/jpg/tvgJpgd.cpp", "src/loaders/jpg/tvgJpgLoader.cpp", # renderer common @@ -59,6 +58,10 @@ thirdparty_sources = [ "src/renderer/sw_engine/tvgSwStroke.cpp", ] +if env["module_webp_enabled"]: + thirdparty_sources += ["src/loaders/external_webp/tvgWebpLoader.cpp"] + env_svg.Append(CPPDEFINES=["THORVG_WEBP_LOADER_SUPPORT"]) + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_svg.Prepend(CPPPATH=[thirdparty_dir + "inc"]) @@ -76,12 +79,15 @@ env_thirdparty.Prepend( thirdparty_dir + "src/renderer/sw_engine", thirdparty_dir + "src/loaders/raw", thirdparty_dir + "src/loaders/external_png", - thirdparty_dir + "src/loaders/external_webp", thirdparty_dir + "src/loaders/jpg", - "#thirdparty/libpng", - "#thirdparty/libwebp/src", ] ) +if env["builtin_libpng"]: + env_thirdparty.Prepend(CPPPATH=["#thirdparty/libpng"]) +if env["module_webp_enabled"]: + env_thirdparty.Prepend(CPPPATH=[thirdparty_dir + "src/loaders/external_webp"]) + if env["builtin_libwebp"]: + env_thirdparty.Prepend(CPPPATH=["#thirdparty/libwebp/src"]) env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) env.modules_sources += thirdparty_obj diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 68a5d499d4..4112b81622 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -142,7 +142,14 @@ if env["builtin_harfbuzz"]: env_harfbuzz.Append(CCFLAGS=["-DHAVE_ICU"]) if env["builtin_icu4c"]: env_harfbuzz.Prepend(CPPPATH=["#thirdparty/icu4c/common/", "#thirdparty/icu4c/i18n/"]) - env_harfbuzz.Append(CCFLAGS=["-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DHAVE_ICU_BUILTIN"]) + env_harfbuzz.Append( + CCFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", + "-DU_HAVE_LIB_SUFFIX=1", + "-DU_LIB_SUFFIX_C_NAME=_godot", + "-DHAVE_ICU_BUILTIN", + ] + ) if freetype_enabled: env_harfbuzz.Append( @@ -499,6 +506,7 @@ if env["builtin_icu4c"]: ) env_text_server_adv.Append( CXXFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DICU_DATA_NAME=" + icu_data_name, diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 018984a52f..effed1e772 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -1,19 +1,27 @@ #!/usr/bin/env python import atexit import sys -import methods import time +import methods + # Enable ANSI escape code support on Windows 10 and later (for colored console output). # <https://github.com/python/cpython/issues/73245> -if sys.platform == "win32": - from ctypes import windll, c_int, byref - - stdout_handle = windll.kernel32.GetStdHandle(c_int(-11)) - mode = c_int(0) - windll.kernel32.GetConsoleMode(c_int(stdout_handle), byref(mode)) - mode = c_int(mode.value | 4) - windll.kernel32.SetConsoleMode(c_int(stdout_handle), mode) +if sys.stdout.isatty() and sys.platform == "win32": + try: + from ctypes import WinError, byref, windll # type: ignore + from ctypes.wintypes import DWORD # type: ignore + + stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11)) + mode = DWORD(0) + if not windll.kernel32.GetConsoleMode(stdout_handle, byref(mode)): + raise WinError() + mode = DWORD(mode.value | 4) + if not windll.kernel32.SetConsoleMode(stdout_handle, mode): + raise WinError() + except Exception as e: + methods._colorize = False + methods.print_error(f"Failed to enable ANSI escape code support, disabling color output.\n{e}") # For the reference: # - CCFLAGS are compilation flags shared between C and C++ @@ -53,8 +61,8 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: thirdparty_tvg_dir = "../../../thirdparty/thorvg/" thirdparty_tvg_sources = [ # common - "src/common/tvgBezier.cpp", "src/common/tvgCompressor.cpp", + "src/common/tvgLines.cpp", "src/common/tvgMath.cpp", "src/common/tvgStr.cpp", # SVG parser @@ -65,6 +73,7 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", "src/loaders/raw/tvgRawLoader.cpp", + # image loaders "src/loaders/external_png/tvgPngLoader.cpp", "src/loaders/jpg/tvgJpgd.cpp", "src/loaders/jpg/tvgJpgLoader.cpp", @@ -109,6 +118,7 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "../../../thirdparty/thorvg/src/loaders/raw", "../../../thirdparty/thorvg/src/loaders/external_png", "../../../thirdparty/thorvg/src/loaders/jpg", + "../../../thirdparty/libpng", ] ) @@ -405,6 +415,7 @@ if env["platform"] == "android" or env["platform"] == "linuxbsd": env_harfbuzz.Append( CCFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DHAVE_ICU_BUILTIN", @@ -713,7 +724,7 @@ if env["static_icu_data"]: env.Append(CXXFLAGS=["-DICU_STATIC_DATA"]) env.Append(CPPPATH=["../../../thirdparty/icu4c/"]) else: - thirdparty_sources += ["../icu_data/icudata_stub.cpp"] + thirdparty_icu_sources += ["../icu_data/icudata_stub.cpp"] env_icu.Append(CPPPATH=["../../../thirdparty/icu4c/common/", "../../../thirdparty/icu4c/i18n/"]) env_icu.Append( @@ -736,6 +747,7 @@ env_icu.Append( ) env.Append( CXXFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DICU_DATA_NAME=" + icu_data_name, diff --git a/modules/text_server_adv/gdextension_build/methods.py b/modules/text_server_adv/gdextension_build/methods.py index 3453c3e8f0..43bf56abfe 100644 --- a/modules/text_server_adv/gdextension_build/methods.py +++ b/modules/text_server_adv/gdextension_build/methods.py @@ -81,9 +81,9 @@ def disable_warnings(self): self.Append(CCFLAGS=["/w"]) self.Append(CFLAGS=["/w"]) self.Append(CXXFLAGS=["/w"]) - self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not x in warn_flags] - self["CFLAGS"] = [x for x in self["CFLAGS"] if not x in warn_flags] - self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if not x in warn_flags] + self["CCFLAGS"] = [x for x in self["CCFLAGS"] if x not in warn_flags] + self["CFLAGS"] = [x for x in self["CFLAGS"] if x not in warn_flags] + self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if x not in warn_flags] else: self.Append(CCFLAGS=["-w"]) self.Append(CFLAGS=["-w"]) @@ -117,31 +117,31 @@ def make_icu_data(target, source, env): def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resource/", exist_ok=True) with open(f"{target}/Resource/Info.plist", "w", encoding="utf-8", newline="\n") as f: - f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n') - f.write( - f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' - ) - f.write(f'<plist version="1.0">\n') - f.write(f"<dict>\n") - f.write(f"\t<key>CFBundleExecutable</key>\n") - f.write(f"\t<string>{binary_name}</string>\n") - f.write(f"\t<key>CFBundleIdentifier</key>\n") - f.write(f"\t<string>{identifier}</string>\n") - f.write(f"\t<key>CFBundleInfoDictionaryVersion</key>\n") - f.write(f"\t<string>6.0</string>\n") - f.write(f"\t<key>CFBundleName</key>\n") - f.write(f"\t<string>{name}</string>\n") - f.write(f"\t<key>CFBundlePackageType</key>\n") - f.write(f"\t<string>FMWK</string>\n") - f.write(f"\t<key>CFBundleShortVersionString</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>CFBundleSupportedPlatforms</key>\n") - f.write(f"\t<array>\n") - f.write(f"\t\t<string>MacOSX</string>\n") - f.write(f"\t</array>\n") - f.write(f"\t<key>CFBundleVersion</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>LSMinimumSystemVersion</key>\n") - f.write(f"\t<string>10.14</string>\n") - f.write(f"</dict>\n") - f.write(f"</plist>\n") + f.write(f"""\ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleExecutable</key> + <string>{binary_name}</string> + <key>CFBundleIdentifier</key> + <string>{identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>{name}</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0</string> + <key>CFBundleSupportedPlatforms</key> + <array> + <string>MacOSX</string> + </array> + <key>CFBundleVersion</key> + <string>1.0.0</string> + <key>LSMinimumSystemVersion</key> + <string>10.14</string> +</dict> +</plist> +""") diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 09a037fd28..3322300dda 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -51,7 +51,7 @@ using namespace godot; #include "core/error/error_macros.h" #include "core/object/worker_thread_pool.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. @@ -136,11 +136,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_h_advance(hb_font_t *p_font, vo return 0; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return 0; } - return bm_font->face->glyph_map[p_glyph].advance.x * 64; + return E->value.advance.x * 64; } hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data) { @@ -150,11 +151,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, vo return 0; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return 0; } - return -bm_font->face->glyph_map[p_glyph].advance.y * 64; + return -E->value.advance.y * 64; } hb_position_t TextServerAdvanced::_bmp_get_glyph_h_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data) { @@ -178,11 +180,12 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_v_origin(hb_font_t *p_font, void *p return false; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return false; } - *r_x = bm_font->face->glyph_map[p_glyph].advance.x * 32; + *r_x = E->value.advance.x * 32; *r_y = -bm_font->face->ascent * 64; return true; @@ -195,14 +198,15 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_extents(hb_font_t *p_font, void *p_ return false; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return false; } r_extents->x_bearing = 0; r_extents->y_bearing = 0; - r_extents->width = bm_font->face->glyph_map[p_glyph].rect.size.x * 64; - r_extents->height = bm_font->face->glyph_map[p_glyph].rect.size.y * 64; + r_extents->width = E->value.rect.size.x * 64; + r_extents->height = E->value.rect.size.y * 64; return true; } @@ -851,7 +855,7 @@ _FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_ { // Zero texture. uint8_t *w = tex.image->ptrw(); - ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.image->data_size(), ret); + ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.image->get_data_size(), ret); // Initialize the texture to all-white pixels to prevent artifacts when the // font is displayed at a non-default scale with filtering enabled. if (p_color_size == 2) { @@ -1040,7 +1044,7 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_msdf( for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { int ofs = ((i + tex_pos.y + p_rect_margin * 2) * tex.texture_w + j + tex_pos.x + p_rect_margin * 2) * 4; - ERR_FAIL_COND_V(ofs >= tex.image->data_size(), FontGlyph()); + ERR_FAIL_COND_V(ofs >= tex.image->get_data_size(), FontGlyph()); wr[ofs + 0] = (uint8_t)(CLAMP(image(j, i)[0] * 256.f, 0.f, 255.f)); wr[ofs + 1] = (uint8_t)(CLAMP(image(j, i)[1] * 256.f, 0.f, 255.f)); wr[ofs + 2] = (uint8_t)(CLAMP(image(j, i)[2] * 256.f, 0.f, 255.f)); @@ -1118,7 +1122,7 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { int ofs = ((i + tex_pos.y + p_rect_margin * 2) * tex.texture_w + j + tex_pos.x + p_rect_margin * 2) * color_size; - ERR_FAIL_COND_V(ofs >= tex.image->data_size(), FontGlyph()); + ERR_FAIL_COND_V(ofs >= tex.image->get_data_size(), FontGlyph()); switch (p_bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: { int byte = i * p_bitmap.pitch + (j >> 3); @@ -1188,18 +1192,21 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma /* Font Cache */ /*************************************************************************/ -_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { - ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); +_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const { + FontForSizeAdvanced *fd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false); int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts. - FontForSizeAdvanced *fd = p_font_data->cache[p_size]; - if (fd->glyph_map.has(p_glyph)) { - return fd->glyph_map[p_glyph].found; + HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph); + if (E) { + r_glyph = E->value; + return E->value.found; } if (glyph_index == 0) { // Non graphical or invalid glyph, do not render. - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return true; } @@ -1235,7 +1242,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, int error = FT_Load_Glyph(fd->face, glyph_index, flags); if (error) { - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } @@ -1339,17 +1347,22 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, cleanup_stroker: FT_Stroker_Done(stroker); } - fd->glyph_map[p_glyph] = gl; + E = fd->glyph_map.insert(p_glyph, gl); + r_glyph = E->value; return gl.found; } #endif - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } -_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const { +_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const { ERR_FAIL_COND_V(p_size.x <= 0, false); - if (p_font_data->cache.has(p_size)) { + + HashMap<Vector2i, FontForSizeAdvanced *>::Iterator E = p_font_data->cache.find(p_size); + if (E) { + r_cache_for_size = E->value; return true; } @@ -1840,7 +1853,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f // Init bitmap font. fd->hb_handle = _bmp_font_create(fd, nullptr); } - p_font_data->cache[p_size] = fd; + p_font_data->cache.insert(p_size, fd); + r_cache_for_size = fd; return true; } @@ -1864,9 +1878,10 @@ hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), nullptr); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), nullptr); - return fd->cache[size]->hb_handle; + return ffsd->hb_handle; } RID TextServerAdvanced::_create_font() { @@ -1989,7 +2004,8 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_flags = p_style; } @@ -1999,7 +2015,8 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); return fd->style_flags; } @@ -2009,7 +2026,8 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_name = p_name; } @@ -2019,7 +2037,8 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->style_name; } @@ -2029,7 +2048,8 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->weight = CLAMP(p_weight, 100, 999); } @@ -2039,7 +2059,8 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400); return fd->weight; } @@ -2049,7 +2070,8 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->stretch = CLAMP(p_stretch, 50, 200); } @@ -2059,7 +2081,8 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100); return fd->stretch; } @@ -2069,7 +2092,8 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->font_name = p_name; } @@ -2079,7 +2103,8 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->font_name; } @@ -2089,9 +2114,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); - hb_face_t *hb_face = hb_font_get_face(fd->cache[size]->hb_handle); + hb_face_t *hb_face = hb_font_get_face(ffsd->hb_handle); unsigned int num_entries = 0; const hb_ot_name_entry_t *names = hb_ot_name_list_names(hb_face, &num_entries); @@ -2468,7 +2494,7 @@ int64_t TextServerAdvanced::_font_get_spacing(const RID &p_font_rid, SpacingType } } -void TextServerAdvanced::_font_set_baseline_offset(const RID &p_font_rid, float p_baseline_offset) { +void TextServerAdvanced::_font_set_baseline_offset(const RID &p_font_rid, double p_baseline_offset) { FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); if (fdv) { if (fdv->baseline_offset != p_baseline_offset) { @@ -2486,7 +2512,7 @@ void TextServerAdvanced::_font_set_baseline_offset(const RID &p_font_rid, float } } -float TextServerAdvanced::_font_get_baseline_offset(const RID &p_font_rid) const { +double TextServerAdvanced::_font_get_baseline_offset(const RID &p_font_rid) const { FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); if (fdv) { return fdv->baseline_offset; @@ -2599,8 +2625,9 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->ascent = p_ascent; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->ascent = p_ascent; } double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { @@ -2610,18 +2637,19 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size; + return ffsd->ascent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->ascent; + return ffsd->ascent; } } @@ -2631,8 +2659,9 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->descent = p_descent; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->descent = p_descent; } double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { @@ -2642,18 +2671,19 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->descent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size; + return ffsd->descent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->descent; + return ffsd->descent; } } @@ -2664,8 +2694,9 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_position = p_underline_position; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_position = p_underline_position; } double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { @@ -2675,18 +2706,19 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_position * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_position; + return ffsd->underline_position; } } @@ -2697,8 +2729,9 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_thickness = p_underline_thickness; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_thickness = p_underline_thickness; } double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { @@ -2708,18 +2741,19 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_thickness; + return ffsd->underline_thickness; } } @@ -2730,13 +2764,15 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { return; // Do not override scale for dynamic fonts, it's calculated automatically. } #endif - fd->cache[size]->scale = p_scale; + ffsd->scale = p_scale; } double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { @@ -2746,18 +2782,19 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size; + return ffsd->scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size; + return ffsd->scale * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->scale / fd->cache[size]->oversampling; + return ffsd->scale / ffsd->oversampling; } } @@ -2768,9 +2805,10 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); - return fd->cache[size]->textures.size(); + return ffsd->textures.size(); } void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { @@ -2779,8 +2817,9 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->textures.clear(); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->textures.clear(); } void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { @@ -2789,10 +2828,11 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size()); - fd->cache[size]->textures.remove_at(p_texture_index); + ffsd->textures.remove_at(p_texture_index); } void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { @@ -2802,13 +2842,14 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.image = p_image; tex.texture_w = p_image->get_width(); @@ -2829,10 +2870,11 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; return tex.image; } @@ -2843,13 +2885,14 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.shelves.clear(); for (int32_t i = 0; i < p_offsets.size(); i += 4) { tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3])); @@ -2862,10 +2905,11 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; PackedInt32Array ret; ret.resize(tex.shelves.size() * 4); @@ -2887,10 +2931,11 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); PackedInt32Array ret; - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; for (const KeyValue<int32_t, FontGlyph> &E : gl) { ret.push_back(E.key); } @@ -2903,9 +2948,10 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.clear(); + ffsd->glyph_map.clear(); } void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { @@ -2914,9 +2960,10 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.erase(p_glyph); + ffsd->glyph_map.erase(p_glyph); } double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const { @@ -2940,22 +2987,22 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64 MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - Vector2 ea; if (fd->embolden != 0.0) { ea.x = fd->embolden * double(size.x) / 64.0; @@ -2963,17 +3010,17 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64 double scale = _font_get_scale(p_font_rid, p_size); if (fd->msdf) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size; } else { - return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); + return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); } } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) { - return (gl[p_glyph | mod].advance + ea).round(); + return (fgl.advance + ea).round(); } else { - return gl[p_glyph | mod].advance + ea; + return fgl.advance + ea; } } @@ -2984,12 +3031,13 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].advance = p_advance; - gl[p_glyph].found = true; + fgl.advance = p_advance; + fgl.found = true; } Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2999,32 +3047,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.position; + return fgl.rect.position; } } @@ -3035,12 +3083,13 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.position = p_offset; - gl[p_glyph].found = true; + fgl.rect.position = p_offset; + fgl.found = true; } Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3050,32 +3099,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.size; + return fgl.rect.size; } } @@ -3086,12 +3135,13 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.size = p_gl_size; - gl[p_glyph].found = true; + fgl.rect.size = p_gl_size; + fgl.found = true; } Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3101,22 +3151,23 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Rect2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].uv_rect; + return fgl.uv_rect; } void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { @@ -3126,12 +3177,13 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].uv_rect = p_uv_rect; - gl[p_glyph].found = true; + fgl.uv_rect = p_uv_rect; + fgl.found = true; } int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3141,22 +3193,23 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return -1; // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].texture_idx; + return fgl.texture_idx; } void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { @@ -3166,12 +3219,13 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].texture_idx = p_texture_idx; - gl[p_glyph].found = true; + fgl.texture_idx = p_texture_idx; + fgl.found = true; } RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3181,27 +3235,28 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return RID(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3214,7 +3269,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid(); + return ffsd->textures[fgl.texture_idx].texture->get_rid(); } } @@ -3228,27 +3283,28 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Size2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3261,7 +3317,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size(); + return ffsd->textures[fgl.texture_idx].texture->get_size(); } } @@ -3275,7 +3331,8 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); #ifdef MODULE_FREETYPE_ENABLED PackedVector3Array points; @@ -3283,20 +3340,20 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i int32_t index = p_index & 0xffffff; // Remove subpixel shifts. - int error = FT_Load_Glyph(fd->cache[size]->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + int error = FT_Load_Glyph(ffsd->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); ERR_FAIL_COND_V(error, Dictionary()); if (fd->embolden != 0.f) { FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64). - FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength); + FT_Outline_Embolden(&ffsd->face->glyph->outline, strength); } if (fd->transform != Transform2D()) { FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536). - FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat); + FT_Outline_Transform(&ffsd->face->glyph->outline, &mat); } - double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; + double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale; if (fd->msdf) { scale = scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -3306,13 +3363,13 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i scale = scale * Math::round((double)p_size / (double)fd->fixed_size); } } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { - points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); + for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) { + points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i]))); } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) { - contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]); + for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) { + contours.push_back(ffsd->face->glyph->outline.contours[i]); } - bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); + bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); Dictionary out; out["points"] = points; @@ -3331,7 +3388,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>()); TypedArray<Vector2i> ret; for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) { @@ -3347,8 +3405,9 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.clear(); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.clear(); } void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { @@ -3358,8 +3417,9 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.erase(p_glyph_pair); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.erase(p_glyph_pair); } void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { @@ -3369,8 +3429,9 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map[p_glyph_pair] = p_kerning; } Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { @@ -3380,9 +3441,10 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); - const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map; + const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map; if (kern.has(p_glyph_pair)) { if (fd->msdf) { @@ -3398,9 +3460,9 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s } } else { #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { FT_Vector delta; - FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta); + FT_Get_Kerning(ffsd->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta); if (fd->msdf) { return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -3426,14 +3488,15 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { if (p_variation_selector) { - return FT_Face_GetCharVariantIndex(fd->cache[size]->face, p_char, p_variation_selector); + return FT_Face_GetCharVariantIndex(ffsd->face, p_char, p_variation_selector); } else { - return FT_Get_Char_Index(fd->cache[size]->face, p_char); + return FT_Get_Char_Index(ffsd->face, p_char); } } else { return (int64_t)p_char; @@ -3449,23 +3512,24 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->inv_glyph_map.is_empty()) { - FT_Face face = fd->cache[size]->face; + if (ffsd->inv_glyph_map.is_empty()) { + FT_Face face = ffsd->face; FT_UInt gindex; FT_ULong charcode = FT_Get_First_Char(face, &gindex); while (gindex != 0) { if (charcode != 0) { - fd->cache[size]->inv_glyph_map[gindex] = charcode; + ffsd->inv_glyph_map[gindex] = charcode; } charcode = FT_Get_Next_Char(face, charcode, &gindex); } } - if (fd->cache[size]->inv_glyph_map.has(p_glyph_index)) { - return fd->cache[size]->inv_glyph_map[p_glyph_index]; + if (ffsd->inv_glyph_map.has(p_glyph_index)) { + return ffsd->inv_glyph_map[p_glyph_index]; } else { return 0; } @@ -3482,17 +3546,19 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c } MutexLock lock(fd->mutex); + FontForSizeAdvanced *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeAdvanced *at_size = fd->cache.begin()->value; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { - return FT_Get_Char_Index(at_size->face, p_char) != 0; + if (ffsd->face) { + return FT_Get_Char_Index(ffsd->face, p_char) != 0; } #endif - return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false; + return ffsd->glyph_map.has((int32_t)p_char); } String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const { @@ -3500,32 +3566,65 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); + FontForSizeAdvanced *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String()); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String()); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeAdvanced *at_size = fd->cache.begin()->value; String chars; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { + if (ffsd->face) { FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex); while (gindex != 0) { if (charcode != 0) { chars = chars + String::chr(charcode); } - charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex); } return chars; } #endif + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; + for (const KeyValue<int32_t, FontGlyph> &E : gl) { + chars = chars + String::chr(E.key); + } + return chars; +} + +PackedInt32Array TextServerAdvanced::_font_get_supported_glyphs(const RID &p_font_rid) const { + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); + + MutexLock lock(fd->mutex); + FontForSizeAdvanced *at_size = nullptr; + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array()); + } else { + at_size = fd->cache.begin()->value; + } + + PackedInt32Array glyphs; +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + while (gindex != 0) { + glyphs.push_back(gindex); + charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + } + return glyphs; + } +#endif if (at_size) { const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; for (const KeyValue<int32_t, FontGlyph> &E : gl) { - chars = chars + String::chr(E.key); + glyphs.push_back(E.key); } } - return chars; + return glyphs; } void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { @@ -3536,25 +3635,27 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); for (int64_t i = p_start; i <= p_end; i++) { #ifdef MODULE_FREETYPE_ENABLED - int32_t idx = FT_Get_Char_Index(fd->cache[size]->face, i); - if (fd->cache[size]->face) { + int32_t idx = FT_Get_Char_Index(ffsd->face, i); + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -3569,24 +3670,26 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); #ifdef MODULE_FREETYPE_ENABLED int32_t idx = p_index & 0xffffff; // Remove subpixel shifts. - if (fd->cache[size]->face) { + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -3603,16 +3706,17 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -3629,24 +3733,24 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3659,12 +3763,12 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { double scale = _font_get_scale(p_font_rid, p_size); Point2 cpos = p_pos; @@ -3677,8 +3781,8 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -3692,9 +3796,9 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -3711,16 +3815,17 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -3737,24 +3842,24 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && fd->cache[size]->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3767,12 +3872,12 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { Point2 cpos = p_pos; double scale = _font_get_scale(p_font_rid, p_size); @@ -3785,8 +3890,8 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -3800,9 +3905,9 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -3867,7 +3972,8 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const return fd->script_support_overrides[p_script]; } else { Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), false); return fd->supported_scripts.has(hb_tag_from_string(p_script.ascii().get_data(), -1)); } } @@ -3914,7 +4020,8 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->feature_overrides = p_overrides; } @@ -3932,7 +4039,8 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); return fd->supported_features; } @@ -3942,7 +4050,8 @@ Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); return fd->supported_varaitions; } @@ -4017,7 +4126,7 @@ int64_t TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd, } void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) { - p_shaped->valid = false; + p_shaped->valid.clear(); p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; p_shaped->justification_ops_valid = false; @@ -4373,7 +4482,7 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V sd->objects[p_key].rect.size = p_size; sd->objects[p_key].inline_align = p_inline_align; sd->objects[p_key].baseline = p_baseline; - if (sd->valid) { + if (sd->valid.is_set()) { // Recalc string metrics. sd->ascent = 0; sd->descent = 0; @@ -4517,7 +4626,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start if (sd->parent != RID()) { return _shaped_text_substr(sd->parent, p_start, p_length); } - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID()); @@ -4545,7 +4654,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start } bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const { - if (p_new_sd->valid) { + if (p_new_sd->valid.is_set()) { return true; } @@ -4694,7 +4803,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S _realign(p_new_sd); } - p_new_sd->valid = true; + p_new_sd->valid.set(); return true; } @@ -4712,7 +4821,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } if (!sd->justification_ops_valid) { @@ -4869,7 +4978,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -5086,7 +5195,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped_line); } @@ -5113,7 +5222,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ Vector<ShapedTextDataAdvanced::Span> &spans = sd->spans; if (sd->parent != RID()) { ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); - ERR_FAIL_COND(!parent_sd->valid); + ERR_FAIL_COND(!parent_sd->valid.is_set()); spans = parent_sd->spans; } @@ -5248,7 +5357,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { - // Insert an additional space when cutting word bound for esthetics. + // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { Glyph gl; gl.count = 1; @@ -5324,7 +5433,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const { Vector<ShapedTextDataAdvanced::Span> &spans = p_sd->spans; if (p_sd->parent != RID()) { ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(p_sd->parent); - ERR_FAIL_COND(!parent_sd->valid); + ERR_FAIL_COND(!parent_sd->valid.is_set()); spans = parent_sd->spans; } @@ -5372,7 +5481,7 @@ PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -5386,7 +5495,7 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } @@ -5657,7 +5766,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -5871,7 +5980,7 @@ _FORCE_INLINE_ void TextServerAdvanced::_add_featuers(const Dictionary &p_source int32_t value = values[i]; if (value >= 0) { hb_feature_t feature; - if (keys[i].get_type() == Variant::STRING) { + if (keys[i].is_string()) { feature.tag = _name_to_tag(keys[i]); } else { feature.tag = keys[i]; @@ -6037,7 +6146,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } @@ -6052,6 +6161,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star unsigned int last_cluster_index = 0; bool last_cluster_valid = true; + double adv_rem = 0.0; for (unsigned int i = 0; i < glyph_count; i++) { if ((i > 0) && (last_cluster_id != glyph_info[i].cluster)) { if (p_direction == HB_DIRECTION_RTL || p_direction == HB_DIRECTION_BTT) { @@ -6096,22 +6206,33 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star gl.index = glyph_info[i].codepoint; if (gl.index != 0) { - _ensure_glyph(fd, fss, gl.index | mod); + FontGlyph fgl; + _ensure_glyph(fd, fss, gl.index | mod, fgl); + if (subpos) { + gl.x_off = (double)glyph_pos[i].x_offset / (64.0 / scale); + } else if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + gl.x_off = Math::round(adv_rem + ((double)glyph_pos[i].x_offset / (64.0 / scale))); + } else { + gl.x_off = Math::round((double)glyph_pos[i].x_offset / (64.0 / scale)); + } + if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + gl.y_off = -Math::round((double)glyph_pos[i].y_offset / (64.0 / scale)); + } else { + gl.y_off = -Math::round(adv_rem + ((double)glyph_pos[i].y_offset / (64.0 / scale))); + } if (p_sd->orientation == ORIENTATION_HORIZONTAL) { if (subpos) { gl.advance = (double)glyph_pos[i].x_advance / (64.0 / scale) + ea; } else { - gl.advance = Math::round((double)glyph_pos[i].x_advance / (64.0 / scale) + ea); + double full_adv = adv_rem + ((double)glyph_pos[i].x_advance / (64.0 / scale) + ea); + gl.advance = Math::round(full_adv); + adv_rem = full_adv - gl.advance; } } else { - gl.advance = -Math::round((double)glyph_pos[i].y_advance / (64.0 / scale)); - } - if (subpos) { - gl.x_off = (double)glyph_pos[i].x_offset / (64.0 / scale); - } else { - gl.x_off = Math::round((double)glyph_pos[i].x_offset / (64.0 / scale)); + double full_adv = adv_rem + ((double)glyph_pos[i].y_advance / (64.0 / scale)); + gl.advance = -Math::round(full_adv); + adv_rem = full_adv + gl.advance; } - gl.y_off = -Math::round((double)glyph_pos[i].y_offset / (64.0 / scale)); if (p_sd->orientation == ORIENTATION_HORIZONTAL) { gl.y_off += _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size)); } else { @@ -6198,7 +6319,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (sd->valid) { + if (sd->valid.is_set()) { return true; } @@ -6206,13 +6327,13 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { if (sd->parent != RID()) { _shaped_text_shape(sd->parent); ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); - ERR_FAIL_COND_V(!parent_sd->valid, false); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false); ERR_FAIL_COND_V(!_shape_substr(sd, parent_sd, sd->start, sd->end - sd->start), false); return true; } if (sd->text.length() == 0) { - sd->valid = true; + sd->valid.set(); return true; } @@ -6405,16 +6526,16 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { } _realign(sd); - sd->valid = true; - return sd->valid; + sd->valid.set(); + return sd->valid.is_set(); } bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, false); - MutexLock lock(sd->mutex); - return sd->valid; + // Atomic read is safe and faster. + return sd->valid.is_set(); } const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const { @@ -6422,7 +6543,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.ptr(); @@ -6433,7 +6554,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.size(); @@ -6444,7 +6565,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped) ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -6484,7 +6605,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->objects[p_key].rect; @@ -6505,7 +6626,7 @@ int64_t TextServerAdvanced::_shaped_text_get_object_glyph(const RID &p_shaped, c MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), -1); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } const ShapedTextDataAdvanced::EmbeddedObject &obj = sd->objects[p_key]; @@ -6524,7 +6645,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { @@ -6539,7 +6660,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->ascent + sd->extra_spacing[SPACING_TOP]; @@ -6550,7 +6671,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->descent + sd->extra_spacing[SPACING_BOTTOM]; @@ -6561,7 +6682,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return Math::ceil(sd->text_trimmed ? sd->width_trimmed : sd->width); @@ -6572,7 +6693,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -6584,7 +6705,7 @@ double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_sha ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -7037,10 +7158,10 @@ PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_str HashSet<int> breaks; UErrorCode err = U_ZERO_ERROR; - UBreakIterator *bi = ubrk_open(UBRK_LINE, lang.ascii().get_data(), (const UChar *)utf16.get_data(), utf16.length(), &err); + UBreakIterator *bi = ubrk_open(UBRK_WORD, lang.ascii().get_data(), (const UChar *)utf16.get_data(), utf16.length(), &err); if (U_SUCCESS(err)) { while (ubrk_next(bi) != UBRK_DONE) { - int pos = _convert_pos(p_string, utf16, ubrk_current(bi)) - 1; + int pos = _convert_pos(p_string, utf16, ubrk_current(bi)); if (pos != p_string.length() - 1) { breaks.insert(pos); } @@ -7050,79 +7171,111 @@ PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_str PackedInt32Array ret; - int line_start = 0; - int line_end = 0; // End of last word on current line. - int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word. - int word_length = 0; + if (p_chars_per_line > 0) { + int line_start = 0; + int last_break = -1; + int line_length = 0; - for (int i = 0; i < p_string.length(); i++) { - const char32_t c = p_string[i]; + for (int i = 0; i < p_string.length(); i++) { + const char32_t c = p_string[i]; - if (is_linebreak(c)) { - // Force newline. - ret.push_back(line_start); - ret.push_back(i); - line_start = i + 1; - line_end = line_start; - word_start = line_start; - word_length = 0; - } else if (c == 0xfffc) { - continue; - } else if ((u_ispunct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || is_whitespace(c)) { - // A whitespace ends current word. - if (word_length > 0) { - line_end = i - 1; - word_start = -1; - word_length = 0; - } - } else if (breaks.has(i)) { - // End current word, no space. - if (word_length > 0) { - line_end = i; - word_start = i + 1; - word_length = 0; - } - if (p_chars_per_line <= 0) { - ret.push_back(line_start); - ret.push_back(line_end + 1); - line_start = word_start; - line_end = line_start; - } - } else { - if (word_start == -1) { - word_start = i; - if (p_chars_per_line <= 0) { + bool is_lb = is_linebreak(c); + bool is_ws = is_whitespace(c); + bool is_p = (u_ispunct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || c == 0xfffc; + + if (is_lb) { + if (line_length > 0) { ret.push_back(line_start); - ret.push_back(line_end + 1); - line_start = word_start; - line_end = line_start; + ret.push_back(i); } + line_start = i; + line_length = 0; + last_break = -1; + continue; + } else if (breaks.has(i) || is_ws || is_p) { + last_break = i; } - word_length += 1; - if (p_chars_per_line > 0) { - if (word_length > p_chars_per_line) { - // Word too long: wrap before current character. + if (line_length == p_chars_per_line) { + if (last_break != -1) { + int last_break_w_spaces = last_break; + while (last_break > line_start && is_whitespace(p_string[last_break - 1])) { + last_break--; + } + if (line_start != last_break) { + ret.push_back(line_start); + ret.push_back(last_break); + } + while (last_break_w_spaces < p_string.length() && is_whitespace(p_string[last_break_w_spaces])) { + last_break_w_spaces++; + } + line_start = last_break_w_spaces; + if (last_break_w_spaces < i) { + line_length = i - last_break_w_spaces; + } else { + i = last_break_w_spaces; + line_length = 0; + } + } else { ret.push_back(line_start); ret.push_back(i); line_start = i; - line_end = i; + line_length = 0; + } + last_break = -1; + } + line_length++; + } + if (line_length > 0) { + ret.push_back(line_start); + ret.push_back(p_string.length()); + } + } else { + int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word. + int word_length = 0; + + for (int i = 0; i < p_string.length(); i++) { + const char32_t c = p_string[i]; + + bool is_lb = is_linebreak(c); + bool is_ws = is_whitespace(c); + bool is_p = (u_ispunct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || c == 0xfffc; + + if (word_start == -1) { + if (!is_lb && !is_ws && !is_p) { + word_start = i; + } + continue; + } + + if (is_lb) { + if (word_start != -1 && word_length > 0) { + ret.push_back(word_start); + ret.push_back(i); + } + word_start = -1; + word_length = 0; + } else if (breaks.has(i) || is_ws || is_p) { + if (word_start != -1 && word_length > 0) { + ret.push_back(word_start); + ret.push_back(i); + } + if (is_ws || is_p) { + word_start = -1; + } else { word_start = i; - word_length = 1; - } else if (i - line_start + 1 > p_chars_per_line) { - // Line too long: wrap after the last word. - ret.push_back(line_start); - ret.push_back(line_end + 1); - line_start = word_start; - line_end = line_start; } + word_length = 0; } + + word_length++; + } + if (word_start != -1 && word_length > 0) { + ret.push_back(word_start); + ret.push_back(p_string.length()); } } - if (line_start < p_string.length()) { - ret.push_back(line_start); - ret.push_back(p_string.length()); - } + return ret; } @@ -7142,9 +7295,7 @@ PackedInt32Array TextServerAdvanced::_string_get_character_breaks(const String & } ubrk_close(bi); } else { - for (int i = 0; i <= p_string.size(); i++) { - ret.push_back(i); - } + return TextServer::string_get_character_breaks(p_string, p_language); } return ret; @@ -7342,7 +7493,7 @@ bool TextServerAdvanced::_is_valid_identifier(const String &p_string) const { return true; } -bool TextServerAdvanced::_is_valid_letter(char32_t p_unicode) const { +bool TextServerAdvanced::_is_valid_letter(uint64_t p_unicode) const { #ifndef ICU_STATIC_DATA if (!icu_data_loaded) { return TextServer::is_valid_letter(p_unicode); @@ -7352,10 +7503,15 @@ bool TextServerAdvanced::_is_valid_letter(char32_t p_unicode) const { return u_isalpha(p_unicode); } +void TextServerAdvanced::_update_settings() { + lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout")); +} + TextServerAdvanced::TextServerAdvanced() { _insert_num_systems_lang(); _insert_feature_sets(); _bmp_create_font_funcs(); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerAdvanced::_update_settings)); } void TextServerAdvanced::_cleanup() { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 8895a83089..c63389b1c6 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -74,6 +74,7 @@ #include <godot_cpp/templates/hash_map.hpp> #include <godot_cpp/templates/hash_set.hpp> #include <godot_cpp/templates/rid_owner.hpp> +#include <godot_cpp/templates/safe_refcount.hpp> #include <godot_cpp/templates/vector.hpp> using namespace godot; @@ -85,6 +86,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" +#include "core/templates/safe_refcount.h" #include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" @@ -151,6 +153,9 @@ class TextServerAdvanced : public TextServerExtension { HashMap<StringName, int32_t> feature_sets; HashMap<int32_t, FeatureInfo> feature_sets_inv; + SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE }; + void _update_settings(); + void _insert_num_systems_lang(); void _insert_feature_sets(); _FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag, Variant::Type p_vtype = Variant::INT, bool p_hidden = false); @@ -296,7 +301,7 @@ class TextServerAdvanced : public TextServerExtension { struct FontAdvancedLinkedVariation { RID base_font; int extra_spacing[4] = { 0, 0, 0, 0 }; - float baseline_offset = 0.0; + double baseline_offset = 0.0; }; struct FontAdvanced { @@ -325,9 +330,9 @@ class TextServerAdvanced : public TextServerExtension { int weight = 400; int stretch = 100; int extra_spacing[4] = { 0, 0, 0, 0 }; - float baseline_offset = 0.0; + double baseline_offset = 0.0; - HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache; + HashMap<Vector2i, FontForSizeAdvanced *> cache; bool face_init = false; HashSet<uint32_t> supported_scripts; @@ -359,8 +364,8 @@ class TextServerAdvanced : public TextServerExtension { #ifdef MODULE_FREETYPE_ENABLED _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const; #endif - _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; - _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const; + _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const; _FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data); static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); @@ -487,7 +492,7 @@ class TextServerAdvanced : public TextServerExtension { /* Shaped data */ TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction. int base_para_direction = UBIDI_DEFAULT_LTR; - bool valid = false; // String is shaped. + SafeFlag valid{ false }; // String is shaped. bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. bool sort_valid = false; @@ -578,7 +583,7 @@ class TextServerAdvanced : public TextServerExtension { double embolden = 0.0; Transform2D transform; int extra_spacing[4] = { 0, 0, 0, 0 }; - float baseline_offset = 0.0; + double baseline_offset = 0.0; bool operator==(const SystemFontKey &p_b) const { return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset); @@ -700,7 +705,7 @@ class TextServerAdvanced : public TextServerExtension { }; protected: - static void _bind_methods(){}; + static void _bind_methods() {} void full_copy(ShapedTextDataAdvanced *p_shaped); void invalidate(ShapedTextDataAdvanced *p_shaped, bool p_text = false); @@ -791,8 +796,8 @@ public: MODBIND3(font_set_spacing, const RID &, SpacingType, int64_t); MODBIND2RC(int64_t, font_get_spacing, const RID &, SpacingType); - MODBIND2(font_set_baseline_offset, const RID &, float); - MODBIND1RC(float, font_get_baseline_offset, const RID &); + MODBIND2(font_set_baseline_offset, const RID &, double); + MODBIND1RC(double, font_get_baseline_offset, const RID &); MODBIND2(font_set_transform, const RID &, const Transform2D &); MODBIND1RC(Transform2D, font_get_transform, const RID &); @@ -871,6 +876,7 @@ public: MODBIND2RC(bool, font_has_char, const RID &, int64_t); MODBIND1RC(String, font_get_supported_chars, const RID &); + MODBIND1RC(PackedInt32Array, font_get_supported_glyphs, const RID &); MODBIND4(font_render_range, const RID &, const Vector2i &, int64_t, int64_t); MODBIND3(font_render_glyph, const RID &, const Vector2i &, int64_t); @@ -988,7 +994,7 @@ public: MODBIND1RC(String, strip_diacritics, const String &); MODBIND1RC(bool, is_valid_identifier, const String &); - MODBIND1RC(bool, is_valid_letter, char32_t); + MODBIND1RC(bool, is_valid_letter, uint64_t); MODBIND2RC(String, string_to_upper, const String &, const String &); MODBIND2RC(String, string_to_lower, const String &, const String &); diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 07940719eb..a3c2052040 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -1,19 +1,27 @@ #!/usr/bin/env python import atexit import sys -import methods import time +import methods + # Enable ANSI escape code support on Windows 10 and later (for colored console output). # <https://github.com/python/cpython/issues/73245> -if sys.platform == "win32": - from ctypes import windll, c_int, byref - - stdout_handle = windll.kernel32.GetStdHandle(c_int(-11)) - mode = c_int(0) - windll.kernel32.GetConsoleMode(c_int(stdout_handle), byref(mode)) - mode = c_int(mode.value | 4) - windll.kernel32.SetConsoleMode(c_int(stdout_handle), mode) +if sys.stdout.isatty() and sys.platform == "win32": + try: + from ctypes import WinError, byref, windll # type: ignore + from ctypes.wintypes import DWORD # type: ignore + + stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11)) + mode = DWORD(0) + if not windll.kernel32.GetConsoleMode(stdout_handle, byref(mode)): + raise WinError() + mode = DWORD(mode.value | 4) + if not windll.kernel32.SetConsoleMode(stdout_handle, mode): + raise WinError() + except Exception as e: + methods._colorize = False + methods.print_error(f"Failed to enable ANSI escape code support, disabling color output.\n{e}") # For the reference: # - CCFLAGS are compilation flags shared between C and C++ @@ -48,8 +56,8 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: thirdparty_tvg_dir = "../../../thirdparty/thorvg/" thirdparty_tvg_sources = [ # common - "src/common/tvgBezier.cpp", "src/common/tvgCompressor.cpp", + "src/common/tvgLines.cpp", "src/common/tvgMath.cpp", "src/common/tvgStr.cpp", # SVG parser @@ -60,6 +68,7 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", "src/loaders/raw/tvgRawLoader.cpp", + # image loaders "src/loaders/external_png/tvgPngLoader.cpp", "src/loaders/jpg/tvgJpgd.cpp", "src/loaders/jpg/tvgJpgLoader.cpp", @@ -104,6 +113,7 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "../../../thirdparty/thorvg/src/loaders/raw", "../../../thirdparty/thorvg/src/loaders/external_png", "../../../thirdparty/thorvg/src/loaders/jpg", + "../../../thirdparty/libpng", ] ) @@ -138,8 +148,6 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]: "core/Projection.cpp", "core/Scanline.cpp", "core/Shape.cpp", - "core/SignedDistance.cpp", - "core/Vector2.cpp", "core/contour-combiners.cpp", "core/edge-coloring.cpp", "core/edge-segments.cpp", diff --git a/modules/text_server_fb/gdextension_build/methods.py b/modules/text_server_fb/gdextension_build/methods.py index 3453c3e8f0..43bf56abfe 100644 --- a/modules/text_server_fb/gdextension_build/methods.py +++ b/modules/text_server_fb/gdextension_build/methods.py @@ -81,9 +81,9 @@ def disable_warnings(self): self.Append(CCFLAGS=["/w"]) self.Append(CFLAGS=["/w"]) self.Append(CXXFLAGS=["/w"]) - self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not x in warn_flags] - self["CFLAGS"] = [x for x in self["CFLAGS"] if not x in warn_flags] - self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if not x in warn_flags] + self["CCFLAGS"] = [x for x in self["CCFLAGS"] if x not in warn_flags] + self["CFLAGS"] = [x for x in self["CFLAGS"] if x not in warn_flags] + self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if x not in warn_flags] else: self.Append(CCFLAGS=["-w"]) self.Append(CFLAGS=["-w"]) @@ -117,31 +117,31 @@ def make_icu_data(target, source, env): def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resource/", exist_ok=True) with open(f"{target}/Resource/Info.plist", "w", encoding="utf-8", newline="\n") as f: - f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n') - f.write( - f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' - ) - f.write(f'<plist version="1.0">\n') - f.write(f"<dict>\n") - f.write(f"\t<key>CFBundleExecutable</key>\n") - f.write(f"\t<string>{binary_name}</string>\n") - f.write(f"\t<key>CFBundleIdentifier</key>\n") - f.write(f"\t<string>{identifier}</string>\n") - f.write(f"\t<key>CFBundleInfoDictionaryVersion</key>\n") - f.write(f"\t<string>6.0</string>\n") - f.write(f"\t<key>CFBundleName</key>\n") - f.write(f"\t<string>{name}</string>\n") - f.write(f"\t<key>CFBundlePackageType</key>\n") - f.write(f"\t<string>FMWK</string>\n") - f.write(f"\t<key>CFBundleShortVersionString</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>CFBundleSupportedPlatforms</key>\n") - f.write(f"\t<array>\n") - f.write(f"\t\t<string>MacOSX</string>\n") - f.write(f"\t</array>\n") - f.write(f"\t<key>CFBundleVersion</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>LSMinimumSystemVersion</key>\n") - f.write(f"\t<string>10.14</string>\n") - f.write(f"</dict>\n") - f.write(f"</plist>\n") + f.write(f"""\ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleExecutable</key> + <string>{binary_name}</string> + <key>CFBundleIdentifier</key> + <string>{identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>{name}</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0</string> + <key>CFBundleSupportedPlatforms</key> + <array> + <string>MacOSX</string> + </array> + <key>CFBundleVersion</key> + <string>1.0.0</string> + <key>LSMinimumSystemVersion</key> + <string>10.14</string> +</dict> +</plist> +""") diff --git a/modules/text_server_fb/register_types.cpp b/modules/text_server_fb/register_types.cpp index 37c623c5eb..3dd359f0ce 100644 --- a/modules/text_server_fb/register_types.cpp +++ b/modules/text_server_fb/register_types.cpp @@ -62,8 +62,8 @@ using namespace godot; extern "C" { -GDExtensionBool GDE_EXPORT textserver_fallback_init(const GDExtensionInterface *p_interface, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { - GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization); +GDExtensionBool GDE_EXPORT textserver_fallback_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { + GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); init_obj.register_initializer(&initialize_text_server_fb_module); init_obj.register_terminator(&uninitialize_text_server_fb_module); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index c62f308818..540ba19cac 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -40,6 +40,8 @@ #include <godot_cpp/classes/translation_server.hpp> #include <godot_cpp/core/error_macros.hpp> +#define OT_TAG(m_c1, m_c2, m_c3, m_c4) ((int32_t)((((uint32_t)(m_c1) & 0xff) << 24) | (((uint32_t)(m_c2) & 0xff) << 16) | (((uint32_t)(m_c3) & 0xff) << 8) | ((uint32_t)(m_c4) & 0xff))) + using namespace godot; #define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get_setting_with_override(m_var) @@ -50,7 +52,7 @@ using namespace godot; #include "core/config/project_settings.h" #include "core/error/error_macros.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. @@ -287,7 +289,7 @@ _FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_ { // Zero texture. uint8_t *w = tex.image->ptrw(); - ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.image->data_size(), ret); + ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.image->get_data_size(), ret); // Initialize the texture to all-white pixels to prevent artifacts when the // font is displayed at a non-default scale with filtering enabled. if (p_color_size == 2) { @@ -475,7 +477,7 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_msdf( for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { int ofs = ((i + tex_pos.y + p_rect_margin * 2) * tex.texture_w + j + tex_pos.x + p_rect_margin * 2) * 4; - ERR_FAIL_COND_V(ofs >= tex.image->data_size(), FontGlyph()); + ERR_FAIL_COND_V(ofs >= tex.image->get_data_size(), FontGlyph()); wr[ofs + 0] = (uint8_t)(CLAMP(image(j, i)[0] * 256.f, 0.f, 255.f)); wr[ofs + 1] = (uint8_t)(CLAMP(image(j, i)[1] * 256.f, 0.f, 255.f)); wr[ofs + 2] = (uint8_t)(CLAMP(image(j, i)[2] * 256.f, 0.f, 255.f)); @@ -552,7 +554,7 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { int ofs = ((i + tex_pos.y + p_rect_margin * 2) * tex.texture_w + j + tex_pos.x + p_rect_margin * 2) * color_size; - ERR_FAIL_COND_V(ofs >= tex.image->data_size(), FontGlyph()); + ERR_FAIL_COND_V(ofs >= tex.image->get_data_size(), FontGlyph()); switch (p_bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: { int byte = i * p_bitmap.pitch + (j >> 3); @@ -622,18 +624,21 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma /* Font Cache */ /*************************************************************************/ -_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { - ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); +_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const { + FontForSizeFallback *fd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false); int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts. - FontForSizeFallback *fd = p_font_data->cache[p_size]; - if (fd->glyph_map.has(p_glyph)) { - return fd->glyph_map[p_glyph].found; + HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph); + if (E) { + r_glyph = E->value; + return E->value.found; } if (glyph_index == 0) { // Non graphical or invalid glyph, do not render. - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return true; } @@ -671,7 +676,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, int error = FT_Load_Glyph(fd->face, glyph_index, flags); if (error) { - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } @@ -775,20 +781,26 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, cleanup_stroker: FT_Stroker_Done(stroker); } - fd->glyph_map[p_glyph] = gl; + E = fd->glyph_map.insert(p_glyph, gl); + r_glyph = E->value; return gl.found; } #endif - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } -_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const { +_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const { ERR_FAIL_COND_V(p_size.x <= 0, false); - if (p_font_data->cache.has(p_size)) { + + HashMap<Vector2i, FontForSizeFallback *>::Iterator E = p_font_data->cache.find(p_size); + if (E) { + r_cache_for_size = E->value; return true; } + r_cache_for_size = nullptr; FontForSizeFallback *fd = memnew(FontForSizeFallback); fd->size = p_size; if (p_font_data->data_ptr && (p_font_data->data_size > 0)) { @@ -971,7 +983,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif } - p_font_data->cache[p_size] = fd; + + p_font_data->cache.insert(p_size, fd); + r_cache_for_size = fd; return true; } @@ -1039,7 +1053,8 @@ void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontSty MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_flags = p_style; } @@ -1117,7 +1132,8 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); return fd->style_flags; } @@ -1127,7 +1143,8 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_name = p_name; } @@ -1137,7 +1154,8 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->style_name; } @@ -1147,7 +1165,8 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->weight = CLAMP(p_weight, 100, 999); } @@ -1157,7 +1176,8 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400); return fd->weight; } @@ -1167,7 +1187,8 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->stretch = CLAMP(p_stretch, 50, 200); } @@ -1177,7 +1198,8 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100); return fd->stretch; } @@ -1187,7 +1209,8 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->font_name = p_name; } @@ -1197,7 +1220,8 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->font_name; } @@ -1474,7 +1498,7 @@ int64_t TextServerFallback::_font_get_spacing(const RID &p_font_rid, SpacingType } } -void TextServerFallback::_font_set_baseline_offset(const RID &p_font_rid, float p_baseline_offset) { +void TextServerFallback::_font_set_baseline_offset(const RID &p_font_rid, double p_baseline_offset) { FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); if (fdv) { if (fdv->baseline_offset != p_baseline_offset) { @@ -1492,7 +1516,7 @@ void TextServerFallback::_font_set_baseline_offset(const RID &p_font_rid, float } } -float TextServerFallback::_font_get_baseline_offset(const RID &p_font_rid) const { +double TextServerFallback::_font_get_baseline_offset(const RID &p_font_rid) const { FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); if (fdv) { return fdv->baseline_offset; @@ -1605,8 +1629,9 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->ascent = p_ascent; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->ascent = p_ascent; } double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { @@ -1616,18 +1641,19 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size; + return ffsd->ascent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->ascent; + return ffsd->ascent; } } @@ -1637,8 +1663,9 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->descent = p_descent; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->descent = p_descent; } double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { @@ -1648,18 +1675,19 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->descent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size; + return ffsd->descent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->descent; + return ffsd->descent; } } @@ -1670,8 +1698,9 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_position = p_underline_position; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_position = p_underline_position; } double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { @@ -1681,18 +1710,19 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_position * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_position; + return ffsd->underline_position; } } @@ -1703,8 +1733,9 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_thickness = p_underline_thickness; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_thickness = p_underline_thickness; } double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { @@ -1714,18 +1745,19 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_thickness; + return ffsd->underline_thickness; } } @@ -1736,13 +1768,14 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { return; // Do not override scale for dynamic fonts, it's calculated automatically. } #endif - fd->cache[size]->scale = p_scale; + ffsd->scale = p_scale; } double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { @@ -1752,18 +1785,19 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size; + return ffsd->scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size; + return ffsd->scale * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->scale / fd->cache[size]->oversampling; + return ffsd->scale / ffsd->oversampling; } } @@ -1774,9 +1808,10 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); - return fd->cache[size]->textures.size(); + return ffsd->textures.size(); } void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { @@ -1785,8 +1820,9 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->textures.clear(); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->textures.clear(); } void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { @@ -1795,10 +1831,11 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size()); - fd->cache[size]->textures.remove_at(p_texture_index); + ffsd->textures.remove_at(p_texture_index); } void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { @@ -1808,13 +1845,14 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.image = p_image; tex.texture_w = p_image->get_width(); @@ -1835,10 +1873,11 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; return tex.image; } @@ -1849,13 +1888,14 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.shelves.clear(); for (int32_t i = 0; i < p_offsets.size(); i += 4) { tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3])); @@ -1868,10 +1908,11 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; PackedInt32Array ret; ret.resize(tex.shelves.size() * 4); @@ -1893,10 +1934,11 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); PackedInt32Array ret; - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; for (const KeyValue<int32_t, FontGlyph> &E : gl) { ret.push_back(E.key); } @@ -1909,9 +1951,10 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.clear(); + ffsd->glyph_map.clear(); } void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { @@ -1920,9 +1963,10 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.erase(p_glyph); + ffsd->glyph_map.erase(p_glyph); } Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const { @@ -1932,22 +1976,22 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64 MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - Vector2 ea; if (fd->embolden != 0.0) { ea.x = fd->embolden * double(size.x) / 64.0; @@ -1955,17 +1999,17 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64 double scale = _font_get_scale(p_font_rid, p_size); if (fd->msdf) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size; } else { - return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); + return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); } } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) { - return (gl[p_glyph | mod].advance + ea).round(); + return (fgl.advance + ea).round(); } else { - return gl[p_glyph | mod].advance + ea; + return fgl.advance + ea; } } @@ -1976,12 +2020,13 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].advance = p_advance; - gl[p_glyph].found = true; + fgl.advance = p_advance; + fgl.found = true; } Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -1991,32 +2036,32 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.position; + return fgl.rect.position; } } @@ -2027,12 +2072,13 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.position = p_offset; - gl[p_glyph].found = true; + fgl.rect.position = p_offset; + fgl.found = true; } Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2042,32 +2088,32 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.size; + return fgl.rect.size; } } @@ -2078,12 +2124,13 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.size = p_gl_size; - gl[p_glyph].found = true; + fgl.rect.size = p_gl_size; + fgl.found = true; } Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2093,22 +2140,23 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Rect2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].uv_rect; + return fgl.uv_rect; } void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { @@ -2118,12 +2166,13 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].uv_rect = p_uv_rect; - gl[p_glyph].found = true; + fgl.uv_rect = p_uv_rect; + fgl.found = true; } int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2133,22 +2182,23 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return -1; // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].texture_idx; + return fgl.texture_idx; } void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { @@ -2158,12 +2208,13 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].texture_idx = p_texture_idx; - gl[p_glyph].found = true; + fgl.texture_idx = p_texture_idx; + fgl.found = true; } RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2173,27 +2224,28 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return RID(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2206,7 +2258,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid(); + return ffsd->textures[fgl.texture_idx].texture->get_rid(); } } @@ -2220,27 +2272,28 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Size2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2253,7 +2306,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size(); + return ffsd->textures[fgl.texture_idx].texture->get_size(); } } @@ -2267,7 +2320,8 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); #ifdef MODULE_FREETYPE_ENABLED PackedVector3Array points; @@ -2275,20 +2329,20 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i int32_t index = p_index & 0xffffff; // Remove subpixel shifts. - int error = FT_Load_Glyph(fd->cache[size]->face, FT_Get_Char_Index(fd->cache[size]->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + int error = FT_Load_Glyph(ffsd->face, FT_Get_Char_Index(ffsd->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); ERR_FAIL_COND_V(error, Dictionary()); if (fd->embolden != 0.f) { FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64). - FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength); + FT_Outline_Embolden(&ffsd->face->glyph->outline, strength); } if (fd->transform != Transform2D()) { FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536). - FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat); + FT_Outline_Transform(&ffsd->face->glyph->outline, &mat); } - double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; + double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale; if (fd->msdf) { scale = scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -2298,13 +2352,13 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i scale = scale * Math::round((double)p_size / (double)fd->fixed_size); } } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { - points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); + for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) { + points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i]))); } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) { - contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]); + for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) { + contours.push_back(ffsd->face->glyph->outline.contours[i]); } - bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); + bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); Dictionary out; out["points"] = points; @@ -2323,10 +2377,11 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>()); TypedArray<Vector2i> ret; - for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) { + for (const KeyValue<Vector2i, Vector2> &E : ffsd->kerning_map) { ret.push_back(E.key); } return ret; @@ -2339,8 +2394,9 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.clear(); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.clear(); } void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { @@ -2350,8 +2406,9 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.erase(p_glyph_pair); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.erase(p_glyph_pair); } void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { @@ -2361,8 +2418,9 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map[p_glyph_pair] = p_kerning; } Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { @@ -2372,9 +2430,10 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); - const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map; + const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map; if (kern.has(p_glyph_pair)) { if (fd->msdf) { @@ -2390,11 +2449,11 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s } } else { #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { FT_Vector delta; - int32_t glyph_a = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.x); - int32_t glyph_b = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.y); - FT_Get_Kerning(fd->cache[size]->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta); + int32_t glyph_a = FT_Get_Char_Index(ffsd->face, p_glyph_pair.x); + int32_t glyph_b = FT_Get_Char_Index(ffsd->face, p_glyph_pair.y); + FT_Get_Kerning(ffsd->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta); if (fd->msdf) { return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -2429,17 +2488,19 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c } MutexLock lock(fd->mutex); + FontForSizeFallback *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeFallback *at_size = fd->cache.begin()->value; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { - return FT_Get_Char_Index(at_size->face, p_char) != 0; + if (ffsd->face) { + return FT_Get_Char_Index(ffsd->face, p_char) != 0; } #endif - return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false; + return ffsd->glyph_map.has((int32_t)p_char); } String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const { @@ -2447,32 +2508,65 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); + FontForSizeFallback *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String()); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String()); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeFallback *at_size = fd->cache.begin()->value; String chars; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { + if (ffsd->face) { FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex); while (gindex != 0) { if (charcode != 0) { chars = chars + String::chr(charcode); } - charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex); } return chars; } #endif + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; + for (const KeyValue<int32_t, FontGlyph> &E : gl) { + chars = chars + String::chr(E.key); + } + return chars; +} + +PackedInt32Array TextServerFallback::_font_get_supported_glyphs(const RID &p_font_rid) const { + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); + + MutexLock lock(fd->mutex); + FontForSizeFallback *at_size = nullptr; + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array()); + } else { + at_size = fd->cache.begin()->value; + } + + PackedInt32Array glyphs; +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + while (gindex != 0) { + glyphs.push_back(gindex); + charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + } + return glyphs; + } +#endif if (at_size) { const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; for (const KeyValue<int32_t, FontGlyph> &E : gl) { - chars = chars + String::chr(E.key); + glyphs.push_back(E.key); } } - return chars; + return glyphs; } void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { @@ -2483,25 +2577,27 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); for (int64_t i = p_start; i <= p_end; i++) { #ifdef MODULE_FREETYPE_ENABLED int32_t idx = i; - if (fd->cache[size]->face) { + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -2516,24 +2612,26 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); #ifdef MODULE_FREETYPE_ENABLED int32_t idx = p_index & 0xffffff; // Remove subpixel shifts. - if (fd->cache[size]->face) { + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -2550,16 +2648,17 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -2576,24 +2675,24 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2606,12 +2705,12 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { Point2 cpos = p_pos; double scale = _font_get_scale(p_font_rid, p_size); @@ -2624,8 +2723,8 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -2639,9 +2738,9 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -2658,16 +2757,17 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -2684,24 +2784,24 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2714,12 +2814,12 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { Point2 cpos = p_pos; double scale = _font_get_scale(p_font_rid, p_size); @@ -2732,8 +2832,8 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -2747,9 +2847,9 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -2839,7 +2939,8 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->script_support_overrides.erase(p_script); } @@ -2861,7 +2962,8 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->feature_overrides = p_overrides; } @@ -2883,7 +2985,8 @@ Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); return fd->supported_varaitions; } @@ -2920,7 +3023,7 @@ void TextServerFallback::_font_set_global_oversampling(double p_oversampling) { /*************************************************************************/ void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) { - p_shaped->valid = false; + p_shaped->valid.clear(); p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; p_shaped->justification_ops_valid = false; @@ -3159,7 +3262,7 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64 span.font_size = p_size; span.features = p_opentype_features; - sd->valid = false; + sd->valid.clear(); } bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { @@ -3255,7 +3358,7 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V sd->objects[p_key].rect.size = p_size; sd->objects[p_key].inline_align = p_inline_align; sd->objects[p_key].baseline = p_baseline; - if (sd->valid) { + if (sd->valid.is_set()) { // Recalc string metrics. sd->ascent = 0; sd->descent = 0; @@ -3398,7 +3501,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start if (sd->parent != RID()) { return _shaped_text_substr(sd->parent, p_start, p_length); } - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID()); @@ -3481,7 +3584,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start _realign(new_sd); } - new_sd->valid = true; + new_sd->valid.set(); return shaped_owner.make_rid(new_sd); } @@ -3499,7 +3602,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } if (!sd->justification_ops_valid) { @@ -3608,7 +3711,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -3664,7 +3767,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } @@ -3728,7 +3831,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -3907,7 +4010,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped_line); } @@ -3934,7 +4037,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ Vector<ShapedTextDataFallback::Span> &spans = sd->spans; if (sd->parent != RID()) { ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); - ERR_FAIL_COND(!parent_sd->valid); + ERR_FAIL_COND(!parent_sd->valid.is_set()); spans = parent_sd->spans; } @@ -4059,7 +4162,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { - // Insert an additional space when cutting word bound for esthetics. + // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { Glyph gl; gl.count = 1; @@ -4128,7 +4231,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (sd->valid) { + if (sd->valid.is_set()) { return true; } @@ -4145,7 +4248,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { sd->glyphs.clear(); if (sd->text.length() == 0) { - sd->valid = true; + sd->valid.set(); return true; } @@ -4274,16 +4377,15 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { // Align embedded objects to baseline. _realign(sd); - sd->valid = true; - return sd->valid; + sd->valid.set(); + return sd->valid.is_set(); } bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, false); - MutexLock lock(sd->mutex); - return sd->valid; + return sd->valid.is_set(); } const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const { @@ -4291,7 +4393,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.ptr(); @@ -4302,7 +4404,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.size(); @@ -4313,7 +4415,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4347,7 +4449,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->objects[p_key].rect; @@ -4384,7 +4486,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { @@ -4399,7 +4501,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->ascent + sd->extra_spacing[SPACING_TOP]; @@ -4410,7 +4512,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->descent + sd->extra_spacing[SPACING_BOTTOM]; @@ -4421,7 +4523,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return Math::ceil(sd->width); @@ -4432,7 +4534,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4444,7 +4546,7 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4456,7 +4558,7 @@ PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4465,7 +4567,11 @@ PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID if (size > 0) { ret.resize(size); for (int i = 0; i < size; i++) { +#ifdef GDEXTENSION + ret[i] = i + 1 + sd->start; +#else ret.write[i] = i + 1 + sd->start; +#endif } } return ret; @@ -4486,71 +4592,116 @@ String TextServerFallback::_string_to_title(const String &p_string, const String PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_string, const String &p_language, int64_t p_chars_per_line) const { PackedInt32Array ret; - int line_start = 0; - int line_end = 0; // End of last word on current line. - int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word. - int word_length = 0; + if (p_chars_per_line > 0) { + int line_start = 0; + int last_break = -1; + int line_length = 0; - for (int i = 0; i < p_string.length(); i++) { - const char32_t c = p_string[i]; + for (int i = 0; i < p_string.length(); i++) { + const char32_t c = p_string[i]; - if (is_linebreak(c)) { - // Force newline. - ret.push_back(line_start); - ret.push_back(i); - line_start = i + 1; - line_end = line_start; - word_start = line_start; - word_length = 0; - } else if (c == 0xfffc) { - continue; - } else if ((is_punct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || is_whitespace(c)) { - // A whitespace ends current word. - if (word_length > 0) { - line_end = i - 1; - word_start = -1; - word_length = 0; - } - } else { - if (word_start == -1) { - word_start = i; - if (p_chars_per_line <= 0) { + bool is_lb = is_linebreak(c); + bool is_ws = is_whitespace(c); + bool is_p = (is_punct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || c == 0xfffc; + + if (is_lb) { + if (line_length > 0) { ret.push_back(line_start); - ret.push_back(line_end + 1); - line_start = word_start; - line_end = line_start; + ret.push_back(i); } + line_start = i; + line_length = 0; + last_break = -1; + continue; + } else if (is_ws || is_p) { + last_break = i; } - word_length += 1; - if (p_chars_per_line > 0) { - if (word_length > p_chars_per_line) { - // Word too long: wrap before current character. + if (line_length == p_chars_per_line) { + if (last_break != -1) { + int last_break_w_spaces = last_break; + while (last_break > line_start && is_whitespace(p_string[last_break - 1])) { + last_break--; + } + if (line_start != last_break) { + ret.push_back(line_start); + ret.push_back(last_break); + } + while (last_break_w_spaces < p_string.length() && is_whitespace(p_string[last_break_w_spaces])) { + last_break_w_spaces++; + } + line_start = last_break_w_spaces; + if (last_break_w_spaces < i) { + line_length = i - last_break_w_spaces; + } else { + i = last_break_w_spaces; + line_length = 0; + } + } else { ret.push_back(line_start); ret.push_back(i); line_start = i; - line_end = i; + line_length = 0; + } + last_break = -1; + } + line_length++; + } + if (line_length > 0) { + ret.push_back(line_start); + ret.push_back(p_string.length()); + } + } else { + int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word. + int word_length = 0; + + for (int i = 0; i < p_string.length(); i++) { + const char32_t c = p_string[i]; + + bool is_lb = is_linebreak(c); + bool is_ws = is_whitespace(c); + bool is_p = (is_punct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || c == 0xfffc; + + if (word_start == -1) { + if (!is_lb && !is_ws && !is_p) { word_start = i; - word_length = 1; - } else if (i - line_start + 1 > p_chars_per_line) { - // Line too long: wrap after the last word. - ret.push_back(line_start); - ret.push_back(line_end + 1); - line_start = word_start; - line_end = line_start; } + continue; + } + + if (is_lb) { + if (word_start != -1 && word_length > 0) { + ret.push_back(word_start); + ret.push_back(i); + } + word_start = -1; + word_length = 0; + } else if (is_ws || is_p) { + if (word_start != -1 && word_length > 0) { + ret.push_back(word_start); + ret.push_back(i); + } + word_start = -1; + word_length = 0; } + + word_length++; + } + if (word_start != -1 && word_length > 0) { + ret.push_back(word_start); + ret.push_back(p_string.length()); } - } - if (line_start < p_string.length()) { - ret.push_back(line_start); - ret.push_back(p_string.length()); } return ret; } +void TextServerFallback::_update_settings() { + lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout")); +} + TextServerFallback::TextServerFallback() { _insert_feature_sets(); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerFallback::_update_settings)); }; void TextServerFallback::_cleanup() { diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 9ad20c7b26..7f12ad593b 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -72,6 +72,7 @@ #include <godot_cpp/templates/hash_map.hpp> #include <godot_cpp/templates/hash_set.hpp> #include <godot_cpp/templates/rid_owner.hpp> +#include <godot_cpp/templates/safe_refcount.hpp> #include <godot_cpp/templates/vector.hpp> using namespace godot; @@ -83,6 +84,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" +#include "core/templates/safe_refcount.h" #include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" @@ -116,6 +118,9 @@ class TextServerFallback : public TextServerExtension { HashMap<StringName, int32_t> feature_sets; HashMap<int32_t, StringName> feature_sets_inv; + SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE }; + void _update_settings(); + void _insert_feature_sets(); _FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag); @@ -247,7 +252,7 @@ class TextServerFallback : public TextServerExtension { struct FontFallbackLinkedVariation { RID base_font; int extra_spacing[4] = { 0, 0, 0, 0 }; - float baseline_offset = 0.0; + double baseline_offset = 0.0; }; struct FontFallback { @@ -276,9 +281,9 @@ class TextServerFallback : public TextServerExtension { int weight = 400; int stretch = 100; int extra_spacing[4] = { 0, 0, 0, 0 }; - float baseline_offset = 0.0; + double baseline_offset = 0.0; - HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache; + HashMap<Vector2i, FontForSizeFallback *> cache; bool face_init = false; Dictionary supported_varaitions; @@ -308,8 +313,8 @@ class TextServerFallback : public TextServerExtension { #ifdef MODULE_FREETYPE_ENABLED _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const; #endif - _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; - _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const; + _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const; _FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data); static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); @@ -432,7 +437,7 @@ class TextServerFallback : public TextServerExtension { /* Shaped data */ TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction. - bool valid = false; // String is shaped. + SafeFlag valid{ false }; // String is shaped. bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. bool sort_valid = false; @@ -494,7 +499,7 @@ class TextServerFallback : public TextServerExtension { double embolden = 0.0; Transform2D transform; int extra_spacing[4] = { 0, 0, 0, 0 }; - float baseline_offset = 0.0; + double baseline_offset = 0.0; bool operator==(const SystemFontKey &p_b) const { return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset); @@ -569,7 +574,7 @@ class TextServerFallback : public TextServerExtension { Mutex ft_mutex; protected: - static void _bind_methods(){}; + static void _bind_methods() {} void full_copy(ShapedTextDataFallback *p_shaped); void invalidate(ShapedTextDataFallback *p_shaped); @@ -659,8 +664,8 @@ public: MODBIND3(font_set_spacing, const RID &, SpacingType, int64_t); MODBIND2RC(int64_t, font_get_spacing, const RID &, SpacingType); - MODBIND2(font_set_baseline_offset, const RID &, float); - MODBIND1RC(float, font_get_baseline_offset, const RID &); + MODBIND2(font_set_baseline_offset, const RID &, double); + MODBIND1RC(double, font_get_baseline_offset, const RID &); MODBIND2(font_set_transform, const RID &, const Transform2D &); MODBIND1RC(Transform2D, font_get_transform, const RID &); @@ -739,6 +744,7 @@ public: MODBIND2RC(bool, font_has_char, const RID &, int64_t); MODBIND1RC(String, font_get_supported_chars, const RID &); + MODBIND1RC(PackedInt32Array, font_get_supported_glyphs, const RID &); MODBIND4(font_render_range, const RID &, const Vector2i &, int64_t, int64_t); MODBIND3(font_render_glyph, const RID &, const Vector2i &, int64_t); diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml index 7eba3ad8ec..4b5ad07688 100644 --- a/modules/upnp/doc_classes/UPNP.xml +++ b/modules/upnp/doc_classes/UPNP.xml @@ -31,13 +31,13 @@ if err != OK: push_error(str(err)) - emit_signal("upnp_completed", err) + upnp_completed.emit(err) return if upnp.get_gateway() and upnp.get_gateway().is_valid_gateway(): upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "UDP") upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "TCP") - emit_signal("upnp_completed", OK) + upnp_completed.emit(OK) func _ready(): thread = Thread.new() diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp index 70f0ea346b..4305bf842a 100644 --- a/modules/upnp/upnp.cpp +++ b/modules/upnp/upnp.cpp @@ -131,7 +131,11 @@ void UPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) { GetUPNPUrls(&urls, &data, dev->get_description_url().utf8().get_data(), 0); char addr[16]; +#if MINIUPNPC_API_VERSION >= 18 + int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16, nullptr, 0); +#else int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16); +#endif if (i != 1) { FreeUPNPUrls(&urls); @@ -229,14 +233,14 @@ Ref<UPNPDevice> UPNP::get_device(int index) const { } void UPNP::add_device(Ref<UPNPDevice> device) { - ERR_FAIL_NULL(device); + ERR_FAIL_COND(device.is_null()); devices.push_back(device); } void UPNP::set_device(int index, Ref<UPNPDevice> device) { ERR_FAIL_INDEX(index, devices.size()); - ERR_FAIL_NULL(device); + ERR_FAIL_COND(device.is_null()); devices.set(index, device); } @@ -257,7 +261,7 @@ Ref<UPNPDevice> UPNP::get_gateway() const { for (int i = 0; i < devices.size(); i++) { Ref<UPNPDevice> dev = get_device(i); - if (dev != nullptr && dev->is_valid_gateway()) { + if (dev.is_valid() && dev->is_valid_gateway()) { return dev; } } @@ -292,7 +296,7 @@ bool UPNP::is_discover_ipv6() const { String UPNP::query_external_address() const { Ref<UPNPDevice> dev = get_gateway(); - if (dev == nullptr) { + if (dev.is_null()) { return ""; } @@ -302,7 +306,7 @@ String UPNP::query_external_address() const { int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { Ref<UPNPDevice> dev = get_gateway(); - if (dev == nullptr) { + if (dev.is_null()) { return UPNP_RESULT_NO_GATEWAY; } @@ -312,7 +316,7 @@ int UPNP::add_port_mapping(int port, int port_internal, String desc, String prot int UPNP::delete_port_mapping(int port, String proto) const { Ref<UPNPDevice> dev = get_gateway(); - if (dev == nullptr) { + if (dev.is_null()) { return UPNP_RESULT_NO_GATEWAY; } diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index b235b6f96c..c89534a60c 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -376,6 +376,25 @@ void AudioStreamPlaybackOggVorbis::seek(double p_time) { } } +void AudioStreamPlaybackOggVorbis::set_is_sample(bool p_is_sample) { + _is_sample = p_is_sample; +} + +bool AudioStreamPlaybackOggVorbis::get_is_sample() const { + return _is_sample; +} + +Ref<AudioSamplePlayback> AudioStreamPlaybackOggVorbis::get_sample_playback() const { + return sample_playback; +} + +void AudioStreamPlaybackOggVorbis::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { + sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } +} + AudioStreamPlaybackOggVorbis::~AudioStreamPlaybackOggVorbis() { if (block_is_allocated) { vorbis_block_clear(&block); @@ -517,6 +536,18 @@ void AudioStreamOggVorbis::get_parameter_list(List<Parameter> *r_parameters) { r_parameters->push_back(Parameter(PropertyInfo(Variant::BOOL, "looping", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CHECKABLE), Variant())); } +Ref<AudioSample> AudioStreamOggVorbis::generate_sample() const { + Ref<AudioSample> sample; + sample.instantiate(); + sample->stream = this; + sample->loop_mode = loop + ? AudioSample::LoopMode::LOOP_FORWARD + : AudioSample::LoopMode::LOOP_DISABLED; + sample->loop_begin = loop_offset; + sample->loop_end = 0; + return sample; +} + void AudioStreamOggVorbis::_bind_methods() { ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "buffer"), &AudioStreamOggVorbis::load_from_buffer); ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file); diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index 64a7815b57..6293951f8d 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -75,6 +75,9 @@ class AudioStreamPlaybackOggVorbis : public AudioStreamPlaybackResampled { Ref<OggPacketSequencePlayback> vorbis_data_playback; Ref<AudioStreamOggVorbis> vorbis_stream; + bool _is_sample = false; + Ref<AudioSamplePlayback> sample_playback; + int _mix_frames(AudioFrame *p_buffer, int p_frames); int _mix_frames_vorbis(AudioFrame *p_buffer, int p_frames); @@ -100,6 +103,11 @@ public: virtual void set_parameter(const StringName &p_name, const Variant &p_value) override; virtual Variant get_parameter(const StringName &p_name) const override; + virtual void set_is_sample(bool p_is_sample) override; + virtual bool get_is_sample() const override; + virtual Ref<AudioSamplePlayback> get_sample_playback() const override; + virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override; + AudioStreamPlaybackOggVorbis() {} ~AudioStreamPlaybackOggVorbis(); }; @@ -159,6 +167,11 @@ public: virtual void get_parameter_list(List<Parameter> *r_parameters) override; + virtual bool can_be_sampled() const override { + return true; + } + virtual Ref<AudioSample> generate_sample() const override; + AudioStreamOggVorbis(); virtual ~AudioStreamOggVorbis(); }; diff --git a/modules/webp/SCsub b/modules/webp/SCsub index e78236a60b..dde4450c23 100644 --- a/modules/webp/SCsub +++ b/modules/webp/SCsub @@ -128,6 +128,7 @@ if env["builtin_libwebp"]: "src/utils/filters_utils.c", "src/utils/huffman_encode_utils.c", "src/utils/huffman_utils.c", + "src/utils/palette.c", "src/utils/quant_levels_dec_utils.c", "src/utils/quant_levels_utils.c", "src/utils/random_utils.c", diff --git a/modules/webrtc/webrtc_peer_connection.cpp b/modules/webrtc/webrtc_peer_connection.cpp index 0a50b677c4..69be873fcf 100644 --- a/modules/webrtc/webrtc_peer_connection.cpp +++ b/modules/webrtc/webrtc_peer_connection.cpp @@ -43,15 +43,20 @@ void WebRTCPeerConnection::set_default_extension(const StringName &p_extension) default_extension = StringName(p_extension, true); } -WebRTCPeerConnection *WebRTCPeerConnection::create() { +WebRTCPeerConnection *WebRTCPeerConnection::create(bool p_notify_postinitialize) { #ifdef WEB_ENABLED - return memnew(WebRTCPeerConnectionJS); + return static_cast<WebRTCPeerConnection *>(ClassDB::creator<WebRTCPeerConnectionJS>(p_notify_postinitialize)); #else if (default_extension == StringName()) { WARN_PRINT_ONCE("No default WebRTC extension configured."); - return memnew(WebRTCPeerConnectionExtension); + return static_cast<WebRTCPeerConnection *>(ClassDB::creator<WebRTCPeerConnectionExtension>(p_notify_postinitialize)); + } + Object *obj = nullptr; + if (p_notify_postinitialize) { + obj = ClassDB::instantiate(default_extension); + } else { + obj = ClassDB::instantiate_without_postinitialization(default_extension); } - Object *obj = ClassDB::instantiate(default_extension); return Object::cast_to<WebRTCPeerConnectionExtension>(obj); #endif } diff --git a/modules/webrtc/webrtc_peer_connection.h b/modules/webrtc/webrtc_peer_connection.h index 0f79c17519..33c95ccd0f 100644 --- a/modules/webrtc/webrtc_peer_connection.h +++ b/modules/webrtc/webrtc_peer_connection.h @@ -85,7 +85,7 @@ public: virtual Error poll() = 0; virtual void close() = 0; - static WebRTCPeerConnection *create(); + static WebRTCPeerConnection *create(bool p_notify_postinitialize = true); WebRTCPeerConnection(); ~WebRTCPeerConnection(); diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 4c2d0159a6..238dd30536 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -139,7 +139,7 @@ <return type="void" /> <param index="0" name="enabled" type="bool" /> <description> - Disable Nagle's algorithm on the underling TCP socket (default). See [method StreamPeerTCP.set_no_delay] for more information. + Disable Nagle's algorithm on the underlying TCP socket (default). See [method StreamPeerTCP.set_no_delay] for more information. [b]Note:[/b] Not available in the Web export. </description> </method> diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 329e5bd532..03a530909b 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -59,8 +59,10 @@ void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int } Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_options) { + ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE); + _clear(); String host; diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h index 38f15c82e5..fe0bc594e6 100644 --- a/modules/websocket/emws_peer.h +++ b/modules/websocket/emws_peer.h @@ -68,7 +68,7 @@ private: String selected_protocol; String requested_url; - static WebSocketPeer *_create() { return memnew(EMWSPeer); } + static WebSocketPeer *_create(bool p_notify_postinitialize) { return static_cast<WebSocketPeer *>(ClassDB::creator<EMWSPeer>(p_notify_postinitialize)); } static void _esws_on_connect(void *obj, char *proto); static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string); static void _esws_on_error(void *obj); diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index 3c0d316bc9..95a1a238e9 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -30,7 +30,7 @@ #include "websocket_peer.h" -WebSocketPeer *(*WebSocketPeer::_create)() = nullptr; +WebSocketPeer *(*WebSocketPeer::_create)(bool p_notify_postinitialize) = nullptr; WebSocketPeer::WebSocketPeer() { } diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index 3110e87071..ef0197cf6c 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -59,7 +59,7 @@ private: virtual Error _send_bind(const PackedByteArray &p_data, WriteMode p_mode = WRITE_MODE_BINARY); protected: - static WebSocketPeer *(*_create)(); + static WebSocketPeer *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); @@ -74,11 +74,11 @@ protected: int max_queued_packets = 2048; public: - static WebSocketPeer *create() { + static WebSocketPeer *create(bool p_notify_postinitialize = true) { if (!_create) { return nullptr; } - return _create(); + return _create(p_notify_postinitialize); } virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0; diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 38cb614847..0a9a4053e3 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -99,6 +99,7 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) { p_tcp->poll(); StreamPeerTCP::Status status = p_tcp->get_status(); if (status == StreamPeerTCP::STATUS_CONNECTED) { + // On Windows, setting TCP_NODELAY may fail if the socket is still connecting. p_tcp->set_no_delay(true); ip_candidates.clear(); return; @@ -124,8 +125,8 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) { /// Server functions /// Error WSLPeer::accept_stream(Ref<StreamPeer> p_stream) { - ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(p_stream.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE); _clear(); @@ -142,6 +143,7 @@ Error WSLPeer::accept_stream(Ref<StreamPeer> p_stream) { } ERR_FAIL_COND_V(connection.is_null() || tcp.is_null(), ERR_INVALID_PARAMETER); is_server = true; + tcp->set_no_delay(true); ready_state = STATE_CONNECTING; handshake_buffer->resize(WSL_MAX_HEADER_SIZE); handshake_buffer->seek(0); @@ -310,7 +312,7 @@ void WSLPeer::_do_client_handshake() { ERR_FAIL_COND(tcp.is_null()); // Try to connect to candidates. - if (resolver.has_more_candidates()) { + if (resolver.has_more_candidates() || tcp->get_status() == StreamPeerTCP::STATUS_CONNECTING) { resolver.try_next_candidate(tcp); if (resolver.has_more_candidates()) { return; // Still pending. @@ -471,9 +473,9 @@ bool WSLPeer::_verify_server_response() { } Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) { - ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE); _clear(); diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h index bf9f5c8527..fb01da7ce2 100644 --- a/modules/websocket/wsl_peer.h +++ b/modules/websocket/wsl_peer.h @@ -49,7 +49,7 @@ class WSLPeer : public WebSocketPeer { private: static CryptoCore::RandomGenerator *_static_rng; - static WebSocketPeer *_create() { return memnew(WSLPeer); } + static WebSocketPeer *_create(bool p_notify_postinitialize) { return static_cast<WebSocketPeer *>(ClassDB::creator<WSLPeer>(p_notify_postinitialize)); } // Callbacks. static ssize_t _wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data); diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index 9fd4511d2b..bd7192520a 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -54,9 +54,10 @@ # supported. webxr_interface.requested_reference_space_types = 'bounded-floor, local-floor, local' # In order to use 'local-floor' or 'bounded-floor' we must also - # mark the features as required or optional. + # mark the features as required or optional. By including 'hand-tracking' + # as an optional feature, it will be enabled if supported. webxr_interface.required_features = 'local-floor' - webxr_interface.optional_features = 'bounded-floor' + webxr_interface.optional_features = 'bounded-floor, hand-tracking' # This will return false if we're unable to even request the session, # however, it can still fail asynchronously later in the process, so we @@ -73,7 +74,10 @@ # This will be the reference space type you ultimately got, out of the # types that you requested above. This is useful if you want the game to # work a little differently in 'bounded-floor' versus 'local-floor'. - print ("Reference space type: " + webxr_interface.reference_space_type) + print("Reference space type: ", webxr_interface.reference_space_type) + # This will be the list of features that were successfully enabled + # (except on browsers that don't support this property). + print("Enabled features: ", webxr_interface.enabled_features) func _webxr_session_ended(): $Button.visible = true @@ -155,13 +159,14 @@ <members> <member name="enabled_features" type="String" setter="" getter="get_enabled_features"> A comma-separated list of features that were successfully enabled by [method XRInterface.initialize] when setting up the WebXR session. - This may include features requested by setting [member required_features] and [member optional_features]. + This may include features requested by setting [member required_features] and [member optional_features], and will only be available after [signal session_started] has been emitted. + [b]Note:[/b] This may not be support by all web browsers, in which case it will be an empty string. </member> <member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features"> A comma-seperated list of optional features used by [method XRInterface.initialize] when setting up the WebXR session. If a user's browser or device doesn't support one of the given features, initialization will continue, but you won't be able to use the requested feature. This doesn't have any effect on the interface when already initialized. - Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features]. + Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url], or include other features like [code]"hand-tracking"[/code] to enable hand tracking. </member> <member name="reference_space_type" type="String" setter="" getter="get_reference_space_type"> The reference space type (from the list of requested types set in the [member requested_reference_space_types] property), that was ultimately used by [method XRInterface.initialize] when setting up the WebXR session. @@ -177,7 +182,7 @@ A comma-seperated list of required features used by [method XRInterface.initialize] when setting up the WebXR session. If a user's browser or device doesn't support one of the given features, initialization will fail and [signal session_failed] will be emitted. This doesn't have any effect on the interface when already initialized. - Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features]. + Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url], or include other features like [code]"hand-tracking"[/code] to enable hand tracking. </member> <member name="session_mode" type="String" setter="set_session_mode" getter="get_session_mode"> The session mode used by [method XRInterface.initialize] when setting up the WebXR session. diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index caa7f217af..1b3e3b6a41 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -45,7 +45,7 @@ enum WebXRInputEvent { }; typedef void (*GodotWebXRSupportedCallback)(char *p_session_mode, int p_supported); -typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type, char *p_enabled_features); +typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type, char *p_enabled_features, char *p_environment_blend_mode); typedef void (*GodotWebXREndedCallback)(); typedef void (*GodotWebXRFailedCallback)(char *p_message); typedef void (*GodotWebXRInputEventCallback)(int p_event_type, int p_input_source_id); diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index 722b448fd5..155409f931 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -67,7 +67,8 @@ const GodotWebXR = { GodotWebXR.orig_requestAnimationFrame = Browser.requestAnimationFrame; } Browser.requestAnimationFrame = enable - ? GodotWebXR.requestAnimationFrame : GodotWebXR.orig_requestAnimationFrame; + ? GodotWebXR.requestAnimationFrame + : GodotWebXR.orig_requestAnimationFrame; }, pauseResumeMainLoop: () => { // Once both GodotWebXR.session and GodotWebXR.space are set or @@ -76,9 +77,9 @@ const GodotWebXR = { // gets picked up automatically, however, in the Oculus Browser // on the Quest, we need to pause and resume the main loop. Browser.mainLoop.pause(); - runtimeKeepalivePush(); // eslint-disable-line no-undef + runtimeKeepalivePush(); window.setTimeout(function () { - runtimeKeepalivePop(); // eslint-disable-line no-undef + runtimeKeepalivePop(); Browser.mainLoop.resume(); }, 0); }, @@ -287,12 +288,12 @@ const GodotWebXR = { // Store onsimpleevent so we can use it later. GodotWebXR.onsimpleevent = onsimpleevent; - const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef + const gl_context_handle = _emscripten_webgl_get_current_context(); const gl = GL.getContext(gl_context_handle).GLctx; GodotWebXR.gl = gl; gl.makeXRCompatible().then(function () { - GodotWebXR.gl_binding = new XRWebGLBinding(session, gl); // eslint-disable-line no-undef + GodotWebXR.gl_binding = new XRWebGLBinding(session, gl); // This will trigger the layer to get created. GodotWebXR.getLayer(); @@ -319,10 +320,14 @@ const GodotWebXR = { // next reference space. window.setTimeout(function () { const reference_space_c_str = GodotRuntime.allocString(reference_space_type); - const enabled_features_c_str = GodotRuntime.allocString(Array.from(session.enabledFeatures).join(',')); - onstarted(reference_space_c_str, enabled_features_c_str); + const enabled_features = 'enabledFeatures' in session ? Array.from(session.enabledFeatures) : []; + const enabled_features_c_str = GodotRuntime.allocString(enabled_features.join(',')); + const environment_blend_mode = 'environmentBlendMode' in session ? session.environmentBlendMode : ''; + const environment_blend_mode_c_str = GodotRuntime.allocString(environment_blend_mode); + onstarted(reference_space_c_str, enabled_features_c_str, environment_blend_mode_c_str); GodotRuntime.free(reference_space_c_str); GodotRuntime.free(enabled_features_c_str); + GodotRuntime.free(environment_blend_mode_c_str); }, 0); } diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js index 35ad33fa93..18125d7869 100644 --- a/modules/webxr/native/webxr.externs.js +++ b/modules/webxr/native/webxr.externs.js @@ -78,6 +78,16 @@ XRSession.prototype.frameRate; XRSession.prototype.supportedFrameRates; /** + * @type {Array<string>} + */ +XRSession.prototype.enabledFeatures; + +/** + * @type {string} + */ +XRSession.prototype.environmentBlendMode; + +/** * @type {?function (Event)} */ XRSession.prototype.onend; diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 916566fc1b..352d495dd4 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -58,7 +58,7 @@ void _emwebxr_on_session_supported(char *p_session_mode, int p_supported) { interface->emit_signal(SNAME("session_supported"), session_mode, p_supported ? true : false); } -void _emwebxr_on_session_started(char *p_reference_space_type, char *p_enabled_features) { +void _emwebxr_on_session_started(char *p_reference_space_type, char *p_enabled_features, char *p_environment_blend_mode) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); @@ -68,6 +68,7 @@ void _emwebxr_on_session_started(char *p_reference_space_type, char *p_enabled_f String reference_space_type = String(p_reference_space_type); interface->_set_reference_space_type(reference_space_type); interface->_set_enabled_features(p_enabled_features); + interface->_set_environment_blend_mode(p_environment_blend_mode); interface->emit_signal(SNAME("session_started")); } @@ -230,6 +231,44 @@ Array WebXRInterfaceJS::get_available_display_refresh_rates() const { return ret; } +Array WebXRInterfaceJS::get_supported_environment_blend_modes() { + Array blend_modes; + // The blend mode can't be changed, so return the current blend mode as the only supported one. + blend_modes.push_back(environment_blend_mode); + return blend_modes; +} + +XRInterface::EnvironmentBlendMode WebXRInterfaceJS::get_environment_blend_mode() const { + return environment_blend_mode; +} + +bool WebXRInterfaceJS::set_environment_blend_mode(EnvironmentBlendMode p_new_environment_blend_mode) { + if (environment_blend_mode == p_new_environment_blend_mode) { + // Environment blend mode can't be changed, but we'll consider it a success to set it + // to what it already is. + return true; + } + return false; +} + +void WebXRInterfaceJS::_set_environment_blend_mode(String p_blend_mode_string) { + if (p_blend_mode_string == "opaque") { + environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE; + } else if (p_blend_mode_string == "additive") { + environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ADDITIVE; + } else if (p_blend_mode_string == "alpha-blend") { + environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ALPHA_BLEND; + } else { + // Not all browsers can give us this information, so as a fallback, + // we'll make some guesses about the blend mode. + if (session_mode == "immersive-ar") { + environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ALPHA_BLEND; + } else { + environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE; + } + } +} + StringName WebXRInterfaceJS::get_name() const { return "WebXR"; }; @@ -336,6 +375,7 @@ void WebXRInterfaceJS::uninitialize() { texture_cache.clear(); reference_space_type.clear(); enabled_features.clear(); + environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE; initialized = false; }; }; @@ -521,8 +561,8 @@ RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) { uint32_t view_count = godot_webxr_get_view_count(); Size2 texture_size = get_render_target_size(); - RID texture = texture_storage->texture_create_external( - view_count == 1 ? GLES3::Texture::TYPE_2D : GLES3::Texture::TYPE_LAYERED, + RID texture = texture_storage->texture_create_from_native_handle( + view_count == 1 ? RS::TEXTURE_TYPE_2D : RS::TEXTURE_TYPE_LAYERED, Image::FORMAT_RGBA8, p_texture_id, (int)texture_size.width, @@ -740,12 +780,20 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { // WebXR doesn't have a palm joint, so we calculate it by finding the middle of the middle finger metacarpal bone. { - // 10 is the WebXR middle finger metacarpal joint, and 12 is the offset to the transform origin. - const float *start_pos = hand_joints + (10 * 16) + 12; - // 11 is the WebXR middle finger phalanx proximal joint, and 12 is the offset to the transform origin. - const float *end_pos = hand_joints + (11 * 16) + 12; - Transform3D palm_transform; - palm_transform.origin = (Vector3(start_pos[0], start_pos[1], start_pos[2]) + Vector3(end_pos[0], end_pos[1], end_pos[2])) / 2.0; + // Start by getting the middle finger metacarpal joint. + // Note: 10 is the WebXR middle finger metacarpal joint. + Transform3D palm_transform = _js_matrix_to_transform(hand_joints + (10 * 16)); + palm_transform.basis *= bone_adjustment; + + // Get the middle finger phalanx position. + // Note: 11 is the WebXR middle finger phalanx proximal joint and 12 is the origin offset. + const float *phalanx_pos = hand_joints + (11 * 16) + 12; + Vector3 phalanx(phalanx_pos[0], phalanx_pos[1], phalanx_pos[2]); + + // Offset the palm half-way towards the phalanx joint. + palm_transform.origin = (palm_transform.origin + phalanx) / 2.0; + + // Set the palm joint and the pose. hand_tracker->set_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM, palm_transform); hand_tracker->set_pose("default", palm_transform, Vector3(), Vector3()); } diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index afce28d410..d02c8d2677 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -60,6 +60,8 @@ private: String reference_space_type; String enabled_features; + XRInterface::EnvironmentBlendMode environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE; + Size2 render_targetsize; RBMap<unsigned int, RID> texture_cache; struct Touch { @@ -113,6 +115,10 @@ public: virtual void set_display_refresh_rate(float p_refresh_rate) override; virtual Array get_available_display_refresh_rates() const override; + virtual Array get_supported_environment_blend_modes() override; + virtual XRInterface::EnvironmentBlendMode get_environment_blend_mode() const override; + virtual bool set_environment_blend_mode(EnvironmentBlendMode p_new_environment_blend_mode) override; + virtual StringName get_name() const override; virtual uint32_t get_capabilities() const override; @@ -136,8 +142,10 @@ public: void _on_input_event(int p_event_type, int p_input_source_id); + // Internal setters used by callbacks from Emscripten. inline void _set_reference_space_type(String p_reference_space_type) { reference_space_type = p_reference_space_type; } inline void _set_enabled_features(String p_enabled_features) { enabled_features = p_enabled_features; } + void _set_environment_blend_mode(String p_blend_mode_string); WebXRInterfaceJS(); ~WebXRInterfaceJS(); diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp index e96d9da7a9..4e182f9787 100644 --- a/modules/zip/zip_packer.cpp +++ b/modules/zip/zip_packer.cpp @@ -48,7 +48,7 @@ Error ZIPPacker::close() { Error err = zipClose(zf, nullptr) == ZIP_OK ? OK : FAILED; if (err == OK) { - DEV_ASSERT(fa == nullptr); + DEV_ASSERT(fa.is_null()); zf = nullptr; } diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp index 123d1e5d46..76f48edb69 100644 --- a/modules/zip/zip_reader.cpp +++ b/modules/zip/zip_reader.cpp @@ -48,7 +48,7 @@ Error ZIPReader::close() { Error err = unzClose(uzf) == UNZ_OK ? OK : FAILED; if (err == OK) { - DEV_ASSERT(fa == nullptr); + DEV_ASSERT(fa.is_null()); uzf = nullptr; } |