diff options
405 files changed, 26622 insertions, 4965 deletions
diff --git a/.clang-format b/.clang-format index 212bc25109..3a2c39a174 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,6 @@ # Commented out parameters are those with the same value as base LLVM style # We can uncomment them if we want to change their value, or enforce the -# chosen value in case the base style changes (last sync: Clang 5.0.0). +# chosen value in case the base style changes (last sync: Clang 6.0.1). --- ### General config, applies to all languages ### BasedOnStyle: LLVM @@ -32,6 +32,7 @@ AllowShortIfStatementsOnASingleLine: true # AfterObjCDeclaration: false # AfterStruct: false # AfterUnion: false +# AfterExternBlock: false # BeforeCatch: false # BeforeElse: false # IndentBraces: false @@ -60,6 +61,7 @@ Cpp11BracedListStyle: false # - foreach # - Q_FOREACH # - BOOST_FOREACH +# IncludeBlocks: Preserve IncludeCategories: - Regex: '".*"' Priority: 1 @@ -69,6 +71,7 @@ IncludeCategories: Priority: 3 # IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: true +# IndentPPDirectives: None IndentWidth: 4 # IndentWrappedFunctionNames: false # JavaScriptQuotes: Leave @@ -86,6 +89,10 @@ IndentWidth: 4 # PenaltyExcessCharacter: 1000000 # PenaltyReturnTypeOnItsOwnLine: 60 # PointerAlignment: Right +# RawStringFormats: +# - Delimiter: pb +# Language: TextProto +# BasedOnStyle: google # ReflowComments: true # SortIncludes: true # SortUsingDeclarations: true diff --git a/.editorconfig b/.editorconfig index e51d0d4c60..d4b7fbb471 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ insert_final_newline = true [*.{cpp,hpp,c,h,mm}] trim_trailing_whitespace = true -[*.py] +[*.{py,cs}] indent_style = space indent_size = 4 diff --git a/.travis.yml b/.travis.yml index 8c24291328..133a134758 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,9 +23,11 @@ matrix: addons: apt: sources: - - llvm-toolchain-trusty-5.0 + - llvm-toolchain-trusty-6.0 + - ubuntu-toolchain-r-test packages: - - clang-format-5.0 + - clang-format-6.0 + - libstdc++6 # >= 4.9 needed for clang-format-6.0 coverity_scan: project: diff --git a/AUTHORS.md b/AUTHORS.md index 12494a487d..d3f0592c89 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -40,14 +40,15 @@ name is available. Ben Brookshire (sheepandshepherd) Benjamin (Nallebeorn) Bernard Liebl (poke1024) - Bojidar Marinov (bojidar-bg) Błażej Szczygieł (zaps166) + Bojidar Marinov (bojidar-bg) + bruvzg Carl Olsson (not-surt) Dana Olson (adolson) Daniel J. Ramirez (djrm) Dmitry Koteroff (Krakean) - Дмитрий Сальников (DmitriySalnikov) Emmanuel Leblond (touilleMan) + est31 Fabio Alessandrelli (Faless) Ferenc Arn (tagcup) Franklin Sobrinho (TheHX) @@ -68,6 +69,7 @@ name is available. J08nY Jakub Grzesik (kubecz3k) Jérôme GULLY (Nutriz) + JFonS Johan Manuel (29jm) Joshua Grams (JoshuaGrams) Juan Linietsky (reduz) @@ -76,15 +78,18 @@ name is available. Kelly Thomas (KellyThomas) Kostadin Damyanov (Max-Might) Leon Krause (eska014) - Marc Gilleron (Zylann) + m4nu3lf Marcelo Fernandez (marcelofg55) + Marc Gilleron (Zylann) Mariano Javier Suligoy (MarianoGnu) Mario Schlack (hurikhan) Martin Sjursen (binbitten) + marynate Masoud BH (masoudbh3) Matthias Hölzl (hoelzl) Max Hilbrunner (mhilbrunner) Michael Alexsander Silva Dias (YeldhamDev) + mrezai Nathan Warden (NathanWarden) Nuno Donato (nunodonato) Ovnuniarchos @@ -102,28 +107,24 @@ name is available. Ray Koopa (RayKoopa) Rémi Verschelde (akien-mga) Roberto F. Arroyo (robfram) + romulox-x + rraallvv Ruslan Mustakov (endragor) Saniko (sanikoyes) SaracenOne + sersoong Theo Hallenius (TheoXD) Thomas Herzog (karroffel) Timo (toger5) - V. Vamsi Krishna (vkbsb) Vinzenz Feenstra (vinzenz) + 박한얼 (volzhs) + V. Vamsi Krishna (vkbsb) Wilhem Barbier (nounoursheureux) Will Nations (willnationsdev) Wilson E. Alvarez (Rubonnek) Xavier Cho (mysticfall) + yg2f (SuperUserNameMan) Yuri Roubinski (Chaosus) Zher Huei Lee (leezh) ZuBsPaCe - 박한얼 (volzhs) - bruvzg - est31 - m4nu3lf - marynate - mrezai - romulox-x - rraallvv - sersoong - yg2f (SuperUserNameMan) + Дмитрий Сальников (DmitriySalnikov) diff --git a/CODEOWNERS b/CODEOWNERS index 30d80990a6..27315f2c9d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -17,7 +17,7 @@ misc/* @akien-mga modules/bullet/* @AndreaCatania modules/enet/* @godotengine/network -modules/gnative/* @karroffel +modules/gdnative/* @karroffel modules/gdscript/* @reduz @vnen @bojidar-bg modules/mbedtls/* @godotengine/network modules/mobile_vr/* @BastiaanOlij @@ -70,6 +70,7 @@ generous deed immortalized in the next stable release of Godot Engine. Johannes Wuensch Josep G. Camarasa Joshua Lesperance + Kyle Szklenski Libre-Dépanne Matthew Bennett Olafur Gislason @@ -87,21 +88,21 @@ generous deed immortalized in the next stable release of Godot Engine. David Churchill Dean Harmon Dexter Miguel - Guilherme Felipe de C. G. da Silva John Justo Delgado Baudí KTL Laurence Bannister + paul gruenbacher Rami Robert Willes Robin Arys + Rodrigo Loli Ronnie Ashlock Rufus Xavier Sarsaparilla ScottMakesGames Thomas Bjarnelöf William Connell Wojciech Chojnacki - Xavier Tan Zaq Poi Alessandra Pereyra @@ -113,13 +114,15 @@ generous deed immortalized in the next stable release of Godot Engine. Cody Parker Corey Auger D - Daniel Eliasinski E.G. + Eric Eric Monson flesk + floopf G Barnes GGGames.org Giovanni Solimeno + Guilherme Felipe de C. G. da Silva Hasen Judy Heath Hayes Jay Horton @@ -133,31 +136,32 @@ generous deed immortalized in the next stable release of Godot Engine. Markus Wiesner Marvin Mohammad Taleb + Neal Barry Nick Nikitin Pablo Cholaky Patrick Schnorbus Pete Goodwin Phyronnaz Ruben Soares Luis + Sindre Sømme Sofox Stoned Xander - Ted Tim Dalporto Trent McPheron - Vladimir ## Silver donors 1D_Inc Adam Carr Adam Smeltzer + Adisibio Alder Stefano Alessandro Senese - Álvaro Domínguez López Anders Jensen-Urstad Anthony Bongiovanni Arda Erol Arthur S. Muszynski + Artur Barichello Aubrey Falconer Avencherus Bailey @@ -168,6 +172,7 @@ generous deed immortalized in the next stable release of Godot Engine. Blair Allen Brandon Bryan Stevenson + Carl Winder Carwyn Edwards Casey Foote Chris Chapin @@ -175,7 +180,6 @@ generous deed immortalized in the next stable release of Godot Engine. Christian Winter Christopher Schmitt Collin Shooltz - Daniel Delgado Corona Daniel Johnson Daniel Kaplan DanielMaximiano @@ -186,6 +190,7 @@ generous deed immortalized in the next stable release of Godot Engine. Dominik Wetzel Duy Kevin Nguyen Edward Herbert + Edwin Acosta Eric Martini Fabian Becker fengjiongmax @@ -194,10 +199,10 @@ generous deed immortalized in the next stable release of Godot Engine. Gerrit Großkopf Gerrit Procee Gilberto K. Otubo + Guillaume Laforte Guldoman Gumichan01 Heribert Hirth - hubert jenkins Hunter Jones ialex32x Ivan Vodopiviz @@ -222,16 +227,15 @@ generous deed immortalized in the next stable release of Godot Engine. Kevin Kamper Meejach Petersen Klavdij Voncina Krzysztof Jankowski - Lars pfeffer Linus Lind Lundgren Luis Moraes Macil magodev Martin Eigel Martins Odabi - Matthew Fitzpatrick Max R.R. Collada Maxwell + Mertcan Mermerkaya mhilbrunner Michael Dürwald Michael Gringauz @@ -246,10 +250,8 @@ generous deed immortalized in the next stable release of Godot Engine. Niclas Eriksen Nicolas SAN AGUSTIN Niko Leopold - nivardus Noi Sek Oleg Tyshchenko - Oleksandr Yemets Pablo Seibelt Pan Ip Pat LaBine @@ -260,28 +262,28 @@ generous deed immortalized in the next stable release of Godot Engine. Pierre-Igor Berthet Pietro Vertechi Piotr Kaczmarski - Rea Rémi Verschelde Richman Stewart Roger Burgess Roger Smith Roman Tinkov Ryan Whited - Samuel El-Borai Sasori Olkof Sootstone Stefan Butucea Theo Cranmore Thibault Barbaroux Thomas Bell + Thomas Hermansen + Thomas Holmes Thomas Kurz - Tomasz Wacławek Tom Larrow Tyler Stafos UltyX Victor Victor Gonzalez Fernandez Viktor Ferenczi + waka nya werner mendizabal Wout Standaert Yu He diff --git a/SConstruct b/SConstruct index cb1e9fd567..1135f7a3bd 100644 --- a/SConstruct +++ b/SConstruct @@ -145,6 +145,7 @@ opts.Add(EnumVariable('bits', "Target platform bits", 'default', ('default', '32 opts.Add('p', "Platform (alias for 'platform')", '') opts.Add('platform', "Target platform (%s)" % ('|'.join(platform_list), ), '') opts.Add(EnumVariable('target', "Compilation target", 'debug', ('debug', 'release_debug', 'release'))) +opts.Add(EnumVariable('optimize', "Optimization type", 'speed', ('speed', 'size'))) opts.Add(BoolVariable('tools', "Build the tools (a.k.a. the Godot editor)", True)) opts.Add(BoolVariable('use_lto', 'Use link-time optimization', False)) @@ -408,6 +409,11 @@ if selected_platform in platform_list: env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"] env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"] + # (SH)LIBSUFFIX will be used for our own built libraries + # LIBSUFFIXES contains LIBSUFFIX and SHLIBSUFFIX by default, + # so we need to append the default suffixes to keep the ability + # to link against thirdparty libraries (.a, .so, .dll, etc.). + env["LIBSUFFIXES"] += [env["LIBSUFFIX"], env["SHLIBSUFFIX"]] env["LIBSUFFIX"] = suffix + env["LIBSUFFIX"] env["SHLIBSUFFIX"] = suffix + env["SHLIBSUFFIX"] @@ -416,11 +422,19 @@ if selected_platform in platform_list: if env['tools']: env.Append(CPPDEFINES=['TOOLS_ENABLED']) if env['disable_3d']: - env.Append(CPPDEFINES=['_3D_DISABLED']) + if env['tools']: + print("Build option 'disable_3d=yes' cannot be used with 'tools=yes' (editor), only with 'tools=no' (export template).") + sys.exit(255) + else: + env.Append(CPPDEFINES=['_3D_DISABLED']) if env['gdscript']: env.Append(CPPDEFINES=['GDSCRIPT_ENABLED']) if env['disable_advanced_gui']: - env.Append(CPPDEFINES=['ADVANCED_GUI_DISABLED']) + if env['tools']: + print("Build option 'disable_advanced_gui=yes' cannot be used with 'tools=yes' (editor), only with 'tools=no' (export template).") + sys.exit(255) + else: + env.Append(CPPDEFINES=['ADVANCED_GUI_DISABLED']) if env['minizip']: env.Append(CPPDEFINES=['MINIZIP_ENABLED']) if env['xml']: diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 7a14e85f20..af1d49ae8c 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -221,6 +221,10 @@ String _OS::get_audio_driver_name(int p_driver) const { return OS::get_singleton()->get_audio_driver_name(p_driver); } +PoolStringArray _OS::get_connected_midi_inputs() { + return OS::get_singleton()->get_connected_midi_inputs(); +} + void _OS::set_video_mode(const Size2 &p_size, bool p_fullscreen, bool p_resizeable, int p_screen) { OS::VideoMode vm; @@ -1058,6 +1062,7 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_video_driver_name", "driver"), &_OS::get_video_driver_name); ClassDB::bind_method(D_METHOD("get_audio_driver_count"), &_OS::get_audio_driver_count); ClassDB::bind_method(D_METHOD("get_audio_driver_name", "driver"), &_OS::get_audio_driver_name); + ClassDB::bind_method(D_METHOD("get_connected_midi_inputs"), &_OS::get_connected_midi_inputs); ClassDB::bind_method(D_METHOD("get_screen_count"), &_OS::get_screen_count); ClassDB::bind_method(D_METHOD("get_current_screen"), &_OS::get_current_screen); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 48b7b74005..1729c23779 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -152,6 +152,8 @@ public: virtual int get_audio_driver_count() const; virtual String get_audio_driver_name(int p_driver) const; + virtual PoolStringArray get_connected_midi_inputs(); + virtual int get_screen_count() const; virtual int get_current_screen() const; virtual void set_current_screen(int p_screen); diff --git a/core/dictionary.cpp b/core/dictionary.cpp index d68411a572..42d9eab310 100644 --- a/core/dictionary.cpp +++ b/core/dictionary.cpp @@ -140,6 +140,11 @@ void Dictionary::erase(const Variant &p_key) { _p->variant_map.erase(p_key); } +bool Dictionary::erase_checked(const Variant &p_key) { + + return _p->variant_map.erase(p_key); +} + bool Dictionary::operator==(const Dictionary &p_dictionary) const { return _p == p_dictionary._p; diff --git a/core/dictionary.h b/core/dictionary.h index 84a5cafe1d..00ec67fb99 100644 --- a/core/dictionary.h +++ b/core/dictionary.h @@ -66,6 +66,7 @@ public: bool has_all(const Array &p_keys) const; void erase(const Variant &p_key); + bool erase_checked(const Variant &p_key); bool operator==(const Dictionary &p_dictionary) const; diff --git a/core/dvector.h b/core/dvector.h index c0190fb9e3..e03a755e6c 100644 --- a/core/dvector.h +++ b/core/dvector.h @@ -150,7 +150,7 @@ class PoolVector { } if (old_alloc->refcount.unref() == true) { - //this should never happen but.. + //this should never happen but.. #ifdef DEBUG_ENABLED MemoryPool::alloc_mutex->lock(); diff --git a/core/global_constants.cpp b/core/global_constants.cpp index 5b4dd05dbf..187813f9d0 100644 --- a/core/global_constants.cpp +++ b/core/global_constants.cpp @@ -89,6 +89,7 @@ VARIANT_ENUM_CAST(KeyList); VARIANT_ENUM_CAST(KeyModifierMask); VARIANT_ENUM_CAST(ButtonList); VARIANT_ENUM_CAST(JoystickList); +VARIANT_ENUM_CAST(MidiMessageList); void register_global_constants() { @@ -458,6 +459,15 @@ void register_global_constants() { BIND_GLOBAL_ENUM_CONSTANT(JOY_ANALOG_L2); BIND_GLOBAL_ENUM_CONSTANT(JOY_ANALOG_R2); + // midi + BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_NOTE_OFF); + BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_NOTE_ON); + BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_AFTERTOUCH); + BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_CONTROL_CHANGE); + BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_PROGRAM_CHANGE); + BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_CHANNEL_PRESSURE); + BIND_GLOBAL_ENUM_CONSTANT(MIDI_MESSAGE_PITCH_BEND); + // error list BIND_GLOBAL_ENUM_CONSTANT(OK); diff --git a/core/image.cpp b/core/image.cpp index b0bed80a6f..19440d1718 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -33,6 +33,7 @@ #include "core/io/image_loader.h" #include "core/os/copymem.h" #include "hash_map.h" +#include "math_funcs.h" #include "print_string.h" #include "thirdparty/misc/hq2x.h" @@ -525,7 +526,7 @@ static double _bicubic_interp_kernel(double x) { } template <int CC> -static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { +static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { // get source image size int width = p_src_width; @@ -555,7 +556,7 @@ static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_wi // initial pixel value - uint8_t *dst = p_dst + (y * p_dst_width + x) * CC; + uint8_t *__restrict dst = p_dst + (y * p_dst_width + x) * CC; double color[CC]; for (int i = 0; i < CC; i++) { @@ -583,7 +584,7 @@ static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_wi ox2 = xmax; // get pixel of original image - const uint8_t *p = p_src + (oy2 * p_src_width + ox2) * CC; + const uint8_t *__restrict p = p_src + (oy2 * p_src_width + ox2) * CC; for (int i = 0; i < CC; i++) { @@ -600,7 +601,7 @@ static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_wi } template <int CC> -static void _scale_bilinear(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { +static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { enum { FRAC_BITS = 8, @@ -655,7 +656,7 @@ static void _scale_bilinear(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src } template <int CC> -static void _scale_nearest(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { +static void _scale_nearest(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { for (uint32_t i = 0; i < p_dst_height; i++) { @@ -676,6 +677,16 @@ static void _scale_nearest(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_ } } +static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, float p_alpha, uint32_t p_width, uint32_t p_height, uint32_t p_pixel_size) { + + uint16_t alpha = CLAMP((uint16_t)(p_alpha * 256.0f), 0, 256); + + for (uint32_t i = 0; i < p_width * p_height * p_pixel_size; i++) { + + p_dst[i] = (p_dst[i] * (256 - alpha) + p_src[i] * alpha) >> 8; + } +} + void Image::resize_to_po2(bool p_square) { if (!_can_modify(format)) { @@ -707,6 +718,8 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { ERR_FAIL(); } + bool mipmap_aware = p_interpolation == INTERPOLATE_TRILINEAR /* || p_interpolation == INTERPOLATE_TRICUBIC */; + ERR_FAIL_COND(p_width <= 0); ERR_FAIL_COND(p_height <= 0); ERR_FAIL_COND(p_width > MAX_WIDTH); @@ -717,6 +730,32 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { Image dst(p_width, p_height, 0, format); + // Setup mipmap-aware scaling + Image dst2; + int mip1; + int mip2; + float mip1_weight; + if (mipmap_aware) { + float avg_scale = ((float)p_width / width + (float)p_height / height) * 0.5f; + if (avg_scale >= 1.0f) { + mipmap_aware = false; + } else { + float level = Math::log(1.0f / avg_scale) / Math::log(2.0f); + mip1 = CLAMP((int)Math::floor(level), 0, get_mipmap_count()); + mip2 = CLAMP((int)Math::ceil(level), 0, get_mipmap_count()); + mip1_weight = 1.0f - (level - mip1); + } + } + bool interpolate_mipmaps = mipmap_aware && mip1 != mip2; + if (interpolate_mipmaps) { + dst2.create(p_width, p_height, 0, format); + } + bool had_mipmaps = mipmaps; + if (interpolate_mipmaps && !had_mipmaps) { + generate_mipmaps(); + } + // -- + PoolVector<uint8_t>::Read r = data.read(); const unsigned char *r_ptr = r.ptr(); @@ -734,13 +773,57 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { case 4: _scale_nearest<4>(r_ptr, w_ptr, width, height, p_width, p_height); break; } } break; - case INTERPOLATE_BILINEAR: { + case INTERPOLATE_BILINEAR: + case INTERPOLATE_TRILINEAR: { + + for (int i = 0; i < 2; ++i) { + int src_width; + int src_height; + const unsigned char *src_ptr; + + if (!mipmap_aware) { + if (i == 0) { + // Standard behavior + src_width = width; + src_height = height; + src_ptr = r_ptr; + } else { + // No need for a second iteration + break; + } + } else { + if (i == 0) { + // Read from the first mipmap that will be interpolated + // (if both levels are the same, we will not interpolate, but at least we'll sample from the right level) + int offs; + _get_mipmap_offset_and_size(mip1, offs, src_width, src_height); + src_ptr = r_ptr + offs; + } else if (!interpolate_mipmaps) { + // No need generate a second image + break; + } else { + // Switch to read from the second mipmap that will be interpolated + int offs; + _get_mipmap_offset_and_size(mip2, offs, src_width, src_height); + src_ptr = r_ptr + offs; + // Switch to write to the second destination image + w = dst2.data.write(); + w_ptr = w.ptr(); + } + } - switch (get_format_pixel_size(format)) { - case 1: _scale_bilinear<1>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 2: _scale_bilinear<2>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 3: _scale_bilinear<3>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 4: _scale_bilinear<4>(r_ptr, w_ptr, width, height, p_width, p_height); break; + switch (get_format_pixel_size(format)) { + case 1: _scale_bilinear<1>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 2: _scale_bilinear<2>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 3: _scale_bilinear<3>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 4: _scale_bilinear<4>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + } + } + + if (interpolate_mipmaps) { + // Switch to read again from the first scaled mipmap to overlay it over the second + r = dst.data.read(); + _overlay(r.ptr(), w.ptr(), mip1_weight, p_width, p_height, get_format_pixel_size(format)); } } break; @@ -759,7 +842,11 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { r = PoolVector<uint8_t>::Read(); w = PoolVector<uint8_t>::Write(); - if (mipmaps > 0) + if (interpolate_mipmaps) { + dst._copy_internals_from(dst2); + } + + if (had_mipmaps) dst.generate_mipmaps(); _copy_internals_from(dst); @@ -1898,8 +1985,9 @@ void Image::fill(const Color &c) { unlock(); } -Ref<Image> (*Image::_png_mem_loader_func)(const uint8_t *, int) = NULL; -Ref<Image> (*Image::_jpg_mem_loader_func)(const uint8_t *, int) = NULL; +ImageMemLoadFunc Image::_png_mem_loader_func = NULL; +ImageMemLoadFunc Image::_jpg_mem_loader_func = NULL; +ImageMemLoadFunc Image::_webp_mem_loader_func = NULL; void (*Image::_image_compress_bc_func)(Image *, Image::CompressSource) = NULL; void (*Image::_image_compress_pvrtc2_func)(Image *) = NULL; @@ -2357,6 +2445,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("load_png_from_buffer", "buffer"), &Image::load_png_from_buffer); ClassDB::bind_method(D_METHOD("load_jpg_from_buffer", "buffer"), &Image::load_jpg_from_buffer); + ClassDB::bind_method(D_METHOD("load_webp_from_buffer", "buffer"), &Image::load_webp_from_buffer); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_data", "_get_data"); @@ -2402,6 +2491,7 @@ void Image::_bind_methods() { BIND_ENUM_CONSTANT(INTERPOLATE_NEAREST); BIND_ENUM_CONSTANT(INTERPOLATE_BILINEAR); BIND_ENUM_CONSTANT(INTERPOLATE_CUBIC); + BIND_ENUM_CONSTANT(INTERPOLATE_TRILINEAR); BIND_ENUM_CONSTANT(ALPHA_NONE); BIND_ENUM_CONSTANT(ALPHA_BIT); @@ -2649,32 +2739,26 @@ String Image::get_format_name(Format p_format) { } Error Image::load_png_from_buffer(const PoolVector<uint8_t> &p_array) { - - int buffer_size = p_array.size(); - - ERR_FAIL_COND_V(buffer_size == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!_png_mem_loader_func, ERR_INVALID_PARAMETER); - - PoolVector<uint8_t>::Read r = p_array.read(); - - Ref<Image> image = _png_mem_loader_func(r.ptr(), buffer_size); - ERR_FAIL_COND_V(!image.is_valid(), ERR_PARSE_ERROR); - - copy_internals_from(image); - - return OK; + return _load_from_buffer(p_array, _png_mem_loader_func); } Error Image::load_jpg_from_buffer(const PoolVector<uint8_t> &p_array) { + return _load_from_buffer(p_array, _jpg_mem_loader_func); +} + +Error Image::load_webp_from_buffer(const PoolVector<uint8_t> &p_array) { + return _load_from_buffer(p_array, _webp_mem_loader_func); +} +Error Image::_load_from_buffer(const PoolVector<uint8_t> &p_array, ImageMemLoadFunc p_loader) { int buffer_size = p_array.size(); ERR_FAIL_COND_V(buffer_size == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!_jpg_mem_loader_func, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!p_loader, ERR_INVALID_PARAMETER); PoolVector<uint8_t>::Read r = p_array.read(); - Ref<Image> image = _jpg_mem_loader_func(r.ptr(), buffer_size); + Ref<Image> image = p_loader(r.ptr(), buffer_size); ERR_FAIL_COND_V(!image.is_valid(), ERR_PARSE_ERROR); copy_internals_from(image); diff --git a/core/image.h b/core/image.h index 43516e2c0b..8c4854e053 100644 --- a/core/image.h +++ b/core/image.h @@ -47,6 +47,7 @@ class Image; typedef Error (*SavePNGFunc)(const String &p_path, const Ref<Image> &p_img); +typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size); class Image : public Resource { GDCLASS(Image, Resource); @@ -107,6 +108,8 @@ public: INTERPOLATE_NEAREST, INTERPOLATE_BILINEAR, INTERPOLATE_CUBIC, + INTERPOLATE_TRILINEAR, + /* INTERPOLATE_TRICUBIC, */ /* INTERPOLATE GAUSS */ }; @@ -118,8 +121,9 @@ public: //some functions provided by something else - static Ref<Image> (*_png_mem_loader_func)(const uint8_t *p_png, int p_size); - static Ref<Image> (*_jpg_mem_loader_func)(const uint8_t *p_png, int p_size); + static ImageMemLoadFunc _png_mem_loader_func; + static ImageMemLoadFunc _jpg_mem_loader_func; + static ImageMemLoadFunc _webp_mem_loader_func; static void (*_image_compress_bc_func)(Image *, CompressSource p_source); static void (*_image_compress_pvrtc2_func)(Image *); @@ -175,6 +179,8 @@ private: void _set_data(const Dictionary &p_data); Dictionary _get_data() const; + Error _load_from_buffer(const PoolVector<uint8_t> &p_array, ImageMemLoadFunc p_loader); + public: int get_width() const; ///< Get image width int get_height() const; ///< Get image height @@ -302,6 +308,7 @@ public: Error load_png_from_buffer(const PoolVector<uint8_t> &p_array); Error load_jpg_from_buffer(const PoolVector<uint8_t> &p_array); + Error load_webp_from_buffer(const PoolVector<uint8_t> &p_array); Image(const uint8_t *p_mem_png_jpg, int p_len = -1); Image(const char **p_xpm); diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index fcbb22b5de..f1620f1493 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -279,6 +279,7 @@ void HTTPClient::close() { chunk_left = 0; read_until_eof = false; response_num = 0; + handshaking = false; } Error HTTPClient::poll() { @@ -327,16 +328,40 @@ Error HTTPClient::poll() { } break; case StreamPeerTCP::STATUS_CONNECTED: { if (ssl) { - Ref<StreamPeerSSL> ssl = StreamPeerSSL::create(); - Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); - if (err != OK) { + Ref<StreamPeerSSL> ssl; + if (!handshaking) { + // Connect the StreamPeerSSL and start handshaking + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ssl->set_blocking_handshake_enabled(false); + Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); + if (err != OK) { + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + connection = ssl; + handshaking = true; + } else { + // We are already handshaking, which means we can use your already active SSL connection + ssl = static_cast<Ref<StreamPeerSSL> >(connection); + ssl->poll(); // Try to finish the handshake + } + + if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { + // Handshake has been successfull + handshaking = false; + status = STATUS_CONNECTED; + return OK; + } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { + // Handshake has failed close(); status = STATUS_SSL_HANDSHAKE_ERROR; return ERR_CANT_CONNECT; } - connection = ssl; + // ... we will need to poll more for handshake to finish + } else { + status = STATUS_CONNECTED; } - status = STATUS_CONNECTED; return OK; } break; case StreamPeerTCP::STATUS_ERROR: @@ -669,6 +694,7 @@ HTTPClient::HTTPClient() { response_num = 0; ssl = false; blocking = false; + handshaking = false; read_chunk_size = 4096; } diff --git a/core/io/http_client.h b/core/io/http_client.h index 38ec82ce8c..82b56b01db 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -165,6 +165,7 @@ private: bool ssl; bool ssl_verify_host; bool blocking; + bool handshaking; Vector<uint8_t> response_str; diff --git a/core/io/stream_peer_ssl.cpp b/core/io/stream_peer_ssl.cpp index 012ba78c6d..c71af6b641 100644 --- a/core/io/stream_peer_ssl.cpp +++ b/core/io/stream_peer_ssl.cpp @@ -52,6 +52,14 @@ bool StreamPeerSSL::is_available() { return available; } +void StreamPeerSSL::set_blocking_handshake_enabled(bool p_enabled) { + blocking_handshake = p_enabled; +} + +bool StreamPeerSSL::is_blocking_handshake_enabled() const { + return blocking_handshake; +} + PoolByteArray StreamPeerSSL::get_project_cert_array() { PoolByteArray out; @@ -84,16 +92,21 @@ PoolByteArray StreamPeerSSL::get_project_cert_array() { void StreamPeerSSL::_bind_methods() { ClassDB::bind_method(D_METHOD("poll"), &StreamPeerSSL::poll); - ClassDB::bind_method(D_METHOD("accept_stream", "stream"), &StreamPeerSSL::accept_stream); + ClassDB::bind_method(D_METHOD("accept_stream"), &StreamPeerSSL::accept_stream); ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "validate_certs", "for_hostname"), &StreamPeerSSL::connect_to_stream, DEFVAL(false), DEFVAL(String())); ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerSSL::get_status); ClassDB::bind_method(D_METHOD("disconnect_from_stream"), &StreamPeerSSL::disconnect_from_stream); + ClassDB::bind_method(D_METHOD("set_blocking_handshake_enabled", "enabled"), &StreamPeerSSL::set_blocking_handshake_enabled); + ClassDB::bind_method(D_METHOD("is_blocking_handshake_enabled"), &StreamPeerSSL::is_blocking_handshake_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blocking_handshake"), "set_blocking_handshake_enabled", "is_blocking_handshake_enabled"); BIND_ENUM_CONSTANT(STATUS_DISCONNECTED); BIND_ENUM_CONSTANT(STATUS_CONNECTED); - BIND_ENUM_CONSTANT(STATUS_ERROR_NO_CERTIFICATE); + BIND_ENUM_CONSTANT(STATUS_ERROR); BIND_ENUM_CONSTANT(STATUS_ERROR_HOSTNAME_MISMATCH); } StreamPeerSSL::StreamPeerSSL() { + blocking_handshake = true; } diff --git a/core/io/stream_peer_ssl.h b/core/io/stream_peer_ssl.h index 77301a7c87..870704e875 100644 --- a/core/io/stream_peer_ssl.h +++ b/core/io/stream_peer_ssl.h @@ -49,14 +49,20 @@ protected: friend class Main; static bool initialize_certs; + bool blocking_handshake; + public: enum Status { STATUS_DISCONNECTED, + STATUS_HANDSHAKING, STATUS_CONNECTED, - STATUS_ERROR_NO_CERTIFICATE, + STATUS_ERROR, STATUS_ERROR_HOSTNAME_MISMATCH }; + void set_blocking_handshake_enabled(bool p_enabled); + bool is_blocking_handshake_enabled() const; + virtual void poll() = 0; virtual Error accept_stream(Ref<StreamPeer> p_base) = 0; virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String()) = 0; diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp index 16d5e3c282..85c1fc5ddf 100644 --- a/core/io/translation_loader_po.cpp +++ b/core/io/translation_loader_po.cpp @@ -54,32 +54,25 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error, const S int line = 1; bool skip_this = false; bool skip_next = false; + bool is_eof = false; - while (true) { + while (!is_eof) { - String l = f->get_line(); + String l = f->get_line().strip_edges(); + is_eof = f->eof_reached(); - if (f->eof_reached()) { + // If we reached last line and it's not a content line, break, otherwise let processing that last loop + if (is_eof && l.empty()) { - if (status == STATUS_READING_STRING) { - - if (msg_id != "") { - if (!skip_this) - translation->add_message(msg_id, msg_str); - } else if (config == "") - config = msg_str; - break; - - } else if (status == STATUS_NONE) + if (status == STATUS_READING_ID) { + memdelete(f); + ERR_EXPLAIN(p_path + ":" + itos(line) + " Unexpected EOF while reading 'msgid' at file: "); + ERR_FAIL_V(RES()); + } else { break; - - memdelete(f); - ERR_EXPLAIN(p_path + ":" + itos(line) + " Unexpected EOF while reading 'msgid' at file: "); - ERR_FAIL_V(RES()); + } } - l = l.strip_edges(); - if (l.begins_with("msgid")) { if (status == STATUS_READING_ID) { @@ -160,6 +153,15 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error, const S f->close(); memdelete(f); + if (status == STATUS_READING_STRING) { + + if (msg_id != "") { + if (!skip_this) + translation->add_message(msg_id, msg_str); + } else if (config == "") + config = msg_str; + } + if (config == "") { ERR_EXPLAIN("No config found in file: " + p_path); ERR_FAIL_V(RES()); diff --git a/core/math/aabb.cpp b/core/math/aabb.cpp index cff19f990c..e2e71dda92 100644 --- a/core/math/aabb.cpp +++ b/core/math/aabb.cpp @@ -245,7 +245,6 @@ Vector3 AABB::get_longest_axis() const { if (size.z > max_size) { axis = Vector3(0, 0, 1); - max_size = size.z; } return axis; @@ -262,7 +261,6 @@ int AABB::get_longest_axis_index() const { if (size.z > max_size) { axis = 2; - max_size = size.z; } return axis; @@ -280,7 +278,6 @@ Vector3 AABB::get_shortest_axis() const { if (size.z < max_size) { axis = Vector3(0, 0, 1); - max_size = size.z; } return axis; @@ -297,7 +294,6 @@ int AABB::get_shortest_axis_index() const { if (size.z < max_size) { axis = 2; - max_size = size.z; } return axis; diff --git a/core/math/bsp_tree.cpp b/core/math/bsp_tree.cpp index b1424e1d78..2e184f7a88 100644 --- a/core/math/bsp_tree.cpp +++ b/core/math/bsp_tree.cpp @@ -244,10 +244,8 @@ bool BSP_Tree::point_is_inside(const Vector3 &p_point) const { const Node *nodesptr = &nodes[0]; const Plane *planesptr = &planes[0]; - int plane_count = planes.size(); int idx = node_count - 1; - int steps = 0; while (true) { @@ -259,21 +257,19 @@ bool BSP_Tree::point_is_inside(const Vector3 &p_point) const { return true; } - uint16_t plane = nodesptr[idx].plane; #ifdef DEBUG_ENABLED - + int plane_count = planes.size(); + uint16_t plane = nodesptr[idx].plane; ERR_FAIL_INDEX_V(plane, plane_count, false); #endif + bool over = planesptr[nodesptr[idx].plane].is_point_over(p_point); idx = over ? nodes[idx].over : nodes[idx].under; #ifdef DEBUG_ENABLED - ERR_FAIL_COND_V(idx < MAX_NODES && idx >= node_count, false); #endif - - steps++; } return false; diff --git a/core/message_queue.cpp b/core/message_queue.cpp index 25ee6eafae..3adaad868a 100644 --- a/core/message_queue.cpp +++ b/core/message_queue.cpp @@ -342,7 +342,7 @@ MessageQueue::MessageQueue() { buffer_end = 0; buffer_max_used = 0; - buffer_size = GLOBAL_DEF("memory/limits/message_queue/max_size_kb", DEFAULT_QUEUE_SIZE_KB); + buffer_size = GLOBAL_DEF_RST("memory/limits/message_queue/max_size_kb", DEFAULT_QUEUE_SIZE_KB); buffer_size *= 1024; buffer = memnew_arr(uint8_t, buffer_size); } diff --git a/core/object.cpp b/core/object.cpp index 1d2aeb7ba5..d86c60a3b8 100644 --- a/core/object.cpp +++ b/core/object.cpp @@ -601,8 +601,12 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons _get_property_listv(p_list, p_reversed); - if (!is_class("Script")) // can still be set, but this is for userfriendlyness + if (!is_class("Script")) { // can still be set, but this is for userfriendlyness +#ifdef TOOLS_ENABLED + p_list->push_back(PropertyInfo(Variant::NIL, "Script", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); +#endif p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NONZERO)); + } #ifdef TOOLS_ENABLED if (editor_section_folding.size()) { p_list->push_back(PropertyInfo(Variant::ARRAY, CoreStringNames::get_singleton()->_sections_unfolded, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); diff --git a/core/os/input_event.cpp b/core/os/input_event.cpp index ca6446d015..e94ccb4f48 100644 --- a/core/os/input_event.cpp +++ b/core/os/input_event.cpp @@ -1080,3 +1080,122 @@ InputEventPanGesture::InputEventPanGesture() { delta = Vector2(0, 0); } +///////////////////////////// + +void InputEventMIDI::set_channel(const int p_channel) { + + channel = p_channel; +} + +int InputEventMIDI::get_channel() const { + return channel; +} + +void InputEventMIDI::set_message(const int p_message) { + + message = p_message; +} + +int InputEventMIDI::get_message() const { + return message; +} + +void InputEventMIDI::set_pitch(const int p_pitch) { + + pitch = p_pitch; +} + +int InputEventMIDI::get_pitch() const { + return pitch; +} + +void InputEventMIDI::set_velocity(const int p_velocity) { + + velocity = p_velocity; +} + +int InputEventMIDI::get_velocity() const { + return velocity; +} + +void InputEventMIDI::set_instrument(const int p_instrument) { + + instrument = p_instrument; +} + +int InputEventMIDI::get_instrument() const { + return instrument; +} + +void InputEventMIDI::set_pressure(const int p_pressure) { + + pressure = p_pressure; +} + +int InputEventMIDI::get_pressure() const { + return pressure; +} + +void InputEventMIDI::set_controller_number(const int p_controller_number) { + + controller_number = p_controller_number; +} + +int InputEventMIDI::get_controller_number() const { + return controller_number; +} + +void InputEventMIDI::set_controller_value(const int p_controller_value) { + + controller_value = p_controller_value; +} + +int InputEventMIDI::get_controller_value() const { + return controller_value; +} + +String InputEventMIDI::as_text() const { + + return "InputEventMIDI : channel=(" + itos(get_channel()) + "), message=(" + itos(get_message()) + ")"; +} + +void InputEventMIDI::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_channel", "channel"), &InputEventMIDI::set_channel); + ClassDB::bind_method(D_METHOD("get_channel"), &InputEventMIDI::get_channel); + ClassDB::bind_method(D_METHOD("set_message", "message"), &InputEventMIDI::set_message); + ClassDB::bind_method(D_METHOD("get_message"), &InputEventMIDI::get_message); + ClassDB::bind_method(D_METHOD("set_pitch", "pitch"), &InputEventMIDI::set_pitch); + ClassDB::bind_method(D_METHOD("get_pitch"), &InputEventMIDI::get_pitch); + ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &InputEventMIDI::set_velocity); + ClassDB::bind_method(D_METHOD("get_velocity"), &InputEventMIDI::get_velocity); + ClassDB::bind_method(D_METHOD("set_instrument", "instrument"), &InputEventMIDI::set_instrument); + ClassDB::bind_method(D_METHOD("get_instrument"), &InputEventMIDI::get_instrument); + ClassDB::bind_method(D_METHOD("set_pressure", "pressure"), &InputEventMIDI::set_pressure); + ClassDB::bind_method(D_METHOD("get_pressure"), &InputEventMIDI::get_pressure); + ClassDB::bind_method(D_METHOD("set_controller_number", "controller_number"), &InputEventMIDI::set_controller_number); + ClassDB::bind_method(D_METHOD("get_controller_number"), &InputEventMIDI::get_controller_number); + ClassDB::bind_method(D_METHOD("set_controller_value", "controller_value"), &InputEventMIDI::set_controller_value); + ClassDB::bind_method(D_METHOD("get_controller_value"), &InputEventMIDI::get_controller_value); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "channel"), "set_channel", "get_channel"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "message"), "set_message", "get_message"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "pitch"), "set_pitch", "get_pitch"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "velocity"), "set_velocity", "get_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "instrument"), "set_instrument", "get_instrument"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "pressure"), "set_pressure", "get_pressure"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "controller_number"), "set_controller_number", "get_controller_number"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "controller_value"), "set_controller_value", "get_controller_value"); +} + +InputEventMIDI::InputEventMIDI() { + + channel = 0; + message = 0; + pitch = 0; + velocity = 0; + instrument = 0; + pressure = 0; + controller_number = 0; + controller_value = 0; +} diff --git a/core/os/input_event.h b/core/os/input_event.h index bd1a85ce29..04126fee77 100644 --- a/core/os/input_event.h +++ b/core/os/input_event.h @@ -140,6 +140,16 @@ enum JoystickList { JOY_ANALOG_R2 = JOY_AXIS_7, }; +enum MidiMessageList { + MIDI_MESSAGE_NOTE_OFF = 0x8, + MIDI_MESSAGE_NOTE_ON = 0x9, + MIDI_MESSAGE_AFTERTOUCH = 0xA, + MIDI_MESSAGE_CONTROL_CHANGE = 0xB, + MIDI_MESSAGE_PROGRAM_CHANGE = 0xC, + MIDI_MESSAGE_CHANNEL_PRESSURE = 0xD, + MIDI_MESSAGE_PITCH_BEND = 0xE, +}; + /** * Input Modifier Status * for keyboard/mouse events. @@ -525,4 +535,50 @@ public: InputEventPanGesture(); }; + +class InputEventMIDI : public InputEvent { + GDCLASS(InputEventMIDI, InputEvent) + + int channel; + int message; + int pitch; + int velocity; + int instrument; + int pressure; + int controller_number; + int controller_value; + +protected: + static void _bind_methods(); + +public: + void set_channel(const int p_channel); + int get_channel() const; + + void set_message(const int p_message); + int get_message() const; + + void set_pitch(const int p_pitch); + int get_pitch() const; + + void set_velocity(const int p_velocity); + int get_velocity() const; + + void set_instrument(const int p_instrument); + int get_instrument() const; + + void set_pressure(const int p_pressure); + int get_pressure() const; + + void set_controller_number(const int p_controller_number); + int get_controller_number() const; + + void set_controller_value(const int p_controller_value); + int get_controller_value() const; + + virtual String as_text() const; + + InputEventMIDI(); +}; + #endif diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp new file mode 100644 index 0000000000..7b4f84473c --- /dev/null +++ b/core/os/midi_driver.cpp @@ -0,0 +1,107 @@ +/*************************************************************************/ +/* midi_driver.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "midi_driver.h" + +#include "main/input_default.h" +#include "os/os.h" + +MIDIDriver *MIDIDriver::singleton = NULL; +MIDIDriver *MIDIDriver::get_singleton() { + + return singleton; +} + +void MIDIDriver::set_singleton() { + + singleton = this; +} + +void MIDIDriver::receive_input_packet(uint64_t timestamp, uint8_t *data, uint32_t length) { + + Ref<InputEventMIDI> event; + event.instance(); + + if (length >= 1) { + event->set_channel(data[0] & 0xF); + event->set_message(data[0] >> 4); + } + + switch (event->get_message()) { + case MIDI_MESSAGE_AFTERTOUCH: + if (length >= 3) { + event->set_pitch(data[1]); + event->set_pressure(data[2]); + } + break; + + case MIDI_MESSAGE_CONTROL_CHANGE: + if (length >= 3) { + event->set_controller_number(data[1]); + event->set_controller_value(data[2]); + } + break; + + case MIDI_MESSAGE_NOTE_ON: + case MIDI_MESSAGE_NOTE_OFF: + case MIDI_MESSAGE_PITCH_BEND: + if (length >= 3) { + event->set_pitch(data[1]); + event->set_velocity(data[2]); + } + break; + + case MIDI_MESSAGE_PROGRAM_CHANGE: + if (length >= 2) { + event->set_instrument(data[1]); + } + break; + + case MIDI_MESSAGE_CHANNEL_PRESSURE: + if (length >= 2) { + event->set_pressure(data[1]); + } + break; + } + + InputDefault *id = Object::cast_to<InputDefault>(Input::get_singleton()); + id->parse_input_event(event); +} + +PoolStringArray MIDIDriver::get_connected_inputs() { + + PoolStringArray list; + return list; +} + +MIDIDriver::MIDIDriver() { + + set_singleton(); +} diff --git a/core/os/midi_driver.h b/core/os/midi_driver.h new file mode 100644 index 0000000000..1a3a67a411 --- /dev/null +++ b/core/os/midi_driver.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* midi_driver.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 MIDI_DRIVER_H +#define MIDI_DRIVER_H + +#include "core/variant.h" +#include "typedefs.h" +/** + * Multi-Platform abstraction for accessing to MIDI. + */ + +class MIDIDriver { + + static MIDIDriver *singleton; + +public: + static MIDIDriver *get_singleton(); + void set_singleton(); + + virtual Error open() = 0; + virtual void close() = 0; + + virtual PoolStringArray get_connected_inputs(); + + static void receive_input_packet(uint64_t timestamp, uint8_t *data, uint32_t length); + + MIDIDriver(); + virtual ~MIDIDriver() {} +}; + +#endif diff --git a/core/os/os.cpp b/core/os/os.cpp index 5eed10e30c..8dcf0990fc 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -33,6 +33,7 @@ #include "dir_access.h" #include "input.h" #include "os/file_access.h" +#include "os/midi_driver.h" #include "project_settings.h" #include "servers/audio_server.h" #include "version_generated.gen.h" @@ -614,6 +615,9 @@ bool OS::has_feature(const String &p_feature) { if (_check_internal_feature_support(p_feature)) return true; + if (ProjectSettings::get_singleton()->has_custom_feature(p_feature)) + return true; + return false; } @@ -656,9 +660,32 @@ const char *OS::get_audio_driver_name(int p_driver) const { return AudioDriverManager::get_driver(p_driver)->get_name(); } +void OS::set_restart_on_exit(bool p_restart, const List<String> &p_restart_arguments) { + restart_on_exit = p_restart; + restart_commandline = p_restart_arguments; +} + +bool OS::is_restart_on_exit_set() const { + return restart_on_exit; +} + +List<String> OS::get_restart_on_exit_arguments() const { + return restart_commandline; +} + +PoolStringArray OS::get_connected_midi_inputs() { + + if (MIDIDriver::get_singleton()) + return MIDIDriver::get_singleton()->get_connected_inputs(); + + PoolStringArray list; + return list; +} + OS::OS() { void *volatile stack_bottom; + restart_on_exit = false; last_error = NULL; singleton = this; _keep_screen_on = true; // set default value to true, because this had been true before godot 2.0. diff --git a/core/os/os.h b/core/os/os.h index adf01a90e7..dd783408e8 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -74,6 +74,9 @@ class OS { CompositeLogger *_logger; + bool restart_on_exit; + List<String> restart_commandline; + protected: void _set_logger(CompositeLogger *p_logger); @@ -182,10 +185,12 @@ public: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; - + virtual int get_current_video_driver() const = 0; virtual int get_audio_driver_count() const; virtual const char *get_audio_driver_name(int p_driver) const; + virtual PoolStringArray get_connected_midi_inputs(); + virtual int get_screen_count() const { return 1; } virtual int get_current_screen() const { return 0; } virtual void set_current_screen(int p_screen) {} @@ -496,6 +501,11 @@ public: bool is_layered_allowed() const { return _allow_layered; } bool is_hidpi_allowed() const { return _allow_hidpi; } + + void set_restart_on_exit(bool p_restart, const List<String> &p_restart_arguments); + bool is_restart_on_exit_set() const; + List<String> get_restart_on_exit_arguments() const; + OS(); virtual ~OS(); }; diff --git a/core/project_settings.cpp b/core/project_settings.cpp index 7f9f4b638a..60e8933751 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -105,6 +105,11 @@ void ProjectSettings::set_initial_value(const String &p_name, const Variant &p_v ERR_FAIL_COND(!props.has(p_name)); props[p_name].initial = p_value; } +void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restart) { + + ERR_FAIL_COND(!props.has(p_name)); + props[p_name].restart_if_changed = p_restart; +} String ProjectSettings::globalize_path(const String &p_path) const { @@ -225,6 +230,9 @@ void ProjectSettings::_get_property_list(List<PropertyInfo> *p_list) const { else vc.flags = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE; + if (v->restart_if_changed) { + vc.flags |= PROPERTY_USAGE_RESTART_IF_CHANGED; + } vclist.insert(vc); } @@ -515,7 +523,11 @@ Error ProjectSettings::_load_settings_text(const String p_path) { } } else { // config_version is checked and dropped - set(section + "/" + assign, value); + if (section == String()) { + set(assign, value); + } else { + set(section + "/" + assign, value); + } } } else if (next_tag.name != String()) { section = next_tag.name; @@ -813,7 +825,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust return OK; } -Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default) { +Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed) { Variant ret; if (!ProjectSettings::get_singleton()->has_setting(p_var)) { @@ -823,6 +835,7 @@ Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default) { ProjectSettings::get_singleton()->set_initial_value(p_var, p_default); ProjectSettings::get_singleton()->set_builtin_order(p_var); + ProjectSettings::get_singleton()->set_restart_if_changed(p_var, p_restart_if_changed); return ret; } @@ -908,6 +921,10 @@ Variant ProjectSettings::get_setting(const String &p_setting) const { return get(p_setting); } +bool ProjectSettings::has_custom_feature(const String &p_feature) const { + return custom_features.has(p_feature); +} + void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("has_setting", "name"), &ProjectSettings::has_setting); @@ -1072,7 +1089,6 @@ ProjectSettings::ProjectSettings() { custom_prop_info["rendering/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/threads/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded"); custom_prop_info["physics/2d/thread_model"] = PropertyInfo(Variant::INT, "physics/2d/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded"); custom_prop_info["rendering/quality/intended_usage/framebuffer_allocation"] = PropertyInfo(Variant::INT, "rendering/quality/intended_usage/framebuffer_allocation", PROPERTY_HINT_ENUM, "2D,2D Without Sampling,3D,3D Without Effects"); - GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_mode", 2); GLOBAL_DEF("debug/settings/profiler/max_functions", 16384); diff --git a/core/project_settings.h b/core/project_settings.h index 66f3ed954e..75ebc5acc8 100644 --- a/core/project_settings.h +++ b/core/project_settings.h @@ -59,18 +59,21 @@ protected: Variant initial; bool hide_from_editor; bool overridden; + bool restart_if_changed; VariantContainer() : order(0), persist(false), hide_from_editor(false), - overridden(false) { + overridden(false), + restart_if_changed(false) { } VariantContainer(const Variant &p_variant, int p_order, bool p_persist = false) : order(p_order), persist(p_persist), variant(p_variant), hide_from_editor(false), - overridden(false) { + overridden(false), + restart_if_changed(false) { } }; @@ -120,6 +123,7 @@ public: String globalize_path(const String &p_path) const; void set_initial_value(const String &p_name, const Variant &p_value); + void set_restart_if_changed(const String &p_name, bool p_restart); bool property_can_revert(const String &p_name); Variant property_get_revert(const String &p_name); @@ -151,13 +155,16 @@ public: void set_registering_order(bool p_enable); + bool has_custom_feature(const String &p_feature) const; + ProjectSettings(); ~ProjectSettings(); }; //not a macro any longer -Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default); +Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false); #define GLOBAL_DEF(m_var, m_value) _GLOBAL_DEF(m_var, m_value) +#define GLOBAL_DEF_RST(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true) #define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var) #endif diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 2a611ccf6a..9bcc2d4530 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -191,7 +191,7 @@ void register_core_types() { void register_core_settings() { //since in register core types, globals may not e present - GLOBAL_DEF("network/limits/packet_peer_stream/max_buffer_po2", (16)); + GLOBAL_DEF_RST("network/limits/packet_peer_stream/max_buffer_po2", (16)); } void register_core_singletons() { diff --git a/core/safe_refcount.cpp b/core/safe_refcount.cpp index 3b203f6977..692ff722f3 100644 --- a/core/safe_refcount.cpp +++ b/core/safe_refcount.cpp @@ -57,113 +57,113 @@ return m_val; \ } -_ALWAYS_INLINE_ uint32_t _atomic_conditional_increment_impl(register uint32_t *pw){ +_ALWAYS_INLINE_ uint32_t _atomic_conditional_increment_impl(volatile uint32_t *pw){ ATOMIC_CONDITIONAL_INCREMENT_BODY(pw, LONG, InterlockedCompareExchange, uint32_t) } -_ALWAYS_INLINE_ uint32_t _atomic_decrement_impl(register uint32_t *pw) { +_ALWAYS_INLINE_ uint32_t _atomic_decrement_impl(volatile uint32_t *pw) { return InterlockedDecrement((LONG volatile *)pw); } -_ALWAYS_INLINE_ uint32_t _atomic_increment_impl(register uint32_t *pw) { +_ALWAYS_INLINE_ uint32_t _atomic_increment_impl(volatile uint32_t *pw) { return InterlockedIncrement((LONG volatile *)pw); } -_ALWAYS_INLINE_ uint32_t _atomic_sub_impl(register uint32_t *pw, register uint32_t val) { +_ALWAYS_INLINE_ uint32_t _atomic_sub_impl(volatile uint32_t *pw, volatile uint32_t val) { return InterlockedExchangeAdd((LONG volatile *)pw, -(int32_t)val) - val; } -_ALWAYS_INLINE_ uint32_t _atomic_add_impl(register uint32_t *pw, register uint32_t val) { +_ALWAYS_INLINE_ uint32_t _atomic_add_impl(volatile uint32_t *pw, volatile uint32_t val) { return InterlockedAdd((LONG volatile *)pw, val); } -_ALWAYS_INLINE_ uint32_t _atomic_exchange_if_greater_impl(register uint32_t *pw, register uint32_t val){ +_ALWAYS_INLINE_ uint32_t _atomic_exchange_if_greater_impl(volatile uint32_t *pw, volatile uint32_t val){ ATOMIC_EXCHANGE_IF_GREATER_BODY(pw, val, LONG, InterlockedCompareExchange, uint32_t) } -_ALWAYS_INLINE_ uint64_t _atomic_conditional_increment_impl(register uint64_t *pw){ +_ALWAYS_INLINE_ uint64_t _atomic_conditional_increment_impl(volatile uint64_t *pw){ ATOMIC_CONDITIONAL_INCREMENT_BODY(pw, LONGLONG, InterlockedCompareExchange64, uint64_t) } -_ALWAYS_INLINE_ uint64_t _atomic_decrement_impl(register uint64_t *pw) { +_ALWAYS_INLINE_ uint64_t _atomic_decrement_impl(volatile uint64_t *pw) { return InterlockedDecrement64((LONGLONG volatile *)pw); } -_ALWAYS_INLINE_ uint64_t _atomic_increment_impl(register uint64_t *pw) { +_ALWAYS_INLINE_ uint64_t _atomic_increment_impl(volatile uint64_t *pw) { return InterlockedIncrement64((LONGLONG volatile *)pw); } -_ALWAYS_INLINE_ uint64_t _atomic_sub_impl(register uint64_t *pw, register uint64_t val) { +_ALWAYS_INLINE_ uint64_t _atomic_sub_impl(volatile uint64_t *pw, volatile uint64_t val) { return InterlockedExchangeAdd64((LONGLONG volatile *)pw, -(int64_t)val) - val; } -_ALWAYS_INLINE_ uint64_t _atomic_add_impl(register uint64_t *pw, register uint64_t val) { +_ALWAYS_INLINE_ uint64_t _atomic_add_impl(volatile uint64_t *pw, volatile uint64_t val) { return InterlockedAdd64((LONGLONG volatile *)pw, val); } -_ALWAYS_INLINE_ uint64_t _atomic_exchange_if_greater_impl(register uint64_t *pw, register uint64_t val){ +_ALWAYS_INLINE_ uint64_t _atomic_exchange_if_greater_impl(volatile uint64_t *pw, volatile uint64_t val){ ATOMIC_EXCHANGE_IF_GREATER_BODY(pw, val, LONGLONG, InterlockedCompareExchange64, uint64_t) } // The actual advertised functions; they'll call the right implementation -uint32_t atomic_conditional_increment(register uint32_t *pw) { +uint32_t atomic_conditional_increment(volatile uint32_t *pw) { return _atomic_conditional_increment_impl(pw); } -uint32_t atomic_decrement(register uint32_t *pw) { +uint32_t atomic_decrement(volatile uint32_t *pw) { return _atomic_decrement_impl(pw); } -uint32_t atomic_increment(register uint32_t *pw) { +uint32_t atomic_increment(volatile uint32_t *pw) { return _atomic_increment_impl(pw); } -uint32_t atomic_sub(register uint32_t *pw, register uint32_t val) { +uint32_t atomic_sub(volatile uint32_t *pw, volatile uint32_t val) { return _atomic_sub_impl(pw, val); } -uint32_t atomic_add(register uint32_t *pw, register uint32_t val) { +uint32_t atomic_add(volatile uint32_t *pw, volatile uint32_t val) { return _atomic_add_impl(pw, val); } -uint32_t atomic_exchange_if_greater(register uint32_t *pw, register uint32_t val) { +uint32_t atomic_exchange_if_greater(volatile uint32_t *pw, volatile uint32_t val) { return _atomic_exchange_if_greater_impl(pw, val); } -uint64_t atomic_conditional_increment(register uint64_t *pw) { +uint64_t atomic_conditional_increment(volatile uint64_t *pw) { return _atomic_conditional_increment_impl(pw); } -uint64_t atomic_decrement(register uint64_t *pw) { +uint64_t atomic_decrement(volatile uint64_t *pw) { return _atomic_decrement_impl(pw); } -uint64_t atomic_increment(register uint64_t *pw) { +uint64_t atomic_increment(volatile uint64_t *pw) { return _atomic_increment_impl(pw); } -uint64_t atomic_sub(register uint64_t *pw, register uint64_t val) { +uint64_t atomic_sub(volatile uint64_t *pw, volatile uint64_t val) { return _atomic_sub_impl(pw, val); } -uint64_t atomic_add(register uint64_t *pw, register uint64_t val) { +uint64_t atomic_add(volatile uint64_t *pw, volatile uint64_t val) { return _atomic_add_impl(pw, val); } -uint64_t atomic_exchange_if_greater(register uint64_t *pw, register uint64_t val) { +uint64_t atomic_exchange_if_greater(volatile uint64_t *pw, volatile uint64_t val) { return _atomic_exchange_if_greater_impl(pw, val); } #endif diff --git a/core/safe_refcount.h b/core/safe_refcount.h index eff209c2db..36bcf5e576 100644 --- a/core/safe_refcount.h +++ b/core/safe_refcount.h @@ -44,7 +44,7 @@ /* Bogus implementation unaware of multiprocessing */ template <class T> -static _ALWAYS_INLINE_ T atomic_conditional_increment(register T *pw) { +static _ALWAYS_INLINE_ T atomic_conditional_increment(volatile T *pw) { if (*pw == 0) return 0; @@ -55,7 +55,7 @@ static _ALWAYS_INLINE_ T atomic_conditional_increment(register T *pw) { } template <class T> -static _ALWAYS_INLINE_ T atomic_decrement(register T *pw) { +static _ALWAYS_INLINE_ T atomic_decrement(volatile T *pw) { (*pw)--; @@ -63,7 +63,7 @@ static _ALWAYS_INLINE_ T atomic_decrement(register T *pw) { } template <class T> -static _ALWAYS_INLINE_ T atomic_increment(register T *pw) { +static _ALWAYS_INLINE_ T atomic_increment(volatile T *pw) { (*pw)++; @@ -71,7 +71,7 @@ static _ALWAYS_INLINE_ T atomic_increment(register T *pw) { } template <class T, class V> -static _ALWAYS_INLINE_ T atomic_sub(register T *pw, register V val) { +static _ALWAYS_INLINE_ T atomic_sub(volatile T *pw, volatile V val) { (*pw) -= val; @@ -79,7 +79,7 @@ static _ALWAYS_INLINE_ T atomic_sub(register T *pw, register V val) { } template <class T, class V> -static _ALWAYS_INLINE_ T atomic_add(register T *pw, register V val) { +static _ALWAYS_INLINE_ T atomic_add(volatile T *pw, volatile V val) { (*pw) += val; @@ -87,7 +87,7 @@ static _ALWAYS_INLINE_ T atomic_add(register T *pw, register V val) { } template <class T, class V> -static _ALWAYS_INLINE_ T atomic_exchange_if_greater(register T *pw, register V val) { +static _ALWAYS_INLINE_ T atomic_exchange_if_greater(volatile T *pw, volatile V val) { if (val > *pw) *pw = val; @@ -103,7 +103,7 @@ static _ALWAYS_INLINE_ T atomic_exchange_if_greater(register T *pw, register V v // Clang states it supports GCC atomic builtins. template <class T> -static _ALWAYS_INLINE_ T atomic_conditional_increment(register T *pw) { +static _ALWAYS_INLINE_ T atomic_conditional_increment(volatile T *pw) { while (true) { T tmp = static_cast<T const volatile &>(*pw); @@ -115,31 +115,31 @@ static _ALWAYS_INLINE_ T atomic_conditional_increment(register T *pw) { } template <class T> -static _ALWAYS_INLINE_ T atomic_decrement(register T *pw) { +static _ALWAYS_INLINE_ T atomic_decrement(volatile T *pw) { return __sync_sub_and_fetch(pw, 1); } template <class T> -static _ALWAYS_INLINE_ T atomic_increment(register T *pw) { +static _ALWAYS_INLINE_ T atomic_increment(volatile T *pw) { return __sync_add_and_fetch(pw, 1); } template <class T, class V> -static _ALWAYS_INLINE_ T atomic_sub(register T *pw, register V val) { +static _ALWAYS_INLINE_ T atomic_sub(volatile T *pw, volatile V val) { return __sync_sub_and_fetch(pw, val); } template <class T, class V> -static _ALWAYS_INLINE_ T atomic_add(register T *pw, register V val) { +static _ALWAYS_INLINE_ T atomic_add(volatile T *pw, volatile V val) { return __sync_add_and_fetch(pw, val); } template <class T, class V> -static _ALWAYS_INLINE_ T atomic_exchange_if_greater(register T *pw, register V val) { +static _ALWAYS_INLINE_ T atomic_exchange_if_greater(volatile T *pw, volatile V val) { while (true) { T tmp = static_cast<T const volatile &>(*pw); @@ -153,19 +153,19 @@ static _ALWAYS_INLINE_ T atomic_exchange_if_greater(register T *pw, register V v #elif defined(_MSC_VER) // For MSVC use a separate compilation unit to prevent windows.h from polluting // the global namespace. -uint32_t atomic_conditional_increment(register uint32_t *pw); -uint32_t atomic_decrement(register uint32_t *pw); -uint32_t atomic_increment(register uint32_t *pw); -uint32_t atomic_sub(register uint32_t *pw, register uint32_t val); -uint32_t atomic_add(register uint32_t *pw, register uint32_t val); -uint32_t atomic_exchange_if_greater(register uint32_t *pw, register uint32_t val); - -uint64_t atomic_conditional_increment(register uint64_t *pw); -uint64_t atomic_decrement(register uint64_t *pw); -uint64_t atomic_increment(register uint64_t *pw); -uint64_t atomic_sub(register uint64_t *pw, register uint64_t val); -uint64_t atomic_add(register uint64_t *pw, register uint64_t val); -uint64_t atomic_exchange_if_greater(register uint64_t *pw, register uint64_t val); +uint32_t atomic_conditional_increment(volatile uint32_t *pw); +uint32_t atomic_decrement(volatile uint32_t *pw); +uint32_t atomic_increment(volatile uint32_t *pw); +uint32_t atomic_sub(volatile uint32_t *pw, volatile uint32_t val); +uint32_t atomic_add(volatile uint32_t *pw, volatile uint32_t val); +uint32_t atomic_exchange_if_greater(volatile uint32_t *pw, volatile uint32_t val); + +uint64_t atomic_conditional_increment(volatile uint64_t *pw); +uint64_t atomic_decrement(volatile uint64_t *pw); +uint64_t atomic_increment(volatile uint64_t *pw); +uint64_t atomic_sub(volatile uint64_t *pw, volatile uint64_t val); +uint64_t atomic_add(volatile uint64_t *pw, volatile uint64_t val); +uint64_t atomic_exchange_if_greater(volatile uint64_t *pw, volatile uint64_t val); #else //no threads supported? diff --git a/core/script_language.cpp b/core/script_language.cpp index acbe3b34db..37ba3cfc62 100644 --- a/core/script_language.cpp +++ b/core/script_language.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "script_language.h" +#include "project_settings.h" ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES]; int ScriptServer::_language_count = 0; @@ -103,6 +104,20 @@ void ScriptServer::unregister_language(ScriptLanguage *p_language) { void ScriptServer::init_languages() { + { //load global classes + global_classes_clear(); + if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) { + Array script_classes = ProjectSettings::get_singleton()->get("_global_script_classes"); + + for (int i = 0; i < script_classes.size(); i++) { + Dictionary c = script_classes[i]; + if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) + continue; + add_global_class(c["class"], c["base"], c["language"], c["path"]); + } + } + } + for (int i = 0; i < _language_count; i++) { _languages[i]->init(); } @@ -113,6 +128,7 @@ void ScriptServer::finish_languages() { for (int i = 0; i < _language_count; i++) { _languages[i]->finish(); } + global_classes_clear(); } void ScriptServer::set_reload_scripts_on_save(bool p_enable) { @@ -139,6 +155,67 @@ void ScriptServer::thread_exit() { } } +HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes; + +void ScriptServer::global_classes_clear() { + global_classes.clear(); +} + +void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path) { + GlobalScriptClass g; + g.language = p_language; + g.path = p_path; + g.base = p_base; + global_classes[p_class] = g; +} +void ScriptServer::remove_global_class(const StringName &p_class) { + global_classes.erase(p_class); +} +bool ScriptServer::is_global_class(const StringName &p_class) { + return global_classes.has(p_class); +} +StringName ScriptServer::get_global_class_language(const StringName &p_class) { + ERR_FAIL_COND_V(!global_classes.has(p_class), StringName()); + return global_classes[p_class].language; +} +String ScriptServer::get_global_class_path(const String &p_class) { + ERR_FAIL_COND_V(!global_classes.has(p_class), String()); + return global_classes[p_class].path; +} + +StringName ScriptServer::get_global_class_base(const String &p_class) { + ERR_FAIL_COND_V(!global_classes.has(p_class), String()); + return global_classes[p_class].base; +} +void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) { + const StringName *K = NULL; + List<StringName> classes; + while ((K = global_classes.next(K))) { + classes.push_back(*K); + } + classes.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = classes.front(); E; E = E->next()) { + r_global_classes->push_back(E->get()); + } +} +void ScriptServer::save_global_classes() { + List<StringName> gc; + get_global_class_list(&gc); + Array gcarr; + for (List<StringName>::Element *E = gc.front(); E; E = E->next()) { + Dictionary d; + d["class"] = E->get(); + d["language"] = global_classes[E->get()].language; + d["path"] = global_classes[E->get()].path; + d["base"] = global_classes[E->get()].base; + gcarr.push_back(d); + } + + ProjectSettings::get_singleton()->set("_global_script_classes", gcarr); + ProjectSettings::get_singleton()->save(); +} + +//////////////////// void ScriptInstance::get_property_state(List<Pair<StringName, Variant> > &state) { List<PropertyInfo> pinfo; diff --git a/core/script_language.h b/core/script_language.h index e7748f93e2..4e81b9b626 100644 --- a/core/script_language.h +++ b/core/script_language.h @@ -54,6 +54,14 @@ class ScriptServer { static bool scripting_enabled; static bool reload_scripts_on_save; + struct GlobalScriptClass { + StringName language; + String path; + String base; + }; + + static HashMap<StringName, GlobalScriptClass> global_classes; + public: static ScriptEditRequestFunction edit_request_func; @@ -70,6 +78,16 @@ public: static void thread_enter(); static void thread_exit(); + static void global_classes_clear(); + static void add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path); + static void remove_global_class(const StringName &p_class); + static bool is_global_class(const StringName &p_class); + static StringName get_global_class_language(const StringName &p_class); + static String get_global_class_path(const String &p_class); + static StringName get_global_class_base(const String &p_class); + static void get_global_class_list(List<StringName> *r_global_classes); + static void save_global_classes(); + static void init_languages(); static void finish_languages(); }; @@ -195,7 +213,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const = 0; virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {} virtual bool is_using_templates() { return false; } - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const = 0; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const = 0; virtual String validate_path(const String &p_path) const { return ""; } virtual Script *create_script() const = 0; virtual bool has_named_classes() const = 0; @@ -285,7 +303,10 @@ public: virtual void frame(); - virtual ~ScriptLanguage(){}; + virtual bool handles_global_class_type(const String &p_type) const { return false; } + virtual String get_global_class_name(const String &p_path, String *r_base_type = NULL) const { return String(); } + + virtual ~ScriptLanguage() {} }; extern uint8_t script_encryption_key[32]; diff --git a/core/translation.cpp b/core/translation.cpp index aaa4de5912..78115c3749 100644 --- a/core/translation.cpp +++ b/core/translation.cpp @@ -1171,13 +1171,11 @@ void TranslationServer::_bind_methods() { void TranslationServer::load_translations() { String locale = get_locale(); - bool found = _load_translations("locale/translations"); //all + _load_translations("locale/translations"); //all + _load_translations("locale/translations_" + locale.substr(0, 2)); - if (_load_translations("locale/translations_" + locale.substr(0, 2))) - found = true; if (locale.substr(0, 2) != locale) { - if (_load_translations("locale/translations_" + locale)) - found = true; + _load_translations("locale/translations_" + locale); } } diff --git a/core/typedefs.h b/core/typedefs.h index 4758a5408d..71771ea4e6 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -74,7 +74,7 @@ T *_nullptr() { #define OFFSET_OF(st, m) \ ((size_t)((char *)&(_nullptr<st>()->m) - (char *)0)) - /** +/** * Some platforms (devices) not define NULL */ @@ -82,7 +82,7 @@ T *_nullptr() { #define NULL 0 #endif - /** +/** * Windows defines a lot of badly stuff we'll never ever use. undefine it. */ @@ -97,6 +97,7 @@ T *_nullptr() { #undef CLAMP // override standard definition #undef Error #undef OK +#undef CONNECT_DEFERRED // override from Windows SDK, clashes with Object enum #endif #include "int_types.h" @@ -104,7 +105,7 @@ T *_nullptr() { #include "error_list.h" #include "error_macros.h" - /** Generic ABS function, for math uses please use Math::abs */ +/** Generic ABS function, for math uses please use Math::abs */ #ifndef ABS #define ABS(m_v) ((m_v < 0) ? (-(m_v)) : (m_v)) diff --git a/core/ustring.cpp b/core/ustring.cpp index 51f05468e2..f552cb13ac 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -161,14 +161,21 @@ void String::copy_from(const CharType *p_cstr, int p_clip_to) { return; } - resize(len + 1); - set(len, 0); + copy_from_unchecked(p_cstr, len); +} - CharType *dst = &operator[](0); +// assumes the following have already been validated: +// p_char != NULL +// p_length > 0 +// p_length <= p_char strlen +void String::copy_from_unchecked(const CharType *p_char, int p_length) { + resize(p_length + 1); + set(p_length, 0); - for (int i = 0; i < len; i++) { + CharType *dst = &operator[](0); - dst[i] = p_cstr[i]; + for (int i = 0; i < p_length; i++) { + dst[i] = p_char[i]; } } @@ -757,36 +764,32 @@ Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int Vector<String> ret; const int len = length(); - int from = len; + int remaining_len = len; while (true) { - int end = rfind(p_splitter, from); - if (end < 0) - end = 0; - - if (p_allow_empty || (end < from)) { - const String str = substr(end > 0 ? end + p_splitter.length() : end, end > 0 ? from - end : from + 2); - - if (p_maxsplit <= 0) { - ret.push_back(str); - } else if (p_maxsplit > 0) { - - // Put rest of the string and leave cycle. - if (p_maxsplit == ret.size()) { - ret.push_back(substr(0, from + 2)); - break; - } - - // Otherwise, push items until positive limit is reached. - ret.push_back(str); + if (remaining_len < p_splitter.length() || (p_maxsplit > 0 && p_maxsplit == ret.size())) { + // no room for another splitter or hit max splits, push what's left and we're done + if (p_allow_empty || remaining_len > 0) { + ret.push_back(substr(0, remaining_len)); } + break; } - if (end == 0) + int left_edge = rfind(p_splitter, remaining_len - p_splitter.length()); + + if (left_edge < 0) { + // no more splitters, we're done + ret.push_back(substr(0, remaining_len)); break; + } - from = end - p_splitter.length(); + int substr_start = left_edge + p_splitter.length(); + if (p_allow_empty || substr_start < remaining_len) { + ret.push_back(substr(substr_start, remaining_len - substr_start)); + } + + remaining_len = left_edge; } ret.invert(); @@ -925,8 +928,8 @@ String String::to_upper() const { for (int i = 0; i < upper.size(); i++) { - const char s = upper[i]; - const char t = _find_upper(s); + const CharType s = upper[i]; + const CharType t = _find_upper(s); if (s != t) // avoid copy on write upper[i] = t; } @@ -940,8 +943,8 @@ String String::to_lower() const { for (int i = 0; i < lower.size(); i++) { - const char s = lower[i]; - const char t = _find_lower(s); + const CharType s = lower[i]; + const CharType t = _find_lower(s); if (s != t) // avoid copy on write lower[i] = t; } @@ -2250,7 +2253,9 @@ String String::substr(int p_from, int p_chars) const { return String(*this); } - return String(&c_str()[p_from], p_chars); + String s = String(); + s.copy_from_unchecked(&c_str()[p_from], p_chars); + return s; } int String::find_last(const String &p_str) const { diff --git a/core/ustring.h b/core/ustring.h index b57e9629d9..001d111d64 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -65,6 +65,7 @@ class String : public Vector<CharType> { void copy_from(const char *p_cstr); void copy_from(const CharType *p_cstr, int p_clip_to = -1); void copy_from(const CharType &p_char); + void copy_from_unchecked(const CharType *p_char, int p_length); bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; public: diff --git a/core/vector.h b/core/vector.h index f586471e27..c026448ddd 100644 --- a/core/vector.h +++ b/core/vector.h @@ -152,6 +152,8 @@ public: Error insert(int p_pos, const T &p_val); + void append_array(const Vector<T> &p_other); + template <class C> void sort_custom() { @@ -408,6 +410,17 @@ Error Vector<T>::insert(int p_pos, const T &p_val) { } template <class T> +void Vector<T>::append_array(const Vector<T> &p_other) { + const int ds = p_other.size(); + if (ds == 0) + return; + const int bs = size(); + resize(bs + ds); + for (int i = 0; i < ds; ++i) + operator[](bs + i) = p_other[i]; +} + +template <class T> Vector<T>::Vector(const Vector &p_from) { _ptr = NULL; diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 35c120cd6a..7fcb827252 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -4,7 +4,16 @@ Generic array datatype. </brief_description> <description> - Generic array, contains several elements of any type, accessible by numerical index starting at 0. Negative indices can be used to count from the right, like in Python. Arrays are always passed by reference. + Generic array, contains several elements of any type, accessible by a numerical index starting at 0. Negative indices can be used to count from the back, like in Python (-1 is the last element, -2 the second to last, etc.). Example: + [codeblock] + var array = ["One", 2, 3, "Four"] + print(array[0]) # One + print(array[2]) # 3 + print(array[-1]) # Four + array[2] = "Three" + print(array[-2]) # Three + [/codeblock] + Arrays are always passed by reference. </description> <tutorials> </tutorials> diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml index e48eb82691..b21d402468 100644 --- a/doc/classes/EditorImportPlugin.xml +++ b/doc/classes/EditorImportPlugin.xml @@ -35,19 +35,20 @@ func get_import_options(i): return [{"name": "my_option", "default_value": false}] - func load(src, dst, opts, r_platform_variants, r_gen_files): + func import(source_file, save_path, options, r_platform_variants, r_gen_files): var file = File.new() - if file.open(src, File.READ) != OK: + if file.open(source_file, File.READ) != OK: return FAILED var mesh = Mesh.new() - var save = dst + "." + get_save_extension() - ResourceSaver.save(file, mesh) + var filename = save_path + "." + get_save_extension() + ResourceSaver.save(filename, mesh) return OK [/codeblock] </description> <tutorials> + <link>http://docs.godotengine.org/en/3.0/tutorials/plugins/editor/import_plugins.html</link> </tutorials> <demos> </demos> @@ -119,7 +120,7 @@ <return type="String"> </return> <description> - Get the godot resource type associated with this loader. e.g. "Mesh" or "Animation". + Get the Godot resource type associated with this loader. e.g. "Mesh" or "Animation". </description> </method> <method name="get_save_extension" qualifiers="virtual"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 9fc7672a80..55693bd49c 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -350,6 +350,15 @@ Loads an image from the binary contents of a PNG file. </description> </method> + <method name="load_webp_from_buffer"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="buffer" type="PoolByteArray"> + </argument> + <description> + Loads an image from the binary contents of a WebP file. + </description> + </method> <method name="lock"> <return type="void"> </return> @@ -584,6 +593,12 @@ </constant> <constant name="INTERPOLATE_CUBIC" value="2" enum="Interpolation"> </constant> + <constant name="INTERPOLATE_TRILINEAR" value="3" enum="Interpolation"> + Performs bilinear separately on the two most suited mipmap levels, then linearly interpolates between them. + It's slower than [code]INTERPOLATE_BILINEAR[/code], but produces higher quality results, with much less aliasing artifacts. + If the image does not have mipmaps, they will be generated and used internally, but no mipmaps will be generated on the resulting image. (Note that if you intend to scale multiple copies of the original image, it's better to call [code]generate_mipmaps[/code] on it in advance, to avoid wasting processing power in generating them again and again.) + On the other hand, if the image already has mipmaps, they will be used, and a new set will be generated for the resulting image. + </constant> <constant name="ALPHA_NONE" value="0" enum="AlphaMode"> </constant> <constant name="ALPHA_BIT" value="1" enum="AlphaMode"> diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index 19be34978d..c1682e71e5 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -117,5 +117,8 @@ <constant name="LINE_TEXTURE_TILE" value="1" enum="LineTextureMode"> Tiles the texture over the line. The texture need to be imported with Repeat Enabled for it to work properly. </constant> + <constant name="LINE_TEXTURE_STRETCH" value="2" enum="LineTextureMode"> + Stretches the texture across the line. Import the texture with Repeat Disabled for best results. + </constant> </constants> </class> diff --git a/doc/classes/SpriteFrames.xml b/doc/classes/SpriteFrames.xml index 91129b5850..68373ebc4f 100644 --- a/doc/classes/SpriteFrames.xml +++ b/doc/classes/SpriteFrames.xml @@ -127,6 +127,13 @@ Changes the animation's name to [code]newname[/code]. </description> </method> + <method name="get_animation_names"> + <return type="PoolStringArray"> + </return> + <description> + Returns an array containing the names associated to each animation. Values are placed in alphabetical order. + </description> + </method> <method name="set_animation_loop"> <return type="void"> </return> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 656063771d..3486b721ca 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -153,6 +153,8 @@ Sets the tile index for the cell given by a Vector2. An index of [code]-1[/code] clears the cell. Optionally, the tile can also be flipped, transposed, or given autotile coordinates. + Note that data such as navigation polygons and collision shapes are not immediately updated for performance reasons. + If you need these to be immediately updated, you can call [method update_dirty_quadrants]. </description> </method> <method name="set_cellv"> @@ -172,6 +174,8 @@ Sets the tile index for the given cell. An index of [code]-1[/code] clears the cell. Optionally, the tile can also be flipped or transposed. + Note that data such as navigation polygons and collision shapes are not immediately updated for performance reasons. + If you need these to be immediately updated, you can call [method update_dirty_quadrants]. </description> </method> <method name="set_collision_layer_bit"> @@ -217,6 +221,13 @@ Calling with invalid (or missing) parameters applies autotiling rules for the entire tilemap. </description> </method> + <method name="update_dirty_quadrants"> + <return type="void"> + </return> + <description> + Updates the tile map's quadrants, allowing things such as navigation and collision shapes to be immediately used if modified. + </description> + </method> <method name="world_to_map" qualifiers="const"> <return type="Vector2"> </return> diff --git a/drivers/SCsub b/drivers/SCsub index 2c5e9434e8..f9cfa3fb05 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -21,6 +21,11 @@ if (env["platform"] == "windows"): if env['xaudio2']: SConscript("xaudio2/SCsub") +# Midi drivers +SConscript('alsamidi/SCsub') +SConscript('coremidi/SCsub') +SConscript('winmidi/SCsub') + # Graphics drivers if (env["platform"] != "server"): SConscript('gles3/SCsub') diff --git a/drivers/alsa/audio_driver_alsa.cpp b/drivers/alsa/audio_driver_alsa.cpp index 1e17e72532..08005efa9d 100644 --- a/drivers/alsa/audio_driver_alsa.cpp +++ b/drivers/alsa/audio_driver_alsa.cpp @@ -58,7 +58,10 @@ Error AudioDriverALSA::init_device() { #define CHECK_FAIL(m_cond) \ if (m_cond) { \ fprintf(stderr, "ALSA ERR: %s\n", snd_strerror(status)); \ - snd_pcm_close(pcm_handle); \ + if (pcm_handle) { \ + snd_pcm_close(pcm_handle); \ + pcm_handle = NULL; \ + } \ ERR_FAIL_COND_V(m_cond, ERR_CANT_OPEN); \ } @@ -142,8 +145,6 @@ Error AudioDriverALSA::init_device() { samples_in.resize(period_size * channels); samples_out.resize(period_size * channels); - snd_pcm_nonblock(pcm_handle, 0); - return OK; } @@ -152,7 +153,6 @@ Error AudioDriverALSA::init() { active = false; thread_exited = false; exit_thread = false; - pcm_open = false; Error err = init_device(); if (err == OK) { @@ -168,54 +168,50 @@ void AudioDriverALSA::thread_func(void *p_udata) { AudioDriverALSA *ad = (AudioDriverALSA *)p_udata; while (!ad->exit_thread) { + + ad->lock(); + ad->start_counting_ticks(); + if (!ad->active) { for (unsigned int i = 0; i < ad->period_size * ad->channels; i++) { ad->samples_out[i] = 0; - }; - } else { - ad->lock(); + } + } else { ad->audio_server_process(ad->period_size, ad->samples_in.ptrw()); - ad->unlock(); - for (unsigned int i = 0; i < ad->period_size * ad->channels; i++) { ad->samples_out[i] = ad->samples_in[i] >> 16; } - }; + } int todo = ad->period_size; int total = 0; - while (todo) { - if (ad->exit_thread) - break; + while (todo && !ad->exit_thread) { uint8_t *src = (uint8_t *)ad->samples_out.ptr(); int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo); - if (wrote < 0) { - if (ad->exit_thread) - break; + if (wrote > 0) { + total += wrote; + todo -= wrote; + } else if (wrote == -EAGAIN) { + ad->stop_counting_ticks(); + ad->unlock(); - if (wrote == -EAGAIN) { - //can't write yet (though this is blocking..) - usleep(1000); - continue; - } + OS::get_singleton()->delay_usec(1000); + + ad->lock(); + ad->start_counting_ticks(); + } else { wrote = snd_pcm_recover(ad->pcm_handle, wrote, 0); if (wrote < 0) { - //absolute fail - fprintf(stderr, "ALSA failed and can't recover: %s\n", snd_strerror(wrote)); + ERR_PRINTS("ALSA: Failed and can't recover: " + String(snd_strerror(wrote))); ad->active = false; ad->exit_thread = true; - break; } - continue; - }; - - total += wrote; - todo -= wrote; - }; + } + } // User selected a new device, finish the current one so we'll init the new device if (ad->device_name != ad->new_device) { @@ -232,10 +228,12 @@ void AudioDriverALSA::thread_func(void *p_udata) { if (err != OK) { ad->active = false; ad->exit_thread = true; - break; } } } + + ad->stop_counting_ticks(); + ad->unlock(); }; ad->thread_exited = true; @@ -296,7 +294,9 @@ String AudioDriverALSA::get_device() { void AudioDriverALSA::set_device(String device) { + lock(); new_device = device; + unlock(); } void AudioDriverALSA::lock() { @@ -315,29 +315,29 @@ void AudioDriverALSA::unlock() { void AudioDriverALSA::finish_device() { - if (pcm_open) { + if (pcm_handle) { snd_pcm_close(pcm_handle); - pcm_open = NULL; + pcm_handle = NULL; } } void AudioDriverALSA::finish() { - if (!thread) - return; - - exit_thread = true; - Thread::wait_to_finish(thread); + if (thread) { + exit_thread = true; + Thread::wait_to_finish(thread); - finish_device(); + memdelete(thread); + thread = NULL; - memdelete(thread); - if (mutex) { - memdelete(mutex); - mutex = NULL; + if (mutex) { + memdelete(mutex); + mutex = NULL; + } } - thread = NULL; -}; + + finish_device(); +} AudioDriverALSA::AudioDriverALSA() { diff --git a/drivers/alsa/audio_driver_alsa.h b/drivers/alsa/audio_driver_alsa.h index 2878e100a2..e2a2325cf3 100644 --- a/drivers/alsa/audio_driver_alsa.h +++ b/drivers/alsa/audio_driver_alsa.h @@ -66,7 +66,6 @@ class AudioDriverALSA : public AudioDriver { bool active; bool thread_exited; mutable bool exit_thread; - bool pcm_open; public: const char *get_name() const { diff --git a/drivers/alsamidi/SCsub b/drivers/alsamidi/SCsub new file mode 100644 index 0000000000..233593b0f9 --- /dev/null +++ b/drivers/alsamidi/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import('env') + +# Driver source files +env.add_source_files(env.drivers_sources, "*.cpp") + +Export('env') diff --git a/drivers/alsamidi/alsa_midi.cpp b/drivers/alsamidi/alsa_midi.cpp new file mode 100644 index 0000000000..599470d7e0 --- /dev/null +++ b/drivers/alsamidi/alsa_midi.cpp @@ -0,0 +1,201 @@ +/*************************************************************************/ +/* alsa_midi.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef ALSAMIDI_ENABLED + +#include <errno.h> + +#include "alsa_midi.h" +#include "os/os.h" +#include "print_string.h" + +static int get_message_size(uint8_t message) { + switch (message & 0xF0) { + case 0x80: // note off + case 0x90: // note on + case 0xA0: // aftertouch + case 0xB0: // continuous controller + return 3; + + case 0xC0: // patch change + case 0xD0: // channel pressure + case 0xE0: // pitch bend + return 2; + } + + return 256; +} + +void MIDIDriverALSAMidi::thread_func(void *p_udata) { + MIDIDriverALSAMidi *md = (MIDIDriverALSAMidi *)p_udata; + uint64_t timestamp = 0; + uint8_t buffer[256]; + int expected_size = 255; + int bytes = 0; + + while (!md->exit_thread) { + int ret; + + md->lock(); + + for (int i = 0; i < md->connected_inputs.size(); i++) { + snd_rawmidi_t *midi_in = md->connected_inputs[i]; + do { + uint8_t byte = 0; + ret = snd_rawmidi_read(midi_in, &byte, 1); + if (ret < 0) { + if (ret != -EAGAIN) { + ERR_PRINTS("snd_rawmidi_read error: " + String(snd_strerror(ret))); + } + } else { + if (byte & 0x80) { + // Flush previous packet if there is any + if (bytes) { + md->receive_input_packet(timestamp, buffer, bytes); + bytes = 0; + } + expected_size = get_message_size(byte); + } + + if (bytes < 256) { + buffer[bytes++] = byte; + // If we know the size of the current packet receive it if it reached the expected size + if (bytes >= expected_size) { + md->receive_input_packet(timestamp, buffer, bytes); + bytes = 0; + } + } + } + } while (ret > 0); + } + + md->unlock(); + + OS::get_singleton()->delay_usec(1000); + } +} + +Error MIDIDriverALSAMidi::open() { + + void **hints; + + if (snd_device_name_hint(-1, "rawmidi", &hints) < 0) + return ERR_CANT_OPEN; + + int i = 0; + for (void **n = hints; *n != NULL; n++) { + char *name = snd_device_name_get_hint(*n, "NAME"); + + if (name != NULL) { + snd_rawmidi_t *midi_in; + int ret = snd_rawmidi_open(&midi_in, NULL, name, SND_RAWMIDI_NONBLOCK); + if (ret >= 0) { + connected_inputs.insert(i++, midi_in); + } + } + + if (name != NULL) + free(name); + } + snd_device_name_free_hint(hints); + + mutex = Mutex::create(); + thread = Thread::create(MIDIDriverALSAMidi::thread_func, this); + + return OK; +} + +void MIDIDriverALSAMidi::close() { + + if (thread) { + exit_thread = true; + Thread::wait_to_finish(thread); + + memdelete(thread); + thread = NULL; + } + + if (mutex) { + memdelete(mutex); + mutex = NULL; + } + + for (int i = 0; i < connected_inputs.size(); i++) { + snd_rawmidi_t *midi_in = connected_inputs[i]; + snd_rawmidi_close(midi_in); + } + connected_inputs.clear(); +} + +void MIDIDriverALSAMidi::lock() const { + + if (mutex) + mutex->lock(); +} + +void MIDIDriverALSAMidi::unlock() const { + + if (mutex) + mutex->unlock(); +} + +PoolStringArray MIDIDriverALSAMidi::get_connected_inputs() { + + PoolStringArray list; + + lock(); + for (int i = 0; i < connected_inputs.size(); i++) { + snd_rawmidi_t *midi_in = connected_inputs[i]; + snd_rawmidi_info_t *info; + + snd_rawmidi_info_malloc(&info); + snd_rawmidi_info(midi_in, info); + list.push_back(snd_rawmidi_info_get_name(info)); + snd_rawmidi_info_free(info); + } + unlock(); + + return list; +} + +MIDIDriverALSAMidi::MIDIDriverALSAMidi() { + + mutex = NULL; + thread = NULL; + + exit_thread = false; +} + +MIDIDriverALSAMidi::~MIDIDriverALSAMidi() { + + close(); +} + +#endif diff --git a/drivers/alsamidi/alsa_midi.h b/drivers/alsamidi/alsa_midi.h new file mode 100644 index 0000000000..90e458a365 --- /dev/null +++ b/drivers/alsamidi/alsa_midi.h @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* alsa_midi.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef ALSAMIDI_ENABLED + +#ifndef ALSA_MIDI_H +#define ALSA_MIDI_H + +#include <alsa/asoundlib.h> +#include <stdio.h> + +#include "core/os/mutex.h" +#include "core/os/thread.h" +#include "core/vector.h" +#include "os/midi_driver.h" + +class MIDIDriverALSAMidi : public MIDIDriver { + + Thread *thread; + Mutex *mutex; + + Vector<snd_rawmidi_t *> connected_inputs; + + bool exit_thread; + + static void thread_func(void *p_udata); + + void lock() const; + void unlock() const; + +public: + virtual Error open(); + virtual void close(); + + virtual PoolStringArray get_connected_inputs(); + + MIDIDriverALSAMidi(); + virtual ~MIDIDriverALSAMidi(); +}; + +#endif +#endif diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp index ef7858b4ca..ac21de91e4 100644 --- a/drivers/coreaudio/audio_driver_coreaudio.cpp +++ b/drivers/coreaudio/audio_driver_coreaudio.cpp @@ -102,7 +102,7 @@ Error AudioDriverCoreAudio::init() { break; } - mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE); + mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); zeromem(&strdesc, sizeof(strdesc)); strdesc.mFormatID = kAudioFormatLinearPCM; @@ -117,7 +117,7 @@ Error AudioDriverCoreAudio::init() { result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &strdesc, sizeof(strdesc)); ERR_FAIL_COND_V(result != noErr, FAILED); - int latency = GLOBAL_DEF("audio/output_latency", DEFAULT_OUTPUT_LATENCY); + int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); // Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels) buffer_frames = closest_power_of_2(latency * mix_rate / 1000); @@ -163,6 +163,8 @@ OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon, return 0; }; + ad->start_counting_ticks(); + for (unsigned int i = 0; i < ioData->mNumberBuffers; i++) { AudioBuffer *abuf = &ioData->mBuffers[i]; @@ -184,6 +186,7 @@ OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon, }; }; + ad->stop_counting_ticks(); ad->unlock(); return 0; diff --git a/drivers/coremidi/SCsub b/drivers/coremidi/SCsub new file mode 100644 index 0000000000..233593b0f9 --- /dev/null +++ b/drivers/coremidi/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import('env') + +# Driver source files +env.add_source_files(env.drivers_sources, "*.cpp") + +Export('env') diff --git a/drivers/coremidi/core_midi.cpp b/drivers/coremidi/core_midi.cpp new file mode 100644 index 0000000000..3619be4a8e --- /dev/null +++ b/drivers/coremidi/core_midi.cpp @@ -0,0 +1,105 @@ +/*************************************************************************/ +/* core_midi.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef COREMIDI_ENABLED + +#include "core_midi.h" +#include "print_string.h" + +#include <CoreAudio/HostTime.h> +#include <CoreServices/CoreServices.h> + +void MIDIDriverCoreMidi::read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con) { + MIDIPacket *packet = const_cast<MIDIPacket *>(packet_list->packet); + for (int i = 0; i < packet_list->numPackets; i++) { + receive_input_packet(packet->timeStamp, packet->data, packet->length); + packet = MIDIPacketNext(packet); + } +} + +Error MIDIDriverCoreMidi::open() { + + CFStringRef name = CFStringCreateWithCString(NULL, "Godot", kCFStringEncodingASCII); + OSStatus result = MIDIClientCreate(name, NULL, NULL, &client); + CFRelease(name); + if (result != noErr) { + ERR_PRINTS("MIDIClientCreate failed: " + String(GetMacOSStatusErrorString(result))); + return ERR_CANT_OPEN; + } + + result = MIDIInputPortCreate(client, CFSTR("Godot Input"), MIDIDriverCoreMidi::read, (void *)this, &port_in); + if (result != noErr) { + ERR_PRINTS("MIDIInputPortCreate failed: " + String(GetMacOSStatusErrorString(result))); + return ERR_CANT_OPEN; + } + + int sources = MIDIGetNumberOfSources(); + for (int i = 0; i < sources; i++) { + + MIDIEndpointRef source = MIDIGetSource(i); + if (source != NULL) { + MIDIPortConnectSource(port_in, source, (void *)this); + connected_sources.insert(i, source); + } + } + + return OK; +} + +void MIDIDriverCoreMidi::close() { + + for (int i = 0; i < connected_sources.size(); i++) { + MIDIEndpointRef source = connected_sources[i]; + MIDIPortDisconnectSource(port_in, source); + } + connected_sources.clear(); + + if (port_in != 0) { + MIDIPortDispose(port_in); + port_in = 0; + } + + if (client != 0) { + MIDIClientDispose(client); + client = 0; + } +} + +MIDIDriverCoreMidi::MIDIDriverCoreMidi() { + + client = 0; +} + +MIDIDriverCoreMidi::~MIDIDriverCoreMidi() { + + close(); +} + +#endif diff --git a/drivers/coremidi/core_midi.h b/drivers/coremidi/core_midi.h new file mode 100644 index 0000000000..fd35e12f4b --- /dev/null +++ b/drivers/coremidi/core_midi.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* core_midi.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef COREMIDI_ENABLED + +#ifndef CORE_MIDI_H +#define CORE_MIDI_H + +#include <stdio.h> + +#include <CoreMIDI/CoreMIDI.h> + +#include "core/vector.h" +#include "os/midi_driver.h" + +class MIDIDriverCoreMidi : public MIDIDriver { + + MIDIClientRef client; + MIDIPortRef port_in; + + Vector<MIDIEndpointRef> connected_sources; + + static void read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con); + +public: + virtual Error open(); + virtual void close(); + + MIDIDriverCoreMidi(); + virtual ~MIDIDriverCoreMidi(); +}; + +#endif +#endif diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 068a14cb8a..bab89f649a 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -235,6 +235,7 @@ public: void textures_keep_original(bool p_enable) {} void texture_set_proxy(RID p_proxy, RID p_base) {} + void texture_set_force_redraw_if_visible(RID p_texture, bool p_enable) {} /* SKY API */ diff --git a/drivers/dummy/texture_loader_dummy.cpp b/drivers/dummy/texture_loader_dummy.cpp index 6d3e176bbb..b099019d17 100644 --- a/drivers/dummy/texture_loader_dummy.cpp +++ b/drivers/dummy/texture_loader_dummy.cpp @@ -45,10 +45,6 @@ RES ResourceFormatDummyTexture::load(const String &p_path, const String &p_origi dstbuff.resize(rowsize * height); - PoolVector<uint8_t>::Write dstbuff_write = dstbuff.write(); - - uint8_t *data = dstbuff_write.ptr(); - uint8_t **row_p = memnew_arr(uint8_t *, height); for (unsigned int i = 0; i < height; i++) { diff --git a/drivers/gles2/rasterizer_canvas_gles2.cpp b/drivers/gles2/rasterizer_canvas_gles2.cpp index d5232a6511..fb150d6820 100644 --- a/drivers/gles2/rasterizer_canvas_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_gles2.cpp @@ -140,6 +140,10 @@ RasterizerStorageGLES2::Texture *RasterizerCanvasGLES2::_bind_canvas_texture(con texture = texture->get_ptr(); + if (texture->redraw_if_visible) { + VisualServerRaster::redraw_request(); + } + if (texture->render_target) { texture->render_target->used_in_frame = true; } @@ -405,8 +409,6 @@ void RasterizerCanvasGLES2::_canvas_item_render_commands(Item *p_item, Item *cur Rect2 dst_rect = Rect2(r->rect.position, r->rect.size); - state.canvas_shader.set_uniform(CanvasShaderGLES2::COLOR_TEXPIXEL_SIZE, texpixel_size); - if (dst_rect.size.width < 0) { dst_rect.position.x += dst_rect.size.width; dst_rect.size.width *= -1; @@ -659,6 +661,8 @@ void RasterizerCanvasGLES2::_canvas_item_render_commands(Item *p_item, Item *cur if (state.canvas_shader.bind()) _set_uniforms(); + _bind_canvas_texture(RID(), RID()); + if (pline->triangles.size()) { _draw_generic(GL_TRIANGLE_STRIP, pline->triangles.size(), pline->triangles.ptr(), NULL, pline->triangle_colors.ptr(), pline->triangle_colors.size() == 1); } else { @@ -909,6 +913,10 @@ void RasterizerCanvasGLES2::canvas_render_items(Item *p_item_list, int p_z, cons t = t->get_ptr(); + if (t->redraw_if_visible) { + VisualServerRaster::redraw_request(); + } + glBindTexture(t->target, t->tex_id); } } else { diff --git a/drivers/gles2/rasterizer_gles2.cpp b/drivers/gles2/rasterizer_gles2.cpp index ab48e682d6..9ea20ff15a 100644 --- a/drivers/gles2/rasterizer_gles2.cpp +++ b/drivers/gles2/rasterizer_gles2.cpp @@ -203,7 +203,7 @@ void RasterizerGLES2::initialize() { #endif // GLAD_ENABLED - // For debugging + // For debugging #ifdef GLES_OVER_GL if (GLAD_GL_ARB_debug_output) { glDebugMessageControlARB(_EXT_DEBUG_SOURCE_API_ARB, _EXT_DEBUG_TYPE_ERROR_ARB, _EXT_DEBUG_SEVERITY_HIGH_ARB, 0, NULL, GL_TRUE); diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index ca39531b0d..0dc506d991 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -674,6 +674,14 @@ void RasterizerStorageGLES2::texture_set_proxy(RID p_texture, RID p_proxy) { } } +void RasterizerStorageGLES2::texture_set_force_redraw_if_visible(RID p_texture, bool p_enable) { + + Texture *texture = texture_owner.getornull(p_texture); + ERR_FAIL_COND(!texture); + + texture->redraw_if_visible = p_enable; +} + void RasterizerStorageGLES2::texture_set_detect_3d_callback(RID p_texture, VisualServer::TextureDetectCallback p_callback, void *p_userdata) { // TODO } @@ -1182,7 +1190,7 @@ RID RasterizerStorageGLES2::multimesh_create() { return RID(); } -void RasterizerStorageGLES2::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format,VS::MultimeshCustomDataFormat p_data) { +void RasterizerStorageGLES2::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data) { } int RasterizerStorageGLES2::multimesh_get_instance_count(RID p_multimesh) const { @@ -1225,8 +1233,6 @@ Color RasterizerStorageGLES2::multimesh_instance_get_custom_data(RID p_multimesh } void RasterizerStorageGLES2::multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array) { - - } void RasterizerStorageGLES2::multimesh_set_visible_instances(RID p_multimesh, int p_visible) { @@ -1999,6 +2005,8 @@ void RasterizerStorageGLES2::initialize() { } } + config.shrink_textures_x2 = false; + frame.count = 0; frame.prev_tick = 0; frame.delta = 0; diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index df8c2fcf47..30e13a9f65 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -182,6 +182,8 @@ public: Ref<Image> images[6]; + bool redraw_if_visible; + Texture() { flags = 0; width = 0; @@ -205,6 +207,8 @@ public: proxy = NULL; render_target = NULL; + + redraw_if_visible = false; } _ALWAYS_INLINE_ Texture *get_ptr() { @@ -264,6 +268,8 @@ public: virtual void texture_set_detect_srgb_callback(RID p_texture, VisualServer::TextureDetectCallback p_callback, void *p_userdata); virtual void texture_set_detect_normal_callback(RID p_texture, VisualServer::TextureDetectCallback p_callback, void *p_userdata); + virtual void texture_set_force_redraw_if_visible(RID p_texture, bool p_enable); + /* SKY API */ virtual RID sky_create(); @@ -508,7 +514,7 @@ public: virtual RID multimesh_create(); - virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format,VS::MultimeshCustomDataFormat p_data=VS::MULTIMESH_CUSTOM_DATA_NONE); + virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE); virtual int multimesh_get_instance_count(RID p_multimesh) const; virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh); diff --git a/drivers/gles2/shader_compiler_gles2.cpp b/drivers/gles2/shader_compiler_gles2.cpp index aa55e72083..549d91a5a0 100644 --- a/drivers/gles2/shader_compiler_gles2.cpp +++ b/drivers/gles2/shader_compiler_gles2.cpp @@ -721,8 +721,6 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() { actions[VS::SHADER_CANVAS_ITEM].renames["NORMAL"] = "normal"; actions[VS::SHADER_CANVAS_ITEM].renames["NORMALMAP"] = "normal_map"; actions[VS::SHADER_CANVAS_ITEM].renames["NORMALMAP_DEPTH"] = "normal_depth"; - actions[VS::SHADER_CANVAS_ITEM].renames["UV"] = "uv_interp"; - actions[VS::SHADER_CANVAS_ITEM].renames["COLOR"] = "color"; actions[VS::SHADER_CANVAS_ITEM].renames["TEXTURE"] = "color_texture"; actions[VS::SHADER_CANVAS_ITEM].renames["TEXTURE_PIXEL_SIZE"] = "color_texpixel_size"; actions[VS::SHADER_CANVAS_ITEM].renames["NORMAL_TEXTURE"] = "normal_texture"; @@ -736,7 +734,6 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() { actions[VS::SHADER_CANVAS_ITEM].renames["LIGHT_HEIGHT"] = "light_height"; actions[VS::SHADER_CANVAS_ITEM].renames["LIGHT_COLOR"] = "light_color"; actions[VS::SHADER_CANVAS_ITEM].renames["LIGHT_UV"] = "light_uv"; - //actions[VS::SHADER_CANVAS_ITEM].renames["LIGHT_SHADOW_COLOR"]="light_shadow_color"; actions[VS::SHADER_CANVAS_ITEM].renames["LIGHT"] = "light"; actions[VS::SHADER_CANVAS_ITEM].renames["SHADOW_COLOR"] = "shadow_color"; @@ -768,7 +765,8 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() { actions[VS::SHADER_SPATIAL].renames["UV2"] = "uv2_interp"; actions[VS::SHADER_SPATIAL].renames["COLOR"] = "color_interp"; actions[VS::SHADER_SPATIAL].renames["POINT_SIZE"] = "gl_PointSize"; - //actions[VS::SHADER_SPATIAL].renames["INSTANCE_ID"]=ShaderLanguage::TYPE_INT; + // gl_InstanceID is not available in OpenGL ES 2.0 + actions[VS::SHADER_SPATIAL].renames["INSTANCE_ID"] = "0"; //builtins @@ -790,13 +788,11 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() { actions[VS::SHADER_SPATIAL].renames["CLEARCOAT_GLOSS"] = "clearcoat_gloss"; actions[VS::SHADER_SPATIAL].renames["ANISOTROPY"] = "anisotropy"; actions[VS::SHADER_SPATIAL].renames["ANISOTROPY_FLOW"] = "anisotropy_flow"; - //actions[VS::SHADER_SPATIAL].renames["SSS_SPREAD"] = "sss_spread"; actions[VS::SHADER_SPATIAL].renames["SSS_STRENGTH"] = "sss_strength"; actions[VS::SHADER_SPATIAL].renames["TRANSMISSION"] = "transmission"; actions[VS::SHADER_SPATIAL].renames["AO"] = "ao"; actions[VS::SHADER_SPATIAL].renames["AO_LIGHT_AFFECT"] = "ao_light_affect"; actions[VS::SHADER_SPATIAL].renames["EMISSION"] = "emission"; - //actions[VS::SHADER_SPATIAL].renames["SCREEN_UV"]=ShaderLanguage::TYPE_VEC2; actions[VS::SHADER_SPATIAL].renames["POINT_COORD"] = "gl_PointCoord"; actions[VS::SHADER_SPATIAL].renames["INSTANCE_CUSTOM"] = "instance_custom"; actions[VS::SHADER_SPATIAL].renames["SCREEN_UV"] = "screen_uv"; @@ -838,8 +834,6 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() { actions[VS::SHADER_SPATIAL].usage_defines["DIFFUSE_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; actions[VS::SHADER_SPATIAL].usage_defines["SPECULAR_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions[VS::SHADER_SPATIAL].renames["SSS_STRENGTH"] = "sss_strength"; - actions[VS::SHADER_SPATIAL].render_mode_defines["skip_vertex_transform"] = "#define SKIP_TRANSFORM_USED\n"; actions[VS::SHADER_SPATIAL].render_mode_defines["world_vertex_coords"] = "#define VERTEX_WORLD_COORDS_USED\n"; diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index f214943bcf..5e13bed198 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -211,6 +211,10 @@ RasterizerStorageGLES3::Texture *RasterizerCanvasGLES3::_bind_canvas_texture(con } else { + if (texture->redraw_if_visible) { //check before proxy, because this is usually used with proxies + VisualServerRaster::redraw_request(); + } + texture = texture->get_ptr(); if (texture->render_target) @@ -248,6 +252,10 @@ RasterizerStorageGLES3::Texture *RasterizerCanvasGLES3::_bind_canvas_texture(con } else { + if (normal_map->redraw_if_visible) { //check before proxy, because this is usually used with proxies + VisualServerRaster::redraw_request(); + } + normal_map = normal_map->get_ptr(); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, normal_map->tex_id); @@ -1266,6 +1274,10 @@ void RasterizerCanvasGLES3::canvas_render_items(Item *p_item_list, int p_z, cons continue; } + if (t->redraw_if_visible) { //check before proxy, because this is usually used with proxies + VisualServerRaster::redraw_request(); + } + t = t->get_ptr(); if (storage->config.srgb_decode_supported && t->using_srgb) { @@ -1883,7 +1895,7 @@ void RasterizerCanvasGLES3::initialize() { } { - uint32_t poly_size = GLOBAL_DEF("rendering/limits/buffers/canvas_polygon_buffer_size_kb", 128); + uint32_t poly_size = GLOBAL_DEF_RST("rendering/limits/buffers/canvas_polygon_buffer_size_kb", 128); poly_size *= 1024; //kb poly_size = MAX(poly_size, (2 + 2 + 4) * 4 * sizeof(float)); glGenBuffers(1, &data.polygon_buffer); @@ -1930,7 +1942,7 @@ void RasterizerCanvasGLES3::initialize() { glGenVertexArrays(1, &data.polygon_buffer_pointer_array); - uint32_t index_size = GLOBAL_DEF("rendering/limits/buffers/canvas_polygon_index_buffer_size_kb", 128); + uint32_t index_size = GLOBAL_DEF_RST("rendering/limits/buffers/canvas_polygon_index_buffer_size_kb", 128); index_size *= 1024; //kb glGenBuffers(1, &data.polygon_index_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.polygon_index_buffer); diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 7c2af755cd..2b7cea8508 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1225,7 +1225,12 @@ bool RasterizerSceneGLES3::_setup_material(RasterizerStorageGLES3::Material *p_m } else { + if (t->redraw_if_visible) { //must check before proxy because this is often used with proxies + VisualServerRaster::redraw_request(); + } + t = t->get_ptr(); //resolve for proxies + #ifdef TOOLS_ENABLED if (t->detect_3d) { t->detect_3d(t->detect_3d_ud); @@ -1569,6 +1574,11 @@ void RasterizerSceneGLES3::_render_geometry(RenderList::Element *e) { RasterizerStorageGLES3::Texture *t = storage->texture_owner.get(c.texture); t = t->get_ptr(); //resolve for proxies + + if (t->redraw_if_visible) { + VisualServerRaster::redraw_request(); + } + #ifdef TOOLS_ENABLED if (t->detect_3d) { t->detect_3d(t->detect_3d_ud); @@ -4076,6 +4086,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const state.ubo_data.z_slope_scale = 0; state.ubo_data.shadow_dual_paraboloid_render_side = 0; state.ubo_data.shadow_dual_paraboloid_render_zfar = 0; + state.ubo_data.opaque_prepass_threshold = 0.99; p_cam_projection.get_viewport_size(state.ubo_data.viewport_size[0], state.ubo_data.viewport_size[1]); @@ -4688,6 +4699,7 @@ void RasterizerSceneGLES3::render_shadow(RID p_light, RID p_shadow_atlas, int p_ state.ubo_data.z_slope_scale = normal_bias; state.ubo_data.shadow_dual_paraboloid_render_side = dp_direction; state.ubo_data.shadow_dual_paraboloid_render_zfar = zfar; + state.ubo_data.opaque_prepass_threshold = 0.1; _setup_environment(NULL, light_projection, light_transform); @@ -4856,7 +4868,7 @@ void RasterizerSceneGLES3::initialize() { glBufferData(GL_UNIFORM_BUFFER, sizeof(State::EnvironmentRadianceUBO), &state.env_radiance_ubo, GL_DYNAMIC_DRAW); glBindBuffer(GL_UNIFORM_BUFFER, 0); - render_list.max_elements = GLOBAL_DEF("rendering/limits/rendering/max_renderable_elements", (int)RenderList::DEFAULT_MAX_ELEMENTS); + render_list.max_elements = GLOBAL_DEF_RST("rendering/limits/rendering/max_renderable_elements", (int)RenderList::DEFAULT_MAX_ELEMENTS); if (render_list.max_elements > 1000000) render_list.max_elements = 1000000; if (render_list.max_elements < 1024) diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 524212b9c1..cf387a69bc 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -141,6 +141,7 @@ public: float subsurface_scatter_width; float ambient_occlusion_affect_light; float ambient_occlusion_affect_ssao; + float opaque_prepass_threshold; uint32_t fog_depth_enabled; float fog_depth_begin; @@ -152,7 +153,7 @@ public: float fog_height_max; float fog_height_curve; // make sure this struct is padded to be a multiple of 16 bytes for webgl - float pad[3]; + float pad[2]; } ubo_data; diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 4a3ebf7a7c..9e389a353e 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -1328,6 +1328,13 @@ void RasterizerStorageGLES3::texture_set_proxy(RID p_texture, RID p_proxy) { } } +void RasterizerStorageGLES3::texture_set_force_redraw_if_visible(RID p_texture, bool p_enable) { + + Texture *texture = texture_owner.get(p_texture); + ERR_FAIL_COND(!texture); + texture->redraw_if_visible = p_enable; +} + RID RasterizerStorageGLES3::sky_create() { Sky *sky = memnew(Sky); @@ -3233,7 +3240,7 @@ void RasterizerStorageGLES3::mesh_surface_update_region(RID p_mesh, int p_surfac PoolVector<uint8_t>::Read r = p_data.read(); - glBindBuffer(GL_ARRAY_BUFFER, mesh->surfaces[p_surface]->array_id); + glBindBuffer(GL_ARRAY_BUFFER, mesh->surfaces[p_surface]->vertex_id); glBufferSubData(GL_ARRAY_BUFFER, p_offset, total_size, r.ptr()); glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind } @@ -3397,6 +3404,7 @@ Vector<PoolVector<uint8_t> > RasterizerStorageGLES3::mesh_surface_get_blend_shap return bsarr; } + Vector<AABB> RasterizerStorageGLES3::mesh_surface_get_skeleton_aabb(RID p_mesh, int p_surface) const { const Mesh *mesh = mesh_owner.getornull(p_mesh); @@ -3448,6 +3456,7 @@ void RasterizerStorageGLES3::mesh_remove_surface(RID p_mesh, int p_surface) { mesh->instance_change_notify(); } + int RasterizerStorageGLES3::mesh_get_surface_count(RID p_mesh) const { const Mesh *mesh = mesh_owner.getornull(p_mesh); @@ -3461,6 +3470,7 @@ void RasterizerStorageGLES3::mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb ERR_FAIL_COND(!mesh); mesh->custom_aabb = p_aabb; + mesh->instance_change_notify(); } AABB RasterizerStorageGLES3::mesh_get_custom_aabb(RID p_mesh) const { @@ -7456,7 +7466,7 @@ void RasterizerStorageGLES3::initialize() { { //transform feedback buffers - uint32_t xf_feedback_size = GLOBAL_DEF("rendering/limits/buffers/blend_shape_max_buffer_size_kb", 4096); + uint32_t xf_feedback_size = GLOBAL_DEF_RST("rendering/limits/buffers/blend_shape_max_buffer_size_kb", 4096); for (int i = 0; i < 2; i++) { glGenBuffers(1, &resources.transform_feedback_buffers[i]); diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index 7a2d56f69b..1db577f23c 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -268,6 +268,7 @@ public: GLuint tex_id; bool using_srgb; + bool redraw_if_visible; uint16_t stored_cube_sides; @@ -306,6 +307,7 @@ public: detect_normal = NULL; detect_normal_ud = NULL; proxy = NULL; + redraw_if_visible = false; } _ALWAYS_INLINE_ Texture *get_ptr() { @@ -366,6 +368,7 @@ public: virtual void texture_set_detect_normal_callback(RID p_texture, VisualServer::TextureDetectCallback p_callback, void *p_userdata); virtual void texture_set_proxy(RID p_texture, RID p_proxy); + virtual void texture_set_force_redraw_if_visible(RID p_texture, bool p_enable); /* SKY API */ @@ -690,7 +693,6 @@ public: AABB custom_aabb; mutable uint64_t last_pass; SelfList<MultiMesh>::List multimeshes; - _FORCE_INLINE_ void update_multimeshes() { SelfList<MultiMesh> *mm = multimeshes.first(); diff --git a/drivers/gles3/shader_compiler_gles3.cpp b/drivers/gles3/shader_compiler_gles3.cpp index 9ad16ac2a2..f3ba7aa408 100644 --- a/drivers/gles3/shader_compiler_gles3.cpp +++ b/drivers/gles3/shader_compiler_gles3.cpp @@ -780,8 +780,6 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() { actions[VS::SHADER_CANVAS_ITEM].renames["NORMAL"] = "normal"; actions[VS::SHADER_CANVAS_ITEM].renames["NORMALMAP"] = "normal_map"; actions[VS::SHADER_CANVAS_ITEM].renames["NORMALMAP_DEPTH"] = "normal_depth"; - actions[VS::SHADER_CANVAS_ITEM].renames["UV"] = "uv_interp"; - actions[VS::SHADER_CANVAS_ITEM].renames["COLOR"] = "color"; actions[VS::SHADER_CANVAS_ITEM].renames["TEXTURE"] = "color_texture"; actions[VS::SHADER_CANVAS_ITEM].renames["TEXTURE_PIXEL_SIZE"] = "color_texpixel_size"; actions[VS::SHADER_CANVAS_ITEM].renames["NORMAL_TEXTURE"] = "normal_texture"; @@ -824,7 +822,7 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() { actions[VS::SHADER_SPATIAL].renames["UV2"] = "uv2_interp"; actions[VS::SHADER_SPATIAL].renames["COLOR"] = "color_interp"; actions[VS::SHADER_SPATIAL].renames["POINT_SIZE"] = "gl_PointSize"; - //actions[VS::SHADER_SPATIAL].renames["INSTANCE_ID"]=ShaderLanguage::TYPE_INT; + actions[VS::SHADER_SPATIAL].renames["INSTANCE_ID"] = "gl_InstanceID"; //builtins @@ -846,13 +844,11 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() { actions[VS::SHADER_SPATIAL].renames["CLEARCOAT_GLOSS"] = "clearcoat_gloss"; actions[VS::SHADER_SPATIAL].renames["ANISOTROPY"] = "anisotropy"; actions[VS::SHADER_SPATIAL].renames["ANISOTROPY_FLOW"] = "anisotropy_flow"; - //actions[VS::SHADER_SPATIAL].renames["SSS_SPREAD"] = "sss_spread"; actions[VS::SHADER_SPATIAL].renames["SSS_STRENGTH"] = "sss_strength"; actions[VS::SHADER_SPATIAL].renames["TRANSMISSION"] = "transmission"; actions[VS::SHADER_SPATIAL].renames["AO"] = "ao"; actions[VS::SHADER_SPATIAL].renames["AO_LIGHT_AFFECT"] = "ao_light_affect"; actions[VS::SHADER_SPATIAL].renames["EMISSION"] = "emission"; - //actions[VS::SHADER_SPATIAL].renames["SCREEN_UV"]=ShaderLanguage::TYPE_VEC2; actions[VS::SHADER_SPATIAL].renames["POINT_COORD"] = "gl_PointCoord"; actions[VS::SHADER_SPATIAL].renames["INSTANCE_CUSTOM"] = "instance_custom"; actions[VS::SHADER_SPATIAL].renames["SCREEN_UV"] = "screen_uv"; @@ -894,8 +890,6 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() { actions[VS::SHADER_SPATIAL].usage_defines["DIFFUSE_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; actions[VS::SHADER_SPATIAL].usage_defines["SPECULAR_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions[VS::SHADER_SPATIAL].renames["SSS_STRENGTH"] = "sss_strength"; - actions[VS::SHADER_SPATIAL].render_mode_defines["skip_vertex_transform"] = "#define SKIP_TRANSFORM_USED\n"; actions[VS::SHADER_SPATIAL].render_mode_defines["world_vertex_coords"] = "#define VERTEX_WORLD_COORDS_USED\n"; actions[VS::SHADER_SPATIAL].render_mode_defines["ensure_correct_normals"] = "#define ENSURE_CORRECT_NORMALS\n"; @@ -913,6 +907,7 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() { actions[VS::SHADER_SPATIAL].render_mode_defines["specular_toon"] = "#define SPECULAR_TOON\n"; actions[VS::SHADER_SPATIAL].render_mode_defines["specular_disabled"] = "#define SPECULAR_DISABLED\n"; actions[VS::SHADER_SPATIAL].render_mode_defines["shadows_disabled"] = "#define SHADOWS_DISABLED\n"; + actions[VS::SHADER_SPATIAL].render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n"; /* PARTICLES SHADER */ diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index ed8df04377..6fd85cc1dd 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -91,6 +91,7 @@ layout(std140) uniform SceneData { //ubo:0 mediump float subsurface_scatter_width; mediump float ambient_occlusion_affect_light; mediump float ambient_occlusion_affect_ao_channel; + mediump float opaque_prepass_threshold; bool fog_depth_enabled; highp float fog_depth_begin; @@ -571,11 +572,6 @@ in vec3 normal_interp; /* PBR CHANNELS */ -//used on forward mainly -uniform bool no_ambient_light; - - - #ifdef USE_RADIANCE_MAP @@ -684,6 +680,7 @@ layout(std140) uniform SceneData { mediump float subsurface_scatter_width; mediump float ambient_occlusion_affect_light; mediump float ambient_occlusion_affect_ao_channel; + mediump float opaque_prepass_threshold; bool fog_depth_enabled; highp float fog_depth_begin; @@ -1031,12 +1028,11 @@ LIGHT_SHADER_CODE diffuse_brdf_NL = cNdotL * (1.0 / M_PI); #endif -#if defined(TRANSMISSION_USED) - diffuse_light += light_color * diffuse_color * mix(vec3(diffuse_brdf_NL), vec3(M_PI), transmission) * attenuation; -#else diffuse_light += light_color * diffuse_color * diffuse_brdf_NL * attenuation; -#endif +#if defined(TRANSMISSION_USED) + diffuse_light += light_color * diffuse_color * (vec3(1.0 / M_PI) - diffuse_brdf_NL) * transmission * attenuation; +#endif #if defined(LIGHT_USE_RIM) @@ -1633,7 +1629,7 @@ void main() { float alpha = 1.0; #if defined(DO_SIDE_CHECK) - float side=float(gl_FrontFacing)*2.0-1.0; + float side=gl_FrontFacing ? 1.0 : -1.0; #else float side=1.0; #endif @@ -1695,9 +1691,10 @@ FRAGMENT_SHADER_CODE #ifdef USE_OPAQUE_PREPASS - if (alpha<0.99) { + if (alpha<opaque_prepass_threshold) { discard; } + #endif #if defined(ENABLE_NORMALMAP) @@ -1752,42 +1749,43 @@ FRAGMENT_SHADER_CODE #ifdef USE_RADIANCE_MAP - if (no_ambient_light) { - ambient_light=vec3(0.0,0.0,0.0); - } else { - { - - { //read radiance from dual paraboloid +#ifdef AMBIENT_LIGHT_DISABLED + ambient_light=vec3(0.0,0.0,0.0); +#else + { - vec3 ref_vec = reflect(-eye_vec,normal); //2.0 * ndotv * normal - view; // reflect(v, n); - ref_vec=normalize((radiance_inverse_xform * vec4(ref_vec,0.0)).xyz); - vec3 radiance = textureDualParaboloid(radiance_map,ref_vec,roughness) * bg_energy; - env_reflection_light = radiance; + { //read radiance from dual paraboloid - } - //no longer a cubemap - //vec3 radiance = textureLod(radiance_cube, r, lod).xyz * ( brdf.x + brdf.y); + vec3 ref_vec = reflect(-eye_vec,normal); //2.0 * ndotv * normal - view; // reflect(v, n); + ref_vec=normalize((radiance_inverse_xform * vec4(ref_vec,0.0)).xyz); + vec3 radiance = textureDualParaboloid(radiance_map,ref_vec,roughness) * bg_energy; + env_reflection_light = radiance; } + //no longer a cubemap + //vec3 radiance = textureLod(radiance_cube, r, lod).xyz * ( brdf.x + brdf.y); + + } #ifndef USE_LIGHTMAP - { + { - vec3 ambient_dir=normalize((radiance_inverse_xform * vec4(normal,0.0)).xyz); - vec3 env_ambient=textureDualParaboloid(radiance_map,ambient_dir,1.0) * bg_energy; + vec3 ambient_dir=normalize((radiance_inverse_xform * vec4(normal,0.0)).xyz); + vec3 env_ambient=textureDualParaboloid(radiance_map,ambient_dir,1.0) * bg_energy; - ambient_light=mix(ambient_light_color.rgb,env_ambient,radiance_ambient_contribution); - //ambient_light=vec3(0.0,0.0,0.0); - } -#endif + ambient_light=mix(ambient_light_color.rgb,env_ambient,radiance_ambient_contribution); + //ambient_light=vec3(0.0,0.0,0.0); } +#endif +#endif //AMBIENT_LIGHT_DISABLED #else - if (no_ambient_light){ - ambient_light=vec3(0.0,0.0,0.0); - } else { - ambient_light=ambient_light_color.rgb; - } +#ifdef AMBIENT_LIGHT_DISABLED + ambient_light=vec3(0.0,0.0,0.0); +#else + ambient_light=ambient_light_color.rgb; +#endif //AMBIENT_LIGHT_DISABLED + #endif ambient_light*=ambient_energy; @@ -2142,6 +2140,8 @@ FRAGMENT_SHADER_CODE #else + + //approximate ambient scale for SSAO, since we will lack full ambient float max_emission=max(emission.r,max(emission.g,emission.b)); float max_ambient=max(ambient_light.r,max(ambient_light.g,ambient_light.b)); @@ -2173,7 +2173,6 @@ FRAGMENT_SHADER_CODE frag_color=vec4(emission+ambient_light+diffuse_light+specular_light,alpha); #endif //SHADELESS - #endif //USE_MULTIPLE_RENDER_TARGETS diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp index 0f47949b4b..864b9714a9 100644 --- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp +++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp @@ -155,7 +155,7 @@ Error AudioDriverPulseAudio::init_device() { break; } - int latency = GLOBAL_DEF("audio/output_latency", DEFAULT_OUTPUT_LATENCY); + int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); buffer_frames = closest_power_of_2(latency * mix_rate / 1000); pa_buffer_size = buffer_frames * pa_map.channels; @@ -204,7 +204,7 @@ Error AudioDriverPulseAudio::init() { thread_exited = false; exit_thread = false; - mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE); + mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); pa_ml = pa_mainloop_new(); ERR_FAIL_COND_V(pa_ml == NULL, ERR_CANT_OPEN); @@ -289,18 +289,18 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) { AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)p_udata; while (!ad->exit_thread) { + + ad->lock(); + ad->start_counting_ticks(); + if (!ad->active) { for (unsigned int i = 0; i < ad->pa_buffer_size; i++) { ad->samples_out[i] = 0; } } else { - ad->lock(); - ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw()); - ad->unlock(); - if (ad->channels == ad->pa_map.channels) { for (unsigned int i = 0; i < ad->pa_buffer_size; i++) { ad->samples_out[i] = ad->samples_in[i] >> 16; @@ -323,9 +323,6 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) { int error_code; int byte_size = ad->pa_buffer_size * sizeof(int16_t); - - ad->lock(); - int ret; do { ret = pa_mainloop_iterate(ad->pa_ml, 0, NULL); @@ -350,11 +347,13 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) { if (ret == 0) { // If pa_mainloop_iterate returns 0 sleep for 1 msec to wait // for the stream to be able to process more bytes + ad->stop_counting_ticks(); ad->unlock(); OS::get_singleton()->delay_usec(1000); ad->lock(); + ad->start_counting_ticks(); } } } @@ -380,6 +379,7 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) { } } + ad->stop_counting_ticks(); ad->unlock(); } @@ -403,7 +403,6 @@ AudioDriver::SpeakerMode AudioDriverPulseAudio::get_speaker_mode() const { void AudioDriverPulseAudio::pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata; - int ctr = 0; // If eol is set to a positive number, you're at the end of the list if (eol > 0) { @@ -453,7 +452,9 @@ String AudioDriverPulseAudio::get_device() { void AudioDriverPulseAudio::set_device(String device) { + lock(); new_device = device; + unlock(); } void AudioDriverPulseAudio::lock() { diff --git a/drivers/rtaudio/audio_driver_rtaudio.cpp b/drivers/rtaudio/audio_driver_rtaudio.cpp index 457486797f..365788e192 100644 --- a/drivers/rtaudio/audio_driver_rtaudio.cpp +++ b/drivers/rtaudio/audio_driver_rtaudio.cpp @@ -88,7 +88,7 @@ Error AudioDriverRtAudio::init() { // FIXME: Adapt to the OutputFormat -> SpeakerMode change /* - String channels = GLOBAL_DEF("audio/output","stereo"); + String channels = GLOBAL_DEF_RST("audio/output","stereo"); if (channels=="5.1") output_format=OUTPUT_5_1; @@ -108,7 +108,7 @@ Error AudioDriverRtAudio::init() { options.numberOfBuffers = 4; parameters.firstChannel = 0; - mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE); + mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); int latency = GLOBAL_DEF("audio/output_latency", DEFAULT_OUTPUT_LATENCY); unsigned int buffer_frames = closest_power_of_2(latency * mix_rate / 1000); diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index e1680601ad..1d96f9ee7d 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -318,7 +318,7 @@ Error AudioDriverWASAPI::finish_device() { Error AudioDriverWASAPI::init() { - mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE); + mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); Error err = init_device(); if (err != OK) { @@ -335,22 +335,6 @@ Error AudioDriverWASAPI::init() { return OK; } -Error AudioDriverWASAPI::reopen() { - Error err = finish_device(); - if (err != OK) { - ERR_PRINT("WASAPI: finish_device error"); - } else { - err = init_device(); - if (err != OK) { - ERR_PRINT("WASAPI: init_device error"); - } else { - start(); - } - } - - return err; -} - int AudioDriverWASAPI::get_mix_rate() const { return mix_rate; @@ -416,7 +400,9 @@ String AudioDriverWASAPI::get_device() { void AudioDriverWASAPI::set_device(String device) { + lock(); new_device = device; + unlock(); } void AudioDriverWASAPI::write_sample(AudioDriverWASAPI *ad, BYTE *buffer, int i, int32_t sample) { @@ -453,24 +439,31 @@ void AudioDriverWASAPI::thread_func(void *p_udata) { AudioDriverWASAPI *ad = (AudioDriverWASAPI *)p_udata; while (!ad->exit_thread) { - if (ad->active) { - ad->lock(); - ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw()); + ad->lock(); + ad->start_counting_ticks(); - ad->unlock(); + if (ad->active) { + ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw()); } else { for (unsigned int i = 0; i < ad->buffer_size; i++) { ad->samples_in[i] = 0; } } + ad->stop_counting_ticks(); + ad->unlock(); + unsigned int left_frames = ad->buffer_frames; unsigned int buffer_idx = 0; while (left_frames > 0 && ad->audio_client) { WaitForSingleObject(ad->event, 1000); + ad->lock(); + ad->start_counting_ticks(); + UINT32 cur_frames; + bool invalidated = false; HRESULT hr = ad->audio_client->GetCurrentPadding(&cur_frames); if (hr == S_OK) { // Check how much frames are available on the WASAPI buffer @@ -506,34 +499,34 @@ void AudioDriverWASAPI::thread_func(void *p_udata) { left_frames -= write_frames; } else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { - // Device is not valid anymore, reopen it - - Error err = ad->finish_device(); - if (err != OK) { - ERR_PRINT("WASAPI: finish_device error"); - } else { - // We reopened the device and samples_in may have resized, so invalidate the current left_frames - left_frames = 0; - } + invalidated = true; } else { ERR_PRINT("WASAPI: Get buffer error"); ad->exit_thread = true; } } else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { - // Device is not valid anymore, reopen it + invalidated = true; + } else { + ERR_PRINT("WASAPI: GetCurrentPadding error"); + } + + if (invalidated) { + // Device is not valid anymore + WARN_PRINT("WASAPI: Current device invalidated, closing device"); Error err = ad->finish_device(); if (err != OK) { ERR_PRINT("WASAPI: finish_device error"); - } else { - // We reopened the device and samples_in may have resized, so invalidate the current left_frames - left_frames = 0; } - } else { - ERR_PRINT("WASAPI: GetCurrentPadding error"); } + + ad->stop_counting_ticks(); + ad->unlock(); } + ad->lock(); + ad->start_counting_ticks(); + // If we're using the Default device and it changed finish it so we'll re-init the device if (ad->device_name == "Default" && default_device_changed) { Error err = ad->finish_device(); @@ -559,6 +552,9 @@ void AudioDriverWASAPI::thread_func(void *p_udata) { ad->start(); } } + + ad->stop_counting_ticks(); + ad->unlock(); } ad->thread_exited = true; diff --git a/drivers/wasapi/audio_driver_wasapi.h b/drivers/wasapi/audio_driver_wasapi.h index c97f4c288c..f3ee5976eb 100644 --- a/drivers/wasapi/audio_driver_wasapi.h +++ b/drivers/wasapi/audio_driver_wasapi.h @@ -72,7 +72,6 @@ class AudioDriverWASAPI : public AudioDriver { Error init_device(bool reinit = false); Error finish_device(); - Error reopen(); public: virtual const char *get_name() const { diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index aa0fd34e0a..ea194e5eae 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -141,9 +141,9 @@ void FileAccessWindows::close() { bool rename_error = true; int attempts = 4; while (rename_error && attempts) { - // This workaround of trying multiple times is added to deal with paranoid Windows - // antiviruses that love reading just written files even if they are not executable, thus - // locking the file and preventing renaming from happening. + // This workaround of trying multiple times is added to deal with paranoid Windows + // antiviruses that love reading just written files even if they are not executable, thus + // locking the file and preventing renaming from happening. #ifdef UWP_ENABLED // UWP has no PathFileExists, so we check attributes instead diff --git a/drivers/winmidi/SCsub b/drivers/winmidi/SCsub new file mode 100644 index 0000000000..233593b0f9 --- /dev/null +++ b/drivers/winmidi/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import('env') + +# Driver source files +env.add_source_files(env.drivers_sources, "*.cpp") + +Export('env') diff --git a/drivers/winmidi/win_midi.cpp b/drivers/winmidi/win_midi.cpp new file mode 100644 index 0000000000..6da6e31b2b --- /dev/null +++ b/drivers/winmidi/win_midi.cpp @@ -0,0 +1,100 @@ +/*************************************************************************/ +/* win_midi.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef WINMIDI_ENABLED + +#include "win_midi.h" +#include "print_string.h" + +void MIDIDriverWinMidi::read(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + + if (wMsg == MIM_DATA) { + receive_input_packet((uint64_t)dwParam2, (uint8_t *)&dwParam1, 3); + } +} + +Error MIDIDriverWinMidi::open() { + + for (UINT i = 0; i < midiInGetNumDevs(); i++) { + HMIDIIN midi_in; + + MMRESULT res = midiInOpen(&midi_in, i, (DWORD_PTR)read, (DWORD_PTR)this, CALLBACK_FUNCTION); + if (res == MMSYSERR_NOERROR) { + midiInStart(midi_in); + connected_sources.insert(i, midi_in); + } else { + char err[256]; + midiInGetErrorText(res, err, 256); + ERR_PRINTS("midiInOpen error: " + String(err)); + } + } + + return OK; +} + +PoolStringArray MIDIDriverWinMidi::get_connected_inputs() { + + PoolStringArray list; + + for (int i = 0; i < connected_sources.size(); i++) { + HMIDIIN midi_in = connected_sources[i]; + UINT id = 0; + MMRESULT res = midiInGetID(midi_in, &id); + if (res == MMSYSERR_NOERROR) { + MIDIINCAPS caps; + res = midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS)); + if (res == MMSYSERR_NOERROR) { + list.push_back(caps.szPname); + } + } + } + + return list; +} + +void MIDIDriverWinMidi::close() { + + for (int i = 0; i < connected_sources.size(); i++) { + HMIDIIN midi_in = connected_sources[i]; + midiInStop(midi_in); + midiInClose(midi_in); + } + connected_sources.clear(); +} + +MIDIDriverWinMidi::MIDIDriverWinMidi() { +} + +MIDIDriverWinMidi::~MIDIDriverWinMidi() { + + close(); +} + +#endif diff --git a/drivers/winmidi/win_midi.h b/drivers/winmidi/win_midi.h new file mode 100644 index 0000000000..1cf9b19b5d --- /dev/null +++ b/drivers/winmidi/win_midi.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* win_midi.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef WINMIDI_ENABLED + +#ifndef WIN_MIDI_H +#define WIN_MIDI_H + +#include <stdio.h> +#include <windows.h> + +#include <mmsystem.h> + +#include "core/vector.h" +#include "os/midi_driver.h" + +class MIDIDriverWinMidi : public MIDIDriver { + + Vector<HMIDIIN> connected_sources; + + static void CALLBACK read(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2); + +public: + virtual Error open(); + virtual void close(); + + virtual PoolStringArray get_connected_inputs(); + + MIDIDriverWinMidi(); + virtual ~MIDIDriverWinMidi(); +}; + +#endif +#endif diff --git a/drivers/xaudio2/audio_driver_xaudio2.cpp b/drivers/xaudio2/audio_driver_xaudio2.cpp index 6675459313..a1002ef4f9 100644 --- a/drivers/xaudio2/audio_driver_xaudio2.cpp +++ b/drivers/xaudio2/audio_driver_xaudio2.cpp @@ -50,7 +50,7 @@ Error AudioDriverXAudio2::init() { speaker_mode = SPEAKER_MODE_STEREO; channels = 2; - int latency = GLOBAL_DEF("audio/output_latency", 25); + int latency = GLOBAL_DEF_RST("audio/output_latency", 25); buffer_size = closest_power_of_2(latency * mix_rate / 1000); samples_in = memnew_arr(int32_t, buffer_size * channels); @@ -97,8 +97,6 @@ void AudioDriverXAudio2::thread_func(void *p_udata) { AudioDriverXAudio2 *ad = (AudioDriverXAudio2 *)p_udata; - uint64_t usdelay = (ad->buffer_size / float(ad->mix_rate)) * 1000000; - while (!ad->exit_thread) { if (!ad->active) { diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 6aec6135f1..925f97de8e 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -784,6 +784,345 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_v_scroll_speed(EditorSettings::get_singleton()->get("text_editor/open_scripts/v_scroll_speed")); } +void CodeTextEditor::trim_trailing_whitespace() { + bool trimed_whitespace = false; + for (int i = 0; i < text_editor->get_line_count(); i++) { + String line = text_editor->get_line(i); + if (line.ends_with(" ") || line.ends_with("\t")) { + + if (!trimed_whitespace) { + text_editor->begin_complex_operation(); + trimed_whitespace = true; + } + + int end = 0; + for (int j = line.length() - 1; j > -1; j--) { + if (line[j] != ' ' && line[j] != '\t') { + end = j + 1; + break; + } + } + text_editor->set_line(i, line.substr(0, end)); + } + } + + if (trimed_whitespace) { + text_editor->end_complex_operation(); + text_editor->update(); + } +} + +void CodeTextEditor::convert_indent_to_spaces() { + int indent_size = EditorSettings::get_singleton()->get("text_editor/indent/size"); + String indent = ""; + + for (int i = 0; i < indent_size; i++) { + indent += " "; + } + + int cursor_line = text_editor->cursor_get_line(); + int cursor_column = text_editor->cursor_get_column(); + + bool changed_indentation = false; + for (int i = 0; i < text_editor->get_line_count(); i++) { + String line = text_editor->get_line(i); + + if (line.length() <= 0) { + continue; + } + + int j = 0; + while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) { + if (line[j] == '\t') { + if (!changed_indentation) { + text_editor->begin_complex_operation(); + changed_indentation = true; + } + if (cursor_line == i && cursor_column > j) { + cursor_column += indent_size - 1; + } + line = line.left(j) + indent + line.right(j + 1); + } + j++; + } + if (changed_indentation) { + text_editor->set_line(i, line); + } + } + if (changed_indentation) { + text_editor->cursor_set_column(cursor_column); + text_editor->end_complex_operation(); + text_editor->update(); + } +} + +void CodeTextEditor::convert_indent_to_tabs() { + int indent_size = EditorSettings::get_singleton()->get("text_editor/indent/size"); + indent_size -= 1; + + int cursor_line = text_editor->cursor_get_line(); + int cursor_column = text_editor->cursor_get_column(); + + bool changed_indentation = false; + for (int i = 0; i < text_editor->get_line_count(); i++) { + String line = text_editor->get_line(i); + + if (line.length() <= 0) { + continue; + } + + int j = 0; + int space_count = -1; + while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) { + if (line[j] != '\t') { + space_count++; + + if (space_count == indent_size) { + if (!changed_indentation) { + text_editor->begin_complex_operation(); + changed_indentation = true; + } + if (cursor_line == i && cursor_column > j) { + cursor_column -= indent_size; + } + line = line.left(j - indent_size) + "\t" + line.right(j + 1); + j = 0; + space_count = -1; + } + } else { + space_count = -1; + } + j++; + } + if (changed_indentation) { + text_editor->set_line(i, line); + } + } + if (changed_indentation) { + text_editor->cursor_set_column(cursor_column); + text_editor->end_complex_operation(); + text_editor->update(); + } +} + +void CodeTextEditor::convert_case(CaseStyle p_case) { + if (!text_editor->is_selection_active()) { + return; + } + + text_editor->begin_complex_operation(); + + int begin = text_editor->get_selection_from_line(); + int end = text_editor->get_selection_to_line(); + int begin_col = text_editor->get_selection_from_column(); + int end_col = text_editor->get_selection_to_column(); + + for (int i = begin; i <= end; i++) { + int len = text_editor->get_line(i).length(); + if (i == end) + len -= len - end_col; + if (i == begin) + len -= begin_col; + String new_line = text_editor->get_line(i).substr(i == begin ? begin_col : 0, len); + + switch (p_case) { + case UPPER: { + new_line = new_line.to_upper(); + } break; + case LOWER: { + new_line = new_line.to_lower(); + } break; + case CAPITALIZE: { + new_line = new_line.capitalize(); + } break; + } + + if (i == begin) { + new_line = text_editor->get_line(i).left(begin_col) + new_line; + } + if (i == end) { + new_line = new_line + text_editor->get_line(i).right(end_col); + } + text_editor->set_line(i, new_line); + } + text_editor->end_complex_operation(); +} + +void CodeTextEditor::move_lines_up() { + text_editor->begin_complex_operation(); + if (text_editor->is_selection_active()) { + int from_line = text_editor->get_selection_from_line(); + int from_col = text_editor->get_selection_from_column(); + int to_line = text_editor->get_selection_to_line(); + int to_column = text_editor->get_selection_to_column(); + + for (int i = from_line; i <= to_line; i++) { + int line_id = i; + int next_id = i - 1; + + if (line_id == 0 || next_id < 0) + return; + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->cursor_set_line(next_id); + } + int from_line_up = from_line > 0 ? from_line - 1 : from_line; + int to_line_up = to_line > 0 ? to_line - 1 : to_line; + text_editor->select(from_line_up, from_col, to_line_up, to_column); + } else { + int line_id = text_editor->cursor_get_line(); + int next_id = line_id - 1; + + if (line_id == 0 || next_id < 0) + return; + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->cursor_set_line(next_id); + } + text_editor->end_complex_operation(); + text_editor->update(); +} + +void CodeTextEditor::move_lines_down() { + text_editor->begin_complex_operation(); + if (text_editor->is_selection_active()) { + int from_line = text_editor->get_selection_from_line(); + int from_col = text_editor->get_selection_from_column(); + int to_line = text_editor->get_selection_to_line(); + int to_column = text_editor->get_selection_to_column(); + + for (int i = to_line; i >= from_line; i--) { + int line_id = i; + int next_id = i + 1; + + if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) + return; + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->cursor_set_line(next_id); + } + int from_line_down = from_line < text_editor->get_line_count() ? from_line + 1 : from_line; + int to_line_down = to_line < text_editor->get_line_count() ? to_line + 1 : to_line; + text_editor->select(from_line_down, from_col, to_line_down, to_column); + } else { + int line_id = text_editor->cursor_get_line(); + int next_id = line_id + 1; + + if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) + return; + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->cursor_set_line(next_id); + } + text_editor->end_complex_operation(); + text_editor->update(); +} + +void CodeTextEditor::delete_lines() { + text_editor->begin_complex_operation(); + if (text_editor->is_selection_active()) { + int to_line = text_editor->get_selection_to_line(); + int from_line = text_editor->get_selection_from_line(); + int count = Math::abs(to_line - from_line) + 1; + while (count) { + text_editor->set_line(text_editor->cursor_get_line(), ""); + text_editor->backspace_at_cursor(); + count--; + if (count) + text_editor->unfold_line(from_line); + } + text_editor->cursor_set_line(from_line - 1); + text_editor->deselect(); + } else { + int line = text_editor->cursor_get_line(); + text_editor->set_line(text_editor->cursor_get_line(), ""); + text_editor->backspace_at_cursor(); + text_editor->unfold_line(line); + text_editor->cursor_set_line(line); + } + text_editor->end_complex_operation(); +} + +void CodeTextEditor::code_lines_down() { + int from_line = text_editor->cursor_get_line(); + int to_line = text_editor->cursor_get_line(); + int column = text_editor->cursor_get_column(); + + if (text_editor->is_selection_active()) { + from_line = text_editor->get_selection_from_line(); + to_line = text_editor->get_selection_to_line(); + column = text_editor->cursor_get_column(); + } + int next_line = to_line + 1; + + if (to_line >= text_editor->get_line_count() - 1) { + text_editor->set_line(to_line, text_editor->get_line(to_line) + "\n"); + } + + text_editor->begin_complex_operation(); + for (int i = from_line; i <= to_line; i++) { + + text_editor->unfold_line(i); + if (i >= text_editor->get_line_count() - 1) { + text_editor->set_line(i, text_editor->get_line(i) + "\n"); + } + String line_clone = text_editor->get_line(i); + text_editor->insert_at(line_clone, next_line); + next_line++; + } + + text_editor->cursor_set_column(column); + if (text_editor->is_selection_active()) { + text_editor->select(to_line + 1, text_editor->get_selection_from_column(), next_line - 1, text_editor->get_selection_to_column()); + } + + text_editor->end_complex_operation(); + text_editor->update(); +} + +void CodeTextEditor::goto_line(int p_line) { + text_editor->deselect(); + text_editor->unfold_line(p_line); + text_editor->call_deferred("cursor_set_line", p_line); +} + +void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { + text_editor->unfold_line(p_line); + text_editor->call_deferred("cursor_set_line", p_line); + text_editor->call_deferred("cursor_set_column", p_begin); + text_editor->select(p_line, p_begin, p_line, p_end); +} + +Variant CodeTextEditor::get_edit_state() { + Dictionary state; + + state["scroll_position"] = text_editor->get_v_scroll(); + state["column"] = text_editor->cursor_get_column(); + state["row"] = text_editor->cursor_get_line(); + + return state; +} + +void CodeTextEditor::set_edit_state(const Variant &p_state) { + Dictionary state = p_state; + text_editor->cursor_set_column(state["column"]); + text_editor->cursor_set_line(state["row"]); + text_editor->set_v_scroll(state["scroll_position"]); + text_editor->grab_focus(); +} + void CodeTextEditor::set_error(const String &p_error) { error->set_text(p_error); diff --git a/editor/code_editor.h b/editor/code_editor.h index 2a3bb1ba76..903f61d87d 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -186,6 +186,29 @@ protected: static void _bind_methods(); public: + void trim_trailing_whitespace(); + + void convert_indent_to_spaces(); + void convert_indent_to_tabs(); + + enum CaseStyle { + UPPER, + LOWER, + CAPITALIZE, + }; + void convert_case(CaseStyle p_case); + + void move_lines_up(); + void move_lines_down(); + void delete_lines(); + void code_lines_down(); + + void goto_line(int p_line); + void goto_line_selection(int p_line, int p_begin, int p_end); + + Variant get_edit_state(); + void set_edit_state(const Variant &p_state); + void update_editor_settings(); void set_error(const String &p_error); void update_line_and_column() { _line_col_changed(); } diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 7f93917744..c4f4e28fec 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -428,6 +428,13 @@ void ConnectionsDock::_make_or_edit_connection() { bool oshot = connect_dialog->get_oneshot(); cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0); + bool add_script_function = connect_dialog->get_make_callback(); + PoolStringArray script_function_args; + if (add_script_function) { + // pick up args here before "it" is deleted by update_tree + script_function_args = it->get_metadata(0).operator Dictionary()["args"]; + } + if (connect_dialog->is_editing()) { _disconnect(*it); _connect(cToMake); @@ -435,9 +442,12 @@ void ConnectionsDock::_make_or_edit_connection() { _connect(cToMake); } - if (connect_dialog->get_make_callback()) { - PoolStringArray args = it->get_metadata(0).operator Dictionary()["args"]; - editor->emit_signal("script_add_function_request", target, cToMake.method, args); + // IMPORTANT NOTE: _disconnect and _connect cause an update_tree, + // which will delete the object "it" is pointing to + it = NULL; + + if (add_script_function) { + editor->emit_signal("script_add_function_request", target, cToMake.method, script_function_args); hide(); } @@ -810,7 +820,9 @@ void ConnectionsDock::update_tree() { if (i > 0) signaldesc += ", "; String tname = "var"; - if (pi.type != Variant::NIL) { + if (pi.type == Variant::OBJECT && pi.class_name != StringName()) { + tname = pi.class_name.operator String(); + } else if (pi.type != Variant::NIL) { tname = Variant::get_type_name(pi.type); } signaldesc += tname + " " + (pi.name == "" ? String("arg " + itos(i)) : pi.name); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index a8cbf52cd2..6b2a072e20 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -243,6 +243,18 @@ void CreateDialog::_update_search() { _parse_fs(EditorFileSystem::get_singleton()->get_filesystem()); */ + List<StringName> global_classes; + ScriptServer::get_global_class_list(&global_classes); + + Map<String, List<String> > global_class_map; + for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) { + String base = ScriptServer::get_global_class_base(E->get()); + if (!global_class_map.has(base)) { + global_class_map[base] = List<String>(); + } + global_class_map[base].push_back(E->get()); + } + HashMap<String, TreeItem *> types; TreeItem *root = search_options->create_item(); @@ -293,6 +305,32 @@ void CreateDialog::_update_search() { add_type(I->get(), types, root, &to_select); } + if (global_class_map.has(type) && ClassDB::is_parent_class(type, base_type)) { + for (List<String>::Element *J = global_class_map[type].front(); J; J = J->next()) { + bool show = search_box->get_text().is_subsequence_ofi(J->get()); + + if (!show) + continue; + + if (!types.has(type)) + add_type(type, types, root, &to_select); + + TreeItem *ti; + if (types.has(type)) + ti = types[type]; + else + ti = search_options->get_root(); + + TreeItem *item = search_options->create_item(ti); + item->set_metadata(0, J->get()); + item->set_text(0, J->get() + " (" + ScriptServer::get_global_class_path(J->get()).get_file() + ")"); + item->set_icon(0, _get_editor_icon(type)); + if (!to_select || J->get() == search_box->get_text()) { + to_select = item; + } + } + } + if (EditorNode::get_editor_data().get_custom_types().has(type) && ClassDB::is_parent_class(type, base_type)) { //there are custom types based on this... cool. @@ -444,6 +482,17 @@ Object *CreateDialog::instance_selected() { custom = md; if (custom != String()) { + + if (ScriptServer::is_global_class(custom)) { + RES script = ResourceLoader::load(ScriptServer::get_global_class_path(custom)); + ERR_FAIL_COND_V(!script.is_valid(), NULL); + + Object *obj = ClassDB::instance(ScriptServer::get_global_class_base(custom)); + ERR_FAIL_COND_V(!obj, NULL); + + obj->set_script(script.get_ref_ptr()); + return obj; + } return EditorNode::get_editor_data().instance_custom_type(selected->get_text(0), custom); } else { return ClassDB::instance(selected->get_text(0)); diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 7739b08eff..317fffad64 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -409,6 +409,7 @@ void EditorExportPlatform::_edit_files_with_filter(DirAccess *da, const Vector<S String cur_dir = da->get_current_dir().replace("\\", "/"); if (!cur_dir.ends_with("/")) cur_dir += "/"; + String cur_dir_no_prefix = cur_dir.replace("res://", ""); Vector<String> dirs; String f; @@ -417,8 +418,10 @@ void EditorExportPlatform::_edit_files_with_filter(DirAccess *da, const Vector<S dirs.push_back(f); else { String fullpath = cur_dir + f; + // Test also against path without res:// so that filters like `file.txt` can work. + String fullpath_no_prefix = cur_dir_no_prefix + f; for (int i = 0; i < p_filters.size(); ++i) { - if (fullpath.matchn(p_filters[i])) { + if (fullpath.matchn(p_filters[i]) || fullpath_no_prefix.matchn(p_filters[i])) { if (!exclude) { r_list.insert(fullpath); } else { diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index d8ae1da72e..d8ab41fa05 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -125,6 +125,14 @@ bool EditorFileSystemDirectory::get_file_import_is_valid(int p_idx) const { return files[p_idx]->import_valid; } +String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const { + return files[p_idx]->script_class_name; +} + +String EditorFileSystemDirectory::get_file_script_class_extends(int p_idx) const { + return files[p_idx]->script_class_extends; +} + StringName EditorFileSystemDirectory::get_file_type(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, files.size(), ""); @@ -149,6 +157,8 @@ void EditorFileSystemDirectory::_bind_methods() { ClassDB::bind_method(D_METHOD("get_file", "idx"), &EditorFileSystemDirectory::get_file); ClassDB::bind_method(D_METHOD("get_file_path", "idx"), &EditorFileSystemDirectory::get_file_path); ClassDB::bind_method(D_METHOD("get_file_type", "idx"), &EditorFileSystemDirectory::get_file_type); + ClassDB::bind_method(D_METHOD("get_file_script_class_name", "idx"), &EditorFileSystemDirectory::get_file_script_class_name); + ClassDB::bind_method(D_METHOD("get_file_script_class_extends", "idx"), &EditorFileSystemDirectory::get_file_script_class_extends); ClassDB::bind_method(D_METHOD("get_file_import_is_valid", "idx"), &EditorFileSystemDirectory::get_file_import_is_valid); ClassDB::bind_method(D_METHOD("get_name"), &EditorFileSystemDirectory::get_name); ClassDB::bind_method(D_METHOD("get_path"), &EditorFileSystemDirectory::get_path); @@ -189,7 +199,7 @@ void EditorFileSystem::_scan_filesystem() { String project = ProjectSettings::get_singleton()->get_resource_path(); - String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache3"); + String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache4"); FileAccess *f = FileAccess::open(fscache, FileAccess::READ); if (f) { @@ -209,7 +219,7 @@ void EditorFileSystem::_scan_filesystem() { } else { Vector<String> split = l.split("::"); - ERR_CONTINUE(split.size() != 6); + ERR_CONTINUE(split.size() != 7); String name = split[0]; String file; @@ -221,8 +231,10 @@ void EditorFileSystem::_scan_filesystem() { fc.modification_time = split[2].to_int64(); fc.import_modification_time = split[3].to_int64(); fc.import_valid = split[4].to_int64() != 0; + fc.script_class_name = split[5].get_slice("<>", 0); + fc.script_class_extends = split[5].get_slice("<>", 1); - String deps = split[5].strip_edges(); + String deps = split[6].strip_edges(); if (deps.length()) { Vector<String> dp = deps.split("<>"); for (int i = 0; i < dp.size(); i++) { @@ -239,7 +251,7 @@ void EditorFileSystem::_scan_filesystem() { memdelete(f); } - String update_cache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update3"); + String update_cache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update4"); if (FileAccess::exists(update_cache)) { { @@ -287,7 +299,7 @@ void EditorFileSystem::_scan_filesystem() { } void EditorFileSystem::_save_filesystem_cache() { - String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache3"); + String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache4"); FileAccess *f = FileAccess::open(fscache, FileAccess::WRITE); if (f == NULL) { @@ -563,6 +575,7 @@ void EditorFileSystem::scan() { scanning = false; emit_signal("filesystem_changed"); emit_signal("sources_changed", sources_changed.size() > 0); + _queue_update_script_classes(); } else { @@ -706,6 +719,9 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess fi->modified_time = fc->modification_time; fi->import_modified_time = fc->import_modification_time; fi->import_valid = fc->import_valid; + fi->script_class_name = fc->script_class_name; + fi->script_class_extends = fc->script_class_extends; + if (fc->type == String()) { fi->type = ResourceLoader::get_resource_type(path); //there is also the chance that file type changed due to reimport, must probably check this somehow here (or kind of note it for next time in another file?) @@ -715,6 +731,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess } else { fi->type = ResourceFormatImporter::get_singleton()->get_resource_type(path); + fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends); fi->modified_time = 0; fi->import_modified_time = 0; fi->import_valid = ResourceLoader::is_import_valid(path); @@ -734,9 +751,12 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess fi->deps = fc->deps; fi->import_modified_time = 0; fi->import_valid = true; + fi->script_class_name = fc->script_class_name; + fi->script_class_extends = fc->script_class_extends; } else { //new or modified time fi->type = ResourceLoader::get_resource_type(path); + fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends); fi->deps = _get_dependencies(path); fi->modified_time = mt; fi->import_modified_time = 0; @@ -835,6 +855,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const fi->modified_time = FileAccess::get_modified_time(path); fi->import_modified_time = 0; fi->type = ResourceLoader::get_resource_type(path); + fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends); fi->import_valid = ResourceLoader::is_import_valid(path); { @@ -1044,6 +1065,7 @@ void EditorFileSystem::_notification(int p_what) { if (_update_scan_actions()) emit_signal("filesystem_changed"); emit_signal("sources_changed", sources_changed.size() > 0); + _queue_update_script_classes(); } } else if (!scanning) { @@ -1059,6 +1081,7 @@ void EditorFileSystem::_notification(int p_what) { _update_scan_actions(); emit_signal("filesystem_changed"); emit_signal("sources_changed", sources_changed.size() > 0); + _queue_update_script_classes(); } } } break; @@ -1087,7 +1110,7 @@ void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir, for (int i = 0; i < p_dir->files.size(); i++) { - String s = p_dir->files[i]->file + "::" + p_dir->files[i]->type + "::" + itos(p_dir->files[i]->modified_time) + "::" + itos(p_dir->files[i]->import_modified_time) + "::" + itos(p_dir->files[i]->import_valid); + String s = p_dir->files[i]->file + "::" + p_dir->files[i]->type + "::" + itos(p_dir->files[i]->modified_time) + "::" + itos(p_dir->files[i]->import_modified_time) + "::" + itos(p_dir->files[i]->import_valid) + "::" + p_dir->files[i]->script_class_name + "<>" + p_dir->files[i]->script_class_extends; s += "::"; for (int j = 0; j < p_dir->files[i]->deps.size(); j++) { @@ -1268,7 +1291,7 @@ EditorFileSystemDirectory *EditorFileSystem::get_filesystem_path(const String &p void EditorFileSystem::_save_late_updated_files() { //files that already existed, and were modified, need re-scanning for dependencies upon project restart. This is done via saving this special file - String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update3"); + String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update4"); FileAccessRef f = FileAccess::open(fscache, FileAccess::WRITE); for (Set<String>::Element *E = late_update_files.front(); E; E = E->next()) { f->store_line(E->get()); @@ -1293,6 +1316,67 @@ Vector<String> EditorFileSystem::_get_dependencies(const String &p_path) { return ret; } +String EditorFileSystem::_get_global_script_class(const String &p_type, const String &p_path, String *r_extends) const { + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) { + String global_name; + String extends; + + global_name = ScriptServer::get_language(i)->get_global_class_name(p_path, &extends); + *r_extends = extends; + return global_name; + } + } + *r_extends = String(); + return String(); +} + +void EditorFileSystem::_scan_script_classes(EditorFileSystemDirectory *p_dir) { + int filecount = p_dir->files.size(); + const EditorFileSystemDirectory::FileInfo *const *files = p_dir->files.ptr(); + for (int i = 0; i < filecount; i++) { + if (files[i]->script_class_name == String()) { + continue; + } + + String lang; + for (int j = 0; j < ScriptServer::get_language_count(); j++) { + if (ScriptServer::get_language(j)->handles_global_class_type(files[i]->type)) { + lang = ScriptServer::get_language(j)->get_name(); + } + } + + ScriptServer::add_global_class(files[i]->script_class_name, files[i]->script_class_extends, lang, p_dir->get_file_path(i)); + } + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _scan_script_classes(p_dir->get_subdir(i)); + } +} + +void EditorFileSystem::update_script_classes() { + + if (!update_script_classes_queued) + return; + + update_script_classes_queued = false; + ScriptServer::global_classes_clear(); + if (get_filesystem()) { + _scan_script_classes(get_filesystem()); + } + + ScriptServer::save_global_classes(); +} + +void EditorFileSystem::_queue_update_script_classes() { + if (update_script_classes_queued) { + return; + } + + update_script_classes_queued = true; + call_deferred("update_script_classes"); +} + void EditorFileSystem::update_file(const String &p_file) { EditorFileSystemDirectory *fs = NULL; @@ -1311,7 +1395,9 @@ void EditorFileSystem::update_file(const String &p_file) { memdelete(fs->files[cpos]); fs->files.remove(cpos); } + call_deferred("emit_signal", "filesystem_changed"); //update later + _queue_update_script_classes(); return; } @@ -1351,6 +1437,7 @@ void EditorFileSystem::update_file(const String &p_file) { } fs->files[cpos]->type = type; + fs->files[cpos]->script_class_name = _get_global_script_class(type, p_file, &fs->files[cpos]->script_class_extends); fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); fs->files[cpos]->deps = _get_dependencies(p_file); fs->files[cpos]->import_valid = ResourceLoader::is_import_valid(p_file); @@ -1359,6 +1446,7 @@ void EditorFileSystem::update_file(const String &p_file) { EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); call_deferred("emit_signal", "filesystem_changed"); //update later + _queue_update_script_classes(); } void EditorFileSystem::_reimport_file(const String &p_file) { @@ -1611,6 +1699,7 @@ void EditorFileSystem::_bind_methods() { ClassDB::bind_method(D_METHOD("update_file", "path"), &EditorFileSystem::update_file); ClassDB::bind_method(D_METHOD("get_filesystem_path", "path"), &EditorFileSystem::get_filesystem_path); ClassDB::bind_method(D_METHOD("get_file_type", "path"), &EditorFileSystem::get_file_type); + ClassDB::bind_method(D_METHOD("update_script_classes"), &EditorFileSystem::update_script_classes); ADD_SIGNAL(MethodInfo("filesystem_changed")); ADD_SIGNAL(MethodInfo("sources_changed", PropertyInfo(Variant::BOOL, "exist"))); @@ -1664,6 +1753,7 @@ EditorFileSystem::EditorFileSystem() { memdelete(da); scan_total = 0; + update_script_classes_queued = false; } EditorFileSystem::~EditorFileSystem() { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index a587d2879a..1aa35f4782 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -58,6 +58,8 @@ class EditorFileSystemDirectory : public Object { bool import_valid; Vector<String> deps; bool verified; //used for checking changes + String script_class_name; + String script_class_extends; }; struct FileInfoSort { @@ -86,6 +88,8 @@ public: StringName get_file_type(int p_idx) const; Vector<String> get_file_deps(int p_idx) const; bool get_file_import_is_valid(int p_idx) const; + String get_file_script_class_name(int p_idx) const; //used for scripts + String get_file_script_class_extends(int p_idx) const; //used for scripts EditorFileSystemDirectory *get_parent(); @@ -157,6 +161,8 @@ class EditorFileSystem : public Node { uint64_t import_modification_time; Vector<String> deps; bool import_valid; + String script_class_name; + String script_class_extends; }; HashMap<String, FileCache> file_cache; @@ -215,6 +221,12 @@ class EditorFileSystem : public Node { } }; + void _scan_script_classes(EditorFileSystemDirectory *p_dir); + volatile bool update_script_classes_queued; + void _queue_update_script_classes(); + + String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends) const; + protected: void _notification(int p_what); static void _bind_methods(); @@ -237,6 +249,8 @@ public: void reimport_files(const Vector<String> &p_files); + void update_script_classes(); + EditorFileSystem(); ~EditorFileSystem(); }; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 65e50560bc..727383b960 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1900,6 +1900,7 @@ void EditorHelpBit::_meta_clicked(String p_select) { void EditorHelpBit::_bind_methods() { ClassDB::bind_method("_meta_clicked", &EditorHelpBit::_meta_clicked); + ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text); ADD_SIGNAL(MethodInfo("request_hide")); } @@ -1925,7 +1926,7 @@ EditorHelpBit::EditorHelpBit() { rich_text = memnew(RichTextLabel); add_child(rich_text); - rich_text->set_anchors_and_margins_preset(Control::PRESET_WIDE); + //rich_text->set_anchors_and_margins_preset(Control::PRESET_WIDE); rich_text->connect("meta_clicked", this, "_meta_clicked"); rich_text->add_color_override("selection_color", get_color("text_editor/theme/selection_color", "Editor")); rich_text->set_override_selected_font_color(false); diff --git a/editor/editor_help.h b/editor/editor_help.h index 514169dc19..dbea97e98b 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -272,9 +272,9 @@ public: ~EditorHelp(); }; -class EditorHelpBit : public Panel { +class EditorHelpBit : public PanelContainer { - GDCLASS(EditorHelpBit, Panel); + GDCLASS(EditorHelpBit, PanelContainer); RichTextLabel *rich_text; void _go_to_help(String p_what); @@ -285,6 +285,7 @@ protected: void _notification(int p_what); public: + RichTextLabel *get_rich_text() { return rich_text; } void set_text(const String &p_text); EditorHelpBit(); }; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index d8ce2bc024..488980db07 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -91,8 +91,11 @@ void EditorProperty::_notification(int p_what) { Rect2 rect; Rect2 bottom_rect; + right_child_rect = Rect2(); + bottom_child_rect = Rect2(); + { - int child_room = size.width / 2; + int child_room = size.width * (1.0 - split_ratio); Ref<Font> font = get_font("font", "Tree"); int height = font->get_height(); @@ -118,7 +121,8 @@ void EditorProperty::_notification(int p_what) { if (bottom_editor) { - int m = get_constant("item_margin", "Tree"); + int m = 0; //get_constant("item_margin", "Tree"); + bottom_rect = Rect2(m, rect.size.height + get_constant("vseparation", "Tree"), size.width - m, bottom_editor->get_combined_minimum_size().height); } } @@ -147,10 +151,12 @@ void EditorProperty::_notification(int p_what) { continue; fit_child_in_rect(c, rect); + right_child_rect = rect; } if (bottom_editor) { fit_child_in_rect(bottom_editor, bottom_rect); + bottom_child_rect = bottom_rect; } update(); //need to redraw text @@ -158,6 +164,7 @@ void EditorProperty::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { Ref<Font> font = get_font("font", "Tree"); + Color dark_color = get_color("dark_color_2", "Editor"); Size2 size = get_size(); if (bottom_editor) { @@ -171,6 +178,13 @@ void EditorProperty::_notification(int p_what) { draw_style_box(sb, Rect2(Vector2(), size)); } + if (right_child_rect != Rect2()) { + draw_rect(right_child_rect, dark_color); + } + if (bottom_child_rect != Rect2()) { + draw_rect(bottom_child_rect, dark_color); + } + Color color; if (draw_red) { color = get_color("error_color", "Editor"); @@ -251,7 +265,7 @@ void EditorProperty::_notification(int p_what) { //int vs = get_constant("vseparation", "Tree"); Color guide_color = get_color("guide_color", "Tree"); int vs_height = get_size().height; // vs / 2; - draw_line(Point2(0, vs_height), Point2(get_size().width, vs_height), guide_color); + // draw_line(Point2(0, vs_height), Point2(get_size().width, vs_height), guide_color); } } @@ -691,11 +705,38 @@ bool EditorProperty::is_selectable() const { return selectable; } +void EditorProperty::set_name_split_ratio(float p_ratio) { + split_ratio = p_ratio; +} + +float EditorProperty::get_name_split_ratio() const { + + return split_ratio; +} + void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) { object = p_object; property = p_property; } +Control *EditorProperty::make_custom_tooltip(const String &p_text) const { + + tooltip_text = p_text; + EditorHelpBit *help_bit = memnew(EditorHelpBit); + help_bit->add_style_override("panel", get_stylebox("panel", "TooltipPanel")); + help_bit->get_rich_text()->set_fixed_size_to_width(300); + + String text = TTR("Property: ") + "[u][b]" + p_text.get_slice("::", 0) + "[/b][/u]\n"; + text += p_text.get_slice("::", 1).strip_edges(); + help_bit->set_text(text); + help_bit->call_deferred("set_text", text); //hack so it uses proper theme once inside scene + return help_bit; +} + +String EditorProperty::get_tooltip_text() const { + return tooltip_text; +} + void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("set_label", "text"), &EditorProperty::set_label); @@ -722,6 +763,8 @@ void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &EditorProperty::_gui_input); ClassDB::bind_method(D_METHOD("_focusable_focused"), &EditorProperty::_focusable_focused); + ClassDB::bind_method(D_METHOD("get_tooltip_text"), &EditorProperty::get_tooltip_text); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable"), "set_checkable", "is_checkable"); @@ -744,6 +787,7 @@ void EditorProperty::_bind_methods() { EditorProperty::EditorProperty() { + split_ratio = 0.5; selectable = true; text_size = 0; read_only = false; @@ -895,6 +939,20 @@ void EditorInspectorCategory::_notification(int p_what) { } } +Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const { + + tooltip_text = p_text; + EditorHelpBit *help_bit = memnew(EditorHelpBit); + help_bit->add_style_override("panel", get_stylebox("panel", "TooltipPanel")); + help_bit->get_rich_text()->set_fixed_size_to_width(300); + + String text = "[u][b]" + p_text.get_slice("::", 0) + "[/b][/u]\n"; + text += p_text.get_slice("::", 1).strip_edges(); + help_bit->set_text(text); + help_bit->call_deferred("set_text", text); //hack so it uses proper theme once inside scene + return help_bit; +} + Size2 EditorInspectorCategory::get_minimum_size() const { Ref<Font> font = get_font("font", "Tree"); @@ -910,12 +968,29 @@ Size2 EditorInspectorCategory::get_minimum_size() const { return ms; } +void EditorInspectorCategory::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_tooltip_text"), &EditorInspectorCategory::get_tooltip_text); +} + +String EditorInspectorCategory::get_tooltip_text() const { + + return tooltip_text; +} + EditorInspectorCategory::EditorInspectorCategory() { } //////////////////////////////////////////////// //////////////////////////////////////////////// +void EditorInspectorSection::_test_unfold() { + + if (!vbox_added) { + add_child(vbox); + vbox_added = true; + } +} + void EditorInspectorSection::_notification(int p_what) { if (p_what == NOTIFICATION_SORT_CHILDREN) { @@ -926,9 +1001,9 @@ void EditorInspectorSection::_notification(int p_what) { #ifdef TOOLS_ENABLED if (foldable) { if (object->editor_is_section_unfolded(section)) { - arrow = get_icon("arrow", "Tree"); + arrow = get_icon("arrow_up", "Tree"); } else { - arrow = get_icon("arrow_collapsed", "Tree"); + arrow = get_icon("arrow", "Tree"); } } #endif @@ -941,7 +1016,7 @@ void EditorInspectorSection::_notification(int p_what) { } offset.y += get_constant("vseparation", "Tree"); - offset.x += get_constant("item_margin", "Tree"); + offset.x += get_constant("inspector_margin", "Editor"); Rect2 rect(offset, size - offset); @@ -969,9 +1044,9 @@ void EditorInspectorSection::_notification(int p_what) { #ifdef TOOLS_ENABLED if (foldable) { if (object->editor_is_section_unfolded(section)) { - arrow = get_icon("arrow", "Tree"); + arrow = get_icon("arrow_up", "Tree"); } else { - arrow = get_icon("arrow_collapsed", "Tree"); + arrow = get_icon("arrow", "Tree"); } } #endif @@ -988,14 +1063,14 @@ void EditorInspectorSection::_notification(int p_what) { int hs = get_constant("hseparation", "Tree"); + Color color = get_color("font_color", "Tree"); + draw_string(font, Point2(hs, font->get_ascent() + (h - font->get_height()) / 2).floor(), label, color, get_size().width); + int ofs = 0; if (arrow.is_valid()) { - draw_texture(arrow, Point2(ofs, (h - arrow->get_height()) / 2).floor()); + draw_texture(arrow, Point2(get_size().width - arrow->get_width(), (h - arrow->get_height()) / 2).floor()); ofs += hs + arrow->get_width(); } - - Color color = get_color("font_color", "Tree"); - draw_string(font, Point2(ofs, font->get_ascent() + (h - font->get_height()) / 2).floor(), label, color, get_size().width); } } @@ -1017,8 +1092,8 @@ Size2 EditorInspectorSection::get_minimum_size() const { } Ref<Font> font = get_font("font", "Tree"); - ms.height += font->get_ascent() + get_constant("vseparation", "Tree"); - ms.width += get_constant("item_margin", "Tree"); + ms.height += font->get_height() + get_constant("vseparation", "Tree"); + ms.width += get_constant("inspector_margin", "Editor"); return ms; } @@ -1031,16 +1106,20 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe bg_color = p_bg_color; foldable = p_foldable; + if (!foldable && !vbox_added) { + add_child(vbox); + vbox_added = true; + } + #ifdef TOOLS_ENABLED if (foldable) { + _test_unfold(); if (object->editor_is_section_unfolded(section)) { vbox->show(); } else { vbox->hide(); } } - // void editor_set_section_unfold(const String &p_section, bool p_unfolded); - #endif } @@ -1053,6 +1132,9 @@ void EditorInspectorSection::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + _test_unfold(); + bool unfold = !object->editor_is_section_unfolded(section); object->editor_set_section_unfold(section, unfold); if (unfold) { @@ -1072,6 +1154,9 @@ void EditorInspectorSection::unfold() { if (!foldable) return; + + _test_unfold(); + #ifdef TOOLS_ENABLED object->editor_set_section_unfold(section, true); @@ -1084,6 +1169,8 @@ void EditorInspectorSection::fold() { if (!foldable) return; + if (!vbox_added) + return; //kinda pointless #ifdef TOOLS_ENABLED object->editor_set_section_unfold(section, false); @@ -1105,7 +1192,14 @@ EditorInspectorSection::EditorInspectorSection() { object = NULL; foldable = false; vbox = memnew(VBoxContainer); - add_child(vbox); + vbox_added = false; + //add_child(vbox); +} + +EditorInspectorSection::~EditorInspectorSection() { + if (!vbox_added) { + memdelete(vbox); + } } //////////////////////////////////////////////// @@ -1114,6 +1208,30 @@ EditorInspectorSection::EditorInspectorSection() { Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; int EditorInspector::inspector_plugin_count = 0; +EditorProperty *EditorInspector::instantiate_property_editor(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) { + + for (int i = inspector_plugin_count - 1; i >= 0; i--) { + + inspector_plugins[i]->parse_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage); + if (inspector_plugins[i]->added_editors.size()) { + for (int j = 1; j < inspector_plugins[i]->added_editors.size(); j++) { //only keep first one + memdelete(inspector_plugins[i]->added_editors[j].property_editor); + } + + EditorProperty *prop = Object::cast_to<EditorProperty>(inspector_plugins[i]->added_editors[0].property_editor); + if (prop) { + + inspector_plugins[i]->added_editors.clear(); + return prop; + } else { + memdelete(inspector_plugins[i]->added_editors[0].property_editor); + inspector_plugins[i]->added_editors.clear(); + } + } + } + return NULL; +} + void EditorInspector::add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) { ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS); @@ -1242,8 +1360,10 @@ void EditorInspector::update_tree() { String filter = search_box ? search_box->get_text() : ""; String group; String group_base; + VBoxContainer *category_vbox = NULL; - List<PropertyInfo> plist; + List<PropertyInfo> + plist; object->get_property_list(&plist, true); HashMap<String, VBoxContainer *> item_path; @@ -1295,6 +1415,7 @@ void EditorInspector::update_tree() { EditorInspectorCategory *category = memnew(EditorInspectorCategory); main_vbox->add_child(category); + category_vbox = NULL; //reset String type = p.name; if (has_icon(type, "EditorIcons")) @@ -1317,7 +1438,7 @@ void EditorInspector::update_tree() { class_descr_cache[type] = descr.word_wrap(80); } - category->set_tooltip(TTR("Class:") + " " + p.name + (class_descr_cache[type] == "" ? "" : "\n\n" + class_descr_cache[type])); + category->set_tooltip(p.name + "::" + (class_descr_cache[type] == "" ? "" : class_descr_cache[type])); } for (List<Ref<EditorInspectorPlugin> >::Element *E = valid_plugins.front(); E; E = E->next()) { @@ -1380,6 +1501,11 @@ void EditorInspector::update_tree() { continue; } + if (category_vbox == NULL) { + category_vbox = memnew(VBoxContainer); + main_vbox->add_child(category_vbox); + } + VBoxContainer *current_vbox = main_vbox; { @@ -1407,6 +1533,14 @@ void EditorInspector::update_tree() { current_vbox = item_path[acc_path]; level = (MIN(level + 1, 4)); } + + if (current_vbox == main_vbox) { + //do not add directly to the main vbox, given it has no spacing + if (category_vbox == NULL) { + category_vbox = memnew(VBoxContainer); + } + current_vbox = category_vbox; + } } bool checkable = false; @@ -1416,12 +1550,19 @@ void EditorInspector::update_tree() { checked = p.usage & PROPERTY_USAGE_CHECKED; } + if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) { + restart_request_props.insert(p.name); + } + String doc_hint; if (use_doc_hints) { StringName classname = object->get_class_name(); - StringName propname = p.name; + if (object_class != String()) { + classname = object_class; + } + StringName propname = property_prefix + p.name; String descr; bool found = false; @@ -1489,9 +1630,9 @@ void EditorInspector::update_tree() { ep->connect("resource_selected", this, "_resource_selected", varray(), CONNECT_DEFERRED); ep->connect("object_id_selected", this, "_object_id_selected", varray(), CONNECT_DEFERRED); if (doc_hint != String()) { - ep->set_tooltip(TTR("Property: ") + p.name + "\n\n" + doc_hint); + ep->set_tooltip(property_prefix + p.name + "::" + doc_hint); } else { - ep->set_tooltip(TTR("Property: ") + p.name); + ep->set_tooltip(property_prefix + p.name); } ep->set_draw_red(draw_red); ep->set_use_folding(use_folding); @@ -1568,6 +1709,7 @@ void EditorInspector::_clear() { editor_property_map.clear(); sections.clear(); pending.clear(); + restart_request_props.clear(); } void EditorInspector::refresh() { @@ -1593,6 +1735,10 @@ void EditorInspector::edit(Object *p_object) { object = p_object; if (object) { + update_scroll_request = 0; //reset + if (scroll_cache.has(object->get_instance_id())) { //if exists, set something else + update_scroll_request = scroll_cache[object->get_instance_id()]; //done this way because wait until full size is accomodated + } object->add_change_receptor(this); update_tree(); } @@ -1698,6 +1844,19 @@ int EditorInspector::get_scroll_offset() const { return get_v_scroll(); } +void EditorInspector::set_use_sub_inspector_bg(bool p_enable) { + + use_sub_inspector_bg = p_enable; + if (!is_inside_tree()) + return; + + if (use_sub_inspector_bg) { + add_style_override("bg", get_stylebox("sub_inspector_bg", "Editor")); + } else { + add_style_override("bg", get_stylebox("bg", "Tree")); + } +} + void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) { if (object != p_object) //may be undoing/redoing for a non edited object, so ignore @@ -1794,6 +1953,10 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v if (changing) this->changing--; + + if (restart_request_props.has(p_path)) { + emit_signal("restart_requested"); + } } void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value) { @@ -1813,6 +1976,9 @@ void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array undo_redo->create_action(TTR("Set Multiple:") + " " + names, UndoRedo::MERGE_ENDS); for (int i = 0; i < p_paths.size(); i++) { _edit_set(p_paths[i], p_values[i], false, ""); + if (restart_request_props.has(p_paths[i])) { + emit_signal("restart_requested"); + } } changing++; undo_redo->commit_action(); @@ -1885,6 +2051,8 @@ void EditorInspector::_property_selected(const String &p_path, int p_focusable) E->get()->deselect(); } } + + emit_signal("property_selected", p_path); } void EditorInspector::_object_id_selected(const String &p_path, ObjectID p_id) { @@ -1908,7 +2076,11 @@ void EditorInspector::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { get_tree()->connect("node_removed", this, "_node_removed"); - add_style_override("bg", get_stylebox("bg", "Tree")); + if (use_sub_inspector_bg) { + add_style_override("bg", get_stylebox("sub_inspector_bg", "Editor")); + } else if (is_inside_tree()) { + add_style_override("bg", get_stylebox("bg", "Tree")); + } } if (p_what == NOTIFICATION_EXIT_TREE) { @@ -1918,6 +2090,10 @@ void EditorInspector::_notification(int p_what) { if (p_what == NOTIFICATION_PROCESS) { + if (update_scroll_request >= 0) { + get_v_scrollbar()->call_deferred("set_value", update_scroll_request); + update_scroll_request = -1; + } if (refresh_countdown > 0) { refresh_countdown -= get_process_delta_time(); if (refresh_countdown <= 0) { @@ -1965,6 +2141,32 @@ void EditorInspector::_changed_callback(Object *p_changed, const char *p_prop) { _edit_request_change(p_changed, p_prop); } +void EditorInspector::_vscroll_changed(double p_offset) { + + if (update_scroll_request >= 0) //waiting, do nothing + return; + + if (object) { + scroll_cache[object->get_instance_id()] = p_offset; + } +} + +void EditorInspector::set_property_prefix(const String &p_prefix) { + property_prefix = p_prefix; +} + +String EditorInspector::get_property_prefix() const { + return property_prefix; +} + +void EditorInspector::set_object_class(const String &p_class) { + object_class = p_class; +} + +String EditorInspector::get_object_class() const { + return object_class; +} + void EditorInspector::_bind_methods() { ClassDB::bind_method("_property_changed", &EditorInspector::_property_changed, DEFVAL(false)); @@ -1980,11 +2182,16 @@ void EditorInspector::_bind_methods() { ClassDB::bind_method("_property_selected", &EditorInspector::_property_selected); ClassDB::bind_method("_resource_selected", &EditorInspector::_resource_selected); ClassDB::bind_method("_object_id_selected", &EditorInspector::_object_id_selected); + ClassDB::bind_method("_vscroll_changed", &EditorInspector::_vscroll_changed); + ClassDB::bind_method("refresh", &EditorInspector::refresh); + ADD_SIGNAL(MethodInfo("property_selected", PropertyInfo(Variant::STRING, "property"))); ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"))); ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "res"), PropertyInfo(Variant::STRING, "prop"))); ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("property_edited", PropertyInfo(Variant::STRING, "property"))); + ADD_SIGNAL(MethodInfo("restart_requested")); } EditorInspector::EditorInspector() { @@ -1992,6 +2199,7 @@ EditorInspector::EditorInspector() { undo_redo = NULL; main_vbox = memnew(VBoxContainer); main_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + main_vbox->add_constant_override("separation", 0); add_child(main_vbox); set_enable_h_scroll(false); set_enable_v_scroll(true); @@ -2013,4 +2221,8 @@ EditorInspector::EditorInspector() { _prop_edited = "property_edited"; set_process(true); property_focusable = -1; + use_sub_inspector_bg = false; + + get_v_scrollbar()->connect("value_changed", this, "_vscroll_changed"); + update_scroll_request = -1; } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 383cb458ec..454622d662 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -55,6 +55,9 @@ private: bool draw_red; bool keying; + Rect2 right_child_rect; + Rect2 bottom_child_rect; + Rect2 keying_rect; bool keying_hover; Rect2 revert_rect; @@ -76,10 +79,14 @@ private: bool selected; int selected_focusable; + float split_ratio; + Vector<Control *> focusables; Control *label_reference; Control *bottom_editor; + mutable String tooltip_text; + protected: void _notification(int p_what); static void _bind_methods(); @@ -134,7 +141,14 @@ public: void set_selectable(bool p_selectable); bool is_selectable() const; + void set_name_split_ratio(float p_ratio); + float get_name_split_ratio() const; + void set_object_and_property(Object *p_object, const StringName &p_property); + virtual Control *make_custom_tooltip(const String &p_text) const; + + String get_tooltip_text() const; + EditorProperty(); }; @@ -172,12 +186,17 @@ class EditorInspectorCategory : public Control { Ref<Texture> icon; String label; Color bg_color; + mutable String tooltip_text; protected: void _notification(int p_what); + static void _bind_methods(); public: virtual Size2 get_minimum_size() const; + virtual Control *make_custom_tooltip(const String &p_text) const; + + String get_tooltip_text() const; EditorInspectorCategory(); }; @@ -189,9 +208,12 @@ class EditorInspectorSection : public Container { String section; Object *object; VBoxContainer *vbox; + bool vbox_added; //optimization Color bg_color; bool foldable; + void _test_unfold(); + protected: void _notification(int p_what); static void _bind_methods(); @@ -208,6 +230,7 @@ public: Object *get_edited_object(); EditorInspectorSection(); + ~EditorInspectorSection(); }; class EditorInspector : public ScrollContainer { @@ -244,15 +267,23 @@ class EditorInspector : public ScrollContainer { bool update_all_pending; bool read_only; bool keying; + bool use_sub_inspector_bg; float refresh_countdown; bool update_tree_pending; StringName _prop_edited; StringName property_selected; int property_focusable; + int update_scroll_request; Map<StringName, Map<StringName, String> > descr_cache; Map<StringName, String> class_descr_cache; + Set<StringName> restart_request_props; + + Map<ObjectID, int> scroll_cache; + + String property_prefix; //used for sectioned inspector + String object_class; void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); @@ -276,6 +307,8 @@ class EditorInspector : public ScrollContainer { void _filter_changed(const String &p_text); void _parse_added_editors(VBoxContainer *current_vbox, Ref<EditorInspectorPlugin> ped); + void _vscroll_changed(double); + protected: static void _bind_methods(); void _notification(int p_what); @@ -285,6 +318,8 @@ public: static void remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); static void cleanup_plugins(); + static EditorProperty *instantiate_property_editor(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage); + void set_undo_redo(UndoRedo *p_undo_redo); String get_selected_path() const; @@ -323,6 +358,14 @@ public: void set_scroll_offset(int p_offset); int get_scroll_offset() const; + void set_property_prefix(const String &p_prefix); + String get_property_prefix() const; + + void set_object_class(const String &p_class); + String get_object_class() const; + + void set_use_sub_inspector_bg(bool p_enable); + EditorInspector(); }; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8d039f8cc0..b366ebd911 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -73,6 +73,7 @@ #include "editor/plugins/animation_state_machine_editor.h" #include "editor/plugins/animation_tree_editor_plugin.h" #include "editor/plugins/asset_library_editor_plugin.h" +#include "editor/plugins/audio_stream_editor_plugin.h" #include "editor/plugins/baked_lightmap_editor_plugin.h" #include "editor/plugins/camera_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" @@ -111,11 +112,13 @@ #include "editor/plugins/sprite_editor_plugin.h" #include "editor/plugins/sprite_frames_editor_plugin.h" #include "editor/plugins/style_box_editor_plugin.h" +#include "editor/plugins/text_editor.h" #include "editor/plugins/texture_editor_plugin.h" #include "editor/plugins/texture_region_editor_plugin.h" #include "editor/plugins/theme_editor_plugin.h" #include "editor/plugins/tile_map_editor_plugin.h" #include "editor/plugins/tile_set_editor_plugin.h" +#include "editor/plugins/visual_shader_editor_plugin.h" #include "editor/pvrtc_compress.h" #include "editor/register_exporters.h" #include "editor/script_editor_debugger.h" @@ -154,7 +157,6 @@ void EditorNode::_update_scene_tabs() { scene_tabs->set_current_tab(editor_data.get_edited_scene()); - int current = editor_data.get_edited_scene(); if (scene_tabs->get_offset_buttons_visible()) { // move add button to fixed position on the tabbar if (scene_tab_add->get_parent() == scene_tabs) { @@ -1066,6 +1068,32 @@ void EditorNode::_save_scene(String p_file, int idx) { } } +void EditorNode::save_all_scenes_and_restart() { + + _menu_option_confirm(RUN_STOP, true); + exiting = true; + + _save_all_scenes(); + + String to_reopen; + if (get_tree()->get_edited_scene_root()) { + to_reopen = get_tree()->get_edited_scene_root()->get_filename(); + } + + get_tree()->quit(); + String exec = OS::get_singleton()->get_executable_path(); + + List<String> args; + args.push_back("--path"); + args.push_back(ProjectSettings::get_singleton()->get_resource_path()); + args.push_back("-e"); + if (to_reopen != String()) { + args.push_back(to_reopen); + } + + OS::get_singleton()->set_restart_on_exit(true, args); +} + void EditorNode::_save_all_scenes() { for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { @@ -1854,10 +1882,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; - case SETTINGS_EXPORT_PREFERENCES: { - - //project_export_settings->popup_centered_ratio(); - } break; case FILE_IMPORT_SUBSCENE: { if (!editor_data.get_edited_scene_root()) { @@ -2206,6 +2230,13 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { about->popup_centered_minsize(Size2(780, 500) * EDSCALE); } break; + case SET_VIDEO_DRIVER_SAVE_AND_RESTART: { + + ProjectSettings::get_singleton()->set("rendering/quality/driver/driver_name", video_driver_request); + ProjectSettings::get_singleton()->save(); + + save_all_scenes_and_restart(); + } break; default: { if (p_option >= IMPORT_PLUGIN_BASE) { } @@ -3860,7 +3891,7 @@ ToolButton *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) { tb->set_focus_mode(Control::FOCUS_NONE); bottom_panel_vb->add_child(p_item); bottom_panel_hb->raise(); - bottom_panel_hb->add_child(tb); + bottom_panel_hb_editors->add_child(tb); p_item->set_v_size_flags(Control::SIZE_EXPAND_FILL); p_item->hide(); BottomPanelItem bpi; @@ -3924,7 +3955,7 @@ void EditorNode::remove_bottom_panel_item(Control *p_item) { _bottom_panel_switch(false, 0); } bottom_panel_vb->remove_child(bottom_panel_items[i].control); - bottom_panel_hb->remove_child(bottom_panel_items[i].button); + bottom_panel_hb_editors->remove_child(bottom_panel_items[i].button); memdelete(bottom_panel_items[i].button); bottom_panel_items.remove(i); break; @@ -3954,6 +3985,11 @@ void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) { } center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE); center_split->set_collapsed(false); + if (bottom_panel_raise->is_pressed()) { + top_split->hide(); + } + bottom_panel_raise->show(); + } else { bottom_panel->add_style_override("panel", gui_base->get_stylebox("panel", "TabContainer")); for (int i = 0; i < bottom_panel_items.size(); i++) { @@ -3963,6 +3999,10 @@ void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) { } center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN); center_split->set_collapsed(true); + bottom_panel_raise->hide(); + if (bottom_panel_raise->is_pressed()) { + top_split->show(); + } } } @@ -4179,7 +4219,7 @@ void EditorNode::_dropped_files(const Vector<String> &p_files, int p_screen) { for (int i = 0; i < p_files.size(); i++) { String from = p_files[i]; - if (!ResourceFormatImporter::get_singleton()->can_be_imported(from) && (just_copy.find(from.get_extension().to_lower()) != -1)) { + if (!ResourceFormatImporter::get_singleton()->can_be_imported(from) && (just_copy.find(from.get_extension().to_lower()) == -1)) { continue; } String to = to_path.plus_file(from.get_file()); @@ -4372,6 +4412,32 @@ Vector<Ref<EditorResourceConversionPlugin> > EditorNode::find_resource_conversio return ret; } +void EditorNode::_bottom_panel_raise_toggled(bool p_pressed) { + + if (p_pressed) { + top_split->hide(); + bottom_panel_raise->set_icon(gui_base->get_icon("ShrinkBottomDock", "EditorIcons")); + } else { + top_split->show(); + bottom_panel_raise->set_icon(gui_base->get_icon("ExpandBottomDock", "EditorIcons")); + } +} + +void EditorNode::_video_driver_selected(int p_which) { + + String driver = video_driver->get_item_metadata(p_which); + + String current = OS::get_singleton()->get_video_driver_name(OS::get_singleton()->get_current_video_driver()); + + if (driver == current) { + return; + } + + video_driver_request = driver; + video_restart_dialog->popup_centered_minsize(); + video_driver->select(video_driver_current); +} + void EditorNode::_bind_methods() { ClassDB::bind_method("_menu_option", &EditorNode::_menu_option); @@ -4440,6 +4506,9 @@ void EditorNode::_bind_methods() { ClassDB::bind_method(D_METHOD("_dim_timeout"), &EditorNode::_dim_timeout); ClassDB::bind_method(D_METHOD("_resources_reimported"), &EditorNode::_resources_reimported); + ClassDB::bind_method(D_METHOD("_bottom_panel_raise_toggled"), &EditorNode::_bottom_panel_raise_toggled); + + ClassDB::bind_method(D_METHOD("_video_driver_selected"), &EditorNode::_video_driver_selected); ADD_SIGNAL(MethodInfo("play_pressed")); ADD_SIGNAL(MethodInfo("pause_pressed")); @@ -4605,6 +4674,10 @@ EditorNode::EditorNode() { Ref<EditorInspectorRootMotionPlugin> rmp; rmp.instance(); EditorInspector::add_inspector_plugin(rmp); + + Ref<EditorInspectorShaderModePlugin> smp; + smp.instance(); + EditorInspector::add_inspector_plugin(smp); } _pvrtc_register_compressors(); @@ -4633,20 +4706,21 @@ EditorNode::EditorNode() { ClassDB::set_class_enabled("RootMotionView", true); //defs here, use EDITOR_GET in logic - EDITOR_DEF("interface/scene_tabs/always_show_close_button", false); - EDITOR_DEF("interface/scene_tabs/resize_if_many_tabs", true); - EDITOR_DEF("interface/scene_tabs/minimum_width", 50); + EDITOR_DEF_RST("interface/scene_tabs/always_show_close_button", false); + EDITOR_DEF_RST("interface/scene_tabs/resize_if_many_tabs", true); + EDITOR_DEF_RST("interface/scene_tabs/minimum_width", 50); EDITOR_DEF("run/output/always_clear_output_on_play", true); EDITOR_DEF("run/output/always_open_output_on_play", true); EDITOR_DEF("run/output/always_close_output_on_stop", true); EDITOR_DEF("run/auto_save/save_before_running", true); - EDITOR_DEF("interface/editor/save_each_scene_on_quit", true); + EDITOR_DEF_RST("interface/editor/save_each_scene_on_quit", true); EDITOR_DEF("interface/editor/quit_confirmation", true); - EDITOR_DEF("interface/scene_tabs/restore_scenes_on_load", false); - EDITOR_DEF("interface/scene_tabs/show_thumbnail_on_hover", true); - EDITOR_DEF("interface/inspector/capitalize_properties", true); - EDITOR_DEF("interface/inspector/disable_folding", false); - EDITOR_DEF("interface/inspector/open_resources_in_new_inspector", false); + EDITOR_DEF_RST("interface/scene_tabs/restore_scenes_on_load", false); + EDITOR_DEF_RST("interface/scene_tabs/show_thumbnail_on_hover", true); + EDITOR_DEF_RST("interface/inspector/capitalize_properties", true); + EDITOR_DEF_RST("interface/inspector/disable_folding", false); + EDITOR_DEF("interface/inspector/open_resources_in_current_inspector", true); + EDITOR_DEF("interface/inspector/resources_types_to_open_in_new_inspector", "SpatialMaterial"); EDITOR_DEF("run/auto_save/save_before_running", true); theme_base = memnew(Control); @@ -5167,6 +5241,37 @@ EditorNode::EditorNode() { play_custom_scene_button->set_shortcut(ED_SHORTCUT("editor/play_custom_scene", TTR("Play Custom Scene"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F5)); #endif + video_driver = memnew(OptionButton); + video_driver->set_flat(true); + video_driver->set_focus_mode(Control::FOCUS_NONE); + video_driver->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + String video_drivers = ProjectSettings::get_singleton()->get_custom_property_info()["rendering/quality/driver/driver_name"].hint_string; + String current_video_driver = OS::get_singleton()->get_video_driver_name(OS::get_singleton()->get_current_video_driver()); + menu_hb->add_child(video_driver); + video_driver_current = 0; + for (int i = 0; i < video_drivers.get_slice_count(","); i++) { + String driver = video_drivers.get_slice(",", i); + if (gui_base->has_icon(driver, "EditorIcons")) { + video_driver->add_icon_item(gui_base->get_icon(driver, "EditorIcons"), ""); + } else { + video_driver->add_item(driver); + } + + video_driver->set_item_metadata(i, driver); + + if (current_video_driver == driver) { + video_driver->select(i); + video_driver_current = i; + } + } + + video_driver->connect("item_selected", this, "_video_driver_selected"); + video_restart_dialog = memnew(ConfirmationDialog); + video_restart_dialog->set_text(TTR("Changing the video driver requires restarting the editor.")); + video_restart_dialog->get_ok()->set_text(TTR("Save & Restart")); + video_restart_dialog->connect("confirmed", this, "_menu_option", varray(SET_VIDEO_DRIVER_SAVE_AND_RESTART)); + gui_base->add_child(video_restart_dialog); + progress_hb = memnew(BackgroundProgress); HBoxContainer *right_menu_hb = memnew(HBoxContainer); @@ -5184,8 +5289,8 @@ EditorNode::EditorNode() { update_menu->set_icon(gui_base->get_icon("Progress1", "EditorIcons")); update_menu->get_popup()->connect("id_pressed", this, "_menu_option"); p = update_menu->get_popup(); - p->add_check_item(TTR("Update Always"), SETTINGS_UPDATE_ALWAYS); - p->add_check_item(TTR("Update Changes"), SETTINGS_UPDATE_CHANGES); + p->add_radio_check_item(TTR("Update Always"), SETTINGS_UPDATE_ALWAYS); + p->add_radio_check_item(TTR("Update Changes"), SETTINGS_UPDATE_CHANGES); p->add_separator(); p->add_check_item(TTR("Disable Update Spinner"), SETTINGS_UPDATE_SPINNER_HIDE); int update_always = EditorSettings::get_singleton()->get_project_metadata("editor_options", "update_always", false); @@ -5263,6 +5368,19 @@ EditorNode::EditorNode() { bottom_panel_hb = memnew(HBoxContainer); bottom_panel_vb->add_child(bottom_panel_hb); + bottom_panel_hb_editors = memnew(HBoxContainer); + bottom_panel_hb_editors->set_h_size_flags(Control::SIZE_EXPAND_FILL); + bottom_panel_hb->add_child(bottom_panel_hb_editors); + bottom_panel_raise = memnew(ToolButton); + bottom_panel_raise->set_icon(gui_base->get_icon("ExpandBottomDock", "EditorIcons")); + + bottom_panel_raise->set_shortcut(ED_SHORTCUT("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KEY_MASK_SHIFT | KEY_F12)); + + bottom_panel_hb->add_child(bottom_panel_raise); + bottom_panel_raise->hide(); + bottom_panel_raise->set_toggle_mode(true); + bottom_panel_raise->connect("toggled", this, "_bottom_panel_raise_toggled"); + log = memnew(EditorLog); ToolButton *output_button = add_bottom_panel_item(TTR("Output"), log); log->set_tool_button(output_button); @@ -5351,6 +5469,7 @@ EditorNode::EditorNode() { EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor(); ScriptTextEditor::register_editor(); //register one for text scripts + TextEditor::register_editor(); if (StreamPeerSSL::is_available()) { add_editor_plugin(memnew(AssetLibraryEditorPlugin(this))); @@ -5367,8 +5486,7 @@ EditorNode::EditorNode() { raise_bottom_panel_item(AnimationPlayerEditor::singleton); add_editor_plugin(memnew(ShaderEditorPlugin(this))); - // FIXME: Disabled for Godot 3.0 as made incompatible, it needs to be ported to the new API. - //add_editor_plugin(memnew(ShaderGraphEditorPlugin(this))); + add_editor_plugin(memnew(VisualShaderEditorPlugin(this))); add_editor_plugin(memnew(AnimationNodeBlendTreeEditorPlugin(this))); add_editor_plugin(memnew(AnimationNodeBlendSpace1DEditorPlugin(this))); add_editor_plugin(memnew(AnimationNodeBlendSpace2DEditorPlugin(this))); @@ -5406,6 +5524,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(CollisionShape2DEditorPlugin(this))); add_editor_plugin(memnew(CurveEditorPlugin(this))); add_editor_plugin(memnew(TextureEditorPlugin(this))); + add_editor_plugin(memnew(AudioStreamEditorPlugin(this))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(SkeletonEditorPlugin(this))); diff --git a/editor/editor_node.h b/editor/editor_node.h index a5f975784c..38e68b2e09 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -164,7 +164,6 @@ private: SETTINGS_UPDATE_ALWAYS, SETTINGS_UPDATE_CHANGES, SETTINGS_UPDATE_SPINNER_HIDE, - SETTINGS_EXPORT_PREFERENCES, SETTINGS_PREFERENCES, SETTINGS_LAYOUT_SAVE, SETTINGS_LAYOUT_DELETE, @@ -183,6 +182,8 @@ private: HELP_COMMUNITY, HELP_ABOUT, + SET_VIDEO_DRIVER_SAVE_AND_RESTART, + IMPORT_PLUGIN_BASE = 100, TOOL_MENU_BASE = 1000 @@ -195,6 +196,13 @@ private: Control *gui_base; VBoxContainer *main_vbox; PanelContainer *play_button_panel; + OptionButton *video_driver; + + ConfirmationDialog *video_restart_dialog; + + int video_driver_current; + String video_driver_request; + void _video_driver_selected(int); //split @@ -377,7 +385,11 @@ private: PanelContainer *bottom_panel; HBoxContainer *bottom_panel_hb; + HBoxContainer *bottom_panel_hb_editors; VBoxContainer *bottom_panel_vb; + ToolButton *bottom_panel_raise; + + void _bottom_panel_raise_toggled(bool); EditorInterface *editor_interface; @@ -742,6 +754,8 @@ public: void add_tool_submenu_item(const String &p_name, PopupMenu *p_submenu); void remove_tool_menu_item(const String &p_name); + void save_all_scenes_and_restart(); + void dim_editor(bool p_dimming); void edit_current() { _edit_current(); }; diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 064569dea0..0b49b5a801 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -178,6 +178,8 @@ void EditorPropertyTextEnum::_bind_methods() { EditorPropertyTextEnum::EditorPropertyTextEnum() { options = memnew(OptionButton); options->set_clip_text(true); + options->set_flat(true); + add_child(options); add_focusable(options); options->connect("item_selected", this, "_option_selected"); @@ -390,13 +392,37 @@ EditorPropertyCheck::EditorPropertyCheck() { void EditorPropertyEnum::_option_selected(int p_which) { - emit_signal("property_changed", get_edited_property(), p_which); + String text = options->get_item_text(p_which); + Vector<String> text_split = text.split(":"); + if (text_split.size() == 1) { + emit_signal("property_changed", get_edited_property(), p_which); + return; + } + String name = text_split[1]; + emit_signal("property_changed", get_edited_property(), name.to_int()); } void EditorPropertyEnum::update_property() { int which = get_edited_object()->get(get_edited_property()); - options->select(which); + if (which == 0) { + options->select(which); + return; + } + + for (int i = 0; i < options->get_item_count(); i++) { + String text = options->get_item_text(i); + Vector<String> text_split = text.split(":"); + if (text_split.size() == 1) { + options->select(which); + return; + } + String name = text_split[1]; + if (itos(which) == name) { + options->select(i); + return; + } + } } void EditorPropertyEnum::setup(const Vector<String> &p_options) { @@ -405,6 +431,10 @@ void EditorPropertyEnum::setup(const Vector<String> &p_options) { } } +void EditorPropertyEnum::set_option_button_clip(bool p_enable) { + options->set_clip_text(p_enable); +} + void EditorPropertyEnum::_bind_methods() { ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyEnum::_option_selected); @@ -413,6 +443,7 @@ void EditorPropertyEnum::_bind_methods() { EditorPropertyEnum::EditorPropertyEnum() { options = memnew(OptionButton); options->set_clip_text(true); + options->set_flat(true); add_child(options); add_focusable(options); options->connect("item_selected", this, "_option_selected"); @@ -613,9 +644,11 @@ void EditorPropertyLayers::setup(LayerType p_layer_type) { } if (name == "") { - name = "Layer " + itos(i + 1); + name = TTR("Layer") + " " + itos(i + 1); } + name += "\n" + vformat(TTR("Bit %d, value %d"), i, 1 << i); + names.push_back(name); } @@ -705,6 +738,7 @@ void EditorPropertyInteger::setup(int p_min, int p_max, bool p_allow_greater, bo EditorPropertyInteger::EditorPropertyInteger() { spin = memnew(EditorSpinSlider); + spin->set_flat(true); add_child(spin); add_focusable(spin); spin->connect("value_changed", this, "_value_changed"); @@ -791,6 +825,7 @@ void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool EditorPropertyFloat::EditorPropertyFloat() { spin = memnew(EditorSpinSlider); + spin->set_flat(true); add_child(spin); add_focusable(spin); spin->connect("value_changed", this, "_value_changed"); @@ -801,6 +836,12 @@ EditorPropertyFloat::EditorPropertyFloat() { void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { + Ref<InputEventMouseButton> mb = p_ev; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + preset->set_global_position(easing_draw->get_global_transform().xform(mb->get_position())); + preset->popup(); + } + Ref<InputEventMouseMotion> mm = p_ev; if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { @@ -838,7 +879,7 @@ void EditorPropertyEasing::_draw_easing() { Size2 s = easing_draw->get_size(); Rect2 r(Point2(), s); r = r.grow(3); - get_stylebox("normal", "LineEdit")->draw(ci, r); + //get_stylebox("normal", "LineEdit")->draw(ci, r); int points = 48; @@ -848,6 +889,7 @@ void EditorPropertyEasing::_draw_easing() { Ref<Font> f = get_font("font", "Label"); Color color = get_color("font_color", "Label"); + Vector<Point2> lines; for (int i = 1; i <= points; i++) { float ifl = i / float(points); @@ -860,10 +902,12 @@ void EditorPropertyEasing::_draw_easing() { iflp = 1.0 - iflp; } - VisualServer::get_singleton()->canvas_item_add_line(ci, Point2(iflp * s.width, prev * s.height), Point2(ifl * s.width, h * s.height), color); + lines.push_back(Point2(ifl * s.width, h * s.height)); + lines.push_back(Point2(iflp * s.width, prev * s.height)); prev = h; } + easing_draw->draw_multiline(lines, color, 1.0, true); f->draw(ci, Point2(10, 10 + f->get_ascent()), String::num(exp, 2), color); } @@ -871,29 +915,17 @@ void EditorPropertyEasing::update_property() { easing_draw->update(); } -void EditorPropertyEasing::_set_preset(float p_val) { - emit_signal("property_changed", get_edited_property(), p_val); +void EditorPropertyEasing::_set_preset(int p_preset) { + static const float preset_value[EASING_MAX] = { 0.0, 1.0, 2.0, 0.5, -2.0, -0.5 }; + + emit_signal("property_changed", get_edited_property(), preset_value[p_preset]); easing_draw->update(); } void EditorPropertyEasing::setup(bool p_full, bool p_flip) { flip = p_flip; - if (p_full) { - HBoxContainer *hb2 = memnew(HBoxContainer); - vb->add_child(hb2); - button_out_in = memnew(ToolButton); - button_out_in->set_tooltip(TTR("Out-In")); - button_out_in->set_h_size_flags(SIZE_EXPAND_FILL); - button_out_in->connect("pressed", this, "_set_preset", varray(-0.5)); - hb2->add_child(button_out_in); - - button_in_out = memnew(ToolButton); - button_in_out->set_tooltip(TTR("In")); - button_in_out->set_h_size_flags(SIZE_EXPAND_FILL); - button_in_out->connect("pressed", this, "_set_preset", varray(-2)); - hb2->add_child(button_in_out); - } + full = p_full; } void EditorPropertyEasing::_notification(int p_what) { @@ -901,15 +933,19 @@ void EditorPropertyEasing::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_ENTER_TREE: { + preset->clear(); + preset->add_icon_item(get_icon("CurveConstant", "EditorIcons"), "Zero", EASING_ZERO); + preset->add_icon_item(get_icon("CurveLinear", "EditorIcons"), "Linear", EASING_LINEAR); + preset->add_icon_item(get_icon("CurveIn", "EditorIcons"), "In", EASING_IN); + preset->add_icon_item(get_icon("CurveOut", "EditorIcons"), "Out", EASING_OUT); + if (full) { + preset->add_icon_item(get_icon("CurveInOut", "EditorIcons"), "In-Out", EASING_IN_OUT); + preset->add_icon_item(get_icon("CurveOutIn", "EditorIcons"), "Out-In", EASING_OUT_IN); + } easing_draw->set_custom_minimum_size(Size2(0, get_font("font", "Label")->get_height() * 2)); - button_linear->set_icon(get_icon("CurveLinear", "EditorIcons")); - button_out->set_icon(get_icon("CurveOut", "EditorIcons")); - button_in->set_icon(get_icon("CurveIn", "EditorIcons")); - button_constant->set_icon(get_icon("CurveConstant", "EditorIcons")); - if (button_out_in) - button_out_in->set_icon(get_icon("CurveOutIn", "EditorIcons")); - if (button_in_out) - button_in_out->set_icon(get_icon("CurveInOut", "EditorIcons")); + } break; + case NOTIFICATION_RESIZED: { + } break; } } @@ -923,47 +959,18 @@ void EditorPropertyEasing::_bind_methods() { EditorPropertyEasing::EditorPropertyEasing() { - vb = memnew(VBoxContainer); - add_child(vb); - HBoxContainer *hb = memnew(HBoxContainer); - set_label_reference(hb); - - vb->add_child(hb); - - button_linear = memnew(ToolButton); - button_linear->set_tooltip(TTR("Linear")); - button_linear->set_h_size_flags(SIZE_EXPAND_FILL); - button_linear->connect("pressed", this, "_set_preset", varray(1)); - hb->add_child(button_linear); - - button_constant = memnew(ToolButton); - button_constant->set_tooltip(TTR("Linear")); - button_constant->set_h_size_flags(SIZE_EXPAND_FILL); - button_constant->connect("pressed", this, "_set_preset", varray(0)); - hb->add_child(button_constant); - - button_out = memnew(ToolButton); - button_out->set_tooltip(TTR("Out")); - button_out->set_h_size_flags(SIZE_EXPAND_FILL); - button_out->connect("pressed", this, "_set_preset", varray(0.5)); - hb->add_child(button_out); - - button_in = memnew(ToolButton); - button_in->set_tooltip(TTR("In")); - button_in->set_h_size_flags(SIZE_EXPAND_FILL); - button_in->connect("pressed", this, "_set_preset", varray(2)); - hb->add_child(button_in); - - button_in_out = NULL; - button_out_in = NULL; - easing_draw = memnew(Control); easing_draw->connect("draw", this, "_draw_easing"); easing_draw->connect("gui_input", this, "_drag_easing"); easing_draw->set_default_cursor_shape(Control::CURSOR_MOVE); - vb->add_child(easing_draw); + add_child(easing_draw); + + preset = memnew(PopupMenu); + add_child(preset); + preset->connect("id_pressed", this, "_set_preset"); flip = false; + full = false; } ///////////////////// VECTOR2 ///////////////////////// @@ -986,6 +993,18 @@ void EditorPropertyVector2::update_property() { setting = false; } +void EditorPropertyVector2::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 2; i++) { + + Color c = base; + c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} + void EditorPropertyVector2::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyVector2::_value_changed); @@ -1006,6 +1025,7 @@ EditorPropertyVector2::EditorPropertyVector2() { static const char *desc[2] = { "x", "y" }; for (int i = 0; i < 2; i++) { spin[i] = memnew(EditorSpinSlider); + spin[i]->set_flat(true); spin[i]->set_label(desc[i]); vb->add_child(spin[i]); add_focusable(spin[i]); @@ -1038,7 +1058,17 @@ void EditorPropertyRect2::update_property() { spin[3]->set_value(val.size.y); setting = false; } +void EditorPropertyRect2::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 4; i++) { + Color c = base; + c.set_hsv(float(i % 2) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyRect2::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyRect2::_value_changed); @@ -1060,6 +1090,8 @@ EditorPropertyRect2::EditorPropertyRect2() { for (int i = 0; i < 4; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); + vb->add_child(spin[i]); add_focusable(spin[i]); spin[i]->connect("value_changed", this, "_value_changed"); @@ -1088,7 +1120,17 @@ void EditorPropertyVector3::update_property() { spin[2]->set_value(val.z); setting = false; } +void EditorPropertyVector3::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 3; i++) { + Color c = base; + c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyVector3::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyVector3::_value_changed); @@ -1110,6 +1152,8 @@ EditorPropertyVector3::EditorPropertyVector3() { for (int i = 0; i < 3; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); + vb->add_child(spin[i]); add_focusable(spin[i]); spin[i]->connect("value_changed", this, "_value_changed"); @@ -1140,7 +1184,17 @@ void EditorPropertyPlane::update_property() { spin[3]->set_value(val.d); setting = false; } +void EditorPropertyPlane::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 3; i++) { + Color c = base; + c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyPlane::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyPlane::_value_changed); @@ -1162,6 +1216,7 @@ EditorPropertyPlane::EditorPropertyPlane() { for (int i = 0; i < 4; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); vb->add_child(spin[i]); add_focusable(spin[i]); spin[i]->connect("value_changed", this, "_value_changed"); @@ -1193,7 +1248,17 @@ void EditorPropertyQuat::update_property() { spin[3]->set_value(val.w); setting = false; } +void EditorPropertyQuat::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 3; i++) { + Color c = base; + c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyQuat::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyQuat::_value_changed); @@ -1215,6 +1280,8 @@ EditorPropertyQuat::EditorPropertyQuat() { for (int i = 0; i < 4; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); + vb->add_child(spin[i]); add_focusable(spin[i]); spin[i]->connect("value_changed", this, "_value_changed"); @@ -1252,7 +1319,17 @@ void EditorPropertyAABB::update_property() { setting = false; } +void EditorPropertyAABB::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 6; i++) { + Color c = base; + c.set_hsv(float(i % 3) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyAABB::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyAABB::_value_changed); @@ -1276,6 +1353,8 @@ EditorPropertyAABB::EditorPropertyAABB() { for (int i = 0; i < 6; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); + g->add_child(spin[i]); spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); add_focusable(spin[i]); @@ -1314,7 +1393,17 @@ void EditorPropertyTransform2D::update_property() { setting = false; } +void EditorPropertyTransform2D::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 6; i++) { + Color c = base; + c.set_hsv(float(i % 2) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyTransform2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyTransform2D::_value_changed); @@ -1334,10 +1423,11 @@ EditorPropertyTransform2D::EditorPropertyTransform2D() { g->set_columns(2); add_child(g); - static const char *desc[6] = { "xx", "xy", "yx", "yy", "ox", "oy" }; + static const char *desc[6] = { "x", "y", "x", "y", "x", "y" }; for (int i = 0; i < 6; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); g->add_child(spin[i]); spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); add_focusable(spin[i]); @@ -1382,7 +1472,17 @@ void EditorPropertyBasis::update_property() { setting = false; } +void EditorPropertyBasis::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 9; i++) { + Color c = base; + c.set_hsv(float(i % 3) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyBasis::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyBasis::_value_changed); @@ -1402,10 +1502,11 @@ EditorPropertyBasis::EditorPropertyBasis() { g->set_columns(3); add_child(g); - static const char *desc[9] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz" }; + static const char *desc[9] = { "x", "y", "z", "x", "y", "z", "x", "y", "z" }; for (int i = 0; i < 9; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); g->add_child(spin[i]); spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); add_focusable(spin[i]); @@ -1456,7 +1557,17 @@ void EditorPropertyTransform::update_property() { setting = false; } +void EditorPropertyTransform::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Color base = get_color("accent_color", "Editor"); + for (int i = 0; i < 12; i++) { + Color c = base; + c.set_hsv(float(i % 3) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v()); + spin[i]->set_custom_label_color(true, c); + } + } +} void EditorPropertyTransform::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyTransform::_value_changed); @@ -1476,10 +1587,11 @@ EditorPropertyTransform::EditorPropertyTransform() { g->set_columns(3); add_child(g); - static const char *desc[12] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz", "ox", "oy", "oz" }; + static const char *desc[12] = { "x", "y", "z", "x", "y", "z", "x", "y", "z", "x", "y", "z" }; for (int i = 0; i < 12; i++) { spin[i] = memnew(EditorSpinSlider); spin[i]->set_label(desc[i]); + spin[i]->set_flat(true); g->add_child(spin[i]); spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); add_focusable(spin[i]); @@ -1826,7 +1938,19 @@ void EditorPropertyResource::_resource_preview(const String &p_path, const Ref<T RES p = get_edited_object()->get(get_edited_property()); if (p.is_valid() && p->get_instance_id() == p_obj) { if (p_preview.is_valid()) { - assign->set_icon(p_preview); + String type = p->get_class_name(); + preview->set_margin(MARGIN_LEFT, assign->get_icon()->get_width() + assign->get_stylebox("normal")->get_default_margin(MARGIN_LEFT) + get_constant("hseparation", "Button")); + if (type == "GradientTexture") { + preview->set_stretch_mode(TextureRect::STRETCH_SCALE); + assign->set_custom_minimum_size(Size2(1, 1)); + } else { + preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); + thumbnail_size *= EDSCALE; + assign->set_custom_minimum_size(Size2(1, thumbnail_size)); + } + preview->set_texture(p_preview); + assign->set_text(""); } } } @@ -2000,7 +2124,7 @@ void EditorPropertyResource::_sub_inspector_object_id_selected(int p_id) { void EditorPropertyResource::_open_editor_pressed() { RES res = get_edited_object()->get(get_edited_property()); if (res.is_valid()) { - EditorNode::get_singleton()->edit_item(res.ptr()); + EditorNode::get_singleton()->edit_resource(res.ptr()); } } @@ -2020,6 +2144,9 @@ void EditorPropertyResource::update_property() { sub_inspector = memnew(EditorInspector); sub_inspector->set_enable_v_scroll(false); + sub_inspector->set_use_sub_inspector_bg(true); + sub_inspector->set_enable_capitalize_paths(true); + sub_inspector->connect("property_keyed", this, "_sub_inspector_property_keyed"); sub_inspector->connect("resource_selected", this, "_sub_inspector_resource_selected"); sub_inspector->connect("object_id_selected", this, "_sub_inspector_object_id_selected"); @@ -2067,6 +2194,7 @@ void EditorPropertyResource::update_property() { #endif } + preview->set_texture(Ref<Texture>()); if (res == RES()) { assign->set_icon(Ref<Texture>()); assign->set_text(TTR("[empty]")); @@ -2266,6 +2394,10 @@ void EditorPropertyResource::drop_data_fw(const Point2 &p_point, const Variant & } } +void EditorPropertyResource::set_use_sub_inspector(bool p_enable) { + use_sub_inspector = p_enable; +} + void EditorPropertyResource::_bind_methods() { ClassDB::bind_method(D_METHOD("_file_selected"), &EditorPropertyResource::_file_selected); @@ -2288,7 +2420,8 @@ EditorPropertyResource::EditorPropertyResource() { sub_inspector = NULL; sub_inspector_vbox = NULL; - use_sub_inspector = !bool(EDITOR_GET("interface/inspector/open_resources_in_new_inspector")); + use_sub_inspector = bool(EDITOR_GET("interface/inspector/open_resources_in_current_inspector")); + HBoxContainer *hbc = memnew(HBoxContainer); add_child(hbc); assign = memnew(Button); @@ -2300,6 +2433,14 @@ EditorPropertyResource::EditorPropertyResource() { assign->connect("draw", this, "_button_draw"); hbc->add_child(assign); + preview = memnew(TextureRect); + preview->set_expand(true); + preview->set_anchors_and_margins_preset(PRESET_WIDE); + preview->set_margin(MARGIN_TOP, 1); + preview->set_margin(MARGIN_BOTTOM, -1); + preview->set_margin(MARGIN_RIGHT, -1); + assign->add_child(preview); + menu = memnew(PopupMenu); add_child(menu); edit = memnew(Button); @@ -2691,6 +2832,22 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ case Variant::OBJECT: { EditorPropertyResource *editor = memnew(EditorPropertyResource); editor->setup(p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource"); + + if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) { + String open_in_new = EDITOR_GET("interface/inspector/resources_types_to_open_in_new_inspector"); + for (int i = 0; i < open_in_new.get_slice_count(","); i++) { + String type = open_in_new.get_slicec(',', i).strip_edges(); + for (int j = 0; j < p_hint_text.get_slice_count(","); j++) { + String inherits = p_hint_text.get_slicec(',', j); + + if (ClassDB::is_parent_class(inherits, type)) { + + editor->set_use_sub_inspector(false); + } + } + } + } + add_property_editor(p_path, editor); } break; diff --git a/editor/editor_properties.h b/editor/editor_properties.h index c67eccb60e..0afb1bf955 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -178,6 +178,7 @@ protected: public: void setup(const Vector<String> &p_options); virtual void update_property(); + void set_option_button_clip(bool p_enable); EditorPropertyEnum(); }; @@ -277,16 +278,26 @@ public: class EditorPropertyEasing : public EditorProperty { GDCLASS(EditorPropertyEasing, EditorProperty) Control *easing_draw; - ToolButton *button_out, *button_in, *button_linear, *button_constant; - ToolButton *button_in_out, *button_out_in; - VBoxContainer *vb; + PopupMenu *preset; + bool full; + + enum { + EASING_ZERO, + EASING_LINEAR, + EASING_IN, + EASING_OUT, + EASING_IN_OUT, + EASING_OUT_IN, + EASING_MAX + + }; bool flip; void _drag_easing(const Ref<InputEvent> &p_ev); void _draw_easing(); void _notification(int p_what); - void _set_preset(float p_val); + void _set_preset(int); protected: static void _bind_methods(); @@ -304,6 +315,7 @@ class EditorPropertyVector2 : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -319,6 +331,7 @@ class EditorPropertyRect2 : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -334,6 +347,7 @@ class EditorPropertyVector3 : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -349,6 +363,7 @@ class EditorPropertyPlane : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -364,6 +379,7 @@ class EditorPropertyQuat : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -379,6 +395,7 @@ class EditorPropertyAABB : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -394,6 +411,7 @@ class EditorPropertyTransform2D : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -409,6 +427,7 @@ class EditorPropertyBasis : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -424,6 +443,7 @@ class EditorPropertyTransform : public EditorProperty { void _value_changed(double p_val); protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -487,6 +507,7 @@ class EditorPropertyResource : public EditorProperty { }; Button *assign; + TextureRect *preview; Button *edit; PopupMenu *menu; EditorFileDialog *file; @@ -531,6 +552,8 @@ public: void collapse_all_folding(); void expand_all_folding(); + void set_use_sub_inspector(bool p_enable); + EditorPropertyResource(); }; diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp new file mode 100644 index 0000000000..72050cd79b --- /dev/null +++ b/editor/editor_sectioned_inspector.cpp @@ -0,0 +1,306 @@ +#include "editor_sectioned_inspector.h" +#include "editor_scale.h" +class SectionedInspectorFilter : public Object { + + GDCLASS(SectionedInspectorFilter, Object); + + Object *edited; + String section; + bool allow_sub; + + bool _set(const StringName &p_name, const Variant &p_value) { + + if (!edited) + return false; + + String name = p_name; + if (section != "") { + name = section + "/" + name; + } + + bool valid; + edited->set(name, p_value, &valid); + return valid; + } + + bool _get(const StringName &p_name, Variant &r_ret) const { + + if (!edited) + return false; + + String name = p_name; + if (section != "") { + name = section + "/" + name; + } + + bool valid = false; + + r_ret = edited->get(name, &valid); + return valid; + } + void _get_property_list(List<PropertyInfo> *p_list) const { + + if (!edited) + return; + + List<PropertyInfo> pinfo; + edited->get_property_list(&pinfo); + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + + PropertyInfo pi = E->get(); + int sp = pi.name.find("/"); + + if (pi.name == "resource_path" || pi.name == "resource_name" || pi.name == "resource_local_to_scene" || pi.name.begins_with("script/")) //skip resource stuff + continue; + + if (sp == -1) { + pi.name = "global/" + pi.name; + } + + if (pi.name.begins_with(section + "/")) { + pi.name = pi.name.replace_first(section + "/", ""); + if (!allow_sub && pi.name.find("/") != -1) + continue; + p_list->push_back(pi); + } + } + } + + bool property_can_revert(const String &p_name) { + + return edited->call("property_can_revert", section + "/" + p_name); + } + + Variant property_get_revert(const String &p_name) { + + return edited->call("property_get_revert", section + "/" + p_name); + } + +protected: + static void _bind_methods() { + + ClassDB::bind_method("property_can_revert", &SectionedInspectorFilter::property_can_revert); + ClassDB::bind_method("property_get_revert", &SectionedInspectorFilter::property_get_revert); + } + +public: + void set_section(const String &p_section, bool p_allow_sub) { + + section = p_section; + allow_sub = p_allow_sub; + _change_notify(); + } + + void set_edited(Object *p_edited) { + edited = p_edited; + _change_notify(); + } + + SectionedInspectorFilter() { + edited = NULL; + } +}; + +void SectionedInspector::_bind_methods() { + + ClassDB::bind_method("_section_selected", &SectionedInspector::_section_selected); + ClassDB::bind_method("_search_changed", &SectionedInspector::_search_changed); + + ClassDB::bind_method("update_category_list", &SectionedInspector::update_category_list); +} + +void SectionedInspector::_section_selected() { + + if (!sections->get_selected()) + return; + + filter->set_section(sections->get_selected()->get_metadata(0), sections->get_selected()->get_children() == NULL); + inspector->set_property_prefix(String(sections->get_selected()->get_metadata(0)) + "/"); +} + +void SectionedInspector::set_current_section(const String &p_section) { + + if (section_map.has(p_section)) { + section_map[p_section]->select(0); + } +} + +String SectionedInspector::get_current_section() const { + + if (sections->get_selected()) + return sections->get_selected()->get_metadata(0); + else + return ""; +} + +String SectionedInspector::get_full_item_path(const String &p_item) { + + String base = get_current_section(); + + if (base != "") + return base + "/" + p_item; + else + return p_item; +} + +void SectionedInspector::edit(Object *p_object) { + + if (!p_object) { + obj = -1; + sections->clear(); + + filter->set_edited(NULL); + inspector->edit(NULL); + + return; + } + + ObjectID id = p_object->get_instance_id(); + + inspector->set_object_class(p_object->get_class()); + + if (obj != id) { + + obj = id; + update_category_list(); + + filter->set_edited(p_object); + inspector->edit(filter); + + if (sections->get_root()->get_children()) { + sections->get_root()->get_children()->select(0); + } + } else { + + update_category_list(); + } +} + +void SectionedInspector::update_category_list() { + + String selected_category = get_current_section(); + sections->clear(); + + Object *o = ObjectDB::get_instance(obj); + + if (!o) + return; + + List<PropertyInfo> pinfo; + o->get_property_list(&pinfo); + + section_map.clear(); + + TreeItem *root = sections->create_item(); + section_map[""] = root; + + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + + PropertyInfo pi = E->get(); + + if (pi.usage & PROPERTY_USAGE_CATEGORY) + continue; + else if (!(pi.usage & PROPERTY_USAGE_EDITOR)) + continue; + + if (pi.name.find(":") != -1 || pi.name == "script" || pi.name == "resource_name" || pi.name == "resource_path" || pi.name == "resource_local_to_scene") + continue; + + if (search_box && search_box->get_text() != String() && pi.name.findn(search_box->get_text()) == -1) + continue; + + int sp = pi.name.find("/"); + if (sp == -1) + pi.name = "Global/" + pi.name; + + Vector<String> sectionarr = pi.name.split("/"); + String metasection; + + int sc = MIN(2, sectionarr.size() - 1); + + for (int i = 0; i < sc; i++) { + + TreeItem *parent = section_map[metasection]; + parent->set_custom_bg_color(0, get_color("prop_subsection", "Editor")); + + if (i > 0) { + metasection += "/" + sectionarr[i]; + } else { + metasection = sectionarr[i]; + } + + if (!section_map.has(metasection)) { + TreeItem *ms = sections->create_item(parent); + section_map[metasection] = ms; + ms->set_text(0, sectionarr[i].capitalize()); + ms->set_metadata(0, metasection); + ms->set_selectable(0, false); + } + + if (i == sc - 1) { + //if it has children, make selectable + section_map[metasection]->set_selectable(0, true); + } + } + } + + if (section_map.has(selected_category)) { + section_map[selected_category]->select(0); + } + + inspector->update_tree(); +} + +void SectionedInspector::register_search_box(LineEdit *p_box) { + + search_box = p_box; + inspector->register_text_enter(p_box); + search_box->connect("text_changed", this, "_search_changed"); +} + +void SectionedInspector::_search_changed(const String &p_what) { + + update_category_list(); +} + +EditorInspector *SectionedInspector::get_inspector() { + + return inspector; +} + +SectionedInspector::SectionedInspector() { + + obj = -1; + + search_box = NULL; + + add_constant_override("autohide", 1); // Fixes the dragger always showing up + + VBoxContainer *left_vb = memnew(VBoxContainer); + left_vb->set_custom_minimum_size(Size2(170, 0) * EDSCALE); + add_child(left_vb); + + sections = memnew(Tree); + sections->set_v_size_flags(SIZE_EXPAND_FILL); + sections->set_hide_root(true); + + left_vb->add_child(sections, true); + + VBoxContainer *right_vb = memnew(VBoxContainer); + right_vb->set_custom_minimum_size(Size2(300, 0) * EDSCALE); + right_vb->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(right_vb); + + filter = memnew(SectionedInspectorFilter); + inspector = memnew(EditorInspector); + inspector->set_v_size_flags(SIZE_EXPAND_FILL); + right_vb->add_child(inspector, true); + inspector->set_use_doc_hints(true); + + sections->connect("cell_selected", this, "_section_selected"); +} + +SectionedInspector::~SectionedInspector() { + + memdelete(filter); +} diff --git a/editor/editor_sectioned_inspector.h b/editor/editor_sectioned_inspector.h new file mode 100644 index 0000000000..75b51a1581 --- /dev/null +++ b/editor/editor_sectioned_inspector.h @@ -0,0 +1,42 @@ +#ifndef EDITOR_SECTIONED_INSPECTOR_H +#define EDITOR_SECTIONED_INSPECTOR_H + +#include "editor/editor_inspector.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tree.h" + +class SectionedInspectorFilter; + +class SectionedInspector : public HSplitContainer { + + GDCLASS(SectionedInspector, HSplitContainer); + + ObjectID obj; + + Tree *sections; + SectionedInspectorFilter *filter; + + Map<String, TreeItem *> section_map; + EditorInspector *inspector; + LineEdit *search_box; + + static void _bind_methods(); + void _section_selected(); + + void _search_changed(const String &p_what); + +public: + void register_search_box(LineEdit *p_box); + EditorInspector *get_inspector(); + void edit(Object *p_object); + String get_full_item_path(const String &p_item); + + void set_current_section(const String &p_section); + String get_current_section() const; + + void update_category_list(); + + SectionedInspector(); + ~SectionedInspector(); +}; +#endif // EDITOR_SECTIONED_INSPECTOR_H diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 4045d6c3d3..c8e97b071f 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -165,6 +165,7 @@ struct _EVCSort { Variant::Type type; int order; bool save; + bool restart_if_changed; bool operator<(const _EVCSort &p_vcs) const { return order < p_vcs.order; } }; @@ -188,6 +189,7 @@ void EditorSettings::_get_property_list(List<PropertyInfo> *p_list) const { vc.order = v->order; vc.type = v->variant.get_type(); vc.save = v->save; + vc.restart_if_changed = v->restart_if_changed; vclist.insert(vc); } @@ -210,6 +212,10 @@ void EditorSettings::_get_property_list(List<PropertyInfo> *p_list) const { if (hints.has(E->get().name)) pi = hints[E->get().name]; + if (E->get().restart_if_changed) { + pi.usage |= PROPERTY_USAGE_RESTART_IF_CHANGED; + } + p_list->push_back(pi); } @@ -280,6 +286,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { } _initial_set("interface/editor/editor_language", best); + set_restart_if_changed("interface/editor/editor_language", true); hints["interface/editor/editor_language"] = PropertyInfo(Variant::STRING, "interface/editor/editor_language", PROPERTY_HINT_ENUM, lang_hint, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); } @@ -291,17 +298,17 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/main_font_size", 14); hints["interface/editor/main_font_size"] = PropertyInfo(Variant::INT, "interface/editor/main_font_size", PROPERTY_HINT_RANGE, "10,40,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/code_font_size", 14); - hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/main_font_hinting", 2); - hints["interface/editor/main_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/main_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["interface/editor/main_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/main_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/code_font_hinting", 2); - hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/main_font", ""); - hints["interface/editor/main_font"] = PropertyInfo(Variant::STRING, "interface/editor/main_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["interface/editor/main_font"] = PropertyInfo(Variant::STRING, "interface/editor/main_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/main_font_bold", ""); - hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_font_bold", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_font_bold", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/code_font", ""); - hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/dim_editor_on_dialog_popup", true); _initial_set("interface/editor/dim_amount", 0.6f); hints["interface/editor/dim_amount"] = PropertyInfo(Variant::REAL, "interface/editor/dim_amount", PROPERTY_HINT_RANGE, "0,1,0.01", PROPERTY_USAGE_DEFAULT); @@ -357,6 +364,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/highlighting/highlight_all_occurrences", true); _initial_set("text_editor/highlighting/highlight_current_line", true); + _initial_set("text_editor/highlighting/highlight_type_safe_lines", true); _initial_set("text_editor/cursor/scroll_past_end_of_file", false); _initial_set("text_editor/indent/type", 0); @@ -397,6 +405,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/completion/callhint_tooltip_offset", Vector2()); _initial_set("text_editor/files/restore_scripts_on_load", true); _initial_set("text_editor/completion/complete_file_paths", true); + _initial_set("text_editor/completion/add_type_hints", false); _initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false); _initial_set("docks/scene_tree/draw_relationship_lines", false); @@ -585,6 +594,7 @@ void EditorSettings::_load_default_text_editor_theme() { _initial_set("text_editor/highlighting/completion_font_color", Color::html("aaaaaa")); _initial_set("text_editor/highlighting/text_color", Color::html("aaaaaa")); _initial_set("text_editor/highlighting/line_number_color", Color::html("66aaaaaa")); + _initial_set("text_editor/highlighting/safe_line_number_color", Color::html("99aac8aa")); _initial_set("text_editor/highlighting/caret_color", Color::html("aaaaaa")); _initial_set("text_editor/highlighting/caret_background_color", Color::html("000000")); _initial_set("text_editor/highlighting/text_selected_color", Color::html("000000")); @@ -1017,6 +1027,14 @@ void EditorSettings::raise_order(const String &p_setting) { props[p_setting].order = ++last_order; } +void EditorSettings::set_restart_if_changed(const StringName &p_setting, bool p_restart) { + _THREAD_SAFE_METHOD_ + + if (!props.has(p_setting)) + return; + props[p_setting].restart_if_changed = p_restart; +} + void EditorSettings::set_initial_value(const StringName &p_setting, const Variant &p_value, bool p_update_current) { _THREAD_SAFE_METHOD_ @@ -1030,16 +1048,19 @@ void EditorSettings::set_initial_value(const StringName &p_setting, const Varian } } -Variant _EDITOR_DEF(const String &p_setting, const Variant &p_default) { +Variant _EDITOR_DEF(const String &p_setting, const Variant &p_default, bool p_restart_if_changed) { Variant ret = p_default; - if (EditorSettings::get_singleton()->has_setting(p_setting)) + if (EditorSettings::get_singleton()->has_setting(p_setting)) { ret = EditorSettings::get_singleton()->get(p_setting); - else + } else { EditorSettings::get_singleton()->set_manually(p_setting, p_default); + EditorSettings::get_singleton()->set_restart_if_changed(p_setting, p_restart_if_changed); + } - if (!EditorSettings::get_singleton()->has_default_value(p_setting)) + if (!EditorSettings::get_singleton()->has_default_value(p_setting)) { EditorSettings::get_singleton()->set_initial_value(p_setting, p_default); + } return ret; } diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 420e067cad..e5b61abf54 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -70,6 +70,7 @@ private: bool has_default_value; bool hide_from_editor; bool save; + bool restart_if_changed; VariantContainer() { variant = Variant(); initial = Variant(); @@ -77,6 +78,7 @@ private: hide_from_editor = false; has_default_value = false; save = false; + restart_if_changed = false; } VariantContainer(const Variant &p_variant, int p_order) { variant = p_variant; @@ -85,6 +87,7 @@ private: hide_from_editor = false; has_default_value = false; save = false; + restart_if_changed = false; } }; @@ -145,6 +148,7 @@ public: void erase(const String &p_setting); void raise_order(const String &p_setting); void set_initial_value(const StringName &p_setting, const Variant &p_value, bool p_update_current = false); + void set_restart_if_changed(const StringName &p_setting, bool p_restart); void set_manually(const StringName &p_setting, const Variant &p_value, bool p_emit_signal = false) { if (p_emit_signal) _set(p_setting, p_value); @@ -200,7 +204,8 @@ public: //not a macro any longer #define EDITOR_DEF(m_var, m_val) _EDITOR_DEF(m_var, Variant(m_val)) -Variant _EDITOR_DEF(const String &p_setting, const Variant &p_default); +#define EDITOR_DEF_RST(m_var, m_val) _EDITOR_DEF(m_var, Variant(m_val), true) +Variant _EDITOR_DEF(const String &p_setting, const Variant &p_default, bool p_restart_if_changed = false); #define EDITOR_GET(m_var) _EDITOR_GET(m_var) Variant _EDITOR_GET(const String &p_setting); diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index c7a33de3f1..0e6d81d13b 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -90,10 +90,12 @@ void EditorSpinSlider::_gui_input(const Ref<InputEvent> &p_event) { } grabbing_spinner_dist_cache += diff_x; - if (!grabbing_spinner && ABS(grabbing_spinner_dist_cache) > 4) { + if (!grabbing_spinner && ABS(grabbing_spinner_dist_cache) > 4 * EDSCALE) { Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); grabbing_spinner = true; - } else { + } + + if (grabbing_spinner) { if (mm->get_control() || updown_offset != -1) { set_value(Math::round(get_value())); if (ABS(grabbing_spinner_dist_cache) > 6) { @@ -159,20 +161,20 @@ void EditorSpinSlider::_notification(int p_what) { updown_offset = -1; Ref<StyleBox> sb = get_stylebox("normal", "LineEdit"); - draw_style_box(sb, Rect2(Vector2(), get_size())); + if (!flat) { + draw_style_box(sb, Rect2(Vector2(), get_size())); + } Ref<Font> font = get_font("font", "LineEdit"); + int sep_base = 4 * EDSCALE; + int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better + + int string_width = font->get_string_size(label).width; + int number_width = get_size().width - sb->get_minimum_size().width - string_width - sep; - int avail_width = get_size().width - sb->get_minimum_size().width; - avail_width -= font->get_string_size(label).width; Ref<Texture> updown = get_icon("updown", "SpinBox"); if (get_step() == 1) { - avail_width -= updown->get_width(); - } - - if (has_focus()) { - Ref<StyleBox> focus = get_stylebox("focus", "LineEdit"); - draw_style_box(focus, Rect2(Vector2(), get_size())); + number_width -= updown->get_width(); } String numstr = get_text_value(); @@ -180,10 +182,26 @@ void EditorSpinSlider::_notification(int p_what) { int vofs = (get_size().height - font->get_height()) / 2 + font->get_ascent(); Color fc = get_color("font_color", "LineEdit"); + Color lc; + if (use_custom_label_color) { + lc = custom_label_color; + } else { + lc = fc; + } - int label_ofs = sb->get_offset().x + avail_width; - draw_string(font, Vector2(label_ofs, vofs), label, fc * Color(1, 1, 1, 0.5)); - draw_string(font, Vector2(sb->get_offset().x, vofs), numstr, fc, avail_width); + if (flat && label != String()) { + Color label_bg_color = get_color("dark_color_3", "Editor"); + draw_rect(Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + string_width, get_size().height)), label_bg_color); + } + + if (has_focus()) { + Ref<StyleBox> focus = get_stylebox("focus", "LineEdit"); + draw_style_box(focus, Rect2(Vector2(), get_size())); + } + + draw_string(font, Vector2(sb->get_offset().x, vofs), label, lc * Color(1, 1, 1, 0.5)); + + draw_string(font, Vector2(sb->get_offset().x + string_width + sep, vofs), numstr, fc, number_width); if (get_step() == 1) { Ref<Texture> updown = get_icon("updown", "SpinBox"); @@ -250,9 +268,12 @@ void EditorSpinSlider::_notification(int p_what) { update(); } if (p_what == NOTIFICATION_FOCUS_ENTER) { - if (!Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT) && !value_input_just_closed) { + /* Sorry, I dont like this, it makes navigating the different fields with arrows more difficult. + * Just press enter to edit. + * if (!Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT) && !value_input_just_closed) { _focus_entered(); - } + }*/ + value_input_just_closed = false; } } @@ -334,6 +355,21 @@ bool EditorSpinSlider::is_read_only() const { return read_only; } +void EditorSpinSlider::set_flat(bool p_enable) { + + flat = p_enable; + update(); +} + +bool EditorSpinSlider::is_flat() const { + return flat; +} + +void EditorSpinSlider::set_custom_label_color(bool p_use_custom_label_color, Color p_custom_label_color) { + use_custom_label_color = p_use_custom_label_color; + custom_label_color = p_custom_label_color; +} + void EditorSpinSlider::_focus_entered() { Rect2 gr = get_global_rect(); value_input->set_text(get_text_value()); @@ -353,6 +389,9 @@ void EditorSpinSlider::_bind_methods() { ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorSpinSlider::set_read_only); ClassDB::bind_method(D_METHOD("is_read_only"), &EditorSpinSlider::is_read_only); + ClassDB::bind_method(D_METHOD("set_flat", "flat"), &EditorSpinSlider::set_flat); + ClassDB::bind_method(D_METHOD("is_flat"), &EditorSpinSlider::is_flat); + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorSpinSlider::_gui_input); ClassDB::bind_method(D_METHOD("_grabber_mouse_entered"), &EditorSpinSlider::_grabber_mouse_entered); ClassDB::bind_method(D_METHOD("_grabber_mouse_exited"), &EditorSpinSlider::_grabber_mouse_exited); @@ -363,10 +402,12 @@ void EditorSpinSlider::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); } EditorSpinSlider::EditorSpinSlider() { + flat = false; grabbing_spinner_attempt = false; grabbing_spinner = false; grabbing_spinner_dist_cache = 0; @@ -395,4 +436,5 @@ EditorSpinSlider::EditorSpinSlider() { value_input_just_closed = false; hide_slider = false; read_only = false; + use_custom_label_color = false; } diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h index 5316c0264a..fb32534ef4 100644 --- a/editor/editor_spin_slider.h +++ b/editor/editor_spin_slider.h @@ -68,6 +68,10 @@ class EditorSpinSlider : public Range { void _value_input_entered(const String &); void _value_focus_exited(); bool hide_slider; + bool flat; + + bool use_custom_label_color; + Color custom_label_color; protected: void _notification(int p_what); @@ -88,6 +92,11 @@ public: void set_read_only(bool p_enable); bool is_read_only() const; + void set_flat(bool p_enable); + bool is_flat() const; + + void set_custom_label_color(bool p_use_custom_label_color, Color p_custom_label_color); + virtual Size2 get_minimum_size() const; EditorSpinSlider(); }; diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 98402d8df5..18cc52a5c6 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -72,10 +72,11 @@ static Ref<StyleBoxFlat> make_flat_stylebox(Color p_color, float p_margin_left = return style; } -static Ref<StyleBoxLine> make_line_stylebox(Color p_color, int p_thickness = 1, float p_grow = 1, bool p_vertical = false) { +static Ref<StyleBoxLine> make_line_stylebox(Color p_color, int p_thickness = 1, float p_grow_begin = 1, float p_grow_end = 1, bool p_vertical = false) { Ref<StyleBoxLine> style(memnew(StyleBoxLine)); style->set_color(p_color); - style->set_grow(p_grow); + style->set_grow_begin(p_grow_begin); + style->set_grow_end(p_grow_end); style->set_thickness(p_thickness); style->set_vertical(p_vertical); return style; @@ -236,8 +237,6 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = ImageLoaderSVG::set_convert_colors(NULL); clock_t end_time = clock(); - - double time_d = (double)(end_time - begin_time) / CLOCKS_PER_SEC; #else print_line("Sorry no icons for you"); #endif @@ -256,7 +255,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { String preset = EDITOR_DEF("interface/theme/preset", "Default"); - int icon_font_color_setting = EDITOR_DEF("interface/theme/icon_and_font_color", 0); bool highlight_tabs = EDITOR_DEF("interface/theme/highlight_tabs", false); int border_size = EDITOR_DEF("interface/theme/border_size", 1); @@ -462,9 +460,20 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Ref<StyleBoxLine> style_popup_separator(memnew(StyleBoxLine)); style_popup_separator->set_color(separator_color); - style_popup_separator->set_grow(popup_margin_size - MAX(EDSCALE, border_width)); + style_popup_separator->set_grow_begin(popup_margin_size - MAX(EDSCALE, border_width)); + style_popup_separator->set_grow_end(popup_margin_size - MAX(EDSCALE, border_width)); style_popup_separator->set_thickness(MAX(EDSCALE, border_width)); + Ref<StyleBoxLine> style_popup_labeled_separator_left(memnew(StyleBoxLine)); + style_popup_labeled_separator_left->set_grow_begin(popup_margin_size - MAX(EDSCALE, border_width)); + style_popup_labeled_separator_left->set_color(separator_color); + style_popup_labeled_separator_left->set_thickness(MAX(EDSCALE, border_width)); + + Ref<StyleBoxLine> style_popup_labeled_separator_right(memnew(StyleBoxLine)); + style_popup_labeled_separator_right->set_grow_end(popup_margin_size - MAX(EDSCALE, border_width)); + style_popup_labeled_separator_right->set_color(separator_color); + style_popup_labeled_separator_right->set_thickness(MAX(EDSCALE, border_width)); + Ref<StyleBoxEmpty> style_empty = make_empty_stylebox(default_margin_size, default_margin_size, default_margin_size, default_margin_size); // Tabs @@ -578,6 +587,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("arrow", "OptionButton", theme->get_icon("GuiOptionArrow", "EditorIcons")); theme->set_constant("arrow_margin", "OptionButton", default_margin_size * EDSCALE); theme->set_constant("modulate_arrow", "OptionButton", true); + theme->set_constant("hseparation", "OptionButton", 4 * EDSCALE); // CheckButton theme->set_stylebox("normal", "CheckButton", style_menu); @@ -626,6 +636,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Ref<StyleBoxFlat> style_popup_menu = style_popup; theme->set_stylebox("panel", "PopupMenu", style_popup_menu); theme->set_stylebox("separator", "PopupMenu", style_popup_separator); + theme->set_stylebox("labeled_separator_left", "PopupMenu", style_popup_labeled_separator_left); + theme->set_stylebox("labeled_separator_right", "PopupMenu", style_popup_labeled_separator_right); + theme->set_color("font_color", "PopupMenu", font_color); theme->set_color("font_color_hover", "PopupMenu", font_color_hl); theme->set_color("font_color_accel", "PopupMenu", font_color_disabled); @@ -640,6 +653,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("visibility_xray", "PopupMenu", theme->get_icon("GuiVisibilityXray", "EditorIcons")); theme->set_constant("vseparation", "PopupMenu", (extra_spacing + default_margin_size) * EDSCALE); + Ref<StyleBoxFlat> sub_inspector_bg = make_flat_stylebox(dark_color_1, 2, 0, 0, 0); + sub_inspector_bg->set_border_width(MARGIN_LEFT, 2); + sub_inspector_bg->set_border_color(MARGIN_LEFT, accent_color * Color(1, 1, 1, 0.3)); + sub_inspector_bg->set_draw_center(true); + + theme->set_stylebox("sub_inspector_bg", "Editor", sub_inspector_bg); + theme->set_constant("inspector_margin", "Editor", 8 * EDSCALE); + // Tree & ItemList background Ref<StyleBoxFlat> style_tree_bg = style_default->duplicate(); style_tree_bg->set_bg_color(dark_color_1); @@ -650,6 +671,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // Tree theme->set_icon("checked", "Tree", theme->get_icon("GuiChecked", "EditorIcons")); theme->set_icon("unchecked", "Tree", theme->get_icon("GuiUnchecked", "EditorIcons")); + theme->set_icon("arrow_up", "Tree", theme->get_icon("GuiTreeArrowUp", "EditorIcons")); theme->set_icon("arrow", "Tree", theme->get_icon("GuiTreeArrowDown", "EditorIcons")); theme->set_icon("arrow_collapsed", "Tree", theme->get_icon("GuiTreeArrowRight", "EditorIcons")); theme->set_icon("updown", "Tree", theme->get_icon("GuiTreeUpdown", "EditorIcons")); @@ -780,7 +802,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // Separators theme->set_stylebox("separator", "HSeparator", make_line_stylebox(separator_color, border_width)); - theme->set_stylebox("separator", "VSeparator", make_line_stylebox(separator_color, border_width, 0, true)); + theme->set_stylebox("separator", "VSeparator", make_line_stylebox(separator_color, border_width, 0, 0, true)); // Debugger @@ -1005,6 +1027,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("title_h_offset", "GraphNode", -16 * EDSCALE); theme->set_constant("close_h_offset", "GraphNode", 20 * EDSCALE); theme->set_constant("close_offset", "GraphNode", 20 * EDSCALE); + theme->set_constant("separation", "GraphNode", 1 * EDSCALE); + theme->set_icon("close", "GraphNode", theme->get_icon("GuiCloseCustomizable", "EditorIcons")); theme->set_icon("resizer", "GraphNode", theme->get_icon("GuiResizer", "EditorIcons")); theme->set_icon("port", "GraphNode", theme->get_icon("GuiGraphNodePort", "EditorIcons")); @@ -1062,6 +1086,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color completion_font_color = font_color; const Color text_color = font_color; const Color line_number_color = dim_color; + const Color safe_line_number_color = dim_color * Color(1, 1.2, 1, 1.5); const Color caret_color = mono_color; const Color caret_background_color = mono_color.inverted(); const Color text_selected_color = dark_color_3; @@ -1096,6 +1121,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { setting->set_initial_value("text_editor/highlighting/completion_font_color", completion_font_color, true); setting->set_initial_value("text_editor/highlighting/text_color", text_color, true); setting->set_initial_value("text_editor/highlighting/line_number_color", line_number_color, true); + setting->set_initial_value("text_editor/highlighting/safe_line_number_color", safe_line_number_color, true); setting->set_initial_value("text_editor/highlighting/caret_color", caret_color, true); setting->set_initial_value("text_editor/highlighting/caret_background_color", caret_background_color, true); setting->set_initial_value("text_editor/highlighting/text_selected_color", text_selected_color, true); diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index 541c848ca3..931785333f 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -123,7 +123,6 @@ void ExportTemplateManager::_update_template_list() { void ExportTemplateManager::_download_template(const String &p_version) { - print_line("download " + p_version); while (template_list->get_child_count()) { memdelete(template_list->get_child(0)); } @@ -228,7 +227,10 @@ void ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_ version = data_str; } - fc++; + if (file.get_file().size() != 0) { + fc++; + } + ret = unzGoToNextFile(pkg); } @@ -268,6 +270,11 @@ void ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_ String file = String(fname).get_file(); + if (file.size() == 0) { + ret = unzGoToNextFile(pkg); + continue; + } + Vector<uint8_t> data; data.resize(info.uncompressed_size); @@ -344,7 +351,6 @@ void ExportTemplateManager::_http_download_mirror_completed(int p_status, int p_ bool mirrors_found = false; Dictionary d = r; - print_line(r); if (d.has("mirrors")) { Array mirrors = d["mirrors"]; for (int i = 0; i < mirrors.size(); i++) { @@ -499,7 +505,6 @@ void ExportTemplateManager::_notification(int p_what) { if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { if (!is_visible_in_tree()) { - print_line("closed"); set_process(false); } } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index eebf1b6ab8..37f86cc912 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1319,6 +1319,9 @@ void FileSystemDock::_file_option(int p_option) { String fpath = files->get_item_metadata(idx); OS::get_singleton()->set_clipboard(fpath); } break; + case FILE_NEW_RESOURCE: { + new_resource_dialog->popup_create(true); + } break; } } @@ -1393,6 +1396,21 @@ void FileSystemDock::_folder_option(int p_option) { } } +void FileSystemDock::_resource_created() const { + Object *c = new_resource_dialog->instance_selected(); + + ERR_FAIL_COND(!c); + Resource *r = Object::cast_to<Resource>(c); + ERR_FAIL_COND(!r); + + REF res(r); + editor->push_item(c); + + RES current_res = RES(r); + + editor->save_resource_as(current_res); +} + void FileSystemDock::_go_to_file_list() { if (low_height_mode) { @@ -1738,6 +1756,7 @@ void FileSystemDock::_files_list_rmb_select(int p_item, const Vector2 &p_pos) { file_options->add_item(TTR("New Folder..."), FILE_NEW_FOLDER); file_options->add_item(TTR("New Script..."), FILE_NEW_SCRIPT); + file_options->add_item(TTR("New Resource..."), FILE_NEW_RESOURCE); file_options->add_item(TTR("Show In File Manager"), FILE_SHOW_IN_EXPLORER); file_options->set_position(files->get_global_position() + p_pos); @@ -1750,6 +1769,7 @@ void FileSystemDock::_rmb_pressed(const Vector2 &p_pos) { file_options->add_item(TTR("New Folder..."), FILE_NEW_FOLDER); file_options->add_item(TTR("New Script..."), FILE_NEW_SCRIPT); + file_options->add_item(TTR("New Resource..."), FILE_NEW_RESOURCE); file_options->add_item(TTR("Show In File Manager"), FILE_SHOW_IN_EXPLORER); file_options->set_position(files->get_global_position() + p_pos); file_options->popup(); @@ -1862,7 +1882,8 @@ void FileSystemDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_file_option"), &FileSystemDock::_file_option); ClassDB::bind_method(D_METHOD("_folder_option"), &FileSystemDock::_folder_option); ClassDB::bind_method(D_METHOD("_make_dir_confirm"), &FileSystemDock::_make_dir_confirm); - ClassDB::bind_method(D_METHOD("_move_operation_confirm"), &FileSystemDock::_move_operation_confirm); + ClassDB::bind_method(D_METHOD("_resource_created"), &FileSystemDock::_resource_created); + ClassDB::bind_method(D_METHOD("_move_operation_confirm", "to_path", "overwrite"), &FileSystemDock::_move_operation_confirm, DEFVAL(false)); ClassDB::bind_method(D_METHOD("_move_with_overwrite"), &FileSystemDock::_move_with_overwrite); ClassDB::bind_method(D_METHOD("_rename_operation_confirm"), &FileSystemDock::_rename_operation_confirm); ClassDB::bind_method(D_METHOD("_duplicate_operation_confirm"), &FileSystemDock::_duplicate_operation_confirm); @@ -2087,6 +2108,11 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { make_script_dialog_text->set_title(TTR("Create Script")); add_child(make_script_dialog_text); + new_resource_dialog = memnew(CreateDialog); + add_child(new_resource_dialog); + new_resource_dialog->set_base_type("Resource"); + new_resource_dialog->connect("create", this, "_resource_created"); + updating_tree = false; initialized = false; import_dock_needs_update = false; diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index e8ab803cca..6a0c73d52e 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -47,6 +47,8 @@ #include "os/dir_access.h" #include "os/thread.h" +#include "create_dialog.h" + #include "dependency_editor.h" #include "editor_dir_dialog.h" #include "editor_file_system.h" @@ -78,7 +80,8 @@ private: FILE_NEW_FOLDER, FILE_NEW_SCRIPT, FILE_SHOW_IN_EXPLORER, - FILE_COPY_PATH + FILE_COPY_PATH, + FILE_NEW_RESOURCE }; enum FolderMenu { @@ -131,6 +134,7 @@ private: LineEdit *make_dir_dialog_text; ConfirmationDialog *overwrite_dialog; ScriptCreateDialog *make_script_dialog_text; + CreateDialog *new_resource_dialog; class FileOrFolder { public: @@ -191,6 +195,7 @@ private: void _update_favorite_dirs_list_after_move(const Map<String, String> &p_renames) const; void _update_project_settings_after_move(const Map<String, String> &p_renames) const; + void _resource_created() const; void _make_dir_confirm(); void _rename_operation_confirm(); void _duplicate_operation_confirm(); diff --git a/editor/icons/icon_GUI_tree_arrow_up.svg b/editor/icons/icon_GUI_tree_arrow_up.svg new file mode 100644 index 0000000000..4e6e8e9e29 --- /dev/null +++ b/editor/icons/icon_GUI_tree_arrow_up.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="12" + height="12" + version="1.1" + viewBox="0 0 12 12" + id="svg6" + sodipodi:docname="icon_GUI_tree_arrow_up.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1673" + inkscape:window-height="594" + id="namedview8" + showgrid="false" + inkscape:zoom="19.666667" + inkscape:cx="-4.3220338" + inkscape:cy="6.0000001" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <g + transform="rotate(180,6,526.08476)" + id="g4"> + <path + d="m 3,1045.4 3,3 3,-3" + id="path2" + inkscape:connector-curvature="0" + style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.39216003" /> + </g> +</svg> diff --git a/editor/icons/icon_animated_texture.svg b/editor/icons/icon_animated_texture.svg new file mode 100644 index 0000000000..dd039df6a7 --- /dev/null +++ b/editor/icons/icon_animated_texture.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_animated_texture.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10"> + <filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter822" + x="-0.012" + width="1.024" + y="-0.012" + height="1.024"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.07" + id="feGaussianBlur824" /> + </filter> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="836" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g4"> + <path + d="m1 1037.4v14h1.1667v-2h1.8333v2h8v-2h2v2h1v-14h-1v2h-2v-2h-8v2h-1.8333v-2zm1.1667 4h1.8333v2h-1.8333zm9.8333 0h2v2h-2zm-9.8333 4h1.8333v2h-1.8333zm9.8333 0h2v2h-2z" + fill="#cea4f1" + id="path2" + style="fill:#e0e0e0;fill-opacity:1;filter:url(#filter822)" /> + </g> +</svg> diff --git a/editor/icons/icon_expand_bottom_dock.svg b/editor/icons/icon_expand_bottom_dock.svg new file mode 100644 index 0000000000..5a1760f377 --- /dev/null +++ b/editor/icons/icon_expand_bottom_dock.svg @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_expand_bottom_dock.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1853" + inkscape:window-height="1016" + id="namedview8" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="9.4509357" + inkscape:cy="6.016355" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg6" /> + <path + style="fill:#e0e0e0" + d="M 4.2130251,4.516057 0.6774912,8.0515909 H 3.2131761 V 13.025308 H 5.2130155 V 8.0515909 H 7.7487004 L 4.2131665,4.516057 Z" + id="path829" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:connector-curvature="0" + id="path831" + d="M 11.907306,4.6119359 8.3717718,8.1474698 h 2.5356852 v 4.9737172 h 1.999839 V 8.1474698 h 2.535685 L 11.907447,4.6119359 Z" + style="fill:#e0e0e0" + sodipodi:nodetypes="ccccccccc" /> + <rect + style="fill:#e0e0e0;fill-opacity:1" + id="rect855" + width="14" + height="1.8305085" + x="1.2881356" + y="1.3700738" /> +</svg> diff --git a/editor/icons/icon_g_l_e_s_2.svg b/editor/icons/icon_g_l_e_s_2.svg new file mode 100644 index 0000000000..efc4f01e4f --- /dev/null +++ b/editor/icons/icon_g_l_e_s_2.svg @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + width="48" + height="16" + viewBox="0 0 47.999999 16" + sodipodi:docname="icon_g_l_e_s_2.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1484" + inkscape:window-height="697" + id="namedview4" + showgrid="false" + inkscape:zoom="13.520979" + inkscape:cx="20.549976" + inkscape:cy="7.9399684" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <path + inkscape:connector-curvature="0" + id="path835" + d="m 19.839879,15.499154 c -0.0652,-0.0268 -0.141743,-0.1098 -0.170113,-0.184417 -0.03304,-0.08688 -0.05158,-0.95731 -0.05158,-5.912028 V 3.1830459 l 0.108486,-0.1379162 c 0.150269,-0.1910365 0.41814,-0.1907342 0.568677,6.436e-4 l 0.10899,0.1385579 -0.01358,6.2990785 c -0.01194,6.8660953 -0.0921,5.3381383 -0.0921,5.9327083 -0.106573,0.104434 -0.315006,0.142158 -0.458762,0.08303 z M 5.3808767,14.575188 C 4.5309456,14.518738 3.6260357,14.196602 2.9750499,13.718734 2.5767564,13.42636 2.0035795,12.787236 1.747789,12.350269 1.2385669,11.480363 1.0170768,10.580508 1.0213778,9.399057 1.0293972,7.2009406 1.9726797,5.5285643 3.6891526,4.6693537 4.7813316,4.1226444 6.2246017,4.0371807 7.4330177,4.4476602 8.1309525,4.6847376 8.4685433,4.8972607 9.0207129,5.4471587 9.4063328,5.8311907 9.5338898,6.0004852 9.7108978,6.3631718 9.8335428,6.6144683 9.9681328,6.9987435 10.020175,7.2461971 10.145759,7.8433551 10.170431,7.8289765 9.0218356,7.828057 8.5307356,7.8276009 8.0769363,7.8134035 8.0133918,7.7963663 7.9392662,7.7764919 7.8757344,7.6970176 7.8361313,7.5746239 7.5012661,6.5397183 6.6297764,6.0267536 5.4889128,6.193037 4.244092,6.3744711 3.4980921,7.3344965 3.343357,8.9541432 3.2260083,10.182472 3.5434132,11.329338 4.1781352,11.97041 c 0.46237,0.466997 0.9869175,0.673904 1.7084683,0.673904 1.2025378,0 1.9439704,-0.533034 2.1862936,-1.57178 0.055989,-0.240028 0.059178,-0.324448 0.012859,-0.341503 -0.033838,-0.01246 -0.5090516,-0.02871 -1.0560342,-0.03612 L 6.0352096,10.681458 V 9.8178001 8.9541431 l 1.9890278,-0.014575 c 1.0939663,-0.00802 2.0422396,-0.00163 2.1072756,0.014201 l 0.118246,0.028779 -0.01356,2.6814549 -0.01356,2.681455 -0.7170922,0.01455 c -0.8295927,0.01682 -0.7753286,0.05076 -0.8815155,-0.55106 -0.036825,-0.208719 -0.077853,-0.379487 -0.091164,-0.379487 -0.013311,0 -0.16916,0.135437 -0.3463303,0.300972 -0.3894417,0.363866 -0.8188673,0.600316 -1.3418506,0.738852 -0.4725114,0.125166 -0.8081647,0.149449 -1.4638111,0.10591 z M 32.49721,14.469781 c -0.928547,-0.194854 -1.630354,-0.56605 -2.174913,-1.150343 -0.515384,-0.552992 -0.832054,-1.344249 -0.800629,-2.000518 l 0.01549,-0.323408 1.060826,-0.01418 1.060825,-0.01418 0.05146,0.135352 c 0.0283,0.07444 0.0517,0.198593 0.05197,0.275887 8.54e-4,0.230559 0.229434,0.649361 0.479979,0.879354 0.347226,0.318744 0.735307,0.44853 1.431019,0.478576 1.267096,0.05472 2.007349,-0.393206 1.947849,-1.178652 -0.0456,-0.601928 -0.471503,-0.860841 -2.12876,-1.294103 C 32.881626,10.103917 32.242852,9.9264243 32.07283,9.8691486 30.95902,9.4939337 30.283515,8.9537559 29.97948,8.195172 29.836139,7.8375288 29.784025,7.0484225 29.874852,6.6109088 30.100606,5.5234588 31.071976,4.6456053 32.416011,4.314394 33.01697,4.1662997 34.128873,4.156633 34.77144,4.293917 c 1.67335,0.3575071 2.584333,1.270761 2.774448,2.7813655 0.0543,0.4314615 0.0347,0.4394334 -1.080484,0.4394334 -0.521251,0 -0.9851,-0.023133 -1.038665,-0.051802 C 35.367672,7.4313026 35.307808,7.3078793 35.273143,7.1462409 35.195527,6.7843357 35.099156,6.6147944 34.849667,6.4012402 34.543832,6.1394568 34.14764,6.029069 33.515213,6.0294329 c -0.428465,2.111e-4 -0.570793,0.021517 -0.784491,0.1172346 -0.47592,0.2131691 -0.654939,0.4628549 -0.654939,0.9134748 0,0.5904894 0.225799,0.7059322 2.58195,1.3200619 1.350552,0.3520209 1.903346,0.598685 2.415601,1.0778741 0.591219,0.5530567 0.852295,1.2543747 0.796412,2.1393787 -0.07929,1.255762 -0.891416,2.255747 -2.192274,2.699402 -0.885807,0.302103 -2.21918,0.374602 -3.180262,0.172924 z M 11.476954,14.306572 c -0.0138,-0.03619 -0.019,-2.268126 -0.01158,-4.9598581 l 0.0135,-4.8940567 1.066335,-0.01419 c 0.742348,-0.00988 1.088249,0.00399 1.138458,0.045665 0.06009,0.049873 0.07211,0.7135739 0.07211,3.9791612 v 3.9193056 h 2.293081 c 1.756352,0 2.314103,0.01538 2.382892,0.06567 0.07993,0.05845 0.08822,0.166396 0.07543,0.981428 l -0.01437,0.915757 -3.495384,0.01345 c -2.768549,0.0107 -3.500605,-1.69e-4 -3.520473,-0.05234 z m 10.193414,0.0026 c -0.04842,-0.04842 -0.06297,-1.193838 -0.06236,-4.9074882 4.61e-4,-2.6643823 0.01959,-4.8739347 0.04256,-4.9101166 0.03301,-0.05201 0.813774,-0.062971 3.728627,-0.052342 l 3.686862,0.013441 V 5.3948518 6.337024 l -2.5648,0.026171 -2.5648,0.026172 v 0.9421438 0.9421716 l 2.313597,0.026171 c 1.548367,0.017515 2.332217,0.044804 2.36989,0.082507 0.03673,0.036745 0.05127,0.3461819 0.04183,0.889829 l -0.01446,0.8334926 -2.355428,0.02617 -2.355429,0.02617 v 1.0992 1.099199 l 2.617143,0.0274 c 1.439428,0.01507 2.623562,0.03274 2.63141,0.03926 0.0078,0.0065 0.0078,0.441727 0,0.967118 l -0.01427,0.955257 -3.718613,0.01343 c -2.848812,0.01027 -3.733388,-0.0013 -3.781773,-0.04973 z m 17.753791,-0.378679 c -0.04061,-0.105824 0.0759,-0.828141 0.198829,-1.232689 0.288088,-0.948035 0.88431,-1.590368 2.319422,-2.498804 1.100465,-0.6965999 1.86374,-1.2293374 2.17747,-1.5198007 0.515251,-0.477031 0.731074,-1.0868265 0.620161,-1.7522036 -0.126353,-0.7579473 -0.607483,-1.1395723 -1.436711,-1.1395723 -0.930964,0 -1.401324,0.4507271 -1.481617,1.4197789 l -0.03634,0.4383927 h -1.099202 -1.099196 l -0.01524,-0.3725124 c -0.03408,-0.8332648 0.288934,-1.6827799 0.855164,-2.2490093 0.399774,-0.3997734 1.09283,-0.7574546 1.70958,-0.8822975 0.580047,-0.1174131 1.71432,-0.1077309 2.332892,0.019914 1.258364,0.2596698 2.203978,1.0560413 2.520675,2.1228587 0.104477,0.3519131 0.117355,0.4871812 0.09657,1.0144101 -0.01959,0.4962935 -0.04847,0.667451 -0.157022,0.9292002 -0.313508,0.7560998 -0.900391,1.3802206 -1.888823,2.0086882 -1.507571,0.958543 -1.915442,1.243322 -2.230808,1.557578 -0.26352,0.262604 -0.32016,0.345357 -0.261709,0.382352 0.04123,0.0261 1.061246,0.04757 2.280484,0.04802 1.96272,7.11e-4 2.209393,0.0099 2.237659,0.0836 0.01749,0.04554 0.03178,0.408703 0.03178,0.807033 0,0.398331 -0.0143,0.761495 -0.03178,0.807033 -0.0286,0.07445 -0.414152,0.0828 -3.822672,0.0828 -3.236429,0 -3.795092,-0.01093 -3.819578,-0.07475 z" + style="fill:#5586a4;fill-opacity:1;stroke-width:0.05234285" + sodipodi:nodetypes="ccscccccccccsscsscccccscccsccsccccccccccssscccccccccccccccscsccsccccsssscscccccccscscscccccccscccccccscccccccscccccccccccscccccsssccccccccscscc" /> + <path + style="fill:#000000;stroke-width:1.06666672" + d="" + id="path819" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000;stroke-width:1.06666672" + d="" + id="path817" + inkscape:connector-curvature="0" /> +</svg> diff --git a/editor/icons/icon_g_l_e_s_3.svg b/editor/icons/icon_g_l_e_s_3.svg new file mode 100644 index 0000000000..dfa3c26b38 --- /dev/null +++ b/editor/icons/icon_g_l_e_s_3.svg @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + width="48" + height="16" + viewBox="0 0 47.999999 16" + sodipodi:docname="icon_g_l_e_s_3.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1484" + inkscape:window-height="697" + id="namedview4" + showgrid="false" + inkscape:zoom="13.520979" + inkscape:cx="20.549976" + inkscape:cy="7.9399684" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <path + style="fill:#6aa455;fill-opacity:1;stroke-width:0.05234285" + d="M 20.011719 2.9023438 C 19.90715 2.9022255 19.801697 2.9494036 19.726562 3.0449219 L 19.619141 3.1835938 L 19.619141 9.4023438 C 19.619141 14.357062 19.636882 15.227573 19.669922 15.314453 C 19.698292 15.38907 19.774644 15.4732 19.839844 15.5 C 19.9836 15.559128 20.192255 15.52045 20.298828 15.416016 C 20.298828 14.821446 20.378685 16.35047 20.390625 9.484375 L 20.404297 3.1835938 L 20.294922 3.0449219 C 20.219653 2.949233 20.116287 2.902462 20.011719 2.9023438 z M 33.578125 4.1972656 C 33.144709 4.2010336 32.716495 4.240406 32.416016 4.3144531 C 31.071981 4.6456644 30.100754 5.5238781 29.875 6.6113281 C 29.784173 7.0488418 29.835175 7.8376693 29.978516 8.1953125 C 30.282551 8.9538964 30.958456 9.4939257 32.072266 9.8691406 C 32.242288 9.9264163 32.881487 10.104023 33.492188 10.263672 C 35.149445 10.696934 35.575494 10.956666 35.621094 11.558594 C 35.680594 12.34404 34.940924 12.791048 33.673828 12.736328 C 32.978116 12.706282 32.589413 12.576557 32.242188 12.257812 C 31.991643 12.02782 31.762573 11.609465 31.761719 11.378906 C 31.761449 11.301612 31.739238 11.176002 31.710938 11.101562 L 31.658203 10.966797 L 30.597656 10.980469 L 29.537109 10.996094 L 29.521484 11.318359 C 29.490059 11.974628 29.806882 12.767321 30.322266 13.320312 C 30.866825 13.904606 31.5695 14.275849 32.498047 14.470703 C 33.459129 14.672381 34.791927 14.598978 35.677734 14.296875 C 36.978592 13.85322 37.789851 12.853418 37.869141 11.597656 C 37.925024 10.712652 37.665438 10.012041 37.074219 9.4589844 C 36.561964 8.9797953 36.008755 8.7328803 34.658203 8.3808594 C 32.302052 7.7667297 32.076172 7.6510363 32.076172 7.0605469 C 32.076172 6.609927 32.254549 6.3596535 32.730469 6.1464844 C 32.944167 6.0507668 33.08716 6.029508 33.515625 6.0292969 C 34.148052 6.028933 34.543774 6.1386072 34.849609 6.4003906 C 35.099098 6.6139448 35.195822 6.7845792 35.273438 7.1464844 C 35.308103 7.3081228 35.366714 7.4312793 35.425781 7.4628906 C 35.479346 7.4915596 35.943593 7.515625 36.464844 7.515625 C 37.580028 7.515625 37.599222 7.5076334 37.544922 7.0761719 C 37.354807 5.5655674 36.444834 4.6504759 34.771484 4.2929688 C 34.450201 4.2243268 34.011541 4.1934977 33.578125 4.1972656 z M 5.5175781 4.1992188 C 4.8691862 4.2376134 4.2355426 4.3965672 3.6894531 4.6699219 C 1.9729802 5.5291325 1.0295038 7.2003211 1.0214844 9.3984375 C 1.0171834 10.579889 1.2388248 11.479703 1.7480469 12.349609 C 2.0038374 12.786576 2.5763159 13.426376 2.9746094 13.71875 C 3.6255952 14.196618 4.5309283 14.517769 5.3808594 14.574219 C 6.0365058 14.617758 6.3712386 14.593916 6.84375 14.46875 C 7.3667333 14.330214 7.7980583 14.094335 8.1875 13.730469 C 8.3646703 13.564934 8.5198921 13.429688 8.5332031 13.429688 C 8.5465141 13.429688 8.588175 13.599875 8.625 13.808594 C 8.7311869 14.410414 8.6762667 14.376195 9.5058594 14.359375 L 10.222656 14.345703 L 10.236328 11.664062 L 10.25 8.9824219 L 10.130859 8.953125 C 10.065823 8.937294 9.1174038 8.9314331 8.0234375 8.9394531 L 6.0351562 8.9550781 L 6.0351562 9.8183594 L 6.0351562 10.681641 L 7.0292969 10.695312 C 7.5762795 10.702722 8.0520995 10.718009 8.0859375 10.730469 C 8.1322565 10.747524 8.1282546 10.832238 8.0722656 11.072266 C 7.8299424 12.111012 7.0892565 12.644531 5.8867188 12.644531 C 5.1651679 12.644531 4.6401044 12.4377 4.1777344 11.970703 C 3.5430124 11.329631 3.2264013 10.183407 3.34375 8.9550781 C 3.4984851 7.3354314 4.2434605 6.3747935 5.4882812 6.1933594 C 6.6291449 6.027076 7.5010723 6.5393131 7.8359375 7.5742188 C 7.8755406 7.6966124 7.9395463 7.7770006 8.0136719 7.796875 C 8.0772164 7.8139122 8.5303844 7.8276689 9.0214844 7.828125 C 10.17008 7.8290445 10.145115 7.8432518 10.019531 7.2460938 C 9.967489 6.9986401 9.8335825 6.6145778 9.7109375 6.3632812 C 9.5339295 6.0005947 9.4071043 5.8312976 9.0214844 5.4472656 C 8.4693148 4.8973676 8.1315285 4.684343 7.4335938 4.4472656 C 6.8293858 4.2420259 6.16597 4.1608241 5.5175781 4.1992188 z M 42.03125 4.2617188 L 41.537109 4.4335938 C 40.933232 4.6433398 40.657695 4.8014669 40.300781 5.1386719 C 39.969225 5.4519119 39.761404 5.8046136 39.621094 6.2910156 C 39.502474 6.7023596 39.433137 7.3494687 39.498047 7.4492188 C 39.531044 7.4999487 39.783863 7.5127831 40.576172 7.5019531 L 41.611328 7.4863281 L 41.691406 7.0703125 C 41.808812 6.4678105 41.927622 6.2685471 42.265625 6.0957031 C 42.510424 5.9705181 42.604184 5.953125 43.019531 5.953125 C 43.426321 5.953125 43.533311 5.9721266 43.765625 6.0878906 C 44.253715 6.3311276 44.455638 6.904517 44.273438 7.53125 C 44.105442 8.109131 43.697334 8.363965 42.791016 8.453125 C 42.521874 8.479605 42.288464 8.51424 42.271484 8.53125 C 42.225224 8.577174 42.232777 9.7874244 42.279297 9.8339844 C 42.301291 9.8559744 42.598053 9.8907794 42.939453 9.9121094 C 43.836652 9.9681724 44.239534 10.166191 44.525391 10.691406 C 44.916028 11.409137 44.561069 12.318315 43.787109 12.582031 C 43.476088 12.688024 42.767292 12.688624 42.470703 12.583984 C 42.00204 12.418633 41.795632 12.174325 41.642578 11.597656 L 41.560547 11.285156 L 40.46875 11.285156 L 39.376953 11.285156 L 39.361328 11.527344 C 39.352678 11.660649 39.384791 11.918152 39.431641 12.099609 C 39.739925 13.294376 40.783209 14.156157 42.212891 14.396484 C 42.284425 14.408514 42.682741 14.422181 43.097656 14.425781 C 44.074936 14.434074 44.653306 14.320796 45.308594 13.996094 C 46.07786 13.61492 46.610204 13.058412 46.847656 12.382812 C 47.087412 11.700564 47.08166 10.999125 46.833984 10.333984 C 46.695621 9.962377 46.130198 9.3782416 45.6875 9.1503906 C 45.523031 9.0657476 45.386773 8.9810006 45.386719 8.9628906 C 45.386654 8.9447846 45.539488 8.8195027 45.724609 8.6835938 C 46.129744 8.3861558 46.390215 8.064434 46.53125 7.6875 C 46.963216 6.532963 46.370297 5.2063894 45.166016 4.6308594 C 44.482944 4.3044164 44.23589 4.2611938 43.072266 4.2617188 L 42.03125 4.2617188 z M 12.544922 4.4375 L 11.478516 4.453125 L 11.464844 9.3476562 C 11.457424 12.039388 11.462763 14.270451 11.476562 14.306641 C 11.49643 14.358812 12.229498 14.370075 14.998047 14.359375 L 18.492188 14.345703 L 18.507812 13.429688 C 18.520602 12.614656 18.511571 12.507669 18.431641 12.449219 C 18.362852 12.398929 17.80518 12.382812 16.048828 12.382812 L 13.755859 12.382812 L 13.755859 8.4628906 C 13.755859 5.1973033 13.743684 4.534248 13.683594 4.484375 C 13.633385 4.4427 13.28727 4.42762 12.544922 4.4375 z M 25.378906 4.4394531 C 22.464053 4.4288241 21.683401 4.4401775 21.650391 4.4921875 C 21.627421 4.5283694 21.607883 6.7379615 21.607422 9.4023438 C 21.606812 13.115994 21.621502 14.260174 21.669922 14.308594 C 21.718307 14.357024 22.60236 14.369645 25.451172 14.359375 L 29.169922 14.345703 L 29.185547 13.390625 C 29.193347 12.865234 29.193347 12.430328 29.185547 12.423828 C 29.177699 12.417308 27.992162 12.399836 26.552734 12.384766 L 23.935547 12.355469 L 23.935547 11.257812 L 23.935547 10.158203 L 26.291016 10.132812 L 28.646484 10.105469 L 28.662109 9.2714844 C 28.671549 8.7278373 28.655871 8.4195575 28.619141 8.3828125 C 28.581468 8.3451095 27.798367 8.3182962 26.25 8.3007812 L 23.935547 8.2734375 L 23.935547 7.3320312 L 23.935547 6.3886719 L 26.501953 6.3632812 L 29.066406 6.3378906 L 29.066406 5.3945312 L 29.066406 4.453125 L 25.378906 4.4394531 z " + id="path3424" /> + <path + style="fill:#000000;stroke-width:1.06666672" + d="" + id="path819" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000;stroke-width:1.06666672" + d="" + id="path817" + inkscape:connector-curvature="0" /> +</svg> diff --git a/editor/icons/icon_new_root.svg b/editor/icons/icon_new_root.svg new file mode 100644 index 0000000000..51c79f038d --- /dev/null +++ b/editor/icons/icon_new_root.svg @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_new_root.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1474" + inkscape:window-height="755" + id="namedview10" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="9.9306919" + inkscape:cy="7.2213369" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <path + style="fill:#e0e0e0" + d="m 2,4.7813475 v 2.0494746 c -0.6177049,0.3566305 -0.998733,1.0152377 -1,1.7285 0,1.1045694 0.8954305,1.9999999 2,1.9999999 0.7139771,-5.54e-4 1.3735116,-0.381678 1.7305,-0.9999995 h 1.3545593 c 0.3566306,0.6177035 1.0152377,0.9987325 1.7285,0.9999995 1.1045696,0 1.9999996,-0.8954305 1.9999996,-1.9999999 0,-1.1045695 -0.89543,-2 -1.9999996,-2 -0.7139771,5.537e-4 -1.3735116,0.3816774 -1.7305,1 H 4.7285 C 4.5537191,7.2563119 4.3025219,7.0044423 3.99998,6.8288521 V 4.7793775 C 3.4615087,4.8084067 2.7017179,4.8161838 2,4.7813475 Z" + id="path2" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccscccccc" /> + <path + style="fill:#e0e0e0" + d="m 6.8474576,9.6288045 v 1.2020165 c -0.617705,0.35663 -0.998733,1.015237 -1,1.7285 0,1.104569 0.89543,2 2,2 0.713977,-5.54e-4 1.373512,-0.381678 1.7305,-1 h 1.2867634 c 0.35663,0.617704 1.015237,0.998733 1.7285,1 1.104569,0 1.999999,-0.895431 1.999999,-2 0,-1.10457 -0.89543,-2 -1.999999,-2 -0.713977,5.53e-4 -1.373512,0.381677 -1.7305,1 H 9.5759576 c -0.174781,-0.303011 -0.425978,-0.55488 -0.72852,-0.73047 V 9.6268345 c 0,0 -1.264363,0.03681 -1.99998,0.002 z" + id="path827" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccsccccccc" /> + <path + sodipodi:nodetypes="ccccccc" + inkscape:connector-curvature="0" + id="path829" + d="m 2.7966098,1.3559322 c -1.104569,0 -2.00000003,0.8954305 -2.00000003,2 5.54e-4,0.7139771 0.38167803,1.3735116 1.00000003,1.7305 0.757716,0.266212 0.949133,0.2840609 1.99998,-0.00197 0.617705,-0.3566306 0.998733,-1.0152377 1,-1.7285 0,-1.1045695 -0.89543,-2 -2,-2 z" + style="fill:#84ffb1;fill-opacity:1" /> +</svg> diff --git a/editor/icons/icon_shrink_bottom_dock.svg b/editor/icons/icon_shrink_bottom_dock.svg new file mode 100644 index 0000000000..c1e8c1bfdb --- /dev/null +++ b/editor/icons/icon_shrink_bottom_dock.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_shrink_bottom_dock.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1853" + inkscape:window-height="1016" + id="namedview8" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="9.4509357" + inkscape:cy="6.016355" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg6" /> + <path + style="fill:#e0e0e0" + d="M 11.907447,9.9752038 15.442981,6.4396699 H 12.907296 V 1.4659528 h -1.999839 l 0,4.9737171 -2.5356852,0 3.5355342,3.5355339 z" + id="path829" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccc" /> + <path + inkscape:connector-curvature="0" + id="path831" + d="M 4.2131662,9.8793249 7.7487004,6.343791 H 5.2130152 V 1.3700738 H 3.2131762 V 6.343791 h -2.535685 l 3.535534,3.5355339 z" + style="fill:#e0e0e0" + sodipodi:nodetypes="ccccccccc" /> + <rect + style="fill:#e0e0e0;fill-opacity:1" + id="rect855" + width="14" + height="1.8305085" + x="-14.832336" + y="-13.121187" + transform="scale(-1)" /> +</svg> diff --git a/editor/icons/icon_soft_body.svg b/editor/icons/icon_soft_body.svg new file mode 100644 index 0000000000..9930026b61 --- /dev/null +++ b/editor/icons/icon_soft_body.svg @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="icon_soft_body.svg"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1027" + id="namedview10" + showgrid="false" + inkscape:zoom="18.792233" + inkscape:cx="2.8961304" + inkscape:cy="4.3816933" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /> + <path + style="opacity:1;fill:#fc9c9c;fill-opacity:0.99607843;fill-rule:nonzero;stroke:none;stroke-width:1.42799997;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m 2.3447105,1.6091897 c -0.011911,1.9816766 -1.4168958,3.9344766 0,5.9495986 1.4168957,2.0151221 0,6.6693597 0,6.6693597 l 10.9510055,0 c 0,0 1.780829,-4.4523824 0,-6.489075 -1.780829,-2.0366925 -0.183458,-4.119112 0,-6.1298833 z m 1.8894067,0.7549031 7.4390658,0 c -0.431995,1.5996085 -1.62289,4.0426807 0,5.3749802 1.622888,1.3322996 0,5.887932 0,5.887932 l -7.4390658,0 c 0,0 1.3903413,-4.3680495 0,-5.9780743 -1.3903412,-1.6100247 -0.3951213,-3.7149271 0,-5.2848379 z" + id="rect4142" + inkscape:connector-curvature="0" + sodipodi:nodetypes="czcczcccczcczc" /> +</svg> diff --git a/editor/icons/icon_visual_shader.svg b/editor/icons/icon_visual_shader.svg new file mode 100644 index 0000000000..e2c4f128b2 --- /dev/null +++ b/editor/icons/icon_visual_shader.svg @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg20" + sodipodi:docname="icon_visual_shader.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata26"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs24" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="640" + inkscape:window-height="480" + id="namedview22" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg20" /> + <g + id="g18" + transform="matrix(1,0,0,0.50605327,0,0.49394673)"> + <path + d="M 2,1 C 1.44774,1.0001 1.00006,1.4477 1,2 v 12 c 5.52e-5,0.5523 0.44774,0.9999 1,1 h 12 c 0.55226,-10e-5 0.99994,-0.4477 1,-1 V 6 L 10,1 Z m 1,2 h 6 v 3 c 0,0.554 0.44599,1 1,1 h 3 v 6 H 3 Z" + id="path2" + inkscape:connector-curvature="0" + style="fill:#e0e0e0" /> + <path + d="m 10,11 h 2 v 1 h -2 z" + id="path4" + inkscape:connector-curvature="0" + style="fill:#9f70ff" /> + <path + d="M 4,6 H 6 V 7 H 4 Z" + id="path6" + inkscape:connector-curvature="0" + style="fill:#ffeb70" /> + <path + d="m 8,8 h 4 V 9 H 8 Z" + id="path8" + inkscape:connector-curvature="0" + style="fill:#9dff70" /> + <path + d="M 7,6 H 8 V 7 H 7 Z" + id="path10" + inkscape:connector-curvature="0" + style="fill:#70deff" /> + <path + d="m 4,11 h 5 v 1 H 4 Z" + id="path12" + inkscape:connector-curvature="0" + style="fill:#ff70ac" /> + <path + d="M 4,4 H 7 V 5 H 4 Z" + id="path14" + inkscape:connector-curvature="0" + style="fill:#ff7070" /> + <path + d="M 4,8 H 7 V 9 H 4 Z" + id="path16" + inkscape:connector-curvature="0" + style="fill:#70ffb9" /> + </g> + <path + inkscape:connector-curvature="0" + style="fill:#e0e0e0" + d="m 2.8642321,9 v 6 h 2 a 3,3 0 0 0 3,-3 V 9 h -2 v 3 a 1,1 0 0 1 -1,1 V 9 Z" + id="path1394" /> + <path + inkscape:connector-curvature="0" + style="fill:#e0e0e0" + d="m 10.864232,9 a 2,2 0 0 0 -1.7323999,1 2,2 0 0 0 0,2 2,2 0 0 0 1.7323999,1 H 8.8642321 v 2 h 1.9999999 a 2,2 0 0 0 1.7324,-1 2,2 0 0 0 0,-2 2,2 0 0 0 -1.7324,-1 h 2 V 9 Z" + id="path30" /> +</svg> diff --git a/editor/icons/icon_vulkan.svg b/editor/icons/icon_vulkan.svg new file mode 100644 index 0000000000..1d5fed0305 --- /dev/null +++ b/editor/icons/icon_vulkan.svg @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + width="48" + height="16" + viewBox="0 0 47.999999 16" + sodipodi:docname="icon_vulkan.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1853" + inkscape:window-height="1016" + id="namedview4" + showgrid="false" + inkscape:zoom="10.24" + inkscape:cx="9.4970674" + inkscape:cy="11.192118" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="g8" /> + <path + style="fill:#000000;stroke-width:1.06666672" + d="" + id="path819" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000;stroke-width:1.06666672" + d="" + id="path817" + inkscape:connector-curvature="0" /> + <g + transform="matrix(0.04333868,0,0,0.04333868,-4.0493236,-3.7704963)" + id="g8"> + <path + inkscape:connector-curvature="0" + d="m 724.1,432.41989 h -40.6 c 0,0 0,-99 0,-129.7 13,7.2 30.1,20.5 40.6,33.3 z" + id="path10" + style="fill:#e6555a;fill-opacity:1" /> + <g + id="g12" + style="fill:#e6555a;fill-opacity:1" + transform="translate(0,47.319882)"> + <path + inkscape:connector-curvature="0" + d="m 381.8,385.1 h -50.6 l -66,-204 h 46 l 45.4,143.5 h 0.6 l 46,-143.5 h 46.3 z" + id="path14" + style="fill:#e6555a;fill-opacity:1" /> + <path + inkscape:connector-curvature="0" + d="M 585.5,385.1 H 546.9 V 364.5 H 546 c -5.1,8.6 -11.8,14.8 -20,18.6 -8.2,3.8 -16.6,5.7 -25.1,5.7 -10.9,0 -19.8,-1.4 -26.7,-4.3 -7,-2.9 -12.4,-6.9 -16.4,-12.1 -4,-5.2 -6.8,-11.6 -8.4,-19.1 -1.6,-7.5 -2.4,-15.9 -2.4,-25 v -90.9 h 40.6 v 83.4 c 0,12.2 1.9,21.3 5.7,27.3 3.8,6 10.6,9 20.3,9 11,0 19.1,-3.3 24,-9.9 5,-6.6 7.4,-17.4 7.4,-32.4 v -77.4 h 40.6 v 147.7 z" + id="path16" + style="fill:#e6555a;fill-opacity:1" /> + </g> + <polygon + points="730.8,296.2 730.7,290.5 781.9,237.3 829.9,237.3 774.2,291.6 836.2,385.1 787,385.1 " + id="polygon18" + style="fill:#e6555a;fill-opacity:1" + transform="translate(0,47.319882)" /> + <path + inkscape:connector-curvature="0" + d="m 843.6,330.11989 c 0.6,-9.5 3,-17.4 7.2,-23.7 4.2,-6.3 9.5,-11.3 16,-15.1 6.5,-3.8 13.8,-6.5 21.9,-8.1 8.1,-1.6 16.2,-2.4 24.4,-2.4 7.4,0 15,0.5 22.6,1.6 7.6,1.1 14.6,3.1 20.9,6.1 6.3,3.1 11.4,7.3 15.4,12.7 4,5.4 6,12.6 6,21.6 v 76.9 c 0,6.7 0.4,13.1 1.1,19.1 0.8,6.1 2.1,10.7 4,13.7 h -41.2 c -0.8,-2.3 -1.4,-4.6 -1.9,-7 -0.5,-2.4 -0.8,-4.8 -1,-7.3 -6.5,6.7 -14.1,11.3 -22.9,14 -8.8,2.7 -17.7,4 -26.9,4 -7,0 -13.6,-0.9 -19.7,-2.6 -6.1,-1.7 -11.4,-4.4 -16,-8 -4.6,-3.6 -8.2,-8.2 -10.7,-13.7 -2.6,-5.5 -3.9,-12.1 -3.9,-19.7 0,-8.4 1.5,-15.3 4.4,-20.7 3,-5.4 6.8,-9.8 11.4,-13 4.7,-3.2 10,-5.7 16,-7.3 6,-1.6 12,-2.9 18.1,-3.9 6.1,-0.9 12.1,-1.7 18,-2.3 5.9,-0.6 11.1,-1.4 15.7,-2.6 4.6,-1.1 8.2,-2.8 10.9,-5 2.7,-2.2 3.9,-5.4 3.7,-9.6 0,-4.4 -0.7,-7.9 -2.2,-10.4 -1.4,-2.6 -3.3,-4.6 -5.7,-6 -2.4,-1.4 -5.1,-2.4 -8.3,-2.9 -3.1,-0.5 -6.5,-0.7 -10.1,-0.7 -8,0 -14.3,1.7 -18.9,5.1 -4.6,3.4 -7.2,9.1 -8,17.1 h -40.3 z m 93.8,30 c -1.7,1.5 -3.9,2.7 -6.4,3.6 -2.6,0.9 -5.3,1.6 -8.3,2.2 -2.9,0.6 -6,1 -9.3,1.4 -3.2,0.4 -6.5,0.9 -9.7,1.4 -3,0.6 -6,1.3 -9,2.3 -3,1 -5.5,2.2 -7.7,3.9 -2.2,1.6 -4,3.7 -5.3,6.1 -1.3,2.5 -2,5.6 -2,9.4 0,3.6 0.7,6.7 2,9.1 1.3,2.5 3.1,4.4 5.4,5.9 2.3,1.4 5,2.4 8,3 3.1,0.6 6.2,0.9 9.4,0.9 8,0 14.2,-1.3 18.6,-4 4.4,-2.7 7.6,-5.9 9.7,-9.6 2.1,-3.7 3.4,-7.5 3.9,-11.3 0.5,-3.8 0.7,-6.9 0.7,-9.1 z" + id="path20" + style="fill:#e6555a;fill-opacity:1" /> + <path + inkscape:connector-curvature="0" + d="m 1004.2,284.61989 h 38.6 v 20.6 h 0.9 c 5.1,-8.6 11.8,-14.8 20,-18.7 8.2,-3.9 16.6,-5.9 25.1,-5.9 10.9,0 19.8,1.5 26.7,4.4 7,3 12.4,7.1 16.4,12.3 4,5.2 6.8,11.6 8.4,19.1 1.6,7.5 2.4,15.9 2.4,25 v 90.9 h -40.6 v -83.4 c 0,-12.2 -1.9,-21.3 -5.7,-27.3 -3.8,-6 -10.6,-9 -20.3,-9 -11,0 -19,3.3 -24,9.9 -5,6.6 -7.4,17.4 -7.4,32.4 v 77.4 h -40.6 v -147.7 z" + id="path22" + style="fill:#e6555a;fill-opacity:1" /> + <g + id="g24" + style="fill:#e6555a;fill-opacity:1" + transform="translate(0,47.319882)"> + <path + inkscape:connector-curvature="0" + d="M 612.4,211.8 V 385 H 653 V 234.2 c -13.1,-8 -26.6,-15.5 -40.6,-22.4 z" + id="path26" + style="fill:#e6555a;fill-opacity:1" /> + </g> + <path + inkscape:connector-curvature="0" + d="m 198.4,266.51989 c 23.5,-68.9 164.2,-94.2 314.1,-56.4 90,22.6 163.5,66.5 211.5,109.9 -21.7,-57.6 -127.3,-139.6 -272.8,-167.7 -164.5,-31.8 -326.7,-3.9 -346.8,69.1 -14.5,52.7 49.2,114.5 147.7,156.7 -44.3,-35.8 -65.8,-76 -53.7,-111.6 z" + id="path28" + style="fill:#e6555a;fill-opacity:1" /> + <g + id="g30" + style="fill:#e6555a;fill-opacity:1" + transform="translate(0,47.319882)"> + <path + inkscape:connector-curvature="0" + d="M 724.2,247.6 V 181 h -40.6 v 20.2 c 17.3,15.5 31,31.2 40.6,46.4 z" + id="path32" + style="fill:#e6555a;fill-opacity:1" /> + </g> + </g> +</svg> diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index eb0bc0f782..f2f4328cd2 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -1711,14 +1711,14 @@ void EditorSceneImporterGLTF::_generate_node(GLTFState &state, int p_node, Node #endif for (int i = 0; i < n->children.size(); i++) { if (state.nodes[n->children[i]]->joints.size()) { - _generate_bone(state, n->children[i], skeletons, Vector<int>(), node); + _generate_bone(state, n->children[i], skeletons, node); } else { _generate_node(state, n->children[i], node, p_owner, skeletons); } } } -void EditorSceneImporterGLTF::_generate_bone(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, const Vector<int> &p_parent_bones, Node *p_parent_node) { +void EditorSceneImporterGLTF::_generate_bone(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, Node *p_parent_node) { ERR_FAIL_INDEX(p_node, state.nodes.size()); if (state.skeleton_nodes.has(p_node)) { @@ -1733,30 +1733,28 @@ void EditorSceneImporterGLTF::_generate_bone(GLTFState &state, int p_node, Vecto } GLTFNode *n = state.nodes[p_node]; - Vector<int> parent_bones; for (int i = 0; i < n->joints.size(); i++) { - ERR_FAIL_COND(n->joints[i].skin < 0); + const int skin = n->joints[i].skin; + ERR_FAIL_COND(skin < 0); - int bone_index = n->joints[i].bone; + Skeleton *s = skeletons[skin]; + const GLTFNode *gltf_bone_node = state.nodes[state.skins[skin].bones[n->joints[i].bone].node]; + const String bone_name = gltf_bone_node->name; + const int parent = gltf_bone_node->parent; + const int parent_index = s->find_bone(state.nodes[parent]->name); - Skeleton *s = skeletons[n->joints[i].skin]; - while (s->get_bone_count() <= bone_index) { - s->add_bone("Bone " + itos(s->get_bone_count())); - } - - if (p_parent_bones.size()) { - s->set_bone_parent(bone_index, p_parent_bones[i]); - } - s->set_bone_rest(bone_index, state.skins[n->joints[i].skin].bones[n->joints[i].bone].inverse_bind.affine_inverse()); + s->add_bone(bone_name); + const int bone_index = s->find_bone(bone_name); + s->set_bone_parent(bone_index, parent_index); + s->set_bone_rest(bone_index, state.skins[skin].bones[n->joints[i].bone].inverse_bind.affine_inverse()); n->godot_nodes.push_back(s); n->joints[i].godot_bone_index = bone_index; - parent_bones.push_back(bone_index); } for (int i = 0; i < n->children.size(); i++) { - _generate_bone(state, n->children[i], skeletons, parent_bones, p_parent_node); + _generate_bone(state, n->children[i], skeletons, p_parent_node); } } @@ -2070,7 +2068,7 @@ Spatial *EditorSceneImporterGLTF::_generate_scene(GLTFState &state, int p_bake_f } for (int i = 0; i < state.root_nodes.size(); i++) { if (state.nodes[state.root_nodes[i]]->joints.size()) { - _generate_bone(state, state.root_nodes[i], skeletons, Vector<int>(), root); + _generate_bone(state, state.root_nodes[i], skeletons, root); } else { _generate_node(state, state.root_nodes[i], root, root, skeletons); } diff --git a/editor/import/editor_scene_importer_gltf.h b/editor/import/editor_scene_importer_gltf.h index 088036ce75..e8f3bdff62 100644 --- a/editor/import/editor_scene_importer_gltf.h +++ b/editor/import/editor_scene_importer_gltf.h @@ -311,7 +311,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Vector<Basis> _decode_accessor_as_basis(GLTFState &state, int p_accessor, bool p_for_vertex); Vector<Transform> _decode_accessor_as_xform(GLTFState &state, int p_accessor, bool p_for_vertex); - void _generate_bone(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, const Vector<int> &p_parent_bones, Node *p_parent_node); + void _generate_bone(GLTFState &state, int p_node, Vector<Skeleton *> &skeletons, Node *p_parent_node); void _generate_node(GLTFState &state, int p_node, Node *p_parent, Node *p_owner, Vector<Skeleton *> &skeletons); void _import_animation(GLTFState &state, AnimationPlayer *ap, int index, int bake_fps, Vector<Skeleton *> skeletons); diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index 0d0b12c911..43baabe2f5 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -140,6 +140,7 @@ void InspectorDock::_load_resource(const String &p_type) { void InspectorDock::_resource_file_selected(String p_file) { RES res = ResourceLoader::load(p_file); + if (res.is_null()) { warning_dialog->get_ok()->set_text("Ugh"); warning_dialog->set_text(TTR("Failed to load resource.")); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 3efb2736b5..c00ad451fa 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -246,7 +246,7 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { Point2 instance_pos = graph->get_scroll_ofs() + graph->get_size() * 0.5; - anode->set_position(instance_pos); + anode->set_position(instance_pos / EDSCALE); String base_name = add_options[p_idx].name; int base = 1; diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp new file mode 100644 index 0000000000..ddb03d0250 --- /dev/null +++ b/editor/plugins/audio_stream_editor_plugin.cpp @@ -0,0 +1,284 @@ +/*************************************************************************/ +/* audio_stream_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "audio_stream_editor_plugin.h" + +#include "editor/editor_settings.h" +#include "io/resource_loader.h" +#include "project_settings.h" + +void AudioStreamEditor::_notification(int p_what) { + + if (p_what == NOTIFICATION_READY) { + AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", this, "_preview_changed"); + } + + if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { + _play_button->set_icon(get_icon("MainPlay", "EditorIcons")); + _stop_button->set_icon(get_icon("Stop", "EditorIcons")); + _preview->set_frame_color(get_color("dark_color_2", "Editor")); + set_frame_color(get_color("dark_color_1", "Editor")); + + _indicator->update(); + _preview->update(); + } + + if (p_what == NOTIFICATION_PROCESS) { + _current = _player->get_playback_position(); + _indicator->update(); + _preview->update(); + } + + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + if (!is_visible_in_tree()) { + _stop(); + } + } +} + +void AudioStreamEditor::_draw_preview() { + Rect2 rect = _preview->get_rect(); + Size2 size = get_size(); + + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); + float preview_len = preview->get_length(); + + Vector<Vector2> lines; + lines.resize(size.width * 2); + + for (int i = 0; i < size.width; i++) { + + float ofs = i * preview_len / size.width; + float ofs_n = (i + 1) * preview_len / size.width; + float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5; + float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5; + + int idx = i; + lines[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y); + lines[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y); + } + + Vector<Color> color; + color.push_back(get_color("contrast_color_2", "Editor")); + + VS::get_singleton()->canvas_item_add_multiline(_preview->get_canvas_item(), lines, color); +} + +void AudioStreamEditor::_preview_changed(ObjectID p_which) { + + if (stream.is_valid() && stream->get_instance_id() == p_which) { + _preview->update(); + } +} + +void AudioStreamEditor::_changed_callback(Object *p_changed, const char *p_prop) { + + if (!is_visible()) + return; + update(); +} + +void AudioStreamEditor::_play() { + + if (_player->is_playing()) { + _player->stop(); + _play_button->set_icon(get_icon("MainPlay", "EditorIcons")); + set_process(false); + } else { + _player->play(_current); + _play_button->set_icon(get_icon("Pause", "EditorIcons")); + set_process(true); + } +} + +void AudioStreamEditor::_stop() { + + _player->stop(); + _on_finished(); +} + +void AudioStreamEditor::_on_finished() { + + _play_button->set_icon(get_icon("MainPlay", "EditorIcons")); + _current = 0; + _indicator->update(); + set_process(false); +} + +void AudioStreamEditor::_draw_indicator() { + + if (!stream.is_valid()) { + return; + } + + Rect2 rect = _preview->get_rect(); + float len = stream->get_length(); + float ofs_x = _current / len * rect.size.width; + _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), get_color("accent_color", "Editor"), 1); + + _current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /"); +} + +void AudioStreamEditor::_on_input_indicator(Ref<InputEvent> p_event) { + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid()) { + if (mb->is_pressed()) { + _seek_to(mb->get_position().x); + } + _dragging = mb->is_pressed(); + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid()) { + if (_dragging) { + _seek_to(mm->get_position().x); + } + } +} + +void AudioStreamEditor::_seek_to(real_t p_x) { + _current = p_x / _preview->get_rect().size.x * stream->get_length(); + _current = CLAMP(_current, 0, stream->get_length()); + _player->seek(_current); + _indicator->update(); +} + +void AudioStreamEditor::edit(Ref<AudioStream> p_stream) { + + if (!stream.is_null()) + stream->remove_change_receptor(this); + + stream = p_stream; + _player->set_stream(stream); + _current = 0; + String text = String::num(stream->get_length(), 2).pad_decimals(2) + "s"; + _duration_label->set_text(text); + + if (!stream.is_null()) { + stream->add_change_receptor(this); + update(); + } else { + hide(); + } +} + +void AudioStreamEditor::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_preview_changed"), &AudioStreamEditor::_preview_changed); + ClassDB::bind_method(D_METHOD("_play"), &AudioStreamEditor::_play); + ClassDB::bind_method(D_METHOD("_stop"), &AudioStreamEditor::_stop); + ClassDB::bind_method(D_METHOD("_on_finished"), &AudioStreamEditor::_on_finished); + ClassDB::bind_method(D_METHOD("_draw_preview"), &AudioStreamEditor::_draw_preview); + ClassDB::bind_method(D_METHOD("_draw_indicator"), &AudioStreamEditor::_draw_indicator); + ClassDB::bind_method(D_METHOD("_on_input_indicator"), &AudioStreamEditor::_on_input_indicator); +} + +AudioStreamEditor::AudioStreamEditor() { + + set_custom_minimum_size(Size2(1, 100)); + _current = 0; + _dragging = false; + + _player = memnew(AudioStreamPlayer); + _player->connect("finished", this, "_on_finished"); + add_child(_player); + + VBoxContainer *vbox = memnew(VBoxContainer); + vbox->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_MINSIZE, 0); + add_child(vbox); + + _preview = memnew(ColorRect); + _preview->set_v_size_flags(SIZE_EXPAND_FILL); + _preview->connect("draw", this, "_draw_preview"); + vbox->add_child(_preview); + + _indicator = memnew(Control); + _indicator->set_anchors_and_margins_preset(PRESET_WIDE); + _indicator->connect("draw", this, "_draw_indicator"); + _indicator->connect("gui_input", this, "_on_input_indicator"); + _preview->add_child(_indicator); + + HBoxContainer *hbox = memnew(HBoxContainer); + hbox->add_constant_override("separation", 0); + vbox->add_child(hbox); + + _play_button = memnew(ToolButton); + hbox->add_child(_play_button); + _play_button->set_focus_mode(Control::FOCUS_NONE); + _play_button->connect("pressed", this, "_play"); + + _stop_button = memnew(ToolButton); + hbox->add_child(_stop_button); + _stop_button->set_focus_mode(Control::FOCUS_NONE); + _stop_button->connect("pressed", this, "_stop"); + + _current_label = memnew(Label); + _current_label->set_align(Label::ALIGN_RIGHT); + _current_label->set_h_size_flags(SIZE_EXPAND_FILL); + _current_label->add_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_font("status_source", "EditorFonts")); + _current_label->set_modulate(Color(1, 1, 1, 0.5)); + hbox->add_child(_current_label); + + _duration_label = memnew(Label); + _duration_label->add_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_font("status_source", "EditorFonts")); + hbox->add_child(_duration_label); +} + +void AudioStreamEditorPlugin::edit(Object *p_object) { + + AudioStream *s = Object::cast_to<AudioStream>(p_object); + if (!s) + return; + + audio_editor->edit(Ref<AudioStream>(s)); +} + +bool AudioStreamEditorPlugin::handles(Object *p_object) const { + + return p_object->is_class("AudioStream"); +} + +void AudioStreamEditorPlugin::make_visible(bool p_visible) { + + audio_editor->set_visible(p_visible); +} + +AudioStreamEditorPlugin::AudioStreamEditorPlugin(EditorNode *p_node) { + + editor = p_node; + audio_editor = memnew(AudioStreamEditor); + add_control_to_container(CONTAINER_PROPERTY_EDITOR_BOTTOM, audio_editor); + audio_editor->hide(); +} + +AudioStreamEditorPlugin::~AudioStreamEditorPlugin() { +} diff --git a/editor/plugins/audio_stream_editor_plugin.h b/editor/plugins/audio_stream_editor_plugin.h new file mode 100644 index 0000000000..1887874b74 --- /dev/null +++ b/editor/plugins/audio_stream_editor_plugin.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* audio_stream_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 AUDIO_STREAM_EDITOR_PLUGIN_H +#define AUDIO_STREAM_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "scene/audio/audio_player.h" +#include "scene/gui/color_rect.h" +#include "scene/resources/texture.h" + +class AudioStreamEditor : public ColorRect { + + GDCLASS(AudioStreamEditor, ColorRect); + + Ref<AudioStream> stream; + AudioStreamPlayer *_player; + ColorRect *_preview; + Control *_indicator; + Label *_current_label; + Label *_duration_label; + + ToolButton *_play_button; + ToolButton *_stop_button; + + float _current; + bool _dragging; + +protected: + void _notification(int p_what); + void _preview_changed(ObjectID p_which); + void _play(); + void _stop(); + void _on_finished(); + void _draw_preview(); + void _draw_indicator(); + void _on_input_indicator(Ref<InputEvent> p_event); + void _seek_to(real_t p_x); + void _changed_callback(Object *p_changed, const char *p_prop); + static void _bind_methods(); + +public: + void edit(Ref<AudioStream> p_stream); + AudioStreamEditor(); +}; + +class AudioStreamEditorPlugin : public EditorPlugin { + + GDCLASS(AudioStreamEditorPlugin, EditorPlugin); + + AudioStreamEditor *audio_editor; + EditorNode *editor; + +public: + virtual String get_name() const { return "Audio"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + AudioStreamEditorPlugin(EditorNode *p_node); + ~AudioStreamEditorPlugin(); +}; + +#endif // AUDIO_STREAM_EDITOR_PLUGIN_H diff --git a/editor/plugins/particles_editor_plugin.cpp b/editor/plugins/particles_editor_plugin.cpp index e0325702a8..1f5a4a8a36 100644 --- a/editor/plugins/particles_editor_plugin.cpp +++ b/editor/plugins/particles_editor_plugin.cpp @@ -40,7 +40,6 @@ bool ParticlesEditorBase::_generate(PoolVector<Vector3> &points, PoolVector<Vect float area_accum = 0; Map<float, int> triangle_area_map; - print_line("geometry size: " + itos(geometry.size())); for (int i = 0; i < geometry.size(); i++) { @@ -300,6 +299,10 @@ void ParticlesEditor::_menu_option(int p_option) { CPUParticles *cpu_particles = memnew(CPUParticles); cpu_particles->convert_from_particles(node); + cpu_particles->set_name(node->get_name()); + cpu_particles->set_transform(node->get_transform()); + cpu_particles->set_visible(node->is_visible()); + cpu_particles->set_pause_mode(node->get_pause_mode()); undo_redo->create_action("Replace Particles by CPUParticles"); undo_redo->add_do_method(node, "replace_by", cpu_particles); diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index 5ec42b07aa..33e182faef 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -109,6 +109,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { action_point = i; moving_from = curve->get_point_out(i); moving_screen_from = gpoint; + orig_in_length = curve->get_point_in(action_point).length(); return true; } else if (dist_to_p_in < grab_threshold && i > 0) { @@ -116,6 +117,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { action_point = i; moving_from = curve->get_point_in(i); moving_screen_from = gpoint; + orig_out_length = curve->get_point_out(action_point).length(); return true; } } @@ -205,6 +207,11 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { undo_redo->create_action(TTR("Move In-Control in Curve")); undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, new_pos); undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, moving_from); + + if (mirror_handle_angle) { + undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length)); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length)); + } undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update"); undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update"); undo_redo->commit_action(); @@ -216,6 +223,11 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { undo_redo->create_action(TTR("Move Out-Control in Curve")); undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, new_pos); undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, moving_from); + + if (mirror_handle_angle) { + undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length)); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_in_length)); + } undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update"); undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update"); undo_redo->commit_action(); @@ -255,10 +267,16 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { case ACTION_MOVING_IN: { curve->set_point_in(action_point, new_pos); + + if (mirror_handle_angle) + curve->set_point_out(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length)); } break; case ACTION_MOVING_OUT: { curve->set_point_out(action_point, new_pos); + + if (mirror_handle_angle) + curve->set_point_in(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length)); } break; } @@ -342,6 +360,7 @@ void Path2DEditor::_bind_methods() { //ClassDB::bind_method(D_METHOD("_menu_option"),&Path2DEditor::_menu_option); ClassDB::bind_method(D_METHOD("_node_visibility_changed"), &Path2DEditor::_node_visibility_changed); ClassDB::bind_method(D_METHOD("_mode_selected"), &Path2DEditor::_mode_selected); + ClassDB::bind_method(D_METHOD("_handle_option_pressed"), &Path2DEditor::_handle_option_pressed); } void Path2DEditor::_mode_selected(int p_mode) { @@ -396,11 +415,33 @@ void Path2DEditor::_mode_selected(int p_mode) { mode = Mode(p_mode); } +void Path2DEditor::_handle_option_pressed(int p_option) { + + PopupMenu *pm; + pm = handle_menu->get_popup(); + + switch (p_option) { + case HANDLE_OPTION_ANGLE: { + bool is_checked = pm->is_item_checked(HANDLE_OPTION_ANGLE); + mirror_handle_angle = !is_checked; + pm->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle); + pm->set_item_disabled(HANDLE_OPTION_LENGTH, !mirror_handle_angle); + } break; + case HANDLE_OPTION_LENGTH: { + bool is_checked = pm->is_item_checked(HANDLE_OPTION_LENGTH); + mirror_handle_length = !is_checked; + pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); + } break; + } +} + Path2DEditor::Path2DEditor(EditorNode *p_editor) { canvas_item_editor = NULL; editor = p_editor; undo_redo = editor->get_undo_redo(); + mirror_handle_angle = true; + mirror_handle_length = true; mode = MODE_EDIT; action = ACTION_NONE; @@ -444,6 +485,20 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { curve_close->set_tooltip(TTR("Close Curve")); curve_close->connect("pressed", this, "_mode_selected", varray(ACTION_CLOSE)); base_hb->add_child(curve_close); + + PopupMenu *menu; + + handle_menu = memnew(MenuButton); + handle_menu->set_text(TTR("Options")); + base_hb->add_child(handle_menu); + + menu = handle_menu->get_popup(); + menu->add_check_item(TTR("Mirror Handle Angles")); + menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle); + menu->add_check_item(TTR("Mirror Handle Lengths")); + menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); + menu->connect("id_pressed", this, "_handle_option_pressed"); + base_hb->hide(); curve_edit->set_pressed(true); diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h index c92a696967..1e3955f84f 100644 --- a/editor/plugins/path_2d_editor_plugin.h +++ b/editor/plugins/path_2d_editor_plugin.h @@ -69,6 +69,15 @@ class Path2DEditor : public HBoxContainer { ToolButton *curve_edit_curve; ToolButton *curve_del; ToolButton *curve_close; + MenuButton *handle_menu; + + bool mirror_handle_angle; + bool mirror_handle_length; + + enum HandleOption { + HANDLE_OPTION_ANGLE, + HANDLE_OPTION_LENGTH + }; enum Action { @@ -82,8 +91,11 @@ class Path2DEditor : public HBoxContainer { int action_point; Point2 moving_from; Point2 moving_screen_from; + float orig_in_length; + float orig_out_length; void _mode_selected(int p_mode); + void _handle_option_pressed(int p_option); void _node_visibility_changed(); friend class Path2DEditorPlugin; diff --git a/editor/plugins/path_editor_plugin.cpp b/editor/plugins/path_editor_plugin.cpp index 6dde639c54..72a8b55a52 100644 --- a/editor/plugins/path_editor_plugin.cpp +++ b/editor/plugins/path_editor_plugin.cpp @@ -128,11 +128,22 @@ void PathSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_p if (p.intersects_ray(ray_from, ray_dir, &inters)) { + if (!PathEditorPlugin::singleton->is_handle_clicked()) { + orig_in_length = c->get_point_in(idx).length(); + orig_out_length = c->get_point_out(idx).length(); + PathEditorPlugin::singleton->set_handle_clicked(true); + } + Vector3 local = gi.xform(inters) - base; if (t == 0) { c->set_point_in(idx, local); + + if (PathEditorPlugin::singleton->mirror_angle_enabled()) + c->set_point_out(idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -local : (-local.normalized() * orig_out_length)); } else { c->set_point_out(idx, local); + if (PathEditorPlugin::singleton->mirror_angle_enabled()) + c->set_point_in(idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -local : (-local.normalized() * orig_in_length)); } } } @@ -165,8 +176,6 @@ void PathSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p int idx = p_idx / 2; int t = p_idx % 2; - Vector3 ofs; - if (t == 0) { if (p_cancel) { c->set_point_in(p_idx, p_restore); @@ -176,6 +185,11 @@ void PathSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p ur->create_action(TTR("Set Curve In Position")); ur->add_do_method(c.ptr(), "set_point_in", idx, c->get_point_in(idx)); ur->add_undo_method(c.ptr(), "set_point_in", idx, p_restore); + + if (PathEditorPlugin::singleton->mirror_angle_enabled()) { + ur->add_do_method(c.ptr(), "set_point_out", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_in(idx) : (-c->get_point_in(idx).normalized() * orig_out_length)); + ur->add_undo_method(c.ptr(), "set_point_out", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_out_length)); + } ur->commit_action(); } else { @@ -188,6 +202,11 @@ void PathSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p ur->create_action(TTR("Set Curve Out Position")); ur->add_do_method(c.ptr(), "set_point_out", idx, c->get_point_out(idx)); ur->add_undo_method(c.ptr(), "set_point_out", idx, p_restore); + + if (PathEditorPlugin::singleton->mirror_angle_enabled()) { + ur->add_do_method(c.ptr(), "set_point_in", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_out(idx) : (-c->get_point_out(idx).normalized() * orig_in_length)); + ur->add_undo_method(c.ptr(), "set_point_in", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_in_length)); + } ur->commit_action(); } } @@ -291,6 +310,9 @@ bool PathEditorPlugin::forward_spatial_gui_input(Camera *p_camera, const Ref<Inp Point2 mbpos(mb->get_position().x, mb->get_position().y); + if (!mb->is_pressed()) + set_handle_clicked(false); + if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->get_control()))) { //click into curve, break it down PoolVector<Vector3> v3a = c->tessellate(); @@ -459,6 +481,7 @@ void PathEditorPlugin::make_visible(bool p_visible) { curve_edit->show(); curve_del->show(); curve_close->show(); + handle_menu->show(); sep->show(); } else { @@ -466,6 +489,7 @@ void PathEditorPlugin::make_visible(bool p_visible) { curve_edit->hide(); curve_del->hide(); curve_close->hide(); + handle_menu->hide(); sep->hide(); { @@ -495,6 +519,26 @@ void PathEditorPlugin::_close_curve() { c->add_point(c->get_point_position(0), c->get_point_in(0), c->get_point_out(0)); } +void PathEditorPlugin::_handle_option_pressed(int p_option) { + + PopupMenu *pm; + pm = handle_menu->get_popup(); + + switch (p_option) { + case HANDLE_OPTION_ANGLE: { + bool is_checked = pm->is_item_checked(HANDLE_OPTION_ANGLE); + mirror_handle_angle = !is_checked; + pm->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle); + pm->set_item_disabled(HANDLE_OPTION_LENGTH, !mirror_handle_angle); + } break; + case HANDLE_OPTION_LENGTH: { + bool is_checked = pm->is_item_checked(HANDLE_OPTION_LENGTH); + mirror_handle_length = !is_checked; + pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); + } break; + } +} + void PathEditorPlugin::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { @@ -510,6 +554,7 @@ void PathEditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("_mode_changed"), &PathEditorPlugin::_mode_changed); ClassDB::bind_method(D_METHOD("_close_curve"), &PathEditorPlugin::_close_curve); + ClassDB::bind_method(D_METHOD("_handle_option_pressed"), &PathEditorPlugin::_handle_option_pressed); } PathEditorPlugin *PathEditorPlugin::singleton = NULL; @@ -519,6 +564,8 @@ PathEditorPlugin::PathEditorPlugin(EditorNode *p_node) { path = NULL; editor = p_node; singleton = this; + mirror_handle_angle = true; + mirror_handle_length = true; path_material = Ref<SpatialMaterial>(memnew(SpatialMaterial)); path_material->set_albedo(Color(0.5, 0.5, 1.0, 0.8)); @@ -567,6 +614,20 @@ PathEditorPlugin::PathEditorPlugin(EditorNode *p_node) { curve_close->set_tooltip(TTR("Close Curve")); SpatialEditor::get_singleton()->add_control_to_menu_panel(curve_close); + PopupMenu *menu; + + handle_menu = memnew(MenuButton); + handle_menu->set_text(TTR("Options")); + handle_menu->hide(); + SpatialEditor::get_singleton()->add_control_to_menu_panel(handle_menu); + + menu = handle_menu->get_popup(); + menu->add_check_item(TTR("Mirror Handle Angles")); + menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle); + menu->add_check_item(TTR("Mirror Handle Lengths")); + menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); + menu->connect("id_pressed", this, "_handle_option_pressed"); + curve_edit->set_pressed(true); /* collision_polygon_editor = memnew( PathEditor(p_node) ); diff --git a/editor/plugins/path_editor_plugin.h b/editor/plugins/path_editor_plugin.h index 6d5f07f729..52dfb78b61 100644 --- a/editor/plugins/path_editor_plugin.h +++ b/editor/plugins/path_editor_plugin.h @@ -1,4 +1,4 @@ -/*************************************************************************/ +/*************************************************************************/ /* path_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ @@ -40,6 +40,8 @@ class PathSpatialGizmo : public EditorSpatialGizmo { Path *path; mutable Vector3 original; + mutable float orig_in_length; + mutable float orig_out_length; public: virtual String get_handle_name(int p_idx) const; @@ -60,6 +62,7 @@ class PathEditorPlugin : public EditorPlugin { ToolButton *curve_edit; ToolButton *curve_del; ToolButton *curve_close; + MenuButton *handle_menu; EditorNode *editor; @@ -67,6 +70,15 @@ class PathEditorPlugin : public EditorPlugin { void _mode_changed(int p_idx); void _close_curve(); + void _handle_option_pressed(int p_option); + bool handle_clicked; + bool mirror_handle_angle; + bool mirror_handle_length; + + enum HandleOption { + HANDLE_OPTION_ANGLE, + HANDLE_OPTION_LENGTH + }; protected: void _notification(int p_what); @@ -88,6 +100,11 @@ public: virtual bool handles(Object *p_object) const; virtual void make_visible(bool p_visible); + bool mirror_angle_enabled() { return mirror_handle_angle; } + bool mirror_length_enabled() { return mirror_handle_length; } + bool is_handle_clicked() { return handle_clicked; } + void set_handle_clicked(bool clicked) { handle_clicked = clicked; } + PathEditorPlugin(EditorNode *p_node); ~PathEditorPlugin(); }; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index aa4673f41e..a1dc746702 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -31,7 +31,6 @@ #include "script_editor_plugin.h" #include "core/io/resource_loader.h" -#include "core/io/resource_saver.h" #include "core/os/file_access.h" #include "core/os/input.h" #include "core/os/keyboard.h" @@ -283,7 +282,6 @@ void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) { - continue; } @@ -402,7 +400,10 @@ void ScriptEditor::_go_to_tab(int p_idx) { if (is_visible_in_tree()) Object::cast_to<ScriptEditorBase>(c)->ensure_focus(); - notify_script_changed(Object::cast_to<ScriptEditorBase>(c)->get_edited_script()); + Ref<Script> script = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource(); + if (script != NULL) { + notify_script_changed(script); + } } if (Object::cast_to<EditorHelp>(c)) { @@ -482,12 +483,23 @@ void ScriptEditor::_open_recent_script(int p_idx) { String path = rc[p_idx]; // if its not on disk its a help file or deleted if (FileAccess::exists(path)) { - Ref<Script> script = ResourceLoader::load(path); - if (script.is_valid()) { - edit(script, true); - return; + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + + if (extensions.find(path.get_extension())) { + Ref<Script> script = ResourceLoader::load(path); + if (script.is_valid()) { + edit(script, true); + return; + } } + Error err; + Ref<TextFile> text_file = _load_text_file(path, &err); + if (text_file.is_valid()) { + edit(text_file, true); + return; + } // if it's a path then its most likely a deleted file not help } else if (!path.is_resource_file()) { _help_class_open(path); @@ -513,12 +525,17 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { return; Node *tselected = tab_container->get_child(selected); + ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); if (current) { if (p_save) { apply_scripts(); } - notify_script_close(current->get_edited_script()); + + Ref<Script> script = current->get_edited_resource(); + if (script != NULL) { + notify_script_close(script); + } } // roll back to previous tab @@ -589,7 +606,7 @@ void ScriptEditor::_close_docs_tab() { void ScriptEditor::_copy_script_path() { ScriptEditorBase *se = _get_current_editor(); - Ref<Script> script = se->get_edited_script(); + RES script = se->get_edited_resource(); OS::get_singleton()->set_clipboard(script->get_path()); } @@ -655,7 +672,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) { if (!se) continue; - Ref<Script> script = se->get_edited_script(); + RES script = se->get_edited_resource(); if (script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1) continue; //internal script, who cares @@ -672,7 +689,14 @@ void ScriptEditor::_resave_scripts(const String &p_str) { } } - editor->save_resource(script); + Ref<TextFile> text_file = script; + if (text_file != NULL) { + se->apply_code(); + _save_text_file(text_file, text_file->get_path()); + break; + } else { + editor->save_resource(script); + } se->tag_saved_version(); } @@ -689,25 +713,37 @@ void ScriptEditor::_reload_scripts() { continue; } - Ref<Script> script = se->get_edited_script(); + RES edited_res = se->get_edited_resource(); - if (script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1) { + if (edited_res->get_path() == "" || edited_res->get_path().find("local://") != -1 || edited_res->get_path().find("::") != -1) { continue; //internal script, who cares } - uint64_t last_date = script->get_last_modified_time(); - uint64_t date = FileAccess::get_modified_time(script->get_path()); + uint64_t last_date = edited_res->get_last_modified_time(); + uint64_t date = FileAccess::get_modified_time(edited_res->get_path()); if (last_date == date) { continue; } - Ref<Script> rel_script = ResourceLoader::load(script->get_path(), script->get_class(), true); - ERR_CONTINUE(!rel_script.is_valid()); - script->set_source_code(rel_script->get_source_code()); - script->set_last_modified_time(rel_script->get_last_modified_time()); - script->reload(); + Ref<Script> script = edited_res; + if (script != NULL) { + Ref<Script> rel_script = ResourceLoader::load(script->get_path(), script->get_class(), true); + ERR_CONTINUE(!rel_script.is_valid()); + script->set_source_code(rel_script->get_source_code()); + script->set_last_modified_time(rel_script->get_last_modified_time()); + script->reload(); + } + + Ref<TextFile> text_file = edited_res; + if (text_file != NULL) { + Error err; + Ref<TextFile> rel_text_file = _load_text_file(text_file->get_path(), &err); + ERR_CONTINUE(!rel_text_file.is_valid()); + text_file->set_text(rel_text_file->get_text()); + text_file->set_last_modified_time(rel_text_file->get_last_modified_time()); + } se->reload_text(); } @@ -725,7 +761,7 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { continue; } - Ref<Script> script = se->get_edited_script(); + RES script = se->get_edited_resource(); if (script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1) { continue; //internal script, who cares @@ -750,7 +786,7 @@ void ScriptEditor::_live_auto_reload_running_scripts() { debugger->reload_scripts(); } -bool ScriptEditor::_test_script_times_on_disk(Ref<Script> p_for_script) { +bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { disk_changed_list->clear(); TreeItem *r = disk_changed_list->create_item(); @@ -765,21 +801,20 @@ bool ScriptEditor::_test_script_times_on_disk(Ref<Script> p_for_script) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (se) { - Ref<Script> script = se->get_edited_script(); - - if (p_for_script.is_valid() && p_for_script != script) + RES edited_res = se->get_edited_resource(); + if (edited_res.is_valid() && p_for_script != edited_res) continue; - if (script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1) + if (edited_res->get_path() == "" || edited_res->get_path().find("local://") != -1 || edited_res->get_path().find("::") != -1) continue; //internal script, who cares - uint64_t last_date = script->get_last_modified_time(); - uint64_t date = FileAccess::get_modified_time(script->get_path()); + uint64_t last_date = edited_res->get_last_modified_time(); + uint64_t date = FileAccess::get_modified_time(edited_res->get_path()); if (last_date != date) { TreeItem *ti = disk_changed_list->create_item(r); - ti->set_text(0, script->get_path().get_file()); + ti->set_text(0, edited_res->get_path().get_file()); if (!use_autoreload || se->is_unsaved()) { need_ask = true; @@ -804,6 +839,49 @@ bool ScriptEditor::_test_script_times_on_disk(Ref<Script> p_for_script) { void ScriptEditor::_file_dialog_action(String p_file) { switch (file_dialog_option) { + case FILE_OPEN: { + + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + if (extensions.find(p_file.get_extension())) { + Ref<Script> scr = ResourceLoader::load(p_file); + if (!scr.is_valid()) { + editor->show_warning(TTR("Error could not load file."), TTR("Error!")); + file_dialog_option = -1; + return; + } + + edit(scr); + file_dialog_option = -1; + return; + } + + Error error; + Ref<TextFile> text_file = _load_text_file(p_file, &error); + if (error != OK) { + editor->show_warning(TTR("Error could not load file."), TTR("Error!")); + } + + if (text_file.is_valid()) { + edit(text_file); + file_dialog_option = -1; + return; + } + } + case FILE_SAVE_AS: { + ScriptEditorBase *current = _get_current_editor(); + + String path = ProjectSettings::get_singleton()->localize_path(p_file); + Error err = _save_text_file(current->get_edited_resource(), path); + + if (err != OK) { + editor->show_accept(TTR("Error saving file!"), TTR("OK")); + return; + } + + ((Resource *)current->get_edited_resource().ptr())->set_path(path); + _update_script_names(); + } break; case THEME_SAVE_AS: { if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) { editor->show_warning(TTR("Error while saving theme"), TTR("Error saving")); @@ -823,7 +901,8 @@ Ref<Script> ScriptEditor::_get_current_script() { ScriptEditorBase *current = _get_current_editor(); if (current) { - return current->get_edited_script(); + Ref<Script> script = current->get_edited_resource(); + return script != NULL ? script : NULL; } else { return NULL; } @@ -848,8 +927,19 @@ void ScriptEditor::_menu_option(int p_option) { script_create_dialog->popup_centered(Size2(300, 300) * EDSCALE); } break; case FILE_OPEN: { + file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); + file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_dialog_option = FILE_OPEN; - editor->open_resource("Script"); + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + file_dialog->clear_filters(); + for (int i = 0; i < extensions.size(); i++) { + file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); + } + + file_dialog->popup_centered_ratio(); + file_dialog->set_title(TTR("Open File")); return; } break; case FILE_SAVE_ALL: { @@ -929,7 +1019,14 @@ void ScriptEditor::_menu_option(int p_option) { current->convert_indent_to_tabs(); } } - editor->save_resource(current->get_edited_script()); + + Ref<TextFile> text_file = current->get_edited_resource(); + if (text_file != NULL) { + current->apply_code(); + _save_text_file(text_file, text_file->get_path()); + break; + } + editor->save_resource(current->get_edited_resource()); } break; case FILE_SAVE_AS: { @@ -943,8 +1040,25 @@ void ScriptEditor::_menu_option(int p_option) { current->convert_indent_to_tabs(); } } - editor->push_item(Object::cast_to<Object>(current->get_edited_script().ptr())); - editor->save_resource_as(current->get_edited_script()); + + Ref<TextFile> text_file = current->get_edited_resource(); + if (text_file != NULL) { + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_dialog_option = FILE_SAVE_AS; + + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + file_dialog->clear_filters(); + file_dialog->set_current_dir(text_file->get_path().get_base_dir()); + file_dialog->set_current_file(text_file->get_path().get_file()); + file_dialog->popup_centered_ratio(); + file_dialog->set_title(TTR("Save File As...")); + break; + } + + editor->push_item(Object::cast_to<Object>(current->get_edited_resource().ptr())); + editor->save_resource_as(current->get_edited_resource()); } break; @@ -956,8 +1070,8 @@ void ScriptEditor::_menu_option(int p_option) { } break; case FILE_RUN: { - Ref<Script> scr = current->get_edited_script(); - if (scr.is_null()) { + Ref<Script> scr = current->get_edited_resource(); + if (scr == NULL || scr.is_null()) { EditorNode::get_singleton()->show_warning("Can't obtain the script for running"); break; } @@ -1000,8 +1114,7 @@ void ScriptEditor::_menu_option(int p_option) { _copy_script_path(); } break; case SHOW_IN_FILE_SYSTEM: { - ScriptEditorBase *se = _get_current_editor(); - Ref<Script> script = se->get_edited_script(); + RES script = current->get_edited_resource(); FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); file_system_dock->navigate_to_path(script->get_path()); // Ensure that the FileSystem dock is visible. @@ -1259,8 +1372,8 @@ void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) { if (se) { - Ref<Script> script = se->get_edited_script(); - if (!script.is_valid()) + Ref<Script> script = se->get_edited_resource(); + if (script == NULL || !script.is_valid()) continue; if (script->get_path().find("::") != -1 && script->get_path().begins_with(p_scene)) { //is an internal script and belongs to scene being closed @@ -1307,9 +1420,13 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { if (!se) continue; + Ref<Script> script = se->get_edited_resource(); + if (script == NULL) { + continue; + } + List<int> bpoints; se->get_breakpoints(&bpoints); - Ref<Script> script = se->get_edited_script(); String base = script->get_path(); ERR_CONTINUE(base.begins_with("local://") || base == ""); @@ -1452,7 +1569,7 @@ void ScriptEditor::_update_members_overview() { members_overview->set_item_metadata(i, functions[i].get_slice(":", 1).to_int() - 1); } - String path = se->get_edited_script()->get_path(); + String path = se->get_edited_resource()->get_path(); bool built_in = !path.is_resource_file(); String name = built_in ? path.get_file() : se->get_name(); filename->set_text(name); @@ -1570,7 +1687,7 @@ void ScriptEditor::_update_script_names() { if (se) { Ref<Texture> icon = se->get_icon(); - String path = se->get_edited_script()->get_path(); + String path = se->get_edited_resource()->get_path(); bool built_in = !path.is_resource_file(); String name = built_in ? path.get_file() : se->get_name(); @@ -1579,7 +1696,7 @@ void ScriptEditor::_update_script_names() { sd.name = name; sd.tooltip = path; sd.index = i; - sd.used = used.has(se->get_edited_script()); + sd.used = used.has(se->get_edited_resource()); sd.category = 0; sd.ref = se; @@ -1681,11 +1798,65 @@ void ScriptEditor::_update_script_names() { _update_script_colors(); } -bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool p_grab_focus) { +Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) { + if (r_error) { + *r_error = ERR_FILE_CANT_OPEN; + } + + String local_path = ProjectSettings::get_singleton()->localize_path(p_path); + String path = ResourceLoader::path_remap(local_path); + + TextFile *text_file = memnew(TextFile); + Ref<TextFile> text_res(text_file); + Error err = text_file->load_text(path); + + if (err != OK) { + ERR_FAIL_COND_V(err != OK, RES()); + } + + text_file->set_file_path(local_path); + text_file->set_path(local_path, true); + + if (r_error) { + *r_error = OK; + } + + return text_res; +} + +Error ScriptEditor::_save_text_file(Ref<TextFile> p_text_file, const String &p_path) { + Ref<TextFile> sqscr = p_text_file; + ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER); + + String source = sqscr->get_text(); + + Error err; + FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err); + + if (err) { + + ERR_FAIL_COND_V(err, err); + } + + file->store_string(source); + if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { + memdelete(file); + return ERR_CANT_CREATE; + } + file->close(); + memdelete(file); + + _res_saved_callback(sqscr); + return OK; +} + +bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_grab_focus) { - if (p_script.is_null()) + if (p_resource.is_null()) return false; + Ref<Script> script = p_resource; + // refuse to open built-in if scene is not loaded // see if already has it @@ -1694,17 +1865,17 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool const bool should_open = open_dominant || !EditorNode::get_singleton()->is_changing_scene(); - if (p_script->get_language()->overrides_external_editor()) { + if (script != NULL && script->get_language()->overrides_external_editor()) { if (should_open) { - Error err = p_script->get_language()->open_in_external_editor(p_script, p_line >= 0 ? p_line : 0, p_col); + Error err = script->get_language()->open_in_external_editor(script, p_line >= 0 ? p_line : 0, p_col); if (err != OK) ERR_PRINT("Couldn't open script in the overridden external text editor"); } return false; } - if ((debugger->get_dump_stack_script() != p_script || debugger->get_debug_with_external_editor()) && - p_script->get_path().is_resource_file() && + if ((debugger->get_dump_stack_script() != p_resource || debugger->get_debug_with_external_editor()) && + p_resource->get_path().is_resource_file() && bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) { String path = EditorSettings::get_singleton()->get("text_editor/external/exec_path"); @@ -1714,7 +1885,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool if (flags.size()) { String project_path = ProjectSettings::get_singleton()->get_resource_path(); - String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); + String script_path = ProjectSettings::get_singleton()->globalize_path(p_resource->get_path()); flags = flags.replacen("{line}", itos(p_line > 0 ? p_line : 0)); flags = flags.replacen("{col}", itos(p_col)); @@ -1762,7 +1933,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool if (!se) continue; - if (se->get_edited_script() == p_script) { + if (se->get_edited_resource() == p_resource) { if (should_open) { if (tab_container->get_current_tab() != i) { @@ -1784,7 +1955,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool ScriptEditorBase *se; for (int i = script_editor_func_count - 1; i >= 0; i--) { - se = script_editor_funcs[i](p_script); + se = script_editor_funcs[i](p_resource); if (se) break; } @@ -1795,9 +1966,9 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool SyntaxHighlighter *highlighter = syntax_highlighters_funcs[i](); se->add_syntax_highlighter(highlighter); - if (!highlighter_set) { + if (script != NULL && !highlighter_set) { List<String> languages = highlighter->get_supported_languages(); - if (languages.find(p_script->get_language()->get_name())) { + if (languages.find(script->get_language()->get_name())) { se->set_syntax_highlighter(highlighter); highlighter_set = true; } @@ -1805,7 +1976,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool } tab_container->add_child(se); - se->set_edited_script(p_script); + se->set_edited_resource(p_resource); se->set_tooltip_request_func("_get_debug_tooltip", this); if (se->get_edit_menu()) { se->get_edit_menu()->hide(); @@ -1829,14 +2000,14 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool //test for modification, maybe the script was not edited but was loaded - _test_script_times_on_disk(p_script); - _update_modified_scripts_for_external_editor(p_script); + _test_script_times_on_disk(p_resource); + _update_modified_scripts_for_external_editor(p_resource); if (p_line >= 0) se->goto_line(p_line - 1); - notify_script_changed(p_script); - _add_recent_script(p_script->get_path()); + notify_script_changed(p_resource); + _add_recent_script(p_resource->get_path()); return true; } @@ -1863,15 +2034,23 @@ void ScriptEditor::save_all_scripts() { if (!se->is_unsaved()) continue; - Ref<Script> script = se->get_edited_script(); - if (script.is_valid()) + RES edited_res = se->get_edited_resource(); + if (edited_res.is_valid()) { se->apply_code(); + } - if (script->get_path() != "" && script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) - editor->save_resource(script); //external script, save it + if (edited_res->get_path() != "" && edited_res->get_path().find("local://") == -1 && edited_res->get_path().find("::") == -1) { + Ref<TextFile> text_file = edited_res; + if (text_file != NULL) { + _save_text_file(text_file, text_file->get_path()); + continue; + } + editor->save_resource(edited_res); //external script, save it + } } _update_script_names(); + EditorFileSystem::get_singleton()->update_script_classes(); } void ScriptEditor::apply_scripts() const { @@ -1937,7 +2116,7 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) continue; - if (se->get_edited_script() != script) + if (se->get_edited_resource() != script) continue; se->add_callback(p_function, p_args); @@ -2227,9 +2406,12 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_other_tabs"), CLOSE_OTHER_TABS); context_menu->add_separator(); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/copy_path"), FILE_COPY_PATH); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_TOOL_RELOAD_SOFT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/show_in_file_system"), SHOW_IN_FILE_SYSTEM); - Ref<Script> scr = se->get_edited_script(); + } + + Ref<Script> scr = se->get_edited_resource(); + if (scr != NULL) { + context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_TOOL_RELOAD_SOFT); if (!scr.is_null() && scr->is_tool()) { context_menu->add_separator(); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/run_file"), FILE_RUN); @@ -2238,8 +2420,6 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/close_file"), FILE_CLOSE); } - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(selected)); - context_menu->add_separator(); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_move_up"), WINDOW_MOVE_UP); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_move_down"), WINDOW_MOVE_DOWN); @@ -2267,14 +2447,28 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = true; + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + for (int i = 0; i < scripts.size(); i++) { String path = scripts[i]; if (!FileAccess::exists(path)) continue; - Ref<Script> scr = ResourceLoader::load(path); - if (scr.is_valid()) { - edit(scr); + + if (extensions.find(path.get_extension())) { + Ref<Script> scr = ResourceLoader::load(path); + if (scr.is_valid()) { + edit(scr); + continue; + } + } + + Error error; + Ref<TextFile> text_file = _load_text_file(path, &error); + if (error == OK && text_file.is_valid()) { + edit(text_file); + continue; } } @@ -2310,7 +2504,7 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (se) { - String path = se->get_edited_script()->get_path(); + String path = se->get_edited_resource()->get_path(); if (!path.is_resource_file()) continue; @@ -2435,7 +2629,10 @@ void ScriptEditor::_update_history_pos(int p_new_pos) { Object::cast_to<ScriptEditorBase>(n)->set_edit_state(history[history_pos].state); Object::cast_to<ScriptEditorBase>(n)->ensure_focus(); - notify_script_changed(Object::cast_to<ScriptEditorBase>(n)->get_edited_script()); + Ref<Script> script = Object::cast_to<ScriptEditorBase>(n)->get_edited_resource(); + if (script != NULL) { + notify_script_changed(script); + } } if (Object::cast_to<EditorHelp>(n)) { @@ -2472,7 +2669,11 @@ Vector<Ref<Script> > ScriptEditor::get_open_scripts() const { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) continue; - out_scripts.push_back(se->get_edited_script()); + + Ref<Script> script = se->get_edited_resource(); + if (script != NULL) { + out_scripts.push_back(script); + } } return out_scripts; @@ -2518,6 +2719,14 @@ void ScriptEditor::_open_script_request(const String &p_path) { Ref<Script> script = ResourceLoader::load(p_path); if (script.is_valid()) { script_editor->edit(script, false); + return; + } + + Error err; + Ref<TextFile> text_file = script_editor->_load_text_file(p_path, &err); + if (text_file.is_valid()) { + script_editor->edit(text_file, false); + return; } } @@ -2551,7 +2760,7 @@ void ScriptEditor::_on_find_in_files_requested(String text) { void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_number, int begin, int end) { - Ref<Resource> res = ResourceLoader::load(fpath); + RES res = ResourceLoader::load(fpath); edit(res); ScriptEditorBase *seb = _get_current_editor(); @@ -2967,14 +3176,21 @@ ScriptEditor::~ScriptEditor() { void ScriptEditorPlugin::edit(Object *p_object) { - if (!Object::cast_to<Script>(p_object)) - return; + if (Object::cast_to<Script>(p_object)) { + script_editor->edit(Object::cast_to<Script>(p_object)); + } - script_editor->edit(Object::cast_to<Script>(p_object)); + if (Object::cast_to<TextFile>(p_object)) { + script_editor->edit(Object::cast_to<TextFile>(p_object)); + } } bool ScriptEditorPlugin::handles(Object *p_object) const { + if (Object::cast_to<TextFile>(p_object)) { + return true; + } + if (Object::cast_to<Script>(p_object)) { bool valid = _can_open_in_editor(Object::cast_to<Script>(p_object)); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index ad12add53f..186c80a5f9 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -43,6 +43,7 @@ #include "scene/gui/tool_button.h" #include "scene/gui/tree.h" #include "scene/main/timer.h" +#include "scene/resources/text_file.h" #include "script_language.h" class ScriptEditorQuickOpen : public ConfirmationDialog { @@ -74,7 +75,7 @@ class ScriptEditorDebugger; class ScriptEditorBase : public VBoxContainer { - GDCLASS(ScriptEditorBase, VBoxContainer); + GDCLASS(ScriptEditorBase, VBoxContainer) protected: static void _bind_methods(); @@ -84,9 +85,9 @@ public: virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter) = 0; virtual void apply_code() = 0; - virtual Ref<Script> get_edited_script() const = 0; + virtual RES get_edited_resource() const = 0; virtual Vector<String> get_functions() = 0; - virtual void set_edited_script(const Ref<Script> &p_script) = 0; + virtual void set_edited_resource(const RES &p_res) = 0; virtual void reload_text() = 0; virtual String get_name() = 0; virtual Ref<Texture> get_icon() = 0; @@ -99,7 +100,7 @@ public: virtual void convert_indent_to_tabs() = 0; virtual void ensure_focus() = 0; virtual void tag_saved_version() = 0; - virtual void reload(bool p_soft) = 0; + virtual void reload(bool p_soft) {} virtual void get_breakpoints(List<int> *p_breakpoints) = 0; virtual void add_callback(const String &p_function, PoolStringArray p_args) = 0; virtual void update_settings() = 0; @@ -116,7 +117,7 @@ public: }; typedef SyntaxHighlighter *(*CreateSyntaxHighlighterFunc)(); -typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Script> &p_script); +typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const RES &p_resource); class EditorScriptCodeCompletionCache; class FindInFilesDialog; @@ -268,7 +269,7 @@ class ScriptEditor : public PanelContainer { void _resave_scripts(const String &p_str); void _reload_scripts(); - bool _test_script_times_on_disk(Ref<Script> p_for_script = Ref<Script>()); + bool _test_script_times_on_disk(RES p_for_script = Ref<Resource>()); void _add_recent_script(String p_path); void _update_recent_scripts(); @@ -378,6 +379,9 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; + Ref<TextFile> _load_text_file(const String &p_path, Error *r_error); + Error _save_text_file(Ref<TextFile> p_text_file, const String &p_path); + void _on_find_in_files_requested(String text); void _on_find_in_files_result_selected(String fpath, int line_number, int begin, int end); void _start_find_in_files(bool with_replace); @@ -400,8 +404,8 @@ public: void ensure_select_current(); - _FORCE_INLINE_ bool edit(const Ref<Script> &p_script, bool p_grab_focus = true) { return edit(p_script, -1, 0, p_grab_focus); } - bool edit(const Ref<Script> &p_script, int p_line, int p_col, bool p_grab_focus = true); + _FORCE_INLINE_ bool edit(const RES &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); } + bool edit(const RES &p_resource, int p_line, int p_col, bool p_grab_focus = true); void get_breakpoints(List<String> *p_breakpoints); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index ffc2203475..165d7e32b9 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -66,11 +66,24 @@ void ScriptTextEditor::apply_code() { _update_member_keywords(); } -Ref<Script> ScriptTextEditor::get_edited_script() const { - +RES ScriptTextEditor::get_edited_resource() const { return script; } +void ScriptTextEditor::set_edited_resource(const RES &p_res) { + ERR_FAIL_COND(!script.is_null()); + + script = p_res; + _set_theme_for_script(); + + code_editor->get_text_edit()->set_text(script->get_source_code()); + code_editor->get_text_edit()->clear_undo_history(); + code_editor->get_text_edit()->tag_saved_version(); + + emit_signal("name_changed"); + code_editor->update_line_and_column(); +} + void ScriptTextEditor::_update_member_keywords() { member_keywords.clear(); code_editor->get_text_edit()->clear_member_keywords(); @@ -116,6 +129,7 @@ void ScriptTextEditor::_load_theme_settings() { Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color"); Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color"); + Color safe_line_number_color = EDITOR_GET("text_editor/highlighting/safe_line_number_color"); Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color"); Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color"); Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color"); @@ -147,6 +161,7 @@ void ScriptTextEditor::_load_theme_settings() { text_edit->add_color_override("completion_font_color", completion_font_color); text_edit->add_color_override("font_color", text_color); text_edit->add_color_override("line_number_color", line_number_color); + text_edit->add_color_override("safe_line_number_color", safe_line_number_color); text_edit->add_color_override("caret_color", caret_color); text_edit->add_color_override("caret_background_color", caret_background_color); text_edit->add_color_override("font_selected_color", text_selected_color); @@ -188,6 +203,7 @@ void ScriptTextEditor::_set_theme_for_script() { List<String> keywords; script->get_language()->get_reserved_words(&keywords); + for (List<String>::Element *E = keywords.front(); E; E = E->next()) { text_edit->add_keyword_color(E->get(), colors_cache.keyword_color); @@ -249,7 +265,6 @@ void ScriptTextEditor::_set_theme_for_script() { //colorize strings List<String> strings; script->get_language()->get_string_delimiters(&strings); - for (List<String>::Element *E = strings.front(); E; E = E->next()) { String string = E->get(); @@ -324,197 +339,32 @@ bool ScriptTextEditor::is_unsaved() { Variant ScriptTextEditor::get_edit_state() { - Dictionary state; + return code_editor->get_edit_state(); +} - state["scroll_position"] = code_editor->get_text_edit()->get_v_scroll(); - state["column"] = code_editor->get_text_edit()->cursor_get_column(); - state["row"] = code_editor->get_text_edit()->cursor_get_line(); +void ScriptTextEditor::set_edit_state(const Variant &p_state) { - return state; + code_editor->set_edit_state(p_state); } -void ScriptTextEditor::_convert_case(CaseStyle p_case) { - TextEdit *te = code_editor->get_text_edit(); - Ref<Script> scr = get_edited_script(); - if (scr.is_null()) { - return; - } - - if (te->is_selection_active()) { - te->begin_complex_operation(); - - int begin = te->get_selection_from_line(); - int end = te->get_selection_to_line(); - int begin_col = te->get_selection_from_column(); - int end_col = te->get_selection_to_column(); - - for (int i = begin; i <= end; i++) { - int len = te->get_line(i).length(); - if (i == end) - len -= len - end_col; - if (i == begin) - len -= begin_col; - String new_line = te->get_line(i).substr(i == begin ? begin_col : 0, len); - - switch (p_case) { - case UPPER: { - new_line = new_line.to_upper(); - } break; - case LOWER: { - new_line = new_line.to_lower(); - } break; - case CAPITALIZE: { - new_line = new_line.capitalize(); - } break; - } +void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { - if (i == begin) { - new_line = te->get_line(i).left(begin_col) + new_line; - } - if (i == end) { - new_line = new_line + te->get_line(i).right(end_col); - } - te->set_line(i, new_line); - } - te->end_complex_operation(); - } + code_editor->convert_case(p_case); } void ScriptTextEditor::trim_trailing_whitespace() { - TextEdit *tx = code_editor->get_text_edit(); - - bool trimed_whitespace = false; - for (int i = 0; i < tx->get_line_count(); i++) { - String line = tx->get_line(i); - if (line.ends_with(" ") || line.ends_with("\t")) { - - if (!trimed_whitespace) { - tx->begin_complex_operation(); - trimed_whitespace = true; - } - - int end = 0; - for (int j = line.length() - 1; j > -1; j--) { - if (line[j] != ' ' && line[j] != '\t') { - end = j + 1; - break; - } - } - tx->set_line(i, line.substr(0, end)); - } - } - if (trimed_whitespace) { - tx->end_complex_operation(); - tx->update(); - } + code_editor->trim_trailing_whitespace(); } void ScriptTextEditor::convert_indent_to_spaces() { - TextEdit *tx = code_editor->get_text_edit(); - Ref<Script> scr = get_edited_script(); - - if (scr.is_null()) { - return; - } - - int indent_size = EditorSettings::get_singleton()->get("text_editor/indent/size"); - String indent = ""; - - for (int i = 0; i < indent_size; i++) { - indent += " "; - } - int cursor_line = tx->cursor_get_line(); - int cursor_column = tx->cursor_get_column(); - - bool changed_indentation = false; - for (int i = 0; i < tx->get_line_count(); i++) { - String line = tx->get_line(i); - - if (line.length() <= 0) { - continue; - } - - int j = 0; - while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) { - if (line[j] == '\t') { - if (!changed_indentation) { - tx->begin_complex_operation(); - changed_indentation = true; - } - if (cursor_line == i && cursor_column > j) { - cursor_column += indent_size - 1; - } - line = line.left(j) + indent + line.right(j + 1); - } - j++; - } - if (changed_indentation) { - tx->set_line(i, line); - } - } - if (changed_indentation) { - tx->cursor_set_column(cursor_column); - tx->end_complex_operation(); - tx->update(); - } + code_editor->convert_indent_to_spaces(); } void ScriptTextEditor::convert_indent_to_tabs() { - TextEdit *tx = code_editor->get_text_edit(); - Ref<Script> scr = get_edited_script(); - - if (scr.is_null()) { - return; - } - - int indent_size = EditorSettings::get_singleton()->get("text_editor/indent/size"); - indent_size -= 1; - - int cursor_line = tx->cursor_get_line(); - int cursor_column = tx->cursor_get_column(); - - bool changed_indentation = false; - for (int i = 0; i < tx->get_line_count(); i++) { - String line = tx->get_line(i); - - if (line.length() <= 0) { - continue; - } - int j = 0; - int space_count = -1; - while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) { - if (line[j] != '\t') { - space_count++; - - if (space_count == indent_size) { - if (!changed_indentation) { - tx->begin_complex_operation(); - changed_indentation = true; - } - if (cursor_line == i && cursor_column > j) { - cursor_column -= indent_size; - } - line = line.left(j - indent_size) + "\t" + line.right(j + 1); - j = 0; - space_count = -1; - } - } else { - space_count = -1; - } - j++; - } - if (changed_indentation) { - tx->set_line(i, line); - } - } - if (changed_indentation) { - tx->cursor_set_column(cursor_column); - tx->end_complex_operation(); - tx->update(); - } + code_editor->convert_indent_to_tabs(); } void ScriptTextEditor::tag_saved_version() { @@ -523,31 +373,17 @@ void ScriptTextEditor::tag_saved_version() { } void ScriptTextEditor::goto_line(int p_line, bool p_with_error) { - TextEdit *tx = code_editor->get_text_edit(); - tx->deselect(); - tx->unfold_line(p_line); - tx->call_deferred("cursor_set_line", p_line); -} -void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { - TextEdit *tx = code_editor->get_text_edit(); - tx->unfold_line(p_line); - tx->call_deferred("cursor_set_line", p_line); - tx->call_deferred("cursor_set_column", p_begin); - tx->select(p_line, p_begin, p_line, p_end); + code_editor->goto_line(p_line); } -void ScriptTextEditor::ensure_focus() { +void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { - code_editor->get_text_edit()->grab_focus(); + code_editor->goto_line_selection(p_line, p_begin, p_end); } -void ScriptTextEditor::set_edit_state(const Variant &p_state) { +void ScriptTextEditor::ensure_focus() { - Dictionary state = p_state; - code_editor->get_text_edit()->cursor_set_column(state["column"]); - code_editor->get_text_edit()->cursor_set_line(state["row"]); - code_editor->get_text_edit()->set_v_scroll(state["scroll_position"]); code_editor->get_text_edit()->grab_focus(); } @@ -576,21 +412,6 @@ Ref<Texture> ScriptTextEditor::get_icon() { return Ref<Texture>(); } -void ScriptTextEditor::set_edited_script(const Ref<Script> &p_script) { - - ERR_FAIL_COND(!script.is_null()); - - script = p_script; - _set_theme_for_script(); - - code_editor->get_text_edit()->set_text(script->get_source_code()); - code_editor->get_text_edit()->clear_undo_history(); - code_editor->get_text_edit()->tag_saved_version(); - - emit_signal("name_changed"); - code_editor->update_line_and_column(); -} - void ScriptTextEditor::_validate_script() { String errortxt; @@ -599,8 +420,9 @@ void ScriptTextEditor::_validate_script() { String text = te->get_text(); List<String> fnc; + Set<int> safe_lines; - if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc)) { + if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &safe_lines)) { String error_text = "error(" + itos(line) + "," + itos(col) + "): " + errortxt; code_editor->set_error(error_text); } else { @@ -621,8 +443,23 @@ void ScriptTextEditor::_validate_script() { } line--; + bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true); + bool last_is_safe = false; for (int i = 0; i < te->get_line_count(); i++) { te->set_line_as_marked(i, line == i); + if (highlight_safe) { + if (safe_lines.has(i + 1)) { + te->set_line_as_safe(i, true); + last_is_safe = true; + } else if (last_is_safe && (te->is_line_comment(i) || te->get_line(i).strip_edges().empty())) { + te->set_line_as_safe(i, true); + } else { + te->set_line_as_safe(i, false); + last_is_safe = false; + } + } else { + te->set_line_as_safe(i, false); + } } emit_signal("name_changed"); @@ -859,98 +696,15 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case EDIT_MOVE_LINE_UP: { - Ref<Script> scr = script; - if (scr.is_null()) - return; - - tx->begin_complex_operation(); - if (tx->is_selection_active()) { - int from_line = tx->get_selection_from_line(); - int from_col = tx->get_selection_from_column(); - int to_line = tx->get_selection_to_line(); - int to_column = tx->get_selection_to_column(); - - for (int i = from_line; i <= to_line; i++) { - int line_id = i; - int next_id = i - 1; - - if (line_id == 0 || next_id < 0) - return; - - tx->unfold_line(line_id); - tx->unfold_line(next_id); - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - int from_line_up = from_line > 0 ? from_line - 1 : from_line; - int to_line_up = to_line > 0 ? to_line - 1 : to_line; - tx->select(from_line_up, from_col, to_line_up, to_column); - } else { - int line_id = tx->cursor_get_line(); - int next_id = line_id - 1; - - if (line_id == 0 || next_id < 0) - return; - - tx->unfold_line(line_id); - tx->unfold_line(next_id); - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - tx->end_complex_operation(); - tx->update(); + code_editor->move_lines_up(); } break; case EDIT_MOVE_LINE_DOWN: { - Ref<Script> scr = get_edited_script(); - if (scr.is_null()) - return; - - tx->begin_complex_operation(); - if (tx->is_selection_active()) { - int from_line = tx->get_selection_from_line(); - int from_col = tx->get_selection_from_column(); - int to_line = tx->get_selection_to_line(); - int to_column = tx->get_selection_to_column(); - - for (int i = to_line; i >= from_line; i--) { - int line_id = i; - int next_id = i + 1; - - if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count()) - return; - - tx->unfold_line(line_id); - tx->unfold_line(next_id); - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - int from_line_down = from_line < tx->get_line_count() ? from_line + 1 : from_line; - int to_line_down = to_line < tx->get_line_count() ? to_line + 1 : to_line; - tx->select(from_line_down, from_col, to_line_down, to_column); - } else { - int line_id = tx->cursor_get_line(); - int next_id = line_id + 1; - - if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count()) - return; - - tx->unfold_line(line_id); - tx->unfold_line(next_id); - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - tx->end_complex_operation(); - tx->update(); - + code_editor->move_lines_down(); } break; case EDIT_INDENT_LEFT: { - Ref<Script> scr = get_edited_script(); + Ref<Script> scr = script; if (scr.is_null()) return; @@ -958,7 +712,7 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case EDIT_INDENT_RIGHT: { - Ref<Script> scr = get_edited_script(); + Ref<Script> scr = script; if (scr.is_null()) return; @@ -966,72 +720,11 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case EDIT_DELETE_LINE: { - Ref<Script> scr = get_edited_script(); - if (scr.is_null()) - return; - tx->begin_complex_operation(); - if (tx->is_selection_active()) { - int to_line = tx->get_selection_to_line(); - int from_line = tx->get_selection_from_line(); - int count = Math::abs(to_line - from_line) + 1; - while (count) { - tx->set_line(tx->cursor_get_line(), ""); - tx->backspace_at_cursor(); - count--; - if (count) - tx->unfold_line(from_line); - } - tx->cursor_set_line(from_line - 1); - tx->deselect(); - } else { - int line = tx->cursor_get_line(); - tx->set_line(tx->cursor_get_line(), ""); - tx->backspace_at_cursor(); - tx->unfold_line(line); - tx->cursor_set_line(line); - } - tx->end_complex_operation(); + code_editor->delete_lines(); } break; case EDIT_CLONE_DOWN: { - Ref<Script> scr = get_edited_script(); - if (scr.is_null()) - return; - - int from_line = tx->cursor_get_line(); - int to_line = tx->cursor_get_line(); - int column = tx->cursor_get_column(); - - if (tx->is_selection_active()) { - from_line = tx->get_selection_from_line(); - to_line = tx->get_selection_to_line(); - column = tx->cursor_get_column(); - } - int next_line = to_line + 1; - - if (to_line >= tx->get_line_count() - 1) { - tx->set_line(to_line, tx->get_line(to_line) + "\n"); - } - - tx->begin_complex_operation(); - for (int i = from_line; i <= to_line; i++) { - - tx->unfold_line(i); - if (i >= tx->get_line_count() - 1) { - tx->set_line(i, tx->get_line(i) + "\n"); - } - String line_clone = tx->get_line(i); - tx->insert_at(line_clone, next_line); - next_line++; - } - - tx->cursor_set_column(column); - if (tx->is_selection_active()) { - tx->select(to_line + 1, tx->get_selection_from_column(), next_line - 1, tx->get_selection_to_column()); - } - - tx->end_complex_operation(); - tx->update(); + code_editor->code_lines_down(); } break; case EDIT_TOGGLE_FOLD_LINE: { @@ -1050,7 +743,7 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case EDIT_TOGGLE_COMMENT: { - Ref<Script> scr = get_edited_script(); + Ref<Script> scr = script; if (scr.is_null()) return; @@ -1118,7 +811,7 @@ void ScriptTextEditor::_edit_option(int p_op) { case EDIT_AUTO_INDENT: { String text = tx->get_text(); - Ref<Script> scr = get_edited_script(); + Ref<Script> scr = script; if (scr.is_null()) return; @@ -1161,15 +854,15 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case EDIT_TO_UPPERCASE: { - _convert_case(UPPER); + _convert_case(CodeTextEditor::UPPER); } break; case EDIT_TO_LOWERCASE: { - _convert_case(LOWER); + _convert_case(CodeTextEditor::LOWER); } break; case EDIT_CAPITALIZE: { - _convert_case(CAPITALIZE); + _convert_case(CodeTextEditor::CAPITALIZE); } break; case SEARCH_FIND: { @@ -1209,7 +902,7 @@ void ScriptTextEditor::_edit_option(int p_op) { int line = tx->cursor_get_line(); bool dobreak = !tx->is_line_set_as_breakpoint(line); tx->set_line_as_breakpoint(line, dobreak); - ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(get_edited_script()->get_path(), line + 1, dobreak); + ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(script->get_path(), line + 1, dobreak); } break; case DEBUG_REMOVE_ALL_BREAKPOINTS: { @@ -1220,7 +913,7 @@ void ScriptTextEditor::_edit_option(int p_op) { int line = E->get(); bool dobreak = !tx->is_line_set_as_breakpoint(line); tx->set_line_as_breakpoint(line, dobreak); - ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(get_edited_script()->get_path(), line + 1, dobreak); + ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(script->get_path(), line + 1, dobreak); } } case DEBUG_GOTO_NEXT_BREAKPOINT: { @@ -1340,7 +1033,7 @@ void ScriptTextEditor::clear_edit_menu() { void ScriptTextEditor::reload(bool p_soft) { TextEdit *te = code_editor->get_text_edit(); - Ref<Script> scr = get_edited_script(); + Ref<Script> scr = script; if (scr.is_null()) return; scr->set_source_code(te->get_text()); @@ -1724,9 +1417,12 @@ ScriptTextEditor::ScriptTextEditor() { code_editor->get_text_edit()->set_drag_forwarding(this); } -static ScriptEditorBase *create_editor(const Ref<Script> &p_script) { +static ScriptEditorBase *create_editor(const RES &p_resource) { - return memnew(ScriptTextEditor); + if (Object::cast_to<Script>(*p_resource)) { + return memnew(ScriptTextEditor); + } + return NULL; } void ScriptTextEditor::register_editor() { diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index a415f478e8..f0b00a9117 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -138,12 +138,7 @@ protected: void _goto_line(int p_line) { goto_line(p_line); } void _lookup_symbol(const String &p_symbol, int p_row, int p_column); - enum CaseStyle { - UPPER, - LOWER, - CAPITALIZE, - }; - void _convert_case(CaseStyle p_case); + void _convert_case(CodeTextEditor::CaseStyle p_case); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; @@ -154,14 +149,13 @@ public: virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter); virtual void apply_code(); - virtual Ref<Script> get_edited_script() const; + virtual RES get_edited_resource() const; + virtual void set_edited_resource(const RES &p_res); virtual Vector<String> get_functions(); - virtual void set_edited_script(const Ref<Script> &p_script); virtual void reload_text(); virtual String get_name(); virtual Ref<Texture> get_icon(); virtual bool is_unsaved(); - virtual Variant get_edit_state(); virtual void set_edit_state(const Variant &p_state); virtual void ensure_focus(); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 9b31e1a421..7650cd6ae7 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -130,9 +130,9 @@ void ShaderTextEditor::_load_theme_settings() { } } - for (const Set<String>::Element *E = ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode())).front(); E; E = E->next()) { + for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode())).size(); i++) { - keywords.push_back(E->get()); + keywords.push_back(ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode()))[i]); } } @@ -258,84 +258,10 @@ void ShaderEditor::_menu_option(int p_option) { shader_editor->get_text_edit()->select_all(); } break; case EDIT_MOVE_LINE_UP: { - - TextEdit *tx = shader_editor->get_text_edit(); - if (shader.is_null()) - return; - - tx->begin_complex_operation(); - if (tx->is_selection_active()) { - int from_line = tx->get_selection_from_line(); - int from_col = tx->get_selection_from_column(); - int to_line = tx->get_selection_to_line(); - int to_column = tx->get_selection_to_column(); - - for (int i = from_line; i <= to_line; i++) { - int line_id = i; - int next_id = i - 1; - - if (line_id == 0 || next_id < 0) - return; - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - int from_line_up = from_line > 0 ? from_line - 1 : from_line; - int to_line_up = to_line > 0 ? to_line - 1 : to_line; - tx->select(from_line_up, from_col, to_line_up, to_column); - } else { - int line_id = tx->cursor_get_line(); - int next_id = line_id - 1; - - if (line_id == 0 || next_id < 0) - return; - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - tx->end_complex_operation(); - tx->update(); - + shader_editor->move_lines_up(); } break; case EDIT_MOVE_LINE_DOWN: { - - TextEdit *tx = shader_editor->get_text_edit(); - if (shader.is_null()) - return; - - tx->begin_complex_operation(); - if (tx->is_selection_active()) { - int from_line = tx->get_selection_from_line(); - int from_col = tx->get_selection_from_column(); - int to_line = tx->get_selection_to_line(); - int to_column = tx->get_selection_to_column(); - - for (int i = to_line; i >= from_line; i--) { - int line_id = i; - int next_id = i + 1; - - if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count()) - return; - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - int from_line_down = from_line < tx->get_line_count() ? from_line + 1 : from_line; - int to_line_down = to_line < tx->get_line_count() ? to_line + 1 : to_line; - tx->select(from_line_down, from_col, to_line_down, to_column); - } else { - int line_id = tx->cursor_get_line(); - int next_id = line_id + 1; - - if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count()) - return; - - tx->swap_lines(line_id, next_id); - tx->cursor_set_line(next_id); - } - tx->end_complex_operation(); - tx->update(); - + shader_editor->move_lines_down(); } break; case EDIT_INDENT_LEFT: { @@ -356,55 +282,10 @@ void ShaderEditor::_menu_option(int p_option) { } break; case EDIT_DELETE_LINE: { - - TextEdit *tx = shader_editor->get_text_edit(); - if (shader.is_null()) - return; - - tx->begin_complex_operation(); - int line = tx->cursor_get_line(); - tx->set_line(tx->cursor_get_line(), ""); - tx->backspace_at_cursor(); - tx->cursor_set_line(line); - tx->end_complex_operation(); - + shader_editor->delete_lines(); } break; case EDIT_CLONE_DOWN: { - - TextEdit *tx = shader_editor->get_text_edit(); - if (shader.is_null()) - return; - - int from_line = tx->cursor_get_line(); - int to_line = tx->cursor_get_line(); - int column = tx->cursor_get_column(); - - if (tx->is_selection_active()) { - from_line = tx->get_selection_from_line(); - to_line = tx->get_selection_to_line(); - column = tx->cursor_get_column(); - } - int next_line = to_line + 1; - - tx->begin_complex_operation(); - for (int i = from_line; i <= to_line; i++) { - - if (i >= tx->get_line_count() - 1) { - tx->set_line(i, tx->get_line(i) + "\n"); - } - String line_clone = tx->get_line(i); - tx->insert_at(line_clone, next_line); - next_line++; - } - - tx->cursor_set_column(column); - if (tx->is_selection_active()) { - tx->select(to_line + 1, tx->get_selection_from_column(), next_line - 1, tx->get_selection_to_column()); - } - - tx->end_complex_operation(); - tx->update(); - + shader_editor->code_lines_down(); } break; case EDIT_TOGGLE_COMMENT: { diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 37b8562e96..f0c874a150 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -4641,9 +4641,7 @@ void SpatialEditor::_init_grid() { Vector3 p2_dest = p2 * (-axis_n1 + axis_n2); Color line_color = secondary_grid_color; - if (j == 0) { - continue; - } else if (j % primary_grid_steps == 0) { + if (j % primary_grid_steps == 0) { line_color = primary_grid_color; } diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h index 637926a913..af882f6e05 100644 --- a/editor/plugins/spatial_editor_plugin.h +++ b/editor/plugins/spatial_editor_plugin.h @@ -60,6 +60,7 @@ public: virtual Variant get_handle_value(int p_idx) const; virtual void set_handle(int p_idx, Camera *p_camera, const Point2 &p_point); virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false); + virtual bool is_gizmo_handle_highlighted(int idx) const { return false; } virtual bool intersect_frustum(const Camera *p_camera, const Vector<Plane> &p_frustum); virtual bool intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp new file mode 100644 index 0000000000..16c25f3074 --- /dev/null +++ b/editor/plugins/text_editor.cpp @@ -0,0 +1,607 @@ +/*************************************************************************/ +/* text_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "text_editor.h" + +void TextEditor::add_syntax_highlighter(SyntaxHighlighter *p_highlighter) { + highlighters[p_highlighter->get_name()] = p_highlighter; + highlighter_menu->add_radio_check_item(p_highlighter->get_name()); +} + +void TextEditor::set_syntax_highlighter(SyntaxHighlighter *p_highlighter) { + TextEdit *te = code_editor->get_text_edit(); + te->_set_syntax_highlighting(p_highlighter); + if (p_highlighter != NULL) { + highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->get_name()), true); + } else { + highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text("Standard"), true); + } + + // little work around. GDScript highlighter goes through text_edit for colours, + // so to remove all colours we need to set and unset them here. + if (p_highlighter == NULL) { // standard + TextEdit *text_edit = code_editor->get_text_edit(); + text_edit->add_color_override("number_color", colors_cache.font_color); + text_edit->add_color_override("function_color", colors_cache.font_color); + text_edit->add_color_override("number_color", colors_cache.font_color); + text_edit->add_color_override("member_variable_color", colors_cache.font_color); + } else { + _load_theme_settings(); + } +} + +void TextEditor::_change_syntax_highlighter(int p_idx) { + Map<String, SyntaxHighlighter *>::Element *el = highlighters.front(); + while (el != NULL) { + highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false); + el = el->next(); + } + set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]); +} + +void TextEditor::_load_theme_settings() { + + TextEdit *text_edit = code_editor->get_text_edit(); + text_edit->clear_colors(); + + Color background_color = EDITOR_GET("text_editor/highlighting/background_color"); + Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color"); + Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color"); + Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color"); + Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color"); + Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color"); + Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); + Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color"); + Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color"); + Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color"); + Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color"); + Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color"); + Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color"); + Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color"); + Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color"); + Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color"); + Color number_color = EDITOR_GET("text_editor/highlighting/number_color"); + Color function_color = EDITOR_GET("text_editor/highlighting/function_color"); + Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color"); + Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color"); + Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color"); + Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color"); + Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color"); + Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); + Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + Color basetype_color = EDITOR_GET("text_editor/highlighting/base_type_color"); + Color type_color = EDITOR_GET("text_editor/highlighting/engine_type_color"); + Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); + Color string_color = EDITOR_GET("text_editor/highlighting/string_color"); + + text_edit->add_color_override("background_color", background_color); + text_edit->add_color_override("completion_background_color", completion_background_color); + text_edit->add_color_override("completion_selected_color", completion_selected_color); + text_edit->add_color_override("completion_existing_color", completion_existing_color); + text_edit->add_color_override("completion_scroll_color", completion_scroll_color); + text_edit->add_color_override("completion_font_color", completion_font_color); + text_edit->add_color_override("font_color", text_color); + text_edit->add_color_override("line_number_color", line_number_color); + text_edit->add_color_override("caret_color", caret_color); + text_edit->add_color_override("caret_background_color", caret_background_color); + text_edit->add_color_override("font_selected_color", text_selected_color); + text_edit->add_color_override("selection_color", selection_color); + text_edit->add_color_override("brace_mismatch_color", brace_mismatch_color); + text_edit->add_color_override("current_line_color", current_line_color); + text_edit->add_color_override("line_length_guideline_color", line_length_guideline_color); + text_edit->add_color_override("word_highlighted_color", word_highlighted_color); + text_edit->add_color_override("number_color", number_color); + text_edit->add_color_override("function_color", function_color); + text_edit->add_color_override("member_variable_color", member_variable_color); + text_edit->add_color_override("breakpoint_color", breakpoint_color); + text_edit->add_color_override("mark_color", mark_color); + text_edit->add_color_override("code_folding_color", code_folding_color); + text_edit->add_color_override("search_result_color", search_result_color); + text_edit->add_color_override("search_result_border_color", search_result_border_color); + text_edit->add_color_override("symbol_color", symbol_color); + + text_edit->add_constant_override("line_spacing", EDITOR_DEF("text_editor/theme/line_spacing", 4)); + + colors_cache.font_color = text_color; + colors_cache.symbol_color = symbol_color; + colors_cache.keyword_color = keyword_color; + colors_cache.basetype_color = basetype_color; + colors_cache.type_color = type_color; + colors_cache.comment_color = comment_color; + colors_cache.string_color = string_color; +} + +String TextEditor::get_name() { + String name; + + if (text_file->get_path().find("local://") == -1 && text_file->get_path().find("::") == -1) { + name = text_file->get_path().get_file(); + if (is_unsaved()) { + name += "(*)"; + } + } else if (text_file->get_name() != "") { + name = text_file->get_name(); + } else { + name = text_file->get_class() + "(" + itos(text_file->get_instance_id()) + ")"; + } + + return name; +} + +Ref<Texture> TextEditor::get_icon() { + + if (get_parent_control() && get_parent_control()->has_icon(text_file->get_class(), "EditorIcons")) { + return get_parent_control()->get_icon(text_file->get_class(), "EditorIcons"); + } + return Ref<Texture>(); +} + +RES TextEditor::get_edited_resource() const { + return text_file; +} + +void TextEditor::set_edited_resource(const RES &p_res) { + ERR_FAIL_COND(!text_file.is_null()); + + text_file = p_res; + + code_editor->get_text_edit()->set_text(text_file->get_text()); + code_editor->get_text_edit()->clear_undo_history(); + code_editor->get_text_edit()->tag_saved_version(); + + emit_signal("name_changed"); + code_editor->update_line_and_column(); +} + +void TextEditor::add_callback(const String &p_function, PoolStringArray p_args) { +} + +void TextEditor::set_debugger_active(bool p_active) { +} + +void TextEditor::get_breakpoints(List<int> *p_breakpoints) { +} + +void TextEditor::reload_text() { + + ERR_FAIL_COND(text_file.is_null()); + + TextEdit *te = code_editor->get_text_edit(); + int column = te->cursor_get_column(); + int row = te->cursor_get_line(); + int h = te->get_h_scroll(); + int v = te->get_v_scroll(); + + te->set_text(text_file->get_text()); + te->clear_undo_history(); + te->cursor_set_line(row); + te->cursor_set_column(column); + te->set_h_scroll(h); + te->set_v_scroll(v); + + te->tag_saved_version(); + + code_editor->update_line_and_column(); +} + +void TextEditor::_validate_script() { + emit_signal("name_changed"); + emit_signal("edited_script_changed"); +} + +void TextEditor::apply_code() { + text_file->set_text(code_editor->get_text_edit()->get_text()); +} + +bool TextEditor::is_unsaved() { + + return code_editor->get_text_edit()->get_version() != code_editor->get_text_edit()->get_saved_version(); +} + +Variant TextEditor::get_edit_state() { + + return code_editor->get_edit_state(); +} + +void TextEditor::set_edit_state(const Variant &p_state) { + + code_editor->set_edit_state(p_state); +} + +void TextEditor::trim_trailing_whitespace() { + + code_editor->trim_trailing_whitespace(); +} + +void TextEditor::convert_indent_to_spaces() { + + code_editor->convert_indent_to_spaces(); +} + +void TextEditor::convert_indent_to_tabs() { + + code_editor->convert_indent_to_tabs(); +} + +void TextEditor::tag_saved_version() { + + code_editor->get_text_edit()->tag_saved_version(); +} + +void TextEditor::goto_line(int p_line, bool p_with_error) { + + code_editor->goto_line(p_line); +} + +void TextEditor::ensure_focus() { + + code_editor->get_text_edit()->grab_focus(); +} + +Vector<String> TextEditor::get_functions() { + + return Vector<String>(); +} + +bool TextEditor::show_members_overview() { + return true; +} + +void TextEditor::update_settings() { + + code_editor->update_editor_settings(); +} + +void TextEditor::set_tooltip_request_func(String p_method, Object *p_obj) { + + code_editor->get_text_edit()->set_tooltip_request_func(p_obj, p_method, this); +} + +Control *TextEditor::get_edit_menu() { + + return edit_hb; +} + +void TextEditor::clear_edit_menu() { + memdelete(edit_hb); +} + +void TextEditor::_notification(int p_what) { + + switch (p_what) { + case NOTIFICATION_READY: + _load_theme_settings(); + set_syntax_highlighter(NULL); + break; + } +} + +void TextEditor::_edit_option(int p_op) { + TextEdit *tx = code_editor->get_text_edit(); + + switch (p_op) { + case EDIT_UNDO: { + + tx->undo(); + tx->call_deferred("grab_focus"); + } break; + case EDIT_REDO: { + + tx->redo(); + tx->call_deferred("grab_focus"); + } break; + case EDIT_CUT: { + + tx->cut(); + tx->call_deferred("grab_focus"); + } break; + case EDIT_COPY: { + + tx->copy(); + tx->call_deferred("grab_focus"); + } break; + case EDIT_PASTE: { + + tx->paste(); + tx->call_deferred("grab_focus"); + } break; + case EDIT_SELECT_ALL: { + + tx->select_all(); + tx->call_deferred("grab_focus"); + } break; + case EDIT_MOVE_LINE_UP: { + + code_editor->move_lines_up(); + } break; + case EDIT_MOVE_LINE_DOWN: { + + code_editor->move_lines_down(); + } break; + case EDIT_INDENT_LEFT: { + + tx->indent_left(); + } break; + case EDIT_INDENT_RIGHT: { + + tx->indent_right(); + } break; + case EDIT_DELETE_LINE: { + + code_editor->delete_lines(); + } break; + case EDIT_CLONE_DOWN: { + + code_editor->code_lines_down(); + } break; + case EDIT_TOGGLE_FOLD_LINE: { + + tx->toggle_fold_line(tx->cursor_get_line()); + tx->update(); + } break; + case EDIT_FOLD_ALL_LINES: { + + tx->fold_all_lines(); + tx->update(); + } break; + case EDIT_UNFOLD_ALL_LINES: { + + tx->unhide_all_lines(); + tx->update(); + } break; + case EDIT_TRIM_TRAILING_WHITESAPCE: { + + trim_trailing_whitespace(); + } break; + case EDIT_CONVERT_INDENT_TO_SPACES: { + + convert_indent_to_spaces(); + } break; + case EDIT_CONVERT_INDENT_TO_TABS: { + + convert_indent_to_tabs(); + } break; + case EDIT_TO_UPPERCASE: { + + _convert_case(CodeTextEditor::UPPER); + } break; + case EDIT_TO_LOWERCASE: { + + _convert_case(CodeTextEditor::LOWER); + } break; + case EDIT_CAPITALIZE: { + + _convert_case(CodeTextEditor::CAPITALIZE); + } break; + case SEARCH_FIND: { + + code_editor->get_find_replace_bar()->popup_search(); + } break; + case SEARCH_FIND_NEXT: { + + code_editor->get_find_replace_bar()->search_next(); + } break; + case SEARCH_FIND_PREV: { + + code_editor->get_find_replace_bar()->search_prev(); + } break; + case SEARCH_REPLACE: { + + code_editor->get_find_replace_bar()->popup_replace(); + } break; + case SEARCH_GOTO_LINE: { + + goto_line_dialog->popup_find_line(tx); + } break; + } +} + +void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { + + code_editor->convert_case(p_case); +} + +void TextEditor::_bind_methods() { + + ClassDB::bind_method("_validate_script", &TextEditor::_validate_script); + ClassDB::bind_method("_load_theme_settings", &TextEditor::_load_theme_settings); + ClassDB::bind_method("_edit_option", &TextEditor::_edit_option); + ClassDB::bind_method("_change_syntax_highlighter", &TextEditor::_change_syntax_highlighter); + ClassDB::bind_method("_text_edit_gui_input", &TextEditor::_text_edit_gui_input); +} + +static ScriptEditorBase *create_editor(const RES &p_resource) { + + if (Object::cast_to<TextFile>(*p_resource)) { + return memnew(TextEditor); + } + return NULL; +} + +void TextEditor::register_editor() { + + ScriptEditor::register_create_script_editor_function(create_editor); +} + +void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { + + Ref<InputEventMouseButton> mb = ev; + + if (mb.is_valid()) { + if (mb->get_button_index() == BUTTON_RIGHT) { + + int col, row; + TextEdit *tx = code_editor->get_text_edit(); + tx->_get_mouse_pos(mb->get_global_position() - tx->get_global_position(), row, col); + + tx->set_right_click_moves_caret(EditorSettings::get_singleton()->get("text_editor/cursor/right_click_moves_caret")); + bool can_fold = tx->can_fold(row); + bool is_folded = tx->is_folded(row); + + if (tx->is_right_click_moving_caret()) { + if (tx->is_selection_active()) { + + int from_line = tx->get_selection_from_line(); + int to_line = tx->get_selection_to_line(); + int from_column = tx->get_selection_from_column(); + int to_column = tx->get_selection_to_column(); + + if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { + // Right click is outside the seleted text + tx->deselect(); + } + } + if (!tx->is_selection_active()) { + tx->cursor_set_line(row, true, false); + tx->cursor_set_column(col); + } + } + + if (!mb->is_pressed()) { + _make_context_menu(tx->is_selection_active(), can_fold, is_folded); + } + } + } +} + +void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded) { + + context_menu->clear(); + if (p_selection) { + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/cut"), EDIT_CUT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/copy"), EDIT_COPY); + } + + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/paste"), EDIT_PASTE); + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/select_all"), EDIT_SELECT_ALL); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO); + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); + + if (p_selection) { + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE); + } + if (p_can_fold || p_is_folded) + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); + + context_menu->set_position(get_global_transform().xform(get_local_mouse_position())); + context_menu->set_size(Vector2(1, 1)); + context_menu->popup(); +} + +TextEditor::TextEditor() { + code_editor = memnew(CodeTextEditor); + add_child(code_editor); + code_editor->add_constant_override("separation", 0); + code_editor->connect("load_theme_settings", this, "_load_theme_settings"); + code_editor->connect("validate_script", this, "_validate_script"); + code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE); + code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + update_settings(); + + code_editor->get_text_edit()->set_context_menu_enabled(false); + code_editor->get_text_edit()->connect("gui_input", this, "_text_edit_gui_input"); + + context_menu = memnew(PopupMenu); + add_child(context_menu); + context_menu->connect("id_pressed", this, "_edit_option"); + + edit_hb = memnew(HBoxContainer); + + search_menu = memnew(MenuButton); + edit_hb->add_child(search_menu); + search_menu->set_text(TTR("Search")); + search_menu->get_popup()->connect("id_pressed", this, "_edit_option"); + + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); + search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); + + goto_line_dialog = memnew(GotoLineDialog); + add_child(goto_line_dialog); + + edit_menu = memnew(MenuButton); + edit_menu->set_text(TTR("Edit")); + edit_menu->get_popup()->connect("id_pressed", this, "_edit_option"); + + edit_hb->add_child(edit_menu); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/cut"), EDIT_CUT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/copy"), EDIT_COPY); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/paste"), EDIT_PASTE); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/select_all"), EDIT_SELECT_ALL); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); + + edit_menu->get_popup()->add_separator(); + PopupMenu *convert_case = memnew(PopupMenu); + convert_case->set_name("convert_case"); + edit_menu->get_popup()->add_child(convert_case); + edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); + convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase")), EDIT_TO_UPPERCASE); + convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase")), EDIT_TO_LOWERCASE); + convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize")), EDIT_CAPITALIZE); + convert_case->connect("id_pressed", this, "_edit_option"); + + highlighters["Standard"] = NULL; + highlighter_menu = memnew(PopupMenu); + highlighter_menu->set_name("highlighter_menu"); + edit_menu->get_popup()->add_child(highlighter_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu"); + highlighter_menu->add_radio_check_item(TTR("Standard")); + highlighter_menu->connect("id_pressed", this, "_change_syntax_highlighter"); + + code_editor->get_text_edit()->set_drag_forwarding(this); +} diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h new file mode 100644 index 0000000000..8b1983d891 --- /dev/null +++ b/editor/plugins/text_editor.h @@ -0,0 +1,146 @@ +/*************************************************************************/ +/* text_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 TEXT_EDITOR_H +#define TEXT_EDITOR_H + +#include "script_editor_plugin.h" + +class TextEditor : public ScriptEditorBase { + + GDCLASS(TextEditor, ScriptEditorBase) + +private: + CodeTextEditor *code_editor; + + Ref<TextFile> text_file; + + HBoxContainer *edit_hb; + MenuButton *edit_menu; + PopupMenu *highlighter_menu; + MenuButton *search_menu; + PopupMenu *context_menu; + + GotoLineDialog *goto_line_dialog; + + struct ColorsCache { + Color font_color; + Color symbol_color; + Color keyword_color; + Color basetype_color; + Color type_color; + Color comment_color; + Color string_color; + } colors_cache; + + enum { + EDIT_UNDO, + EDIT_REDO, + EDIT_CUT, + EDIT_COPY, + EDIT_PASTE, + EDIT_SELECT_ALL, + EDIT_TRIM_TRAILING_WHITESAPCE, + EDIT_CONVERT_INDENT_TO_SPACES, + EDIT_CONVERT_INDENT_TO_TABS, + EDIT_MOVE_LINE_UP, + EDIT_MOVE_LINE_DOWN, + EDIT_INDENT_RIGHT, + EDIT_INDENT_LEFT, + EDIT_DELETE_LINE, + EDIT_CLONE_DOWN, + EDIT_TO_UPPERCASE, + EDIT_TO_LOWERCASE, + EDIT_CAPITALIZE, + EDIT_TOGGLE_FOLD_LINE, + EDIT_FOLD_ALL_LINES, + EDIT_UNFOLD_ALL_LINES, + SEARCH_FIND, + SEARCH_FIND_NEXT, + SEARCH_FIND_PREV, + SEARCH_REPLACE, + SEARCH_GOTO_LINE, + }; + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + void _edit_option(int p_op); + void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded); + void _text_edit_gui_input(const Ref<InputEvent> &ev); + + Map<String, SyntaxHighlighter *> highlighters; + void _change_syntax_highlighter(int p_idx); + void _load_theme_settings(); + + void _convert_case(CodeTextEditor::CaseStyle p_case); + + void _validate_script(); + +public: + virtual void add_syntax_highlighter(SyntaxHighlighter *p_highlighter); + virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter); + + virtual String get_name(); + virtual Ref<Texture> get_icon(); + virtual RES get_edited_resource() const; + virtual void set_edited_resource(const RES &p_res); + void set_edited_file(const Ref<TextFile> &p_file); + virtual void reload_text(); + virtual void apply_code(); + virtual bool is_unsaved(); + virtual Variant get_edit_state(); + virtual void set_edit_state(const Variant &p_state); + virtual Vector<String> get_functions(); + virtual void get_breakpoints(List<int> *p_breakpoints); + virtual void goto_line(int p_line, bool p_with_error = false); + virtual void trim_trailing_whitespace(); + virtual void convert_indent_to_spaces(); + virtual void convert_indent_to_tabs(); + virtual void ensure_focus(); + virtual void tag_saved_version(); + virtual void update_settings(); + virtual bool show_members_overview(); + virtual bool can_lose_focus_on_node_selection() { return true; } + virtual void set_debugger_active(bool p_active); + virtual void set_tooltip_request_func(String p_method, Object *p_obj); + virtual void add_callback(const String &p_function, PoolStringArray p_args); + + virtual Control *get_edit_menu(); + virtual void clear_edit_menu(); + + static void register_editor(); + + TextEditor(); +}; + +#endif // TEXT_EDITOR_H diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index 19646f37b5..484da3b4f3 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -526,7 +526,7 @@ PoolVector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool era if (!erase) { ids = get_selected_tiles(); - if (ids.size() == 0 && ids[0] == TileMap::INVALID_CELL) + if (ids.size() == 0 || ids[0] == TileMap::INVALID_CELL) return PoolVector<Vector2>(); } else if (prev_id == TileMap::INVALID_CELL) { return PoolVector<Vector2>(); @@ -538,9 +538,7 @@ PoolVector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool era } } - Rect2i r = node->_edit_get_rect(); - r.position = r.position / node->get_cell_size(); - r.size = r.size / node->get_cell_size(); + Rect2i r = node->get_used_rect(); int area = r.get_area(); if (preview) { @@ -1029,7 +1027,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (points.size() == 0) return false; - undo_redo->create_action(TTR("Bucket Fill")); + _start_undo(TTR("Bucket Fill")); Dictionary op; op["id"] = get_selected_tiles(); @@ -1039,7 +1037,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { _fill_points(points, op); - undo_redo->commit_action(); + _finish_undo(); // We want to keep the bucket-tool active return true; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp new file mode 100644 index 0000000000..b9b8b07a2e --- /dev/null +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -0,0 +1,1217 @@ +#include "visual_shader_editor_plugin.h" + +#include "core/io/resource_loader.h" +#include "core/project_settings.h" +#include "editor/editor_properties.h" +#include "os/input.h" +#include "os/keyboard.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/panel.h" +#include "scene/main/viewport.h" + +Control *VisualShaderNodePlugin::create_editor(const Ref<VisualShaderNode> &p_node) { + + if (get_script_instance()) { + return get_script_instance()->call("create_editor", p_node); + } + return NULL; +} + +void VisualShaderNodePlugin::_bind_methods() { + + BIND_VMETHOD(MethodInfo(Variant::OBJECT, "create_editor", PropertyInfo(Variant::OBJECT, "for_node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode"))); +} + +/////////////////// + +void VisualShaderEditor::edit(VisualShader *p_visual_shader) { + + if (p_visual_shader) { + visual_shader = Ref<VisualShader>(p_visual_shader); + } else { + visual_shader.unref(); + } + + if (visual_shader.is_null()) { + hide(); + } else { + _update_graph(); + } +} + +void VisualShaderEditor::add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin) { + if (plugins.find(p_plugin) != -1) + return; + plugins.push_back(p_plugin); +} + +void VisualShaderEditor::remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin) { + plugins.erase(p_plugin); +} + +void VisualShaderEditor::add_custom_type(const String &p_name, const String &p_category, const Ref<Script> &p_script) { + + for (int i = 0; i < add_options.size(); i++) { + ERR_FAIL_COND(add_options[i].script == p_script); + } + + AddOption ao; + ao.name = p_name; + ao.script = p_script; + ao.category = p_category; + add_options.push_back(ao); + + _update_options_menu(); +} + +void VisualShaderEditor::remove_custom_type(const Ref<Script> &p_script) { + + for (int i = 0; i < add_options.size(); i++) { + if (add_options[i].script == p_script) { + add_options.remove(i); + return; + } + } + + _update_options_menu(); +} + +void VisualShaderEditor::_update_options_menu() { + + String prev_category; + add_node->get_popup()->clear(); + for (int i = 0; i < add_options.size(); i++) { + if (prev_category != add_options[i].category) { + add_node->get_popup()->add_separator(add_options[i].category); + } + add_node->get_popup()->add_item(add_options[i].name, i); + prev_category = add_options[i].category; + } +} + +Size2 VisualShaderEditor::get_minimum_size() const { + + return Size2(10, 200); +} + +void VisualShaderEditor::_draw_color_over_button(Object *obj, Color p_color) { + + Button *button = Object::cast_to<Button>(obj); + if (!button) + return; + + Ref<StyleBox> normal = get_stylebox("normal", "Button"); + button->draw_rect(Rect2(normal->get_offset(), button->get_size() - normal->get_minimum_size()), p_color); +} + +static Ref<StyleBoxEmpty> make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { + Ref<StyleBoxEmpty> style(memnew(StyleBoxEmpty)); + style->set_default_margin(MARGIN_LEFT, p_margin_left * EDSCALE); + style->set_default_margin(MARGIN_RIGHT, p_margin_right * EDSCALE); + style->set_default_margin(MARGIN_BOTTOM, p_margin_bottom * EDSCALE); + style->set_default_margin(MARGIN_TOP, p_margin_top * EDSCALE); + return style; +} + +void VisualShaderEditor::_update_graph() { + + if (updating) + return; + + graph->set_scroll_ofs(visual_shader->get_graph_offset() * EDSCALE); + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + graph->clear_connections(); + //erase all nodes + for (int i = 0; i < graph->get_child_count(); i++) { + + if (Object::cast_to<GraphNode>(graph->get_child(i))) { + memdelete(graph->get_child(i)); + i--; + } + } + + static const Color type_color[3] = { + Color::html("#61daf4"), + Color::html("#d67dee"), + Color::html("#f6a86e") + }; + + List<VisualShader::Connection> connections; + visual_shader->get_node_connections(type, &connections); + + Ref<StyleBoxEmpty> label_style = make_empty_stylebox(2, 1, 2, 1); + + Vector<int> nodes = visual_shader->get_node_list(type); + + for (int n_i = 0; n_i < nodes.size(); n_i++) { + + Vector2 position = visual_shader->get_node_position(type, nodes[n_i]); + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, nodes[n_i]); + + GraphNode *node = memnew(GraphNode); + graph->add_child(node); + + /*if (!vsnode->is_connected("changed", this, "_node_changed")) { + vsnode->connect("changed", this, "_node_changed", varray(vsnode->get_instance_id()), CONNECT_DEFERRED); + }*/ + + node->set_offset(position); + + node->set_title(vsnode->get_caption()); + node->set_name(itos(nodes[n_i])); + + if (nodes[n_i] >= 2) { + node->set_show_close_button(true); + node->connect("close_request", this, "_delete_request", varray(nodes[n_i]), CONNECT_DEFERRED); + } + + node->connect("dragged", this, "_node_dragged", varray(nodes[n_i])); + + Control *custom_editor = NULL; + int port_offset = 0; + + Ref<VisualShaderNodeUniform> uniform = vsnode; + if (uniform.is_valid()) { + LineEdit *uniform_name = memnew(LineEdit); + uniform_name->set_text(uniform->get_uniform_name()); + node->add_child(uniform_name); + uniform_name->connect("text_entered", this, "_line_edit_changed", varray(uniform_name, nodes[n_i])); + uniform_name->connect("focus_exited", this, "_line_edit_focus_out", varray(uniform_name, nodes[n_i])); + + if (vsnode->get_input_port_count() == 0 && vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { + //shortcut + VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0); + node->set_slot(0, false, VisualShaderNode::PORT_TYPE_SCALAR, Color(), true, port_right, type_color[port_right]); + continue; + } + port_offset++; + } + + for (int i = 0; i < plugins.size(); i++) { + custom_editor = plugins[i]->create_editor(vsnode); + if (custom_editor) { + break; + } + } + + if (custom_editor && vsnode->get_output_port_count() > 0 && vsnode->get_output_port_name(0) == "" && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == "")) { + //will be embedded in first port + } else if (custom_editor) { + port_offset++; + node->add_child(custom_editor); + custom_editor = NULL; + } + + for (int i = 0; i < MAX(vsnode->get_input_port_count(), vsnode->get_output_port_count()); i++) { + + if (vsnode->is_port_separator(i)) { + node->add_child(memnew(HSeparator)); + port_offset++; + } + + bool valid_left = i < vsnode->get_input_port_count(); + VisualShaderNode::PortType port_left = VisualShaderNode::PORT_TYPE_SCALAR; + bool port_left_used = false; + String name_left; + if (valid_left) { + name_left = vsnode->get_input_port_name(i); + port_left = vsnode->get_input_port_type(i); + for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { + if (E->get().to_node == nodes[n_i] && E->get().to_port == i) { + port_left_used = true; + } + } + } + + bool valid_right = i < vsnode->get_output_port_count(); + VisualShaderNode::PortType port_right = VisualShaderNode::PORT_TYPE_SCALAR; + String name_right; + if (valid_right) { + name_right = vsnode->get_output_port_name(i); + port_right = vsnode->get_output_port_type(i); + } + + HBoxContainer *hb = memnew(HBoxContainer); + + Variant default_value; + + if (valid_left && !port_left_used) { + default_value = vsnode->get_input_port_default_value(i); + } + + if (default_value.get_type() != Variant::NIL) { // only a label + Button *button = memnew(Button); + hb->add_child(button); + button->connect("pressed", this, "_edit_port_default_input", varray(button, nodes[n_i], i)); + + switch (default_value.get_type()) { + + case Variant::COLOR: { + button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + button->connect("draw", this, "_draw_color_over_button", varray(button, default_value)); + } break; + case Variant::INT: + case Variant::REAL: { + button->set_text(String::num(default_value, 4)); + } break; + case Variant::VECTOR3: { + Vector3 v = default_value; + button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3)); + } break; + default: {} + } + } + + if (i == 0 && custom_editor) { + + hb->add_child(custom_editor); + custom_editor->set_h_size_flags(SIZE_EXPAND_FILL); + } else { + + if (valid_left) { + + Label *label = memnew(Label); + label->set_text(name_left); + label->add_style_override("normal", label_style); //more compact + hb->add_child(label); + } + + hb->add_spacer(); + + if (valid_right) { + + Label *label = memnew(Label); + label->set_text(name_right); + label->set_align(Label::ALIGN_RIGHT); + label->add_style_override("normal", label_style); //more compact + hb->add_child(label); + } + } + + if (valid_right && edit_type->get_selected() == VisualShader::TYPE_FRAGMENT) { + TextureButton *preview = memnew(TextureButton); + preview->set_toggle_mode(true); + preview->set_normal_texture(get_icon("GuiVisibilityHidden", "EditorIcons")); + preview->set_pressed_texture(get_icon("GuiVisibilityVisible", "EditorIcons")); + preview->set_v_size_flags(SIZE_SHRINK_CENTER); + + if (vsnode->get_output_port_for_preview() == i) { + preview->set_pressed(true); + } + + preview->connect("pressed", this, "_preview_select_port", varray(nodes[n_i], i), CONNECT_DEFERRED); + hb->add_child(preview); + } + + node->add_child(hb); + + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); + } + + if (vsnode->get_output_port_for_preview() >= 0) { + VisualShaderNodePortPreview *port_preview = memnew(VisualShaderNodePortPreview); + port_preview->setup(visual_shader, type, nodes[n_i], vsnode->get_output_port_for_preview()); + port_preview->set_h_size_flags(SIZE_SHRINK_CENTER); + node->add_child(port_preview); + } + + String error = vsnode->get_warning(visual_shader->get_mode(), type); + if (error != String()) { + Label *error_label = memnew(Label); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + error_label->set_text(error); + node->add_child(error_label); + } + } + + for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { + + int from = E->get().from_node; + int from_idx = E->get().from_port; + int to = E->get().to_node; + int to_idx = E->get().to_port; + + graph->connect_node(itos(from), from_idx, itos(to), to_idx); + } +} + +void VisualShaderEditor::_preview_select_port(int p_node, int p_port) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + Ref<VisualShaderNode> node = visual_shader->get_node(type, p_node); + if (node.is_null()) { + return; + } + + if (node->get_output_port_for_preview() == p_port) { + p_port = -1; //toggle it + } + undo_redo->create_action("Set Uniform Name"); + undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", p_port); + undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", node->get_output_port_for_preview()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_line_edit_changed(const String &p_text, Object *line_edit, int p_node_id) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + Ref<VisualShaderNodeUniform> node = visual_shader->get_node(type, p_node_id); + ERR_FAIL_COND(!node.is_valid()); + + String validated_name = visual_shader->validate_uniform_name(p_text, node); + + updating = true; + undo_redo->create_action("Set Uniform Name"); + undo_redo->add_do_method(node.ptr(), "set_uniform_name", validated_name); + undo_redo->add_undo_method(node.ptr(), "set_uniform_name", node->get_uniform_name()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + + Object::cast_to<LineEdit>(line_edit)->set_text(validated_name); +} + +void VisualShaderEditor::_line_edit_focus_out(Object *line_edit, int p_node_id) { + + String text = Object::cast_to<LineEdit>(line_edit)->get_text(); + _line_edit_changed(text, line_edit, p_node_id); +} + +void VisualShaderEditor::_port_edited() { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + Variant value = property_editor->get_variant(); + Ref<VisualShaderNode> vsn = visual_shader->get_node(type, editing_node); + ERR_FAIL_COND(!vsn.is_valid()); + + undo_redo->create_action("Set Input Default Port"); + undo_redo->add_do_method(vsn.ptr(), "set_input_port_default_value", editing_port, value); + undo_redo->add_undo_method(vsn.ptr(), "set_input_port_default_value", editing_port, vsn->get_input_port_default_value(editing_port)); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + property_editor->hide(); +} + +void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node, int p_port) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + Ref<VisualShaderNode> vsn = visual_shader->get_node(type, p_node); + + Button *button = Object::cast_to<Button>(p_button); + ERR_FAIL_COND(!button); + Variant value = vsn->get_input_port_default_value(p_port); + property_editor->set_global_position(button->get_global_position() + Vector2(0, button->get_size().height)); + property_editor->edit(NULL, "", value.get_type(), value, 0, ""); + property_editor->popup(); + editing_node = p_node; + editing_port = p_port; +} + +void VisualShaderEditor::_add_node(int p_idx) { + + ERR_FAIL_INDEX(p_idx, add_options.size()); + + Ref<VisualShaderNode> vsnode; + + if (add_options[p_idx].type != String()) { + VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(add_options[p_idx].type)); + ERR_FAIL_COND(!vsn); + vsnode = Ref<VisualShaderNode>(vsn); + } else { + ERR_FAIL_COND(add_options[p_idx].script.is_null()); + String base_type = add_options[p_idx].script->get_instance_base_type(); + VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(base_type)); + ERR_FAIL_COND(!vsn); + vsnode = Ref<VisualShaderNode>(vsn); + vsnode->set_script(add_options[p_idx].script.get_ref_ptr()); + } + + Point2 position = (graph->get_scroll_ofs() + graph->get_size() * 0.5) / EDSCALE; + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + int id_to_use = visual_shader->get_valid_node_id(type); + + undo_redo->create_action("Add Node to Visual Shader"); + undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, vsnode, position, id_to_use); + undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_to_use); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + updating = true; + undo_redo->create_action("Node Moved"); + undo_redo->add_do_method(visual_shader.ptr(), "set_node_position", type, p_node, p_to); + undo_redo->add_undo_method(visual_shader.ptr(), "set_node_position", type, p_node, p_from); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; +} + +void VisualShaderEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + int from = p_from.to_int(); + int to = p_to.to_int(); + + if (!visual_shader->can_connect_nodes(type, from, p_from_index, to, p_to_index)) { + EditorNode::get_singleton()->show_warning(TTR("Unable to connect, port may be in use or connection may be invalid.")); + return; + } + + undo_redo->create_action("Nodes Connected"); + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + + graph->disconnect_node(p_from, p_from_index, p_to, p_to_index); + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + int from = p_from.to_int(); + int to = p_to.to_int(); + + //updating = true; seems graph edit can handle this, no need to protect + undo_redo->create_action("Nodes Disconnected"); + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, from, p_from_index, to, p_to_index); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + //updating = false; +} + +void VisualShaderEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { +} + +void VisualShaderEditor::_delete_request(int which) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + undo_redo->create_action("Delete Node"); + undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, which); + undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, visual_shader->get_node(type, which), visual_shader->get_node_position(type, which), which); + + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().from_node == which || E->get().to_node == which) { + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_node_selected(Object *p_node) { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + GraphNode *gn = Object::cast_to<GraphNode>(p_node); + ERR_FAIL_COND(!gn); + + int id = String(gn->get_name()).to_int(); + + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); + ERR_FAIL_COND(!vsnode.is_valid()); + + //do not rely on this, makes editor more complex + //EditorNode::get_singleton()->push_item(vsnode.ptr(), "", true); +} + +void VisualShaderEditor::_input(const Ref<InputEvent> p_event) { + if (graph->has_focus()) { + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + add_node->get_popup()->set_position(get_viewport()->get_mouse_position()); + add_node->get_popup()->show_modal(); + } + } +} + +void VisualShaderEditor::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + + error_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + } + + if (p_what == NOTIFICATION_PROCESS) { + } +} + +void VisualShaderEditor::_scroll_changed(const Vector2 &p_scroll) { + if (updating) + return; + updating = true; + visual_shader->set_graph_offset(p_scroll / EDSCALE); + updating = false; +} + +void VisualShaderEditor::_node_changed(int p_id) { + if (updating) + return; + + if (is_visible_in_tree()) { + _update_graph(); + } +} + +void VisualShaderEditor::_duplicate_nodes() { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + + List<int> nodes; + + for (int i = 0; i < graph->get_child_count(); i++) { + + if (Object::cast_to<GraphNode>(graph->get_child(i))) { + int id = String(graph->get_child(i)->get_name()).to_int(); + Ref<VisualShaderNode> node = visual_shader->get_node(type, id); + Ref<VisualShaderNodeOutput> output = node; + if (output.is_valid()) //cant duplicate output + continue; + if (node.is_valid()) { + nodes.push_back(id); + } + } + } + + if (nodes.empty()) + return; + + undo_redo->create_action("Duplicate Nodes"); + + int base_id = visual_shader->get_valid_node_id(type); + int id_from = base_id; + Map<int, int> connection_remap; + + for (List<int>::Element *E = nodes.front(); E; E = E->next()) { + + connection_remap[E->get()] = id_from; + Ref<VisualShaderNode> node = visual_shader->get_node(type, E->get()); + + Ref<VisualShaderNode> dupli = node->duplicate(); + + undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(type, E->get()) + Vector2(10, 10) * EDSCALE, id_from); + undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_from); + + id_from++; + } + + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (connection_remap.has(E->get().from_node) && connection_remap.has(E->get().to_node)) { + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, connection_remap[E->get().from_node], E->get().from_port, connection_remap[E->get().to_node], E->get().to_port); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + //reselect + for (int i = 0; i < graph->get_child_count(); i++) { + + if (Object::cast_to<GraphNode>(graph->get_child(i))) { + int id = String(graph->get_child(i)->get_name()).to_int(); + if (nodes.find(id)) { + Object::cast_to<GraphNode>(graph->get_child(i))->set_selected(true); + } else { + Object::cast_to<GraphNode>(graph->get_child(i))->set_selected(false); + } + } + } +} + +void VisualShaderEditor::_mode_selected(int p_id) { + _update_graph(); +} + +void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> input, String name) { + + String prev_name = input->get_input_name(); + + if (name == prev_name) + return; + + bool type_changed = input->get_input_type_by_name(name) != input->get_input_type_by_name(prev_name); + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo->create_action("Visual Shader Input Type Changed"); + + undo_redo->add_do_method(input.ptr(), "set_input_name", name); + undo_redo->add_undo_method(input.ptr(), "set_input_name", prev_name); + + if (type_changed) { + //restore connections if type changed + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + int id = visual_shader->find_node_id(type, input); + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().from_node == id) { + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + } + } + + undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_graph"); + undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_graph"); + + undo_redo->commit_action(); +} + +void VisualShaderEditor::_bind_methods() { + + ClassDB::bind_method("_update_graph", &VisualShaderEditor::_update_graph); + ClassDB::bind_method("_add_node", &VisualShaderEditor::_add_node); + ClassDB::bind_method("_node_dragged", &VisualShaderEditor::_node_dragged); + ClassDB::bind_method("_connection_request", &VisualShaderEditor::_connection_request); + ClassDB::bind_method("_disconnection_request", &VisualShaderEditor::_disconnection_request); + ClassDB::bind_method("_node_selected", &VisualShaderEditor::_node_selected); + ClassDB::bind_method("_scroll_changed", &VisualShaderEditor::_scroll_changed); + ClassDB::bind_method("_delete_request", &VisualShaderEditor::_delete_request); + ClassDB::bind_method("_node_changed", &VisualShaderEditor::_node_changed); + ClassDB::bind_method("_edit_port_default_input", &VisualShaderEditor::_edit_port_default_input); + ClassDB::bind_method("_port_edited", &VisualShaderEditor::_port_edited); + ClassDB::bind_method("_connection_to_empty", &VisualShaderEditor::_connection_to_empty); + ClassDB::bind_method("_line_edit_focus_out", &VisualShaderEditor::_line_edit_focus_out); + ClassDB::bind_method("_line_edit_changed", &VisualShaderEditor::_line_edit_changed); + ClassDB::bind_method("_duplicate_nodes", &VisualShaderEditor::_duplicate_nodes); + ClassDB::bind_method("_mode_selected", &VisualShaderEditor::_mode_selected); + ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item); + ClassDB::bind_method("_preview_select_port", &VisualShaderEditor::_preview_select_port); + ClassDB::bind_method("_input", &VisualShaderEditor::_input); +} + +VisualShaderEditor *VisualShaderEditor::singleton = NULL; + +VisualShaderEditor::VisualShaderEditor() { + + singleton = this; + updating = false; + + graph = memnew(GraphEdit); + add_child(graph); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_TRANSFORM); + //graph->add_valid_left_disconnect_type(0); + graph->set_v_size_flags(SIZE_EXPAND_FILL); + graph->connect("connection_request", this, "_connection_request", varray(), CONNECT_DEFERRED); + graph->connect("disconnection_request", this, "_disconnection_request", varray(), CONNECT_DEFERRED); + graph->connect("node_selected", this, "_node_selected"); + graph->connect("scroll_offset_changed", this, "_scroll_changed"); + graph->connect("duplicate_nodes_request", this, "_duplicate_nodes"); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_TRANSFORM, VisualShaderNode::PORT_TYPE_TRANSFORM); + + VSeparator *vs = memnew(VSeparator); + graph->get_zoom_hbox()->add_child(vs); + graph->get_zoom_hbox()->move_child(vs, 0); + + edit_type = memnew(OptionButton); + edit_type->add_item(TTR("Vertex")); + edit_type->add_item(TTR("Fragment")); + edit_type->add_item(TTR("Light")); + edit_type->select(1); + edit_type->connect("item_selected", this, "_mode_selected"); + graph->get_zoom_hbox()->add_child(edit_type); + graph->get_zoom_hbox()->move_child(edit_type, 0); + + add_node = memnew(MenuButton); + graph->get_zoom_hbox()->add_child(add_node); + add_node->set_text(TTR("Add Node..")); + graph->get_zoom_hbox()->move_child(add_node, 0); + add_node->get_popup()->connect("id_pressed", this, "_add_node"); + + add_options.push_back(AddOption("Scalar", "Constants", "VisualShaderNodeScalarConstant")); + add_options.push_back(AddOption("Vector", "Constants", "VisualShaderNodeVec3Constant")); + add_options.push_back(AddOption("Color", "Constants", "VisualShaderNodeColorConstant")); + add_options.push_back(AddOption("Transform", "Constants", "VisualShaderNodeTransformConstant")); + add_options.push_back(AddOption("Texture", "Constants", "VisualShaderNodeTexture")); + add_options.push_back(AddOption("CubeMap", "Constants", "VisualShaderNodeCubeMap")); + add_options.push_back(AddOption("ScalarOp", "Operators", "VisualShaderNodeScalarOp")); + add_options.push_back(AddOption("VectorOp", "Operators", "VisualShaderNodeVectorOp")); + add_options.push_back(AddOption("ColorOp", "Operators", "VisualShaderNodeColorOp")); + add_options.push_back(AddOption("TransformMult", "Operators", "VisualShaderNodeTransformMult")); + add_options.push_back(AddOption("TransformVectorMult", "Operators", "VisualShaderNodeTransformVecMult")); + add_options.push_back(AddOption("ScalarFunc", "Functions", "VisualShaderNodeScalarFunc")); + add_options.push_back(AddOption("VectorFunc", "Functions", "VisualShaderNodeVectorFunc")); + add_options.push_back(AddOption("DotProduct", "Functions", "VisualShaderNodeDotProduct")); + add_options.push_back(AddOption("VectorLen", "Functions", "VisualShaderNodeVectorLen")); + add_options.push_back(AddOption("ScalarInterp", "Interpolation", "VisualShaderNodeScalarInterp")); + add_options.push_back(AddOption("VectorInterp", "Interpolation", "VisualShaderNodeVectorInterp")); + add_options.push_back(AddOption("VectorCompose", "Compose", "VisualShaderNodeVectorCompose")); + add_options.push_back(AddOption("TransformCompose", "Compose", "VisualShaderNodeTransformCompose")); + add_options.push_back(AddOption("VectorDecompose", "Decompose", "VisualShaderNodeVectorDecompose")); + add_options.push_back(AddOption("TransformDecompose", "Decompose", "VisualShaderNodeTransformDecompose")); + add_options.push_back(AddOption("Scalar", "Uniforms", "VisualShaderNodeScalarUniform")); + add_options.push_back(AddOption("Vector", "Uniforms", "VisualShaderNodeVec3Uniform")); + add_options.push_back(AddOption("Color", "Uniforms", "VisualShaderNodeColorUniform")); + add_options.push_back(AddOption("Transform", "Uniforms", "VisualShaderNodeTransformUniform")); + add_options.push_back(AddOption("Texture", "Uniforms", "VisualShaderNodeTextureUniform")); + add_options.push_back(AddOption("CubeMap", "Uniforms", "VisualShaderNodeCubeMapUniform")); + add_options.push_back(AddOption("Input", "Inputs", "VisualShaderNodeInput")); + + _update_options_menu(); + + error_panel = memnew(PanelContainer); + add_child(error_panel); + error_label = memnew(Label); + error_panel->add_child(error_label); + error_label->set_text("eh"); + error_panel->hide(); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + Ref<VisualShaderNodePluginDefault> default_plugin; + default_plugin.instance(); + add_plugin(default_plugin); + + property_editor = memnew(CustomPropertyEditor); + add_child(property_editor); + + property_editor->connect("variant_changed", this, "_port_edited"); +} + +void VisualShaderEditorPlugin::edit(Object *p_object) { + + visual_shader_editor->edit(Object::cast_to<VisualShader>(p_object)); +} + +bool VisualShaderEditorPlugin::handles(Object *p_object) const { + + return p_object->is_class("VisualShader"); +} + +void VisualShaderEditorPlugin::make_visible(bool p_visible) { + + if (p_visible) { + //editor->hide_animation_player_editors(); + //editor->animation_panel_make_visible(true); + button->show(); + editor->make_bottom_panel_item_visible(visual_shader_editor); + visual_shader_editor->set_process_input(true); + //visual_shader_editor->set_process(true); + } else { + + if (visual_shader_editor->is_visible_in_tree()) + editor->hide_bottom_panel(); + button->hide(); + visual_shader_editor->set_process_input(false); + //visual_shader_editor->set_process(false); + } +} + +VisualShaderEditorPlugin::VisualShaderEditorPlugin(EditorNode *p_node) { + + editor = p_node; + visual_shader_editor = memnew(VisualShaderEditor); + visual_shader_editor->set_custom_minimum_size(Size2(0, 300)); + + button = editor->add_bottom_panel_item(TTR("VisualShader"), visual_shader_editor); + button->hide(); +} + +VisualShaderEditorPlugin::~VisualShaderEditorPlugin() { +} + +//////////////// + +class VisualShaderNodePluginInputEditor : public OptionButton { + GDCLASS(VisualShaderNodePluginInputEditor, OptionButton) + + Ref<VisualShaderNodeInput> input; + +protected: + static void _bind_methods() { + ClassDB::bind_method("_item_selected", &VisualShaderNodePluginInputEditor::_item_selected); + } + +public: + void _notification(int p_what) { + if (p_what == NOTIFICATION_READY) { + connect("item_selected", this, "_item_selected"); + } + } + + void _item_selected(int p_item) { + VisualShaderEditor::get_singleton()->call_deferred("_input_select_item", input, get_item_text(p_item)); + } + + void setup(const Ref<VisualShaderNodeInput> &p_input) { + input = p_input; + Ref<Texture> type_icon[3] = { + EditorNode::get_singleton()->get_gui_base()->get_icon("float", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_icon("Vector3", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_icon("Transform", "EditorIcons"), + }; + + add_item("[None]"); + int to_select = -1; + for (int i = 0; i < input->get_input_index_count(); i++) { + if (input->get_input_name() == input->get_input_index_name(i)) { + to_select = i + 1; + } + add_icon_item(type_icon[input->get_input_index_type(i)], input->get_input_index_name(i)); + } + + if (to_select >= 0) { + select(to_select); + } + } +}; + +class VisualShaderNodePluginDefaultEditor : public VBoxContainer { + GDCLASS(VisualShaderNodePluginDefaultEditor, VBoxContainer) +public: + void _property_changed(const String &prop, const Variant &p_value) { + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + updating = true; + undo_redo->create_action("Edit Visual Property: " + prop, UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(node.ptr(), prop, p_value); + undo_redo->add_undo_property(node.ptr(), prop, node->get(prop)); + undo_redo->commit_action(); + updating = false; + } + + void _node_changed() { + if (updating) + return; + for (int i = 0; i < properties.size(); i++) { + properties[i]->update_property(); + } + } + + void _refresh_request() { + VisualShaderEditor::get_singleton()->call_deferred("_update_graph"); + } + + bool updating; + Ref<VisualShaderNode> node; + Vector<EditorProperty *> properties; + + void setup(Vector<EditorProperty *> p_properties, const Vector<StringName> &p_names, Ref<VisualShaderNode> p_node) { + updating = false; + node = p_node; + properties = p_properties; + + for (int i = 0; i < p_properties.size(); i++) { + + add_child(p_properties[i]); + + properties[i]->connect("property_changed", this, "_property_changed"); + properties[i]->set_object_and_property(node.ptr(), p_names[i]); + properties[i]->update_property(); + properties[i]->set_name_split_ratio(0); + } + node->connect("changed", this, "_node_changed"); + node->connect("editor_refresh_request", this, "_refresh_request", varray(), CONNECT_DEFERRED); + } + + static void _bind_methods() { + ClassDB::bind_method("_property_changed", &VisualShaderNodePluginDefaultEditor::_property_changed); + ClassDB::bind_method("_node_changed", &VisualShaderNodePluginDefaultEditor::_node_changed); + ClassDB::bind_method("_refresh_request", &VisualShaderNodePluginDefaultEditor::_refresh_request); + } +}; + +Control *VisualShaderNodePluginDefault::create_editor(const Ref<VisualShaderNode> &p_node) { + + if (p_node->is_class("VisualShaderNodeInput")) { + //create input + VisualShaderNodePluginInputEditor *input_editor = memnew(VisualShaderNodePluginInputEditor); + input_editor->setup(p_node); + return input_editor; + } + + Vector<StringName> properties = p_node->get_editable_properties(); + if (properties.size() == 0) { + return NULL; + } + + List<PropertyInfo> props; + p_node->get_property_list(&props); + + Vector<PropertyInfo> pinfo; + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + for (int i = 0; i < properties.size(); i++) { + if (E->get().name == String(properties[i])) { + pinfo.push_back(E->get()); + } + } + } + + if (pinfo.size() == 0) + return NULL; + + properties.clear(); + + Ref<VisualShaderNode> node = p_node; + Vector<EditorProperty *> editors; + + for (int i = 0; i < pinfo.size(); i++) { + + EditorProperty *prop = EditorInspector::instantiate_property_editor(node.ptr(), pinfo[i].type, pinfo[i].name, pinfo[i].hint, pinfo[i].hint_string, pinfo[i].usage); + if (!prop) + return NULL; + + if (Object::cast_to<EditorPropertyResource>(prop)) { + Object::cast_to<EditorPropertyResource>(prop)->set_use_sub_inspector(false); + prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyTransform>(prop)) { + prop->set_custom_minimum_size(Size2(250 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyFloat>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) { + prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyEnum>(prop)) { + prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + Object::cast_to<EditorPropertyEnum>(prop)->set_option_button_clip(false); + } + + editors.push_back(prop); + properties.push_back(pinfo[i].name); + } + VisualShaderNodePluginDefaultEditor *editor = memnew(VisualShaderNodePluginDefaultEditor); + editor->setup(editors, properties, p_node); + return editor; +} + +void EditorPropertyShaderMode::_option_selected(int p_which) { + + //will not use this, instead will do all the logic setting manually + //emit_signal("property_changed", get_edited_property(), p_which); + + Ref<VisualShader> visual_shader(Object::cast_to<VisualShader>(get_edited_object())); + + if (visual_shader->get_mode() == p_which) + return; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo->create_action("Visual Shader Mode Changed"); + //do is easy + undo_redo->add_do_method(visual_shader.ptr(), "set_mode", p_which); + undo_redo->add_undo_method(visual_shader.ptr(), "set_mode", visual_shader->get_mode()); + //now undo is hell + + //1. restore connections to output + for (int i = 0; i < VisualShader::TYPE_MAX; i++) { + + VisualShader::Type type = VisualShader::Type(i); + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().to_node == VisualShader::NODE_ID_OUTPUT) { + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + } + } + //2. restore input indices + for (int i = 0; i < VisualShader::TYPE_MAX; i++) { + + VisualShader::Type type = VisualShader::Type(i); + Vector<int> nodes = visual_shader->get_node_list(type); + for (int i = 0; i < nodes.size(); i++) { + Ref<VisualShaderNodeInput> input = visual_shader->get_node(type, nodes[i]); + if (!input.is_valid()) { + continue; + } + + undo_redo->add_undo_method(input.ptr(), "set_input_name", input->get_input_name()); + } + } + + //3. restore enums and flags + List<PropertyInfo> props; + visual_shader->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + if (E->get().name.begins_with("flags/") || E->get().name.begins_with("modes/")) { + undo_redo->add_undo_property(visual_shader.ptr(), E->get().name, visual_shader->get(E->get().name)); + } + } + + //update graph + undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_graph"); + undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_graph"); + + undo_redo->commit_action(); +} + +void EditorPropertyShaderMode::update_property() { + + int which = get_edited_object()->get(get_edited_property()); + options->select(which); +} + +void EditorPropertyShaderMode::setup(const Vector<String> &p_options) { + for (int i = 0; i < p_options.size(); i++) { + options->add_item(p_options[i], i); + } +} + +void EditorPropertyShaderMode::set_option_button_clip(bool p_enable) { + options->set_clip_text(p_enable); +} + +void EditorPropertyShaderMode::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyShaderMode::_option_selected); +} + +EditorPropertyShaderMode::EditorPropertyShaderMode() { + options = memnew(OptionButton); + options->set_clip_text(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", this, "_option_selected"); +} + +bool EditorInspectorShaderModePlugin::can_handle(Object *p_object) { + return true; //can handle everything +} + +void EditorInspectorShaderModePlugin::parse_begin(Object *p_object) { + //do none +} + +bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) { + + if (p_path == "mode" && p_object->is_class("VisualShader") && p_type == Variant::INT) { + + EditorPropertyShaderMode *editor = memnew(EditorPropertyShaderMode); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + + return true; + } + + return false; //can be overriden, although it will most likely be last anyway +} + +void EditorInspectorShaderModePlugin::parse_end() { + //do none +} +////////////////////////////////// + +void VisualShaderNodePortPreview::_shader_changed() { + if (shader.is_null()) { + return; + } + + Vector<VisualShader::DefaultTextureParam> default_textures; + String shader_code = shader->generate_preview_shader(type, node, port, default_textures); + + Ref<Shader> preview_shader; + preview_shader.instance(); + preview_shader->set_code(shader_code); + for (int i = 0; i < default_textures.size(); i++) { + preview_shader->set_default_texture_param(default_textures[i].name, default_textures[i].param); + } + + Ref<ShaderMaterial> material; + material.instance(); + material->set_shader(preview_shader); + + //find if a material is also being edited and copy parameters to this one + + for (int i = EditorNode::get_singleton()->get_editor_history()->get_path_size() - 1; i >= 0; i--) { + Object *object = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_history()->get_path_object(i)); + if (!object) + continue; + ShaderMaterial *src_mat = Object::cast_to<ShaderMaterial>(object); + if (src_mat && src_mat->get_shader().is_valid()) { + + List<PropertyInfo> params; + src_mat->get_shader()->get_param_list(¶ms); + for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) { + material->set(E->get().name, src_mat->get(E->get().name)); + } + } + } + + set_material(material); +} + +void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port) { + + shader = p_shader; + shader->connect("changed", this, "_shader_changed"); + type = p_type; + port = p_port; + node = p_node; + update(); + _shader_changed(); +} + +Size2 VisualShaderNodePortPreview::get_minimum_size() const { + return Size2(100, 100) * EDSCALE; +} + +void VisualShaderNodePortPreview::_notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) { + Vector<Vector2> points; + Vector<Vector2> uvs; + Vector<Color> colors; + points.push_back(Vector2()); + uvs.push_back(Vector2(0, 0)); + colors.push_back(Color(1, 1, 1, 1)); + points.push_back(Vector2(get_size().width, 0)); + uvs.push_back(Vector2(1, 0)); + colors.push_back(Color(1, 1, 1, 1)); + points.push_back(get_size()); + uvs.push_back(Vector2(1, 1)); + colors.push_back(Color(1, 1, 1, 1)); + points.push_back(Vector2(0, get_size().height)); + uvs.push_back(Vector2(0, 1)); + colors.push_back(Color(1, 1, 1, 1)); + + draw_primitive(points, colors, uvs); + } +} + +void VisualShaderNodePortPreview::_bind_methods() { + ClassDB::bind_method("_shader_changed", &VisualShaderNodePortPreview::_shader_changed); +} + +VisualShaderNodePortPreview::VisualShaderNodePortPreview() { +} diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h new file mode 100644 index 0000000000..f86374ff6b --- /dev/null +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -0,0 +1,187 @@ +#ifndef VISUAL_SHADER_EDITOR_PLUGIN_H +#define VISUAL_SHADER_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/property_editor.h" +#include "scene/gui/button.h" +#include "scene/gui/graph_edit.h" +#include "scene/gui/popup.h" +#include "scene/gui/tree.h" +#include "scene/resources/visual_shader.h" + +class VisualShaderNodePlugin : public Reference { + + GDCLASS(VisualShaderNodePlugin, Reference) +protected: + static void _bind_methods(); + +public: + virtual Control *create_editor(const Ref<VisualShaderNode> &p_node); +}; + +class VisualShaderEditor : public VBoxContainer { + + GDCLASS(VisualShaderEditor, VBoxContainer); + + CustomPropertyEditor *property_editor; + int editing_node; + int editing_port; + + Ref<VisualShader> visual_shader; + GraphEdit *graph; + MenuButton *add_node; + + OptionButton *edit_type; + + PanelContainer *error_panel; + Label *error_label; + + UndoRedo *undo_redo; + + void _update_graph(); + + struct AddOption { + String name; + String category; + String type; + Ref<Script> script; + AddOption(const String &p_name = String(), const String &p_category = String(), const String &p_type = String()) { + name = p_name; + type = p_type; + category = p_category; + } + }; + + Vector<AddOption> add_options; + + void _draw_color_over_button(Object *obj, Color p_color); + + void _add_node(int p_idx); + void _update_options_menu(); + + static VisualShaderEditor *singleton; + + void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node); + bool updating; + + void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); + void _disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); + + void _scroll_changed(const Vector2 &p_scroll); + void _node_selected(Object *p_node); + + void _delete_request(int); + + void _removed_from_graph(); + + void _node_changed(int p_id); + + void _edit_port_default_input(Object *p_button, int p_node, int p_port); + void _port_edited(); + + void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); + + void _line_edit_changed(const String &p_text, Object *line_edit, int p_node_id); + void _line_edit_focus_out(Object *line_edit, int p_node_id); + + void _duplicate_nodes(); + + Vector<Ref<VisualShaderNodePlugin> > plugins; + + void _mode_selected(int p_id); + + void _input_select_item(Ref<VisualShaderNodeInput> input, String name); + + void _preview_select_port(int p_node, int p_port); + void _input(const Ref<InputEvent> p_event); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); + void remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); + + static VisualShaderEditor *get_singleton() { return singleton; } + + void add_custom_type(const String &p_name, const String &p_category, const Ref<Script> &p_script); + void remove_custom_type(const Ref<Script> &p_script); + + virtual Size2 get_minimum_size() const; + void edit(VisualShader *p_visual_shader); + VisualShaderEditor(); +}; + +class VisualShaderEditorPlugin : public EditorPlugin { + + GDCLASS(VisualShaderEditorPlugin, EditorPlugin); + + VisualShaderEditor *visual_shader_editor; + EditorNode *editor; + Button *button; + +public: + virtual String get_name() const { return "VisualShader"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + VisualShaderEditorPlugin(EditorNode *p_node); + ~VisualShaderEditorPlugin(); +}; + +class VisualShaderNodePluginDefault : public VisualShaderNodePlugin { + + GDCLASS(VisualShaderNodePluginDefault, VisualShaderNodePlugin) + +public: + virtual Control *create_editor(const Ref<VisualShaderNode> &p_node); +}; + +class EditorPropertyShaderMode : public EditorProperty { + GDCLASS(EditorPropertyShaderMode, EditorProperty) + OptionButton *options; + + void _option_selected(int p_which); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + void set_option_button_clip(bool p_enable); + EditorPropertyShaderMode(); +}; + +class EditorInspectorShaderModePlugin : public EditorInspectorPlugin { + GDCLASS(EditorInspectorShaderModePlugin, EditorInspectorPlugin) + +public: + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); + virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage); + virtual void parse_end(); +}; + +class VisualShaderNodePortPreview : public Control { + GDCLASS(VisualShaderNodePortPreview, Control) + Ref<VisualShader> shader; + VisualShader::Type type; + int node; + int port; + void _shader_changed(); //must regen +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual Size2 get_minimum_size() const; + void setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port); + VisualShaderNodePortPreview(); +}; + +#endif // VISUAL_SHADER_EDITOR_PLUGIN_H diff --git a/editor/project_export.cpp b/editor/project_export.cpp index 9f87fc82b5..170546f14c 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -76,6 +76,9 @@ void ProjectExportDialog::popup_export() { } _update_presets(); + if (presets->get_current() >= 0) { + _edit_preset(presets->get_current()); // triggers rescan for templates if newly installed + } // Restore valid window bounds or pop up at default size. if (EditorSettings::get_singleton()->has_setting("interface/dialogs/export_bounds")) { @@ -154,7 +157,6 @@ void ProjectExportDialog::_update_presets() { if (current_idx != -1) { presets->select(current_idx); - //_edit_preset(current_idx); } updating = false; @@ -167,6 +169,7 @@ void ProjectExportDialog::_edit_preset(int p_index) { name->set_editable(false); runnable->set_disabled(true); parameters->edit(NULL); + presets->unselect_all(); delete_preset->set_disabled(true); sections->hide(); patches->clear(); @@ -438,11 +441,9 @@ void ProjectExportDialog::_delete_preset() { void ProjectExportDialog::_delete_preset_confirm() { int idx = presets->get_current(); - parameters->edit(NULL); //to avoid crash _edit_preset(-1); EditorExport::get_singleton()->remove_export_preset(idx); _update_presets(); - _edit_preset(presets->get_current()); } Variant ProjectExportDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) { diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 0d06b71420..af7c4bb379 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -72,16 +72,26 @@ private: MESSAGE_SUCCESS }; + enum InputType { + PROJECT_PATH, + INSTALL_PATH + }; + Mode mode; Button *browse; + Button *install_browse; Button *create_dir; Container *name_container; Container *path_container; + Container *install_path_container; Label *msg; LineEdit *project_path; LineEdit *project_name; + LineEdit *install_path; TextureRect *status_rect; + TextureRect *install_status_rect; FileDialog *fdialog; + FileDialog *fdialog_install; String zip_path; String zip_title; AcceptDialog *dialog_error; @@ -89,10 +99,11 @@ private: String created_folder_path; - void set_message(const String &p_msg, MessageType p_type = MESSAGE_SUCCESS) { + void set_message(const String &p_msg, MessageType p_type = MESSAGE_SUCCESS, InputType input_type = PROJECT_PATH) { msg->set_text(p_msg); - Ref<Texture> current_icon = status_rect->get_texture(); + Ref<Texture> current_path_icon = status_rect->get_texture(); + Ref<Texture> current_install_icon = install_status_rect->get_texture(); Ref<Texture> new_icon; switch (p_type) { @@ -119,8 +130,11 @@ private: } break; } - if (current_icon != new_icon) + if (current_path_icon != new_icon && input_type == PROJECT_PATH) { status_rect->set_texture(new_icon); + } else if (current_install_icon != new_icon && input_type == INSTALL_PATH) { + install_status_rect->set_texture(new_icon); + } set_size(Size2(500, 0) * EDSCALE); } @@ -128,11 +142,19 @@ private: String _test_path() { DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - String valid_path; + String valid_path, valid_install_path; if (d->change_dir(project_path->get_text()) == OK) { valid_path = project_path->get_text(); } else if (d->change_dir(project_path->get_text().strip_edges()) == OK) { valid_path = project_path->get_text().strip_edges(); + } else if (project_path->get_text().ends_with(".zip")) { + if (d->file_exists(project_path->get_text())) { + valid_path = project_path->get_text(); + } + } else if (project_path->get_text().strip_edges().ends_with(".zip")) { + if (d->file_exists(project_path->get_text().strip_edges())) { + valid_path = project_path->get_text().strip_edges(); + } } if (valid_path == "") { @@ -142,11 +164,94 @@ private: return ""; } + if (mode == MODE_IMPORT && valid_path.ends_with(".zip")) { + if (d->change_dir(install_path->get_text()) == OK) { + valid_install_path = install_path->get_text(); + } else if (d->change_dir(install_path->get_text().strip_edges()) == OK) { + valid_install_path = install_path->get_text().strip_edges(); + } + + if (valid_install_path == "") { + set_message(TTR("The path does not exist."), MESSAGE_ERROR, INSTALL_PATH); + memdelete(d); + get_ok()->set_disabled(true); + return ""; + } + } + if (mode == MODE_IMPORT || mode == MODE_RENAME) { if (valid_path != "" && !d->file_exists("project.godot")) { - set_message(TTR("Please choose a 'project.godot' file."), MESSAGE_ERROR); + if (valid_path.ends_with(".zip")) { + FileAccess *src_f = NULL; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + + unzFile pkg = unzOpen2(valid_path.utf8().get_data(), &io); + if (!pkg) { + + set_message(TTR("Error opening package file, not in zip format."), MESSAGE_ERROR); + memdelete(d); + get_ok()->set_disabled(true); + unzClose(pkg); + return ""; + } + + int ret = unzGoToFirstFile(pkg); + while (ret == UNZ_OK) { + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0); + + if (String(fname).ends_with("project.godot")) { + break; + } + + ret = unzGoToNextFile(pkg); + } + + if (ret == UNZ_END_OF_LIST_OF_FILE) { + set_message(TTR("Invalid '.zip' project file, does not contain a 'project.godot' file."), MESSAGE_ERROR); + memdelete(d); + get_ok()->set_disabled(true); + unzClose(pkg); + return ""; + } + + unzClose(pkg); + + // check if the specified install folder is empty, even though this is not an error, it is good to check here + d->list_dir_begin(); + bool is_empty = true; + String n = d->get_next(); + while (n != String()) { + if (n != "." && n != "..") { + is_empty = false; + break; + } + n = d->get_next(); + } + d->list_dir_end(); + + if (!is_empty) { + + set_message(TTR("Please choose an empty folder."), MESSAGE_WARNING, INSTALL_PATH); + memdelete(d); + get_ok()->set_disabled(true); + return ""; + } + + } else { + set_message(TTR("Please choose a 'project.godot' or '.zip' file."), MESSAGE_ERROR); + memdelete(d); + install_path_container->hide(); + get_ok()->set_disabled(true); + return ""; + } + + } else if (valid_path.ends_with("zip")) { + + set_message(TTR("Directory already contains a Godot project."), MESSAGE_ERROR, INSTALL_PATH); memdelete(d); get_ok()->set_disabled(true); return ""; @@ -159,7 +264,7 @@ private: bool is_empty = true; String n = d->get_next(); while (n != String()) { - if (!n.begins_with(".")) { // i don't know if this is enough to guarantee an empty dir + if (n != "." && n != "..") { // i don't know if this is enough to guarantee an empty dir is_empty = false; break; } @@ -177,6 +282,7 @@ private: } set_message(""); + set_message("", MESSAGE_SUCCESS, INSTALL_PATH); memdelete(d); get_ok()->set_disabled(false); return valid_path; @@ -214,9 +320,14 @@ private: if (mode == MODE_IMPORT) { if (p.ends_with("project.godot")) { p = p.get_base_dir(); + install_path_container->hide(); + get_ok()->set_disabled(false); + } else if (p.ends_with(".zip")) { + install_path->set_text(p.get_base_dir()); + install_path_container->show(); get_ok()->set_disabled(false); } else { - set_message(TTR("Please choose a 'project.godot' file."), MESSAGE_ERROR); + set_message(TTR("Please choose a 'project.godot' or '.zip' file."), MESSAGE_ERROR); get_ok()->set_disabled(true); return; } @@ -224,7 +335,11 @@ private: String sp = p.simplify_path(); project_path->set_text(sp); _path_text_changed(sp); - get_ok()->call_deferred("grab_focus"); + if (p.ends_with(".zip")) { + install_path->call_deferred("grab_focus"); + } else { + get_ok()->call_deferred("grab_focus"); + } } void _path_selected(const String &p_path) { @@ -236,6 +351,14 @@ private: get_ok()->call_deferred("grab_focus"); } + void _install_path_selected(const String &p_path) { + String p = p_path; + String sp = p.simplify_path(); + install_path->set_text(sp); + _path_text_changed(sp); + get_ok()->call_deferred("grab_focus"); + } + void _browse_path() { fdialog->set_current_dir(project_path->get_text()); @@ -245,12 +368,19 @@ private: fdialog->set_mode(FileDialog::MODE_OPEN_FILE); fdialog->clear_filters(); fdialog->add_filter("project.godot ; " VERSION_NAME " Project"); + fdialog->add_filter("*.zip ; Zip File"); } else { fdialog->set_mode(FileDialog::MODE_OPEN_DIR); } fdialog->popup_centered_ratio(); } + void _browse_install_path() { + fdialog_install->set_current_dir(install_path->get_text()); + fdialog_install->set_mode(FileDialog::MODE_OPEN_DIR); + fdialog_install->popup_centered_ratio(); + } + void _create_folder() { if (project_name->get_text() == "" || created_folder_path != "" || project_name->get_text().ends_with(".") || project_name->get_text().ends_with(" ")) { @@ -328,7 +458,15 @@ private: } else { if (mode == MODE_IMPORT) { - // nothing to do + + if (project_path->get_text().ends_with(".zip")) { + + mode = MODE_INSTALL; + ok_pressed(); + + return; + } + } else { if (mode == MODE_NEW) { @@ -357,6 +495,11 @@ private: } else if (mode == MODE_INSTALL) { + if (project_path->get_text().ends_with(".zip")) { + dir = install_path->get_text(); + zip_path = project_path->get_text(); + } + FileAccess *src_f = NULL; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); @@ -444,7 +587,7 @@ private: dialog_error->set_text(msg); dialog_error->popup_centered_minsize(); - } else { + } else if (!project_path->get_text().ends_with(".zip")) { dialog_error->set_text(TTR("Package Installed Successfully!")); dialog_error->popup_centered_minsize(); } @@ -486,6 +629,9 @@ private: if (status_rect->get_texture() == get_icon("StatusError", "EditorIcons")) msg->show(); + + if (install_status_rect->get_texture() == get_icon("StatusError", "EditorIcons")) + msg->show(); } void _notification(int p_what) { @@ -503,6 +649,8 @@ protected: ClassDB::bind_method("_path_text_changed", &ProjectDialog::_path_text_changed); ClassDB::bind_method("_path_selected", &ProjectDialog::_path_selected); ClassDB::bind_method("_file_selected", &ProjectDialog::_file_selected); + ClassDB::bind_method("_install_path_selected", &ProjectDialog::_install_path_selected); + ClassDB::bind_method("_browse_install_path", &ProjectDialog::_browse_install_path); ADD_SIGNAL(MethodInfo("project_created")); ADD_SIGNAL(MethodInfo("project_renamed")); } @@ -530,12 +678,15 @@ public: project_path->set_editable(false); browse->hide(); + install_browse->hide(); set_title(TTR("Rename Project")); get_ok()->set_text(TTR("Rename")); name_container->show(); status_rect->hide(); msg->hide(); + install_path_container->hide(); + install_status_rect->hide(); get_ok()->set_disabled(false); ProjectSettings *current = memnew(ProjectSettings); @@ -575,14 +726,18 @@ public: project_path->set_editable(true); browse->set_disabled(false); browse->show(); + install_browse->set_disabled(false); + install_browse->show(); create_dir->show(); status_rect->show(); + install_status_rect->show(); msg->show(); if (mode == MODE_IMPORT) { set_title(TTR("Import Existing Project")); get_ok()->set_text(TTR("Import & Edit")); name_container->hide(); + install_path_container->hide(); project_path->grab_focus(); } else if (mode == MODE_NEW) { @@ -590,6 +745,7 @@ public: set_title(TTR("Create New Project")); get_ok()->set_text(TTR("Create & Edit")); name_container->show(); + install_path_container->hide(); project_name->grab_focus(); } else if (mode == MODE_INSTALL) { @@ -597,6 +753,7 @@ public: set_title(TTR("Install Project:") + " " + zip_title); get_ok()->set_text(TTR("Install & Edit")); name_container->hide(); + install_path_container->hide(); project_path->grab_focus(); } @@ -644,6 +801,20 @@ public: project_path->set_h_size_flags(SIZE_EXPAND_FILL); pphb->add_child(project_path); + install_path_container = memnew(VBoxContainer); + vb->add_child(install_path_container); + + l = memnew(Label); + l->set_text(TTR("Project Installation Path:")); + install_path_container->add_child(l); + + HBoxContainer *iphb = memnew(HBoxContainer); + install_path_container->add_child(iphb); + + install_path = memnew(LineEdit); + install_path->set_h_size_flags(SIZE_EXPAND_FILL); + iphb->add_child(install_path); + // status icon status_rect = memnew(TextureRect); status_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); @@ -654,17 +825,33 @@ public: browse->connect("pressed", this, "_browse_path"); pphb->add_child(browse); + // install status icon + install_status_rect = memnew(TextureRect); + install_status_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + iphb->add_child(install_status_rect); + + install_browse = memnew(Button); + install_browse->set_text(TTR("Browse")); + install_browse->connect("pressed", this, "_browse_install_path"); + iphb->add_child(install_browse); + msg = memnew(Label); msg->set_align(Label::ALIGN_CENTER); vb->add_child(msg); fdialog = memnew(FileDialog); fdialog->set_access(FileDialog::ACCESS_FILESYSTEM); + fdialog_install = memnew(FileDialog); + fdialog_install->set_access(FileDialog::ACCESS_FILESYSTEM); add_child(fdialog); + add_child(fdialog_install); project_name->connect("text_changed", this, "_text_changed"); project_path->connect("text_changed", this, "_path_text_changed"); + install_path->connect("text_changed", this, "_path_text_changed"); fdialog->connect("dir_selected", this, "_path_selected"); fdialog->connect("file_selected", this, "_file_selected"); + fdialog_install->connect("dir_selected", this, "_install_path_selected"); + fdialog_install->connect("file_selected", this, "_install_path_selected"); set_hide_on_ok(false); mode = MODE_NEW; diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 8c7565a441..e6ae2d64e7 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -106,6 +106,12 @@ void ProjectSettingsEditor::_notification(int p_what) { translation_res_file_open->add_filter("*." + E->get()); translation_res_option_file_open->add_filter("*." + E->get()); } + + restart_close_button->set_icon(get_icon("Close", "EditorIcons")); + restart_container->add_style_override("panel", get_stylebox("bg", "Tree")); + restart_icon->set_texture(get_icon("StatusWarning", "EditorIcons")); + restart_label->add_color_override("font_color", get_color("error_color", "Editor")); + } break; case NOTIFICATION_POPUP_HIDE: { EditorSettings::get_singleton()->set("interface/dialogs/project_settings_bounds", get_rect()); @@ -394,6 +400,7 @@ void ProjectSettingsEditor::_show_last_added(const Ref<InputEvent> &p_event, con while (child) { Variant input = child->get_meta("__input"); if (p_event == input) { + r->set_collapsed(false); child->select(0); found = true; break; @@ -654,6 +661,14 @@ void ProjectSettingsEditor::_update_actions() { if (setting) return; + Map<String, bool> collapsed; + + if (input_editor->get_root() && input_editor->get_root()->get_children()) { + for (TreeItem *item = input_editor->get_root()->get_children(); item; item = item->get_next()) { + collapsed[item->get_text(0)] = item->is_collapsed(); + } + } + input_editor->clear(); TreeItem *root = input_editor->create_item(); input_editor->set_hide_root(true); @@ -677,6 +692,8 @@ void ProjectSettingsEditor::_update_actions() { TreeItem *item = input_editor->create_item(root); item->set_text(0, name); item->set_custom_bg_color(0, get_color("prop_subsection", "Editor")); + if (collapsed.has(name)) + item->set_collapsed(collapsed[name]); item->set_editable(1, true); item->set_cell_mode(1, TreeItem::CELL_MODE_RANGE); @@ -789,15 +806,13 @@ void ProjectSettingsEditor::popup_project_settings() { plugin_settings->update_plugins(); } -void ProjectSettingsEditor::_item_selected() { +void ProjectSettingsEditor::_item_selected(const String &p_path) { - TreeItem *ti = globals_editor->get_property_editor()->get_property_tree()->get_selected(); - if (!ti) - return; - if (!ti->get_parent()) + String selected_path = p_path; + if (selected_path == String()) return; category->set_text(globals_editor->get_current_section()); - property->set_text(ti->get_text(0)); + property->set_text(selected_path); popup_copy_to_feature->set_disabled(false); } @@ -854,7 +869,7 @@ void ProjectSettingsEditor::_item_add() { void ProjectSettingsEditor::_item_del() { - String path = globals_editor->get_property_editor()->get_selected_path(); + String path = globals_editor->get_inspector()->get_selected_path(); if (path == String()) { EditorNode::get_singleton()->show_warning(TTR("Select a setting item first!")); return; @@ -1032,7 +1047,7 @@ void ProjectSettingsEditor::_copy_to_platform_about_to_show() { void ProjectSettingsEditor::_copy_to_platform(int p_which) { - String path = globals_editor->get_property_editor()->get_selected_path(); + String path = globals_editor->get_inspector()->get_selected_path(); if (path == String()) { EditorNode::get_singleton()->show_warning(TTR("Select a setting item first!")); return; @@ -1561,7 +1576,7 @@ void ProjectSettingsEditor::_update_translations() { void ProjectSettingsEditor::_toggle_search_bar(bool p_pressed) { - globals_editor->get_property_editor()->set_use_filter(p_pressed); + globals_editor->get_inspector()->set_use_filter(p_pressed); if (p_pressed) { @@ -1582,7 +1597,7 @@ void ProjectSettingsEditor::_clear_search_box() { return; search_box->clear(); - globals_editor->get_property_editor()->update_tree(); + globals_editor->get_inspector()->update_tree(); } void ProjectSettingsEditor::set_plugins_page() { @@ -1595,6 +1610,18 @@ TabContainer *ProjectSettingsEditor::get_tabs() { return tab_container; } +void ProjectSettingsEditor::_editor_restart() { + EditorNode::get_singleton()->save_all_scenes_and_restart(); +} + +void ProjectSettingsEditor::_editor_restart_request() { + restart_container->show(); +} + +void ProjectSettingsEditor::_editor_restart_close() { + restart_container->hide(); +} + void ProjectSettingsEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_item_selected"), &ProjectSettingsEditor::_item_selected); @@ -1640,6 +1667,10 @@ void ProjectSettingsEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_copy_to_platform_about_to_show"), &ProjectSettingsEditor::_copy_to_platform_about_to_show); + ClassDB::bind_method(D_METHOD("_editor_restart_request"), &ProjectSettingsEditor::_editor_restart_request); + ClassDB::bind_method(D_METHOD("_editor_restart"), &ProjectSettingsEditor::_editor_restart); + ClassDB::bind_method(D_METHOD("_editor_restart_close"), &ProjectSettingsEditor::_editor_restart_close); + ClassDB::bind_method(D_METHOD("get_tabs"), &ProjectSettingsEditor::get_tabs); } @@ -1726,16 +1757,17 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { search_bar->add_child(clear_button); clear_button->connect("pressed", this, "_clear_search_box"); - globals_editor = memnew(SectionedPropertyEditor); + globals_editor = memnew(SectionedInspector); props_base->add_child(globals_editor); - globals_editor->get_property_editor()->set_undo_redo(EditorNode::get_singleton()->get_undo_redo()); - globals_editor->get_property_editor()->set_property_selectable(true); + globals_editor->get_inspector()->set_undo_redo(EditorNode::get_singleton()->get_undo_redo()); + globals_editor->get_inspector()->set_property_selectable(true); //globals_editor->hide_top_label(); globals_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); globals_editor->register_search_box(search_box); - globals_editor->get_property_editor()->get_property_tree()->connect("cell_selected", this, "_item_selected"); - globals_editor->get_property_editor()->connect("property_toggled", this, "_item_checked", varray(), CONNECT_DEFERRED); - globals_editor->get_property_editor()->connect("property_edited", this, "_settings_prop_edited"); + globals_editor->get_inspector()->connect("property_selected", this, "_item_selected"); + //globals_editor->get_inspector()->connect("property_toggled", this, "_item_checked", varray(), CONNECT_DEFERRED); + globals_editor->get_inspector()->connect("property_edited", this, "_settings_prop_edited"); + globals_editor->get_inspector()->connect("restart_requested", this, "_editor_restart_request"); Button *del = memnew(Button); hbc->add_child(del); @@ -1755,6 +1787,26 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { get_ok()->set_text(TTR("Close")); set_hide_on_ok(true); + restart_container = memnew(PanelContainer); + props_base->add_child(restart_container); + HBoxContainer *restart_hb = memnew(HBoxContainer); + restart_container->add_child(restart_hb); + restart_icon = memnew(TextureRect); + restart_icon->set_v_size_flags(SIZE_SHRINK_CENTER); + restart_hb->add_child(restart_icon); + restart_label = memnew(Label); + restart_label->set_text(TTR("Editor must be restarted for changes to take effect")); + restart_hb->add_child(restart_label); + restart_hb->add_spacer(); + Button *restart_button = memnew(Button); + restart_button->connect("pressed", this, "_editor_restart"); + restart_hb->add_child(restart_button); + restart_button->set_text(TTR("Save & Restart")); + restart_close_button = memnew(ToolButton); + restart_close_button->connect("pressed", this, "_editor_restart_close"); + restart_hb->add_child(restart_close_button); + restart_container->hide(); + message = memnew(AcceptDialog); add_child(message); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index 0ced88d7f6..3b74ae1909 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -35,7 +35,7 @@ #include "editor/editor_autoload_settings.h" #include "editor/editor_data.h" #include "editor/editor_plugin_settings.h" -#include "editor/property_editor.h" +#include "editor/editor_sectioned_inspector.h" #include "scene/gui/dialogs.h" #include "scene/gui/tab_container.h" @@ -64,7 +64,7 @@ class ProjectSettingsEditor : public AcceptDialog { EditorData *data; UndoRedo *undo_redo; - SectionedPropertyEditor *globals_editor; + SectionedInspector *globals_editor; HBoxContainer *search_bar; Button *search_button; @@ -112,7 +112,7 @@ class ProjectSettingsEditor : public AcceptDialog { EditorPluginSettings *plugin_settings; - void _item_selected(); + void _item_selected(const String &); void _item_adds(String); void _item_add(); void _item_del(); @@ -166,6 +166,15 @@ class ProjectSettingsEditor : public AcceptDialog { static ProjectSettingsEditor *singleton; + Label *restart_label; + TextureRect *restart_icon; + PanelContainer *restart_container; + ToolButton *restart_close_button; + + void _editor_restart_request(); + void _editor_restart(); + void _editor_restart_close(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index 7f46844f6c..576227344b 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -4394,7 +4394,7 @@ PropertyEditor::PropertyEditor() { use_filter = false; subsection_selectable = false; property_selectable = false; - show_type_icons = EDITOR_DEF("interface/editor/show_type_icons", false); + show_type_icons = false; // TODO: need to reimplement it to work with the new inspector } PropertyEditor::~PropertyEditor() { diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 77ee65879b..8d38bf39b5 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -551,6 +551,32 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { reparent_dialog->set_current(nodeset); } break; + case TOOL_MAKE_ROOT: { + + List<Node *> nodes = editor_selection->get_selected_node_list(); + ERR_FAIL_COND(nodes.size() != 1); + + Node *node = nodes.front()->get(); + Node *root = get_tree()->get_edited_scene_root(); + + if (node == root) + return; + + editor_data->get_undo_redo().create_action("Make node as Root"); + _node_replace_owner(root, node, node, MODE_DO); + editor_data->get_undo_redo().add_do_method(node->get_parent(), "remove_child", node); + editor_data->get_undo_redo().add_do_method(editor, "set_edited_scene", node); + editor_data->get_undo_redo().add_do_method(node, "set_filename", root->get_filename()); + + editor_data->get_undo_redo().add_undo_method(node, "set_filename", String()); + editor_data->get_undo_redo().add_undo_method(editor, "set_edited_scene", root); + editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node); + _node_replace_owner(root, node, root, MODE_UNDO); + editor_data->get_undo_redo().add_do_method(scene_tree, "update_tree"); + editor_data->get_undo_redo().add_undo_method(scene_tree, "update_tree"); + editor_data->get_undo_redo().add_undo_reference(root); + editor_data->get_undo_redo().commit_action(); + } break; case TOOL_MULTI_EDIT: { Node *root = EditorNode::get_singleton()->get_edited_scene(); @@ -698,7 +724,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } } } break; - case TOOL_SCENE_CLEAR_INSTANCING: { + case TOOL_SCENE_MAKE_LOCAL: { List<Node *> selection = editor_selection->get_selected_node_list(); List<Node *>::Element *e = selection.front(); if (e) { @@ -710,7 +736,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { break; ERR_FAIL_COND(node->get_filename() == String()); - undo_redo->create_action(TTR("Discard Instancing")); + undo_redo->create_action(TTR("Make Local")); undo_redo->add_do_method(node, "set_filename", ""); undo_redo->add_undo_method(node, "set_filename", node->get_filename()); _node_replace_owner(node, node, root); @@ -757,6 +783,26 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } } } break; + case TOOL_CREATE_2D_SCENE: + case TOOL_CREATE_3D_SCENE: + case TOOL_CREATE_USER_INTERFACE: { + + Node *new_node; + switch (p_tool) { + case TOOL_CREATE_2D_SCENE: new_node = memnew(Node2D); break; + case TOOL_CREATE_3D_SCENE: new_node = memnew(Spatial); break; + case TOOL_CREATE_USER_INTERFACE: new_node = memnew(Control); break; + } + + editor_data->get_undo_redo().create_action("New Scene Root"); + editor_data->get_undo_redo().add_do_method(editor, "set_edited_scene", new_node); + editor_data->get_undo_redo().add_do_method(scene_tree, "update_tree"); + editor_data->get_undo_redo().add_do_reference(new_node); + editor_data->get_undo_redo().add_undo_method(editor, "set_edited_scene", (Object *)NULL); + editor_data->get_undo_redo().commit_action(); + + } break; + default: { if (p_tool >= EDIT_SUBRESOURCE_BASE) { @@ -803,6 +849,35 @@ void SceneTreeDock::_notification(int p_what) { EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", this, "_selection_changed"); + create_root_dialog->add_child(memnew(Label(TTR("Create Root Node:")))); + + Button *button_2d = memnew(Button); + create_root_dialog->add_child(button_2d); + + button_2d->set_text(TTR("2D Scene")); + button_2d->set_icon(get_icon("Node2D", "EditorIcons")); + button_2d->connect("pressed", this, "_tool_selected", make_binds(TOOL_CREATE_2D_SCENE, false)); + + Button *button_3d = memnew(Button); + create_root_dialog->add_child(button_3d); + button_3d->set_text(TTR("3D Scene")); + button_3d->set_icon(get_icon("Spatial", "EditorIcons")); + button_3d->connect("pressed", this, "_tool_selected", make_binds(TOOL_CREATE_3D_SCENE, false)); + + Button *button_ui = memnew(Button); + create_root_dialog->add_child(button_ui); + button_ui->set_text(TTR("User Interface")); + button_ui->set_icon(get_icon("Control", "EditorIcons")); + button_ui->connect("pressed", this, "_tool_selected", make_binds(TOOL_CREATE_USER_INTERFACE, false)); + + Button *button_custom = memnew(Button); + create_root_dialog->add_child(button_custom); + button_custom->set_text(TTR("Custom Node")); + button_custom->set_icon(get_icon("Add", "EditorIcons")); + button_custom->connect("pressed", this, "_tool_selected", make_binds(TOOL_NEW, false)); + + create_root_dialog->add_spacer(); + } break; case NOTIFICATION_ENTER_TREE: { @@ -820,21 +895,49 @@ void SceneTreeDock::_notification(int p_what) { filter->add_icon_override("right_icon", get_icon("Search", "EditorIcons")); } break; + case NOTIFICATION_PROCESS: { + + bool show_create_root = bool(EDITOR_GET("interface/editors/show_scene_tree_root_selection")) && get_tree()->get_edited_scene_root() == NULL; + + if (show_create_root != create_root_dialog->is_visible_in_tree()) { + if (show_create_root) { + create_root_dialog->show(); + scene_tree->hide(); + } else { + create_root_dialog->hide(); + scene_tree->show(); + } + } + + } break; } } -void SceneTreeDock::_node_replace_owner(Node *p_base, Node *p_node, Node *p_root) { +void SceneTreeDock::_node_replace_owner(Node *p_base, Node *p_node, Node *p_root, ReplaceOwnerMode p_mode) { if (p_base != p_node) { if (p_node->get_owner() == p_base) { UndoRedo *undo_redo = &editor_data->get_undo_redo(); - undo_redo->add_do_method(p_node, "set_owner", p_root); - undo_redo->add_undo_method(p_node, "set_owner", p_base); + switch (p_mode) { + case MODE_BIDI: { + undo_redo->add_do_method(p_node, "set_owner", p_root); + undo_redo->add_undo_method(p_node, "set_owner", p_base); + + } break; + case MODE_DO: { + undo_redo->add_do_method(p_node, "set_owner", p_root); + + } break; + case MODE_UNDO: { + undo_redo->add_undo_method(p_node, "set_owner", p_root); + + } break; + } } } for (int i = 0; i < p_node->get_child_count(); i++) { - _node_replace_owner(p_base, p_node->get_child(i), p_root); + _node_replace_owner(p_base, p_node->get_child(i), p_root, p_mode); } } @@ -1904,6 +2007,8 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_icon_shortcut(get_icon("Reparent", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/reparent"), TOOL_REPARENT); if (selection.size() == 1) { + + menu->add_icon_shortcut(get_icon("NewRoot", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/make_root"), TOOL_MAKE_ROOT); menu->add_separator(); menu->add_icon_shortcut(get_icon("Blend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/merge_from_scene"), TOOL_MERGE_FROM_SCENE); menu->add_icon_shortcut(get_icon("CreateNewSceneFrom", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/save_branch_as_scene"), TOOL_NEW_SCENE_FROM); @@ -1923,7 +2028,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { bool placeholder = selection[0]->get_scene_instance_load_placeholder(); menu->add_check_item(TTR("Editable Children"), TOOL_SCENE_EDITABLE_CHILDREN); menu->add_check_item(TTR("Load As Placeholder"), TOOL_SCENE_USE_PLACEHOLDER); - menu->add_item(TTR("Discard Instancing"), TOOL_SCENE_CLEAR_INSTANCING); + menu->add_item(TTR("Make Local"), TOOL_SCENE_MAKE_LOCAL); menu->add_icon_item(get_icon("Load", "EditorIcons"), TTR("Open in Editor"), TOOL_SCENE_OPEN); menu->set_item_checked(menu->get_item_idx_from_text(TTR("Editable Children")), editable); menu->set_item_checked(menu->get_item_idx_from_text(TTR("Load As Placeholder")), placeholder); @@ -2090,6 +2195,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel ED_SHORTCUT("scene_tree/move_down", TTR("Move Down"), KEY_MASK_CMD | KEY_DOWN); ED_SHORTCUT("scene_tree/duplicate", TTR("Duplicate"), KEY_MASK_CMD | KEY_D); ED_SHORTCUT("scene_tree/reparent", TTR("Reparent")); + ED_SHORTCUT("scene_tree/make_root", TTR("Make Scene Root")); ED_SHORTCUT("scene_tree/merge_from_scene", TTR("Merge From Scene")); ED_SHORTCUT("scene_tree/save_branch_as_scene", TTR("Save Branch as Scene")); ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KEY_MASK_CMD | KEY_C); @@ -2153,6 +2259,10 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel remote_tree = NULL; button_hb->hide(); + create_root_dialog = memnew(VBoxContainer); + vbc->add_child(create_root_dialog); + create_root_dialog->hide(); + scene_tree = memnew(SceneTreeEditor(false, true, true)); vbc->add_child(scene_tree); @@ -2227,4 +2337,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel add_child(clear_inherit_confirm); set_process_input(true); + set_process(true); + + EDITOR_DEF("interface/editors/show_scene_tree_root_selection", true); } diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 17deab25de..57f4759747 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -68,6 +68,7 @@ class SceneTreeDock : public VBoxContainer { TOOL_MOVE_DOWN, TOOL_DUPLICATE, TOOL_REPARENT, + TOOL_MAKE_ROOT, TOOL_NEW_SCENE_FROM, TOOL_MERGE_FROM_SCENE, TOOL_MULTI_EDIT, @@ -76,11 +77,16 @@ class SceneTreeDock : public VBoxContainer { TOOL_BUTTON_MAX, TOOL_SCENE_EDITABLE_CHILDREN, TOOL_SCENE_USE_PLACEHOLDER, - TOOL_SCENE_CLEAR_INSTANCING, + TOOL_SCENE_MAKE_LOCAL, TOOL_SCENE_OPEN, TOOL_SCENE_CLEAR_INHERITANCE, TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM, - TOOL_SCENE_OPEN_INHERITED + TOOL_SCENE_OPEN_INHERITED, + + TOOL_CREATE_2D_SCENE, + TOOL_CREATE_3D_SCENE, + TOOL_CREATE_USER_INTERFACE, + }; enum { @@ -134,13 +140,22 @@ class SceneTreeDock : public VBoxContainer { Node *edited_scene; EditorNode *editor; + VBoxContainer *create_root_dialog; + void _add_children_to_popup(Object *p_obj, int p_depth); void _node_reparent(NodePath p_path, bool p_keep_global_xform); void _do_reparent(Node *p_new_parent, int p_position_in_parent, Vector<Node *> p_nodes, bool p_keep_global_xform); void _set_owners(Node *p_owner, const Array &p_nodes); - void _node_replace_owner(Node *p_base, Node *p_node, Node *p_root); + + enum ReplaceOwnerMode { + MODE_BIDI, + MODE_DO, + MODE_UNDO + }; + + void _node_replace_owner(Node *p_base, Node *p_node, Node *p_root, ReplaceOwnerMode p_mode = MODE_BIDI); void _load_request(const String &p_path); void _script_open_request(const Ref<Script> &p_script); diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index 62848a6035..9ce0e973f7 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -1249,6 +1249,9 @@ void ScriptEditorDebugger::stop() { EditorNode::get_singleton()->get_scene_tree_dock()->hide_remote_tree(); EditorNode::get_singleton()->get_scene_tree_dock()->hide_tab_buttons(); + Node *node = editor->get_scene_tree_dock()->get_tree_editor()->get_selected(); + editor->push_item(node); + if (hide_on_stop) { if (is_visible_in_tree()) EditorNode::get_singleton()->hide_bottom_panel(); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index ae88b3a035..fe379703e5 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -54,12 +54,12 @@ void EditorSettingsDialog::_settings_changed() { void EditorSettingsDialog::_settings_property_edited(const String &p_name) { - String full_name = property_editor->get_full_item_path(p_name); + String full_name = inspector->get_full_item_path(p_name); // Small usability workaround to update the text color settings when the // color theme is changed if (full_name == "text_editor/theme/color_theme") { - property_editor->get_property_editor()->update_tree(); + inspector->get_inspector()->update_tree(); } else if (full_name == "interface/theme/accent_color" || full_name == "interface/theme/base_color" || full_name == "interface/theme/contrast") { EditorSettings::get_singleton()->set_manually("interface/theme/preset", "Custom"); // set preset to Custom } else if (full_name.begins_with("text_editor/highlighting")) { @@ -88,8 +88,8 @@ void EditorSettingsDialog::popup_edit_settings() { EditorSettings::get_singleton()->list_text_editor_themes(); // make sure we have an up to date list of themes - property_editor->edit(EditorSettings::get_singleton()); - property_editor->get_property_editor()->update_tree(); + inspector->edit(EditorSettings::get_singleton()); + inspector->get_inspector()->update_tree(); search_box->select_all(); search_box->grab_focus(); @@ -120,7 +120,7 @@ void EditorSettingsDialog::_clear_search_box() { return; search_box->clear(); - property_editor->get_property_editor()->update_tree(); + inspector->get_inspector()->update_tree(); } void EditorSettingsDialog::_clear_shortcut_search_box() { @@ -158,7 +158,7 @@ void EditorSettingsDialog::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { _update_icons(); // Update theme colors. - property_editor->update_category_list(); + inspector->update_category_list(); _update_shortcuts(); } break; } @@ -202,6 +202,11 @@ void EditorSettingsDialog::_update_icons() { shortcut_search_box->add_icon_override("right_icon", get_icon("Search", "EditorIcons")); clear_button->set_icon(get_icon("Close", "EditorIcons")); shortcut_clear_button->set_icon(get_icon("Close", "EditorIcons")); + + restart_close_button->set_icon(get_icon("Close", "EditorIcons")); + restart_container->add_style_override("panel", get_stylebox("bg", "Tree")); + restart_icon->set_texture(get_icon("StatusWarning", "EditorIcons")); + restart_label->add_color_override("font_color", get_color("error_color", "Editor")); } void EditorSettingsDialog::_update_shortcuts() { @@ -388,6 +393,18 @@ void EditorSettingsDialog::_focus_current_search_box() { } } +void EditorSettingsDialog::_editor_restart() { + EditorNode::get_singleton()->save_all_scenes_and_restart(); +} + +void EditorSettingsDialog::_editor_restart_request() { + restart_container->show(); +} + +void EditorSettingsDialog::_editor_restart_close() { + restart_container->hide(); +} + void EditorSettingsDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_unhandled_input"), &EditorSettingsDialog::_unhandled_input); @@ -402,6 +419,10 @@ void EditorSettingsDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_press_a_key_confirm"), &EditorSettingsDialog::_press_a_key_confirm); ClassDB::bind_method(D_METHOD("_wait_for_key"), &EditorSettingsDialog::_wait_for_key); ClassDB::bind_method(D_METHOD("_tabs_tab_changed"), &EditorSettingsDialog::_tabs_tab_changed); + + ClassDB::bind_method(D_METHOD("_editor_restart_request"), &EditorSettingsDialog::_editor_restart_request); + ClassDB::bind_method(D_METHOD("_editor_restart"), &EditorSettingsDialog::_editor_restart); + ClassDB::bind_method(D_METHOD("_editor_restart_close"), &EditorSettingsDialog::_editor_restart_close); } EditorSettingsDialog::EditorSettingsDialog() { @@ -434,14 +455,35 @@ EditorSettingsDialog::EditorSettingsDialog() { hbc->add_child(clear_button); clear_button->connect("pressed", this, "_clear_search_box"); - property_editor = memnew(SectionedPropertyEditor); - //property_editor->hide_top_label(); - property_editor->get_property_editor()->set_use_filter(true); - property_editor->register_search_box(search_box); - property_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - property_editor->get_property_editor()->set_undo_redo(undo_redo); - tab_general->add_child(property_editor); - property_editor->get_property_editor()->connect("property_edited", this, "_settings_property_edited"); + inspector = memnew(SectionedInspector); + //inspector->hide_top_label(); + inspector->get_inspector()->set_use_filter(true); + inspector->register_search_box(search_box); + inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); + inspector->get_inspector()->set_undo_redo(undo_redo); + tab_general->add_child(inspector); + inspector->get_inspector()->connect("property_edited", this, "_settings_property_edited"); + inspector->get_inspector()->connect("restart_requested", this, "_editor_restart_request"); + + restart_container = memnew(PanelContainer); + tab_general->add_child(restart_container); + HBoxContainer *restart_hb = memnew(HBoxContainer); + restart_container->add_child(restart_hb); + restart_icon = memnew(TextureRect); + restart_icon->set_v_size_flags(SIZE_SHRINK_CENTER); + restart_hb->add_child(restart_icon); + restart_label = memnew(Label); + restart_label->set_text(TTR("Editor must be restarted for changes to take effect")); + restart_hb->add_child(restart_label); + restart_hb->add_spacer(); + Button *restart_button = memnew(Button); + restart_button->connect("pressed", this, "_editor_restart"); + restart_hb->add_child(restart_button); + restart_button->set_text(TTR("Save & Restart")); + restart_close_button = memnew(ToolButton); + restart_close_button->connect("pressed", this, "_editor_restart_close"); + restart_hb->add_child(restart_close_button); + restart_container->hide(); // Shortcuts Tab diff --git a/editor/settings_config_dialog.h b/editor/settings_config_dialog.h index 6676e870d0..6cf2eb6bdf 100644 --- a/editor/settings_config_dialog.h +++ b/editor/settings_config_dialog.h @@ -31,9 +31,14 @@ #ifndef SETTINGS_CONFIG_DIALOG_H #define SETTINGS_CONFIG_DIALOG_H -#include "property_editor.h" +#include "editor/editor_sectioned_inspector.h" +#include "editor_inspector.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/panel_container.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/tab_container.h" +#include "scene/gui/texture_rect.h" +#include "scene/gui/tool_button.h" class EditorSettingsDialog : public AcceptDialog { @@ -49,7 +54,7 @@ class EditorSettingsDialog : public AcceptDialog { LineEdit *shortcut_search_box; ToolButton *clear_button; ToolButton *shortcut_clear_button; - SectionedPropertyEditor *property_editor; + SectionedInspector *inspector; Timer *timer; @@ -89,6 +94,15 @@ class EditorSettingsDialog : public AcceptDialog { static void _undo_redo_callback(void *p_self, const String &p_name); + Label *restart_label; + TextureRect *restart_icon; + PanelContainer *restart_container; + ToolButton *restart_close_button; + + void _editor_restart_request(); + void _editor_restart(); + void _editor_restart_close(); + protected: static void _bind_methods(); diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index 3b0ac8864a..35544f711b 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -33,6 +33,7 @@ #include "geometry.h" #include "quick_hull.h" #include "scene/3d/camera.h" +#include "scene/3d/soft_body.h" #include "scene/resources/box_shape.h" #include "scene/resources/capsule_shape.h" #include "scene/resources/convex_polygon_shape.h" @@ -256,8 +257,12 @@ void EditorSpatialGizmo::add_handles(const Vector<Vector3> &p_handles, bool p_bi for (int i = 0; i < p_handles.size(); i++) { Color col(1, 1, 1, 1); + if (is_gizmo_handle_highlighted(i)) + col = Color(0, 0, 1, 0.9); + if (SpatialEditor::get_singleton()->get_over_gizmo_handle() != i) - col = Color(0.9, 0.9, 0.9, 0.9); + col.a = 0.8; + w[i] = col; } } @@ -1914,6 +1919,100 @@ VehicleWheelSpatialGizmo::VehicleWheelSpatialGizmo(VehicleWheel *p_car_wheel) { /////////// +void SoftBodySpatialGizmo::redraw() { + clear(); + + if (!soft_body || soft_body->get_mesh().is_null()) { + return; + } + + // find mesh + + Vector<Vector3> lines; + + soft_body->get_mesh()->generate_debug_mesh_lines(lines); + + if (!lines.size()) { + return; + } + + Vector<Vector3> points; + soft_body->get_mesh()->generate_debug_mesh_indices(points); + + soft_body->get_mesh()->clear_cache(); + + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); + Ref<Material> material = create_material("shape_material", gizmo_color); + + add_lines(lines, material); + add_collision_segments(lines); + add_handles(points); +} + +bool SoftBodySpatialGizmo::intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle, bool p_sec_first) { + return EditorSpatialGizmo::intersect_ray(p_camera, p_point, r_pos, r_normal, r_gizmo_handle, p_sec_first); + + /* Perform a shape cast but doesn't work with softbody + PhysicsDirectSpaceState *space_state = PhysicsServer::get_singleton()->space_get_direct_state(SceneTree::get_singleton()->get_root()->get_world()->get_space()); + if (!physics_sphere_shape.is_valid()) { + physics_sphere_shape = PhysicsServer::get_singleton()->shape_create(PhysicsServer::SHAPE_SPHERE); + real_t radius = 0.02; + PhysicsServer::get_singleton()->shape_set_data(physics_sphere_shape, radius); + } + + Vector3 sphere_motion(p_camera->project_ray_normal(p_point)); + real_t closest_safe; + real_t closest_unsafe; + PhysicsDirectSpaceState::ShapeRestInfo result; + bool collided = space_state->cast_motion( + physics_sphere_shape, + p_camera->get_transform(), + sphere_motion * Vector3(1000, 1000, 1000), + 0.f, + closest_safe, + closest_unsafe, + Set<RID>(), + 0xFFFFFFFF, + 0xFFFFFFFF, + &result); + + if (collided) { + + if (result.collider_id == soft_body->get_instance_id()) { + print_line("Collided"); + } else { + print_line("Collided but with wrong object: " + itos(result.collider_id)); + } + } else { + print_line("Not collided, motion: x: " + rtos(sphere_motion[0]) + " y: " + rtos(sphere_motion[1]) + " z: " + rtos(sphere_motion[2])); + } + return false; + */ +} + +void SoftBodySpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) { + soft_body->pin_point_toggle(p_idx); + redraw(); +} + +bool SoftBodySpatialGizmo::is_gizmo_handle_highlighted(int idx) const { + return soft_body->is_point_pinned(idx); +} + +SoftBodySpatialGizmo::SoftBodySpatialGizmo(SoftBody *p_soft_physics_body) : + EditorSpatialGizmo(), + soft_body(p_soft_physics_body) { + set_spatial_node(p_soft_physics_body); +} + +SoftBodySpatialGizmo::~SoftBodySpatialGizmo() { + //if (!physics_sphere_shape.is_valid()) { + // PhysicsServer::get_singleton()->free(physics_sphere_shape); + //} +} + +/////////// + String CollisionShapeSpatialGizmo::get_handle_name(int p_idx) const { Ref<Shape> s = cs->get_shape(); @@ -3272,10 +3371,10 @@ NavigationMeshSpatialGizmo::NavigationMeshSpatialGizmo(NavigationMeshInstance *p navmesh = p_navmesh; } - ////// - /// - /// - /// +////// +/// +/// +/// #define BODY_A_RADIUS 0.25 #define BODY_B_RADIUS 0.27 @@ -4051,6 +4150,12 @@ Ref<SpatialEditorGizmo> SpatialEditorGizmos::get_gizmo(Spatial *p_spatial) { return lsg; } + if (Object::cast_to<SoftBody>(p_spatial)) { + + Ref<SoftBodySpatialGizmo> misg = memnew(SoftBodySpatialGizmo(Object::cast_to<SoftBody>(p_spatial))); + return misg; + } + if (Object::cast_to<MeshInstance>(p_spatial)) { Ref<MeshInstanceSpatialGizmo> misg = memnew(MeshInstanceSpatialGizmo(Object::cast_to<MeshInstance>(p_spatial))); @@ -4081,6 +4186,7 @@ Ref<SpatialEditorGizmo> SpatialEditorGizmos::get_gizmo(Spatial *p_spatial) { return misg; } */ + if (Object::cast_to<CollisionShape>(p_spatial)) { Ref<CollisionShapeSpatialGizmo> misg = memnew(CollisionShapeSpatialGizmo(Object::cast_to<CollisionShape>(p_spatial))); diff --git a/editor/spatial_editor_gizmos.h b/editor/spatial_editor_gizmos.h index 924f82dc16..198d028516 100644 --- a/editor/spatial_editor_gizmos.h +++ b/editor/spatial_editor_gizmos.h @@ -331,6 +331,23 @@ public: BakedIndirectLightGizmo(BakedLightmap *p_baker = NULL); }; +class SoftBodySpatialGizmo : public EditorSpatialGizmo { + GDCLASS(SoftBodySpatialGizmo, EditorSpatialGizmo); + + class SoftBody *soft_body; + //RID physics_sphere_shape; // Used for raycast that doesn't work, in this moment, with softbody + +public: + void redraw(); + virtual bool intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false); + virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel); + + virtual bool is_gizmo_handle_highlighted(int idx) const; + + SoftBodySpatialGizmo(SoftBody *p_soft_physics_body = NULL); + ~SoftBodySpatialGizmo(); +}; + class CollisionShapeSpatialGizmo : public EditorSpatialGizmo { GDCLASS(CollisionShapeSpatialGizmo, EditorSpatialGizmo); diff --git a/main/main.cpp b/main/main.cpp index 23acb60c89..56dd5f73e7 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -825,7 +825,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->set_cmdline(execpath, main_args); GLOBAL_DEF("rendering/quality/driver/driver_name", "GLES3"); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name", PropertyInfo(Variant::STRING, "rendering/quality/driver/driver_name", PROPERTY_HINT_ENUM, "GLES3,GLES2")); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name", PropertyInfo(Variant::STRING, "rendering/quality/driver/driver_name", PROPERTY_HINT_ENUM, "GLES2,GLES3")); if (video_driver == "") { video_driver = GLOBAL_GET("rendering/quality/driver/driver_name"); } @@ -914,7 +914,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } if (audio_driver == "") { // specified in project.godot - audio_driver = GLOBAL_DEF("audio/driver", OS::get_singleton()->get_audio_driver_name(0)); + audio_driver = GLOBAL_DEF_RST("audio/driver", OS::get_singleton()->get_audio_driver_name(0)); } for (int i = 0; i < OS::get_singleton()->get_audio_driver_count(); i++) { @@ -1823,6 +1823,8 @@ bool Main::iteration() { ScriptServer::get_language(i)->frame(); } + AudioServer::get_singleton()->update(); + if (script_debugger) { if (script_debugger->is_profiling()) { script_debugger->profiling_set_frame_times(USEC_TO_SEC(frame_time), USEC_TO_SEC(idle_process_ticks), USEC_TO_SEC(physics_process_ticks), frame_slice); @@ -1955,6 +1957,15 @@ void Main::cleanup() { if (engine) memdelete(engine); + if (OS::get_singleton()->is_restart_on_exit_set()) { + //attempt to restart with arguments + String exec = OS::get_singleton()->get_executable_path(); + List<String> args = OS::get_singleton()->get_restart_on_exit_arguments(); + OS::ProcessID pid = 0; + OS::get_singleton()->execute(exec, args, false, &pid); + OS::get_singleton()->set_restart_on_exit(false, List<String>()); //clear list (uses memory) + } + unregister_core_driver_types(); unregister_core_types(); diff --git a/main/performance.cpp b/main/performance.cpp index fc915e2e76..70e0a5f7aa 100644 --- a/main/performance.cpp +++ b/main/performance.cpp @@ -32,6 +32,7 @@ #include "message_queue.h" #include "os/os.h" #include "scene/main/scene_tree.h" +#include "servers/audio_server.h" #include "servers/physics_2d_server.h" #include "servers/physics_server.h" #include "servers/visual_server.h" @@ -68,6 +69,7 @@ void Performance::_bind_methods() { BIND_ENUM_CONSTANT(PHYSICS_3D_ACTIVE_OBJECTS); BIND_ENUM_CONSTANT(PHYSICS_3D_COLLISION_PAIRS); BIND_ENUM_CONSTANT(PHYSICS_3D_ISLAND_COUNT); + BIND_ENUM_CONSTANT(AUDIO_OUTPUT_LATENCY); BIND_ENUM_CONSTANT(MONITOR_MAX); } @@ -104,6 +106,7 @@ String Performance::get_monitor_name(Monitor p_monitor) const { "physics_3d/active_objects", "physics_3d/collision_pairs", "physics_3d/islands", + "audio/output_latency", }; @@ -147,6 +150,7 @@ float Performance::get_monitor(Monitor p_monitor) const { case PHYSICS_3D_ACTIVE_OBJECTS: return PhysicsServer::get_singleton()->get_process_info(PhysicsServer::INFO_ACTIVE_OBJECTS); case PHYSICS_3D_COLLISION_PAIRS: return PhysicsServer::get_singleton()->get_process_info(PhysicsServer::INFO_COLLISION_PAIRS); case PHYSICS_3D_ISLAND_COUNT: return PhysicsServer::get_singleton()->get_process_info(PhysicsServer::INFO_ISLAND_COUNT); + case AUDIO_OUTPUT_LATENCY: return AudioServer::get_singleton()->get_output_latency(); default: {} } @@ -186,6 +190,7 @@ Performance::MonitorType Performance::get_monitor_type(Monitor p_monitor) const MONITOR_TYPE_QUANTITY, MONITOR_TYPE_QUANTITY, MONITOR_TYPE_QUANTITY, + MONITOR_TYPE_TIME, }; diff --git a/main/performance.h b/main/performance.h index 464226b517..de00df5ff9 100644 --- a/main/performance.h +++ b/main/performance.h @@ -77,6 +77,7 @@ public: PHYSICS_3D_COLLISION_PAIRS, PHYSICS_3D_ISLAND_COUNT, //physics + AUDIO_OUTPUT_LATENCY, MONITOR_MAX }; diff --git a/main/tests/test_gdscript.cpp b/main/tests/test_gdscript.cpp index 5c7633a0cf..5f3a2c1dc8 100644 --- a/main/tests/test_gdscript.cpp +++ b/main/tests/test_gdscript.cpp @@ -193,14 +193,6 @@ static String _parser_expr(const GDScriptParser::Node *p_expr) { case GDScriptParser::OperatorNode::OP_BIT_INVERT: { txt = "~" + _parser_expr(c_node->arguments[0]); } break; - case GDScriptParser::OperatorNode::OP_PREINC: { - } break; - case GDScriptParser::OperatorNode::OP_PREDEC: { - } break; - case GDScriptParser::OperatorNode::OP_INC: { - } break; - case GDScriptParser::OperatorNode::OP_DEC: { - } break; case GDScriptParser::OperatorNode::OP_IN: { txt = _parser_expr(c_node->arguments[0]) + " in " + _parser_expr(c_node->arguments[1]); } break; @@ -455,10 +447,9 @@ static void _parser_show_class(const GDScriptParser::ClassNode *p_class, int p_i print_line("\n"); } - for (int i = 0; i < p_class->constant_expressions.size(); i++) { - - const GDScriptParser::ClassNode::Constant &constant = p_class->constant_expressions[i]; - _print_indent(p_indent, "const " + String(constant.identifier) + "=" + _parser_expr(constant.expression)); + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { + const GDScriptParser::ClassNode::Constant &constant = E->get(); + _print_indent(p_indent, "const " + String(E->key()) + "=" + _parser_expr(constant.expression)); } for (int i = 0; i < p_class->variables.size(); i++) { diff --git a/main/tests/test_shader_lang.cpp b/main/tests/test_shader_lang.cpp index 63032597ed..7103b436e1 100644 --- a/main/tests/test_shader_lang.cpp +++ b/main/tests/test_shader_lang.cpp @@ -321,8 +321,8 @@ MainLoop *test() { dt["fragment"].built_ins["ALBEDO"] = SL::TYPE_VEC3; dt["fragment"].can_discard = true; - Set<String> rm; - rm.insert("popo"); + Vector<StringName> rm; + rm.push_back("popo"); Set<String> types; types.insert("spatial"); diff --git a/misc/dist/linux/godot.appdata.xml b/misc/dist/linux/org.godotengine.Godot.appdata.xml index a381556025..8278f1f6f5 100644 --- a/misc/dist/linux/godot.appdata.xml +++ b/misc/dist/linux/org.godotengine.Godot.appdata.xml @@ -1,11 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Copyright 2017-2018 Rémi Verschelde <akien@godotengine.org> --> <component type="desktop"> - <id>godot.desktop</id> + <id>org.godotengine.Godot.desktop</id> <metadata_license>CC0-1.0</metadata_license> <project_license>MIT</project_license> <name>Godot Engine</name> <summary>Multi-platform 2D and 3D game engine with a feature-rich editor</summary> + <icon type="remote">https://raw.githubusercontent.com/godotengine/godot/master/icon.png</icon> + <launchable type="desktop-id">org.godotengine.Godot.desktop</launchable> <description> <p> Godot is an advanced, feature-packed, multi-platform 2D and 3D game @@ -24,8 +26,12 @@ <image>https://download.tuxfamily.org/godotengine/media/screenshots/editor_3d_fracteed-720p.jpg</image> </screenshot> </screenshots> + <categories> + <category>Development</category> + </categories> <url type="homepage">https://godotengine.org</url> <url type="bugtracker">https://github.com/godotengine/godot/issues</url> + <url type="faq">http://docs.godotengine.org/en/latest/about/faq.html</url> <url type="help">http://docs.godotengine.org</url> <url type="donation">https://godotengine.org/donate</url> <url type="translate">https://hosted.weblate.org/projects/godot-engine/godot</url> diff --git a/misc/dist/linux/godot.desktop b/misc/dist/linux/org.godotengine.Godot.desktop index 974352b117..439b1d87b8 100644 --- a/misc/dist/linux/godot.desktop +++ b/misc/dist/linux/org.godotengine.Godot.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Name=Godot Engine GenericName=Libre game engine -Comment=Multi-platform 2D and 3D game engine with a feature rich editor +Comment=Multi-platform 2D and 3D game engine with a feature-rich editor Exec=godot -p Icon=godot Terminal=false diff --git a/misc/travis/clang-format.sh b/misc/travis/clang-format.sh index d1e37cc10e..5463998720 100755 --- a/misc/travis/clang-format.sh +++ b/misc/travis/clang-format.sh @@ -1,6 +1,6 @@ #!/bin/sh -CLANG_FORMAT=clang-format-5.0 +CLANG_FORMAT=clang-format-6.0 if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then # Check the whole commit range against $TRAVIS_BRANCH, the base merge branch diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 54431f93f1..2390c71b0a 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -169,7 +169,7 @@ real_t BulletPhysicsServer::shape_get_custom_solver_bias(RID p_shape) const { } RID BulletPhysicsServer::space_create() { - SpaceBullet *space = bulletnew(SpaceBullet(false)); + SpaceBullet *space = bulletnew(SpaceBullet); CreateThenReturnRID(space_owner, space); } @@ -567,9 +567,6 @@ void BulletPhysicsServer::body_clear_shapes(RID p_body) { void BulletPhysicsServer::body_attach_object_instance_id(RID p_body, uint32_t p_ID) { CollisionObjectBullet *body = get_collisin_object(p_body); - if (!body) { - body = soft_body_owner.get(p_body); - } ERR_FAIL_COND(!body); body->set_instance_id(p_ID); @@ -647,6 +644,20 @@ float BulletPhysicsServer::body_get_param(RID p_body, BodyParameter p_param) con return body->get_param(p_param); } +void BulletPhysicsServer::body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode) { + RigidBodyBullet *body = rigid_body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->set_combine_mode(p_param, p_mode); +} + +PhysicsServer::CombineMode BulletPhysicsServer::body_get_combine_mode(RID p_body, BodyParameter p_param) const { + RigidBodyBullet *body = rigid_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, COMBINE_MODE_INHERIT); + + return body->get_combine_mode(p_param); +} + void BulletPhysicsServer::body_set_kinematic_safe_margin(RID p_body, real_t p_margin) { RigidBodyBullet *body = rigid_body_owner.get(p_body); ERR_FAIL_COND(!body); @@ -853,6 +864,13 @@ RID BulletPhysicsServer::soft_body_create(bool p_init_sleeping) { CreateThenReturnRID(soft_body_owner, body); } +void BulletPhysicsServer::soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->update_visual_server(p_visual_server_handler); +} + void BulletPhysicsServer::soft_body_set_space(RID p_body, RID p_space) { SoftBodyBullet *body = soft_body_owner.get(p_body); ERR_FAIL_COND(!body); @@ -879,11 +897,11 @@ RID BulletPhysicsServer::soft_body_get_space(RID p_body) const { return space->get_self(); } -void BulletPhysicsServer::soft_body_set_trimesh_body_shape(RID p_body, PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num) { +void BulletPhysicsServer::soft_body_set_mesh(RID p_body, const REF &p_mesh) { SoftBodyBullet *body = soft_body_owner.get(p_body); ERR_FAIL_COND(!body); - body->set_trimesh_body_shape(p_indices, p_vertices, p_triangles_num); + body->set_soft_mesh(p_mesh); } void BulletPhysicsServer::soft_body_set_collision_layer(RID p_body, uint32_t p_layer) { @@ -961,14 +979,16 @@ void BulletPhysicsServer::soft_body_set_transform(RID p_body, const Transform &p SoftBodyBullet *body = soft_body_owner.get(p_body); ERR_FAIL_COND(!body); - body->set_transform(p_transform); + body->set_soft_transform(p_transform); } -Transform BulletPhysicsServer::soft_body_get_transform(RID p_body) const { +Vector3 BulletPhysicsServer::soft_body_get_vertex_position(RID p_body, int vertex_index) const { const SoftBodyBullet *body = soft_body_owner.get(p_body); - ERR_FAIL_COND_V(!body, Transform()); + Vector3 pos; + ERR_FAIL_COND_V(!body, pos); - return body->get_transform(); + body->get_node_position(vertex_index, pos); + return pos; } void BulletPhysicsServer::soft_body_set_ray_pickable(RID p_body, bool p_enable) { @@ -983,6 +1003,154 @@ bool BulletPhysicsServer::soft_body_is_ray_pickable(RID p_body) const { return body->is_ray_pickable(); } +void BulletPhysicsServer::soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_simulation_precision(p_simulation_precision); +} + +int BulletPhysicsServer::soft_body_get_simulation_precision(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_simulation_precision(); +} + +void BulletPhysicsServer::soft_body_set_total_mass(RID p_body, real_t p_total_mass) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_total_mass(p_total_mass); +} + +real_t BulletPhysicsServer::soft_body_get_total_mass(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_total_mass(); +} + +void BulletPhysicsServer::soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_linear_stiffness(p_stiffness); +} + +real_t BulletPhysicsServer::soft_body_get_linear_stiffness(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_linear_stiffness(); +} + +void BulletPhysicsServer::soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_areaAngular_stiffness(p_stiffness); +} + +real_t BulletPhysicsServer::soft_body_get_areaAngular_stiffness(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_areaAngular_stiffness(); +} + +void BulletPhysicsServer::soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_volume_stiffness(p_stiffness); +} + +real_t BulletPhysicsServer::soft_body_get_volume_stiffness(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_volume_stiffness(); +} + +void BulletPhysicsServer::soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_pressure_coefficient(p_pressure_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_pressure_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_pressure_coefficient(); +} + +void BulletPhysicsServer::soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + return body->set_pose_matching_coefficient(p_pose_matching_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_pose_matching_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_pose_matching_coefficient(); +} + +void BulletPhysicsServer::soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_damping_coefficient(p_damping_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_damping_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_damping_coefficient(); +} + +void BulletPhysicsServer::soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_drag_coefficient(p_drag_coefficient); +} + +real_t BulletPhysicsServer::soft_body_get_drag_coefficient(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_drag_coefficient(); +} + +void BulletPhysicsServer::soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_node_position(p_point_index, p_global_position); +} + +Vector3 BulletPhysicsServer::soft_body_get_point_global_position(RID p_body, int p_point_index) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, Vector3(0., 0., 0.)); + Vector3 pos; + body->get_node_position(p_point_index, pos); + return pos; +} + +Vector3 BulletPhysicsServer::soft_body_get_point_offset(RID p_body, int p_point_index) const { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, Vector3()); + Vector3 res; + body->get_node_offset(p_point_index, res); + return res; +} + +void BulletPhysicsServer::soft_body_remove_all_pinned_points(RID p_body) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->reset_all_node_mass(); +} + +void BulletPhysicsServer::soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND(!body); + body->set_node_mass(p_point_index, p_pin ? 0 : 1); +} + +bool BulletPhysicsServer::soft_body_is_point_pinned(RID p_body, int p_point_index) { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, 0.f); + return body->get_node_mass(p_point_index); +} + PhysicsServer::JointType BulletPhysicsServer::joint_get_type(RID p_joint) const { JointBullet *joint = joint_owner.get(p_joint); ERR_FAIL_COND_V(!joint, JOINT_PIN); diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h index e931915bba..2165845529 100644 --- a/modules/bullet/bullet_physics_server.h +++ b/modules/bullet/bullet_physics_server.h @@ -213,6 +213,9 @@ public: virtual void body_set_param(RID p_body, BodyParameter p_param, float p_value); virtual float body_get_param(RID p_body, BodyParameter p_param) const; + virtual void body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode); + virtual CombineMode body_get_combine_mode(RID p_body, BodyParameter p_param) const; + virtual void body_set_kinematic_safe_margin(RID p_body, real_t p_margin); virtual real_t body_get_kinematic_safe_margin(RID p_body) const; @@ -259,10 +262,12 @@ public: virtual RID soft_body_create(bool p_init_sleeping = false); + virtual void soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler); + virtual void soft_body_set_space(RID p_body, RID p_space); virtual RID soft_body_get_space(RID p_body) const; - virtual void soft_body_set_trimesh_body_shape(RID p_body, PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num); + virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh); virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer); virtual uint32_t soft_body_get_collision_layer(RID p_body) const; @@ -277,12 +282,49 @@ public: virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant); virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const; + /// Special function. This function has bad performance virtual void soft_body_set_transform(RID p_body, const Transform &p_transform); - virtual Transform soft_body_get_transform(RID p_body) const; + virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const; virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable); virtual bool soft_body_is_ray_pickable(RID p_body) const; + virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision); + virtual int soft_body_get_simulation_precision(RID p_body); + + virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass); + virtual real_t soft_body_get_total_mass(RID p_body); + + virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness); + virtual real_t soft_body_get_linear_stiffness(RID p_body); + + virtual void soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness); + virtual real_t soft_body_get_areaAngular_stiffness(RID p_body); + + virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness); + virtual real_t soft_body_get_volume_stiffness(RID p_body); + + virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient); + virtual real_t soft_body_get_pressure_coefficient(RID p_body); + + virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient); + virtual real_t soft_body_get_pose_matching_coefficient(RID p_body); + + virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient); + virtual real_t soft_body_get_damping_coefficient(RID p_body); + + virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient); + virtual real_t soft_body_get_drag_coefficient(RID p_body); + + virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position); + virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index); + + virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const; + + virtual void soft_body_remove_all_pinned_points(RID p_body); + virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin); + virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index); + /* JOINT API */ virtual JointType joint_get_type(RID p_joint) const; diff --git a/modules/bullet/collision_object_bullet.cpp b/modules/bullet/collision_object_bullet.cpp index 57e4db708e..1d63318fd7 100644 --- a/modules/bullet/collision_object_bullet.cpp +++ b/modules/bullet/collision_object_bullet.cpp @@ -111,6 +111,8 @@ void CollisionObjectBullet::setupBulletCollisionObject(btCollisionObject *p_coll void CollisionObjectBullet::add_collision_exception(const CollisionObjectBullet *p_ignoreCollisionObject) { exceptions.insert(p_ignoreCollisionObject->get_self()); + if (!bt_collision_object) + return; bt_collision_object->setIgnoreCollisionCheck(p_ignoreCollisionObject->bt_collision_object, true); if (space) space->get_broadphase()->getOverlappingPairCache()->cleanProxyFromPairs(bt_collision_object->getBroadphaseHandle(), space->get_dispatcher()); diff --git a/modules/bullet/register_types.cpp b/modules/bullet/register_types.cpp index b119b7720f..a76b0438b4 100644 --- a/modules/bullet/register_types.cpp +++ b/modules/bullet/register_types.cpp @@ -32,19 +32,26 @@ #include "bullet_physics_server.h" #include "class_db.h" +#include "project_settings.h" /** @author AndreaCatania */ +#ifndef _3D_DISABLED PhysicsServer *_createBulletPhysicsCallback() { return memnew(BulletPhysicsServer); } +#endif void register_bullet_types() { - +#ifndef _3D_DISABLED PhysicsServerManager::register_server("Bullet", &_createBulletPhysicsCallback); PhysicsServerManager::set_default_server("Bullet", 1); + + GLOBAL_DEF("physics/3d/active_soft_world", true); + ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/active_soft_world", PropertyInfo(Variant::BOOL, "physics/3d/active_soft_world")); +#endif } void unregister_bullet_types() { diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index 2fc96a77b5..19fad283af 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -257,6 +257,8 @@ RigidBodyBullet::RigidBodyBullet() : angularDamp(0), can_sleep(true), omit_forces_integration(false), + restitution_combine_mode(PhysicsServer::COMBINE_MODE_INHERIT), + friction_combine_mode(PhysicsServer::COMBINE_MODE_INHERIT), force_integration_callback(NULL), isTransformChanged(false), previousActiveState(true), @@ -750,6 +752,22 @@ Vector3 RigidBodyBullet::get_angular_velocity() const { return gVec; } +void RigidBodyBullet::set_combine_mode(const PhysicsServer::BodyParameter p_param, const PhysicsServer::CombineMode p_mode) { + if (p_param == PhysicsServer::BODY_PARAM_BOUNCE) { + restitution_combine_mode = p_mode; + } else { + friction_combine_mode = p_mode; + } +} + +PhysicsServer::CombineMode RigidBodyBullet::get_combine_mode(PhysicsServer::BodyParameter p_param) const { + if (p_param == PhysicsServer::BODY_PARAM_BOUNCE) { + return restitution_combine_mode; + } else { + return friction_combine_mode; + } +} + void RigidBodyBullet::set_transform__bullet(const btTransform &p_global_transform) { if (mode == PhysicsServer::BODY_MODE_KINEMATIC) { // The kinematic use MotionState class diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index b9511243c7..911e93bfef 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -200,6 +200,9 @@ private: bool can_sleep; bool omit_forces_integration; + PhysicsServer::CombineMode restitution_combine_mode; + PhysicsServer::CombineMode friction_combine_mode; + Vector<CollisionData> collisions; // these parameters are used to avoid vector resize int maxCollisionsDetection; @@ -295,6 +298,12 @@ public: void set_angular_velocity(const Vector3 &p_velocity); Vector3 get_angular_velocity() const; + void set_combine_mode(const PhysicsServer::BodyParameter p_param, const PhysicsServer::CombineMode p_mode); + PhysicsServer::CombineMode get_combine_mode(PhysicsServer::BodyParameter p_param) const; + + _FORCE_INLINE_ PhysicsServer::CombineMode get_restitution_combine_mode() const { return restitution_combine_mode; } + _FORCE_INLINE_ PhysicsServer::CombineMode get_friction_combine_mode() const { return friction_combine_mode; } + virtual void set_transform__bullet(const btTransform &p_global_transform); virtual const btTransform &get_transform__bullet() const; diff --git a/modules/bullet/soft_body_bullet.cpp b/modules/bullet/soft_body_bullet.cpp index 5c20eb73f1..b3680d58db 100644 --- a/modules/bullet/soft_body_bullet.cpp +++ b/modules/bullet/soft_body_bullet.cpp @@ -32,42 +32,24 @@ #include "bullet_types_converter.h" #include "bullet_utilities.h" -#include "scene/3d/immediate_geometry.h" +#include "scene/3d/soft_body.h" #include "space_bullet.h" -/** - @author AndreaCatania -*/ - SoftBodyBullet::SoftBodyBullet() : CollisionObjectBullet(CollisionObjectBullet::TYPE_SOFT_BODY), - mass(1), + total_mass(1), simulation_precision(5), - stiffness(0.5f), - pressure_coefficient(50), - damping_coefficient(0.005), - drag_coefficient(0.005), + linear_stiffness(0.5), + areaAngular_stiffness(0.5), + volume_stiffness(0.5), + pressure_coefficient(0.), + pose_matching_coefficient(0.), + damping_coefficient(0.01), + drag_coefficient(0.), bt_soft_body(NULL), - soft_shape_type(SOFT_SHAPETYPE_NONE), - isScratched(false), - soft_body_shape_data(NULL) { - - test_geometry = memnew(ImmediateGeometry); - - red_mat = Ref<SpatialMaterial>(memnew(SpatialMaterial)); - red_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); - red_mat->set_line_width(20.0); - red_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - red_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - red_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); - red_mat->set_albedo(Color(1, 0, 0, 1)); - test_geometry->set_material_override(red_mat); - - test_is_in_scene = false; -} + isScratched(false) {} SoftBodyBullet::~SoftBodyBullet() { - bulletdelete(soft_body_shape_data); } void SoftBodyBullet::reload_body() { @@ -80,8 +62,6 @@ void SoftBodyBullet::reload_body() { void SoftBodyBullet::set_space(SpaceBullet *p_space) { if (space) { isScratched = false; - - // Remove this object from the physics world space->remove_soft_body(this); } @@ -90,86 +70,181 @@ void SoftBodyBullet::set_space(SpaceBullet *p_space) { if (space) { space->add_soft_body(this); } - - reload_soft_body(); } -void SoftBodyBullet::dispatch_callbacks() { - if (!bt_soft_body) { +void SoftBodyBullet::dispatch_callbacks() {} + +void SoftBodyBullet::on_collision_filters_change() {} + +void SoftBodyBullet::on_collision_checker_start() {} + +void SoftBodyBullet::on_enter_area(AreaBullet *p_area) {} + +void SoftBodyBullet::on_exit_area(AreaBullet *p_area) {} + +void SoftBodyBullet::update_visual_server(SoftBodyVisualServerHandler *p_visual_server_handler) { + if (!bt_soft_body) return; + + /// Update visual server vertices + const btSoftBody::tNodeArray &nodes(bt_soft_body->m_nodes); + const int nodes_count = nodes.size(); + + Vector<int> *vs_indices; + const void *vertex_position; + const void *vertex_normal; + + for (int vertex_index = 0; vertex_index < nodes_count; ++vertex_index) { + vertex_position = reinterpret_cast<const void *>(&nodes[vertex_index].m_x); + vertex_normal = reinterpret_cast<const void *>(&nodes[vertex_index].m_n); + + vs_indices = &indices_table[vertex_index]; + + const int vs_indices_size(vs_indices->size()); + for (int x = 0; x < vs_indices_size; ++x) { + p_visual_server_handler->set_vertex((*vs_indices)[x], vertex_position); + p_visual_server_handler->set_normal((*vs_indices)[x], vertex_normal); + } } - if (!test_is_in_scene) { - test_is_in_scene = true; - SceneTree::get_singleton()->get_current_scene()->add_child(test_geometry); + /// Generate AABB + btVector3 aabb_min; + btVector3 aabb_max; + bt_soft_body->getAabb(aabb_min, aabb_max); + + btVector3 size(aabb_max - aabb_min); + + AABB aabb; + B_TO_G(aabb_min, aabb.position); + B_TO_G(size, aabb.size); + + p_visual_server_handler->set_aabb(aabb); +} + +void SoftBodyBullet::set_soft_mesh(const Ref<Mesh> &p_mesh) { + + if (p_mesh.is_null() || !p_mesh->surface_is_softbody_friendly(0)) + soft_mesh.unref(); + else + soft_mesh = p_mesh; + + if (soft_mesh.is_null()) { + + destroy_soft_body(); + return; } - test_geometry->clear(); - test_geometry->begin(Mesh::PRIMITIVE_LINES, NULL); - bool first = true; - Vector3 pos; - for (int i = 0; i < bt_soft_body->m_nodes.size(); ++i) { - const btSoftBody::Node &n = bt_soft_body->m_nodes[i]; - B_TO_G(n.m_x, pos); - test_geometry->add_vertex(pos); - if (!first) { - test_geometry->add_vertex(pos); - } else { - first = false; - } + Array arrays = soft_mesh->surface_get_arrays(0); + ERR_FAIL_COND(!(soft_mesh->surface_get_format(0) & VS::ARRAY_FORMAT_INDEX)); + set_trimesh_body_shape(arrays[VS::ARRAY_INDEX], arrays[VS::ARRAY_VERTEX]); +} + +void SoftBodyBullet::destroy_soft_body() { + + if (!bt_soft_body) + return; + + if (space) { + /// Remove from world before deletion + space->remove_soft_body(this); } - test_geometry->end(); + + destroyBulletCollisionObject(); + bt_soft_body = NULL; +} + +void SoftBodyBullet::set_soft_transform(const Transform &p_transform) { + reset_all_node_positions(); + move_all_nodes(p_transform); } -void SoftBodyBullet::on_collision_filters_change() { +void SoftBodyBullet::move_all_nodes(const Transform &p_transform) { + if (!bt_soft_body) + return; + btTransform bt_transf; + G_TO_B(p_transform, bt_transf); + bt_soft_body->transform(bt_transf); } -void SoftBodyBullet::on_collision_checker_start() { +void SoftBodyBullet::set_node_position(int p_node_index, const Vector3 &p_global_position) { + btVector3 bt_pos; + G_TO_B(p_global_position, bt_pos); + set_node_position(p_node_index, bt_pos); } -void SoftBodyBullet::on_enter_area(AreaBullet *p_area) { +void SoftBodyBullet::set_node_position(int p_node_index, const btVector3 &p_global_position) { + if (bt_soft_body) { + bt_soft_body->m_nodes[p_node_index].m_x = p_global_position; + } } -void SoftBodyBullet::on_exit_area(AreaBullet *p_area) { +void SoftBodyBullet::get_node_position(int p_node_index, Vector3 &r_position) const { + if (bt_soft_body) { + B_TO_G(bt_soft_body->m_nodes[p_node_index].m_x, r_position); + } } -void SoftBodyBullet::set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num) { +void SoftBodyBullet::get_node_offset(int p_node_index, Vector3 &r_offset) const { + if (soft_mesh.is_null()) + return; + + Array arrays = soft_mesh->surface_get_arrays(0); + PoolVector<Vector3> vertices(arrays[VS::ARRAY_VERTEX]); - TrimeshSoftShapeData *shape_data = bulletnew(TrimeshSoftShapeData); - shape_data->m_triangles_indices = p_indices; - shape_data->m_vertices = p_vertices; - shape_data->m_triangles_num = p_triangles_num; + if (0 <= p_node_index && vertices.size() > p_node_index) { + r_offset = vertices[p_node_index]; + } +} - set_body_shape_data(shape_data, SOFT_SHAPE_TYPE_TRIMESH); - reload_soft_body(); +void SoftBodyBullet::get_node_offset(int p_node_index, btVector3 &r_offset) const { + Vector3 off; + get_node_offset(p_node_index, off); + G_TO_B(off, r_offset); } -void SoftBodyBullet::set_body_shape_data(SoftShapeData *p_soft_shape_data, SoftShapeType p_type) { - bulletdelete(soft_body_shape_data); - soft_body_shape_data = p_soft_shape_data; - soft_shape_type = p_type; +void SoftBodyBullet::set_node_mass(int node_index, btScalar p_mass) { + if (0 >= p_mass) { + pin_node(node_index); + } else { + unpin_node(node_index); + } + if (bt_soft_body) { + bt_soft_body->setMass(node_index, p_mass); + } } -void SoftBodyBullet::set_transform(const Transform &p_transform) { - transform = p_transform; +btScalar SoftBodyBullet::get_node_mass(int node_index) const { if (bt_soft_body) { - // TODO the softbody set new transform considering the current transform as center of world - // like if it's local transform, so I must fix this by setting nwe transform considering the old - btTransform bt_trans; - G_TO_B(transform, bt_trans); - //bt_soft_body->transform(bt_trans); + return bt_soft_body->getMass(node_index); + } else { + return -1 == search_node_pinned(node_index) ? 1 : 0; } } -const Transform &SoftBodyBullet::get_transform() const { - return transform; +void SoftBodyBullet::reset_all_node_mass() { + if (bt_soft_body) { + for (int i = pinned_nodes.size() - 1; 0 <= i; --i) { + bt_soft_body->setMass(pinned_nodes[i], 1); + } + } + pinned_nodes.resize(0); } -void SoftBodyBullet::get_first_node_origin(btVector3 &p_out_origin) const { - if (bt_soft_body && bt_soft_body->m_nodes.size()) { - p_out_origin = bt_soft_body->m_nodes[0].m_x; - } else { - p_out_origin.setZero(); +void SoftBodyBullet::reset_all_node_positions() { + if (soft_mesh.is_null()) + return; + + Array arrays = soft_mesh->surface_get_arrays(0); + PoolVector<Vector3> vs_vertices(arrays[VS::ARRAY_VERTEX]); + PoolVector<Vector3>::Read vs_vertices_read = vs_vertices.read(); + + for (int vertex_index = bt_soft_body->m_nodes.size() - 1; 0 <= vertex_index; --vertex_index) { + + G_TO_B(vs_vertices_read[indices_table[vertex_index][0]], bt_soft_body->m_nodes[vertex_index].m_x); + + bt_soft_body->m_nodes[vertex_index].m_q = bt_soft_body->m_nodes[vertex_index].m_x; + bt_soft_body->m_nodes[vertex_index].m_v = btVector3(0, 0, 0); + bt_soft_body->m_nodes[vertex_index].m_f = btVector3(0, 0, 0); } } @@ -181,22 +256,34 @@ void SoftBodyBullet::set_activation_state(bool p_active) { } } -void SoftBodyBullet::set_mass(real_t p_val) { +void SoftBodyBullet::set_total_mass(real_t p_val) { if (0 >= p_val) { p_val = 1; } - mass = p_val; + total_mass = p_val; if (bt_soft_body) { - bt_soft_body->setTotalMass(mass); + bt_soft_body->setTotalMass(total_mass); } } -void SoftBodyBullet::set_stiffness(real_t p_val) { - stiffness = p_val; +void SoftBodyBullet::set_linear_stiffness(real_t p_val) { + linear_stiffness = p_val; if (bt_soft_body) { - mat0->m_kAST = stiffness; - mat0->m_kLST = stiffness; - mat0->m_kVST = stiffness; + mat0->m_kLST = linear_stiffness; + } +} + +void SoftBodyBullet::set_areaAngular_stiffness(real_t p_val) { + areaAngular_stiffness = p_val; + if (bt_soft_body) { + mat0->m_kAST = areaAngular_stiffness; + } +} + +void SoftBodyBullet::set_volume_stiffness(real_t p_val) { + volume_stiffness = p_val; + if (bt_soft_body) { + mat0->m_kVST = volume_stiffness; } } @@ -204,6 +291,9 @@ void SoftBodyBullet::set_simulation_precision(int p_val) { simulation_precision = p_val; if (bt_soft_body) { bt_soft_body->m_cfg.piterations = simulation_precision; + bt_soft_body->m_cfg.viterations = simulation_precision; + bt_soft_body->m_cfg.diterations = simulation_precision; + bt_soft_body->m_cfg.citerations = simulation_precision; } } @@ -214,6 +304,13 @@ void SoftBodyBullet::set_pressure_coefficient(real_t p_val) { } } +void SoftBodyBullet::set_pose_matching_coefficient(real_t p_val) { + pose_matching_coefficient = p_val; + if (bt_soft_body) { + bt_soft_body->m_cfg.kMT = pose_matching_coefficient; + } +} + void SoftBodyBullet::set_damping_coefficient(real_t p_val) { damping_coefficient = p_val; if (bt_soft_body) { @@ -228,89 +325,156 @@ void SoftBodyBullet::set_drag_coefficient(real_t p_val) { } } -void SoftBodyBullet::reload_soft_body() { - +void SoftBodyBullet::set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices) { + /// Assert the current soft body is destroyed destroy_soft_body(); - create_soft_body(); - if (bt_soft_body) { + /// Parse visual server indices to physical indices. + /// Merge all overlapping vertices and create a map of physical vertices to visual server - // TODO the softbody set new transform considering the current transform as center of world - // like if it's local transform, so I must fix this by setting nwe transform considering the old - btTransform bt_trans; - G_TO_B(transform, bt_trans); - bt_soft_body->transform(bt_trans); + { + /// This is the map of visual server indices to physics indices (So it's the inverse of idices_map), Thanks to it I don't need make a heavy search in the indices_map + Vector<int> vs_indices_to_physics_table; - bt_soft_body->generateBendingConstraints(2, mat0); - mat0->m_kAST = stiffness; - mat0->m_kLST = stiffness; - mat0->m_kVST = stiffness; + { // Map vertices + indices_table.resize(0); - bt_soft_body->m_cfg.piterations = simulation_precision; - bt_soft_body->m_cfg.kDP = damping_coefficient; - bt_soft_body->m_cfg.kDG = drag_coefficient; - bt_soft_body->m_cfg.kPR = pressure_coefficient; - bt_soft_body->setTotalMass(mass); - } - if (space) { - // TODO remove this please - space->add_soft_body(this); - } -} + int index = 0; + Map<Vector3, int> unique_vertices; -void SoftBodyBullet::create_soft_body() { - if (!space || !soft_body_shape_data) { - return; - } - ERR_FAIL_COND(!space->is_using_soft_world()); - switch (soft_shape_type) { - case SOFT_SHAPE_TYPE_TRIMESH: { - TrimeshSoftShapeData *trimesh_data = static_cast<TrimeshSoftShapeData *>(soft_body_shape_data); - - Vector<int> indices; - Vector<btScalar> vertices; - - int i; - const int indices_size = trimesh_data->m_triangles_indices.size(); - const int vertices_size = trimesh_data->m_vertices.size(); - indices.resize(indices_size); - vertices.resize(vertices_size * 3); - - PoolVector<int>::Read i_r = trimesh_data->m_triangles_indices.read(); - for (i = 0; i < indices_size; ++i) { - indices[i] = i_r[i]; + const int vs_vertices_size(p_vertices.size()); + + PoolVector<Vector3>::Read p_vertices_read = p_vertices.read(); + + for (int vs_vertex_index = 0; vs_vertex_index < vs_vertices_size; ++vs_vertex_index) { + + Map<Vector3, int>::Element *e = unique_vertices.find(p_vertices_read[vs_vertex_index]); + int vertex_id; + if (e) { + // Already rxisting + vertex_id = e->value(); + } else { + // Create new one + unique_vertices[p_vertices_read[vs_vertex_index]] = vertex_id = index++; + indices_table.push_back(Vector<int>()); + } + + indices_table[vertex_id].push_back(vs_vertex_index); + vs_indices_to_physics_table.push_back(vertex_id); + } + } + + const int indices_map_size(indices_table.size()); + + Vector<btScalar> bt_vertices; + + { // Parse vertices to bullet + + bt_vertices.resize(indices_map_size * 3); + PoolVector<Vector3>::Read p_vertices_read = p_vertices.read(); + + for (int i = 0; i < indices_map_size; ++i) { + bt_vertices[3 * i + 0] = p_vertices_read[indices_table[i][0]].x; + bt_vertices[3 * i + 1] = p_vertices_read[indices_table[i][0]].y; + bt_vertices[3 * i + 2] = p_vertices_read[indices_table[i][0]].z; } - i_r = PoolVector<int>::Read(); + } + + Vector<int> bt_triangles; + const int triangles_size(p_indices.size() / 3); + + { // Parse indices + + bt_triangles.resize(triangles_size * 3); + + PoolVector<int>::Read p_indices_read = p_indices.read(); - PoolVector<Vector3>::Read f_r = trimesh_data->m_vertices.read(); - for (int j = i = 0; i < vertices_size; ++i, j += 3) { - vertices[j + 0] = f_r[i][0]; - vertices[j + 1] = f_r[i][1]; - vertices[j + 2] = f_r[i][2]; + for (int i = 0; i < triangles_size; ++i) { + bt_triangles[3 * i + 0] = vs_indices_to_physics_table[p_indices_read[3 * i + 2]]; + bt_triangles[3 * i + 1] = vs_indices_to_physics_table[p_indices_read[3 * i + 1]]; + bt_triangles[3 * i + 2] = vs_indices_to_physics_table[p_indices_read[3 * i + 0]]; } - f_r = PoolVector<Vector3>::Read(); + } - bt_soft_body = btSoftBodyHelpers::CreateFromTriMesh(*space->get_soft_body_world_info(), vertices.ptr(), indices.ptr(), trimesh_data->m_triangles_num); - } break; - default: - ERR_PRINT("Shape type not supported"); - return; + btSoftBodyWorldInfo fake_world_info; + bt_soft_body = btSoftBodyHelpers::CreateFromTriMesh(fake_world_info, &bt_vertices[0], &bt_triangles[0], triangles_size, false); + setup_soft_body(); } +} + +void SoftBodyBullet::setup_soft_body() { + + if (!bt_soft_body) + return; + // Soft body setup setupBulletCollisionObject(bt_soft_body); - bt_soft_body->getCollisionShape()->setMargin(0.001f); + bt_soft_body->m_worldInfo = NULL; // Remove fake world info + bt_soft_body->getCollisionShape()->setMargin(0.01); bt_soft_body->setCollisionFlags(bt_soft_body->getCollisionFlags() & (~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT))); + + // Space setup + if (space) { + space->add_soft_body(this); + } + mat0 = bt_soft_body->appendMaterial(); + + // Assign soft body data + bt_soft_body->generateBendingConstraints(2, mat0); + + mat0->m_kLST = linear_stiffness; + mat0->m_kAST = areaAngular_stiffness; + mat0->m_kVST = volume_stiffness; + + // Clusters allow to have Soft vs Soft collision but doesn't work well right now + + //bt_soft_body->m_cfg.kSRHR_CL = 1;// Soft vs rigid hardness [0,1] (cluster only) + //bt_soft_body->m_cfg.kSKHR_CL = 1;// Soft vs kinematic hardness [0,1] (cluster only) + //bt_soft_body->m_cfg.kSSHR_CL = 1;// Soft vs soft hardness [0,1] (cluster only) + //bt_soft_body->m_cfg.kSR_SPLT_CL = 1; // Soft vs rigid impulse split [0,1] (cluster only) + //bt_soft_body->m_cfg.kSK_SPLT_CL = 1; // Soft vs kinematic impulse split [0,1] (cluster only) + //bt_soft_body->m_cfg.kSS_SPLT_CL = 1; // Soft vs Soft impulse split [0,1] (cluster only) + //bt_soft_body->m_cfg.collisions = btSoftBody::fCollision::CL_SS + btSoftBody::fCollision::CL_RS + btSoftBody::fCollision::VF_SS; + //bt_soft_body->generateClusters(64); + + bt_soft_body->m_cfg.piterations = simulation_precision; + bt_soft_body->m_cfg.viterations = simulation_precision; + bt_soft_body->m_cfg.diterations = simulation_precision; + bt_soft_body->m_cfg.citerations = simulation_precision; + bt_soft_body->m_cfg.kDP = damping_coefficient; + bt_soft_body->m_cfg.kDG = drag_coefficient; + bt_soft_body->m_cfg.kPR = pressure_coefficient; + bt_soft_body->m_cfg.kMT = pose_matching_coefficient; + bt_soft_body->setTotalMass(total_mass); + + btSoftBodyHelpers::ReoptimizeLinkOrder(bt_soft_body); + bt_soft_body->updateBounds(); + + // Set pinned nodes + for (int i = pinned_nodes.size() - 1; 0 <= i; --i) { + bt_soft_body->setMass(pinned_nodes[i], 0); + } } -void SoftBodyBullet::destroy_soft_body() { - if (space) { - /// This step is required to assert that the body is not into the world during deletion - /// This step is required since to change the body shape the body must be re-created. - /// Here is handled the case when the body is assigned into a world and the body - /// shape is changed. - space->remove_soft_body(this); +void SoftBodyBullet::pin_node(int p_node_index) { + if (-1 == search_node_pinned(p_node_index)) { + pinned_nodes.push_back(p_node_index); } - destroyBulletCollisionObject(); - bt_soft_body = NULL; +} + +void SoftBodyBullet::unpin_node(int p_node_index) { + const int id = search_node_pinned(p_node_index); + if (-1 != id) { + pinned_nodes.remove(id); + } +} + +int SoftBodyBullet::search_node_pinned(int p_node_index) const { + for (int i = pinned_nodes.size() - 1; 0 <= i; --i) { + if (p_node_index == pinned_nodes[i]) { + return i; + } + } + return -1; } diff --git a/modules/bullet/soft_body_bullet.h b/modules/bullet/soft_body_bullet.h index 9895643b84..c775193584 100644 --- a/modules/bullet/soft_body_bullet.h +++ b/modules/bullet/soft_body_bullet.h @@ -40,7 +40,10 @@ #define x11_None 0L #endif -#include <BulletSoftBody/btSoftBodyHelpers.h> +#include "BulletSoftBody/btSoftBodyHelpers.h" +#include "collision_object_bullet.h" +#include "scene/resources/mesh.h" +#include "servers/physics_server.h" #ifdef x11_None /// This is required to re add the macro None defined by x11 compiler @@ -52,39 +55,34 @@ @author AndreaCatania */ -struct SoftShapeData {}; -struct TrimeshSoftShapeData : public SoftShapeData { - PoolVector<int> m_triangles_indices; - PoolVector<Vector3> m_vertices; - int m_triangles_num; -}; - class SoftBodyBullet : public CollisionObjectBullet { -public: - enum SoftShapeType { - SOFT_SHAPETYPE_NONE = 0, - SOFT_SHAPE_TYPE_TRIMESH - }; private: btSoftBody *bt_soft_body; + Vector<Vector<int> > indices_table; btSoftBody::Material *mat0; // This is just a copy of pointer managed by btSoftBody - SoftShapeType soft_shape_type; bool isScratched; - SoftShapeData *soft_body_shape_data; + Ref<Mesh> soft_mesh; - Transform transform; int simulation_precision; - real_t mass; - real_t stiffness; // [0,1] + real_t total_mass; + real_t linear_stiffness; // [0,1] + real_t areaAngular_stiffness; // [0,1] + real_t volume_stiffness; // [0,1] real_t pressure_coefficient; // [-inf,+inf] + real_t pose_matching_coefficient; // [0,1] real_t damping_coefficient; // [0,1] real_t drag_coefficient; // [0,1] + Vector<int> pinned_nodes; - class ImmediateGeometry *test_geometry; // TODO remove this please - Ref<SpatialMaterial> red_mat; // TODO remove this please - bool test_is_in_scene; // TODO remove this please + // Other property to add + //btScalar kVC; // Volume conversation coefficient [0,+inf] + //btScalar kDF; // Dynamic friction coefficient [0,1] + //btScalar kMT; // Pose matching coefficient [0,1] + //btScalar kCHR; // Rigid contacts hardness [0,1] + //btScalar kKHR; // Kinetic contacts hardness [0,1] + //btScalar kSHR; // Soft contacts hardness [0,1] public: SoftBodyBullet(); @@ -101,39 +99,64 @@ public: _FORCE_INLINE_ btSoftBody *get_bt_soft_body() const { return bt_soft_body; } - void set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num); - void set_body_shape_data(SoftShapeData *p_soft_shape_data, SoftShapeType p_type); + void update_visual_server(class SoftBodyVisualServerHandler *p_visual_server_handler); - void set_transform(const Transform &p_transform); - /// This function doesn't return the exact COM transform. - /// It returns the origin only of first node (vertice) of current soft body - /// --- - /// The soft body doesn't have a fixed center of mass, but is a group of nodes (vertices) - /// that each has its own position in the world. - /// For this reason return the correct COM is not so simple and must be calculate - /// Check this to improve this function http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=8803 - const Transform &get_transform() const; - void get_first_node_origin(btVector3 &p_out_origin) const; + void set_soft_mesh(const Ref<Mesh> &p_mesh); + void destroy_soft_body(); + + // Special function. This function has bad performance + void set_soft_transform(const Transform &p_transform); + + void move_all_nodes(const Transform &p_transform); + void set_node_position(int node_index, const Vector3 &p_global_position); + void set_node_position(int node_index, const btVector3 &p_global_position); + void get_node_position(int node_index, Vector3 &r_position) const; + // Heavy function, Please cache this info + void get_node_offset(int node_index, Vector3 &r_offset) const; + // Heavy function, Please cache this info + void get_node_offset(int node_index, btVector3 &r_offset) const; + + void set_node_mass(int node_index, btScalar p_mass); + btScalar get_node_mass(int node_index) const; + void reset_all_node_mass(); + void reset_all_node_positions(); void set_activation_state(bool p_active); - void set_mass(real_t p_val); - _FORCE_INLINE_ real_t get_mass() const { return mass; } - void set_stiffness(real_t p_val); - _FORCE_INLINE_ real_t get_stiffness() const { return stiffness; } + void set_total_mass(real_t p_val); + _FORCE_INLINE_ real_t get_total_mass() const { return total_mass; } + + void set_linear_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_linear_stiffness() const { return linear_stiffness; } + + void set_areaAngular_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_areaAngular_stiffness() const { return areaAngular_stiffness; } + + void set_volume_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_volume_stiffness() const { return volume_stiffness; } + void set_simulation_precision(int p_val); _FORCE_INLINE_ int get_simulation_precision() const { return simulation_precision; } + void set_pressure_coefficient(real_t p_val); _FORCE_INLINE_ real_t get_pressure_coefficient() const { return pressure_coefficient; } + + void set_pose_matching_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_pose_matching_coefficient() const { return pose_matching_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; } private: - void reload_soft_body(); - void create_soft_body(); - void destroy_soft_body(); + void set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices); + void setup_soft_body(); + + void pin_node(int p_node_index); + void unpin_node(int p_node_index); + int search_node_pinned(int p_node_index) const; }; #endif // SOFT_BODY_BULLET_H diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index 971fd39509..132c3739d6 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -36,6 +36,7 @@ #include "constraint_bullet.h" #include "godot_collision_configuration.h" #include "godot_collision_dispatcher.h" +#include "project_settings.h" #include "rigid_body_bullet.h" #include "servers/physics_server.h" #include "soft_body_bullet.h" @@ -325,7 +326,7 @@ Vector3 BulletPhysicsDirectSpaceState::get_closest_point_to_object_volume(RID p_ } } -SpaceBullet::SpaceBullet(bool p_create_soft_world) : +SpaceBullet::SpaceBullet() : broadphase(NULL), dispatcher(NULL), solver(NULL), @@ -338,7 +339,7 @@ SpaceBullet::SpaceBullet(bool p_create_soft_world) : gravityMagnitude(10), contactDebugCount(0) { - create_empty_world(p_create_soft_world); + create_empty_world(GLOBAL_DEF("physics/3d/active_soft_world", true)); direct_access = memnew(BulletPhysicsDirectSpaceState(this)); } @@ -355,6 +356,7 @@ void SpaceBullet::flush_queries() { } void SpaceBullet::step(real_t p_delta_time) { + delta_time = p_delta_time; dynamicsWorld->stepSimulation(p_delta_time, 0, 0); } @@ -483,6 +485,7 @@ void SpaceBullet::reload_collision_filters(RigidBodyBullet *p_body) { void SpaceBullet::add_soft_body(SoftBodyBullet *p_body) { if (is_using_soft_world()) { if (p_body->get_bt_soft_body()) { + p_body->get_bt_soft_body()->m_worldInfo = get_soft_body_world_info(); static_cast<btSoftRigidDynamicsWorld *>(dynamicsWorld)->addSoftBody(p_body->get_bt_soft_body(), p_body->get_collision_layer(), p_body->get_collision_mask()); } } else { @@ -494,6 +497,7 @@ void SpaceBullet::remove_soft_body(SoftBodyBullet *p_body) { if (is_using_soft_world()) { if (p_body->get_bt_soft_body()) { static_cast<btSoftRigidDynamicsWorld *>(dynamicsWorld)->removeSoftBody(p_body->get_bt_soft_body()); + p_body->get_bt_soft_body()->m_worldInfo = NULL; } } } @@ -549,7 +553,43 @@ BulletPhysicsDirectSpaceState *SpaceBullet::get_direct_state() { } btScalar calculateGodotCombinedRestitution(const btCollisionObject *body0, const btCollisionObject *body1) { - return MAX(body0->getRestitution(), body1->getRestitution()); + + const PhysicsServer::CombineMode cm = static_cast<RigidBodyBullet *>(body0->getUserPointer())->get_restitution_combine_mode(); + + switch (cm) { + case PhysicsServer::COMBINE_MODE_INHERIT: + if (static_cast<RigidBodyBullet *>(body1->getUserPointer())->get_restitution_combine_mode() != PhysicsServer::COMBINE_MODE_INHERIT) + return calculateGodotCombinedRestitution(body1, body0); + // else use MAX [This is used when the two bodies doesn't use physical material] + case PhysicsServer::COMBINE_MODE_MAX: + return MAX(body0->getRestitution(), body1->getRestitution()); + case PhysicsServer::COMBINE_MODE_MIN: + return MIN(body0->getRestitution(), body1->getRestitution()); + case PhysicsServer::COMBINE_MODE_MULTIPLY: + return body0->getRestitution() * body1->getRestitution(); + default: // Is always PhysicsServer::COMBINE_MODE_AVERAGE: + return (body0->getRestitution() + body1->getRestitution()) / 2; + } +} + +btScalar calculateGodotCombinedFriction(const btCollisionObject *body0, const btCollisionObject *body1) { + + const PhysicsServer::CombineMode cm = static_cast<RigidBodyBullet *>(body0->getUserPointer())->get_friction_combine_mode(); + + switch (cm) { + case PhysicsServer::COMBINE_MODE_INHERIT: + if (static_cast<RigidBodyBullet *>(body1->getUserPointer())->get_friction_combine_mode() != PhysicsServer::COMBINE_MODE_INHERIT) + return calculateGodotCombinedFriction(body1, body0); + // else use MULTIPLY [This is used when the two bodies doesn't use physical material] + case PhysicsServer::COMBINE_MODE_MULTIPLY: + return body0->getFriction() * body1->getFriction(); + case PhysicsServer::COMBINE_MODE_MAX: + return MAX(body0->getFriction(), body1->getFriction()); + case PhysicsServer::COMBINE_MODE_MIN: + return MIN(body0->getFriction(), body1->getFriction()); + default: // Is always PhysicsServer::COMBINE_MODE_AVERAGE: + return (body0->getFriction() * body1->getFriction()) / 2; + } } void SpaceBullet::create_empty_world(bool p_create_soft_world) { @@ -585,6 +625,7 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) { ghostPairCallback = bulletnew(btGhostPairCallback); godotFilterCallback = bulletnew(GodotFilterCallback); gCalculateCombinedRestitutionCallback = &calculateGodotCombinedRestitution; + gCalculateCombinedFrictionCallback = &calculateGodotCombinedFriction; dynamicsWorld->setWorldUserInfo(this); diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index a6c2786878..006c6462cf 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -84,7 +84,7 @@ public: }; class SpaceBullet : public RIDBullet { -private: + friend class AreaBullet; friend void onBulletTickCallback(btDynamicsWorld *world, btScalar timeStep); friend class BulletPhysicsDirectSpaceState; @@ -109,12 +109,14 @@ private: Vector<Vector3> contactDebug; int contactDebugCount; + real_t delta_time; public: - SpaceBullet(bool p_create_soft_world); + SpaceBullet(); virtual ~SpaceBullet(); void flush_queries(); + real_t get_delta_time() { return delta_time; } void step(real_t p_delta_time); _FORCE_INLINE_ btBroadphaseInterface *get_broadphase() { return broadphase; } diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 5f13474d2c..67cc7e1ba2 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -1585,7 +1585,11 @@ CSGBrush *CSGPolygon::_build_brush() { case MODE_PATH: { float bl = curve->get_baked_length(); int splits = MAX(2, Math::ceil(bl / path_interval)); - face_count = triangles.size() * 2 / 3 + splits * final_polygon.size() * 2; + if (path_joined) { + face_count = splits * final_polygon.size() * 2; + } else { + face_count = triangles.size() * 2 / 3 + splits * final_polygon.size() * 2; + } } break; } @@ -1793,8 +1797,14 @@ CSGBrush *CSGPolygon::_build_brush() { float bl = curve->get_baked_length(); int splits = MAX(2, Math::ceil(bl / path_interval)); + float u1 = 0.0; + float u2 = path_continuous_u ? 0.0 : 1.0; - Transform path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); + Transform path_to_this; + if (!path_local) { + // center on paths origin + path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); + } Transform prev_xf; @@ -1812,6 +1822,9 @@ CSGBrush *CSGPolygon::_build_brush() { for (int i = 0; i <= splits; i++) { float ofs = i * path_interval; + if (i == splits && path_joined) { + ofs = 0.0; + } Transform xf; xf.origin = curve->interpolate_baked(ofs); @@ -1836,6 +1849,11 @@ CSGBrush *CSGPolygon::_build_brush() { xf = path_to_this * xf; if (i > 0) { + if (path_continuous_u) { + u1 = u2; + u2 += (prev_xf.origin - xf.origin).length(); + }; + //put triangles where they belong //add triangles for depth for (int j = 0; j < final_polygon.size(); j++) { @@ -1850,10 +1868,10 @@ CSGBrush *CSGPolygon::_build_brush() { }; Vector2 u[4] = { - Vector2(0, 0), - Vector2(0, 1), - Vector2(1, 1), - Vector2(1, 0) + Vector2(u1, 0), + Vector2(u1, 1), + Vector2(u2, 1), + Vector2(u2, 0) }; // face 1 @@ -1888,7 +1906,7 @@ CSGBrush *CSGPolygon::_build_brush() { } } - if (i == 0) { + if (i == 0 && !path_joined) { for (int j = 0; j < triangles.size(); j += 3) { for (int k = 0; k < 3; k++) { @@ -1905,7 +1923,7 @@ CSGBrush *CSGPolygon::_build_brush() { } } - if (i == splits) { + if (i == splits && !path_joined) { for (int j = 0; j < triangles.size(); j += 3) { for (int k = 0; k < 3; k++) { @@ -2003,6 +2021,15 @@ void CSGPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path_rotation", "mode"), &CSGPolygon::set_path_rotation); ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon::get_path_rotation); + ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGPolygon::set_path_local); + ClassDB::bind_method(D_METHOD("is_path_local"), &CSGPolygon::is_path_local); + + ClassDB::bind_method(D_METHOD("set_path_continuous_u", "enable"), &CSGPolygon::set_path_continuous_u); + ClassDB::bind_method(D_METHOD("is_path_continuous_u"), &CSGPolygon::is_path_continuous_u); + + ClassDB::bind_method(D_METHOD("set_path_joined", "enable"), &CSGPolygon::set_path_joined); + ClassDB::bind_method(D_METHOD("is_path_joined"), &CSGPolygon::is_path_joined); + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGPolygon::set_material); ClassDB::bind_method(D_METHOD("get_material"), &CSGPolygon::get_material); @@ -2023,6 +2050,9 @@ void CSGPolygon::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Path"), "set_path_node", "get_path_node"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "path_interval", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_path_interval", "get_path_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_local"), "set_path_local", "is_path_local"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_joined"), "set_path_joined", "is_path_joined"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); @@ -2067,6 +2097,15 @@ float CSGPolygon::get_depth() const { return depth; } +void CSGPolygon::set_path_continuous_u(bool p_enable) { + path_continuous_u = p_enable; + _make_dirty(); +} + +bool CSGPolygon::is_path_continuous_u() const { + return path_continuous_u; +} + void CSGPolygon::set_spin_degrees(const float p_spin_degrees) { ERR_FAIL_COND(p_spin_degrees < 0.01 || p_spin_degrees > 360); spin_degrees = p_spin_degrees; @@ -2119,6 +2158,26 @@ CSGPolygon::PathRotation CSGPolygon::get_path_rotation() const { return path_rotation; } +void CSGPolygon::set_path_local(bool p_enable) { + path_local = p_enable; + _make_dirty(); + update_gizmo(); +} + +bool CSGPolygon::is_path_local() const { + return path_local; +} + +void CSGPolygon::set_path_joined(bool p_enable) { + path_joined = p_enable; + _make_dirty(); + update_gizmo(); +} + +bool CSGPolygon::is_path_joined() const { + return path_joined; +} + void CSGPolygon::set_smooth_faces(const bool p_smooth_faces) { smooth_faces = p_smooth_faces; _make_dirty(); @@ -2160,5 +2219,8 @@ CSGPolygon::CSGPolygon() { smooth_faces = false; path_interval = 1; path_rotation = PATH_ROTATION_PATH; + path_local = false; + path_continuous_u = false; + path_joined = false; path_cache = NULL; } diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index cbb5c7e041..6898cdaf64 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -334,10 +334,13 @@ private: NodePath path_node; float path_interval; PathRotation path_rotation; + bool path_local; Node *path_cache; bool smooth_faces; + bool path_continuous_u; + bool path_joined; bool _is_editable_3d_polygon() const; bool _has_editable_3d_polygon_no_depth() const; @@ -375,6 +378,15 @@ public: void set_path_rotation(PathRotation p_rotation); PathRotation get_path_rotation() const; + void set_path_local(bool p_enable); + bool is_path_local() const; + + void set_path_continuous_u(bool p_enable); + bool is_path_continuous_u() const; + + void set_path_joined(bool p_enable); + bool is_path_joined() const; + void set_smooth_faces(bool p_smooth_faces); bool get_smooth_faces() const; diff --git a/modules/csg/doc_classes/CSGBox.xml b/modules/csg/doc_classes/CSGBox.xml index 80455fda80..5ec7b5089d 100644 --- a/modules/csg/doc_classes/CSGBox.xml +++ b/modules/csg/doc_classes/CSGBox.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGBox" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Box shape. </brief_description> <description> + This node allows you to create a box for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,12 +14,16 @@ </methods> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth"> + Depth of the box measured from the center of the box. </member> <member name="height" type="float" setter="set_height" getter="get_height"> + Height of the box measured from the center of the box. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> + The material used to render the box. </member> <member name="width" type="float" setter="set_width" getter="get_width"> + Width of the box measured from the center of the box. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGCombiner.xml b/modules/csg/doc_classes/CSGCombiner.xml index b2265d7703..1cfaa74b7d 100644 --- a/modules/csg/doc_classes/CSGCombiner.xml +++ b/modules/csg/doc_classes/CSGCombiner.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGCombiner" inherits="CSGShape" category="Core" version="3.1"> <brief_description> + A CSG node that allows you to combine other CSG modifiers. </brief_description> <description> + For complex arrangements of shapes it is sometimes needed to add structure to your CSG nodes. The CSGCombiner node allows you to create this structure. The node encapsulates the result of the CSG operations of its children. In this way it is possible to do operations on one set of shapes that are children of one CSGCombiner node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner node, and then do an operation that takes the two end results as their input to create the final shape. </description> <tutorials> </tutorials> diff --git a/modules/csg/doc_classes/CSGCylinder.xml b/modules/csg/doc_classes/CSGCylinder.xml index 0cab26ad3d..92b170ed1f 100644 --- a/modules/csg/doc_classes/CSGCylinder.xml +++ b/modules/csg/doc_classes/CSGCylinder.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGCylinder" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Cylinder shape. </brief_description> <description> + This node allows you to create a cylinder (or cone) for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,16 +14,22 @@ </methods> <members> <member name="cone" type="bool" setter="set_cone" getter="is_cone"> + If true a cone is created, the [member radius] will only apply to one side. </member> <member name="height" type="float" setter="set_height" getter="get_height"> + The height of the cylinder. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> + The material used to render the cylinder. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius"> + The radius of the cylinder. </member> <member name="sides" type="int" setter="set_sides" getter="get_sides"> + The number of sides of the cylinder, the higher this number the more detail there will be in the cylinder. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + If true the normals of the cylinder are set to give a smooth effect making the cylinder seem rounded. When false the cylinder will have a flat shaded look. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGMesh.xml b/modules/csg/doc_classes/CSGMesh.xml index e5c3e5ccf3..419214b7e6 100644 --- a/modules/csg/doc_classes/CSGMesh.xml +++ b/modules/csg/doc_classes/CSGMesh.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGMesh" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Mesh shape that uses a mesh resource. </brief_description> <description> + This CSG node allows you to use any mesh resource as a CSG shape provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more then two faces. </description> <tutorials> </tutorials> @@ -12,6 +14,7 @@ </methods> <members> <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> + The mesh resource to use as a CSG shape. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGPolygon.xml b/modules/csg/doc_classes/CSGPolygon.xml index 379c512d6a..a33e5557cb 100644 --- a/modules/csg/doc_classes/CSGPolygon.xml +++ b/modules/csg/doc_classes/CSGPolygon.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGPolygon" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + Extrudes a 2D polygon shape to create a 3D mesh. </brief_description> <description> + This node takes a 2D polygon shape and extrudes it to create a 3D mesh. </description> <tutorials> </tutorials> @@ -12,38 +14,63 @@ </methods> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth"> + Extrusion depth when [member mode] is [constant MODE_DEPTH]. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> + Material to use for the resulting mesh. </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon.Mode"> + Extrusion mode. + </member> + <member name="path_continuous_u" type="bool" setter="set_path_continuous_u" getter="is_path_continuous_u"> + If true the u component of our uv will continuously increase in unison with the distance traveled along our path when [member mode] is [constant MODE_PATH]. </member> <member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval"> + Interval at which a new extrusion slice is added along the path when [member mode] is [constant MODE_PATH]. + </member> + <member name="path_joined" type="bool" setter="set_path_joined" getter="is_path_joined"> + If true the start and end of our path are joined together ensuring there is no seam when [member mode] is [constant MODE_PATH]. + </member> + <member name="path_local" type="bool" setter="set_path_local" getter="is_path_local"> + If false we extrude centered on our path, if true we extrude in relation to the position of our CSGPolygon when [member mode] is [constant MODE_PATH]. </member> <member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node"> + The [Shape] object containing the path along which we extrude when [member mode] is [constant MODE_PATH]. </member> <member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon.PathRotation"> + The method by which each slice is rotated along the path when [member mode] is [constant MODE_PATH]. </member> <member name="polygon" type="PoolVector2Array" setter="set_polygon" getter="get_polygon"> + Point array that defines the shape that we'll extrude. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + Generates smooth normals so smooth shading is applied to our mesh. </member> <member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees"> + Degrees to rotate our extrusion for each slice when [member mode] is [constant MODE_SPIN]. </member> <member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides"> + Number of extrusion when [member mode] is [constant MODE_SPIN]. </member> </members> <constants> <constant name="MODE_DEPTH" value="0" enum="Mode"> + Shape is extruded to [member depth]. </constant> <constant name="MODE_SPIN" value="1" enum="Mode"> + Shape is extruded by rotating it around an axis. </constant> <constant name="MODE_PATH" value="2" enum="Mode"> + Shape is extruded along a path set by a [Shape] set in [member path_node]. </constant> <constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation"> + Slice is not rotated. </constant> <constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation"> + Slice is rotated around the up vector of the path. </constant> <constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation"> + Slice is rotate to match the path exactly. </constant> </constants> </class> diff --git a/modules/csg/doc_classes/CSGPrimitive.xml b/modules/csg/doc_classes/CSGPrimitive.xml index bf41c40f22..2591bab7e3 100644 --- a/modules/csg/doc_classes/CSGPrimitive.xml +++ b/modules/csg/doc_classes/CSGPrimitive.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGPrimitive" inherits="CSGShape" category="Core" version="3.1"> <brief_description> + Base class for CSG primitives. </brief_description> <description> </description> @@ -12,6 +13,7 @@ </methods> <members> <member name="invert_faces" type="bool" setter="set_invert_faces" getter="is_inverting_faces"> + Invert the faces of the mesh. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGShape.xml b/modules/csg/doc_classes/CSGShape.xml index cf236a4207..90621b94f4 100644 --- a/modules/csg/doc_classes/CSGShape.xml +++ b/modules/csg/doc_classes/CSGShape.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGShape" inherits="VisualInstance" category="Core" version="3.1"> <brief_description> + The CSG base class. </brief_description> <description> + This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot. </description> <tutorials> </tutorials> @@ -13,23 +15,29 @@ <return type="bool"> </return> <description> + Returns true if this is a root shape and is thus the object that is rendered. </description> </method> </methods> <members> <member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape.Operation"> + 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"> </member> <member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision"> + 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. </member> </members> <constants> <constant name="OPERATION_UNION" value="0" enum="Operation"> + Geometry of both primitives is merged, intersecting geometry is removed. </constant> <constant name="OPERATION_INTERSECTION" value="1" enum="Operation"> + Only intersecting geometry remains, the rest is removed. </constant> <constant name="OPERATION_SUBTRACTION" value="2" enum="Operation"> + The second shape is susbtracted from the first, leaving a dent with it's shape. </constant> </constants> </class> diff --git a/modules/csg/doc_classes/CSGSphere.xml b/modules/csg/doc_classes/CSGSphere.xml index 520368506e..a0069879cb 100644 --- a/modules/csg/doc_classes/CSGSphere.xml +++ b/modules/csg/doc_classes/CSGSphere.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGSphere" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Sphere shape. </brief_description> <description> + This node allows you to create a sphere for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,14 +14,19 @@ </methods> <members> <member name="material" type="Material" setter="set_material" getter="get_material"> + The material used to render the sphere. </member> <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments"> + Number of vertical slices for the sphere. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius"> + Radius of the sphere. </member> <member name="rings" type="int" setter="set_rings" getter="get_rings"> + Number of horizontal slices for the sphere. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + If true the normals of the sphere are set to give a smooth effect making the sphere seem rounded. When false the sphere will have a flat shaded look. </member> </members> <constants> diff --git a/modules/csg/doc_classes/CSGTorus.xml b/modules/csg/doc_classes/CSGTorus.xml index 58bbef2600..187d71a2fa 100644 --- a/modules/csg/doc_classes/CSGTorus.xml +++ b/modules/csg/doc_classes/CSGTorus.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSGTorus" inherits="CSGPrimitive" category="Core" version="3.1"> <brief_description> + A CSG Torus shape. </brief_description> <description> + This node allows you to create a torus for use with the CSG system. </description> <tutorials> </tutorials> @@ -12,16 +14,22 @@ </methods> <members> <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius"> + The inner radius of the torus. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> + The material used to render the torus. </member> <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius"> + The outer radius of the torus. </member> <member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides"> + The number of edges each ring of the torus is constructed of. </member> <member name="sides" type="int" setter="set_sides" getter="get_sides"> + The number of slices the torus is constructed of. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + If true the normals of the torus are set to give a smooth effect making the torus seem rounded. When false the torus will have a flat shaded look. </member> </members> <constants> diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index 4012e821bb..70ca8d68b8 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -110,7 +110,6 @@ struct ClassAPI { bool is_singleton; bool is_instanciable; // @Unclear - bool is_creatable; bool is_reference; List<MethodAPI> methods; @@ -385,7 +384,6 @@ static List<String> generate_c_api_json(const List<ClassAPI> &p_api) { source.push_back(String("\t\t\"instanciable\": ") + (api.is_instanciable ? "true" : "false") + ",\n"); source.push_back(String("\t\t\"is_reference\": ") + (api.is_reference ? "true" : "false") + ",\n"); // @Unclear - // source.push_back(String("\t\t\"createable\": ") + (api.is_creatable ? "true" : "false") + ",\n"); source.push_back("\t\t\"constants\": {\n"); for (List<ConstantAPI>::Element *e = api.constants.front(); e; e = e->next()) { diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 7bab718b81..5e093109d5 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -1053,7 +1053,7 @@ Ref<Script> NativeScriptLanguage::get_template(const String &p_class_name, const s->set_class_name(p_class_name); return Ref<NativeScript>(s); } -bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const { return true; } diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index be093dde4b..1b39b63ad9 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -295,7 +295,7 @@ public: virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp index 8018178bd5..816b0f0cab 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.cpp +++ b/modules/gdnative/pluginscript/pluginscript_language.cpp @@ -108,7 +108,7 @@ Ref<Script> PluginScriptLanguage::get_template(const String &p_class_name, const return script; } -bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const { PoolStringArray functions; if (_desc.validate) { bool ret = _desc.validate( diff --git a/modules/gdnative/pluginscript/pluginscript_language.h b/modules/gdnative/pluginscript/pluginscript_language.h index 709345885b..2443e31361 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.h +++ b/modules/gdnative/pluginscript/pluginscript_language.h @@ -74,7 +74,7 @@ public: virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index fedc510f01..f2a1a5b50c 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -75,9 +75,11 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ bool in_keyword = false; bool in_word = false; bool in_function_name = false; + bool in_variable_declaration = false; bool in_member_variable = false; bool in_node_path = false; bool is_hex_notation = false; + bool expect_type = false; Color keyword_color; Color color; @@ -205,6 +207,8 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (str[k] == '(') { in_function_name = true; + } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_VAR)) { + in_variable_declaration = true; } } @@ -222,6 +226,28 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (is_symbol) { in_function_name = false; in_member_variable = false; + + if (expect_type && str[j] != ' ' && str[j] != '\t' && str[j] != ':') { + expect_type = false; + } + if (j > 0 && str[j] == '>' && str[j - 1] == '-') { + expect_type = true; + } + + if (in_variable_declaration || previous_text == "(" || previous_text == ",") { + int k = j; + // Skip space + while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { + k++; + } + + if (str[k] == ':') { + // has type hint + expect_type = true; + } + } + + in_variable_declaration = false; } if (!in_node_path && in_region == -1 && str[j] == '$') { @@ -256,6 +282,9 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } else if (is_number) { next_type = NUMBER; color = number_color; + } else if (expect_type) { + next_type = TYPE; + color = type_color; } else { next_type = IDENTIFIER; } @@ -330,6 +359,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color"); node_path_color = EDITOR_GET("text_editor/highlighting/gdscript/node_path_color"); + type_color = EDITOR_GET("text_editor/highlighting/base_type_color"); } SyntaxHighlighter *GDScriptSyntaxHighlighter::create() { diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 0296ab7652..b8cb4a65e9 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -44,7 +44,8 @@ private: FUNCTION, KEYWORD, MEMBER, - IDENTIFIER + IDENTIFIER, + TYPE, }; // colours @@ -56,6 +57,7 @@ private: Color number_color; Color member_color; Color node_path_color; + Color type_color; public: static SyntaxHighlighter *create(); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index f23e7854a5..8bd29ffc55 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -220,16 +220,14 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { for (const Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) { + GDScriptFunction *func = E->get(); MethodInfo mi; mi.name = E->key(); - for (int i = 0; i < E->get()->get_argument_count(); i++) { - PropertyInfo arg; - arg.type = Variant::NIL; //variant - arg.name = E->get()->get_argument_name(i); - mi.arguments.push_back(arg); + for (int i = 0; i < func->get_argument_count(); i++) { + mi.arguments.push_back(func->get_argument_type(i)); } - mi.return_val.name = "Variant"; + mi.return_val = func->get_return_type(); p_list->push_back(mi); } } @@ -277,16 +275,14 @@ MethodInfo GDScript::get_method_info(const StringName &p_method) const { if (!E) return MethodInfo(); + GDScriptFunction *func = E->get(); MethodInfo mi; mi.name = E->key(); - for (int i = 0; i < E->get()->get_argument_count(); i++) { - PropertyInfo arg; - arg.type = Variant::NIL; //variant - arg.name = E->get()->get_argument_name(i); - mi.arguments.push_back(arg); + for (int i = 0; i < func->get_argument_count(); i++) { + mi.arguments.push_back(func->get_argument_type(i)); } - mi.return_val.name = "Variant"; + mi.return_val = func->get_return_type(); return mi; } @@ -941,8 +937,12 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { if (err.error == Variant::CallError::CALL_OK) { return true; //function exists, call was successful } - } else + } else { + if (!E->get().data_type.is_type(p_value)) { + return false; // Type mismatch + } members[E->get().index] = p_value; + } return true; } } @@ -1735,10 +1735,13 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "NAN", "self", "true", + "void", // functions + "as", "assert", "breakpoint", "class", + "class_name", "extends", "is", "func", @@ -1788,6 +1791,82 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { } } +bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { + + return p_type == "GDScript"; +} + +String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type) const { + + PoolVector<uint8_t> sourcef; + Error err; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); + if (err) { + return String(); + } + + int len = f->get_len(); + sourcef.resize(len + 1); + PoolVector<uint8_t>::Write w = sourcef.write(); + int r = f->get_buffer(w.ptr(), len); + f->close(); + memdelete(f); + ERR_FAIL_COND_V(r != len, String()); + w[len] = 0; + + String s; + if (s.parse_utf8((const char *)w.ptr())) { + return String(); + } + + GDScriptParser parser; + + parser.parse(s, p_path.get_base_dir(), true, p_path); + + if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) { + + const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree()); + if (r_base_type) { + GDScriptParser::DataType base_type; + if (c->base_type.has_type) { + base_type = c->base_type; + while (base_type.has_type && base_type.kind != GDScriptParser::DataType::NATIVE) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } else { + base_type = GDScriptParser::DataType(); + } + } break; + default: { + base_type = GDScriptParser::DataType(); + } break; + } + } + } + if (base_type.has_type) { + *r_base_type = base_type.native_type; + } else { + // Fallback + if (c->extends_used && c->extends_class.size() == 1) { + *r_base_type = c->extends_class[0]; + } else if (!c->extends_used) { + *r_base_type = "Reference"; + } + } + } + return c->name; + } + + return String(); +} + GDScriptLanguage::GDScriptLanguage() { calls = 0; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index a35b0a10d5..d5fe7a000b 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -64,6 +64,7 @@ class GDScript : public Script { StringName setter; StringName getter; MultiplayerAPI::RPCMode rpc_mode; + GDScriptDataType data_type; }; friend class GDScriptInstance; @@ -145,8 +146,13 @@ public: const Map<StringName, Ref<GDScript> > &get_subclasses() const { return subclasses; } const Map<StringName, Variant> &get_constants() const { return constants; } const Set<StringName> &get_members() const { return members; } + const GDScriptDataType &get_member_type(const StringName &p_member) const { + ERR_FAIL_COND_V(!member_indices.has(p_member), GDScriptDataType()); + return member_indices[p_member].data_type; + } const Map<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; } const Ref<GDScriptNativeClass> &get_native() const { return native; } + const String &get_script_class_name() const { return name; } virtual bool has_script_signal(const StringName &p_signal) const; virtual void get_script_signal_list(List<MethodInfo> *r_signals) const; @@ -391,7 +397,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; @@ -439,6 +445,11 @@ public: virtual void get_recognized_extensions(List<String> *p_extensions) const; + /* GLOBAL CLASSES */ + + virtual bool handles_global_class_type(const String &p_type) const; + virtual String get_global_class_name(const String &p_path, String *r_base_type = NULL) const; + GDScriptLanguage(); ~GDScriptLanguage(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 85c36647a1..a428ccd306 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -111,23 +111,50 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP return true; } -/* -int GDScriptCompiler::_parse_subexpression(CodeGen& codegen,const GDScriptParser::Node *p_expression) { - +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { + if (!p_datatype.has_type) { + return GDScriptDataType(); + } - int ret = _parse_expression(codegen,p_expression); - if (ret<0) - return ret; + GDScriptDataType result; + result.has_type = true; - if (ret&(GDScriptFunction::ADDR_TYPE_STACK<<GDScriptFunction::ADDR_BITS)) { - codegen.stack_level++; - codegen.check_max_stack_level(); - //stack was used, keep value + switch (p_datatype.kind) { + case GDScriptParser::DataType::BUILTIN: { + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = p_datatype.builtin_type; + } break; + case GDScriptParser::DataType::NATIVE: { + result.kind = GDScriptDataType::NATIVE; + result.native_type = p_datatype.native_type; + } break; + case GDScriptParser::DataType::SCRIPT: { + result.kind = GDScriptDataType::SCRIPT; + result.script_type = p_datatype.script_type; + result.native_type = result.script_type->get_instance_base_type(); + } + case GDScriptParser::DataType::GDSCRIPT: { + result.kind = GDScriptDataType::GDSCRIPT; + result.script_type = p_datatype.script_type; + result.native_type = result.script_type->get_instance_base_type(); + } break; + case GDScriptParser::DataType::CLASS: { + result.kind = GDScriptDataType::GDSCRIPT; + if (p_datatype.class_type->name == StringName()) { + result.script_type = Ref<GDScript>(main_script); + } else { + result.script_type = class_map[p_datatype.class_type->name]; + } + result.native_type = result.script_type->get_instance_base_type(); + } break; + default: { + ERR_PRINT("Parser bug: converting unresolved type."); + result.has_type = false; + } } - return ret; + return result; } -*/ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level) { @@ -263,21 +290,47 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: owner = owner->_owner; } - /* - handled in constants now - if (codegen.script->subclasses.has(identifier)) { - //same with a subclass, make it a local constant. - int idx = codegen.get_constant_pos(codegen.script->subclasses[identifier]); - return idx|(GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT<<GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) - - }*/ - if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } + /* TRY GLOBAL CLASSES */ + + if (ScriptServer::is_global_class(identifier)) { + + const GDScriptParser::ClassNode *class_node = codegen.class_node; + while (class_node->owner) { + class_node = class_node->owner; + } + + if (class_node->name == identifier) { + _set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression); + return -1; + } + + RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + return -1; + } + + Variant key = res; + int idx; + + if (!codegen.constant_map.has(key)) { + + idx = codegen.constant_map.size(); + codegen.constant_map[key] = idx; + + } else { + idx = codegen.constant_map[key]; + } + + return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + } + #ifdef TOOLS_ENABLED if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { @@ -395,6 +448,83 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return dst_addr; } break; + case GDScriptParser::Node::TYPE_CAST: { + const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); + + int slevel = p_stack_level; + int src_addr = _parse_expression(codegen, cn->source_node, slevel); + if (src_addr < 0) + return src_addr; + if (src_addr & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + switch (cn->cast_type.kind) { + case GDScriptParser::DataType::BUILTIN: { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); + codegen.opcodes.push_back(cn->cast_type.builtin_type); + } break; + case GDScriptParser::DataType::NATIVE: { + int class_idx; + if (GDScriptLanguage::get_singleton()->get_global_map().has(cn->cast_type.native_type)) { + + class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cn->cast_type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + } else { + _set_error("Invalid native class type '" + String(cn->cast_type.native_type) + "'.", cn); + return -1; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator + codegen.opcodes.push_back(class_idx); // variable type + } break; + case GDScriptParser::DataType::CLASS: { + + Variant script; + int idx = -1; + if (cn->cast_type.class_type->name == StringName()) { + script = codegen.script; + } else { + StringName name = cn->cast_type.class_type->name; + if (class_map[name] == codegen.script->subclasses[name]) { + idx = codegen.get_name_map_pos(name); + idx |= GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS; + } else { + script = class_map[name]; + } + } + + if (idx < 0) { + idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + } + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + + Variant script = cn->cast_type.script_type; + int idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + } break; + default: { + _set_error("Parser bug: unresolved data type.", cn); + return -1; + } + } + + codegen.opcodes.push_back(src_addr); // source adddress + int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode + codegen.alloc_stack(p_stack_level); + return dst_addr; + + } break; case GDScriptParser::Node::TYPE_OPERATOR: { //hell breaks loose @@ -747,14 +877,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: case GDScriptParser::OperatorNode::OP_BIT_INVERT: { if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) return -1; } break; - case GDScriptParser::OperatorNode::OP_PREINC: { - } break; //? - case GDScriptParser::OperatorNode::OP_PREDEC: { - } break; - case GDScriptParser::OperatorNode::OP_INC: { - } break; - case GDScriptParser::OperatorNode::OP_DEC: { - } break; //binary operators (in precedence order) case GDScriptParser::OperatorNode::OP_IN: { if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level)) return -1; @@ -828,7 +950,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: if (on->arguments[0]->type == GDScriptParser::Node::TYPE_OPERATOR && (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX || static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED)) { - // SET (chained) MODE! + // SET (chained) MODE! #ifdef DEBUG_ENABLED if (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { const GDScriptParser::OperatorNode *inon = static_cast<GDScriptParser::OperatorNode *>(on->arguments[0]); @@ -1029,12 +1151,87 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: if (src_address_b < 0) return -1; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + GDScriptParser::DataType assign_type = on->arguments[0]->get_datatype(); + + if (assign_type.has_type && !on->arguments[1]->get_datatype().has_type) { + // Typed assignment + switch (assign_type.kind) { + case GDScriptParser::DataType::BUILTIN: { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator + codegen.opcodes.push_back(assign_type.builtin_type); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + case GDScriptParser::DataType::NATIVE: { + int class_idx; + if (GDScriptLanguage::get_singleton()->get_global_map().has(assign_type.native_type)) { + + class_idx = GDScriptLanguage::get_singleton()->get_global_map()[assign_type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + } else { + _set_error("Invalid native class type '" + String(assign_type.native_type) + "'.", on->arguments[0]); + return -1; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator + codegen.opcodes.push_back(class_idx); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + case GDScriptParser::DataType::CLASS: { + + Variant script; + int idx = -1; + if (assign_type.class_type->name == StringName()) { + script = codegen.script; + } else { + StringName name = assign_type.class_type->name; + if (class_map[name] == codegen.script->subclasses[name]) { + idx = codegen.get_name_map_pos(name); + idx |= GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS; + } else { + script = class_map[name]; + } + } + + if (idx < 0) { + idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + } + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + + Variant script = assign_type.script_type; + int idx = codegen.get_constant_pos(script); + idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + } + } + } else { + // Either untyped assignment or already type-checked by the parser + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + } return dst_address_a; //if anything, returns wathever was assigned or correct stack position } - } break; case GDScriptParser::OperatorNode::OP_IS: { @@ -1490,6 +1687,18 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (p_func) { gdfunc->_static = p_func->_static; gdfunc->rpc_mode = p_func->rpc_mode; + gdfunc->argument_types.resize(p_func->argument_types.size()); + for (int i = 0; i < p_func->argument_types.size(); i++) { + gdfunc->argument_types[i] = _gdtype_from_datatype(p_func->argument_types[i]); + } + gdfunc->return_type = _gdtype_from_datatype(p_func->return_type); + } else { + gdfunc->_static = false; + gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + gdfunc->return_type = GDScriptDataType(); + gdfunc->return_type.has_type = true; + gdfunc->return_type.kind = GDScriptDataType::BUILTIN; + gdfunc->return_type.builtin_type = Variant::NIL; } #ifdef TOOLS_ENABLED @@ -1618,12 +1827,23 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser return OK; } -Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - - Map<StringName, Ref<GDScript> > old_subclasses; +Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - if (p_keep_state) { - old_subclasses = p_script->subclasses; + if (p_class->owner && p_class->owner->owner) { + // Owner is not root + StringName owner_name = p_class->owner->name; + if (!parsed_classes.has(owner_name)) { + if (parsing_classes.has(owner_name)) { + _set_error("Cyclic class reference for '" + String(owner_name) + "'.", p_class); + return ERR_PARSE_ERROR; + } + parsing_classes.insert(owner_name); + Error err = _parse_class_level(class_map[owner_name].ptr(), class_map[owner_name]->_owner, p_class->owner, p_keep_state); + if (err) { + return err; + } + parsing_classes.erase(owner_name); + } } p_script->native = Ref<GDScriptNativeClass>(); @@ -1647,236 +1867,100 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons Ref<GDScriptNativeClass> native; - if (p_class->extends_used) { - //do inheritance - String path = p_class->extends_file; - - Ref<GDScript> script; - - if (path != "") { - //path (and optionally subclasses) - - if (path.is_rel_path()) { - - String base; - - if (p_owner) { - GDScript *current_class = p_owner; - while (current_class != NULL) { - base = current_class->get_path(); - if (base == "") - current_class = current_class->_owner; - else - break; - } - } else { - base = p_script->get_path(); - } - - if (base == "" || base.is_rel_path()) { - _set_error("Could not resolve relative path for parent class: " + path, p_class); - return ERR_FILE_NOT_FOUND; - } - path = base.get_base_dir().plus_file(path).simplify_path(); - } - script = ResourceLoader::load(path); - if (script.is_null()) { - _set_error("Could not load base class: " + path, p_class); - return ERR_FILE_NOT_FOUND; - } - if (!script->valid) { - - _set_error("Script not fully loaded (cyclic preload?): " + path, p_class); - return ERR_BUSY; - } - //print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid)); - - if (p_class->extends_class.size()) { - - for (int i = 0; i < p_class->extends_class.size(); i++) { - - String sub = p_class->extends_class[i]; - if (script->subclasses.has(sub)) { - - Ref<Script> subclass = script->subclasses[sub]; //avoid reference from disappearing - script = subclass; - } else { - - _set_error("Could not find subclass: " + sub, p_class); - return ERR_FILE_NOT_FOUND; - } - } - } - - } else { - - ERR_FAIL_COND_V(p_class->extends_class.size() == 0, ERR_BUG); - //look around for the subclasses - - String base = p_class->extends_class[0]; - GDScript *p = p_owner; - Ref<GDScript> base_class; - - while (p) { - - if (p->subclasses.has(base)) { - - base_class = p->subclasses[base]; - break; - } - - if (p->constants.has(base)) { - - base_class = p->constants[base]; - if (base_class.is_null()) { - _set_error("Constant is not a class: " + base, p_class); - return ERR_SCRIPT_FAILED; - } - break; - } - - p = p->_owner; - } - - if (base_class.is_valid()) { - - String ident = base; - - for (int i = 1; i < p_class->extends_class.size(); i++) { - - String subclass = p_class->extends_class[i]; - - ident += ("." + subclass); - - if (base_class->subclasses.has(subclass)) { - - base_class = base_class->subclasses[subclass]; - } else if (base_class->constants.has(subclass)) { - - Ref<GDScript> new_base_class = base_class->constants[subclass]; - if (new_base_class.is_null()) { - _set_error("Constant is not a class: " + ident, p_class); - return ERR_SCRIPT_FAILED; - } - base_class = new_base_class; - } else { - - _set_error("Could not find subclass: " + ident, p_class); - return ERR_FILE_NOT_FOUND; - } - } - - script = base_class; - - } else { - - if (p_class->extends_class.size() > 1) { - - _set_error("Invalid inheritance (unknown class+subclasses)", p_class); - return ERR_FILE_NOT_FOUND; - } - //if not found, try engine classes - if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - - _set_error("Unknown class: '" + base + "'", p_class); - return ERR_FILE_NOT_FOUND; + // Inheritance + switch (p_class->base_type.kind) { + case GDScriptParser::DataType::CLASS: { + StringName base_name = p_class->base_type.class_type->name; + // Make sure dependency is parsed first + if (!parsed_classes.has(base_name)) { + if (parsing_classes.has(base_name)) { + _set_error("Cyclic class reference for '" + String(base_name) + "'.", p_class); + return ERR_PARSE_ERROR; } - - int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; - native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; - if (!native.is_valid()) { - - _set_error("Global not a class: '" + base + "'", p_class); - - return ERR_FILE_NOT_FOUND; + parsing_classes.insert(base_name); + Error err = _parse_class_level(class_map[base_name].ptr(), class_map[base_name]->_owner, p_class->base_type.class_type, p_keep_state); + if (err) { + return err; } + parsing_classes.erase(base_name); } - } - - if (script.is_valid()) { - - p_script->base = script; + Ref<GDScript> base = class_map[base_name]; + p_script->base = base; p_script->_base = p_script->base.ptr(); - p_script->member_indices = script->member_indices; - - } else if (native.is_valid()) { - + p_script->member_indices = base->member_indices; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> base = p_class->base_type.script_type; + p_script->base = base; + p_script->_base = p_script->base.ptr(); + p_script->member_indices = base->member_indices; + } break; + case GDScriptParser::DataType::NATIVE: { + int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_class->base_type.native_type]; + native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; + ERR_FAIL_COND_V(native.is_null(), ERR_BUG); p_script->native = native; - } else { - - _set_error("Could not determine inheritance", p_class); - return ERR_FILE_NOT_FOUND; - } - - } else { - // without extends, implicitly extend Reference - int native_idx = GDScriptLanguage::get_singleton()->get_global_map()["Reference"]; - native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; - ERR_FAIL_COND_V(native.is_null(), ERR_BUG); - p_script->native = native; + } break; + default: { + _set_error("Parser bug: invalid inheritance.", p_class); + return ERR_BUG; + } break; } - //print_line("Script: "+p_script->get_path()+" indices: "+itos(p_script->member_indices.size())); - for (int i = 0; i < p_class->variables.size(); i++) { StringName name = p_class->variables[i].identifier; - if (p_script->member_indices.has(name)) { - _set_error("Member '" + name + "' already exists (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } - if (_is_class_member_property(p_script, name)) { - _set_error("Member '" + name + "' already exists as a class property.", p_class); - return ERR_ALREADY_EXISTS; - } - if (p_class->variables[i]._export.type != Variant::NIL) { + GDScript::MemberInfo minfo; + minfo.index = p_script->member_indices.size(); + minfo.setter = p_class->variables[i].setter; + minfo.getter = p_class->variables[i].getter; + minfo.rpc_mode = p_class->variables[i].rpc_mode; + minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type); + + PropertyInfo prop_info = minfo.data_type; + prop_info.name = name; + PropertyInfo export_info = p_class->variables[i]._export; - p_script->member_info[name] = p_class->variables[i]._export; + if (export_info.type != Variant::NIL) { + + if (!minfo.data_type.has_type) { + prop_info.type = export_info.type; + prop_info.class_name = export_info.class_name; + } + prop_info.hint = export_info.hint; + prop_info.hint_string = export_info.hint_string; + prop_info.usage = export_info.usage; #ifdef TOOLS_ENABLED if (p_class->variables[i].default_value.get_type() != Variant::NIL) { - p_script->member_default_values[name] = p_class->variables[i].default_value; } #endif } else { - - p_script->member_info[name] = PropertyInfo(Variant::NIL, name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); + prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } - //int new_idx = p_script->member_indices.size(); - GDScript::MemberInfo minfo; - minfo.index = p_script->member_indices.size(); - minfo.setter = p_class->variables[i].setter; - minfo.getter = p_class->variables[i].getter; - minfo.rpc_mode = p_class->variables[i].rpc_mode; - + p_script->member_info[name] = prop_info; p_script->member_indices[name] = minfo; p_script->members.insert(name); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = p_class->variables[i].line; #endif } - for (int i = 0; i < p_class->constant_expressions.size(); i++) { + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { - StringName name = p_class->constant_expressions[i].identifier; - ERR_CONTINUE(p_class->constant_expressions[i].expression->type != GDScriptParser::Node::TYPE_CONSTANT); + StringName name = E->key(); - if (_is_class_member_property(p_script, name)) { - _set_error("Member '" + name + "' already exists as a class property.", p_class); - return ERR_ALREADY_EXISTS; - } + ERR_CONTINUE(E->get().expression->type != GDScriptParser::Node::TYPE_CONSTANT); - GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(p_class->constant_expressions[i].expression); + GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(E->get().expression); p_script->constants.insert(name, constant->value); -//p_script->constants[constant->value].make_const(); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = p_class->constant_expressions[i].expression->line; + p_script->member_lines[name] = E->get().expression->line; #endif } @@ -1909,23 +1993,27 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons p_script->_signals[name] = p_class->_signals[i].arguments; } + + if (p_class->name != StringName()) { + parsed_classes.insert(p_class->name); + } + //parse sub-classes for (int i = 0; i < p_class->subclasses.size(); i++) { StringName name = p_class->subclasses[i]->name; - Ref<GDScript> subclass; + Ref<GDScript> subclass = class_map[name]; - if (old_subclasses.has(name)) { - subclass = old_subclasses[name]; - } else { - subclass.instance(); + // Subclass might still be parsing, just skip it + if (!parsed_classes.has(name) && !parsing_classes.has(name)) { + parsing_classes.insert(name); + Error err = _parse_class_level(subclass.ptr(), p_script, p_class->subclasses[i], p_keep_state); + if (err) + return err; + parsing_classes.erase(name); } - Error err = _parse_class(subclass.ptr(), p_script, p_class->subclasses[i], p_keep_state); - if (err) - return err; - #ifdef TOOLS_ENABLED p_script->member_lines[name] = p_class->subclasses[i]->line; @@ -1935,6 +2023,10 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons p_script->subclasses.insert(name, subclass); } + return OK; +} + +Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { //parse methods bool has_initializer = false; @@ -1975,44 +2067,6 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons } #ifdef DEBUG_ENABLED - //validate setters/getters if debug is enabled - for (int i = 0; i < p_class->variables.size(); i++) { - - if (p_class->variables[i].setter) { - const Map<StringName, GDScriptFunction *>::Element *E = p_script->get_member_functions().find(p_class->variables[i].setter); - if (!E) { - _set_error("Setter function '" + String(p_class->variables[i].setter) + "' not found in class.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - - if (E->get()->is_static()) { - - _set_error("Setter function '" + String(p_class->variables[i].setter) + "' is static.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - } - if (p_class->variables[i].getter) { - const Map<StringName, GDScriptFunction *>::Element *E = p_script->get_member_functions().find(p_class->variables[i].getter); - if (!E) { - _set_error("Getter function '" + String(p_class->variables[i].getter) + "' not found in class.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - - if (E->get()->is_static()) { - - _set_error("Getter function '" + String(p_class->variables[i].getter) + "' is static.", NULL); - err_line = p_class->variables[i].line; - err_column = 0; - return ERR_PARSE_ERROR; - } - } - } //validate instances if keeping state @@ -2065,22 +2119,67 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons } #endif + for (int i = 0; i < p_class->subclasses.size(); i++) { + StringName name = p_class->subclasses[i]->name; + Ref<GDScript> subclass = class_map[name]; + + Error err = _parse_class_blocks(subclass.ptr(), p_class->subclasses[i], p_keep_state); + if (err) { + return err; + } + } + p_script->valid = true; return OK; } +void GDScriptCompiler::_make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + + Map<StringName, Ref<GDScript> > old_subclasses; + + if (p_keep_state) { + old_subclasses = p_script->subclasses; + } + + for (int i = 0; i < p_class->subclasses.size(); i++) { + StringName name = p_class->subclasses[i]->name; + + Ref<GDScript> subclass; + + if (old_subclasses.has(name)) { + subclass = old_subclasses[name]; + } else { + subclass.instance(); + } + + subclass->_owner = const_cast<GDScript *>(p_script); + class_map.insert(name, subclass); + + _make_scripts(subclass.ptr(), p_class->subclasses[i], p_keep_state); + } +} + Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { err_line = -1; err_column = -1; error = ""; parser = p_parser; + main_script = p_script; const GDScriptParser::Node *root = parser->get_parse_tree(); ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_INVALID_DATA); source = p_script->get_path(); - Error err = _parse_class(p_script, NULL, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + // Create scripts for subclasses beforehand so they can be referenced + _make_scripts(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + + Error err = _parse_class_level(p_script, NULL, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + + if (err) + return err; + + err = _parse_class_blocks(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); if (err) return err; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 237b0de9e7..55f5e2b48e 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -31,12 +31,17 @@ #ifndef GDSCRIPT_COMPILER_H #define GDSCRIPT_COMPILER_H +#include "core/set.h" #include "gdscript.h" #include "gdscript_parser.h" class GDScriptCompiler { const GDScriptParser *parser; + Map<StringName, Ref<GDScript> > class_map; + Set<StringName> parsed_classes; + Set<StringName> parsing_classes; + GDScript *main_script; struct CodeGen { GDScript *script; @@ -138,11 +143,15 @@ class GDScriptCompiler { bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level); bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false); + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; + int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level); int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false); Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1); Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); - Error _parse_class(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + void _make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); int err_line; int err_column; StringName source; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index c0c3bd7b06..2e4a4c40dd 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -37,6 +37,7 @@ #include "os/file_access.h" #ifdef TOOLS_ENABLED +#include "core/reference.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" #include "engine.h" @@ -53,21 +54,45 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("' '"); } Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const { +#ifdef TOOLS_ENABLED + bool th = EDITOR_DEF("text_editor/completion/add_type_hints", false); +#else + bool th = false; +#endif String _template = "extends %BASE%\n" "\n" "# Declare member variables here. Examples:\n" - "# var a = 2\n" - "# var b = \"text\"\n" + "# var a %INT_TYPE%= 2\n" + "# var b %STRING_TYPE%= \"text\"\n" "\n" "# Called when the node enters the scene tree for the first time.\n" - "func _ready():\n" + "func _ready()%VOID_RETURN%:\n" "%TS%pass # Replace with function body.\n" "\n" "# Called every frame. 'delta' is the elapsed time since the previous frame.\n" - "#func _process(delta):\n" + "#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%:\n" "#%TS%pass\n"; +#ifdef TOOLS_ENABLED + if (EDITOR_DEF("text_editor/completion/add_type_hints", false)) { + _template = _template.replace("%INT_TYPE%", ": int "); + _template = _template.replace("%STRING_TYPE%", ": String "); + _template = _template.replace("%FLOAT_TYPE%", " : float"); + _template = _template.replace("%VOID_RETURN%", " -> void"); + } else { + _template = _template.replace("%INT_TYPE%", ""); + _template = _template.replace("%STRING_TYPE%", ""); + _template = _template.replace("%FLOAT_TYPE%", ""); + _template = _template.replace("%VOID_RETURN%", ""); + } +#else + _template = _template.replace("%INT_TYPE%", ""); + _template = _template.replace("%STRING_TYPE%", ""); + _template = _template.replace("%FLOAT_TYPE%", ""); + _template = _template.replace("%VOID_RETURN%", ""); +#endif + _template = _template.replace("%BASE%", p_base_class_name); _template = _template.replace("%TS%", _get_indentation()); @@ -91,11 +116,11 @@ void GDScriptLanguage::make_template(const String &p_class_name, const String &p p_script->set_source_code(src); } -bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const { GDScriptParser parser; - Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path); + Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines); if (err) { r_line_error = parser.get_error_line(); r_col_error = parser.get_error_column(); @@ -415,63 +440,34 @@ void GDScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_cons String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const { +#ifdef TOOLS_ENABLED + bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints"); +#else + bool th = false; +#endif + String s = "func " + p_name + "("; if (p_args.size()) { for (int i = 0; i < p_args.size(); i++) { if (i > 0) s += ", "; s += p_args[i].get_slice(":", 0); + if (th) { + String type = p_args[i].get_slice(":", 1); + if (!type.empty() && type != "var") { + s += " : " + type; + } + } } } - s += "):\n" + _get_indentation() + "pass # Replace with function body.\n"; + s += String(")") + (th ? " -> void" : "") + ":\n" + _get_indentation() + "pass # Replace with function body.\n"; return s; } -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - -struct GDScriptCompletionIdentifier { - - String enumeration; - StringName obj_type; - Ref<GDScript> script; - Variant::Type type; - Variant value; //im case there is a value, also return it +//////// COMPLETION ////////// - GDScriptCompletionIdentifier() : - type(Variant::NIL) {} -}; - -static GDScriptCompletionIdentifier _get_type_from_variant(const Variant &p_variant, bool p_allow_gdnative_class = false) { - - GDScriptCompletionIdentifier t; - t.type = p_variant.get_type(); - t.value = p_variant; - if (p_variant.get_type() == Variant::OBJECT) { - Object *obj = p_variant; - if (obj) { - - if (p_allow_gdnative_class && Object::cast_to<GDScriptNativeClass>(obj)) { - t.obj_type = Object::cast_to<GDScriptNativeClass>(obj)->get_name(); - t.value = Variant(); - } else { - - t.obj_type = obj->get_class(); - } - } - } - return t; -} - -static GDScriptCompletionIdentifier _get_type_from_pinfo(const PropertyInfo &p_info) { - - GDScriptCompletionIdentifier t; - t.type = p_info.type; - if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - t.obj_type = p_info.hint_string; - } - return t; -} +#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) struct GDScriptCompletionContext { @@ -480,1043 +476,1612 @@ struct GDScriptCompletionContext { const GDScriptParser::BlockNode *block; Object *base; String base_path; -}; - -static Ref<Reference> _get_parent_class(GDScriptCompletionContext &context) { - - if (context._class->extends_used) { - //do inheritance - String path = context._class->extends_file; - - Ref<GDScript> script; - Ref<GDScriptNativeClass> native; + int line; - if (path != "") { - //path (and optionally subclasses) - - if (path.is_rel_path()) { - - path = context.base_path.plus_file(path); - } - - if (ScriptCodeCompletionCache::get_singleton()) - script = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path); - else - script = ResourceLoader::load(path); + GDScriptCompletionContext() : + _class(NULL), + function(NULL), + block(NULL), + base(NULL) {} +}; - if (script.is_null()) { - return REF(); - } - if (!script->is_valid()) { +struct GDScriptCompletionIdentifier { + GDScriptParser::DataType type; + String enumeration; + Variant value; + const GDScriptParser::Node *assigned_expression; - return REF(); - } - //print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid)); + GDScriptCompletionIdentifier() : + assigned_expression(NULL) {} +}; - if (context._class->extends_class.size()) { +static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) { - for (int i = 0; i < context._class->extends_class.size(); i++) { + for (int i = 0; i < p_dir->get_file_count(); i++) { + r_list.insert("\"" + p_dir->get_file_path(i) + "\""); + } - String sub = context._class->extends_class[i]; - if (script->get_subclasses().has(sub)) { + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _get_directory_contents(p_dir->get_subdir(i), r_list); + } +} - script = script->get_subclasses()[sub]; - } else { +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) { - return REF(); - } - } - } + if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + String enum_name = p_info.class_name; + if (enum_name.find(".") == -1) { + return enum_name; + } + return enum_name.get_slice(".", 1); + } - if (script.is_valid()) - return script; + String n = p_info.name; + int idx = n.find(":"); + if (idx != -1) { + return n.substr(idx + 1, n.length()); + } + if (p_info.type == Variant::OBJECT) { + if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { + return p_info.hint_string; } else { + return p_info.class_name.operator String(); + } + } + if (p_info.type == Variant::NIL) { + if (p_isarg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + return "var"; + } else { + return "void"; + } + } - if (context._class->extends_class.size() == 0) { - ERR_PRINT("BUG"); - return REF(); - } - - String base = context._class->extends_class[0]; - - if (context._class->extends_class.size() > 1) { - - return REF(); - } - //if not found, try engine classes - if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { + return Variant::get_type_name(p_info.type); +} - return REF(); +static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { + GDScriptCompletionIdentifier ci; + ci.value = p_value; + ci.type.is_constant = true; + ci.type.has_type = true; + ci.type.kind = GDScriptParser::DataType::BUILTIN; + ci.type.builtin_type = p_value.get_type(); + + if (ci.type.builtin_type == Variant::OBJECT) { + Object *obj = p_value.operator Object *(); + if (!obj) { + return ci; + } + ci.type.native_type = obj->get_class_name(); + Ref<Script> scr = p_value; + if (scr.is_valid()) { + ci.type.is_meta_type = true; + } else { + ci.type.is_meta_type = false; + scr = obj->get_script(); + } + if (scr.is_valid()) { + ci.type.script_type = scr; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + ci.type.kind = GDScriptParser::DataType::GDSCRIPT; + } else { + ci.type.kind = GDScriptParser::DataType::SCRIPT; } - - int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; - native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; - return native; + ci.type.native_type = scr->get_instance_base_type(); + } else { + ci.type.kind = GDScriptParser::DataType::NATIVE; } } - return Ref<Reference>(); + return ci; } -static GDScriptCompletionIdentifier _get_native_class(GDScriptCompletionContext &context) { +static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_property) { + GDScriptCompletionIdentifier ci; - GDScriptCompletionIdentifier id; - - REF pc = _get_parent_class(context); - if (!pc.is_valid()) { - return id; + if (p_property.type == Variant::NIL) { + // Variant + return ci; } - Ref<GDScriptNativeClass> nc = pc; - Ref<GDScript> s = pc; - if (s.is_null() && nc.is_null()) { - return id; - } - while (!s.is_null()) { - nc = s->get_native(); - s = s->get_base(); - } - if (nc.is_null()) { - return id; + if (p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + ci.enumeration = p_property.class_name; } - id.type = Variant::OBJECT; - if (context.base) - id.value = context.base; - id.obj_type = nc->get_name(); - return id; + ci.type.has_type = true; + ci.type.builtin_type = p_property.type; + if (p_property.type == Variant::OBJECT) { + ci.type.kind = GDScriptParser::DataType::NATIVE; + ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } else { + ci.type.kind = GDScriptParser::DataType::BUILTIN; + } + return ci; } -static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing); - -static bool _guess_expression_type(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, GDScriptCompletionIdentifier &r_type, bool p_for_indexing = false) { - - if (p_node->type == GDScriptParser::Node::TYPE_CONSTANT) { - - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_node); +static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_gdtype) { + GDScriptCompletionIdentifier ci; + if (!p_gdtype.has_type) { + return ci; + } - r_type = _get_type_from_variant(cn->value); + ci.type.has_type = true; + ci.type.builtin_type = p_gdtype.builtin_type; + ci.type.native_type = p_gdtype.native_type; + ci.type.script_type = p_gdtype.script_type; - return true; - } else if (p_node->type == GDScriptParser::Node::TYPE_DICTIONARY) { - - r_type.type = Variant::DICTIONARY; + switch (p_gdtype.kind) { + case GDScriptDataType::BUILTIN: { + ci.type.kind = GDScriptParser::DataType::BUILTIN; + } break; + case GDScriptDataType::NATIVE: { + ci.type.kind = GDScriptParser::DataType::NATIVE; + } break; + case GDScriptDataType::GDSCRIPT: { + ci.type.kind = GDScriptParser::DataType::GDSCRIPT; + } break; + case GDScriptDataType::SCRIPT: { + ci.type.kind = GDScriptParser::DataType::SCRIPT; + } break; + } + return ci; +} - //what the heck, fill it anyway - const GDScriptParser::DictionaryNode *an = static_cast<const GDScriptParser::DictionaryNode *>(p_node); - Dictionary d; - for (int i = 0; i < an->elements.size(); i++) { - GDScriptCompletionIdentifier k; - if (_guess_expression_type(context, an->elements[i].key, p_line, k) && k.value.get_type() != Variant::NIL) { - GDScriptCompletionIdentifier v; - if (_guess_expression_type(context, an->elements[i].value, p_line, v)) { - d[k.value] = v.value; +static bool _guess_identifier_type(const GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); +static bool _guess_identifier_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); +static bool _guess_method_return_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); + +static bool _guess_expression_type(const GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_expression, GDScriptCompletionIdentifier &r_type) { + bool found = false; + switch (p_expression->type) { + case GDScriptParser::Node::TYPE_CONSTANT: { + const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression); + r_type = _type_from_variant(cn->value); + found = true; + } break; + case GDScriptParser::Node::TYPE_SELF: { + if (p_context._class) { + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::CLASS; + r_type.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + r_type.type.is_constant = true; + r_type.value = p_context.base; + found = true; + } + } break; + case GDScriptParser::Node::TYPE_IDENTIFIER: { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); + found = _guess_identifier_type(p_context, id->name, r_type); + } break; + case GDScriptParser::Node::TYPE_DICTIONARY: { + // Try to recreate the dictionary + const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); + Dictionary d; + bool full = true; + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (_guess_expression_type(p_context, dn->elements[i].key, key)) { + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, dn->elements[i].value, value)) { + if (!value.type.is_constant) { + full = false; + break; + } + d[key.value] = value.value; + } else { + full = false; + break; + } + } else { + full = false; + break; } } - } - r_type.value = d; - return true; - } else if (p_node->type == GDScriptParser::Node::TYPE_ARRAY) { - - r_type.type = Variant::ARRAY; - //what the heck, fill it anyway - const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_node); - Array arr; - arr.resize(an->elements.size()); - for (int i = 0; i < an->elements.size(); i++) { - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(context, an->elements[i], p_line, ci)) { - arr[i] = ci.value; + if (full) { + // If not fully constant, setting this value is detrimental to the inference + r_type.value = d; + r_type.type.is_constant = true; } - } - r_type.value = arr; - return true; - - } else if (p_node->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - - MethodInfo mi = GDScriptFunctions::get_info(static_cast<const GDScriptParser::BuiltInFunctionNode *>(p_node)->function); - r_type = _get_type_from_pinfo(mi.return_val); - - return true; - } else if (p_node->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - return _guess_identifier_type(context, p_line - 1, static_cast<const GDScriptParser::IdentifierNode *>(p_node)->name, r_type, p_for_indexing); - } else if (p_node->type == GDScriptParser::Node::TYPE_SELF) { - //eeh... - - r_type = _get_native_class(context); - return r_type.type != Variant::NIL; - - } else if (p_node->type == GDScriptParser::Node::TYPE_OPERATOR) { - - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node); - if (op->op == GDScriptParser::OperatorNode::OP_CALL) { - - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - r_type.type = tn->vtype; - return true; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - - const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - return _guess_expression_type(context, bin, p_line, r_type); - - } else if (op->arguments.size() > 1 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; - - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && String(id) == "new") { - - //shortcut - StringName identifier = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - - if (ClassDB::class_exists(identifier)) { - r_type.type = Variant::OBJECT; - r_type.value = Variant(); - r_type.obj_type = identifier; - return true; - } + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::DICTIONARY; + } break; + case GDScriptParser::Node::TYPE_ARRAY: { + // Try to recreate the array + const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); + Array a; + bool full = true; + a.resize(an->elements.size()); + for (int i = 0; i < an->elements.size(); i++) { + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, an->elements[i], value)) { + a[i] = value.value; + } else { + full = false; + break; } - - GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, op->arguments[0], p_line, base)) - return false; - - if (base.type == Variant::OBJECT) { - - if (id.operator String() == "new" && base.value.get_type() == Variant::OBJECT) { - - Object *obj = base.value; - if (obj && Object::cast_to<GDScriptNativeClass>(obj)) { - GDScriptNativeClass *gdnc = Object::cast_to<GDScriptNativeClass>(obj); - r_type.type = Variant::OBJECT; - r_type.value = Variant(); - r_type.obj_type = gdnc->get_name(); - return true; - } else { - - if (base.obj_type != StringName()) { - - r_type.type = Variant::OBJECT; - r_type.value = Variant(); - r_type.obj_type = base.obj_type; - return true; - } + } + if (full) { + // If not fully constant, setting this value is detrimental to the inference + r_type.value = a; + } + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::ARRAY; + } break; + case GDScriptParser::Node::TYPE_OPERATOR: { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_expression); + switch (op->op) { + case GDScriptParser::OperatorNode::OP_CALL: { + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { + const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = tn->vtype; + found = true; + break; + } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { + const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); + MethodInfo mi = GDScriptFunctions::get_info(bin->function); + r_type = _type_from_property(mi.return_val); + found = true; + break; + } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; + + GDScriptCompletionContext c = p_context; + c.line = op->line; + + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(c, op->arguments[0], base)) { + found = false; + break; } - } - if (ClassDB::has_method(base.obj_type, id)) { - -#ifdef TOOLS_ENABLED - MethodBind *mb = ClassDB::get_method(base.obj_type, id); - PropertyInfo pi = mb->get_return_info(); + // Try call if constant methods with constant arguments + if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) { + GDScriptParser::DataType native_type = base.type; - //try calling the function if constant and all args are constant, should not crash.. - Object *baseptr = base.value; - - if (mb->is_const() && pi.type == Variant::OBJECT) { - - bool all_valid = true; - Vector<Variant> args; - for (int i = 2; i < op->arguments.size(); i++) { - GDScriptCompletionIdentifier arg; + while (native_type.kind == GDScriptParser::DataType::CLASS) { + native_type = native_type.class_type->base_type; + } - if (_guess_expression_type(context, op->arguments[i], p_line, arg)) { - if (arg.value.get_type() != Variant::NIL && arg.value.get_type() != Variant::OBJECT) { // calling with object seems dangerous, i don' t know - args.push_back(arg.value); + while (native_type.kind == GDScriptParser::DataType::GDSCRIPT || native_type.kind == GDScriptParser::DataType::SCRIPT) { + if (native_type.script_type.is_valid()) { + Ref<Script> parent = native_type.script_type->get_base_script(); + if (parent.is_valid()) { + native_type.script_type = parent; } else { - all_valid = false; - break; + native_type.kind = GDScriptParser::DataType::NATIVE; + native_type.native_type = native_type.script_type->get_instance_base_type(); + if (!ClassDB::class_exists(native_type.native_type)) { + native_type.native_type = String("_") + native_type.native_type; + if (!ClassDB::class_exists(native_type.native_type)) { + native_type.has_type = false; + } + } } - } else { - all_valid = false; } } - if (all_valid && String(id) == "get_node" && ClassDB::is_parent_class(base.obj_type, "Node") && args.size()) { - - String arg1 = args[0]; - if (arg1.begins_with("/root/")) { - String which = arg1.get_slice("/", 2); - if (which != "") { - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - //print_line("find singleton"); - - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - //print_line("found "+s); - String name = s.get_slice("/", 1); - //print_line("name: "+name+", which: "+which); - if (name == which) { - String script = ProjectSettings::get_singleton()->get(s); - - if (!script.begins_with("res://")) { - script = "res://" + script; - } - - if (!script.ends_with(".gd")) { - //not a script, try find the script anyway, - //may have some success - script = script.get_basename() + ".gd"; - } - - if (FileAccess::exists(script)) { - - //print_line("is a script"); - - Ref<Script> scr; - if (ScriptCodeCompletionCache::get_singleton()) - scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script); - else - scr = ResourceLoader::load(script); - - r_type.obj_type = "Node"; - r_type.type = Variant::OBJECT; - r_type.script = scr; - r_type.value = Variant(); - - return true; - } + if (native_type.has_type && native_type.kind == GDScriptParser::DataType::NATIVE) { + MethodBind *mb = ClassDB::get_method(native_type.native_type, id); + if (mb && mb->is_const()) { + bool all_is_const = true; + Vector<Variant> args; + GDScriptCompletionContext c = p_context; + c.line = op->line; + for (int i = 2; all_is_const && i < op->arguments.size(); i++) { + GDScriptCompletionIdentifier arg; + + if (_guess_expression_type(c, op->arguments[i], arg)) { + if (arg.type.has_type && arg.type.is_constant && arg.value.get_type() != Variant::OBJECT) { + args.push_back(arg.value); + } else { + all_is_const = false; } + } else { + all_is_const = false; } } - } - } - if (baseptr) { - - if (all_valid) { - Vector<const Variant *> argptr; - for (int i = 0; i < args.size(); i++) { - argptr.push_back(&args[i]); + Object *baseptr = base.value; + + if (all_is_const && String(id) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) { + + String arg1 = args[0]; + if (arg1.begins_with("/root/")) { + String which = arg1.get_slice("/", 2); + if (which != "") { + // Try singletons first + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); + found = true; + } else { + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == which) { + String script = ProjectSettings::get_singleton()->get(s); + + if (script.begins_with("*")) { + script = script.right(1); + } + + if (!script.begins_with("res://")) { + script = "res://" + script; + } + + if (!script.ends_with(".gd")) { + //not a script, try find the script anyway, + //may have some success + script = script.get_basename() + ".gd"; + } + + if (FileAccess::exists(script)) { + Ref<Script> scr; + if (ScriptCodeCompletionCache::get_singleton()) { + scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script); + } else { + scr = ResourceLoader::load(script); + } + if (scr.is_valid()) { + r_type.type.has_type = true; + r_type.type.script_type = scr; + r_type.type.is_constant = false; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + r_type.type.kind = GDScriptParser::DataType::GDSCRIPT; + } else { + r_type.type.kind = GDScriptParser::DataType::SCRIPT; + } + r_type.value = Variant(); + found = true; + } + } + break; + } + } + } + } + } } - Variant::CallError ce; - Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); - - if (ce.error == Variant::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + if (!found && all_is_const && baseptr) { + Vector<const Variant *> argptr; + for (int i = 0; i < args.size(); i++) { + argptr.push_back(&args[i]); + } - if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != NULL) { + Variant::CallError ce; + Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); - r_type = _get_type_from_variant(ret); - return true; + if (ce.error == Variant::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != NULL) { + r_type = _type_from_variant(ret); + found = true; + } } } } } } - r_type.type = pi.type; - if (pi.hint == PROPERTY_HINT_RESOURCE_TYPE) { - r_type.obj_type = pi.hint_string; + if (!found) { + found = _guess_method_return_type_from_base(c, base, id, r_type); } - - return true; -#else - return false; -#endif - } else { - return false; } - } else { - //method for some variant.. - Variant::CallError ce; - Variant v = Variant::construct(base.type, NULL, 0, ce); - List<MethodInfo> mi; - v.get_method_list(&mi); - for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) { - - if (!E->get().name.begins_with("_") && E->get().name == id.operator String()) { - - MethodInfo mi = E->get(); - r_type.type = mi.return_val.type; - if (mi.return_val.hint == PROPERTY_HINT_RESOURCE_TYPE) { - r_type.obj_type = mi.return_val.hint_string; - } - return true; - } + } break; + case GDScriptParser::OperatorNode::OP_PARENT_CALL: { + if (!p_context._class || !op->arguments.size() || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + break; } - } - } - } else if (op->op == GDScriptParser::OperatorNode::OP_INDEX || op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { - GDScriptCompletionIdentifier p1; - GDScriptCompletionIdentifier p2; + StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { + GDScriptCompletionIdentifier base; + base.value = p_context.base; + base.type = p_context._class->base_type; - if (op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - String id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; - p2.type = Variant::STRING; - p2.value = id; - } + GDScriptCompletionContext c = p_context; + c.line = op->line; - } else { - if (op->arguments[1]) { - if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) { - - return false; + found = _guess_method_return_type_from_base(c, base, id, r_type); + } break; + case GDScriptParser::OperatorNode::OP_INDEX_NAMED: { + if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + found = false; + break; } - } - } - - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) { - - const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]); - if (p2.value.is_num()) { - int index = p2.value; - if (index < 0 || index >= an->elements.size()) - return false; - return _guess_expression_type(context, an->elements[index], p_line, r_type); - } - - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { - - const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); - if (p2.value.get_type() == Variant::NIL) - return false; - - for (int i = 0; i < dn->elements.size(); i++) { - - GDScriptCompletionIdentifier k; + GDScriptCompletionContext c = p_context; + c.line = op->line; - if (!_guess_expression_type(context, dn->elements[i].key, p_line, k)) { - - return false; + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(c, op->arguments[0], base)) { + found = false; + break; } - if (k.value.get_type() == Variant::NIL) - return false; + if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(id->name))) { + Variant value = base.value.operator Dictionary()[String(id->name)]; + r_type = _type_from_variant(value); + found = true; + break; + } - if (k.value == p2.value) { + const GDScriptParser::DictionaryNode *dn = NULL; + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); + } - return _guess_expression_type(context, dn->elements[i].value, p_line, r_type); + if (dn) { + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (!_guess_expression_type(c, dn->elements[i].key, key)) { + continue; + } + if (key.value == String(id->name)) { + r_type.assigned_expression = dn->elements[i].value; + found = _guess_expression_type(c, dn->elements[i].value, r_type); + break; + } + } } - } - } else { + if (!found) { + found = _guess_identifier_type_from_base(c, base, id->name, r_type); + } + } break; + case GDScriptParser::OperatorNode::OP_INDEX: { + if (op->arguments.size() < 2) { + found = false; + break; + } - if (op->arguments[0]) { - if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) { + GDScriptCompletionContext c = p_context; + c.line = op->line; - return false; + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(c, op->arguments[0], base)) { + found = false; + break; } - } - if (p1.value.get_type() == Variant::OBJECT) { - //?? - - if (p1.obj_type != StringName() && p2.type == Variant::STRING) { + GDScriptCompletionIdentifier index; + if (!_guess_expression_type(c, op->arguments[1], index)) { + found = false; + break; + } - StringName base_type = p1.obj_type; + if (base.value.in(index.value)) { + Variant value = base.value.get(index.value); + r_type = _type_from_variant(value); + found = true; + break; + } - if (p1.obj_type == "GDScriptNativeClass") { - //native enum - Ref<GDScriptNativeClass> gdn = p1.value; - if (gdn.is_valid()) { + // Look if it is a dictionary node + const GDScriptParser::DictionaryNode *dn = NULL; + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); + } - base_type = gdn->get_name(); + if (dn) { + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (!_guess_expression_type(c, dn->elements[i].key, key)) { + continue; } - } - StringName index = p2.value; - bool valid; - Variant::Type t = ClassDB::get_property_type(base_type, index, &valid); - if (t != Variant::NIL && valid) { - r_type.type = t; - if (t == Variant::INT || t == Variant::OBJECT) { -//check for enum! -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - - StringName getter = ClassDB::get_property_getter(base_type, index); - if (getter != StringName()) { - MethodBind *mb = ClassDB::get_method(base_type, getter); - if (mb) { - PropertyInfo rt = mb->get_return_info(); - if ((rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && t == Variant::INT) { - r_type.enumeration = rt.class_name; - } else if (t == Variant::OBJECT) { - - r_type.obj_type = rt.class_name; - } - } - } -#endif + if (key.value == index.value) { + r_type.assigned_expression = dn->elements[i].value; + found = _guess_expression_type(p_context, dn->elements[i].value, r_type); + break; } - - return true; } } - } else if (p1.value.get_type() != Variant::NIL) { - bool valid; - Variant ret = p1.value.get(p2.value, &valid); - if (valid) { - r_type = _get_type_from_variant(ret); - return true; + // Look if it is an array node + if (!found && index.value.is_num()) { + int idx = index.value; + const GDScriptParser::ArrayNode *an = NULL; + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) { + an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_ARRAY) { + an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression); + } + + if (an && idx >= 0 && an->elements.size() > idx) { + r_type.assigned_expression = an->elements[idx]; + found = _guess_expression_type(c, an->elements[idx], r_type); + break; + } } - } else { - if (p1.type != Variant::NIL) { - Variant::CallError ce; - Variant base = Variant::construct(p1.type, NULL, 0, ce); - bool valid; - Variant ret = base.get(p2.value, &valid); + // Look for valid indexing in other types + if (!found && (index.value.get_type() == Variant::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) { + Variant::CallError err; + Variant base_val = Variant::construct(base.type.builtin_type, NULL, 0, err); + bool valid = false; + Variant res = base_val.get(index.value, &valid); if (valid) { - r_type = _get_type_from_variant(ret); - return true; + r_type = _type_from_variant(res); + r_type.value = Variant(); + r_type.type.is_constant = false; + found = true; } } - } - } + } break; + default: { + if (op->arguments.size() < 2) { + found = false; + break; + } - } else { + Variant::Operator vop = Variant::OP_MAX; + switch (op->op) { + case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break; + case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break; + case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break; + case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break; + case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break; + case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break; + case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break; + case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break; + case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break; + case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break; + default: {} + } - Variant::Operator vop = Variant::OP_MAX; - switch (op->op) { - case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break; - case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break; - case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break; - case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break; - case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break; - case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break; - case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break; - case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break; - case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break; - case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break; - default: {} - } - - if (vop == Variant::OP_MAX) - return false; + if (vop == Variant::OP_MAX) { + break; + } - GDScriptCompletionIdentifier p1; - GDScriptCompletionIdentifier p2; + GDScriptCompletionContext context = p_context; + context.line = op->line; - if (op->arguments[0]) { - if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) { + GDScriptCompletionIdentifier p1; + GDScriptCompletionIdentifier p2; - return false; - } - } + if (!_guess_expression_type(context, op->arguments[0], p1)) { + found = false; + break; + } - if (op->arguments.size() > 1) { - if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) { + if (!_guess_expression_type(context, op->arguments[1], p2)) { + found = false; + break; + } - return false; - } - } + Variant::CallError ce; + bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; + Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, NULL, 0, ce); + bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; + Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, NULL, 0, ce); + // avoid potential invalid ops + if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { + v2 = 1; + v2_use_value = false; + } + if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::REAL) { + v2 = 1.0; + v2_use_value = false; + } - Variant::CallError ce; - bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; - Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type, NULL, 0, ce); - bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; - Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type, NULL, 0, ce); - // avoid potential invalid ops - if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { - v2 = 1; - v2_use_value = false; - } - if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::REAL) { - v2 = 1.0; - v2_use_value = false; + Variant res; + bool valid; + Variant::evaluate(vop, v1, v2, res, valid); + if (!valid) { + found = false; + break; + } + r_type = _type_from_variant(res); + if (!v1_use_value || !v2_use_value) { + r_type.value = Variant(); + r_type.type.is_constant = false; + } + + found = true; + } break; } + } break; + } - Variant r; - bool valid; - Variant::evaluate(vop, v1, v2, r, valid); - if (!valid) - return false; - r_type.type = r.get_type(); - if (v1_use_value && v2_use_value) - r_type.value = r; + // It may have found a null, but that's never useful + if (found && r_type.type.has_type && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) { + found = false; + } - return true; + // 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().has_type) { + r_type.type = p_expression->get_datatype(); + if (!r_type.assigned_expression) { + r_type.assigned_expression = p_expression; } + found = true; } - return false; + return found; } -static bool _guess_identifier_type_in_block(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { +static bool _guess_identifier_type(const GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { - if (context.block->if_condition && context.block->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) { - //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. - //super dirty hack, but very useful - //credit: Zylann - //TODO: this could be hacked to detect ANDed conditions too.. - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition); - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) { - //bingo - if (_guess_expression_type(context, op->arguments[1], op->line, r_type)) { - return true; + // Look in blocks first + const GDScriptParser::BlockNode *blk = p_context.block; + int last_assign_line = -1; + const GDScriptParser::Node *last_assigned_expression = NULL; + GDScriptParser::DataType var_type; + while (blk) { + if (blk->variables.has(p_identifier)) { + if (blk->variables[p_identifier]->line > p_context.line) { + return false; } - } - } - GDScriptCompletionIdentifier gdi = _get_native_class(context); - if (gdi.obj_type != StringName()) { - bool valid; - Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_identifier, &valid); - if (t != Variant::NIL && valid) { - r_type.type = t; - if (t == Variant::INT) { -//check for enum! -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) + var_type = blk->variables[p_identifier]->datatype; - StringName getter = ClassDB::get_property_getter(gdi.obj_type, p_identifier); - if (getter != StringName()) { - MethodBind *mb = ClassDB::get_method(gdi.obj_type, getter); - if (mb) { - PropertyInfo rt = mb->get_return_info(); - if (rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - r_type.enumeration = rt.class_name; - } - } + if (!last_assigned_expression && blk->variables[p_identifier]->assign && blk->variables[p_identifier]->assign->type == GDScriptParser::Node::TYPE_OPERATOR) { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->variables[p_identifier]->assign); + if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN && op->arguments.size() >= 2) { + last_assign_line = op->line; + last_assigned_expression = op->arguments[1]; } -#endif } - return true; } - } - - const GDScriptParser::Node *last_assign = NULL; - int last_assign_line = -1; - - for (int i = 0; i < context.block->statements.size(); i++) { - - if (context.block->statements[i]->line > p_line) - continue; - - if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(context.block->statements[i]); + for (const List<GDScriptParser::Node *>::Element *E = blk->statements.front(); E; E = E->next()) { + const GDScriptParser::Node *expr = E->get(); + if (expr->line > p_context.line || expr->type != GDScriptParser::Node::TYPE_OPERATOR) { + continue; + } - if (lv->assign && lv->name == p_identifier) { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(expr); + if (op->op != GDScriptParser::OperatorNode::OP_ASSIGN || op->line < last_assign_line) { + continue; + } - last_assign = lv->assign; - last_assign_line = context.block->statements[i]->line; + if (op->arguments.size() >= 2 && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]); + if (id->name == p_identifier) { + last_assign_line = op->line; + last_assigned_expression = op->arguments[1]; + } } } - if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->statements[i]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - - if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]); - - if (id->name == p_identifier) { - - last_assign = op->arguments[1]; - last_assign_line = context.block->statements[i]->line; - } + if (blk->if_condition && blk->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) { + //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. + //super dirty hack, but very useful + //credit: Zylann + //TODO: this could be hacked to detect ANDed conditions too.. + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition); + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) { + //bingo + GDScriptCompletionContext c = p_context; + c.line = op->line; + c.block = blk; + if (_guess_expression_type(p_context, op->arguments[1], r_type)) { + return true; } } } - } - - //use the last assignment, (then backwards?) - if (last_assign && last_assign_line != p_line) { - return _guess_expression_type(context, last_assign, last_assign_line, r_type); + blk = blk->parent_block; } - return false; -} - -static bool _guess_identifier_from_assignment_in_function(GDScriptCompletionContext &context, int p_src_line, const StringName &p_identifier, const StringName &p_function, GDScriptCompletionIdentifier &r_type) { - - const GDScriptParser::FunctionNode *func = NULL; - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == p_function) { - func = context._class->functions[i]; - break; + if (last_assigned_expression && last_assign_line != p_context.line) { + GDScriptCompletionContext c = p_context; + c.line = last_assign_line; + r_type.assigned_expression = last_assigned_expression; + if (_guess_expression_type(c, last_assigned_expression, r_type)) { + return true; } } - if (!func) - return false; + if (var_type.has_type) { + r_type.type = var_type; + return true; + } - for (int i = 0; i < func->body->statements.size(); i++) { + if (p_context.function) { + for (int i = 0; i < p_context.function->arguments.size(); i++) { + if (p_context.function->arguments[i] == p_identifier) { + if (p_context.function->argument_types[i].has_type) { + r_type.type = p_context.function->argument_types[i]; + return true; + } - if (func->body->statements[i]->line == p_src_line) { - break; + int def_from = p_context.function->arguments.size() - p_context.function->default_values.size(); + if (i >= def_from) { + int def_idx = def_from - i; + if (p_context.function->default_values[def_idx]->type == GDScriptParser::Node::TYPE_OPERATOR) { + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_context.function->default_values[def_idx]); + if (op->arguments.size() < 2) { + return false; + } + GDScriptCompletionContext c = p_context; + c.function = NULL; + c.block = NULL; + return _guess_expression_type(c, op->arguments[1], r_type); + } + } + break; + } } - if (func->body->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->body->statements[i]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - - if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]); - - if (id->name == p_identifier) { - - return _guess_expression_type(context, op->arguments[1], func->body->statements[i]->line, r_type); + GDScriptParser::DataType base_type = p_context._class->base_type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid() && gds->has_method(p_context.function->name)) { + GDScriptFunction *func = gds->get_member_functions()[p_context.function->name]; + if (func) { + for (int i = 0; i < func->get_argument_count(); i++) { + if (func->get_argument_name(i) == p_identifier) { + r_type = _type_from_gdtype(func->get_argument_type(i)); + return true; + } + } + } + Ref<GDScript> base_gds = gds->get_base_script(); + if (base_gds.is_valid()) { + base_type.kind = GDScriptParser::DataType::GDSCRIPT; + base_type.script_type = base_gds; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); } - } + } break; + case GDScriptParser::DataType::NATIVE: { + List<MethodInfo> methods; + ClassDB::get_method_list(base_type.native_type, &methods); + ClassDB::get_virtual_methods(base_type.native_type, &methods); + + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_context.function->name) { + MethodInfo &mi = E->get(); + for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { + if (E->get().name == p_identifier) { + r_type = _type_from_property(E->get()); + return true; + } + } + } + } + base_type.has_type = false; + } break; + default: { + base_type.has_type = false; + } break; } } } - return false; -} - -static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing) { - - //go to block first + // Check current class (including inheritance) + if (p_context._class) { + GDScriptCompletionIdentifier context_base; + context_base.value = p_context.base; + context_base.type.has_type = true; + context_base.type.kind = GDScriptParser::DataType::CLASS; + context_base.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + context_base.type.is_meta_type = p_context.function && p_context.function->_static; - const GDScriptParser::BlockNode *block = context.block; - - while (block) { - - GDScriptCompletionContext c = context; - c.block = block; - - if (_guess_identifier_type_in_block(c, p_line, p_identifier, r_type)) { + if (_guess_identifier_type_from_base(p_context, context_base, p_identifier, r_type)) { return true; } - - block = block->parent_block; } - //guess from argument if virtual - if (context.function && context.function->name != StringName()) { - - int argindex = -1; + // Check named scripts + if (ScriptServer::is_global_class(p_identifier)) { + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); + if (scr.is_valid()) { + r_type = _type_from_variant(scr); + r_type.type.is_meta_type = true; + return true; + } + return false; + } - for (int i = 0; i < context.function->arguments.size(); i++) { + // Check ClassDB + if (ClassDB::class_exists(p_identifier)) { + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::NATIVE; + r_type.type.native_type = p_identifier; + if (Engine::get_singleton()->has_singleton(p_identifier)) { + r_type.type.is_meta_type = false; + r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier); + } else { + r_type.type.is_meta_type = true; + int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; + r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + } + return true; + } - if (context.function->arguments[i] == p_identifier) { - argindex = i; - break; - } + // ClassDB again for underscore-prefixed classes + StringName under_id = String("_") + p_identifier; + if (ClassDB::class_exists(under_id)) { + r_type.type.has_type = true; + r_type.type.kind = GDScriptParser::DataType::NATIVE; + r_type.type.native_type = p_identifier; + if (Engine::get_singleton()->has_singleton(p_identifier)) { + r_type.type.is_meta_type = false; + r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier); + } else { + r_type.type.is_meta_type = true; + int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; + r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx]; } + return true; + } - if (argindex != -1) { - GDScriptCompletionIdentifier id = _get_native_class(context); - if (id.type == Variant::OBJECT && id.obj_type != StringName()) { - //this kinda sucks but meh + // Check autoload singletons + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]); + return true; + } - List<MethodInfo> vmethods; - ClassDB::get_virtual_methods(id.obj_type, &vmethods); - for (List<MethodInfo>::Element *E = vmethods.front(); E; E = E->next()) { + return false; +} - if (E->get().name == context.function->name && argindex < E->get().arguments.size()) { +static bool _guess_identifier_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (base_type.class_type->constant_expressions.has(p_identifier)) { + GDScriptParser::ClassNode::Constant c = base_type.class_type->constant_expressions[p_identifier]; + r_type.type = c.type; + if (c.expression->type == GDScriptParser::Node::TYPE_CONSTANT) { + r_type.value = static_cast<const GDScriptParser::ConstantNode *>(c.expression)->value; + } + return true; + } - PropertyInfo arg = E->get().arguments[argindex]; + if (!_static) { + for (int i = 0; i < base_type.class_type->variables.size(); i++) { + GDScriptParser::ClassNode::Member m = base_type.class_type->variables[i]; + if (m.identifier == p_identifier) { + if (m.data_type.has_type) { + r_type.type = m.data_type; + return true; + } + if (m.expression) { + if (_guess_expression_type(p_context, m.expression, r_type)) { + return true; + } + if (m.expression->get_datatype().has_type) { + r_type.type = m.expression->get_datatype(); + return true; + } + } + return false; + } + } + } + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + if (gds->get_constants().has(p_identifier)) { + r_type = _type_from_variant(gds->get_constants()[p_identifier]); + return true; + } + if (!_static) { + const Set<StringName>::Element *m = gds->get_members().find(p_identifier); + if (m) { + r_type = _type_from_gdtype(gds->get_member_type(p_identifier)); + return true; + } + } + Ref<GDScript> parent = gds->get_base_script(); + if (parent.is_valid()) { + base_type.script_type = parent; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); + if (constants.has(p_identifier)) { + r_type = _type_from_variant(constants[p_identifier]); + return true; + } - int scp = String(arg.name).find(":"); - if (scp != -1) { + if (!_static) { + List<PropertyInfo> members; + scr->get_script_property_list(&members); + for (const List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == p_identifier) { + r_type = _type_from_property(prop); + return true; + } + } + } + Ref<Script> parent = scr->get_base_script(); + if (parent.is_valid()) { + base_type.script_type = parent; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + return false; + } + } - r_type.type = Variant::OBJECT; - r_type.obj_type = String(arg.name).substr(scp + 1, String(arg.name).length()); - return true; + // Skip constants since they're all integers. Type does not matter because int has no members + List<PropertyInfo> props; + ClassDB::get_property_list(class_name, &props); + for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == p_identifier) { + StringName getter = ClassDB::get_property_getter(class_name, p_identifier); + if (getter != StringName()) { + MethodBind *g = ClassDB::get_method(class_name, getter); + if (g) { + r_type = _type_from_property(g->get_return_info()); + return true; + } } else { - - r_type.type = arg.type; - if (arg.hint == PROPERTY_HINT_RESOURCE_TYPE) - r_type.obj_type = arg.hint_string; + r_type = _type_from_property(prop); return true; } + break; } } - } + return false; + } break; + case GDScriptParser::DataType::BUILTIN: { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + if (err.error != Variant::CallError::CALL_OK) { + return false; + } + bool valid = false; + Variant res = tmp.get(p_identifier, &valid); + if (valid) { + r_type = _type_from_variant(res); + r_type.value = Variant(); + r_type.type.is_constant = false; + return true; + } + return false; + } break; + default: { + return false; + } break; } } - //guess type in constant + return false; +} - for (int i = 0; i < context._class->constant_expressions.size(); i++) { +static bool _find_last_return_in_block(const GDScriptCompletionContext &p_context, int &r_last_return_line, const GDScriptParser::Node **r_last_returned_value) { + if (!p_context.block) { + return false; + } - if (context._class->constant_expressions[i].identifier == p_identifier) { + for (int i = 0; i < p_context.block->statements.size(); i++) { + if (p_context.block->statements[i]->line < r_last_return_line) { + continue; + } + if (p_context.block->statements[i]->type != GDScriptParser::Node::TYPE_CONTROL_FLOW) { + continue; + } - ERR_FAIL_COND_V(context._class->constant_expressions[i].expression->type != GDScriptParser::Node::TYPE_CONSTANT, false); - r_type = _get_type_from_variant(static_cast<const GDScriptParser::ConstantNode *>(context._class->constant_expressions[i].expression)->value); - return true; + const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(p_context.block->statements[i]); + if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_RETURN && cf->arguments.size() > 0) { + if (cf->line > r_last_return_line) { + r_last_return_line = cf->line; + *r_last_returned_value = cf->arguments[0]; + } } } - if (!(context.function && context.function->_static)) { + // Recurse into subblocks + for (int i = 0; i < p_context.block->sub_blocks.size(); i++) { + GDScriptCompletionContext c = p_context; + c.block = p_context.block->sub_blocks[i]; + _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); + } - for (int i = 0; i < context._class->variables.size(); i++) { + return false; +} - if (context._class->variables[i].identifier == p_identifier) { +static bool _guess_method_return_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; - if (context._class->variables[i]._export.type != Variant::NIL) { + if (_static && p_method == "new") { + r_type.type = base_type; + r_type.type.is_meta_type = false; + r_type.type.is_constant = false; + return true; + } - r_type = _get_type_from_pinfo(context._class->variables[i]._export); - return true; - } else if (context._class->variables[i].expression) { - if (p_line <= context._class->variables[i].line) - return false; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (!base_type.class_type) { + base_type.has_type = false; + break; + } - bool rtype = _guess_expression_type(context, context._class->variables[i].expression, context._class->variables[i].line, r_type); - if (rtype && r_type.type != Variant::NIL) - return true; - //return _guess_expression_type(context,context._class->variables[i].expression,context._class->variables[i].line,r_type); + for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { + if (base_type.class_type->static_functions[i]->name == p_method) { + int last_return_line = -1; + const GDScriptParser::Node *last_returned_value = NULL; + GDScriptCompletionContext c = p_context; + c._class = base_type.class_type; + c.function = base_type.class_type->static_functions[i]; + c.block = c.function->body; + + _find_last_return_in_block(c, last_return_line, &last_returned_value); + if (last_returned_value) { + c.line = c.block->end_line; + return _guess_expression_type(c, last_returned_value, r_type); + } + } + } + if (!_static) { + for (int i = 0; i < base_type.class_type->functions.size(); i++) { + if (base_type.class_type->functions[i]->name == p_method) { + int last_return_line = -1; + const GDScriptParser::Node *last_returned_value = NULL; + GDScriptCompletionContext c = p_context; + c._class = base_type.class_type; + c.function = base_type.class_type->functions[i]; + c.block = c.function->body; + + _find_last_return_in_block(c, last_return_line, &last_returned_value); + if (last_returned_value) { + c.line = c.block->end_line; + return _guess_expression_type(c, last_returned_value, r_type); + } + } + } } - //try to guess from assignment in constructor or _ready - if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_ready", r_type)) - return true; - if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_enter_tree", r_type)) - return true; - if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_init", r_type)) + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + if (gds->get_member_functions().has(p_method)) { + r_type = _type_from_gdtype(gds->get_member_functions()[p_method]->get_return_type()); + return true; + } + Ref<GDScript> base_script = gds->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + List<MethodInfo> methods; + scr->get_script_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + MethodInfo &mi = E->get(); + if (mi.name == p_method) { + r_type = _type_from_property(mi.return_val); + return true; + } + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + return false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName native = base_type.native_type; + if (!ClassDB::class_exists(native)) { + native = String("_") + native; + if (!ClassDB::class_exists(native)) { + return false; + } + } + MethodBind *mb = ClassDB::get_method(native, p_method); + if (mb) { + r_type = _type_from_property(mb->get_return_info()); return true; + } + return false; + } break; + case GDScriptParser::DataType::BUILTIN: { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + return false; + } + + List<MethodInfo> methods; + tmp.get_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + MethodInfo &mi = E->get(); + if (mi.name == p_method) { + r_type = _type_from_property(mi.return_val); + return true; + } + } + return false; + } break; + default: { return false; } } } + return false; +} - //autoloads as singletons - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - String name = s.get_slice("/", 1); - if (name == String(p_identifier)) { +static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) { - String path = ProjectSettings::get_singleton()->get(s); - if (path.begins_with("*")) { - String script = path.substr(1, path.length()); + String arghint = _get_visual_datatype(p_info.return_val, false) + " " + p_info.name + "("; - if (!script.ends_with(".gd")) { - //not a script, try find the script anyway, - //may have some success - script = script.get_basename() + ".gd"; - } + int def_args = p_info.arguments.size() - p_info.default_arguments.size(); + int i = 0; + for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) { + if (i > 0) { + arghint += ", "; + } else { + arghint += " "; + } - if (FileAccess::exists(script)) { + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + arghint += _get_visual_datatype(E->get(), true) + " " + E->get().name; - //print_line("is a script"); + if (i - def_args >= 0) { + arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); + } - Ref<Script> scr; - if (ScriptCodeCompletionCache::get_singleton()) - scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script); - else - scr = ResourceLoader::load(script); + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } - r_type.obj_type = "Node"; - r_type.type = Variant::OBJECT; - r_type.script = scr; - r_type.value = Variant(); + i++; + } - return true; - } - } + if (p_info.flags & METHOD_FLAG_VARARG) { + if (p_info.arguments.size() > 0) { + arghint += ", "; + } else { + arghint += " "; } + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); + } + arghint += "..."; + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); + } + } + if (p_info.arguments.size() > 0 || (p_info.flags & METHOD_FLAG_VARARG)) { + arghint += " "; } - //global - for (Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - if (E->key() == p_identifier) { + arghint += ")"; - r_type = _get_type_from_variant(GDScriptLanguage::get_singleton()->get_global_array()[E->get()], !p_for_indexing); - return true; - } - } - return false; + return arghint; } -static void _find_identifiers_in_block(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) { +static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { - if (p_only_functions) - return; + String arghint = p_function->return_type.to_string() + " " + p_function->name.operator String() + "("; - for (int i = 0; i < context.block->statements.size(); i++) { + int def_args = p_function->arguments.size() - p_function->default_values.size(); + for (int i = 0; i < p_function->arguments.size(); i++) { + if (i > 0) { + arghint += ", "; + } else { + arghint += " "; + } - GDScriptParser::Node *statement = context.block->statements[i]; - if (statement->line > p_line) - continue; + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + arghint += p_function->argument_types[i].to_string() + " " + p_function->arguments[i].operator String(); + + if (i - def_args >= 0) { + String def_val = "<unknown>"; + if (p_function->default_values[i - def_args] && p_function->default_values[i - def_args]->type == GDScriptParser::Node::TYPE_OPERATOR) { + const GDScriptParser::OperatorNode *assign = static_cast<const GDScriptParser::OperatorNode *>(p_function->default_values[i - def_args]); + + if (assign->arguments.size() >= 2) { + if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) { + const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(assign->arguments[1]); + def_val = cn->value.get_construct_string(); + } else if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->arguments[1]); + def_val = id->name.operator String(); + } + } + } + arghint += " = " + def_val; + } + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + } - GDScriptParser::BlockNode::Type statementType = statement->type; - if (statementType == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { + if (p_function->arguments.size() > 0) { + arghint += " "; + } + arghint += ")"; - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(statement); - result.insert(lv->name.operator String()); - } else if (statementType == GDScriptParser::BlockNode::TYPE_CONTROL_FLOW) { + return arghint; +} - const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(statement); - if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_FOR) { +static void _find_enumeration_candidates(const String p_enum_hint, Set<String> &r_result) { - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(cf->arguments[0]); - result.insert(id->name.operator String()); + if (p_enum_hint.find(".") == -1) { + // Global constant + StringName current_enum = p_enum_hint; + for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { + if (GlobalConstants::get_global_constant_enum(i) == current_enum) { + r_result.insert(GlobalConstants::get_global_constant_name(i)); } } - } -} - -static void _find_identifiers_in_class(GDScriptCompletionContext &context, bool p_static, bool p_only_functions, Set<String> &result) { + } else { + String class_name = p_enum_hint.get_slice(".", 0); + String enum_name = p_enum_hint.get_slice(".", 1); - if (!p_static && !p_only_functions) { + if (!ClassDB::class_exists(class_name)) { + return; + } - for (int i = 0; i < context._class->variables.size(); i++) { - result.insert(context._class->variables[i].identifier); + List<StringName> enum_constants; + ClassDB::get_enum_constants(class_name, enum_name, &enum_constants); + for (List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { + String candidate = class_name + "." + E->get(); + r_result.insert(candidate); } } - if (!p_only_functions) { +} - for (int i = 0; i < context._class->constant_expressions.size(); i++) { - result.insert(context._class->constant_expressions[i].identifier); +static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Set<String> &r_result) { + for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) { + if (E->get()->line < p_context.line) { + r_result.insert(E->key().operator String()); } + } + if (p_context.block->parent_block) { + GDScriptCompletionContext c = p_context; + c.block = p_context.block->parent_block; + _find_identifiers_in_block(c, r_result); + } +} + +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result); - for (int i = 0; i < context._class->subclasses.size(); i++) { - result.insert(context._class->subclasses[i]->name); +static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Set<String> &r_result) { + if (!p_parent_only) { + if (!p_static && !p_only_functions) { + for (int i = 0; i < p_context._class->variables.size(); i++) { + r_result.insert(p_context._class->variables[i].identifier); + } } - } - for (int i = 0; i < context._class->static_functions.size(); i++) { - if (context._class->static_functions[i]->arguments.size()) - result.insert(context._class->static_functions[i]->name.operator String() + "("); - else - result.insert(context._class->static_functions[i]->name.operator String() + "()"); - } + if (!p_only_functions) { + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) { + r_result.insert(E->key()); + } + for (int i = 0; i < p_context._class->subclasses.size(); i++) { + r_result.insert(p_context._class->subclasses[i]->name); + } + } - if (!p_static) { + for (int i = 0; i < p_context._class->static_functions.size(); i++) { + if (p_context._class->static_functions[i]->arguments.size()) { + r_result.insert(p_context._class->static_functions[i]->name.operator String() + "("); + } else { + r_result.insert(p_context._class->static_functions[i]->name.operator String() + "()"); + } + } - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->arguments.size()) - result.insert(context._class->functions[i]->name.operator String() + "("); - else - result.insert(context._class->functions[i]->name.operator String() + "()"); + if (!p_static) { + for (int i = 0; i < p_context._class->functions.size(); i++) { + if (p_context._class->functions[i]->arguments.size()) { + r_result.insert(p_context._class->functions[i]->name.operator String() + "("); + } else { + r_result.insert(p_context._class->functions[i]->name.operator String() + "()"); + } + } } } - //globals - - Ref<Reference> base = _get_parent_class(context); + // Parents + GDScriptCompletionIdentifier base_type; + base_type.type = p_context._class->base_type; + base_type.type.is_meta_type = p_static; + base_type.value = p_context.base; - while (true) { + GDScriptCompletionContext c = p_context; + c.block = NULL; + c.function = NULL; - Ref<GDScript> script = base; - Ref<GDScriptNativeClass> nc = base; - if (script.is_valid()) { + _find_identifiers_in_base(c, base_type, p_only_functions, r_result); +} - if (!p_static && !p_only_functions) { - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - result.insert(E->get().operator String()); +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result) { + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; + + if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { + r_result.insert("new("); + } + + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + GDScriptCompletionContext c = p_context; + c._class = base_type.class_type; + c.block = NULL; + c.function = NULL; + _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> script = base_type.script_type; + if (script.is_valid()) { + if (!_static && !p_only_functions) { + for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { + r_result.insert(E->get().operator String()); + } + } + if (!p_only_functions) { + for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) { + r_result.insert(E->key().operator String()); + } + } + for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { + if (!_static || E->get()->is_static()) { + if (E->get()->get_argument_count()) { + r_result.insert(E->key().operator String() + "("); + } else { + r_result.insert(E->key().operator String() + "()"); + } + } + } + if (!p_only_functions) { + for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) { + r_result.insert(E->key().operator String()); + } + } + base_type = GDScriptParser::DataType(); + if (script->get_base().is_valid()) { + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::GDSCRIPT; + base_type.script_type = script->get_base(); + } else { + base_type.has_type = script->get_instance_base_type() != StringName(); + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.script_type = script->get_instance_base_type(); + } + } else { + return; } - } + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + if (!_static && !p_only_functions) { + List<PropertyInfo> members; + scr->get_script_property_list(&members); + for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + r_result.insert(E->get().name); + } + } + if (!p_only_functions) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); + for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { + r_result.insert(E->key().operator String()); + } + } - if (!p_only_functions) { - for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) { - result.insert(E->key().operator String()); - } - } + List<MethodInfo> methods; + scr->get_script_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().arguments.size()) { + r_result.insert(E->get().name + "("); + } else { + r_result.insert(E->get().name + "()"); + } + } - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { - if (!p_static || E->get()->is_static()) { - if (E->get()->get_argument_count()) - result.insert(E->key().operator String() + "("); - else - result.insert(E->key().operator String() + "()"); + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + return; } - } - - if (!p_only_functions) { - for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) { - result.insert(E->key().operator String()); + } break; + case GDScriptParser::DataType::NATIVE: { + StringName type = base_type.native_type; + if (!ClassDB::class_exists(type)) { + type = String("_") + type; + if (!ClassDB::class_exists(type)) { + return; + } } - } - base = script->get_base(); - if (base.is_null()) - base = script->get_native(); - } else if (nc.is_valid()) { + if (!p_only_functions) { + List<String> constants; + ClassDB::get_integer_constant_list(type, &constants); + for (List<String>::Element *E = constants.front(); E; E = E->next()) { + r_result.insert(E->get()); + } - StringName type = nc->get_name(); + if (!_static) { + List<PropertyInfo> pinfo; + ClassDB::get_property_list(type, &pinfo); + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { + continue; + } + if (E->get().name.find("/") != -1) { + continue; + } + r_result.insert(E->get().name); + } + } + } - if (!p_only_functions) { + if (!_static) { + List<MethodInfo> methods; + ClassDB::get_method_list(type, &methods, false, true); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name.begins_with("_")) { + continue; + } + if (E->get().arguments.size()) { + r_result.insert(E->get().name + "("); + } else { + r_result.insert(E->get().name + "()"); + } + } + } - List<String> constants; - ClassDB::get_integer_constant_list(type, &constants); - for (List<String>::Element *E = constants.front(); E; E = E->next()) { - result.insert(E->get()); + return; + } break; + case GDScriptParser::DataType::BUILTIN: { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + return; } - List<PropertyInfo> pinfo; + if (!p_only_functions) { + List<PropertyInfo> members; + tmp.get_property_list(&members); - ClassDB::get_property_list(type, &pinfo); + for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + if (String(E->get().name).find("/") == -1) { + r_result.insert(E->get().name); + } + } + } - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) - continue; - if (String(E->get().name).find("/") != -1) - continue; - result.insert(E->get().name); + List<MethodInfo> methods; + tmp.get_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().arguments.size()) { + r_result.insert(E->get().name + "("); + } else { + r_result.insert(E->get().name + "()"); + } } - } - List<MethodInfo> methods; - ClassDB::get_method_list(type, &methods, false, true); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name.begins_with("_")) - continue; - if (E->get().arguments.size()) - result.insert(E->get().name + "("); - else - result.insert(E->get().name + "()"); - } - break; - } else - break; + return; + } break; + default: { + return; + } break; + } } } -static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) { +static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Set<String> &r_result) { - const GDScriptParser::BlockNode *block = context.block; + const GDScriptParser::BlockNode *block = p_context.block; - if (context.function) { + if (p_context.function) { - const GDScriptParser::FunctionNode *f = context.function; + const GDScriptParser::FunctionNode *f = p_context.function; for (int i = 0; i < f->arguments.size(); i++) { - result.insert(f->arguments[i].operator String()); + r_result.insert(f->arguments[i].operator String()); } } - while (block) { - - GDScriptCompletionContext c = context; + if (!p_only_functions && block) { + GDScriptCompletionContext c = p_context; c.block = block; - - _find_identifiers_in_block(c, p_line, p_only_functions, result); - block = block->parent_block; + _find_identifiers_in_block(c, r_result); } - const GDScriptParser::ClassNode *clss = context._class; - - bool _static = context.function && context.function->_static; + const GDScriptParser::ClassNode *clss = p_context._class; + bool _static = !p_context.function || p_context.function->_static; while (clss) { - GDScriptCompletionContext c = context; + GDScriptCompletionContext c = p_context; c._class = clss; c.block = NULL; c.function = NULL; - _find_identifiers_in_class(c, _static, p_only_functions, result); + _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); + _static = true; clss = clss->owner; } for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - - result.insert(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))); + MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); + if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) { + r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "("); + } else { + r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "()"); + } } static const char *_type_names[Variant::VARIANT_MAX] = { @@ -1526,667 +2091,372 @@ static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bo }; for (int i = 0; i < Variant::VARIANT_MAX; i++) { - result.insert(_type_names[i]); + r_result.insert(_type_names[i]); } - List<String> reserved_words; - GDScriptLanguage::get_singleton()->get_reserved_words(&reserved_words); + static const char *_keywords[] = { + "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert", + "breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield", + "const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif", + "else", "for", "pass", "return", "match", "while", "remote", "sync", "master", "slave", + "remotesync", "mastersync", "slavesync", + 0 + }; - for (List<String>::Element *E = reserved_words.front(); E; E = E->next()) { - result.insert(E->get()); + const char **kw = _keywords; + while (*kw) { + r_result.insert(*kw); + kw++; } - //autoload singletons + // Autoload singletons List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) + if (!s.begins_with("autoload/")) { continue; - String name = s.get_slice("/", 1); + } String path = ProjectSettings::get_singleton()->get(s); if (path.begins_with("*")) { - result.insert(name); + r_result.insert(s.get_slice("/", 1)); } } - for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - result.insert(E->key().operator String()); - } -} - -static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) { - - String n = p_info.name; - int idx = n.find(":"); - if (idx != -1) { - return n.substr(idx + 1, n.length()); + // Named scripts + List<StringName> named_scripts; + ScriptServer::get_global_class_list(&named_scripts); + for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { + r_result.insert(E->get().operator String()); } - if (p_info.type == Variant::OBJECT && p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) - return p_info.hint_string; - if (p_info.type == Variant::NIL) { - if (p_isarg) - return "var"; - else - return "void"; + // Native classes + for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { + r_result.insert(E->key().operator String()); } - - return Variant::get_type_name(p_info.type); } -static void _make_function_hint(const GDScriptParser::FunctionNode *p_func, int p_argidx, String &arghint) { - - arghint = "func " + p_func->name + "("; - for (int i = 0; i < p_func->arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += p_func->arguments[i].operator String(); - int deffrom = p_func->arguments.size() - p_func->default_values.size(); - - if (i >= deffrom) { - int defidx = deffrom - i; - - if (defidx >= 0 && defidx < p_func->default_values.size()) { - - if (p_func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) { - - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[defidx]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]); - arghint += "=" + cn->value.get_construct_string(); +static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Set<String> &r_result, String &r_arghint) { + Variant base = p_base.value; + GDScriptParser::DataType base_type = p_base.type; + bool _static = false; + + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { + if (base_type.class_type->static_functions[i]->name == p_method) { + r_arghint = _make_arguments_hint(base_type.class_type->static_functions[i], p_argidx); + return; + } + } + if (!_static) { + for (int i = 0; i < base_type.class_type->functions.size(); i++) { + if (base_type.class_type->functions[i]->name == p_method) { + r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx); + return; + } } - } else { } - } - } - - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - if (p_func->arguments.size() > 0) - arghint += " "; - arghint += ")"; -} - -void get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) { - - for (int i = 0; i < p_dir->get_subdir_count(); i++) { - get_directory_contents(p_dir->get_subdir(i), r_list); - } - - for (int i = 0; i < p_dir->get_file_count(); i++) { - r_list.insert("\"" + p_dir->get_file_path(i) + "\""); - } -} - -static void _find_type_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, const StringName &p_method, const GDScriptCompletionIdentifier &id, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) { - - //print_line("find type arguments?"); - if (id.type == Variant::OBJECT && id.obj_type != StringName()) { - - MethodBind *m = ClassDB::get_method(id.obj_type, p_method); - if (!m) { - //not in static method, see script - - //print_line("not in static: "+String(p_method)); - Ref<GDScript> on_script; - - if (id.value.get_type()) { - Object *obj = id.value; - GDScript *scr = Object::cast_to<GDScript>(obj); - if (scr) { - while (scr) { + if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { + for (int i = 0; i < base_type.class_type->_signals.size(); i++) { + r_result.insert("\"" + base_type.class_type->_signals[i].name.operator String() + "\""); + } + } - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (E->get()->is_static() && p_method == E->get()->get_name()) { - arghint = "static func " + String(p_method) + "("; - for (int i = 0; i < E->get()->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += "var " + E->get()->get_argument_name(i); - int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count(); - if (i >= deffrom) { - int defidx = deffrom - i; - if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) { - arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string(); - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - arghint += ")"; - return; //found - } + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = base_type.script_type; + if (gds.is_valid()) { + if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { + List<MethodInfo> signals; + gds->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + r_result.insert("\"" + E->get().name + "\""); } - - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + } + Ref<GDScript> base_script = gds->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = gds->get_instance_base_type(); } } else { - if (obj) { - on_script = obj->get_script(); + return; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + base_type.has_type = false; + break; } } - } - - //print_line("but it has a script?"); - if (!on_script.is_valid() && id.script.is_valid()) { - //print_line("yes"); - on_script = id.script; - } - - if (on_script.is_valid()) { - - GDScript *scr = on_script.ptr(); - if (scr) { - while (scr) { - - String code = scr->get_source_code(); - //print_line("has source code!"); - - if (code != "") { - //if there is code, parse it. This way is slower but updates in real-time - GDScriptParser p; - //Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false); - - Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false); - - if (err == OK) { - //print_line("checking the functions..."); - //only if ok, otherwise use what is cached on the script - //GDScriptParser::ClassNode *base = p. - const GDScriptParser::Node *root = p.get_parse_tree(); - ERR_FAIL_COND(root->type != GDScriptParser::Node::TYPE_CLASS); - - const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root); - - const GDScriptParser::FunctionNode *func = NULL; - bool st = false; - - for (int i = 0; i < cl->functions.size(); i++) { - //print_line(String(cl->functions[i]->name)+" vs "+String(p_method)); - if (cl->functions[i]->name == p_method) { - func = cl->functions[i]; - } - } - - for (int i = 0; i < cl->static_functions.size(); i++) { - - //print_line(String(cl->static_functions[i]->name)+" vs "+String(p_method)); - if (cl->static_functions[i]->name == p_method) { - func = cl->static_functions[i]; - st = true; - } - } - - if (func) { - - arghint = "func " + String(p_method) + "("; - if (st) - arghint = "static " + arghint; - for (int i = 0; i < func->arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += "var " + String(func->arguments[i]); - int deffrom = func->arguments.size() - func->default_values.size(); - if (i >= deffrom) { - int defidx = deffrom - i; + List<MethodInfo> methods; + ClassDB::get_method_list(class_name, &methods); + ClassDB::get_virtual_methods(class_name, &methods); + int method_args = 0; - if (defidx >= 0 && defidx < func->default_values.size() && func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->default_values[defidx]); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) { - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]); - arghint += "=" + cn->value.get_construct_string(); - } - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - - arghint += " )"; - return; + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_method) { + method_args = E->get().arguments.size(); + if (base.get_type() == Variant::OBJECT) { + Object *obj = base.operator Object *(); + if (obj) { + List<String> options; + obj->get_argument_options(p_method, p_argidx, &options); + for (List<String>::Element *E = options.front(); E; E = E->next()) { + r_result.insert(E->get()); } - } else { - //print_line("failed parsing?"); - code = ""; } } - if (code == "") { - - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (p_method == E->get()->get_name()) { - arghint = "func " + String(p_method) + "("; - for (int i = 0; i < E->get()->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += "var " + E->get()->get_argument_name(i); - int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count(); - if (i >= deffrom) { - int defidx = deffrom - i; - if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) { - arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string(); - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - arghint += ")"; - return; //found - } + if (p_argidx < method_args) { + PropertyInfo arg_info = E->get().arguments[p_argidx]; + if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + _find_enumeration_candidates(arg_info.class_name, r_result); } } - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + r_arghint = _make_arguments_hint(E->get(), p_argidx); + break; } } - } - - } else { - - //regular method -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - if (p_argidx < m->get_argument_count()) { - PropertyInfo pi = m->get_argument_info(p_argidx); - - if (pi.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - String enumeration = pi.class_name; - if (enumeration.find(".") != -1) { - //class constant - List<StringName> constants; - String cls = enumeration.get_slice(".", 0); - String enm = enumeration.get_slice(".", 1); - - ClassDB::get_enum_constants(cls, enm, &constants); - //constants.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - String add = cls + "." + E->get(); - result.insert(add); - r_forced = true; - } - } else { - //global constant - StringName current_enum = enumeration; - - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - result.insert(GlobalConstants::get_global_constant_name(i)); - r_forced = true; - } - } - //global + if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { + List<MethodInfo> signals; + ClassDB::get_signal_list(class_name, &signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + r_result.insert("\"" + E->get().name + "\""); } } - } -#endif - if (p_method.operator String() == "connect" || (p_method.operator String() == "emit_signal" && p_argidx == 0)) { - - if (p_argidx == 0) { - List<MethodInfo> sigs; - ClassDB::get_signal_list(id.obj_type, &sigs); - - if (id.script.is_valid()) { - id.script->get_script_signal_list(&sigs); - } else if (id.value.get_type() == Variant::OBJECT) { - Object *obj = id.value; - if (obj && !obj->get_script().is_null()) { - Ref<Script> scr = obj->get_script(); - if (scr.is_valid()) { - scr->get_script_signal_list(&sigs); - } - } - } - - for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) { - result.insert("\"" + E->get().name + "\""); - r_forced = true; - } - } else if (p_argidx == 2) { + if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) { + // Get autoloads + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); - if (context._class) { - for (int i = 0; i < context._class->functions.size(); i++) { - result.insert("\"" + context._class->functions[i]->name + "\""); - r_forced = true; + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; } + String name = s.get_slice("/", 1); + r_result.insert("\"/root/" + name + "\""); } } - /*if (p_argidx==2) { - - ERR_FAIL_COND(p_node->type!=GDScriptParser::Node::TYPE_OPERATOR); - const GDScriptParser::OperatorNode *op=static_cast<const GDScriptParser::OperatorNode *>(p_node); - if (op->arguments.size()>) - - }*/ - } else { - - if (p_argidx == 0 && (String(p_method) == "get_node" || String(p_method) == "has_node") && ClassDB::is_parent_class(id.obj_type, "Node")) { + if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) { + // Get input actions List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) + if (!s.begins_with("input/")) { continue; - //print_line("found "+s); + } String name = s.get_slice("/", 1); - result.insert("\"/root/" + name + "\""); - r_forced = true; + r_result.insert("\"" + name + "\""); } } - Object *obj = id.value; - if (obj) { - List<String> options; - obj->get_argument_options(p_method, p_argidx, &options); - - for (List<String>::Element *E = options.front(); E; E = E->next()) { - - result.insert(E->get()); - r_forced = true; + base_type.has_type = false; + } break; + case GDScriptParser::DataType::BUILTIN: { + if (base.get_type() == Variant::NIL) { + Variant::CallError err; + base = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + return; } } - } - - arghint = _get_visual_datatype(m->get_return_info(), false) + " " + p_method.operator String() + String("("); - - for (int i = 0; i < m->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - String n = m->get_argument_info(i).name; - int dp = n.find(":"); - if (dp != -1) - n = n.substr(0, dp); - arghint += _get_visual_datatype(m->get_argument_info(i)) + " " + n; - int deffrom = m->get_argument_count() - m->get_default_argument_count(); - - if (i >= deffrom) { - int defidx = i - deffrom; - if (defidx >= 0 && defidx < m->get_default_argument_count()) { - Variant v = m->get_default_argument(i); - arghint += "=" + v.get_construct_string(); + List<MethodInfo> methods; + base.get_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_method) { + r_arghint = _make_arguments_hint(E->get(), p_argidx); + return; } } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - if (m->get_argument_count() > 0) - arghint += " "; - - arghint += ")"; + base_type.has_type = false; + } break; + default: { + base_type.has_type = false; + } break; } } } -static void _find_call_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) { +static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Set<String> &r_result, bool &r_forced, String &r_arghint) { if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) { - return; } + Variant base; + GDScriptParser::DataType base_type; + StringName function; + bool _static = false; const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node); - if (op->op != GDScriptParser::OperatorNode::OP_CALL) { + GDScriptCompletionIdentifier connect_base; + if (op->op != GDScriptParser::OperatorNode::OP_CALL && op->op != GDScriptParser::OperatorNode::OP_PARENT_CALL) { return; } - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - //complete built-in function - const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - MethodInfo mi = GDScriptFunctions::get_info(fn->function); + if (!op->arguments.size()) { + return; + } - if (mi.name == "load" && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { - get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), result); - } + if (op->op == GDScriptParser::OperatorNode::OP_CALL) { + if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { + // Complete built-in function + const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); + MethodInfo mi = GDScriptFunctions::get_info(fn->function); - arghint = _get_visual_datatype(mi.return_val, false) + " " + GDScriptFunctions::get_func_name(fn->function) + String("("); - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) { - arghint += String::chr(0xFFFF); - } - arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name; - if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) { - arghint += String::chr(0xFFFF); + if ((mi.name == "load" || mi.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { + _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); } - } - if (mi.arguments.size() > 0) - arghint += " "; - arghint += ")"; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - //complete constructor - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); + r_arghint = _make_arguments_hint(mi, p_argidx); + return; - List<MethodInfo> mil; - Variant::get_constructor_list(tn->vtype, &mil); + } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { + // Complete constructor + const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - for (List<MethodInfo>::Element *E = mil.front(); E; E = E->next()) { + List<MethodInfo> constructors; + Variant::get_constructor_list(tn->vtype, &constructors); - MethodInfo mi = E->get(); - if (mi.arguments.size() == 0) - continue; - if (E->prev()) - arghint += "\n"; - arghint += Variant::get_type_name(tn->vtype) + " " + Variant::get_type_name(tn->vtype) + String("("); - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); + int i = 0; + for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { + if (p_argidx >= E->get().arguments.size()) { + continue; } - } - if (mi.arguments.size() > 0) - arghint += " "; - arghint += ")"; - } - - } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - //make sure identifier exists... - - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) { - //self, look up - - for (int i = 0; i < context._class->static_functions.size(); i++) { - if (context._class->static_functions[i]->name == id->name) { - _make_function_hint(context._class->static_functions[i], p_argidx, arghint); - return; + if (i > 0) { + r_arghint += "\n"; } + r_arghint += _make_arguments_hint(E->get(), p_argidx); + i++; } + return; + } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) { - if (context.function && !context.function->_static) { - - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == id->name) { - _make_function_hint(context._class->functions[i], p_argidx, arghint); - return; - } - } + if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + return; } - Ref<Reference> base = _get_parent_class(context); - - while (true) { - - Ref<GDScript> script = base; - Ref<GDScriptNativeClass> nc = base; - if (script.is_valid()) { - - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { + base = p_context.base; - if (E->key() == id->name) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); + function = id->name; + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::CLASS; + base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + _static = p_context.function && p_context.function->_static; - if (context.function && context.function->_static && !E->get()->is_static()) - continue; + if (function == "connect" && op->arguments.size() >= 4) { + _guess_expression_type(p_context, op->arguments[3], connect_base); + } - arghint = "func " + id->name.operator String() + String("("); - for (int i = 0; i < E->get()->get_argument_count(); i++) { - if (i > 0) - arghint += ", "; - else - arghint += " "; - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - arghint += E->get()->get_argument_name(i); - int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count(); - if (i >= deffrom) { - int defidx = deffrom - i; - if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) { - arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string(); - } - } - if (i == p_argidx) { - arghint += String::chr(0xFFFF); - } - } - if (E->get()->get_argument_count() > 0) - arghint += " "; - arghint += ")"; - return; - } - } + } else { + if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + return; + } + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); + function = id->name; - base = script->get_base(); - if (base.is_null()) - base = script->get_native(); - } else if (nc.is_valid()) { + GDScriptCompletionIdentifier ci; + if (_guess_expression_type(p_context, op->arguments[0], ci)) { + base_type = ci.type; + base = ci.value; + } else { + return; + } + _static = ci.type.is_meta_type; - if (!(context.function && context.function->_static)) { + if (function == "connect" && op->arguments.size() >= 4) { + _guess_expression_type(p_context, op->arguments[3], connect_base); + } + } + } else { + if (!p_context._class || op->arguments.size() < 1 || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { + return; + } + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::CLASS; + base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); + base_type.is_meta_type = p_context.function && p_context.function->_static; + base = p_context.base; - GDScriptCompletionIdentifier ci; - ci.type = Variant::OBJECT; - ci.obj_type = nc->get_name(); - if (!context._class->owner) - ci.value = context.base; + function = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - _find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint); - //guess type.. - /* - List<MethodInfo> methods; - ClassDB::get_method_list(type,&methods); - for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { - if (E->get().arguments.size()) - result.insert(E->get().name+"("); - else - result.insert(E->get().name+"()"); - }*/ - } - break; - } else - break; - } - } else { - //indexed lookup + if (function == "connect" && op->arguments.size() >= 4) { + _guess_expression_type(p_context, op->arguments[3], connect_base); + } + } - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(context, op->arguments[0], p_line, ci)) { + GDScriptCompletionIdentifier ci; + ci.type = base_type; + ci.value = base; + _find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint); - _find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint); - return; - } + if (function == "connect" && p_argidx == 2) { + Set<String> methods; + _find_identifiers_in_base(p_context, connect_base, true, methods); + for (Set<String>::Element *E = methods.front(); E; E = E->next()) { + r_result.insert("\"" + E->get().replace("(", "").replace(")", "") + "\""); } } + + r_forced = r_result.size() > 0; } Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) { - GDScriptParser p; + GDScriptParser parser; - p.parse(p_code, p_base_path, false, "", true); - bool isfunction = false; - Set<String> options; + parser.parse(p_code, p_base_path, false, "", true); r_forced = false; + Set<String> options; GDScriptCompletionContext context; - context._class = p.get_completion_class(); - context.block = p.get_completion_block(); - context.function = p.get_completion_function(); + context._class = parser.get_completion_class(); + context.block = parser.get_completion_block(); + context.function = parser.get_completion_function(); context.base = p_owner; context.base_path = p_base_path; + context.line = parser.get_completion_line(); + bool is_function = false; - switch (p.get_completion_type()) { - + switch (parser.get_completion_type()) { case GDScriptParser::COMPLETION_NONE: { } break; case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { List<StringName> constants; - Variant::get_numeric_constants_for_type(p.get_completion_built_in_constant(), &constants); + Variant::get_numeric_constants_for_type(parser.get_completion_built_in_constant(), &constants); for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { options.insert(E->get().operator String()); } - - } break; - case GDScriptParser::COMPLETION_FUNCTION: - isfunction = true; - case GDScriptParser::COMPLETION_IDENTIFIER: { - - _find_identifiers(context, p.get_completion_line(), isfunction, options); } break; case GDScriptParser::COMPLETION_PARENT_FUNCTION: { - + _find_identifiers_in_class(context, !context.function || context.function->_static, true, true, options); + } break; + case GDScriptParser::COMPLETION_FUNCTION: { + is_function = true; + } // fallthrough + case GDScriptParser::COMPLETION_IDENTIFIER: { + _find_identifiers(context, is_function, options); } break; case GDScriptParser::COMPLETION_GET_NODE: { - if (p_owner) { List<String> opts; p_owner->get_argument_options("get_node", 0, &opts); @@ -2204,315 +2474,358 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base } } } + + // Get autoloads + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + options.insert("\"/root/" + name + "\""); + } } } break; - case GDScriptParser::COMPLETION_METHOD: - isfunction = true; + case GDScriptParser::COMPLETION_METHOD: { + is_function = true; + } // fallthrough case GDScriptParser::COMPLETION_INDEX: { - - const GDScriptParser::Node *node = p.get_completion_node(); - if (node->type != GDScriptParser::Node::TYPE_OPERATOR) + const GDScriptParser::Node *node = parser.get_completion_node(); + if (node->type != GDScriptParser::Node::TYPE_OPERATOR) { break; + } + const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(node); + if (op->arguments.size() < 1) { + break; + } - GDScriptCompletionIdentifier t; - if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t, true)) { - - if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") { - //native enum - Ref<GDScriptNativeClass> gdn = t.value; - if (gdn.is_valid()) { - StringName cn = gdn->get_name(); - List<String> cnames; - ClassDB::get_integer_constant_list(cn, &cnames); - for (List<String>::Element *E = cnames.front(); E; E = E->next()) { - options.insert(E->get()); - } - - List<PropertyInfo> pinfo; - ClassDB::get_property_list(cn, &pinfo); - - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) - continue; - if (String(E->get().name).find("/") != -1) - continue; - options.insert(E->get().name); - } - } - } else if (t.type == Variant::OBJECT && t.obj_type != StringName()) { - - Ref<GDScript> on_script; - - if (t.value.get_type()) { - Object *obj = t.value; - - GDScript *scr = Object::cast_to<GDScript>(obj); - if (scr) { - while (scr) { + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(context, op->arguments[0], base)) { + break; + } - if (!isfunction) { - for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) { - options.insert(E->key()); - } - } - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (E->get()->is_static()) - options.insert(E->key()); - } + GDScriptCompletionContext c = context; + c.function = NULL; + c.block = NULL; + c.base = base.value.get_type() == Variant::OBJECT ? base.value.operator Object *() : NULL; + if (base.type.kind == GDScriptParser::DataType::CLASS) { + c._class = base.type.class_type; + } else { + c._class = NULL; + } - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + _find_identifiers_in_base(c, base, is_function, options); + } break; + case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { + _find_call_arguments(context, parser.get_completion_node(), parser.get_completion_argument_index(), options, r_forced, r_call_hint); + } break; + case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { + GDScriptParser::DataType native_type = context._class->base_type; + while (native_type.has_type && native_type.kind != GDScriptParser::DataType::NATIVE) { + switch (native_type.kind) { + case GDScriptParser::DataType::CLASS: { + native_type = native_type.class_type->base_type; + } break; + case GDScriptParser::DataType::GDSCRIPT: { + Ref<GDScript> gds = native_type.script_type; + if (gds.is_valid()) { + Ref<GDScript> base = gds->get_base_script(); + if (base.is_valid()) { + native_type.script_type = base; + } else { + native_type.native_type = gds->get_instance_base_type(); + native_type.kind = GDScriptParser::DataType::NATIVE; } } else { - if (obj) { - on_script = obj->get_script(); - } + native_type.has_type = false; } - } - - if (!on_script.is_valid() && t.script.is_valid()) { - on_script = t.script; - } - - if (on_script.is_valid()) { - - GDScript *scr = on_script.ptr(); - if (scr) { - while (scr) { - - String code = scr->get_source_code(); - - if (code != "") { - //if there is code, parse it. This way is slower but updates in real-time - GDScriptParser p; - - Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false); - - if (err == OK) { - //only if ok, otherwise use what is cached on the script - //GDScriptParser::ClassNode *base = p. - const GDScriptParser::Node *root = p.get_parse_tree(); - ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_PARSE_ERROR); - - const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root); - - for (int i = 0; i < cl->functions.size(); i++) { - - if (cl->functions[i]->arguments.size()) - options.insert(String(cl->functions[i]->name) + "("); - else - options.insert(String(cl->functions[i]->name) + "()"); - } - - for (int i = 0; i < cl->static_functions.size(); i++) { - - if (cl->static_functions[i]->arguments.size()) - options.insert(String(cl->static_functions[i]->name) + "("); - else - options.insert(String(cl->static_functions[i]->name) + "()"); - } + } break; + default: { + native_type.has_type = false; + } break; + } + } - if (!isfunction) { - for (int i = 0; i < cl->variables.size(); i++) { + if (!native_type.has_type) { + break; + } - options.insert(String(cl->variables[i].identifier)); - } + StringName class_name = native_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + break; + } + } - for (int i = 0; i < cl->constant_expressions.size(); i++) { + bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool(); - options.insert(String(cl->constant_expressions[i].identifier)); - } - } + List<MethodInfo> virtual_methods; + ClassDB::get_virtual_methods(class_name, &virtual_methods); + for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) { - } else { - code = ""; //well, then no code - } - } - - if (code == "") { - //use class directly, no code was found - if (!isfunction) { - for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) { - options.insert(E->key()); - } - } - for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) { - if (E->get()->get_argument_count()) - options.insert(String(E->key()) + "()"); - else - options.insert(String(E->key()) + "("); - } - - for (const Set<StringName>::Element *E = scr->get_members().front(); E; E = E->next()) { - options.insert(E->get()); - } - } + MethodInfo &mi = E->get(); + String method_hint = mi.name; + if (method_hint.find(":") != -1) { + method_hint = method_hint.get_slice(":", 0); + } + method_hint += "("; - if (scr->get_base().is_valid()) - scr = scr->get_base().ptr(); - else - scr = NULL; + if (mi.arguments.size()) { + for (int i = 0; i < mi.arguments.size(); i++) { + if (i > 0) { + method_hint += ", "; + } + String arg = mi.arguments[i].name; + if (arg.find(":") != -1) { + arg = arg.substr(0, arg.find(":")); + } + method_hint += arg; + if (use_type_hint && mi.arguments[i].type != Variant::NIL) { + method_hint += " : "; + if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) { + method_hint += mi.arguments[i].class_name.operator String(); + } else { + method_hint += Variant::get_type_name(mi.arguments[i].type); } } } - - if (!isfunction) { - ClassDB::get_integer_constant_list(t.obj_type, r_options); - - List<PropertyInfo> pinfo; - ClassDB::get_property_list(t.obj_type, &pinfo); - - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) - continue; - if (String(E->get().name).find("/") != -1) - continue; - r_options->push_back(E->get().name); - } + } + method_hint += ")"; + if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { + method_hint += " -> "; + if (mi.return_val.type == Variant::NIL) { + method_hint += "void"; + } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) { + method_hint += mi.return_val.class_name.operator String(); + } else { + method_hint += Variant::get_type_name(mi.return_val.type); } + } + method_hint += ":"; - List<MethodInfo> mi; - ClassDB::get_method_list(t.obj_type, &mi, false, true); - for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) { - - if (E->get().name.begins_with("_")) - continue; + options.insert(method_hint); + } + } break; + case GDScriptParser::COMPLETION_YIELD: { + const GDScriptParser::Node *node = parser.get_completion_node(); - if (E->get().arguments.size()) - options.insert(E->get().name + "("); - else - options.insert(E->get().name + "()"); - } - } else { + GDScriptCompletionContext c = context; + c.line = node->line; + GDScriptCompletionIdentifier type; + if (!_guess_expression_type(c, node, type)) { + break; + } - //check InputEvent hint - { - if (t.value.get_type() == Variant::NIL) { - Variant::CallError ce; - t.value = Variant::construct(t.type, NULL, 0, ce); + GDScriptParser::DataType base_type = type.type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + for (int i = 0; i < base_type.class_type->_signals.size(); i++) { + options.insert("\"" + base_type.class_type->_signals[i].name.operator String() + "\""); } - - if (!isfunction) { - List<PropertyInfo> pl; - t.value.get_property_list(&pl); - for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) { - - if (String(E->get().name).find("/") == -1) - options.insert(E->get().name); + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + List<MethodInfo> signals; + scr->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + options.insert("\"" + E->get().name + "\""); + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + base_type.has_type = false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + base_type.has_type = false; + + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + break; } } - List<MethodInfo> mi; - t.value.get_method_list(&mi); - for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) { - if (E->get().arguments.size()) - options.insert(E->get().name + "("); - else - options.insert(E->get().name + "()"); + List<MethodInfo> signals; + ClassDB::get_signal_list(class_name, &signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + options.insert("\"" + E->get().name + "\""); } + } break; + default: { + base_type.has_type = false; } } } - } break; - case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { - - _find_call_arguments(context, p.get_completion_node(), p.get_completion_line(), p.get_completion_argument_index(), options, r_forced, r_call_hint); + case GDScriptParser::COMPLETION_RESOURCE_PATH: { + if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) { + _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); + r_forced = true; + } } break; - case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { - - GDScriptCompletionIdentifier cid = _get_native_class(context); - - if (cid.obj_type != StringName()) { - List<MethodInfo> vm; - ClassDB::get_virtual_methods(cid.obj_type, &vm); - for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) { + case GDScriptParser::COMPLETION_ASSIGN: { + GDScriptCompletionIdentifier type; + if (!_guess_expression_type(context, parser.get_completion_node(), type)) { + break; + } - MethodInfo &mi = E->get(); - String m = mi.name; - if (m.find(":") != -1) - m = m.substr(0, m.find(":")); - m += "("; - - if (mi.arguments.size()) { - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) - m += ", "; - String n = mi.arguments[i].name; - if (n.find(":") != -1) - n = n.substr(0, n.find(":")); - m += n; + if (!type.enumeration.empty()) { + _find_enumeration_candidates(type.enumeration, options); + r_forced = options.size() > 0; + } + } break; + case GDScriptParser::COMPLETION_TYPE_HINT: { + const GDScriptParser::ClassNode *clss = context._class; + while (clss) { + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = clss->constant_expressions.front(); E; E = E->next()) { + GDScriptCompletionIdentifier constant; + GDScriptCompletionContext c = context; + c.function = NULL; + c.block = NULL; + c.line = E->value().expression->line; + if (_guess_expression_type(c, E->value().expression, constant)) { + if (constant.type.has_type && constant.type.is_meta_type) { + options.insert(E->key().operator String()); } } - m += "):"; + } + for (int i = 0; i < clss->subclasses.size(); i++) { + if (clss->subclasses[i]->name != StringName()) { + options.insert(clss->subclasses[i]->name.operator String()); + } + } + clss = clss->owner; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + options.insert(Variant::get_type_name((Variant::Type)i)); + } + } - options.insert(m); + List<StringName> native_classes; + ClassDB::get_class_list(&native_classes); + for (List<StringName>::Element *E = native_classes.front(); E; E = E->next()) { + String class_name = E->get().operator String(); + if (class_name.begins_with("_")) { + class_name = class_name.right(1); + } + if (Engine::get_singleton()->has_singleton(class_name)) { + continue; } + options.insert(class_name); } - } break; - case GDScriptParser::COMPLETION_YIELD: { - const GDScriptParser::Node *node = p.get_completion_node(); + // Named scripts + List<StringName> named_scripts; + ScriptServer::get_global_class_list(&named_scripts); + for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { + options.insert(E->get().operator String()); + } - GDScriptCompletionIdentifier t; - if (!_guess_expression_type(context, node, p.get_completion_line(), t)) + if (parser.get_completion_identifier_is_function()) { + options.insert("void"); + } + r_forced = true; + } break; + case GDScriptParser::COMPLETION_TYPE_HINT_INDEX: { + GDScriptCompletionIdentifier base; + String index = parser.get_completion_cursor().operator String(); + if (!_guess_identifier_type(context, index.get_slice(".", 0), base)) { break; + } - if (t.type == Variant::OBJECT && t.obj_type != StringName()) { + GDScriptCompletionContext c = context; + c._class = NULL; + c.function = NULL; + c.block = NULL; + bool finding = true; + index = index.right(index.find(".") + 1); + while (index.find(".") != -1) { + String id = index.get_slice(".", 0); - List<MethodInfo> sigs; - ClassDB::get_signal_list(t.obj_type, &sigs); - for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) { - options.insert("\"" + E->get().name + "\""); - r_forced = true; + GDScriptCompletionIdentifier sub_base; + if (!_guess_identifier_type_from_base(c, base, id, sub_base)) { + finding = false; + break; } + index = index.right(index.find(".") + 1); + base = sub_base; } - } break; - case GDScriptParser::COMPLETION_RESOURCE_PATH: { - - if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) { - get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); - r_forced = true; + if (!finding) { + break; } - } break; - case GDScriptParser::COMPLETION_ASSIGN: { -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(context, p.get_completion_node(), p.get_completion_line(), ci)) { - - String enumeration = ci.enumeration; - if (enumeration.find(".") != -1) { - //class constant - List<StringName> constants; - String cls = enumeration.get_slice(".", 0); - String enm = enumeration.get_slice(".", 1); - - ClassDB::get_enum_constants(cls, enm, &constants); - //constants.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - String add = cls + "." + E->get(); - r_options->push_back(add); - r_forced = true; - } - } else { - - //global constant - StringName current_enum = enumeration; + GDScriptParser::DataType base_type = base.type; + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (base_type.class_type) { + for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = base_type.class_type->constant_expressions.front(); E; E = E->next()) { + GDScriptCompletionIdentifier constant; + GDScriptCompletionContext c = context; + c._class = base_type.class_type; + c.function = NULL; + c.block = NULL; + c.line = E->value().expression->line; + if (_guess_expression_type(c, E->value().expression, constant)) { + if (constant.type.has_type && constant.type.is_meta_type) { + options.insert(E->key().operator String()); + } + } + } + for (int i = 0; i < base_type.class_type->subclasses.size(); i++) { + if (base_type.class_type->subclasses[i]->name != StringName()) { + options.insert(base_type.class_type->subclasses[i]->name.operator String()); + } + } - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - r_options->push_back(GlobalConstants::get_global_constant_name(i)); - r_forced = true; + base_type = base_type.class_type->base_type; + } else { + base_type.has_type = false; } - } - //global + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); + for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { + Ref<Script> const_scr = E->value(); + if (const_scr.is_valid()) { + options.insert(E->key().operator String()); + } + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.has_type = false; + } + } else { + base_type.has_type = false; + } + } break; + default: { + base_type.has_type = false; + } break; } } -#endif + r_forced = options.size() > 0; } break; } @@ -2531,6 +2844,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base #endif +//////// END COMPLETION ////////// + String GDScriptLanguage::_get_indentation() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -2616,6 +2931,185 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t #ifdef TOOLS_ENABLED +static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) { + GDScriptParser::DataType base_type = p_base; + + while (base_type.has_type) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + if (base_type.class_type) { + if (p_is_function) { + for (int i = 0; i < base_type.class_type->functions.size(); i++) { + if (base_type.class_type->functions[i]->name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->functions[i]->line; + return OK; + } + } + for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { + if (base_type.class_type->static_functions[i]->name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->static_functions[i]->line; + return OK; + } + } + } else { + if (base_type.class_type->constant_expressions.has(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->constant_expressions[p_symbol].expression->line; + return OK; + } + + for (int i = 0; i < base_type.class_type->variables.size(); i++) { + if (base_type.class_type->variables[i].identifier == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->variables[i].line; + return OK; + } + } + } + } + base_type = base_type.class_type->base_type; + } break; + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::GDSCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + int line = scr->get_member_line(p_symbol); + if (line >= 0) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = line; + r_result.script = scr; + return OK; + } + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + base_type.has_type = false; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName class_name = base_type.native_type; + if (!ClassDB::class_exists(class_name)) { + class_name = String("_") + class_name; + if (!ClassDB::class_exists(class_name)) { + base_type.has_type = false; + break; + } + } + + if (ClassDB::has_method(class_name, p_symbol, true)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + + List<MethodInfo> virtual_methods; + ClassDB::get_virtual_methods(class_name, &virtual_methods, true); + for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) { + if (E->get().name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + } + + StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true); + if (enum_name != StringName()) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM; + r_result.class_name = base_type.native_type; + r_result.class_member = enum_name; + return OK; + } + + List<String> constants; + ClassDB::get_integer_constant_list(class_name, &constants, true); + for (List<String>::Element *E = constants.front(); E; E = E->next()) { + if (E->get() == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + } + + List<PropertyInfo> properties; + ClassDB::get_property_list(class_name, &properties, true); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; + } + } + + StringName parent = ClassDB::get_parent_class(class_name); + if (parent != StringName()) { + if (String(parent).begins_with("_")) { + base_type.native_type = String(parent).right(1); + } else { + base_type.native_type = parent; + } + } else { + base_type.has_type = false; + } + } break; + case GDScriptParser::DataType::BUILTIN: { + base_type.has_type = false; + + if (Variant::has_numeric_constant(base_type.builtin_type, p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name = Variant::get_type_name(base_type.builtin_type); + r_result.class_member = p_symbol; + return OK; + } + + Variant v; + REF v_ref; + if (base_type.builtin_type == Variant::OBJECT) { + v_ref.instance(); + v = v_ref; + } else { + Variant::CallError err; + v = Variant::construct(base_type.builtin_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + break; + } + } + + if (v.has_method(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = Variant::get_type_name(base_type.builtin_type); + r_result.class_member = p_symbol; + return OK; + } + + bool valid = false; + v.get(p_symbol, &valid); + if (valid) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name = Variant::get_type_name(base_type.builtin_type); + r_result.class_member = p_symbol; + return OK; + } + } break; + default: { + base_type.has_type = false; + } break; + } + } + + return ERR_CANT_RESOLVE; +} + Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_base_path, Object *p_owner, LookupResult &r_result) { //before parsing, try the usual stuff @@ -2623,6 +3117,13 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; r_result.class_name = p_symbol; return OK; + } else { + String under_prefix = "_" + p_symbol; + if (ClassDB::class_exists(under_prefix)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; + r_result.class_name = p_symbol; + return OK; + } } for (int i = 0; i < Variant::VARIANT_MAX; i++) { @@ -2650,17 +3151,18 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol return OK; } - GDScriptParser p; - p.parse(p_code, p_base_path, false, "", true); + GDScriptParser parser; + parser.parse(p_code, p_base_path, false, "", true); - if (p.get_completion_type() == GDScriptParser::COMPLETION_NONE) + if (parser.get_completion_type() == GDScriptParser::COMPLETION_NONE) { return ERR_CANT_RESOLVE; + } GDScriptCompletionContext context; - - context._class = p.get_completion_class(); - context.block = p.get_completion_block(); - context.function = p.get_completion_function(); + context._class = parser.get_completion_class(); + context.function = parser.get_completion_function(); + context.block = parser.get_completion_block(); + context.line = parser.get_completion_line(); context.base = p_owner; context.base_path = p_base_path; @@ -2675,171 +3177,68 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } - bool isfunction = false; + bool is_function = false; - switch (p.get_completion_type()) { - - case GDScriptParser::COMPLETION_GET_NODE: - case GDScriptParser::COMPLETION_NONE: { - } break; + switch (parser.get_completion_type()) { case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = Variant::get_type_name(p.get_completion_built_in_constant()); + r_result.class_name = Variant::get_type_name(parser.get_completion_built_in_constant()); r_result.class_member = p_symbol; return OK; - } break; + case GDScriptParser::COMPLETION_PARENT_FUNCTION: case GDScriptParser::COMPLETION_FUNCTION: { - - if (context._class && context._class->functions.size()) { - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->functions[i]->line; - return OK; - } - } - } - - Ref<GDScript> parent = _get_parent_class(context); - while (parent.is_valid()) { - int line = parent->get_member_line(p_symbol); - if (line >= 0) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = line; - r_result.script = parent; - return OK; - } - - parent = parent->get_base(); - } - - GDScriptCompletionIdentifier identifier = _get_native_class(context); - print_line("identifier: " + String(identifier.obj_type)); - - if (ClassDB::has_method(identifier.obj_type, p_symbol)) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = identifier.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - } break; + is_function = true; + } // fallthrough case GDScriptParser::COMPLETION_IDENTIFIER: { - //check if a function - if (p.get_completion_identifier_is_function()) { - if (context._class && context._class->functions.size()) { - for (int i = 0; i < context._class->functions.size(); i++) { - if (context._class->functions[i]->name == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->functions[i]->line; - return OK; - } - } - } - - Ref<GDScript> parent = _get_parent_class(context); - while (parent.is_valid()) { - int line = parent->get_member_line(p_symbol); - if (line >= 0) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = line; - r_result.script = parent; - return OK; - } - - parent = parent->get_base(); - } - - GDScriptCompletionIdentifier identifier = _get_native_class(context); - - if (ClassDB::has_method(identifier.obj_type, p_symbol)) { + if (!is_function) { + is_function = parser.get_completion_identifier_is_function(); + } - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = identifier.obj_type; - r_result.class_member = p_symbol; - return OK; + GDScriptParser::DataType base_type; + if (context._class) { + if (parser.get_completion_type() != GDScriptParser::COMPLETION_PARENT_FUNCTION) { + base_type.has_type = true; + base_type.kind = GDScriptParser::DataType::CLASS; + base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class); + } else { + base_type = context._class->base_type; } } else { + break; + } - GDScriptCompletionIdentifier gdi = _get_native_class(context); - if (gdi.obj_type != StringName()) { - bool valid; - Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_symbol, &valid); - if (t != Variant::NIL && valid) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; - r_result.class_name = gdi.obj_type; - r_result.class_member = p_symbol; - return OK; - } - } - + if (!is_function && context.block) { + // Lookup local variables const GDScriptParser::BlockNode *block = context.block; - //search in blocks going up (local var?) while (block) { - - for (int i = 0; i < block->statements.size(); i++) { - - if (block->statements[i]->line > p.get_completion_line()) - continue; - - if (block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { - - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(block->statements[i]); - - if (lv->assign && lv->name == p_symbol) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = block->statements[i]->line; - return OK; - } - } + if (block->variables.has(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = block->variables[p_symbol]->line; + return OK; } block = block->parent_block; } + } - //guess from function arguments - if (context.function && context.function->name != StringName()) { - - for (int i = 0; i < context.function->arguments.size(); i++) { - - if (context.function->arguments[i] == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context.function->line; - return OK; - } - } - } - - //guess in class constants - - for (int i = 0; i < context._class->constant_expressions.size(); i++) { - - if (context._class->constant_expressions[i].identifier == p_symbol) { + if (context.function && context.function->name != StringName()) { + // Lookup function arguments + for (int i = 0; i < context.function->arguments.size(); i++) { + if (context.function->arguments[i] == p_symbol) { r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->constant_expressions[i].expression->line; + r_result.location = context.function->line; return OK; } } + } - //guess in class variables - if (!(context.function && context.function->_static)) { - - for (int i = 0; i < context._class->variables.size(); i++) { - - if (context._class->variables[i].identifier == p_symbol) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context._class->variables[i].line; - return OK; - } - } - } + if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { + return OK; + } - //guess in autoloads as singletons + if (!is_function) { + // Guess in autoloads as singletons List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2856,8 +3255,8 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol String script = path.substr(1, path.length()); if (!script.ends_with(".gd")) { - //not a script, try find the script anyway, - //may have some success + // Not a script, try find the script anyway, + // may have some success script = script.get_basename() + ".gd"; } @@ -2872,7 +3271,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } - //global + // Global Map<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map(); if (classes.has(p_symbol)) { Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]]; @@ -2918,152 +3317,31 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } } - - } break; - case GDScriptParser::COMPLETION_PARENT_FUNCTION: { - } break; - case GDScriptParser::COMPLETION_METHOD: - isfunction = true; + case GDScriptParser::COMPLETION_METHOD: { + is_function = true; + } // fallthrough case GDScriptParser::COMPLETION_INDEX: { - - const GDScriptParser::Node *node = p.get_completion_node(); - if (node->type != GDScriptParser::Node::TYPE_OPERATOR) + const GDScriptParser::Node *node = parser.get_completion_node(); + if (node->type != GDScriptParser::Node::TYPE_OPERATOR) { + break; + } + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], base)) { break; - - GDScriptCompletionIdentifier t; - if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t)) { - - if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") { - //native enum - Ref<GDScriptNativeClass> gdn = t.value; - if (gdn.is_valid()) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = gdn->get_name(); - r_result.class_member = p_symbol; - return OK; - } - } else if (t.type == Variant::OBJECT && t.obj_type != StringName()) { - - Ref<GDScript> on_script; - - if (t.value.get_type()) { - Object *obj = t.value; - - if (obj) { - - on_script = obj->get_script(); - - if (on_script.is_valid()) { - int loc = on_script->get_member_line(p_symbol); - if (loc >= 0) { - r_result.script = on_script; - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = loc; - return OK; - } - } - } - } - - if (ClassDB::has_method(t.obj_type, p_symbol)) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = t.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - StringName enumName = ClassDB::get_integer_constant_enum(t.obj_type, p_symbol, true); - if (enumName != StringName()) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM; - r_result.class_name = t.obj_type; - r_result.class_member = enumName; - return OK; - } - - bool success; - ClassDB::get_integer_constant(t.obj_type, p_symbol, &success); - if (success) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = t.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - ClassDB::get_property_type(t.obj_type, p_symbol, &success); - - if (success) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; - r_result.class_name = t.obj_type; - r_result.class_member = p_symbol; - return OK; - } - - } else { - - Variant::CallError ce; - Variant v = Variant::construct(t.type, NULL, 0, ce); - - bool valid; - v.get_numeric_constant_value(t.type, p_symbol, &valid); - if (valid) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = Variant::get_type_name(t.type); - r_result.class_member = p_symbol; - return OK; - } - - //todo check all inputevent types for property - - v.get(p_symbol, &valid); - - if (valid) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; - r_result.class_name = Variant::get_type_name(t.type); - r_result.class_member = p_symbol; - return OK; - } - - if (v.has_method(p_symbol)) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = Variant::get_type_name(t.type); - r_result.class_member = p_symbol; - return OK; - } - } } - } break; - case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { - - return ERR_CANT_RESOLVE; + if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) { + return OK; + } } break; case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { + GDScriptParser::DataType base_type = context._class->base_type; - GDScriptCompletionIdentifier cid = _get_native_class(context); - - if (cid.obj_type != StringName()) { - List<MethodInfo> vm; - ClassDB::get_virtual_methods(cid.obj_type, &vm); - for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) { - - if (p_symbol == E->get().name) { - - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = cid.obj_type; - r_result.class_member = p_symbol; - return OK; - } - } + if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { + return OK; } } break; - case GDScriptParser::COMPLETION_YIELD: { - - return ERR_CANT_RESOLVE; - - } break; } return ERR_CANT_RESOLVE; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 10599f0c38..6a08d86904 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -200,6 +200,12 @@ static String _get_var_type(const Variant *p_type) { &&OPCODE_ASSIGN, \ &&OPCODE_ASSIGN_TRUE, \ &&OPCODE_ASSIGN_FALSE, \ + &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_NATIVE, \ + &&OPCODE_ASSIGN_TYPED_SCRIPT, \ + &&OPCODE_CAST_TO_BUILTIN, \ + &&OPCODE_CAST_TO_NATIVE, \ + &&OPCODE_CAST_TO_SCRIPT, \ &&OPCODE_CONSTRUCT, \ &&OPCODE_CONSTRUCT_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ @@ -318,10 +324,28 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (_stack_size) { stack = (Variant *)aptr; - for (int i = 0; i < p_argcount; i++) - memnew_placement(&stack[i], Variant(*p_args[i])); - for (int i = p_argcount; i < _stack_size; i++) + for (int i = 0; i < p_argcount; i++) { + if (!argument_types[i].has_type) { + memnew_placement(&stack[i], Variant(*p_args[i])); + continue; + } + + if (!argument_types[i].is_type(*p_args[i], true)) { + r_err.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; + return Variant(); + } + if (argument_types[i].kind == GDScriptDataType::BUILTIN) { + Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err); + memnew_placement(&stack[i], Variant(arg)); + } else { + memnew_placement(&stack[i], Variant(*p_args[i])); + } + } + for (int i = p_argcount; i < _stack_size; i++) { memnew_placement(&stack[i], Variant); + } } else { stack = NULL; } @@ -709,6 +733,199 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { + + CHECK_SPACE(4); + Variant::Type var_type = (Variant::Type)_code_ptr[ip + 1]; + GET_VARIANT_PTR(dst, 2); + GET_VARIANT_PTR(src, 3); + + GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + + if (src->get_type() != var_type) { + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; + OPCODE_BREAK; + } + + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(type, 1); + GET_VARIANT_PTR(dst, 2); + GET_VARIANT_PTR(src, 3); + + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); + GD_ERR_BREAK(!nc); + Object *src_obj = src->operator Object *(); + GD_ERR_BREAK(!src_obj); + + if (!ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + err_text = "Trying to assign value of type '" + src_obj->get_class_name() + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } + + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(type, 1); + GET_VARIANT_PTR(dst, 2); + GET_VARIANT_PTR(src, 3); + + Script *base_type = Object::cast_to<Script>(type->operator Object *()); + + GD_ERR_BREAK(!base_type); + + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + if (src->get_type() != Variant::NIL && src->operator Object *() != NULL) { + + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + if (!scr_inst) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + bool valid = false; + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + + if (!valid) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + } + + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_BUILTIN) { + + CHECK_SPACE(4); + Variant::Type to_type = (Variant::Type)_code_ptr[ip + 1]; + GET_VARIANT_PTR(src, 2); + GET_VARIANT_PTR(dst, 3); + + GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); + + Variant::CallError err; + *dst = Variant::construct(to_type, (const Variant **)&src, 1, err); + +#ifdef DEBUG_ENABLED + if (err.error != Variant::CallError::CALL_OK) { + err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'."; + OPCODE_BREAK; + } +#endif + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_NATIVE) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(to_type, 1); + GET_VARIANT_PTR(src, 2); + GET_VARIANT_PTR(dst, 3); + + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); + GD_ERR_BREAK(!nc); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Invalid cast: can't convert a non-object value to an object type."; + OPCODE_BREAK; + } +#endif + Object *src_obj = src->operator Object *(); + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + *dst = Variant(); // invalid cast, assign NULL + } else { + *dst = *src; + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_SCRIPT) { + + CHECK_SPACE(4); + GET_VARIANT_PTR(to_type, 1); + GET_VARIANT_PTR(src, 2); + GET_VARIANT_PTR(dst, 3); + + Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); + + GD_ERR_BREAK(!base_type); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } +#endif + + bool valid = false; + + if (src->get_type() != Variant::NIL && src->operator Object *() != NULL) { + + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + + if (scr_inst) { + + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + } + } + + if (valid) { + *dst = *src; // Valid cast, copy the source object + } else { + *dst = Variant(); // invalid cast, assign NULL + } + + ip += 4; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT) { CHECK_SPACE(2); @@ -1370,6 +1587,15 @@ int GDScriptFunction::get_default_argument_addr(int p_idx) const { return default_arguments[p_idx]; } +GDScriptDataType GDScriptFunction::get_return_type() const { + return return_type; +} + +GDScriptDataType GDScriptFunction::get_argument_type(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, argument_types.size(), GDScriptDataType()); + return argument_types[p_idx]; +} + StringName GDScriptFunction::get_name() const { return name; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 770d5c8733..3ce84290fd 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -42,6 +42,95 @@ class GDScriptInstance; class GDScript; +struct GDScriptDataType { + bool has_type; + enum { + BUILTIN, + NATIVE, + SCRIPT, + GDSCRIPT + } kind; + Variant::Type builtin_type; + StringName native_type; + Ref<Script> script_type; + + bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const { + if (!has_type) return true; // Can't type check + + switch (kind) { + case BUILTIN: { + Variant::Type var_type = p_variant.get_type(); + bool valid = builtin_type == var_type; + if (!valid && p_allow_implicit_conversion) { + valid = Variant::can_convert_strict(var_type, builtin_type); + } + return valid; + } break; + case NATIVE: { + if (p_variant.get_type() == Variant::NIL) { + return true; + } + if (p_variant.get_type() != Variant::OBJECT) { + return false; + } + Object *obj = p_variant.operator Object *(); + if (obj && !ClassDB::is_parent_class(obj->get_class_name(), native_type)) { + return false; + } + return true; + } break; + case SCRIPT: + case GDSCRIPT: { + if (p_variant.get_type() == Variant::NIL) { + return true; + } + if (p_variant.get_type() != Variant::OBJECT) { + return false; + } + Object *obj = p_variant.operator Object *(); + Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : NULL; + bool valid = false; + while (base.is_valid()) { + if (base == script_type) { + valid = true; + break; + } + base = base->get_base_script(); + } + return valid; + } break; + } + return false; + } + + operator PropertyInfo() const { + PropertyInfo info; + if (has_type) { + switch (kind) { + case BUILTIN: { + info.type = builtin_type; + } break; + case NATIVE: { + info.type = Variant::OBJECT; + info.class_name = native_type; + } break; + case SCRIPT: + case GDSCRIPT: { + info.type = Variant::OBJECT; + info.class_name = script_type->get_instance_base_type(); + } break; + } + } else { + info.type = Variant::NIL; + info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + return info; + } + + GDScriptDataType() : + has_type(false) {} +}; + class GDScriptFunction { public: enum Opcode { @@ -56,6 +145,12 @@ public: OPCODE_ASSIGN, OPCODE_ASSIGN_TRUE, OPCODE_ASSIGN_FALSE, + OPCODE_ASSIGN_TYPED_BUILTIN, + OPCODE_ASSIGN_TYPED_NATIVE, + OPCODE_ASSIGN_TYPED_SCRIPT, + OPCODE_CAST_TO_BUILTIN, + OPCODE_CAST_TO_NATIVE, + OPCODE_CAST_TO_SCRIPT, OPCODE_CONSTRUCT, //only for basic types!! OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, @@ -139,6 +234,8 @@ private: #endif Vector<int> default_arguments; Vector<int> code; + Vector<GDScriptDataType> argument_types; + GDScriptDataType return_type; #ifdef TOOLS_ENABLED Vector<StringName> arg_names; @@ -199,6 +296,8 @@ public: int get_max_stack_size() const; int get_default_argument_count() const; int get_default_argument_addr(int p_idx) const; + GDScriptDataType get_return_type() const; + GDScriptDataType get_argument_type(int p_idx) const; GDScript *get_script() const { return _script; } StringName get_source() const { return source; } diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index ce91e7dff3..7e98b6ced9 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -1663,7 +1663,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { MethodInfo mi("weakref", PropertyInfo(Variant::OBJECT, "obj")); mi.return_val.type = Variant::OBJECT; - mi.return_val.name = "WeakRef"; + mi.return_val.class_name = "WeakRef"; return mi; @@ -1672,19 +1672,20 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { MethodInfo mi("funcref", PropertyInfo(Variant::OBJECT, "instance"), PropertyInfo(Variant::STRING, "funcname")); mi.return_val.type = Variant::OBJECT; - mi.return_val.name = "FuncRef"; + mi.return_val.class_name = "FuncRef"; return mi; } break; case TYPE_CONVERT: { - MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what"), PropertyInfo(Variant::INT, "type")); - mi.return_val.type = Variant::OBJECT; + MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::INT, "type")); + mi.return_val.type = Variant::NIL; + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; return mi; } break; case TYPE_OF: { - MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what")); + MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; @@ -1760,7 +1761,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case VAR_TO_STR: { - MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::STRING; return mi; @@ -1773,7 +1774,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case VAR_TO_BYTES: { - MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::POOL_BYTE_ARRAY; return mi; @@ -1796,7 +1797,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { MethodInfo mi("load", PropertyInfo(Variant::STRING, "path")); mi.return_val.type = Variant::OBJECT; - mi.return_val.name = "Resource"; + mi.return_val.class_name = "Resource"; return mi; } break; case INST2DICT: { @@ -1826,13 +1827,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TO_JSON: { - MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::STRING; return mi; } break; case HASH: { - MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; } break; @@ -1868,7 +1869,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case LEN: { - MethodInfo mi("len", PropertyInfo(Variant::NIL, "var")); + MethodInfo mi("len", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; } break; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 9650563ee6..ac53f33e9e 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -30,6 +30,10 @@ #include "gdscript_parser.h" +#include "core/core_string_names.h" +#include "core/engine.h" +#include "core/project_settings.h" +#include "core/reference.h" #include "gdscript.h" #include "io/resource_loader.h" #include "os/file_access.h" @@ -138,8 +142,9 @@ bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bo } Node *arg = _parse_expression(p_parent, p_static); - if (!arg) + if (!arg) { return false; + } p_args.push_back(arg); @@ -263,6 +268,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s bool need_identifier = true; bool done = false; + int line = tokenizer->get_token_line(); while (!done) { @@ -330,16 +336,19 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_CALL; - + op->line = line; op->arguments.push_back(alloc_node<SelfNode>()); + op->arguments[0]->line = line; IdentifierNode *funcname = alloc_node<IdentifierNode>(); funcname->name = "get_node"; - + funcname->line = line; op->arguments.push_back(funcname); ConstantNode *nodepath = alloc_node<ConstantNode>(); nodepath->value = NodePath(StringName(path)); + nodepath->datatype = _type_from_variant(nodepath->value); + nodepath->line = line; op->arguments.push_back(nodepath); expr = op; @@ -353,6 +362,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = tokenizer->get_token_constant(); + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) { @@ -360,6 +370,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_PI; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) { @@ -367,6 +378,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_TAU; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) { @@ -374,6 +386,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_INF; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) { @@ -381,6 +394,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_NAN; + constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) { @@ -419,15 +433,13 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } if (subexpr->type == Node::TYPE_IDENTIFIER) { IdentifierNode *in = static_cast<IdentifierNode *>(subexpr); - Vector<ClassNode::Constant> ce = current_class->constant_expressions; // Try to find the constant expression by the identifier - for (int i = 0; i < ce.size(); ++i) { - if (ce[i].identifier == in->name) { - if (ce[i].expression->type == Node::TYPE_CONSTANT) { - cn = static_cast<ConstantNode *>(ce[i].expression); - found_constant = true; - } + if (current_class->constant_expressions.has(in->name)) { + Node *cn_exp = current_class->constant_expressions[in->name].expression; + if (cn_exp->type == Node::TYPE_CONSTANT) { + cn = static_cast<ConstantNode *>(cn_exp); + found_constant = true; } } } @@ -480,15 +492,28 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s _set_error("Expected ')' after 'preload' path"); return NULL; } + + Ref<GDScript> gds = res; + if (gds.is_valid() && !gds->is_valid()) { + _set_error("Could not fully preload the script, possible cyclic reference or compilation error."); + return NULL; + } + tokenizer->advance(); ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = res; + constant->datatype = _type_from_variant(constant->value); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) { - //constant defined by tokenizer + if (!current_function) { + _set_error("yield() can only be used inside function blocks."); + return NULL; + } + + current_function->has_yield = true; tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { @@ -618,6 +643,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = Variant::get_numeric_constant_value(bi_type, identifier); + cn->datatype = _type_from_variant(cn->value); expr = cn; } @@ -695,24 +721,56 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s const ClassNode *cln = current_class; bool bfn = false; StringName identifier; + int id_line = tokenizer->get_token_line(); if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) { } - if (p_parsing_constant) { - for (int i = 0; i < cln->constant_expressions.size(); ++i) { - - if (cln->constant_expressions[i].identifier == identifier) { + BlockNode *b = current_block; + while (b) { + if (b->variables.has(identifier)) { + IdentifierNode *id = alloc_node<IdentifierNode>(); + LocalVarNode *lv = b->variables[identifier]; + id->name = identifier; + id->declared_block = b; + id->line = id_line; + expr = id; + bfn = true; - expr = cln->constant_expressions[i].expression; - bfn = true; - break; + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_OP_ASSIGN_ADD: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: + case GDScriptTokenizer::TK_OP_ASSIGN_DIV: + case GDScriptTokenizer::TK_OP_ASSIGN_MOD: + case GDScriptTokenizer::TK_OP_ASSIGN_MUL: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: + case GDScriptTokenizer::TK_OP_ASSIGN_SUB: { + if (lv->assignments == 0 && !lv->datatype.has_type) { + _set_error("Using assignment with operation on a variable that was never assigned."); + return NULL; + } + } // fallthrough + case GDScriptTokenizer::TK_OP_ASSIGN: { + lv->assignments += 1; + } } + break; } + b = b->parent_block; + } - if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + if (!bfn && p_parsing_constant) { + if (cln->constant_expressions.has(identifier)) { + expr = cln->constant_expressions[identifier].expression; + bfn = true; + } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { //check from constants ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]]; + constant->datatype = _type_from_variant(constant->value); + constant->line = id_line; expr = constant; bfn = true; } @@ -729,6 +787,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (!bfn) { IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; + id->line = id_line; expr = id; } @@ -902,6 +961,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //lua style identifier, easier to write ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = tokenizer->get_token_literal(); + cn->datatype = _type_from_variant(cn->value); key = cn; tokenizer->advance(2); expecting = DICT_EXPECT_VALUE; @@ -941,7 +1001,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s expr = dict; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) && tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { + } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name // parent call @@ -953,17 +1013,23 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s op->arguments.push_back(self); forbidden for now */ StringName identifier; - if (_get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier)) { - //indexing stuff - } + bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion; IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; op->arguments.push_back(id); - tokenizer->advance(1); - if (!_parse_arguments(op, op->arguments, p_static)) - return NULL; + if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { + if (!is_completion) { + _set_error("Expected '(' for parent function call."); + return NULL; + } + } else { + tokenizer->advance(); + if (!_parse_arguments(op, op->arguments, p_static)) { + return NULL; + } + } expr = op; @@ -1087,6 +1153,26 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s break; } + /*****************/ + /* Parse Casting */ + /*****************/ + + bool has_casting = expr->type == Node::TYPE_CAST; + if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) { + if (has_casting) { + _set_error("Unexpected 'as'."); + return NULL; + } + CastNode *cn = alloc_node<CastNode>(); + if (!_parse_type(cn->cast_type)) { + _set_error("Expected type after 'as'."); + return NULL; + } + has_casting = true; + cn->source_node = expr; + expr = cn; + } + /******************/ /* Parse Operator */ /******************/ @@ -1110,7 +1196,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s //assign, if allowed is only allowed on the first operator #define _VALIDATE_ASSIGN \ - if (!p_allow_assign) { \ + if (!p_allow_assign || has_casting) { \ _set_error("Unexpected assign."); \ return NULL; \ } \ @@ -1338,11 +1424,11 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) { _set_error("Expected else after ternary if."); - ERR_FAIL_V(NULL); + return NULL; } if (next_op >= (expression.size() - 3)) { _set_error("Expected value after ternary else."); - ERR_FAIL_V(NULL); + return NULL; } OperatorNode *op = alloc_node<OperatorNode>(); @@ -1450,13 +1536,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); Array arr; - //print_line("mk array "+itos(!p_to_const)); arr.resize(an->elements.size()); for (int i = 0; i < an->elements.size(); i++) { ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]); arr[i] = acn->value; } cn->value = arr; + cn->datatype = _type_from_variant(cn->value); return cn; } @@ -1490,6 +1576,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to dict[key_c->value] = value_c->value; } cn->value = dict; + cn->datatype = _type_from_variant(cn->value); return cn; } @@ -1591,6 +1678,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; + cn->datatype = _type_from_variant(v); return cn; } else if (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && last_not_constant == 0) { @@ -1620,24 +1708,9 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; + cn->datatype = _type_from_variant(v); return cn; - - } /*else if (op->arguments[0]->type==Node::TYPE_CONSTANT && op->arguments[1]->type==Node::TYPE_IDENTIFIER) { - - ConstantNode *ca = static_cast<ConstantNode*>(op->arguments[0]); - IdentifierNode *ib = static_cast<IdentifierNode*>(op->arguments[1]); - - bool valid; - Variant v = ca->value.get_named(ib->name,&valid); - if (!valid) { - _set_error("invalid index '"+String(ib->name)+"' in constant expression"); - return op; - } - - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value=v; - return cn; - }*/ + } return op; @@ -1658,13 +1731,14 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; + cn->datatype = _type_from_variant(v); return cn; } return op; } - //validate assignment (don't assign to cosntant expression + //validate assignment (don't assign to constant expression switch (op->op) { case OperatorNode::OP_ASSIGN: @@ -1711,6 +1785,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to } \ ConstantNode *cn = alloc_node<ConstantNode>(); \ cn->value = res; \ + cn->datatype = _type_from_variant(res); \ return cn; #define _REDUCE_BINARY(m_vop) \ @@ -1724,6 +1799,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to } \ ConstantNode *cn = alloc_node<ConstantNode>(); \ cn->value = res; \ + cn->datatype = _type_from_variant(res); \ return cn; switch (op->op) { @@ -1799,6 +1875,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to case OperatorNode::OP_BIT_XOR: { _REDUCE_BINARY(Variant::OP_BIT_XOR); } break; + case OperatorNode::OP_TERNARY_IF: { + if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) { + return op->arguments[1]; + } else { + return op->arguments[2]; + } + } break; default: { ERR_FAIL_V(op); } } @@ -1905,6 +1988,15 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) { tokenizer->advance(); pattern->pt_type = GDScriptParser::PatternNode::PT_BIND; pattern->bind = tokenizer->get_token_identifier(); + // Check if binding is already used + if (current_block->variables.has(pattern->bind)) { + _set_error("Binding name of '" + pattern->bind.operator String() + "' was already used in the pattern."); + return NULL; + } + // Create local variable for proper identifier detection later + LocalVarNode *lv = alloc_node<LocalVarNode>(); + lv->name = pattern->bind; + current_block->variables.insert(lv->name, lv); tokenizer->advance(); } break; // dictionary @@ -2038,18 +2130,34 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran } PatternBranchNode *branch = alloc_node<PatternBranchNode>(); + branch->body = alloc_node<BlockNode>(); + branch->body->parent_block = p_block; + p_block->sub_blocks.push_back(branch->body); + current_block = branch->body; branch->patterns.push_back(_parse_pattern(p_static)); if (!branch->patterns[0]) { return; } + bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND; + bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD; + while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); branch->patterns.push_back(_parse_pattern(p_static)); if (!branch->patterns[branch->patterns.size() - 1]) { return; } + + PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type; + + if (pt == PatternNode::PT_BIND) { + _set_error("Cannot use bindings with multipattern."); + return; + } + + catch_all = catch_all || pt == PatternNode::PT_WILDCARD; } if (!_enter_indent_block()) { @@ -2057,54 +2165,76 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran return; } - branch->body = alloc_node<BlockNode>(); - branch->body->parent_block = p_block; - p_block->sub_blocks.push_back(branch->body); - current_block = branch->body; - _parse_block(branch->body, p_static); current_block = p_block; + if (catch_all && branch->body->has_return) { + p_block->has_return = true; + } + p_branches.push_back(branch); } } void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) { + + const DataType &to_match_type = p_node_to_match->get_datatype(); + switch (p_pattern->pt_type) { case PatternNode::PT_CONSTANT: { - // typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; + DataType pattern_type = _reduce_node_type(p_pattern->constant); + if (error_set) { + return; + } - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); + OperatorNode *type_comp = NULL; - OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); - typeof_pattern_value->op = OperatorNode::OP_CALL; - typeof_pattern_value->arguments.push_back(typeof_node); - typeof_pattern_value->arguments.push_back(p_pattern->constant); + // static type check if possible + if (pattern_type.has_type && to_match_type.has_type) { + if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) { + _set_error("Pattern type (" + pattern_type.to_string() + ") is not compatible with the type of the value to match (" + to_match_type.to_string() + ").", + p_pattern->line); + return; + } + } else { + // runtime typecheck + BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); + typeof_node->function = GDScriptFunctions::TYPE_OF; - OperatorNode *type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_pattern_value); + OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); + typeof_match_value->op = OperatorNode::OP_CALL; + typeof_match_value->arguments.push_back(typeof_node); + typeof_match_value->arguments.push_back(p_node_to_match); - // comare the actual values + OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); + typeof_pattern_value->op = OperatorNode::OP_CALL; + typeof_pattern_value->arguments.push_back(typeof_node); + typeof_pattern_value->arguments.push_back(p_pattern->constant); + + type_comp = alloc_node<OperatorNode>(); + type_comp->op = OperatorNode::OP_EQUAL; + type_comp->arguments.push_back(typeof_match_value); + type_comp->arguments.push_back(typeof_pattern_value); + } + + // compare the actual values OperatorNode *value_comp = alloc_node<OperatorNode>(); value_comp->op = OperatorNode::OP_EQUAL; value_comp->arguments.push_back(p_pattern->constant); value_comp->arguments.push_back(p_node_to_match); - OperatorNode *comparison = alloc_node<OperatorNode>(); - comparison->op = OperatorNode::OP_AND; - comparison->arguments.push_back(type_comp); - comparison->arguments.push_back(value_comp); + if (type_comp) { + OperatorNode *full_comparison = alloc_node<OperatorNode>(); + full_comparison->op = OperatorNode::OP_AND; + full_comparison->arguments.push_back(type_comp); + full_comparison->arguments.push_back(value_comp); - p_resulting_node = comparison; + p_resulting_node = full_comparison; + } else { + p_resulting_node = value_comp; + } } break; case PatternNode::PT_BIND: { @@ -2129,22 +2259,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length { - // typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); - typeof_array->name = "TYPE_ARRAY"; - - OperatorNode *type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_array); + OperatorNode *type_comp = NULL; + // static type check if possible + if (to_match_type.has_type) { + // must be an array + if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) { + _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line); + return; + } + } else { + // runtime typecheck + BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); + typeof_node->function = GDScriptFunctions::TYPE_OF; + + OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); + typeof_match_value->op = OperatorNode::OP_CALL; + typeof_match_value->arguments.push_back(typeof_node); + typeof_match_value->arguments.push_back(p_node_to_match); + + IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); + typeof_array->name = "TYPE_ARRAY"; + + type_comp = alloc_node<OperatorNode>(); + type_comp->op = OperatorNode::OP_EQUAL; + type_comp->arguments.push_back(typeof_match_value); + type_comp->arguments.push_back(typeof_array); + } // size ConstantNode *length = alloc_node<ConstantNode>(); @@ -2163,12 +2303,16 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m length_comparison->arguments.push_back(call); length_comparison->arguments.push_back(length); - OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); - type_and_length_comparison->op = OperatorNode::OP_AND; - type_and_length_comparison->arguments.push_back(type_comp); - type_and_length_comparison->arguments.push_back(length_comparison); + if (type_comp) { + OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); + type_and_length_comparison->op = OperatorNode::OP_AND; + type_and_length_comparison->arguments.push_back(type_comp); + type_and_length_comparison->arguments.push_back(length_comparison); - p_resulting_node = type_and_length_comparison; + p_resulting_node = type_and_length_comparison; + } else { + p_resulting_node = length_comparison; + } } for (int i = 0; i < p_pattern->array.size(); i++) { @@ -2208,22 +2352,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length { - // typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); - typeof_dictionary->name = "TYPE_DICTIONARY"; - - OperatorNode *type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_dictionary); + OperatorNode *type_comp = NULL; + // static type check if possible + if (to_match_type.has_type) { + // must be an dictionary + if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) { + _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line); + return; + } + } else { + // runtime typecheck + BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); + typeof_node->function = GDScriptFunctions::TYPE_OF; + + OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); + typeof_match_value->op = OperatorNode::OP_CALL; + typeof_match_value->arguments.push_back(typeof_node); + typeof_match_value->arguments.push_back(p_node_to_match); + + IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); + typeof_dictionary->name = "TYPE_DICTIONARY"; + + type_comp = alloc_node<OperatorNode>(); + type_comp->op = OperatorNode::OP_EQUAL; + type_comp->arguments.push_back(typeof_match_value); + type_comp->arguments.push_back(typeof_dictionary); + } // size ConstantNode *length = alloc_node<ConstantNode>(); @@ -2242,12 +2396,16 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m length_comparison->arguments.push_back(call); length_comparison->arguments.push_back(length); - OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); - type_and_length_comparison->op = OperatorNode::OP_AND; - type_and_length_comparison->arguments.push_back(type_comp); - type_and_length_comparison->arguments.push_back(length_comparison); + if (type_comp) { + OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); + type_and_length_comparison->op = OperatorNode::OP_AND; + type_and_length_comparison->arguments.push_back(type_comp); + type_and_length_comparison->arguments.push_back(length_comparison); - p_resulting_node = type_and_length_comparison; + p_resulting_node = type_and_length_comparison; + } else { + p_resulting_node = length_comparison; + } } for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) { @@ -2308,9 +2466,20 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m } } -void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement) { +void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) { IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = "#match_value"; + id->line = p_match_statement->line; + id->datatype = _reduce_node_type(p_match_statement->val_to_match); + if (id->datatype.has_type) { + _mark_line_as_safe(id->line); + } else { + _mark_line_as_unsafe(id->line); + } + + if (error_set) { + return; + } for (int i = 0; i < p_match_statement->branches.size(); i++) { @@ -2323,11 +2492,16 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_ for (int j = 0; j < branch->patterns.size(); j++) { PatternNode *pattern = branch->patterns[j]; + _mark_line_as_safe(pattern->line); Map<StringName, Node *> bindings; - Node *resulting_node; + Node *resulting_node = NULL; _generate_pattern(pattern, id, resulting_node, bindings); + if (!resulting_node) { + return; + } + if (!binding.empty() && !bindings.empty()) { _set_error("Multipatterns can't contain bindings"); return; @@ -2335,6 +2509,14 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_ binding = bindings; } + // Result is always a boolean + DataType resulting_node_type; + resulting_node_type.has_type = true; + resulting_node_type.is_constant = true; + resulting_node_type.kind = DataType::BUILTIN; + resulting_node_type.builtin_type = Variant::BOOL; + resulting_node->set_datatype(resulting_node_type); + if (compiled_branch.compiled_pattern) { OperatorNode *or_node = alloc_node<OperatorNode>(); or_node->op = OperatorNode::OP_OR; @@ -2350,12 +2532,19 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_ // prepare the body ...hehe for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) { - LocalVarNode *local_var = alloc_node<LocalVarNode>(); - local_var->name = e->key(); + if (!branch->body->variables.has(e->key())) { + _set_error("Parser bug: missing pattern bind variable.", branch->line); + ERR_FAIL(); + } + + LocalVarNode *local_var = branch->body->variables[e->key()]; local_var->assign = e->value(); + local_var->set_datatype(local_var->assign->get_datatype()); IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = local_var->name; + id->declared_block = branch->body; + id->set_datatype(local_var->assign->get_datatype()); OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_ASSIGN; @@ -2413,7 +2602,21 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } switch (token) { - + case GDScriptTokenizer::TK_EOF: + case GDScriptTokenizer::TK_ERROR: + case GDScriptTokenizer::TK_NEWLINE: + case GDScriptTokenizer::TK_CF_PASS: { + // will check later + } break; + default: { + // TODO: Make this a warning + /*if (p_block->has_return) { + _set_error("Unreacheable code."); + return; + }*/ + } break; + } + switch (token) { case GDScriptTokenizer::TK_EOF: p_block->end_line = tokenizer->get_token_line(); case GDScriptTokenizer::TK_ERROR: { @@ -2443,6 +2646,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { _set_error("Expected ';' or <NewLine>."); return; } + _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { // Ignore semicolon after 'pass' @@ -2453,6 +2657,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { //variale declaration and (eventual) initialization tokenizer->advance(); + int var_line = tokenizer->get_token_line(); if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected identifier for local variable name."); @@ -2470,24 +2675,34 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } BlockNode *check_block = p_block; while (check_block) { - for (int i = 0; i < check_block->variables.size(); i++) { - if (n == check_block->variables[i]) { - _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variable_lines[i]) + ")."); - return; - } + if (check_block->variables.has(n)) { + _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variables[n]->line) + ")."); + return; } check_block = check_block->parent_block; } - int var_line = tokenizer->get_token_line(); - //must know when the local variable is declared LocalVarNode *lv = alloc_node<LocalVarNode>(); lv->name = n; + lv->line = var_line; p_block->statements.push_back(lv); Node *assigned = NULL; + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + lv->datatype = DataType(); +#ifdef DEBUG_ENABLED + lv->datatype.infer_type = true; +#endif + tokenizer->advance(); + } else if (!_parse_type(lv->datatype)) { + _set_error("Expected type for variable."); + return; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { tokenizer->advance(); @@ -2499,26 +2714,36 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; } - lv->assign = subexpr; + lv->assignments++; assigned = subexpr; } else { ConstantNode *c = alloc_node<ConstantNode>(); - c->value = Variant(); + if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) { + Variant::CallError err; + c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err); + } else { + c->value = Variant(); + } + c->line = var_line; assigned = c; } //must be added later, to avoid self-referencing. - p_block->variables.push_back(n); //line? - p_block->variable_lines.push_back(var_line); + p_block->variables.insert(n, lv); IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = n; + id->declared_block = p_block; + id->line = var_line; OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_ASSIGN; op->arguments.push_back(id); op->arguments.push_back(assigned); + op->line = var_line; p_block->statements.push_back(op); + lv->assign_op = op; + lv->assign = assigned; if (!_end_statement()) { _set_error("Expected end of statement (var)"); @@ -2563,6 +2788,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; p_block->statements.push_back(cf_if); + bool all_have_return = cf_if->body->has_return; + bool have_else = false; + while (true) { while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) @@ -2619,6 +2847,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (error_set) return; + all_have_return = all_have_return && cf_else->body->has_return; + } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) { if (tab_level.back()->get() > indent_level) { @@ -2642,12 +2872,19 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (error_set) return; + all_have_return = all_have_return && cf_if->body_else->has_return; + have_else = true; + break; //after else, exit } else break; } + cf_if->body->has_return = all_have_return; + // If there's no else block, path out of the if might not have a return + p_block->has_return = all_have_return && have_else; + } break; case GDScriptTokenizer::TK_CF_WHILE: { @@ -2680,6 +2917,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { current_block = p_block; if (error_set) return; + p_block->has_return = cf_while->body->has_return; p_block->statements.push_back(cf_while); } break; case GDScriptTokenizer::TK_CF_FOR: { @@ -2711,6 +2949,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; } + DataType iter_type; + iter_type.is_constant = true; + if (container->type == Node::TYPE_OPERATOR) { OperatorNode *op = static_cast<OperatorNode *>(container); @@ -2745,6 +2986,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { case 2: cn->value = Vector2(constants[0], constants[1]); break; case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break; } + cn->datatype = _type_from_variant(cn->value); container = cn; } else { OperatorNode *on = alloc_node<OperatorNode>(); @@ -2766,6 +3008,10 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { container = on; } } + + iter_type.has_type = true; + iter_type.kind = DataType::BUILTIN; + iter_type.builtin_type = Variant::INT; } } @@ -2789,15 +3035,20 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { // this is for checking variable for redefining // inside this _parse_block - cf_for->body->variables.push_back(id->name); - cf_for->body->variable_lines.push_back(id->line); + LocalVarNode *lv = alloc_node<LocalVarNode>(); + lv->name = id->name; + lv->line = id->line; + lv->assignments++; + id->declared_block = cf_for->body; + lv->set_datatype(iter_type); + id->set_datatype(iter_type); + cf_for->body->variables.insert(id->name, lv); _parse_block(cf_for->body, p_static); - cf_for->body->variables.remove(0); - cf_for->body->variable_lines.remove(0); current_block = p_block; if (error_set) return; + p_block->has_return = cf_for->body->has_return; p_block->statements.push_back(cf_for); } break; case GDScriptTokenizer::TK_CF_CONTINUE: { @@ -2827,6 +3078,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { tokenizer->advance(); ControlFlowNode *cf_return = alloc_node<ControlFlowNode>(); cf_return->cf_type = ControlFlowNode::CF_RETURN; + cf_return->line = tokenizer->get_token_line(-1); if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { //expect end of statement @@ -2850,6 +3102,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; } } + p_block->has_return = true; } break; case GDScriptTokenizer::TK_CF_MATCH: { @@ -2882,12 +3135,14 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { _parse_pattern_block(compiled_branches, match_node->branches, p_static); - _transform_match_statment(compiled_branches, match_node); + if (error_set) return; ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>(); match_cf_node->cf_type = ControlFlowNode::CF_MATCH; match_cf_node->match = match_node; + match_cf_node->body = compiled_branches; + p_block->has_return = p_block->has_return || compiled_branches->has_return; p_block->statements.push_back(match_cf_node); _end_statement(); @@ -2938,16 +3193,6 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } } break; - /* - case GDScriptTokenizer::TK_CF_LOCAL: { - - if (tokenizer->get_token(1)!=GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1)!=GDScriptTokenizer::TK_NEWLINE ) { - - _set_error("Expected ';' or <NewLine>."); - } - tokenizer->advance(); - } break; - */ } } } @@ -3103,6 +3348,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } break; case GDScriptTokenizer::TK_PR_EXTENDS: { + _mark_line_as_safe(tokenizer->get_token_line()); _parse_extends(p_class); if (error_set) return; @@ -3112,6 +3358,33 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } break; + case GDScriptTokenizer::TK_PR_CLASS_NAME: { + + if (p_class->owner) { + _set_error("'class_name' is only valid for the main class namespace."); + return; + } + if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { + + _set_error("'class_name' syntax: 'class_name <UniqueName>'"); + return; + } + + p_class->name = tokenizer->get_token_identifier(1); + + if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) { + _set_error("Unique global class '" + p_class->name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); + return; + } + + if (ClassDB::class_exists(p_class->name)) { + _set_error("Class '" + p_class->name + "' shadows a native class."); + return; + } + + tokenizer->advance(2); + + } break; case GDScriptTokenizer::TK_PR_TOOL: { if (p_class->tool) { @@ -3128,7 +3401,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { //class inside class :D StringName name; - StringName extends; if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { @@ -3138,6 +3410,31 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { name = tokenizer->get_token_identifier(1); tokenizer->advance(2); + // Check if name is shadowing something else + if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) { + _set_error("Class '" + String(name) + "' shadows a native class."); + return; + } + if (ScriptServer::is_global_class(name)) { + _set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); + return; + } + ClassNode *outer_class = p_class; + while (outer_class) { + for (int i = 0; i < outer_class->subclasses.size(); i++) { + if (outer_class->subclasses[i]->name == name) { + _set_error("Another class named '" + String(name) + "' already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ")."); + return; + } + } + if (outer_class->constant_expressions.has(name)) { + _set_error("A constant named '" + String(name) + "' already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ")."); + return; + } + + outer_class = outer_class->owner; + } + ClassNode *newclass = alloc_node<ClassNode>(); newclass->initializer = alloc_node<BlockNode>(); newclass->initializer->parent_class = newclass; @@ -3222,6 +3519,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); Vector<StringName> arguments; + Vector<DataType> argument_types; Vector<Node *> default_values; int fnline = tokenizer->get_token_line(); @@ -3252,6 +3550,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); + DataType argtype; + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (!_parse_type(argtype)) { + _set_error("Expected type for argument."); + return; + } + } + argument_types.push_back(argtype); + if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Default parameter expected."); @@ -3269,9 +3576,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { OperatorNode *on = alloc_node<OperatorNode>(); on->op = OperatorNode::OP_ASSIGN; + on->line = fnline; IdentifierNode *in = alloc_node<IdentifierNode>(); in->name = argname; + in->line = fnline; on->arguments.push_back(in); on->arguments.push_back(defval); @@ -3308,6 +3617,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (name == "_init") { + if (_static) { + _set_error("Constructor cannot be static."); + return; + } + if (p_class->extends_used) { OperatorNode *cparent = alloc_node<OperatorNode>(); @@ -3322,6 +3636,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("expected '(' for parent constructor arguments."); + return; } tokenizer->advance(); @@ -3359,6 +3674,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } + DataType return_type; + if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) { + + if (!_parse_type(return_type, true)) { + _set_error("Expected return type for function."); + return; + } + } + if (!_enter_indent_block(block)) { _set_error("Indented block expected."); @@ -3367,7 +3691,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { FunctionNode *function = alloc_node<FunctionNode>(); function->name = name; + function->return_type = return_type; function->arguments = arguments; + function->argument_types = argument_types; function->default_values = default_values; function->_static = _static; function->line = fnline; @@ -4082,10 +4408,37 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member.line = tokenizer->get_token_line(); member.rpc_mode = rpc_mode; + if (current_class->constant_expressions.has(member.identifier)) { + _set_error("A constant named '" + String(member.identifier) + "' alread exists in this class (at line: " + + itos(current_class->constant_expressions[member.identifier].expression->line) + ")."); + return; + } + + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == member.identifier) { + _set_error("Variable '" + String(member.identifier) + "' alread exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } + tokenizer->advance(); rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + member.data_type = DataType(); +#ifdef DEBUG_ENABLED + member.data_type.infer_type = true; +#endif + tokenizer->advance(); + } else if (!_parse_type(member.data_type)) { + _set_error("Expected type for class variable."); + return; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { #ifdef DEBUG_ENABLED @@ -4117,42 +4470,31 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member.expression = subexpr; - if (autoexport) { - if (1) /*(subexpr->type==Node::TYPE_ARRAY) { - - member._export.type=Variant::ARRAY; - - } else if (subexpr->type==Node::TYPE_DICTIONARY) { + if (autoexport && !member.data_type.has_type) { - member._export.type=Variant::DICTIONARY; - - } else*/ - { - - if (subexpr->type != Node::TYPE_CONSTANT) { + if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Type-less export needs a constant expression assigned to infer type."); - return; - } + _set_error("Type-less export needs a constant expression assigned to infer type."); + return; + } - ConstantNode *cn = static_cast<ConstantNode *>(subexpr); - if (cn->value.get_type() == Variant::NIL) { + ConstantNode *cn = static_cast<ConstantNode *>(subexpr); + if (cn->value.get_type() == Variant::NIL) { - _set_error("Can't accept a null constant expression for inferring export type."); + _set_error("Can't accept a null constant expression for inferring export type."); + return; + } + member._export.type = cn->value.get_type(); + member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; + if (cn->value.get_type() == Variant::OBJECT) { + Object *obj = cn->value; + Resource *res = Object::cast_to<Resource>(obj); + if (res == NULL) { + _set_error("Exported constant not a type or resource."); return; } - member._export.type = cn->value.get_type(); - member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - if (cn->value.get_type() == Variant::OBJECT) { - Object *obj = cn->value; - Resource *res = Object::cast_to<Resource>(obj); - if (res == NULL) { - _set_error("Exported constant not a type or resource."); - return; - } - member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; - member._export.hint_string = res->get_class(); - } + member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; + member._export.hint_string = res->get_class(); } } #ifdef TOOLS_ENABLED @@ -4186,15 +4528,36 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { else p_class->initializer->statements.push_back(op); - } else { + member.initial_assignment = op; - if (autoexport) { + } else { + if (autoexport && !member.data_type.has_type) { _set_error("Type-less export needs a constant expression assigned to infer type."); return; } } + if (autoexport && member.data_type.has_type) { + if (member.data_type.kind == DataType::BUILTIN) { + member._export.type = member.data_type.builtin_type; + } else if (member.data_type.kind == DataType::NATIVE) { + if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) { + member._export.type = Variant::OBJECT; + member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; + member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; + member._export.class_name = member.data_type.native_type; + } else { + _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); + return; + } + + } else { + _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); + return; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) { tokenizer->advance(); @@ -4231,7 +4594,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } break; case GDScriptTokenizer::TK_PR_CONST: { - //variale declaration and (eventual) initialization + // constant declaration and initialization ClassNode::Constant constant; @@ -4242,9 +4605,38 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - constant.identifier = tokenizer->get_token_literal(); + StringName const_id = tokenizer->get_token_literal(); + int line = tokenizer->get_token_line(); + + if (current_class->constant_expressions.has(const_id)) { + _set_error("Constant '" + String(const_id) + "' alread exists in this class (at line: " + + itos(current_class->constant_expressions[const_id].expression->line) + ")."); + return; + } + + for (int i = 0; i < current_class->variables.size(); i++) { + if (current_class->variables[i].identifier == const_id) { + _set_error("A variable named '" + String(const_id) + "' alread exists in this class (at line: " + + itos(current_class->variables[i].line) + ")."); + return; + } + } + tokenizer->advance(); + if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { + constant.type = DataType(); +#ifdef DEBUG_ENABLED + constant.type.infer_type = true; +#endif + tokenizer->advance(); + } else if (!_parse_type(constant.type)) { + _set_error("Expected type for class constant."); + return; + } + } + if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Constant expects assignment."); return; @@ -4261,14 +4653,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Expected constant expression"); + _set_error("Expected constant expression", line); + return; } + subexpr->line = line; constant.expression = subexpr; - p_class->constant_expressions.push_back(constant); + p_class->constant_expressions.insert(const_id, constant); if (!_end_statement()) { - _set_error("Expected end of statement (constant)"); + _set_error("Expected end of statement (constant)", line); return; } @@ -4311,7 +4705,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } else { // tokenizer->is_token_literal(0, true) ClassNode::Constant constant; - constant.identifier = tokenizer->get_token_literal(); + StringName const_id = tokenizer->get_token_literal(); tokenizer->advance(); @@ -4328,22 +4722,25 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (subexpr->type != Node::TYPE_CONSTANT) { _set_error("Expected constant expression"); + return; } - const ConstantNode *subexpr_const = static_cast<const ConstantNode *>(subexpr); + ConstantNode *subexpr_const = static_cast<ConstantNode *>(subexpr); if (subexpr_const->value.get_type() != Variant::INT) { _set_error("Expected an int value for enum"); + return; } last_assign = subexpr_const->value; - constant.expression = subexpr; + constant.expression = subexpr_const; } else { last_assign = last_assign + 1; ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = last_assign; + cn->datatype = _type_from_variant(cn->value); constant.expression = cn; } @@ -4353,20 +4750,25 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (enum_name != "") { const ConstantNode *cn = static_cast<const ConstantNode *>(constant.expression); - enum_dict[constant.identifier] = cn->value; + enum_dict[const_id] = cn->value; } - p_class->constant_expressions.push_back(constant); + constant.type.has_type = true; + constant.type.kind = DataType::BUILTIN; + constant.type.builtin_type = Variant::INT; + p_class->constant_expressions.insert(const_id, constant); } } if (enum_name != "") { ClassNode::Constant enum_constant; - enum_constant.identifier = enum_name; ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = enum_dict; + cn->datatype = _type_from_variant(cn->value); + enum_constant.expression = cn; - p_class->constant_expressions.push_back(enum_constant); + enum_constant.type = cn->datatype; + p_class->constant_expressions.insert(enum_name, enum_constant); } if (!_end_statement()) { @@ -4396,6 +4798,2711 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } +void GDScriptParser::_determine_inheritance(ClassNode *p_class) { + + if (p_class->extends_used) { + //do inheritance + String path = p_class->extends_file; + + Ref<GDScript> script; + StringName native; + ClassNode *base_class = NULL; + + if (path != "") { + //path (and optionally subclasses) + + if (path.is_rel_path()) { + + String base = base_path; + + if (base == "" || base.is_rel_path()) { + _set_error("Could not resolve relative path for parent class: " + path, p_class->line); + return; + } + path = base.plus_file(path).simplify_path(); + } + script = ResourceLoader::load(path); + if (script.is_null()) { + _set_error("Could not load base class: " + path, p_class->line); + return; + } + if (!script->is_valid()) { + + _set_error("Script not fully loaded (cyclic preload?): " + path, p_class->line); + return; + } + + if (p_class->extends_class.size()) { + + for (int i = 0; i < p_class->extends_class.size(); i++) { + + String sub = p_class->extends_class[i]; + if (script->get_subclasses().has(sub)) { + + Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing + script = subclass; + } else { + + _set_error("Could not find subclass: " + sub, p_class->line); + return; + } + } + } + + } else { + + if (p_class->extends_class.size() == 0) { + _set_error("Parser bug: undecidable inheritance.", p_class->line); + ERR_FAIL(); + } + //look around for the subclasses + + int extend_iter = 1; + String base = p_class->extends_class[0]; + ClassNode *p = p_class->owner; + Ref<GDScript> base_script; + + if (ScriptServer::is_global_class(base)) { + base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base)); + if (!base_script.is_valid()) { + _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line); + return; + } + p = NULL; + } + + while (p) { + + bool found = false; + + for (int i = 0; i < p->subclasses.size(); i++) { + if (p->subclasses[i]->name == base) { + ClassNode *test = p->subclasses[i]; + while (test) { + if (test == p_class) { + _set_error("Cyclic inheritance.", test->line); + return; + } + if (test->base_type.kind == DataType::CLASS) { + test = test->base_type.class_type; + } else { + break; + } + } + found = true; + if (extend_iter < p_class->extends_class.size()) { + // Keep looking at current classes if possible + base = p_class->extends_class[extend_iter++]; + p = p->subclasses[i]; + } else { + base_class = p->subclasses[i]; + } + break; + } + } + + if (base_class) break; + if (found) continue; + + if (p->constant_expressions.has(base)) { + if (!p->constant_expressions[base].expression->type == Node::TYPE_CONSTANT) { + _set_error("Could not resolve constant '" + base + "'.", p_class->line); + return; + } + const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression); + base_script = cn->value; + if (base_script.is_null()) { + _set_error("Constant is not a class: " + base, p_class->line); + return; + } + break; + } + + p = p->owner; + } + + if (base_script.is_valid()) { + + String ident = base; + + for (int i = extend_iter; i < p_class->extends_class.size(); i++) { + + String subclass = p_class->extends_class[i]; + + ident += ("." + subclass); + + if (base_script->get_subclasses().has(subclass)) { + + base_script = base_script->get_subclasses()[subclass]; + } else if (base_script->get_constants().has(subclass)) { + + Ref<GDScript> new_base_class = base_script->get_constants()[subclass]; + if (new_base_class.is_null()) { + _set_error("Constant is not a class: " + ident, p_class->line); + return; + } + base_script = new_base_class; + } else { + + _set_error("Could not find subclass: " + ident, p_class->line); + return; + } + } + + script = base_script; + + } else if (!base_class) { + + if (p_class->extends_class.size() > 1) { + + _set_error("Invalid inheritance (unknown class + subclasses)", p_class->line); + return; + } + //if not found, try engine classes + if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { + + _set_error("Unknown class: '" + base + "'", p_class->line); + return; + } + + native = base; + } + } + + if (base_class) { + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::CLASS; + p_class->base_type.class_type = base_class; + } else if (script.is_valid()) { + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::GDSCRIPT; + p_class->base_type.script_type = script; + p_class->base_type.native_type = script->get_instance_base_type(); + } else if (native != StringName()) { + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::NATIVE; + p_class->base_type.native_type = native; + } else { + _set_error("Could not determine inheritance", p_class->line); + return; + } + + } else { + // without extends, implicitly extend Reference + p_class->base_type.has_type = true; + p_class->base_type.kind = DataType::NATIVE; + p_class->base_type.native_type = "Reference"; + } + + // Recursively determine subclasses + for (int i = 0; i < p_class->subclasses.size(); i++) { + _determine_inheritance(p_class->subclasses[i]); + } +} + +String GDScriptParser::DataType::to_string() const { + if (!has_type) return "var"; + switch (kind) { + case BUILTIN: { + if (builtin_type == Variant::NIL) return "null"; + return Variant::get_type_name(builtin_type); + } break; + case NATIVE: { + if (is_meta_type) { + return "GDScriptNativeClass"; + } + return native_type.operator String(); + } break; + + case GDSCRIPT: { + Ref<GDScript> gds = script_type; + const String &gds_class = gds->get_script_class_name(); + if (!gds_class.empty()) { + return gds_class; + } + } // fallthrough + case SCRIPT: { + if (is_meta_type) { + return script_type->get_class_name().operator String(); + } + String name = script_type->get_name(); + if (name != String()) { + return name; + } + name = script_type->get_path().get_file(); + if (name != String()) { + return name; + } + return native_type.operator String(); + } break; + case CLASS: { + ERR_FAIL_COND_V(!class_type, String()); + if (is_meta_type) { + return "GDScript"; + } + if (class_type->name == StringName()) { + return "self"; + } + return class_type->name.operator String(); + } break; + } + + return "Unresolved"; +} + +bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { + tokenizer->advance(); + r_type.has_type = true; + + bool finished = false; + bool can_index = false; + String full_name; + + if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { + completion_cursor = StringName(); + completion_type = COMPLETION_TYPE_HINT; + completion_class = current_class; + completion_function = current_function; + completion_line = tokenizer->get_token_line(); + completion_argument = 0; + completion_block = current_block; + completion_found = true; + completion_ident_is_call = p_can_be_void; + tokenizer->advance(); + } + + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_PR_VOID: { + if (!p_can_be_void) { + return false; + } + r_type.kind = DataType::BUILTIN; + r_type.builtin_type = Variant::NIL; + } break; + case GDScriptTokenizer::TK_BUILT_IN_TYPE: { + r_type.builtin_type = tokenizer->get_token_type(); + if (tokenizer->get_token_type() == Variant::OBJECT) { + r_type.kind = DataType::NATIVE; + r_type.native_type = "Object"; + } else { + r_type.kind = DataType::BUILTIN; + } + } break; + case GDScriptTokenizer::TK_IDENTIFIER: { + r_type.native_type = tokenizer->get_token_identifier(); + if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) { + r_type.kind = DataType::NATIVE; + } else { + r_type.kind = DataType::UNRESOLVED; + can_index = true; + full_name = r_type.native_type; + } + } break; + default: { + return false; + } + } + + tokenizer->advance(); + + if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { + completion_cursor = r_type.native_type; + completion_type = COMPLETION_TYPE_HINT; + completion_class = current_class; + completion_function = current_function; + completion_line = tokenizer->get_token_line(); + completion_argument = 0; + completion_block = current_block; + completion_found = true; + completion_ident_is_call = p_can_be_void; + tokenizer->advance(); + } + + if (can_index) { + while (!finished) { + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_PERIOD: { + if (!can_index) { + _set_error("Unexpected '.'."); + return false; + } + can_index = false; + tokenizer->advance(); + } break; + case GDScriptTokenizer::TK_IDENTIFIER: { + if (can_index) { + _set_error("Unexpected identifier."); + return false; + } + + StringName id; + bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id); + if (id == StringName()) { + id = "@temp"; + } + + full_name += "." + id.operator String(); + can_index = true; + if (has_completion) { + completion_cursor = full_name; + } + } break; + default: { + finished = true; + } break; + } + } + + if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) { + _set_error("Expected subclass identifier."); + return false; + } + + r_type.native_type = full_name; + } + + return true; +} + +GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) { + if (!p_source.has_type) return p_source; + if (p_source.kind != DataType::UNRESOLVED) return p_source; + + Vector<String> full_name = p_source.native_type.operator String().split(".", false); + int name_part = 0; + + DataType result; + result.has_type = true; + + while (name_part < full_name.size()) { + + bool found = false; + StringName id = full_name[name_part]; + DataType base_type = result; + + ClassNode *p = NULL; + if (name_part == 0) { + if (ScriptServer::is_global_class(id)) { + String script_path = ScriptServer::get_global_class_path(id); + if (script_path == self_path) { + result.kind = DataType::CLASS; + result.class_type = current_class; + } else { + Ref<Script> script = ResourceLoader::load(script_path); + Ref<GDScript> gds = script; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + } else if (script.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = script; + } else { + _set_error("Class '" + id + "' was found in global scope but its script could not be loaded.", p_line); + return DataType(); + } + } + name_part++; + continue; + } else { + p = current_class; + } + } else if (base_type.kind == DataType::CLASS) { + p = base_type.class_type; + } + while (p) { + if (p->constant_expressions.has(id)) { + if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) { + _set_error("Parser bug: unresolved constant.", p_line); + ERR_FAIL_V(result); + } + const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression); + Ref<GDScript> gds = cn->value; + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + found = true; + } else { + Ref<Script> scr = cn->value; + if (scr.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = scr; + found = true; + } + } + break; + } + + // Inner classes + ClassNode *outer_class = p; + while (outer_class) { + for (int i = 0; i < outer_class->subclasses.size(); i++) { + if (outer_class->subclasses[i] == p) { + continue; + } + if (outer_class->subclasses[i]->name == id) { + found = true; + result.kind = DataType::CLASS; + result.class_type = outer_class->subclasses[i]; + break; + } + } + if (found) { + break; + } + outer_class = outer_class->owner; + } + + if (!found && p->base_type.kind == DataType::CLASS) { + p = p->base_type.class_type; + } else { + base_type = p->base_type; + break; + } + } + + // Still look for class constants in parent script + if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) { + Ref<Script> scr = base_type.script_type; + ERR_FAIL_COND_V(scr.is_null(), result); + Map<StringName, Variant> constants; + scr->get_constants(&constants); + + if (constants.has(id)) { + Ref<GDScript> gds = constants[id]; + + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + result.script_type = gds; + found = true; + } else { + Ref<Script> scr = constants[id]; + if (scr.is_valid()) { + result.kind = DataType::SCRIPT; + result.script_type = scr; + found = true; + } + } + } + } + + if (!found && !for_completion) { + String base; + if (name_part == 0) { + base = "self"; + } else { + base = result.to_string(); + } + _set_error("Identifier '" + String(id) + "' is not a valid type (not a script or class), or could not be found on base '" + + base + "'.", + p_line); + return DataType(); + } + + name_part++; + } + + return result; +} + +GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const { + DataType result; + result.has_type = true; + result.is_constant = true; + result.kind = DataType::BUILTIN; + result.builtin_type = p_value.get_type(); + + if (result.builtin_type == Variant::OBJECT) { + Object *obj = p_value.operator Object *(); + if (!obj) { + return DataType(); + } + result.native_type = obj->get_class_name(); + Ref<Script> scr = p_value; + if (scr.is_valid()) { + result.is_meta_type = true; + } else { + result.is_meta_type = false; + scr = obj->get_script(); + } + if (scr.is_valid()) { + result.script_type = scr; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = DataType::GDSCRIPT; + } else { + result.kind = DataType::SCRIPT; + } + result.native_type = scr->get_instance_base_type(); + } else { + result.kind = DataType::NATIVE; + } + } + + return result; +} + +GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const { + DataType ret; + if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { + // Variant + return ret; + } + ret.has_type = true; + ret.builtin_type = p_property.type; + if (p_property.type == Variant::OBJECT) { + ret.kind = DataType::NATIVE; + ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } else { + ret.kind = DataType::BUILTIN; + } + return ret; +} + +GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const { + DataType result; + if (!p_gdtype.has_type) { + return result; + } + + result.has_type = true; + result.builtin_type = p_gdtype.builtin_type; + result.native_type = p_gdtype.native_type; + result.script_type = p_gdtype.script_type; + + switch (p_gdtype.kind) { + case GDScriptDataType::BUILTIN: { + result.kind = DataType::BUILTIN; + } break; + case GDScriptDataType::NATIVE: { + result.kind = DataType::NATIVE; + } break; + case GDScriptDataType::GDSCRIPT: { + result.kind = DataType::GDSCRIPT; + } break; + case GDScriptDataType::SCRIPT: { + result.kind = DataType::SCRIPT; + } break; + } + return result; +} + +GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const { + if (!p_a.has_type || !p_b.has_type) { + r_valid = true; + return DataType(); + } + + Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT; + Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT; + + Variant a; + REF a_ref; + if (a_type == Variant::OBJECT) { + a_ref.instance(); + a = a_ref; + } else { + Variant::CallError err; + a = Variant::construct(a_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + r_valid = false; + return DataType(); + } + } + Variant b; + REF b_ref; + if (b_type == Variant::OBJECT) { + b_ref.instance(); + b = b_ref; + } else { + Variant::CallError err; + b = Variant::construct(b_type, NULL, 0, err); + if (err.error != Variant::CallError::CALL_OK) { + r_valid = false; + return DataType(); + } + } + + // Avoid division by zero + if (a_type == Variant::INT || a_type == Variant::REAL) { + Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid); + } + if (b_type == Variant::INT || b_type == Variant::REAL) { + Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid); + } + + Variant ret; + Variant::evaluate(p_op, a, b, ret, r_valid); + + if (r_valid) { + return _type_from_variant(ret); + } + + return DataType(); +} + +Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const { + switch (p_op) { + case OperatorNode::OP_NEG: { + return Variant::OP_NEGATE; + } break; + case OperatorNode::OP_POS: { + return Variant::OP_POSITIVE; + } break; + case OperatorNode::OP_NOT: { + return Variant::OP_NOT; + } break; + case OperatorNode::OP_BIT_INVERT: { + return Variant::OP_BIT_NEGATE; + } break; + case OperatorNode::OP_IN: { + return Variant::OP_IN; + } break; + case OperatorNode::OP_EQUAL: { + return Variant::OP_EQUAL; + } break; + case OperatorNode::OP_NOT_EQUAL: { + return Variant::OP_NOT_EQUAL; + } break; + case OperatorNode::OP_LESS: { + return Variant::OP_LESS; + } break; + case OperatorNode::OP_LESS_EQUAL: { + return Variant::OP_LESS_EQUAL; + } break; + case OperatorNode::OP_GREATER: { + return Variant::OP_GREATER; + } break; + case OperatorNode::OP_GREATER_EQUAL: { + return Variant::OP_GREATER_EQUAL; + } break; + case OperatorNode::OP_AND: { + return Variant::OP_AND; + } break; + case OperatorNode::OP_OR: { + return Variant::OP_OR; + } break; + case OperatorNode::OP_ASSIGN_ADD: + case OperatorNode::OP_ADD: { + return Variant::OP_ADD; + } break; + case OperatorNode::OP_ASSIGN_SUB: + case OperatorNode::OP_SUB: { + return Variant::OP_SUBTRACT; + } break; + case OperatorNode::OP_ASSIGN_MUL: + case OperatorNode::OP_MUL: { + return Variant::OP_MULTIPLY; + } break; + case OperatorNode::OP_ASSIGN_DIV: + case OperatorNode::OP_DIV: { + return Variant::OP_DIVIDE; + } break; + case OperatorNode::OP_ASSIGN_MOD: + case OperatorNode::OP_MOD: { + return Variant::OP_MODULE; + } break; + case OperatorNode::OP_ASSIGN_BIT_AND: + case OperatorNode::OP_BIT_AND: { + return Variant::OP_BIT_AND; + } break; + case OperatorNode::OP_ASSIGN_BIT_OR: + case OperatorNode::OP_BIT_OR: { + return Variant::OP_BIT_OR; + } break; + case OperatorNode::OP_ASSIGN_BIT_XOR: + case OperatorNode::OP_BIT_XOR: { + return Variant::OP_BIT_XOR; + } break; + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: + case OperatorNode::OP_SHIFT_LEFT: { + return Variant::OP_SHIFT_LEFT; + } + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + case OperatorNode::OP_SHIFT_RIGHT: { + return Variant::OP_SHIFT_RIGHT; + } + default: { + return Variant::OP_MAX; + } break; + } +} + +bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const { + // Ignore for completion + if (!check_types || for_completion) { + return true; + } + // Can't test if not all have type + if (!p_container.has_type || !p_expression.has_type) { + return true; + } + + // Should never get here unresolved + ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false); + ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false); + + if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) { + bool valid = p_container.builtin_type == p_expression.builtin_type; + if (p_allow_implicit_conversion) { + valid = valid || (p_container.builtin_type == Variant::INT && p_expression.builtin_type == Variant::REAL); + valid = valid || (p_container.builtin_type == Variant::REAL && p_expression.builtin_type == Variant::INT); + valid = valid || (p_container.builtin_type == Variant::STRING && p_expression.builtin_type == Variant::NODE_PATH); + valid = valid || (p_container.builtin_type == Variant::NODE_PATH && p_expression.builtin_type == Variant::STRING); + valid = valid || (p_container.builtin_type == Variant::BOOL && p_expression.builtin_type == Variant::REAL); + valid = valid || (p_container.builtin_type == Variant::BOOL && p_expression.builtin_type == Variant::INT); + valid = valid || (p_container.builtin_type == Variant::INT && p_expression.builtin_type == Variant::BOOL); + valid = valid || (p_container.builtin_type == Variant::REAL && p_expression.builtin_type == Variant::BOOL); + } + return valid; + } + + if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) { + // Can't mix built-ins with objects + return false; + } + + // From now on everything is objects, check polymorphism + // The container must be the same class or a superclass of the expression + + if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) { + // Null can be assigned to object types + return true; + } + + StringName expr_native; + Ref<Script> expr_script; + ClassNode *expr_class = NULL; + + switch (p_expression.kind) { + case DataType::NATIVE: { + if (p_container.kind != DataType::NATIVE) { + // Non-native type can't be a superclass of a native type + return false; + } + if (p_expression.is_meta_type) { + expr_native = GDScriptNativeClass::get_class_static(); + } else { + expr_native = p_expression.native_type; + } + } break; + case DataType::SCRIPT: + case DataType::GDSCRIPT: { + if (p_container.kind == DataType::CLASS) { + // This cannot be resolved without cyclic dependencies, so just bail out + return false; + } + if (p_expression.is_meta_type) { + expr_native = p_expression.script_type->get_class_name(); + } else { + expr_script = p_expression.script_type; + expr_native = expr_script->get_instance_base_type(); + } + } break; + case DataType::CLASS: { + if (p_expression.is_meta_type) { + expr_native = GDScript::get_class_static(); + } else { + expr_class = p_expression.class_type; + ClassNode *base = expr_class; + while (base->base_type.kind == DataType::CLASS) { + base = base->base_type.class_type; + } + expr_native = base->base_type.native_type; + expr_script = base->base_type.script_type; + } + } + } + + switch (p_container.kind) { + case DataType::NATIVE: { + if (p_container.is_meta_type) { + return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static()); + } else { + return ClassDB::is_parent_class(expr_native, p_container.native_type); + } + } break; + case DataType::SCRIPT: + case DataType::GDSCRIPT: { + if (p_container.is_meta_type) { + return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); + } + if (expr_class == head && p_container.script_type->get_path() == self_path) { + // Special case: container is self script and expression is self + return true; + } + while (expr_script.is_valid()) { + if (expr_script == p_container.script_type) { + return true; + } + expr_script = expr_script->get_base_script(); + } + return false; + } break; + case DataType::CLASS: { + if (p_container.is_meta_type) { + return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); + } + if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) { + // Special case: container is self and expression is self script + return true; + } + while (expr_class) { + if (expr_class == p_container.class_type) { + return true; + } + expr_class = expr_class->base_type.class_type; + } + return false; + } + } + + return false; +} + +GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { + if (p_node->get_datatype().has_type) { + return p_node->get_datatype(); + } + + DataType node_type; + + switch (p_node->type) { + case Node::TYPE_CONSTANT: { + node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value); + } break; + case Node::TYPE_ARRAY: { + node_type.has_type = true; + node_type.kind = DataType::BUILTIN; + node_type.builtin_type = Variant::ARRAY; + } break; + case Node::TYPE_DICTIONARY: { + node_type.has_type = true; + node_type.kind = DataType::BUILTIN; + node_type.builtin_type = Variant::DICTIONARY; + } break; + case Node::TYPE_SELF: { + node_type.has_type = true; + node_type.kind = DataType::CLASS; + node_type.class_type = current_class; + } break; + case Node::TYPE_IDENTIFIER: { + IdentifierNode *id = static_cast<IdentifierNode *>(p_node); + if (id->declared_block) { + node_type = id->declared_block->variables[id->name]->get_datatype(); + } else if (id->name == "#match_value") { + // It's a special id just for the match statetement, ignore + break; + } else if (current_function && current_function->arguments.find(id->name) >= 0) { + int idx = current_function->arguments.find(id->name); + node_type = current_function->argument_types[idx]; + } else { + node_type = _reduce_identifier_type(NULL, id->name, id->line); + } + } break; + case Node::TYPE_CAST: { + CastNode *cn = static_cast<CastNode *>(p_node); + + DataType source_type = _reduce_node_type(cn->source_node); + cn->cast_type = _resolve_type(cn->cast_type, cn->line); + if (source_type.has_type) { + + bool valid = false; + if (check_types) { + if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) { + valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type); + } + if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) { + valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type); + } + + if (!valid) { + _set_error("Invalid cast. Cannot convert from '" + source_type.to_string() + + "' to '" + cn->cast_type.to_string() + "'.", + cn->line); + return DataType(); + } + } + } else { + _mark_line_as_unsafe(cn->line); + } + + node_type = cn->cast_type; + + } break; + case Node::TYPE_OPERATOR: { + OperatorNode *op = static_cast<OperatorNode *>(p_node); + + switch (op->op) { + case OperatorNode::OP_CALL: + case OperatorNode::OP_PARENT_CALL: { + node_type = _reduce_function_call_type(op); + } break; + case OperatorNode::OP_YIELD: { + if (op->arguments.size() == 2) { + DataType base_type = _reduce_node_type(op->arguments[0]); + DataType signal_type = _reduce_node_type(op->arguments[1]); + // TODO: Check if signal exists when it's a constant + if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) { + _set_error("First argument of 'yield()' must be an object.", op->line); + return DataType(); + } + if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) { + _set_error("Second argument of 'yield()' must be a string.", op->line); + return DataType(); + } + } + // yield can return anything + node_type.has_type = false; + } break; + case OperatorNode::OP_IS: { + + if (op->arguments.size() != 2) { + _set_error("Parser bug: binary operation without 2 arguments.", op->line); + ERR_FAIL_V(DataType()); + } + + DataType value_type = _reduce_node_type(op->arguments[0]); + DataType type_type = _reduce_node_type(op->arguments[1]); + + if (check_types && type_type.has_type) { + if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) { + _set_error("Invalid 'is' test: right operand is not a type (not a native type nor a script).", op->line); + return DataType(); + } + type_type.is_meta_type = false; // Test the actual type + if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) { + // TODO: Make this a warning? + _set_error("A value of type '" + value_type.to_string() + "' will never be an instance of '" + type_type.to_string() + "'.", op->line); + return DataType(); + } + } + + node_type.has_type = true; + node_type.is_constant = true; + node_type.is_meta_type = false; + node_type.kind = DataType::BUILTIN; + node_type.builtin_type = Variant::BOOL; + } break; + // Unary operators + case OperatorNode::OP_NEG: + case OperatorNode::OP_POS: + case OperatorNode::OP_NOT: + case OperatorNode::OP_BIT_INVERT: { + + DataType argument_type = _reduce_node_type(op->arguments[0]); + if (!argument_type.has_type) { + break; + } + + Variant::Operator var_op = _get_variant_operation(op->op); + bool valid = false; + node_type = _get_operation_type(var_op, argument_type, argument_type, valid); + + if (check_types && !valid) { + _set_error("Invalid operand type ('" + argument_type.to_string() + + "') to unary operator '" + Variant::get_operator_name(var_op) + "'.", + op->line, op->column); + return DataType(); + } + + } break; + // Binary operators + case OperatorNode::OP_IN: + case OperatorNode::OP_EQUAL: + case OperatorNode::OP_NOT_EQUAL: + case OperatorNode::OP_LESS: + case OperatorNode::OP_LESS_EQUAL: + case OperatorNode::OP_GREATER: + case OperatorNode::OP_GREATER_EQUAL: + case OperatorNode::OP_AND: + case OperatorNode::OP_OR: + case OperatorNode::OP_ADD: + case OperatorNode::OP_SUB: + case OperatorNode::OP_MUL: + case OperatorNode::OP_DIV: + case OperatorNode::OP_MOD: + case OperatorNode::OP_SHIFT_LEFT: + case OperatorNode::OP_SHIFT_RIGHT: + case OperatorNode::OP_BIT_AND: + case OperatorNode::OP_BIT_OR: + case OperatorNode::OP_BIT_XOR: { + + if (op->arguments.size() != 2) { + _set_error("Parser bug: binary operation without 2 arguments.", op->line); + ERR_FAIL_V(DataType()); + } + + DataType argument_a_type = _reduce_node_type(op->arguments[0]); + DataType argument_b_type = _reduce_node_type(op->arguments[1]); + if (!argument_a_type.has_type || !argument_b_type.has_type) { + _mark_line_as_unsafe(op->line); + break; + } + + Variant::Operator var_op = _get_variant_operation(op->op); + bool valid = false; + node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid); + + if (check_types && !valid) { + _set_error("Invalid operand types ('" + argument_a_type.to_string() + "' and '" + + argument_b_type.to_string() + "') to operator '" + Variant::get_operator_name(var_op) + "'.", + op->line, op->column); + return DataType(); + } + + } break; + // Ternary operators + case OperatorNode::OP_TERNARY_IF: { + if (op->arguments.size() != 3) { + _set_error("Parser bug: ternary operation without 3 arguments"); + ERR_FAIL_V(DataType()); + } + + DataType true_type = _reduce_node_type(op->arguments[1]); + DataType false_type = _reduce_node_type(op->arguments[2]); + + // If types are equal, then the expression is of the same type + // If they are compatible, return the broader type + if (true_type == false_type || _is_type_compatible(true_type, false_type)) { + node_type = true_type; + } else if (_is_type_compatible(false_type, true_type)) { + node_type = false_type; + } + + // TODO: Warn if types aren't compatible + + } break; + // Assignment should never happen within an expression + case OperatorNode::OP_ASSIGN: + case OperatorNode::OP_ASSIGN_ADD: + case OperatorNode::OP_ASSIGN_SUB: + case OperatorNode::OP_ASSIGN_MUL: + case OperatorNode::OP_ASSIGN_DIV: + case OperatorNode::OP_ASSIGN_MOD: + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + case OperatorNode::OP_ASSIGN_BIT_AND: + case OperatorNode::OP_ASSIGN_BIT_OR: + case OperatorNode::OP_ASSIGN_BIT_XOR: + case OperatorNode::OP_INIT_ASSIGN: { + + _set_error("Assignment inside expression is not allowed (parser bug?).", op->line); + return DataType(); + + } break; + case OperatorNode::OP_INDEX_NAMED: { + if (op->arguments.size() != 2) { + _set_error("Parser bug: named index with invalid arguments.", op->line); + ERR_FAIL_V(DataType()); + } + if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) { + _set_error("Parser bug: named index without identifier argument.", op->line); + ERR_FAIL_V(DataType()); + } + + DataType base_type = _reduce_node_type(op->arguments[0]); + IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]); + + if (base_type.has_type) { + if (check_types && base_type.kind == DataType::BUILTIN) { + // Variant type, just test if it's possible + DataType result; + switch (base_type.builtin_type) { + case Variant::NIL: + case Variant::DICTIONARY: { + result.has_type = false; + } break; + default: { + Variant::CallError err; + Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + bool valid = false; + Variant res = temp.get(member_id->name.operator String(), &valid); + + if (valid) { + result = _type_from_variant(res); + } else if (check_types) { + _set_error("Can't get index '" + String(member_id->name.operator String()) + "' on base '" + + base_type.to_string() + "'.", + op->line); + return DataType(); + } + } break; + } + result.is_constant = false; + node_type = result; + } else { + node_type = _reduce_identifier_type(&base_type, member_id->name, op->line); + } + } else { + _mark_line_as_unsafe(op->line); + } + if (error_set) { + return DataType(); + } + } break; + case OperatorNode::OP_INDEX: { + + if (op->arguments[1]->type == Node::TYPE_CONSTANT) { + ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); + if (cn->value.get_type() == Variant::STRING) { + // Treat this as named indexing + + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->name = cn->value.operator StringName(); + + op->op = OperatorNode::OP_INDEX_NAMED; + op->arguments[1] = id; + + return _reduce_node_type(op); + } + } + + DataType base_type = _reduce_node_type(op->arguments[0]); + DataType index_type = _reduce_node_type(op->arguments[1]); + + if (!base_type.has_type) { + _mark_line_as_unsafe(op->line); + break; + } + + if (check_types && index_type.has_type) { + if (base_type.kind == DataType::BUILTIN) { + // Check if indexing is valid + bool error = index_type.kind != DataType::BUILTIN; + if (!error) { + switch (base_type.builtin_type) { + // Expect int or real as index + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_COLOR_ARRAY: + case Variant::POOL_INT_ARRAY: + case Variant::POOL_REAL_ARRAY: + case Variant::POOL_STRING_ARRAY: + case Variant::POOL_VECTOR2_ARRAY: + case Variant::POOL_VECTOR3_ARRAY: + case Variant::ARRAY: + case Variant::STRING: { + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL; + } break; + // Expect String only + case Variant::RECT2: + case Variant::PLANE: + case Variant::QUAT: + case Variant::AABB: + case Variant::OBJECT: { + error = index_type.builtin_type != Variant::STRING; + } break; + // Expect String or number + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::TRANSFORM2D: + case Variant::BASIS: + case Variant::TRANSFORM: { + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL && + index_type.builtin_type != Variant::STRING; + } break; + // Expect String or int + case Variant::COLOR: { + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + } break; + } + } + if (error) { + _set_error("Invalid index type (" + index_type.to_string() + ") for base '" + base_type.to_string() + "'.", + op->line); + return DataType(); + } + + if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) { + ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); + // Index is a constant, just try it if possible + switch (base_type.builtin_type) { + // Arrays/string have variable indexing, can't test directly + case Variant::STRING: + case Variant::ARRAY: + case Variant::DICTIONARY: + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_COLOR_ARRAY: + case Variant::POOL_INT_ARRAY: + case Variant::POOL_REAL_ARRAY: + case Variant::POOL_STRING_ARRAY: + case Variant::POOL_VECTOR2_ARRAY: + case Variant::POOL_VECTOR3_ARRAY: { + break; + } + default: { + Variant::CallError err; + Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + bool valid = false; + Variant res = temp.get(cn->value, &valid); + + if (valid) { + node_type = _type_from_variant(res); + node_type.is_constant = false; + } else if (check_types) { + _set_error("Can't get index '" + String(cn->value) + "' on base '" + + base_type.to_string() + "'.", + op->line); + return DataType(); + } + } break; + } + } else { + _mark_line_as_unsafe(op->line); + } + } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) { + _set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line); + return DataType(); + } + } + if (check_types && !node_type.has_type) { + // Can infer indexing type for some variant types + DataType result; + result.has_type = true; + result.kind = DataType::BUILTIN; + switch (base_type.builtin_type) { + // Can't index at all + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::REAL: + case Variant::NODE_PATH: + case Variant::_RID: { + _set_error("Can't index on a value of type '" + base_type.to_string() + "'.", op->line); + return DataType(); + } break; + // Return int + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_INT_ARRAY: { + result.builtin_type = Variant::INT; + } break; + // Return real + case Variant::POOL_REAL_ARRAY: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUAT: { + result.builtin_type = Variant::REAL; + } break; + // Return color + case Variant::POOL_COLOR_ARRAY: { + result.builtin_type = Variant::COLOR; + } break; + // Return string + case Variant::POOL_STRING_ARRAY: + case Variant::STRING: { + result.builtin_type = Variant::STRING; + } break; + // Return Vector2 + case Variant::POOL_VECTOR2_ARRAY: + case Variant::TRANSFORM2D: + case Variant::RECT2: { + result.builtin_type = Variant::VECTOR2; + } break; + // Return Vector3 + case Variant::POOL_VECTOR3_ARRAY: + case Variant::AABB: + case Variant::BASIS: { + result.builtin_type = Variant::VECTOR3; + } break; + // Depends on the index + case Variant::TRANSFORM: + case Variant::PLANE: + case Variant::COLOR: + default: { + result.has_type = false; + } break; + } + node_type = result; + } + } break; + default: { + _set_error("Parser bug: unhandled operation.", op->line); + ERR_FAIL_V(DataType()); + } + } + } break; + } + + p_node->set_datatype(_resolve_type(node_type, p_node->line)); + return node_type; +} + +bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const { + + r_static = false; + r_default_arg_count = 0; + + DataType original_type = p_base_type; + ClassNode *base = NULL; + FunctionNode *callee = NULL; + + if (p_base_type.kind == DataType::CLASS) { + base = p_base_type.class_type; + } + + // Look up the current file (parse tree) + while (!callee && base) { + for (int i = 0; i < base->static_functions.size(); i++) { + FunctionNode *func = base->static_functions[i]; + if (p_function == func->name) { + r_static = true; + callee = func; + break; + } + } + if (!callee && !p_base_type.is_meta_type) { + for (int i = 0; i < base->functions.size(); i++) { + FunctionNode *func = base->functions[i]; + if (p_function == func->name) { + callee = func; + break; + } + } + } + p_base_type = base->base_type; + if (p_base_type.kind == DataType::CLASS) { + base = p_base_type.class_type; + } else { + break; + } + } + + if (callee) { + r_return_type = callee->get_datatype(); + for (int i = 0; i < callee->argument_types.size(); i++) { + r_arg_types.push_back(callee->argument_types[i]); + } + r_default_arg_count = callee->default_values.size(); + return true; + } + + // Nothing in current file, check parent script + Ref<GDScript> base_gdscript; + Ref<Script> base_script; + StringName native; + if (p_base_type.kind == DataType::GDSCRIPT) { + base_gdscript = p_base_type.script_type; + } else if (p_base_type.kind == DataType::SCRIPT) { + base_script = p_base_type.script_type; + } else if (p_base_type.kind == DataType::NATIVE) { + native = p_base_type.native_type; + } + + while (base_gdscript.is_valid()) { + native = base_gdscript->get_instance_base_type(); + + Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions(); + + if (funcs.has(p_function)) { + GDScriptFunction *f = funcs[p_function]; + r_static = f->is_static(); + r_default_arg_count = f->get_default_argument_count(); + r_return_type = _type_from_gdtype(f->get_return_type()); + for (int i = 0; i < f->get_argument_count(); i++) { + r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i))); + } + return true; + } + + base_gdscript = base_gdscript->get_base_script(); + } + + while (base_script.is_valid()) { + native = base_script->get_instance_base_type(); + MethodInfo mi = base_script->get_method_info(p_function); + + if (!(mi == MethodInfo())) { + r_return_type = _type_from_property(mi.return_val, false); + r_default_arg_count = mi.default_arguments.size(); + for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { + r_arg_types.push_back(_type_from_property(E->get())); + } + return true; + } + base_script = base_script->get_base_script(); + } + +#ifdef DEBUG_METHODS_ENABLED + + // Only native remains + if (!ClassDB::class_exists(native)) { + native = "_" + native.operator String(); + } + if (!ClassDB::class_exists(native)) { + if (!check_types) return false; + ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found."); + ERR_FAIL_V(false); + } + + MethodBind *method = ClassDB::get_method(native, p_function); + + if (!method) { + // Try virtual methods + List<MethodInfo> virtuals; + ClassDB::get_virtual_methods(native, &virtuals); + + for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { + const MethodInfo &mi = E->get(); + if (mi.name == p_function) { + r_default_arg_count = mi.default_arguments.size(); + for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { + r_arg_types.push_back(_type_from_property(pi->get())); + } + r_return_type = _type_from_property(mi.return_val, false); + r_vararg = mi.flags & METHOD_FLAG_VARARG; + return true; + } + } + + // If the base is a script, it might be trying to access members of the Script class itself + if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) { + method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function); + + if (method) { + r_static = true; + } else { + // Try virtual methods of the script type + virtuals.clear(); + ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals); + for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { + const MethodInfo &mi = E->get(); + if (mi.name == p_function) { + r_default_arg_count = mi.default_arguments.size(); + for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { + r_arg_types.push_back(_type_from_property(pi->get())); + } + r_return_type = _type_from_property(mi.return_val, false); + r_static = true; + r_vararg = mi.flags & METHOD_FLAG_VARARG; + return true; + } + } + return false; + } + } else { + return false; + } + } + + r_default_arg_count = method->get_default_argument_count(); + r_return_type = _type_from_property(method->get_return_info(), false); + r_vararg = method->is_vararg(); + + for (int i = 0; i < method->get_argument_count(); i++) { + r_arg_types.push_back(_type_from_property(method->get_argument_info(i))); + } + return true; +#else + return false; +#endif +} + +GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) { + if (p_call->arguments.size() < 1) { + _set_error("Parser bug: function call without enough arguments.", p_call->line); + ERR_FAIL_V(DataType()); + } + + DataType return_type; + List<DataType> arg_types; + int default_args_count = 0; + int arg_count = p_call->arguments.size(); + String callee_name; + bool is_vararg = false; + + switch (p_call->arguments[0]->type) { + case GDScriptParser::Node::TYPE_TYPE: { + // Built-in constructor, special case + TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]); + + Vector<DataType> par_types; + par_types.resize(p_call->arguments.size() - 1); + for (int i = 1; i < p_call->arguments.size(); i++) { + par_types[i - 1] = _reduce_node_type(p_call->arguments[i]); + } + + if (error_set) return DataType(); + + bool match = false; + List<MethodInfo> constructors; + Variant::get_constructor_list(tn->vtype, &constructors); + PropertyInfo return_type; + + for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { + MethodInfo &mi = E->get(); + + if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) { + continue; + } + if (p_call->arguments.size() - 1 > mi.arguments.size()) { + continue; + } + + bool types_match = true; + for (int i = 0; i < par_types.size(); i++) { + DataType arg_type; + if (mi.arguments[i].type != Variant::NIL) { + arg_type.has_type = true; + arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN; + arg_type.builtin_type = mi.arguments[i].type; + arg_type.native_type = mi.arguments[i].class_name; + } + + if (!_is_type_compatible(arg_type, par_types[i], true)) { + types_match = false; + break; + } + } + + if (types_match) { + match = true; + return_type = mi.return_val; + break; + } + } + + if (match) { + return _type_from_property(return_type, false); + } else if (check_types) { + String err = "No constructor of '"; + err += Variant::get_type_name(tn->vtype); + err += "' matches the signature '"; + err += Variant::get_type_name(tn->vtype) + "("; + for (int i = 0; i < par_types.size(); i++) { + if (i > 0) err += ", "; + err += par_types[i].to_string(); + } + err += ")'."; + _set_error(err, p_call->line, p_call->column); + return DataType(); + } + return DataType(); + } break; + case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { + BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]); + MethodInfo mi = GDScriptFunctions::get_info(func->function); + + return_type = _type_from_property(mi.return_val, false); + + // Check arguments + + is_vararg = mi.flags & METHOD_FLAG_VARARG; + + default_args_count = mi.default_arguments.size(); + callee_name = mi.name; + arg_count -= 1; + + // Check each argument type + for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { + arg_types.push_back(_type_from_property(E->get())); + } + } break; + default: { + if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) { + _set_error("Parser bug: self method call without enough arguments.", p_call->line); + ERR_FAIL_V(DataType()); + } + + int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0; + + if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) { + _set_error("Parser bug: invalid function call argument.", p_call->line); + ERR_FAIL_V(DataType()); + } + + IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]); + callee_name = func_id->name; + arg_count -= 1 + arg_id; + + DataType base_type; + if (p_call->op == OperatorNode::OP_PARENT_CALL) { + base_type = current_class->base_type; + } else { + base_type = _reduce_node_type(p_call->arguments[0]); + } + + if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) { + _mark_line_as_unsafe(p_call->line); + return DataType(); + } + + if (base_type.kind == DataType::BUILTIN) { + Variant::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); + + if (check_types) { + if (!tmp.has_method(callee_name)) { + _set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line); + return DataType(); + } + + default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size(); + const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name); + + for (int i = 0; i < var_arg_types.size(); i++) { + DataType argtype; + if (var_arg_types[i] != Variant::NIL) { + argtype.has_type = true; + argtype.kind = DataType::BUILTIN; + argtype.builtin_type = var_arg_types[i]; + } + arg_types.push_back(argtype); + } + } + + return_type.has_type = true; + return_type.kind = DataType::BUILTIN; + return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name); + break; + } + + DataType original_type = base_type; + bool is_initializer = callee_name == "new"; + bool is_static = false; + bool valid = false; + + if (is_initializer && original_type.is_meta_type) { + // Try to check it as initializer + base_type = original_type; + callee_name = "_init"; + base_type.is_meta_type = false; + + valid = _get_function_signature(base_type, callee_name, return_type, arg_types, + default_args_count, is_static, is_vararg); + + if (valid) { + return_type = original_type; + return_type.is_meta_type = false; + } + } + + if (!valid) { + base_type = original_type; + return_type = DataType(); + valid = _get_function_signature(base_type, callee_name, return_type, arg_types, + default_args_count, is_static, is_vararg); + } + + if (!valid) { +#ifdef DEBUG_ENABLED + if (p_call->arguments[0]->type == Node::TYPE_SELF) { + _set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line); + return DataType(); + } + _mark_line_as_unsafe(p_call->line); +#endif + return DataType(); + } + +#ifdef DEBUG_ENABLED + if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) { + if (current_function && current_function->_static && p_call->arguments[0]->type == Node::TYPE_SELF) { + _set_error("Can't call non-static function from a static function.", p_call->line); + return DataType(); + } + } + + if (check_types && !is_static && !is_initializer && base_type.is_meta_type) { + _set_error("Non-static function '" + String(callee_name) + "' can only be called from an instance.", p_call->line); + return DataType(); + } +#endif + } break; + } + + if (!check_types) { + return return_type; + } + + if (arg_count < arg_types.size() - default_args_count) { + _set_error("Too few arguments for '" + callee_name + "()' call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line); + return return_type; + } + if (!is_vararg && arg_count > arg_types.size()) { + _set_error("Too many arguments for '" + callee_name + "()' call. Expected at most " + itos(arg_types.size()) + ".", p_call->line); + return return_type; + } + + int arg_diff = p_call->arguments.size() - arg_count; + for (int i = arg_diff; i < p_call->arguments.size(); i++) { + DataType par_type = _reduce_node_type(p_call->arguments[i]); + + if ((i - arg_diff) >= arg_types.size()) { + continue; + } + + if (!par_type.has_type) { + _mark_line_as_unsafe(p_call->line); + } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) { + // Supertypes are acceptable for dynamic compliance + if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) { + _set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" + + par_type.to_string() + ") doesn't match the function argument's type (" + + arg_types[i - arg_diff].to_string() + ").", + p_call->line); + return DataType(); + } else { + _mark_line_as_unsafe(p_call->line); + } + } + } + + return return_type; +} + +bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const { + DataType base_type = p_base_type; + + // Check classes in current file + ClassNode *base = NULL; + if (base_type.kind == DataType::CLASS) { + base = base_type.class_type; + } + + while (base) { + if (base->constant_expressions.has(p_member)) { + r_member_type = base->constant_expressions[p_member].expression->get_datatype(); + return true; + } + + if (!base_type.is_meta_type) { + for (int i = 0; i < base->variables.size(); i++) { + ClassNode::Member m = base->variables[i]; + if (m.identifier == p_member) { + r_member_type = m.data_type; + return true; + } + } + } else { + for (int i = 0; i < base->subclasses.size(); i++) { + ClassNode *c = base->subclasses[i]; + if (c->name == p_member) { + DataType class_type; + class_type.has_type = true; + class_type.is_constant = true; + class_type.is_meta_type = true; + class_type.kind = DataType::CLASS; + class_type.class_type = c; + r_member_type = class_type; + return true; + } + } + } + + base_type = base->base_type; + if (base_type.kind == DataType::CLASS) { + base = base_type.class_type; + } else { + break; + } + } + + Ref<GDScript> gds; + if (base_type.kind == DataType::GDSCRIPT) { + gds = base_type.script_type; + } + + Ref<Script> scr; + if (base_type.kind == DataType::SCRIPT) { + scr = base_type.script_type; + } + + StringName native; + if (base_type.kind == DataType::NATIVE) { + native = base_type.native_type; + } + + // Check GDScripts + while (gds.is_valid()) { + if (gds->get_constants().has(p_member)) { + Variant c = gds->get_constants()[p_member]; + r_member_type = _type_from_variant(c); + return true; + } + + if (!base_type.is_meta_type) { + if (gds->get_members().has(p_member)) { + r_member_type = _type_from_gdtype(gds->get_member_type(p_member)); + return true; + } + } + + native = gds->get_instance_base_type(); + if (gds->get_base_script().is_valid()) { + gds = gds->get_base_script(); + scr = gds->get_base_script(); + bool is_meta = base_type.is_meta_type; + base_type = _type_from_variant(scr.operator Variant()); + base_type.is_meta_type = is_meta; + } else { + break; + } + } + + // Check other script types + while (scr.is_valid()) { + Map<StringName, Variant> constants; + scr->get_constants(&constants); + if (constants.has(p_member)) { + r_member_type = _type_from_variant(constants[p_member]); + return true; + } + + List<PropertyInfo> properties; + scr->get_script_property_list(&properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_member) { + r_member_type = _type_from_property(E->get()); + return true; + } + } + + base_type = _type_from_variant(scr.operator Variant()); + native = scr->get_instance_base_type(); + scr = scr->get_base_script(); + } + + // Check ClassDB + if (!ClassDB::class_exists(native)) { + native = "_" + native.operator String(); + } + if (!ClassDB::class_exists(native)) { + if (!check_types) return false; + ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found."); + ERR_FAIL_V(false); + } + + bool valid = false; + ClassDB::get_integer_constant(native, p_member, &valid); + if (valid) { + DataType ct; + ct.has_type = true; + ct.is_constant = true; + ct.kind = DataType::BUILTIN; + ct.builtin_type = Variant::INT; + r_member_type = ct; + return true; + } + + if (!base_type.is_meta_type) { + List<PropertyInfo> properties; + ClassDB::get_property_list(native, &properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_member) { + // Check if a getter exists + StringName getter_name = ClassDB::get_property_getter(native, p_member); + if (getter_name != StringName()) { + // Use the getter return type +#ifdef DEBUG_METHODS_ENABLED + MethodBind *getter_method = ClassDB::get_method(native, getter_name); + if (getter_method) { + r_member_type = _type_from_property(getter_method->get_return_info()); + } else { + r_member_type = DataType(); + } +#else + r_member_type = DataType(); +#endif + } else { + r_member_type = _type_from_property(E->get()); + } + return true; + } + } + } + + // If the base is a script, it might be trying to access members of the Script class itself + if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) { + native = p_base_type.script_type->get_class_name(); + ClassDB::get_integer_constant(native, p_member, &valid); + if (valid) { + DataType ct; + ct.has_type = true; + ct.is_constant = true; + ct.kind = DataType::BUILTIN; + ct.builtin_type = Variant::INT; + r_member_type = ct; + return true; + } + + List<PropertyInfo> properties; + ClassDB::get_property_list(native, &properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_member) { + // Check if a getter exists + StringName getter_name = ClassDB::get_property_getter(native, p_member); + if (getter_name != StringName()) { + // Use the getter return type +#ifdef DEBUG_METHODS_ENABLED + MethodBind *getter_method = ClassDB::get_method(native, getter_name); + if (getter_method) { + r_member_type = _type_from_property(getter_method->get_return_info()); + } else { + r_member_type = DataType(); + } +#else + r_member_type = DataType(); +#endif + } else { + r_member_type = _type_from_property(E->get()); + } + return true; + } + } + } + + return false; +} + +GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line) { + + if (p_base_type && !p_base_type->has_type) { + return DataType(); + } + + DataType base_type; + + // Check classes in current file + ClassNode *base = NULL; + if (!p_base_type) { + // Possibly this is a global, check first + + if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) { + DataType result; + result.has_type = true; + result.is_constant = true; + result.is_meta_type = true; + if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) { + result.is_meta_type = false; + } + result.kind = DataType::NATIVE; + result.native_type = p_identifier; + return result; + } + + ClassNode *outer_class = current_class; + while (outer_class) { + if (outer_class->name == p_identifier) { + DataType result; + result.has_type = true; + result.is_constant = true; + result.is_meta_type = true; + result.kind = DataType::CLASS; + result.class_type = outer_class; + return result; + } + for (int i = 0; i < outer_class->subclasses.size(); i++) { + if (outer_class->subclasses[i] == current_class) { + continue; + } + if (outer_class->subclasses[i]->name == p_identifier) { + DataType result; + result.has_type = true; + result.is_constant = true; + result.is_meta_type = true; + result.kind = DataType::CLASS; + result.class_type = outer_class->subclasses[i]; + return result; + } + } + outer_class = outer_class->owner; + } + + if (ScriptServer::is_global_class(p_identifier)) { + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); + if (scr.is_valid()) { + DataType result; + result.has_type = true; + result.script_type = scr; + result.is_meta_type = true; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Class '" + p_identifier + "' could not be fully loaded (script error or cyclic inheritance)."); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + } else { + result.kind = DataType::SCRIPT; + } + return result; + } + _set_error("Class '" + p_identifier + "' was found in global scope but its script could not be loaded."); + return DataType(); + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; + Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return _type_from_variant(g); + } + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { + Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]; + return _type_from_variant(g); + } + + // Non-tool singletons aren't loaded, check project settings + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + String s = E->get().name; + if (!s.begins_with("autoload/")) { + continue; + } + String name = s.get_slice("/", 1); + if (name == p_identifier) { + String script = ProjectSettings::get_singleton()->get(s); + if (script.begins_with("*")) { + script = script.right(1); + } + if (!script.begins_with("res://")) { + script = "res://" + script; + } + Ref<Script> singleton = ResourceLoader::load(script); + if (singleton.is_valid()) { + DataType result; + result.has_type = true; + result.script_type = singleton; + + Ref<GDScript> gds = singleton; + if (gds.is_valid()) { + if (!gds->is_valid()) { + _set_error("Couldn't fully load singleton script '" + p_identifier + "' (possible cyclic reference or parse error).", p_line); + return DataType(); + } + result.kind = DataType::GDSCRIPT; + } else { + result.kind = DataType::SCRIPT; + } + } + } + } + + // Nothing found, keep looking in local scope + + base = current_class; + base_type.has_type = true; + base_type.is_constant = true; + base_type.kind = DataType::CLASS; + base_type.class_type = base; + } else { + base_type = *p_base_type; + if (base_type.kind == DataType::CLASS) { + base = base_type.class_type; + } + } + + DataType member_type; + + if (_get_member_type(base_type, p_identifier, member_type)) { + return member_type; + } + + if (!p_base_type) { + // This means looking in the current class, which type is always known + _set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line); + } + + _mark_line_as_unsafe(p_line); + return DataType(); +} + +void GDScriptParser::_check_class_level_types(ClassNode *p_class) { + + _mark_line_as_safe(p_class->line); + + // Constants + for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { + ClassNode::Constant &c = E->get(); + _mark_line_as_safe(c.expression->line); + DataType cont = _resolve_type(c.type, c.expression->line); + DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line); + + if (!_is_type_compatible(cont, expr)) { + _set_error("Constant value type (" + expr.to_string() + ") is not compatible with declared type (" + cont.to_string() + ").", + c.expression->line); + return; + } + + expr.is_constant = true; + c.type = expr; + c.expression->set_datatype(expr); + } + + // Function declarations + for (int i = 0; i < p_class->static_functions.size(); i++) { + _check_function_types(p_class->static_functions[i]); + if (error_set) return; + } + + for (int i = 0; i < p_class->functions.size(); i++) { + _check_function_types(p_class->functions[i]); + if (error_set) return; + } + + // Class variables + for (int i = 0; i < p_class->variables.size(); i++) { + ClassNode::Member &v = p_class->variables[i]; + + DataType tmp; + if (_get_member_type(p_class->base_type, v.identifier, tmp)) { + _set_error("Member '" + String(v.identifier) + "' already exists in parent class.", v.line); + return; + } + + _mark_line_as_safe(v.line); + v.data_type = _resolve_type(v.data_type, v.line); + + if (v.expression) { + DataType expr_type = _reduce_node_type(v.expression); + + if (!_is_type_compatible(v.data_type, expr_type)) { + // Try supertype test + if (_is_type_compatible(expr_type, v.data_type)) { + _mark_line_as_unsafe(v.line); + } else { + // Try with implicit conversion + if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) { + _set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" + + v.data_type.to_string() + ").", + v.line); + return; + } + + // Replace assigment with implict conversion + BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); + convert->line = v.line; + convert->function = GDScriptFunctions::TYPE_CONVERT; + + ConstantNode *tgt_type = alloc_node<ConstantNode>(); + tgt_type->line = v.line; + tgt_type->value = (int)v.data_type.builtin_type; + + OperatorNode *convert_call = alloc_node<OperatorNode>(); + convert_call->line = v.line; + convert_call->op = OperatorNode::OP_CALL; + convert_call->arguments.push_back(convert); + convert_call->arguments.push_back(v.expression); + convert_call->arguments.push_back(tgt_type); + + v.expression = convert_call; + v.initial_assignment->arguments[1] = convert_call; + } + } + + if (v.data_type.infer_type) { + if (!expr_type.has_type) { + _set_error("Assigned value does not have a set type, variable type cannot be inferred.", v.line); + return; + } + v.data_type = expr_type; + v.data_type.is_constant = false; + } + } else if (v.data_type.has_type && v.data_type.kind == DataType::BUILTIN) { + // Create default value based on the type + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->line = v.line; + id->name = v.identifier; + + ConstantNode *init = alloc_node<ConstantNode>(); + init->line = v.line; + Variant::CallError err; + init->value = Variant::construct(v.data_type.builtin_type, NULL, 0, err); + + OperatorNode *op = alloc_node<OperatorNode>(); + op->line = v.line; + op->op = OperatorNode::OP_INIT_ASSIGN; + op->arguments.push_back(id); + op->arguments.push_back(init); + + p_class->initializer->statements.push_front(op); + v.initial_assignment = op; +#ifdef DEBUG_ENABLED + NewLineNode *nl = alloc_node<NewLineNode>(); + nl->line = v.line - 1; + p_class->initializer->statements.push_front(nl); +#endif + } + + // Check export hint + if (v.data_type.has_type && v._export.type != Variant::NIL) { + DataType export_type = _type_from_property(v._export); + if (!_is_type_compatible(v.data_type, export_type, true)) { + _set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" + + v.data_type.to_string() + ").", + v.line); + return; + } + } + + // Setter and getter + if (v.setter == StringName() && v.getter == StringName()) continue; + + bool found_getter = false; + bool found_setter = false; + for (int j = 0; j < p_class->functions.size(); j++) { + if (v.setter == p_class->functions[j]->name) { + found_setter = true; + FunctionNode *setter = p_class->functions[j]; + + if (setter->arguments.size() != 1) { + _set_error("Setter function needs to receive exactly 1 argument. See '" + setter->name + + "()' definition at line " + itos(setter->line) + ".", + v.line); + return; + } + if (!_is_type_compatible(v.data_type, setter->argument_types[0])) { + _set_error("Setter argument type (" + setter->argument_types[0].to_string() + + ") doesn't match the variable's type (" + v.data_type.to_string() + "). See '" + + setter->name + "()' definition at line " + itos(setter->line) + ".", + v.line); + return; + } + continue; + } + if (v.getter == p_class->functions[j]->name) { + found_getter = true; + FunctionNode *getter = p_class->functions[j]; + + if (getter->arguments.size() != 0) { + _set_error("Getter function can't receive arguments. See '" + getter->name + + "()' definition at line " + itos(getter->line) + ".", + v.line); + return; + } + if (!_is_type_compatible(v.data_type, getter->get_datatype())) { + _set_error("Getter return type (" + getter->get_datatype().to_string() + + ") doesn't match the variable's type (" + v.data_type.to_string() + + "). See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", + v.line); + return; + } + } + if (found_getter && found_setter) break; + } + + if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue; + + // Check for static functions + for (int j = 0; j < p_class->static_functions.size(); j++) { + if (v.setter == p_class->static_functions[j]->name) { + FunctionNode *setter = p_class->static_functions[j]; + _set_error("Setter can't be a static function. See '" + setter->name + "()' definition at line " + itos(setter->line) + ".", v.line); + return; + } + if (v.getter == p_class->static_functions[j]->name) { + FunctionNode *getter = p_class->static_functions[j]; + _set_error("Getter can't be a static function. See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", v.line); + return; + } + } + + if (!found_setter && v.setter != StringName()) { + _set_error("Setter function is not defined.", v.line); + return; + } + + if (!found_getter && v.getter != StringName()) { + _set_error("Getter function is not defined.", v.line); + return; + } + } + + // Inner classes + for (int i = 0; i < p_class->subclasses.size(); i++) { + current_class = p_class->subclasses[i]; + _check_class_level_types(current_class); + if (error_set) return; + current_class = p_class; + } +} + +void GDScriptParser::_check_function_types(FunctionNode *p_function) { + + p_function->return_type = _resolve_type(p_function->return_type, p_function->line); + + // Arguments + int defaults_ofs = p_function->arguments.size() - p_function->default_values.size(); + for (int i = 0; i < p_function->arguments.size(); i++) { + + // Resolve types + p_function->argument_types[i] = _resolve_type(p_function->argument_types[i], p_function->line); + + if (i >= defaults_ofs) { + if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) { + _set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column); + return; + } + + OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]); + + if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) { + _set_error("Parser bug: invalid argument default value operation.", p_function->line); + return; + } + + DataType def_type = _reduce_node_type(op->arguments[1]); + + if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) { + String arg_name = p_function->arguments[i]; + _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" + + arg_name + "' (" + p_function->arguments[i] + ")", + p_function->line); + } + } + } + + if (!(p_function->name == "_init")) { + // Signature for the initializer may vary + DataType return_type; + List<DataType> arg_types; + int default_arg_count = 0; + bool _static = false; + bool vararg = false; + + DataType base_type = current_class->base_type; + if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) { + bool valid = _static == p_function->_static; + valid = valid && return_type == p_function->return_type; + valid = valid && p_function->default_values.size() >= default_arg_count; + valid = valid && arg_types.size() == p_function->arguments.size(); + int i = 0; + for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) { + valid = valid && E->get() == p_function->argument_types[i++]; + } + + if (!valid) { + _set_error("Function signature doesn't match the parent.", p_function->line); + return; + } + } + } else { + if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { + _set_error("Constructor cannot return a value.", p_function->line); + return; + } + } + + if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { + if (!p_function->body->has_return) { + _set_error("Non-void function must return a value in all possible paths.", p_function->line); + return; + } + } + + if (p_function->has_yield) { + // yield() will make the function return a GDScriptFunctionState, so the type is ambiguous + p_function->return_type.has_type = false; + } +} + +void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) { + + // Function blocks + for (int i = 0; i < p_class->static_functions.size(); i++) { + current_function = p_class->static_functions[i]; + current_block = current_function->body; + _mark_line_as_safe(current_function->line); + _check_block_types(current_block); + current_block = NULL; + current_function = NULL; + if (error_set) return; + } + + for (int i = 0; i < p_class->functions.size(); i++) { + current_function = p_class->functions[i]; + current_block = current_function->body; + _mark_line_as_safe(current_function->line); + _check_block_types(current_block); + current_block = NULL; + current_function = NULL; + if (error_set) return; + } + + // Inner classes + for (int i = 0; i < p_class->subclasses.size(); i++) { + current_class = p_class->subclasses[i]; + _check_class_blocks_types(current_class); + if (error_set) return; + current_class = p_class; + } +} + +void GDScriptParser::_check_block_types(BlockNode *p_block) { + + Node *last_var_assign = NULL; + + // Check each statement + for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) { + Node *statement = E->get(); + switch (statement->type) { + case Node::TYPE_NEWLINE: + case Node::TYPE_BREAKPOINT: + case Node::TYPE_ASSERT: { + // Nothing to do + } break; + case Node::TYPE_LOCAL_VAR: { + LocalVarNode *lv = static_cast<LocalVarNode *>(statement); + lv->datatype = _resolve_type(lv->datatype, lv->line); + _mark_line_as_safe(lv->line); + + if (lv->assign) { + DataType assign_type = _reduce_node_type(lv->assign); + if (!_is_type_compatible(lv->datatype, assign_type)) { + // Try supertype test + if (_is_type_compatible(assign_type, lv->datatype)) { + _mark_line_as_unsafe(lv->line); + } else { + // Try implict conversion + if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) { + _set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" + + lv->datatype.to_string() + ").", + lv->line); + return; + } + // Replace assigment with implict conversion + BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); + convert->line = lv->line; + convert->function = GDScriptFunctions::TYPE_CONVERT; + + ConstantNode *tgt_type = alloc_node<ConstantNode>(); + tgt_type->line = lv->line; + tgt_type->value = (int)lv->datatype.builtin_type; + + OperatorNode *convert_call = alloc_node<OperatorNode>(); + convert_call->line = lv->line; + convert_call->op = OperatorNode::OP_CALL; + convert_call->arguments.push_back(convert); + convert_call->arguments.push_back(lv->assign); + convert_call->arguments.push_back(tgt_type); + + lv->assign = convert_call; + lv->assign_op->arguments[1] = convert_call; + } + } + if (lv->datatype.infer_type) { + if (!assign_type.has_type) { + _set_error("Assigned value does not have a set type, variable type cannot be inferred.", lv->line); + return; + } + lv->datatype = assign_type; + lv->datatype.is_constant = false; + } + if (lv->datatype.has_type && !assign_type.has_type) { + _mark_line_as_unsafe(lv->line); + } + } + last_var_assign = lv->assign; + + // TODO: Make a warning + /* + if (lv->assignments == 0) { + _set_error("Variable '" + String(lv->name) + "' is never assigned.", lv->line); + return; + } + */ + } break; + case Node::TYPE_OPERATOR: { + OperatorNode *op = static_cast<OperatorNode *>(statement); + + switch (op->op) { + case OperatorNode::OP_ASSIGN: + case OperatorNode::OP_ASSIGN_ADD: + case OperatorNode::OP_ASSIGN_SUB: + case OperatorNode::OP_ASSIGN_MUL: + case OperatorNode::OP_ASSIGN_DIV: + case OperatorNode::OP_ASSIGN_MOD: + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + case OperatorNode::OP_ASSIGN_BIT_AND: + case OperatorNode::OP_ASSIGN_BIT_OR: + case OperatorNode::OP_ASSIGN_BIT_XOR: { + if (op->arguments.size() < 2) { + _set_error("Parser bug: operation without enough arguments.", op->line, op->column); + return; + } + + if (op->arguments[1] == last_var_assign) { + // Assignment was already checked + break; + } + + _mark_line_as_safe(op->line); + + DataType lh_type = _reduce_node_type(op->arguments[0]); + + if (error_set) { + return; + } + + if (!lh_type.has_type) { + if (op->arguments[0]->type == Node::TYPE_OPERATOR) { + _mark_line_as_unsafe(op->line); + } + } else if (lh_type.is_constant) { + _set_error("Cannot assign a new value to a constant.", op->line); + return; + } + + DataType rh_type; + if (op->op != OperatorNode::OP_ASSIGN) { + // Validate operation + DataType arg_type = _reduce_node_type(op->arguments[1]); + if (!arg_type.has_type) { + _mark_line_as_unsafe(op->line); + break; + } + + Variant::Operator oper = _get_variant_operation(op->op); + bool valid = false; + rh_type = _get_operation_type(oper, lh_type, arg_type, valid); + + if (!valid) { + _set_error("Invalid operand types ('" + lh_type.to_string() + "' and '" + arg_type.to_string() + + "') to assignment operator '" + Variant::get_operator_name(oper) + "'.", + op->line); + return; + } + } else { + rh_type = _reduce_node_type(op->arguments[1]); + } + + if (!_is_type_compatible(lh_type, rh_type)) { + // Try supertype test + if (_is_type_compatible(rh_type, lh_type)) { + _mark_line_as_unsafe(op->line); + } else { + // Try implict conversion + if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) { + _set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" + + lh_type.to_string() + ").", + op->line); + return; + } + // Replace assigment with implict conversion + BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); + convert->line = op->line; + convert->function = GDScriptFunctions::TYPE_CONVERT; + + ConstantNode *tgt_type = alloc_node<ConstantNode>(); + tgt_type->line = op->line; + tgt_type->value = (int)lh_type.builtin_type; + + OperatorNode *convert_call = alloc_node<OperatorNode>(); + convert_call->line = op->line; + convert_call->op = OperatorNode::OP_CALL; + convert_call->arguments.push_back(convert); + convert_call->arguments.push_back(op->arguments[1]); + convert_call->arguments.push_back(tgt_type); + + op->arguments[1] = convert_call; + } + } + if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) { + _mark_line_as_unsafe(op->line); + } + } break; + case OperatorNode::OP_CALL: + case OperatorNode::OP_PARENT_CALL: { + _mark_line_as_safe(op->line); + _reduce_function_call_type(op); + if (error_set) return; + } break; + default: { + _mark_line_as_safe(op->line); + _reduce_node_type(op); // Test for safety anyway + // TODO: Make this a warning + /*_set_error("Standalone expression, nothing is done in this line.", statement->line); + return; */ + } + } + } break; + case Node::TYPE_CONTROL_FLOW: { + ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement); + _mark_line_as_safe(cf->line); + + switch (cf->cf_type) { + case ControlFlowNode::CF_RETURN: { + DataType function_type = current_function->get_datatype(); + + DataType ret_type; + if (cf->arguments.size() > 0) { + ret_type = _reduce_node_type(cf->arguments[0]); + if (error_set) { + return; + } + } + + if (!function_type.has_type) break; + + if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) { + // Return void, should not have arguments + if (cf->arguments.size() > 0) { + _set_error("Void function cannot return a value.", cf->line, cf->column); + return; + } + } else { + // Return something, cannot be empty + if (cf->arguments.size() == 0) { + _set_error("Non-void function must return a value.", cf->line, cf->column); + return; + } + + if (!_is_type_compatible(function_type, ret_type)) { + _set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" + + function_type.to_string() + ").", + cf->line, cf->column); + return; + } + } + } break; + case ControlFlowNode::CF_MATCH: { + MatchNode *match_node = cf->match; + _transform_match_statment(match_node); + } break; + default: { + if (cf->body_else) { + _mark_line_as_safe(cf->body_else->line); + } + for (int i = 0; i < cf->arguments.size(); i++) { + _reduce_node_type(cf->arguments[i]); + } + } break; + } + } break; + case Node::TYPE_CONSTANT: { + ConstantNode *cn = static_cast<ConstantNode *>(statement); + // Strings are fine since they can be multiline comments + if (cn->value.get_type() == Variant::STRING) { + break; + } + } // falthrough + default: { + _mark_line_as_safe(statement->line); + _reduce_node_type(statement); // Test for safety anyway + // TODO: Make this a warning + /* _set_error("Standalone expression, nothing is done in this line.", statement->line); + return; */ + } + } + } + + // Parse sub blocks + for (int i = 0; i < p_block->sub_blocks.size(); i++) { + current_block = p_block->sub_blocks[i]; + _check_block_types(current_block); + current_block = p_block; + if (error_set) return; + } +} + void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) { if (error_set) @@ -4440,10 +7547,39 @@ Error GDScriptParser::_parse(const String &p_base_path) { _set_error("Parse Error: " + tokenizer->get_token_error()); } + if (error_set && !for_completion) { + return ERR_PARSE_ERROR; + } + + _determine_inheritance(main_class); + + if (error_set) { + return ERR_PARSE_ERROR; + } + + current_class = main_class; + current_function = NULL; + current_block = NULL; +#ifdef DEBUG_ENABLED + if (for_completion) check_types = false; +#else + check_types = false; +#endif + + // Resolve all class-level stuff before getting into function blocks + _check_class_level_types(main_class); + if (error_set) { + return ERR_PARSE_ERROR; + } + // Resolve the function blocks + _check_class_blocks_types(main_class); + + if (error_set) { return ERR_PARSE_ERROR; } + return OK; } @@ -4461,7 +7597,7 @@ Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const St return ret; } -Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion) { +Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines) { clear(); @@ -4471,6 +7607,9 @@ Error GDScriptParser::parse(const String &p_code, const String &p_base_path, boo validating = p_just_validate; for_completion = p_for_completion; +#ifdef DEBUG_ENABLED + safe_lines = r_safe_lines; +#endif // DEBUG_ENABLED tokenizer = tt; Error ret = _parse(p_base_path); memdelete(tt); @@ -4523,7 +7662,11 @@ void GDScriptParser::clear() { pending_newline = -1; parenthesis = 0; current_export.type = Variant::NIL; + check_types = true; error = ""; +#ifdef DEBUG_ENABLED + safe_lines = NULL; +#endif // DEBUG_ENABLED } GDScriptParser::CompletionType GDScriptParser::get_completion_type() { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index b88a59537c..48f256b4c6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -37,8 +37,68 @@ #include "object.h" #include "script_language.h" +struct GDScriptDataType; + class GDScriptParser { public: + struct ClassNode; + + struct DataType { + enum { + BUILTIN, + NATIVE, + SCRIPT, + GDSCRIPT, + CLASS, + UNRESOLVED + } kind; + + bool has_type; + bool is_constant; + bool is_meta_type; // Whether the value can be used as a type + bool infer_type; + + Variant::Type builtin_type; + StringName native_type; + Ref<Script> script_type; + ClassNode *class_type; + + String to_string() const; + + bool operator==(const DataType &other) const { + if (!has_type || !other.has_type) { + return true; // Can be considered equal for parsing purpose + } + if (kind != other.kind) { + return false; + } + switch (kind) { + case BUILTIN: { + return builtin_type == other.builtin_type; + } break; + case NATIVE: { + return native_type == other.native_type; + } break; + case GDSCRIPT: + case SCRIPT: { + return script_type == other.script_type; + } break; + case CLASS: { + return class_type == other.class_type; + } break; + } + return false; + } + + DataType() : + has_type(false), + is_constant(false), + is_meta_type(false), + infer_type(false), + builtin_type(Variant::NIL), + class_type(NULL) {} + }; + struct Node { enum Type { @@ -55,6 +115,7 @@ public: TYPE_OPERATOR, TYPE_CONTROL_FLOW, TYPE_LOCAL_VAR, + TYPE_CAST, TYPE_ASSERT, TYPE_BREAKPOINT, TYPE_NEWLINE, @@ -65,11 +126,17 @@ public: int column; Type type; + virtual DataType get_datatype() const { return DataType(); } + virtual void set_datatype(const DataType &p_datatype) {} + virtual ~Node() {} }; struct FunctionNode; struct BlockNode; + struct ConstantNode; + struct LocalVarNode; + struct OperatorNode; struct ClassNode : public Node { @@ -78,6 +145,7 @@ public: bool extends_used; StringName extends_file; Vector<StringName> extends_class; + DataType base_type; struct Member { PropertyInfo _export; @@ -85,15 +153,17 @@ public: Variant default_value; #endif StringName identifier; + DataType data_type; StringName setter; StringName getter; int line; Node *expression; + OperatorNode *initial_assignment; MultiplayerAPI::RPCMode rpc_mode; }; struct Constant { - StringName identifier; Node *expression; + DataType type; }; struct Signal { @@ -103,7 +173,7 @@ public: Vector<ClassNode *> subclasses; Vector<Member> variables; - Vector<Constant> constant_expressions; + Map<StringName, Constant> constant_expressions; Vector<FunctionNode *> functions; Vector<FunctionNode *> static_functions; Vector<Signal> _signals; @@ -126,15 +196,22 @@ public: bool _static; MultiplayerAPI::RPCMode rpc_mode; + bool has_yield; StringName name; + DataType return_type; Vector<StringName> arguments; + Vector<DataType> argument_types; Vector<Node *> default_values; BlockNode *body; + virtual DataType get_datatype() const { return return_type; } + virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; } + FunctionNode() { type = TYPE_FUNCTION; _static = false; rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + has_yield = false; } }; @@ -142,10 +219,9 @@ public: ClassNode *parent_class; BlockNode *parent_block; - Map<StringName, int> locals; List<Node *> statements; - Vector<StringName> variables; - Vector<int> variable_lines; + Map<StringName, LocalVarNode *> variables; + bool has_return; Node *if_condition; //tiny hack to improve code completion on if () blocks @@ -158,6 +234,7 @@ public: end_line = -1; parent_block = NULL; parent_class = NULL; + has_return = false; } }; @@ -174,28 +251,53 @@ public: struct IdentifierNode : public Node { StringName name; - IdentifierNode() { type = TYPE_IDENTIFIER; } + BlockNode *declared_block; // Simplify lookup by checking if it is declared locally + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + IdentifierNode() { + type = TYPE_IDENTIFIER; + declared_block = NULL; + } }; struct LocalVarNode : public Node { StringName name; Node *assign; + OperatorNode *assign_op; + int assignments; + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } LocalVarNode() { type = TYPE_LOCAL_VAR; assign = NULL; + assign_op = NULL; + assignments = 0; } }; struct ConstantNode : public Node { Variant value; + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } ConstantNode() { type = TYPE_CONSTANT; } }; struct ArrayNode : public Node { Vector<Node *> elements; - ArrayNode() { type = TYPE_ARRAY; } + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + ArrayNode() { + type = TYPE_ARRAY; + datatype.has_type = true; + datatype.kind = DataType::BUILTIN; + datatype.builtin_type = Variant::ARRAY; + } }; struct DictionaryNode : public Node { @@ -207,7 +309,15 @@ public: }; Vector<Pair> elements; - DictionaryNode() { type = TYPE_DICTIONARY; } + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + DictionaryNode() { + type = TYPE_DICTIONARY; + datatype.has_type = true; + datatype.kind = DataType::BUILTIN; + datatype.builtin_type = Variant::DICTIONARY; + } }; struct SelfNode : public Node { @@ -229,10 +339,6 @@ public: OP_POS, OP_NOT, OP_BIT_INVERT, - OP_PREINC, - OP_PREDEC, - OP_INC, - OP_DEC, //binary operators (in precedence order) OP_IN, OP_EQUAL, @@ -273,6 +379,9 @@ public: Operator op; Vector<Node *> arguments; + DataType datatype; + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } OperatorNode() { type = TYPE_OPERATOR; } }; @@ -340,6 +449,15 @@ public: } }; + struct CastNode : public Node { + Node *source_node; + DataType cast_type; + DataType return_type; + virtual DataType get_datatype() const { return return_type; } + virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; } + CastNode() { type = TYPE_CAST; } + }; + struct AssertNode : public Node { Node *condition; AssertNode() { type = TYPE_ASSERT; } @@ -362,76 +480,6 @@ public: }; }; - /* - struct OperatorNode : public Node { - - DataType return_cache; - Operator op; - Vector<Node*> arguments; - virtual DataType get_datatype() const { return return_cache; } - - OperatorNode() { type=TYPE_OPERATOR; return_cache=TYPE_VOID; } - }; - - struct VariableNode : public Node { - - DataType datatype_cache; - StringName name; - virtual DataType get_datatype() const { return datatype_cache; } - - VariableNode() { type=TYPE_VARIABLE; datatype_cache=TYPE_VOID; } - }; - - struct ConstantNode : public Node { - - DataType datatype; - Variant value; - virtual DataType get_datatype() const { return datatype; } - - ConstantNode() { type=TYPE_CONSTANT; } - }; - - struct BlockNode : public Node { - - Map<StringName,DataType> variables; - List<Node*> statements; - BlockNode() { type=TYPE_BLOCK; } - }; - - struct ControlFlowNode : public Node { - - FlowOperation flow_op; - Vector<Node*> statements; - ControlFlowNode() { type=TYPE_CONTROL_FLOW; flow_op=FLOW_OP_IF;} - }; - - struct MemberNode : public Node { - - DataType datatype; - StringName name; - Node* owner; - virtual DataType get_datatype() const { return datatype; } - MemberNode() { type=TYPE_MEMBER; } - }; - - - struct ProgramNode : public Node { - - struct Function { - StringName name; - FunctionNode*function; - }; - - Map<StringName,DataType> builtin_variables; - Map<StringName,DataType> preexisting_variables; - - Vector<Function> functions; - BlockNode *body; - - ProgramNode() { type=TYPE_PROGRAM; } - }; -*/ - enum CompletionType { COMPLETION_NONE, COMPLETION_BUILT_IN_TYPE_CONSTANT, @@ -446,6 +494,8 @@ public: COMPLETION_VIRTUAL_FUNC, COMPLETION_YIELD, COMPLETION_ASSIGN, + COMPLETION_TYPE_HINT, + COMPLETION_TYPE_HINT_INDEX, }; private: @@ -463,6 +513,10 @@ private: String error; int error_line; int error_column; + bool check_types; +#ifdef DEBUG_ENABLED + Set<int> *safe_lines; +#endif // DEBUG_ENABLED int pending_newline; @@ -507,7 +561,7 @@ private: PatternNode *_parse_pattern(bool p_static); void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static); - void _transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement); + void _transform_match_statment(MatchNode *p_match_statement); void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings); void _parse_block(BlockNode *p_block, bool p_static); @@ -515,13 +569,43 @@ private: void _parse_class(ClassNode *p_class); bool _end_statement(); + void _determine_inheritance(ClassNode *p_class); + bool _parse_type(DataType &r_type, bool p_can_be_void = false); + DataType _resolve_type(const DataType &p_source, int p_line); + DataType _type_from_variant(const Variant &p_value) const; + DataType _type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant = true) const; + DataType _type_from_gdtype(const GDScriptDataType &p_gdtype) const; + DataType _get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const; + Variant::Operator _get_variant_operation(const OperatorNode::Operator &p_op) const; + bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const; + bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const; + bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const; + + DataType _reduce_node_type(Node *p_node); + DataType _reduce_function_call_type(const OperatorNode *p_call); + DataType _reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line); + void _check_class_level_types(ClassNode *p_class); + void _check_class_blocks_types(ClassNode *p_class); + void _check_function_types(FunctionNode *p_function); + void _check_block_types(BlockNode *p_block); + _FORCE_INLINE_ void _mark_line_as_safe(int p_line) const { +#ifdef DEBUG_ENABLED + if (safe_lines) safe_lines->insert(p_line); +#endif // DEBUG_ENABLED + } + _FORCE_INLINE_ void _mark_line_as_unsafe(int p_line) const { +#ifdef DEBUG_ENABLED + if (safe_lines) safe_lines->erase(p_line); +#endif // DEBUG_ENABLED + } + Error _parse(const String &p_base_path); public: String get_error() const; int get_error_line() const; int get_error_column() const; - Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false); + Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false, Set<int> *r_safe_lines = NULL); Error parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path = "", const String &p_self_path = ""); bool is_tool_script() const; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 3c8e1ddbe4..940bdcbc8d 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -91,6 +91,7 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "match", "func", "class", + "class_name", "extends", "is", "onready", @@ -100,6 +101,8 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "setget", "const", "var", + "as", + "void", "enum", "preload", "assert", @@ -124,6 +127,7 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "'.'", "'?'", "':'", + "'->'", "'$'", "'\\n'", "PI", @@ -187,6 +191,7 @@ static const _kws _keyword_list[] = { //func { GDScriptTokenizer::TK_PR_FUNCTION, "func" }, { GDScriptTokenizer::TK_PR_CLASS, "class" }, + { GDScriptTokenizer::TK_PR_CLASS_NAME, "class_name" }, { GDScriptTokenizer::TK_PR_EXTENDS, "extends" }, { GDScriptTokenizer::TK_PR_IS, "is" }, { GDScriptTokenizer::TK_PR_ONREADY, "onready" }, @@ -195,6 +200,8 @@ static const _kws _keyword_list[] = { { GDScriptTokenizer::TK_PR_EXPORT, "export" }, { GDScriptTokenizer::TK_PR_SETGET, "setget" }, { GDScriptTokenizer::TK_PR_VAR, "var" }, + { GDScriptTokenizer::TK_PR_AS, "as" }, + { GDScriptTokenizer::TK_PR_VOID, "void" }, { GDScriptTokenizer::TK_PR_PRELOAD, "preload" }, { GDScriptTokenizer::TK_PR_ASSERT, "assert" }, { GDScriptTokenizer::TK_PR_YIELD, "yield" }, @@ -705,11 +712,9 @@ void GDScriptTokenizerText::_advance() { if (GETCHAR(1) == '=') { _make_token(TK_OP_ASSIGN_SUB); INCPOS(1); - /* - } else if (GETCHAR(1)=='-') { - _make_token(TK_OP_MINUS_MINUS); + } else if (GETCHAR(1) == '>') { + _make_token(TK_FORWARD_ARROW); INCPOS(1); - */ } else { _make_token(TK_OP_SUB); } @@ -1135,9 +1140,9 @@ void GDScriptTokenizerText::advance(int p_amount) { _advance(); } - ////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////// -#define BYTECODE_VERSION 12 +#define BYTECODE_VERSION 13 Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) { diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index c4f1f9fd94..5bd303224c 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -96,6 +96,7 @@ public: TK_CF_MATCH, TK_PR_FUNCTION, TK_PR_CLASS, + TK_PR_CLASS_NAME, TK_PR_EXTENDS, TK_PR_IS, TK_PR_ONREADY, @@ -105,6 +106,8 @@ public: TK_PR_SETGET, TK_PR_CONST, TK_PR_VAR, + TK_PR_AS, + TK_PR_VOID, TK_PR_ENUM, TK_PR_PRELOAD, TK_PR_ASSERT, @@ -130,6 +133,7 @@ public: TK_QUESTION_MARK, TK_COLON, TK_DOLLAR, + TK_FORWARD_ARROW, TK_NEWLINE, TK_CONST_PI, TK_CONST_TAU, diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index 0168be3a26..437c0d57fa 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -121,9 +121,7 @@ static Ref<Image> _jpegd_mem_loader_func(const uint8_t *p_png, int p_size) { Ref<Image> img; img.instance(); Error err = jpeg_load_image_from_buffer(img.ptr(), p_png, p_size); - if (err) - ERR_PRINT("Couldn't initialize ImageLoaderJPG with the given resource."); - + ERR_FAIL_COND_V(err, Ref<Image>()); return img; } diff --git a/modules/mbedtls/stream_peer_mbed_tls.cpp b/modules/mbedtls/stream_peer_mbed_tls.cpp index a63e53ec1f..884c26ddfe 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.cpp +++ b/modules/mbedtls/stream_peer_mbed_tls.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "stream_peer_mbed_tls.h" +#include "mbedtls/platform_util.h" +#include "os/file_access.h" static void my_debug(void *ctx, int level, const char *file, int line, @@ -81,6 +83,36 @@ int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { return got; } +void StreamPeerMbedTLS::_cleanup() { + + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + base = Ref<StreamPeer>(); + status = STATUS_DISCONNECTED; +} + +Error StreamPeerMbedTLS::_do_handshake() { + int ret = 0; + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ERR_PRINTS("TLS handshake error: " + itos(ret)); + _print_error(ret); + disconnect_from_stream(); + status = STATUS_ERROR; + return FAILED; + } else if (!blocking_handshake) { + // Will retry via poll later + return OK; + } + } + + status = STATUS_CONNECTED; + return OK; +} + Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname) { base = p_base; @@ -95,6 +127,7 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); if (ret != 0) { ERR_PRINTS(" failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret)); + _cleanup(); return FAILED; } @@ -112,29 +145,24 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida mbedtls_ssl_set_bio(&ssl, this, bio_send, bio_recv, NULL); - while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - ERR_PRINTS("TLS handshake error: " + itos(ret)); - _print_error(ret); - status = STATUS_ERROR_HOSTNAME_MISMATCH; - return FAILED; - } - } + status = STATUS_HANDSHAKING; - connected = true; - status = STATUS_CONNECTED; + if ((ret = _do_handshake()) != OK) { + status = STATUS_ERROR_HOSTNAME_MISMATCH; + return FAILED; + } return OK; } Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base) { - return ERR_UNAVAILABLE; + return OK; } Error StreamPeerMbedTLS::put_data(const uint8_t *p_data, int p_bytes) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); Error err; int sent = 0; @@ -155,7 +183,7 @@ Error StreamPeerMbedTLS::put_data(const uint8_t *p_data, int p_bytes) { Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); r_sent = 0; @@ -177,7 +205,7 @@ Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, in Error StreamPeerMbedTLS::get_data(uint8_t *p_buffer, int p_bytes) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); Error err; @@ -199,7 +227,7 @@ Error StreamPeerMbedTLS::get_data(uint8_t *p_buffer, int p_bytes) { Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) { - ERR_FAIL_COND_V(!connected, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); r_received = 0; @@ -218,27 +246,30 @@ Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r void StreamPeerMbedTLS::poll() { - ERR_FAIL_COND(!connected); + ERR_FAIL_COND(status != STATUS_CONNECTED && status != STATUS_HANDSHAKING); ERR_FAIL_COND(!base.is_valid()); + if (status == STATUS_HANDSHAKING) { + _do_handshake(); + return; + } + int ret = mbedtls_ssl_read(&ssl, NULL, 0); if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { _print_error(ret); disconnect_from_stream(); - return; } } int StreamPeerMbedTLS::get_available_bytes() const { - ERR_FAIL_COND_V(!connected, 0); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, 0); return mbedtls_ssl_get_bytes_avail(&ssl); } StreamPeerMbedTLS::StreamPeerMbedTLS() { - connected = false; status = STATUS_DISCONNECTED; } @@ -248,17 +279,10 @@ StreamPeerMbedTLS::~StreamPeerMbedTLS() { void StreamPeerMbedTLS::disconnect_from_stream() { - if (!connected) + if (status != STATUS_CONNECTED && status != STATUS_HANDSHAKING) return; - mbedtls_ssl_free(&ssl); - mbedtls_ssl_config_free(&conf); - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&entropy); - - base = Ref<StreamPeer>(); - connected = false; - status = STATUS_DISCONNECTED; + _cleanup(); } StreamPeerMbedTLS::Status StreamPeerMbedTLS::get_status() const { diff --git a/modules/mbedtls/stream_peer_mbed_tls.h b/modules/mbedtls/stream_peer_mbed_tls.h index 2b96a194a1..7f4e5a4513 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.h +++ b/modules/mbedtls/stream_peer_mbed_tls.h @@ -48,8 +48,6 @@ private: Status status; String hostname; - bool connected; - Ref<StreamPeer> base; static StreamPeerSSL *_create_func(); @@ -57,9 +55,11 @@ private: 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); + void _cleanup(); protected: static mbedtls_x509_crt cacert; + mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ssl_context ssl; @@ -67,6 +67,8 @@ protected: static void _bind_methods(); + Error _do_handshake(); + public: virtual void poll(); virtual Error accept_stream(Ref<StreamPeer> p_base); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 62a6b96bb5..996e73a4bb 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -121,7 +121,7 @@ void CSharpLanguage::init() { #ifdef TOOLS_ENABLED EditorNode::add_init_callback(&gdsharp_editor_init_callback); - GLOBAL_DEF("mono/export/include_scripts_content", true); + GLOBAL_DEF("mono/export/include_scripts_content", false); #endif } @@ -1609,7 +1609,7 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { - MonoType *raw_type = GDMonoClass::get_raw_type(p_delegate); + MonoType *raw_type = p_delegate->get_mono_type(); if (mono_type_get_type(raw_type) == MONO_TYPE_CLASS) { // Arguments are accessibles as arguments of .Invoke method diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index df597ba776..7f9732c297 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -292,7 +292,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { return true; } + /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines = NULL) const { return true; } virtual String validate_path(const String &p_path) const; virtual Script *create_script() const; virtual bool has_named_classes() const; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 4c598d4f37..307a7d3e94 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -100,8 +100,6 @@ #define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL "::mono_string_from_godot" #define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type #define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" -#define C_METHOD_MANAGED_TO_DICT C_NS_MONOMARSHAL "::mono_object_to_Dictionary" -#define C_METHOD_MANAGED_FROM_DICT C_NS_MONOMARSHAL "::Dictionary_to_mono_object" #define BINDINGS_GENERATOR_VERSION UINT32_C(2) @@ -1338,7 +1336,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf } else if (return_type->cs_out.empty()) { p_output.push_back("return " + im_call + ";\n"); } else { - p_output.push_back(INDENT3); p_output.push_back(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); p_output.push_back("\n"); } @@ -1768,6 +1765,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { continue; } + if (!ClassDB::is_class_enabled(type_cname)) { + if (verbose_output) + WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not enabled"); + class_list.pop_front(); + continue; + } + ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(type_cname); TypeInterface itype = TypeInterface::create_object_type(type_cname, api_type); @@ -2337,7 +2341,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { #define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t) - INSERT_ARRAY(Array, object); INSERT_ARRAY(PoolIntArray, int); INSERT_ARRAY_FULL(PoolByteArray, PoolByteArray, byte); @@ -2355,20 +2358,36 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { #undef INSERT_ARRAY + // Array + itype = TypeInterface(); + itype.name = "Array"; + itype.cname = itype.name; + itype.proxy_name = "Array"; + itype.c_out = "\treturn memnew(Array(%1));\n"; + itype.c_type = itype.name; + itype.c_type_in = itype.c_type + "*"; + itype.c_type_out = itype.c_type + "*"; + itype.cs_type = itype.proxy_name; + itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; + itype.cs_out = "return new Array(%0);"; + itype.im_type_in = "IntPtr"; + itype.im_type_out = "IntPtr"; + builtin_types.insert(itype.cname, itype); + // Dictionary itype = TypeInterface(); itype.name = "Dictionary"; itype.cname = itype.name; - itype.proxy_name = "Dictionary<object, object>"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_DICT "(%1);\n"; - itype.c_out = "\treturn " C_METHOD_MANAGED_FROM_DICT "(%1);\n"; - itype.c_arg_in = "&%s_in"; + itype.proxy_name = "Dictionary"; + itype.c_out = "\treturn memnew(Dictionary(%1));\n"; itype.c_type = itype.name; - itype.c_type_in = "MonoObject*"; - itype.c_type_out = "MonoObject*"; + itype.c_type_in = itype.c_type + "*"; + itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; + itype.cs_out = "return new Dictionary(%0);"; + itype.im_type_in = "IntPtr"; + itype.im_type_out = "IntPtr"; builtin_types.insert(itype.cname, itype); // void (fictitious type to represent the return type of methods that do not return anything) diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp index 998da8bda3..faeb58e5a7 100644 --- a/modules/mono/editor/godotsharp_editor.cpp +++ b/modules/mono/editor/godotsharp_editor.cpp @@ -278,13 +278,11 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { about_label->set_autowrap(true); String about_text = String("C# support in Godot Engine is a brand new feature and a work in progress.\n") + - "It is at the alpha stage and thus not suitable for use in production.\n\n" + - "As of Godot 3.0, C# support is not feature-complete and may crash in some situations. " + - "Bugs and usability issues will be addressed gradually over 3.0.x and 3.x releases, " + + "It is currently in an alpha stage and is not suitable for use in production.\n\n" + + "As of Godot 3.1, C# support is not feature-complete and may crash in some situations. " + + "Bugs and usability issues will be addressed gradually over future 3.x releases, " + "including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + - "The main missing feature is the ability to export games using C# assemblies - you will therefore be able to develop and run games in the editor, " + - "but not to share them as standalone binaries yet. This feature is of course high on the priority list and should be available as soon as possible.\n\n" + - "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono version, IDE, etc.:\n\n" + + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono version, IDE, etc:\n\n" + " https://github.com/godotengine/godot/issues\n\n" + "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; about_label->set_text(about_text); diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp new file mode 100644 index 0000000000..0551c1991a --- /dev/null +++ b/modules/mono/glue/collections_glue.cpp @@ -0,0 +1,240 @@ +/*************************************************************************/ +/* collections_glue.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "collections_glue.h" + +#include <mono/metadata/exception.h> + +#include "../mono_gd/gd_mono_class.h" + +Array *godot_icall_Array_Ctor() { + return memnew(Array); +} + +void godot_icall_Array_Dtor(Array *ptr) { + memdelete(ptr); +} + +MonoObject *godot_icall_Array_At(Array *ptr, int index) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return NULL; + } + return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index)); +} + +void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return; + } + ptr->operator[](index) = GDMonoMarshal::mono_object_to_variant(value); +} + +int godot_icall_Array_Count(Array *ptr) { + return ptr->size(); +} + +void godot_icall_Array_Add(Array *ptr, MonoObject *item) { + ptr->append(GDMonoMarshal::mono_object_to_variant(item)); +} + +void godot_icall_Array_Clear(Array *ptr) { + ptr->clear(); +} + +bool godot_icall_Array_Contains(Array *ptr, MonoObject *item) { + return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1; +} + +void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) { + int count = ptr->size(); + + if (mono_array_length(array) < (array_index + count)) { + MonoException *exc = mono_get_exception_argument("", "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + GDMonoUtils::set_pending_exception(exc); + return; + } + + for (int i = 0; i < count; i++) { + MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(ptr->operator[](i)); + mono_array_setref(array, array_index, boxed); + array_index++; + } +} + +int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { + return ptr->find(GDMonoMarshal::mono_object_to_variant(item)); +} + +void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return; + } + ptr->insert(index, GDMonoMarshal::mono_object_to_variant(item)); +} + +bool godot_icall_Array_Remove(Array *ptr, MonoObject *item) { + int idx = ptr->find(GDMonoMarshal::mono_object_to_variant(item)); + if (idx >= 0) { + ptr->remove(idx); + return true; + } + return false; +} + +void godot_icall_Array_RemoveAt(Array *ptr, int index) { + if (index < 0 || index > ptr->size()) { + GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); + return; + } + ptr->remove(index); +} + +Dictionary *godot_icall_Dictionary_Ctor() { + return memnew(Dictionary); +} + +void godot_icall_Dictionary_Dtor(Dictionary *ptr) { + memdelete(ptr); +} + +MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) { + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + if (ret == NULL) { + MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr()); +#ifdef DEBUG_ENABLED + CRASH_COND(!exc); +#endif + GDMonoUtils::runtime_object_init(exc); + GDMonoUtils::set_pending_exception((MonoException *)exc); + return NULL; + } + return GDMonoMarshal::variant_to_mono_object(ret); +} + +void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value) { + ptr->operator[](GDMonoMarshal::mono_object_to_variant(key)) = GDMonoMarshal::mono_object_to_variant(value); +} + +Array *godot_icall_Dictionary_Keys(Dictionary *ptr) { + return memnew(Array(ptr->keys())); +} + +Array *godot_icall_Dictionary_Values(Dictionary *ptr) { + return memnew(Array(ptr->values())); +} + +int godot_icall_Dictionary_Count(Dictionary *ptr) { + return ptr->size(); +} + +void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value) { + Variant varKey = GDMonoMarshal::mono_object_to_variant(key); + Variant *ret = ptr->getptr(varKey); + if (ret != NULL) { + GDMonoUtils::set_pending_exception(mono_get_exception_argument("key", "An element with the same key already exists")); + return; + } + ptr->operator[](varKey) = GDMonoMarshal::mono_object_to_variant(value); +} + +void godot_icall_Dictionary_Clear(Dictionary *ptr) { + ptr->clear(); +} + +bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) { + // no dupes + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + return ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value); +} + +bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) { + return ptr->has(GDMonoMarshal::mono_object_to_variant(key)); +} + +bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) { + return ptr->erase_checked(GDMonoMarshal::mono_object_to_variant(key)); +} + +bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value) { + Variant varKey = GDMonoMarshal::mono_object_to_variant(key); + + // no dupes + Variant *ret = ptr->getptr(varKey); + if (ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value)) { + ptr->erase_checked(varKey); + return true; + } + + return false; +} + +bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) { + Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); + if (ret == NULL) { + *value = NULL; + return false; + } + *value = GDMonoMarshal::variant_to_mono_object(ret); + return true; +} + +void godot_register_collections_icalls() { + mono_add_internal_call("Godot.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor); + mono_add_internal_call("Godot.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor); + mono_add_internal_call("Godot.Array::godot_icall_Array_At", (void *)godot_icall_Array_At); + mono_add_internal_call("Godot.Array::godot_icall_Array_SetAt", (void *)godot_icall_Array_SetAt); + mono_add_internal_call("Godot.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count); + mono_add_internal_call("Godot.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add); + mono_add_internal_call("Godot.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear); + mono_add_internal_call("Godot.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains); + mono_add_internal_call("Godot.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo); + mono_add_internal_call("Godot.Array::godot_icall_Array_IndexOf", (void *)godot_icall_Array_IndexOf); + mono_add_internal_call("Godot.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert); + mono_add_internal_call("Godot.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove); + mono_add_internal_call("Godot.Array::godot_icall_Array_RemoveAt", (void *)godot_icall_Array_RemoveAt); + + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Ctor", (void *)godot_icall_Dictionary_Ctor); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Dtor", (void *)godot_icall_Dictionary_Dtor); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_GetValue", (void *)godot_icall_Dictionary_GetValue); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_SetValue", (void *)godot_icall_Dictionary_SetValue); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Keys", (void *)godot_icall_Dictionary_Keys); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Values", (void *)godot_icall_Dictionary_Values); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Count", (void *)godot_icall_Dictionary_Count); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Add", (void *)godot_icall_Dictionary_Add); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Clear", (void *)godot_icall_Dictionary_Clear); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Contains", (void *)godot_icall_Dictionary_Contains); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_ContainsKey", (void *)godot_icall_Dictionary_ContainsKey); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove); + mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue); +} diff --git a/modules/mono/glue/collections_glue.h b/modules/mono/glue/collections_glue.h new file mode 100644 index 0000000000..eb5ecfb725 --- /dev/null +++ b/modules/mono/glue/collections_glue.h @@ -0,0 +1,100 @@ +/*************************************************************************/ +/* collections_glue.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 COLLECTIONS_GLUE_H +#define COLLECTIONS_GLUE_H + +#include "core/array.h" + +#include "../mono_gd/gd_mono_marshal.h" + +// Array + +Array *godot_icall_Array_Ctor(); + +void godot_icall_Array_Dtor(Array *ptr); + +MonoObject *godot_icall_Array_At(Array *ptr, int index); + +void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value); + +int godot_icall_Array_Count(Array *ptr); + +void godot_icall_Array_Add(Array *ptr, MonoObject *item); + +void godot_icall_Array_Clear(Array *ptr); + +bool godot_icall_Array_Contains(Array *ptr, MonoObject *item); + +void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index); + +int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item); + +void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item); + +bool godot_icall_Array_Remove(Array *ptr, MonoObject *item); + +void godot_icall_Array_RemoveAt(Array *ptr, int index); + +// Dictionary + +Dictionary *godot_icall_Dictionary_Ctor(); + +void godot_icall_Dictionary_Dtor(Dictionary *ptr); + +MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key); + +void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value); + +Array *godot_icall_Dictionary_Keys(Dictionary *ptr); + +Array *godot_icall_Dictionary_Values(Dictionary *ptr); + +int godot_icall_Dictionary_Count(Dictionary *ptr); + +void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value); + +void godot_icall_Dictionary_Clear(Dictionary *ptr); + +bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value); + +bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key); + +bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key); + +bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value); + +bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value); + +// Register internal calls + +void godot_register_collections_icalls(); + +#endif // COLLECTIONS_GLUE_H diff --git a/modules/mono/glue/cs_files/Array.cs b/modules/mono/glue/cs_files/Array.cs new file mode 100644 index 0000000000..51f57daef4 --- /dev/null +++ b/modules/mono/glue/cs_files/Array.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Godot +{ + class ArraySafeHandle : SafeHandle + { + public ArraySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + this.handle = handle; + } + + public override bool IsInvalid + { + get + { + return handle == IntPtr.Zero; + } + } + + protected override bool ReleaseHandle() + { + Array.godot_icall_Array_Dtor(handle); + return true; + } + } + + public class Array : IList<object>, ICollection<object>, IEnumerable<object>, IDisposable + { + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Array_Ctor(); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Dtor(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_Array_At(IntPtr ptr, int index); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_Array_Count(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Add(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Clear(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, object[] array, int arrayIndex); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_Insert(IntPtr ptr, int index, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Array_Remove(IntPtr ptr, object item); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Array_RemoveAt(IntPtr ptr, int index); + + ArraySafeHandle safeHandle; + bool disposed = false; + + public Array() + { + safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); + } + + internal Array(ArraySafeHandle handle) + { + safeHandle = handle; + } + + internal Array(IntPtr handle) + { + safeHandle = new ArraySafeHandle(handle); + } + + internal IntPtr GetPtr() + { + return safeHandle.DangerousGetHandle(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (safeHandle != null) + { + safeHandle.Dispose(); + safeHandle = null; + } + + disposed = true; + } + + public object this[int index] + { + get + { + return godot_icall_Array_At(GetPtr(), index); + } + set + { + godot_icall_Array_SetAt(GetPtr(), index, value); + } + } + + public int Count + { + get + { + return godot_icall_Array_Count(GetPtr()); + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public void Add(object item) + { + godot_icall_Array_Add(GetPtr(), item); + } + + public void Clear() + { + godot_icall_Array_Clear(GetPtr()); + } + + public bool Contains(object item) + { + return godot_icall_Array_Contains(GetPtr(), item); + } + + public void CopyTo(object[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); + + // Internal call may throw ArgumentException + godot_icall_Array_CopyTo(GetPtr(), array, arrayIndex); + } + + public IEnumerator<object> GetEnumerator() + { + int count = Count; + + for (int i = 0; i < count; i++) + { + yield return godot_icall_Array_At(GetPtr(), i); + } + } + + public int IndexOf(object item) + { + return godot_icall_Array_IndexOf(GetPtr(), item); + } + + public void Insert(int index, object item) + { + godot_icall_Array_Insert(GetPtr(), index, item); + } + + public bool Remove(object item) + { + return godot_icall_Array_Remove(GetPtr(), item); + } + + public void RemoveAt(int index) + { + godot_icall_Array_RemoveAt(GetPtr(), index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T> + { + Array objectArray; + + public Array() + { + objectArray = new Array(); + } + + public Array(Array array) + { + objectArray = array; + } + + internal Array(IntPtr handle) + { + objectArray = new Array(handle); + } + + internal Array(ArraySafeHandle handle) + { + objectArray = new Array(handle); + } + + public static explicit operator Array(Array<T> from) + { + return from.objectArray; + } + + public T this[int index] + { + get + { + return (T)objectArray[index]; + } + set + { + objectArray[index] = value; + } + } + + public int Count + { + get + { + return objectArray.Count; + } + } + + public bool IsReadOnly + { + get + { + return objectArray.IsReadOnly; + } + } + + public void Add(T item) + { + objectArray.Add(item); + } + + public void Clear() + { + objectArray.Clear(); + } + + public bool Contains(T item) + { + return objectArray.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); + + // TODO This may be quite slow because every element access is an internal call. + // It could be moved entirely to an internal call if we find out how to do the cast there. + + int count = objectArray.Count; + + if (array.Length < (arrayIndex + count)) + throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + + for (int i = 0; i < count; i++) + { + array[arrayIndex] = (T)objectArray[i]; + arrayIndex++; + } + } + + public IEnumerator<T> GetEnumerator() + { + int count = objectArray.Count; + + for (int i = 0; i < count; i++) + { + yield return (T)objectArray[i]; + } + } + + public int IndexOf(T item) + { + return objectArray.IndexOf(item); + } + + public void Insert(int index, T item) + { + objectArray.Insert(index, item); + } + + public bool Remove(T item) + { + return objectArray.Remove(item); + } + + public void RemoveAt(int index) + { + objectArray.RemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/modules/mono/glue/cs_files/Basis.cs b/modules/mono/glue/cs_files/Basis.cs index aa49a5e04f..c280d32c61 100644 --- a/modules/mono/glue/cs_files/Basis.cs +++ b/modules/mono/glue/cs_files/Basis.cs @@ -343,17 +343,17 @@ namespace Godot { var tr = this; - real_t temp = this[0, 1]; - this[0, 1] = this[1, 0]; - this[1, 0] = temp; + real_t temp = tr[0, 1]; + tr[0, 1] = tr[1, 0]; + tr[1, 0] = temp; - temp = this[0, 2]; - this[0, 2] = this[2, 0]; - this[2, 0] = temp; + temp = tr[0, 2]; + tr[0, 2] = tr[2, 0]; + tr[2, 0] = temp; - temp = this[1, 2]; - this[1, 2] = this[2, 1]; - this[2, 1] = temp; + temp = tr[1, 2]; + tr[1, 2] = tr[2, 1]; + tr[2, 1] = temp; return tr; } diff --git a/modules/mono/glue/cs_files/Dictionary.cs b/modules/mono/glue/cs_files/Dictionary.cs new file mode 100644 index 0000000000..57a1960ad9 --- /dev/null +++ b/modules/mono/glue/cs_files/Dictionary.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Godot +{ + class DictionarySafeHandle : SafeHandle + { + public DictionarySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + this.handle = handle; + } + + public override bool IsInvalid + { + get + { + return handle == IntPtr.Zero; + } + } + + protected override bool ReleaseHandle() + { + Dictionary.godot_icall_Dictionary_Dtor(handle); + return true; + } + } + + public class Dictionary : + IDictionary<object, object>, + ICollection<KeyValuePair<object, object>>, + IEnumerable<KeyValuePair<object, object>>, + IDisposable + { + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Dictionary_Ctor(); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_Dtor(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Dictionary_Values(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_Dictionary_Count(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_Clear(IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); + + DictionarySafeHandle safeHandle; + bool disposed = false; + + public Dictionary() + { + safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); + } + + internal Dictionary(DictionarySafeHandle handle) + { + safeHandle = handle; + } + + internal Dictionary(IntPtr handle) + { + safeHandle = new DictionarySafeHandle(handle); + } + + internal IntPtr GetPtr() + { + return safeHandle.DangerousGetHandle(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (safeHandle != null) + { + safeHandle.Dispose(); + safeHandle = null; + } + + disposed = true; + } + + public object this[object key] + { + get + { + return godot_icall_Dictionary_GetValue(GetPtr(), key); + } + set + { + godot_icall_Dictionary_SetValue(GetPtr(), key, value); + } + } + + public ICollection<object> Keys + { + get + { + IntPtr handle = godot_icall_Dictionary_Keys(GetPtr()); + return new Array(new ArraySafeHandle(handle)); + } + } + + public ICollection<object> Values + { + get + { + IntPtr handle = godot_icall_Dictionary_Values(GetPtr()); + return new Array(new ArraySafeHandle(handle)); + } + } + + public int Count + { + get + { + return godot_icall_Dictionary_Count(GetPtr()); + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public void Add(object key, object value) + { + godot_icall_Dictionary_Add(GetPtr(), key, value); + } + + public void Add(KeyValuePair<object, object> item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + godot_icall_Dictionary_Clear(GetPtr()); + } + + public bool Contains(KeyValuePair<object, object> item) + { + return godot_icall_Dictionary_Contains(GetPtr(), item.Key, item.Value); + } + + public bool ContainsKey(object key) + { + return godot_icall_Dictionary_ContainsKey(GetPtr(), key); + } + + public void CopyTo(KeyValuePair<object, object>[] array, int arrayIndex) + { + // TODO 3 internal calls, can reduce to 1 + Array keys = (Array)Keys; + Array values = (Array)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + array[arrayIndex] = new KeyValuePair<object, object>(keys[i], values[i]); + arrayIndex++; + } + } + + public IEnumerator<KeyValuePair<object, object>> GetEnumerator() + { + // TODO 3 internal calls, can reduce to 1 + Array keys = (Array)Keys; + Array values = (Array)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + yield return new KeyValuePair<object, object>(keys[i], values[i]); + } + } + + public bool Remove(object key) + { + return godot_icall_Dictionary_RemoveKey(GetPtr(), key); + } + + public bool Remove(KeyValuePair<object, object> item) + { + return godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value); + } + + public bool TryGetValue(object key, out object value) + { + object retValue; + bool found = godot_icall_Dictionary_TryGetValue(GetPtr(), key, out retValue); + value = found ? retValue : default(object); + return found; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + + public class Dictionary<TKey, TValue> : + IDictionary<TKey, TValue>, + ICollection<KeyValuePair<TKey, TValue>>, + IEnumerable<KeyValuePair<TKey, TValue>> + { + Dictionary objectDict; + + public Dictionary() + { + objectDict = new Dictionary(); + } + + public Dictionary(Dictionary dictionary) + { + objectDict = dictionary; + } + + internal Dictionary(IntPtr handle) + { + objectDict = new Dictionary(handle); + } + + internal Dictionary(DictionarySafeHandle handle) + { + objectDict = new Dictionary(handle); + } + + public static explicit operator Dictionary(Dictionary<TKey, TValue> from) + { + return from.objectDict; + } + + public TValue this[TKey key] + { + get + { + return (TValue)objectDict[key]; + } + set + { + objectDict[key] = value; + } + } + + public ICollection<TKey> Keys + { + get + { + IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(objectDict.GetPtr()); + return new Array<TKey>(new ArraySafeHandle(handle)); + } + } + + public ICollection<TValue> Values + { + get + { + IntPtr handle = Dictionary.godot_icall_Dictionary_Values(objectDict.GetPtr()); + return new Array<TValue>(new ArraySafeHandle(handle)); + } + } + + public int Count + { + get + { + return objectDict.Count; + } + } + + public bool IsReadOnly + { + get + { + return objectDict.IsReadOnly; + } + } + + public void Add(TKey key, TValue value) + { + objectDict.Add(key, value); + } + + public void Add(KeyValuePair<TKey, TValue> item) + { + objectDict.Add(item.Key, item.Value); + } + + public void Clear() + { + objectDict.Clear(); + } + + public bool Contains(KeyValuePair<TKey, TValue> item) + { + return objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); + } + + public bool ContainsKey(TKey key) + { + return objectDict.ContainsKey(key); + } + + public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) + { + // TODO 3 internal calls, can reduce to 1 + Array<TKey> keys = (Array<TKey>)Keys; + Array<TValue> values = (Array<TValue>)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + array[arrayIndex] = new KeyValuePair<TKey, TValue>(keys[i], values[i]); + arrayIndex++; + } + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + // TODO 3 internal calls, can reduce to 1 + Array<TKey> keys = (Array<TKey>)Keys; + Array<TValue> values = (Array<TValue>)Values; + int count = Count; + + for (int i = 0; i < count; i++) + { + // TODO 2 internal calls, can reduce to 1 + yield return new KeyValuePair<TKey, TValue>(keys[i], values[i]); + } + } + + public bool Remove(TKey key) + { + return objectDict.Remove(key); + } + + public bool Remove(KeyValuePair<TKey, TValue> item) + { + return objectDict.Remove(new KeyValuePair<object, object>(item.Key, item.Value)); + } + + public bool TryGetValue(TKey key, out TValue value) + { + object retValue; + bool found = objectDict.TryGetValue(key, out retValue); + value = found ? (TValue)retValue : default(TValue); + return found; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/modules/mono/glue/cs_files/GD.cs b/modules/mono/glue/cs_files/GD.cs index ec1534cb9a..e2457ff98b 100644 --- a/modules/mono/glue/cs_files/GD.cs +++ b/modules/mono/glue/cs_files/GD.cs @@ -1,4 +1,9 @@ using System; +#if REAL_T_IS_DOUBLE +using real_t = System.Double; +#else +using real_t = System.Single; +#endif // TODO: Add comments describing what this class does. It is not obvious. @@ -16,22 +21,22 @@ namespace Godot return NativeCalls.godot_icall_Godot_convert(what, type); } - public static float Db2Linear(float db) + public static real_t Db2Linear(real_t db) { - return (float)Math.Exp(db * 0.11512925464970228420089957273422); + return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); } - public static float Dectime(float value, float amount, float step) + public static real_t DecTime(real_t value, real_t amount, real_t step) { - float sgn = value < 0 ? -1.0f : 1.0f; - float val = Mathf.Abs(value); + real_t sgn = Mathf.Sign(value); + real_t val = Mathf.Abs(value); val -= amount * step; - if (val < 0.0f) - val = 0.0f; + if (val < 0) + val = 0; return val * sgn; } - public static FuncRef Funcref(Object instance, string funcname) + public static FuncRef FuncRef(Object instance, string funcname) { var ret = new FuncRef(); ret.SetInstance(instance); @@ -49,9 +54,9 @@ namespace Godot return NativeCalls.godot_icall_Godot_instance_from_id(instanceId); } - public static double Linear2Db(double linear) + public static real_t Linear2Db(real_t linear) { - return Math.Log(linear) * 8.6858896380650365530225783783321; + return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321); } public static Resource Load(string path) @@ -69,22 +74,22 @@ namespace Godot Print(System.Environment.StackTrace); } - public static void Printerr(params object[] what) + public static void PrintErr(params object[] what) { NativeCalls.godot_icall_Godot_printerr(what); } - public static void Printraw(params object[] what) + public static void PrintRaw(params object[] what) { NativeCalls.godot_icall_Godot_printraw(what); } - public static void Prints(params object[] what) + public static void PrintS(params object[] what) { NativeCalls.godot_icall_Godot_prints(what); } - public static void Printt(params object[] what) + public static void PrintT(params object[] what) { NativeCalls.godot_icall_Godot_printt(what); } @@ -183,7 +188,7 @@ namespace Godot return NativeCalls.godot_icall_Godot_var2str(var); } - public static WeakRef Weakref(Object obj) + public static WeakRef WeakRef(Object obj) { return NativeCalls.godot_icall_Godot_weakref(Object.GetPtr(obj)); } diff --git a/modules/mono/glue/cs_files/MarshalUtils.cs b/modules/mono/glue/cs_files/MarshalUtils.cs index ff4477cc6c..6ad4b3dcb2 100644 --- a/modules/mono/glue/cs_files/MarshalUtils.cs +++ b/modules/mono/glue/cs_files/MarshalUtils.cs @@ -1,36 +1,17 @@ using System; -using System.Collections.Generic; namespace Godot { - internal static class MarshalUtils + static class MarshalUtils { - private static Dictionary<object, object> ArraysToDictionary(object[] keys, object[] values) + static bool IsArrayGenericType(Type type) { - var ret = new Dictionary<object, object>(); - - for (int i = 0; i < keys.Length; i++) - { - ret.Add(keys[i], values[i]); - } - - return ret; - } - - private static void DictionaryToArrays(Dictionary<object, object> from, out object[] keysTo, out object[] valuesTo) - { - var keys = from.Keys; - keysTo = new object[keys.Count]; - keys.CopyTo(keysTo, 0); - - var values = from.Values; - valuesTo = new object[values.Count]; - values.CopyTo(valuesTo, 0); + return type.GetGenericTypeDefinition() == typeof(Array<>); } - private static Type GetDictionaryType() + static bool IsDictionaryGenericType(Type type) { - return typeof(Dictionary<object, object>); + return type.GetGenericTypeDefinition() == typeof(Dictionary<, >); } } } diff --git a/modules/mono/glue/cs_files/VERSION.txt b/modules/mono/glue/cs_files/VERSION.txt index b8626c4cff..7ed6ff82de 100755 --- a/modules/mono/glue/cs_files/VERSION.txt +++ b/modules/mono/glue/cs_files/VERSION.txt @@ -1 +1 @@ -4 +5 diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h index cedc8e9992..6a6f3062b4 100644 --- a/modules/mono/glue/glue_header.h +++ b/modules/mono/glue/glue_header.h @@ -29,6 +29,7 @@ /*************************************************************************/ #include "builtin_types_glue.h" +#include "collections_glue.h" #include "../csharp_script.h" #include "../mono_gd/gd_mono_class.h" @@ -308,4 +309,5 @@ MonoObject *godot_icall_Godot_weakref(Object *p_obj) { void godot_register_header_icalls() { godot_register_builtin_type_icalls(); + godot_register_collections_icalls(); } diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index bc84f43b4f..0f4e211be5 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -805,9 +805,9 @@ void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) { void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) { -// This method will be called by the runtime when a thrown exception is not handled. -// It won't be called when we manually treat a thrown exception as unhandled. -// We assume the exception was already printed before calling this hook. + // This method will be called by the runtime when a thrown exception is not handled. + // It won't be called when we manually treat a thrown exception as unhandled. + // We assume the exception was already printed before calling this hook. #ifdef DEBUG_ENABLED GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 66339d7ae6..e2597a7d42 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -33,23 +33,37 @@ #include <mono/metadata/attrdefs.h> #include "gd_mono_assembly.h" +#include "gd_mono_marshal.h" -MonoType *GDMonoClass::get_raw_type(GDMonoClass *p_class) { +String GDMonoClass::get_full_name(MonoClass *p_mono_class) { + // mono_type_get_full_name is not exposed to embedders, but this seems to do the job + MonoReflectionType *type_obj = mono_type_get_object(mono_domain_get(), get_mono_type(p_mono_class)); - return mono_class_get_type(p_class->get_mono_ptr()); -} + MonoException *exc = NULL; + GD_MONO_BEGIN_RUNTIME_INVOKE; + MonoString *str = mono_object_to_string((MonoObject *)type_obj, (MonoObject **)&exc); + GD_MONO_END_RUNTIME_INVOKE; + UNLIKELY_UNHANDLED_EXCEPTION(exc); -bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { + return GDMonoMarshal::mono_string_to_godot(str); +} - return mono_class_is_assignable_from(mono_class, p_from->mono_class); +MonoType *GDMonoClass::get_mono_type(MonoClass *p_mono_class) { + return mono_class_get_type(p_mono_class); } String GDMonoClass::get_full_name() const { + return get_full_name(mono_class); +} - String res = namespace_name; - if (res.length()) - res += "."; - return res + class_name; +MonoType *GDMonoClass::get_mono_type() { + // Care, you cannot compare MonoType pointers + return get_mono_type(mono_class); +} + +bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { + + return mono_class_is_assignable_from(mono_class, p_from->mono_class); } GDMonoClass *GDMonoClass::get_parent_class() { diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h index 417c138594..f81ab84cd0 100644 --- a/modules/mono/mono_gd/gd_mono_class.h +++ b/modules/mono/mono_gd/gd_mono_class.h @@ -98,7 +98,11 @@ class GDMonoClass { GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly); public: - static MonoType *get_raw_type(GDMonoClass *p_class); + static String get_full_name(MonoClass *p_mono_class); + static MonoType *get_mono_type(MonoClass *p_mono_class); + + String get_full_name() const; + MonoType *get_mono_type(); bool is_assignable_from(GDMonoClass *p_from) const; @@ -108,8 +112,6 @@ public: _FORCE_INLINE_ MonoClass *get_mono_ptr() const { return mono_class; } _FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; } - String get_full_name() const; - GDMonoClass *get_parent_class(); #ifdef TOOLS_ENABLED diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 3b91777ed4..d3a673dc1b 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -148,7 +148,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type()); if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) SET_FROM_ARRAY_AND_BREAK(Array); @@ -200,6 +200,18 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } + if (CACHED_CLASS(Dictionary) == type_class) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary)); + mono_field_set_value(p_object, mono_field, managed); + break; + } + + if (CACHED_CLASS(Array) == type_class) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); + mono_field_set_value(p_object, mono_field, managed); + break; + } + ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name()); ERR_FAIL(); } break; @@ -248,10 +260,13 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } case Variant::DICTIONARY: { - MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary()); + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary)); + mono_field_set_value(p_object, mono_field, managed); + } break; + case Variant::ARRAY: { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); mono_field_set_value(p_object, mono_field, managed); } break; - case Variant::ARRAY: SET_FROM_ARRAY_AND_BREAK(Array); case Variant::POOL_BYTE_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolByteArray); case Variant::POOL_INT_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolIntArray); case Variant::POOL_REAL_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolRealArray); @@ -265,8 +280,28 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == type.type_class->get_mono_ptr()) { - MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary()); + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); + + MonoException *exc = NULL; + + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class); + mono_field_set_value(p_object, mono_field, managed); + break; + } + + exc = NULL; + + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class); mono_field_set_value(p_object, mono_field, managed); break; } diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/mono_gd/gd_mono_header.h index 2b5110f0b9..72a5439044 100644 --- a/modules/mono/mono_gd/gd_mono_header.h +++ b/modules/mono/mono_gd/gd_mono_header.h @@ -45,7 +45,8 @@ struct ManagedType { GDMonoClass *type_class; ManagedType() { - type_class = 0; + type_encoding = 0; + type_class = NULL; } }; diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 31c5bbb2fb..de91e71bab 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -120,7 +120,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type()); if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) return Variant::ARRAY; @@ -162,12 +162,36 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) { if (CACHED_CLASS(RID) == type_class) { return Variant::_RID; } + + if (CACHED_CLASS(Dictionary) == type_class) { + return Variant::DICTIONARY; + } + + if (CACHED_CLASS(Array) == type_class) { + return Variant::ARRAY; + } } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_mono_ptr()) { + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); + + MonoException *exc = NULL; + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { return Variant::DICTIONARY; } + + exc = NULL; + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + return Variant::ARRAY; + } } break; default: { @@ -216,6 +240,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var) { ManagedType type; type.type_encoding = MONO_TYPE_OBJECT; + // type.type_class is not needed when we specify the MONO_TYPE_OBJECT encoding return variant_to_mono_object(p_var, type); } @@ -315,7 +340,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type()); if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) return (MonoObject *)Array_to_mono_array(p_var->operator Array()); @@ -360,6 +385,14 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty if (CACHED_CLASS(RID) == type_class) { return GDMonoUtils::create_managed_from(p_var->operator RID()); } + + if (CACHED_CLASS(Dictionary) == type_class) { + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary)); + } + + if (CACHED_CLASS(Array) == type_class) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); + } } break; case MONO_TYPE_OBJECT: { // Variant @@ -411,9 +444,9 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *()); } case Variant::DICTIONARY: - return Dictionary_to_mono_object(p_var->operator Dictionary()); + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary)); case Variant::ARRAY: - return (MonoObject *)Array_to_mono_array(p_var->operator Array()); + return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array)); case Variant::POOL_BYTE_ARRAY: return (MonoObject *)PoolByteArray_to_mono_array(p_var->operator PoolByteArray()); case Variant::POOL_INT_ARRAY: @@ -433,8 +466,24 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_mono_ptr()) { - return Dictionary_to_mono_object(p_var->operator Dictionary()); + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type()); + + MonoException *exc = NULL; + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { + return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class); + } + + exc = NULL; + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + return GDMonoUtils::create_managed_from(p_var->operator Array(), p_type.type_class); } } break; } break; @@ -452,7 +501,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) { GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_object_get_class(p_obj)); ERR_FAIL_COND_V(!tclass, Variant()); - MonoType *raw_type = tclass->get_raw_type(tclass); + MonoType *raw_type = tclass->get_mono_type(); ManagedType type; @@ -531,7 +580,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) { case MONO_TYPE_ARRAY: case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(type.type_class)); + MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type()); if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) return mono_array_to_Array((MonoArray *)p_obj); @@ -579,11 +628,51 @@ Variant mono_object_to_variant(MonoObject *p_obj) { RID *ptr = unbox<RID *>(CACHED_FIELD(RID, ptr)->get_value(p_obj)); return ptr ? Variant(*ptr) : Variant(); } + + if (CACHED_CLASS(Array) == type_class) { + MonoException *exc = NULL; + GDMonoUtils::Array_GetPtr get_ptr = CACHED_METHOD_THUNK(Array, GetPtr); + Array *ptr = get_ptr(p_obj, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + return ptr ? Variant(*ptr) : Variant(); + } + + if (CACHED_CLASS(Dictionary) == type_class) { + MonoException *exc = NULL; + GDMonoUtils::Dictionary_GetPtr get_ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr); + Dictionary *ptr = get_ptr(p_obj, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + return ptr ? Variant(*ptr) : Variant(); + } } break; case MONO_TYPE_GENERICINST: { - if (CACHED_RAW_MONO_CLASS(Dictionary) == type.type_class->get_mono_ptr()) { - return mono_object_to_Dictionary(p_obj); + MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type()); + + MonoException *exc = NULL; + + GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); + MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_dict) { + MonoException *exc = NULL; + MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + return *unbox<Dictionary *>(ret); + } + + exc = NULL; + + GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); + MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + if (is_array) { + MonoException *exc = NULL; + MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + return *unbox<Array *>(ret); } } break; } @@ -822,66 +911,4 @@ PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) { return ret; } - -MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict) { - MonoArray *keys = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size()); - MonoArray *values = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size()); - - int i = 0; - const Variant *dkey = NULL; - while ((dkey = p_dict.next(dkey))) { - mono_array_set(keys, MonoObject *, i, variant_to_mono_object(dkey)); - mono_array_set(values, MonoObject *, i, variant_to_mono_object(p_dict[*dkey])); - i++; - } - - GDMonoUtils::MarshalUtils_ArraysToDict arrays_to_dict = CACHED_METHOD_THUNK(MarshalUtils, ArraysToDictionary); - - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = arrays_to_dict(keys, values, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL_V(NULL); - } - - return ret; -} - -Dictionary mono_object_to_Dictionary(MonoObject *p_dict) { - Dictionary ret; - - if (!p_dict) - return ret; - - GDMonoUtils::MarshalUtils_DictToArrays dict_to_arrays = CACHED_METHOD_THUNK(MarshalUtils, DictionaryToArrays); - - MonoArray *keys = NULL; - MonoArray *values = NULL; - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - dict_to_arrays(p_dict, &keys, &values, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL_V(Dictionary()); - } - - int length = mono_array_length(keys); - - for (int i = 0; i < length; i++) { - MonoObject *key_obj = mono_array_get(keys, MonoObject *, i); - MonoObject *value_obj = mono_array_get(values, MonoObject *, i); - - Variant key = key_obj ? mono_object_to_variant(key_obj) : Variant(); - Variant value = value_obj ? mono_object_to_variant(value_obj) : Variant(); - - ret[key] = value; - } - - return ret; -} -} +} // namespace GDMonoMarshal diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h index 6572408ab5..464f584a0a 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ b/modules/mono/mono_gd/gd_mono_marshal.h @@ -143,11 +143,6 @@ PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array); MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array); PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array); -// Dictionary - -MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict); -Dictionary mono_object_to_Dictionary(MonoObject *p_dict); - #ifdef YOLO_COPY #define MARSHALLED_OUT(m_t, m_in, m_out) m_t *m_out = (m_t *)&m_in; #define MARSHALLED_IN(m_t, m_in, m_out) m_t m_out = *reinterpret_cast<m_t *>(m_in); diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp index 1f837a2d78..a1c710c26c 100644 --- a/modules/mono/mono_gd/gd_mono_property.cpp +++ b/modules/mono/mono_gd/gd_mono_property.cpp @@ -139,23 +139,8 @@ bool GDMonoProperty::has_setter() { } void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) { - MonoMethod *prop_method = mono_property_get_set_method(mono_property); - - MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1); - mono_array_set(params, MonoObject *, 0, p_value); - - MonoException *exc = NULL; - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_runtime_invoke_array(prop_method, p_object, params, (MonoObject **)&exc); - GD_MONO_END_RUNTIME_INVOKE; - - if (exc) { - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } + void *params[1] = { p_value }; + set_value(p_object, params, r_exc); } void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoException **r_exc) { diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index a229552b76..7cd922138f 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -87,6 +87,8 @@ void MonoCache::clear_members() { method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL; #endif + class_KeyNotFoundException = NULL; + rawclass_Dictionary = NULL; class_Vector2 = NULL; @@ -107,6 +109,8 @@ void MonoCache::clear_members() { class_Control = NULL; class_Spatial = NULL; class_WeakRef = NULL; + class_Array = NULL; + class_Dictionary = NULL; class_MarshalUtils = NULL; #ifdef DEBUG_ENABLED @@ -134,8 +138,10 @@ void MonoCache::clear_members() { field_Image_ptr = NULL; field_RID_ptr = NULL; - methodthunk_MarshalUtils_DictionaryToArrays = NULL; - methodthunk_MarshalUtils_ArraysToDictionary = NULL; + methodthunk_Array_GetPtr = NULL; + methodthunk_Dictionary_GetPtr = NULL; + methodthunk_MarshalUtils_IsArrayGenericType = NULL; + methodthunk_MarshalUtils_IsDictionaryGenericType = NULL; methodthunk_SignalAwaiter_SignalCallback = NULL; methodthunk_SignalAwaiter_FailureCallback = NULL; methodthunk_GodotTaskScheduler_Activate = NULL; @@ -175,6 +181,8 @@ void update_corlib_cache() { CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true)); #endif + CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException")); + mono_cache.corlib_cache_updated = true; } @@ -198,6 +206,8 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial)); CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef)); + CACHE_CLASS_AND_CHECK(Array, GODOT_API_CLASS(Array)); + CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_CLASS(Dictionary)); CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); #ifdef DEBUG_ENABLED @@ -224,8 +234,10 @@ void update_godot_api_cache() { CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryToArrays, (MarshalUtils_DictToArrays)CACHED_CLASS(MarshalUtils)->get_method("DictionaryToArrays", 3)->get_thunk()); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArraysToDictionary, (MarshalUtils_ArraysToDict)CACHED_CLASS(MarshalUtils)->get_method("ArraysToDictionary", 2)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, (Array_GetPtr)GODOT_API_CLASS(Array)->get_method("GetPtr", 0)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, (Dictionary_GetPtr)GODOT_API_CLASS(Dictionary)->get_method("GetPtr", 0)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsArrayGenericType, (IsArrayGenericType)GODOT_API_CLASS(MarshalUtils)->get_method("IsArrayGenericType", 1)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsDictionaryGenericType, (IsDictionaryGenericType)GODOT_API_CLASS(MarshalUtils)->get_method("IsDictionaryGenericType", 1)->get_thunk()); CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, (SignalAwaiter_SignalCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("SignalCallback", 1)->get_thunk()); CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0)->get_thunk()); CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)->get_thunk()); @@ -234,24 +246,9 @@ void update_godot_api_cache() { CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method("GetStackFrameInfo", 4)->get_thunk()); #endif - { - /* - * TODO Right now we only support Dictionary<object, object>. - * It would be great if we could support other key/value types - * without forcing the user to copy the entries. - */ - GDMonoMethod *method_get_dict_type = CACHED_CLASS(MarshalUtils)->get_method("GetDictionaryType", 0); - ERR_FAIL_NULL(method_get_dict_type); - MonoReflectionType *dict_refl_type = (MonoReflectionType *)method_get_dict_type->invoke(NULL); - ERR_FAIL_NULL(dict_refl_type); - MonoType *dict_type = mono_reflection_type_get_type(dict_refl_type); - ERR_FAIL_NULL(dict_type); - - CACHE_RAW_MONO_CLASS_AND_CHECK(Dictionary, mono_class_from_mono_type(dict_type)); - } - + // TODO Move to CSharpLanguage::init() MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); - mono_runtime_object_init(task_scheduler); + GDMonoUtils::runtime_object_init(task_scheduler); mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); mono_cache.godot_api_cache_updated = true; @@ -304,6 +301,12 @@ MonoThread *get_current_thread() { return mono_thread_current(); } +void runtime_object_init(MonoObject *p_this_obj) { + GD_MONO_BEGIN_RUNTIME_INVOKE; + mono_runtime_object_init(p_this_obj); + GD_MONO_END_RUNTIME_INVOKE; +} + GDMonoClass *get_object_class(MonoObject *p_object) { return GDMono::get_singleton()->get_class(mono_object_get_class(p_object)); } @@ -358,7 +361,7 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); // Construct - mono_runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object); return mono_object; } @@ -368,7 +371,7 @@ MonoObject *create_managed_from(const NodePath &p_from) { ERR_FAIL_NULL_V(mono_object, NULL); // Construct - mono_runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object); CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from))); @@ -380,13 +383,73 @@ MonoObject *create_managed_from(const RID &p_from) { ERR_FAIL_NULL_V(mono_object, NULL); // Construct - mono_runtime_object_init(mono_object); + GDMonoUtils::runtime_object_init(mono_object); CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from))); return mono_object; } +MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + ERR_FAIL_NULL_V(mono_object, NULL); + + // Search constructor that takes a pointer as parameter + MonoMethod *m; + void *iter = NULL; + while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { + if (strcmp(mono_method_get_name(m), ".ctor") == 0) { + MonoMethodSignature *sig = mono_method_signature(m); + void *front = NULL; + if (mono_signature_get_param_count(sig) == 1 && + mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { + break; + } + } + } + + CRASH_COND(m == NULL); + + Array *new_array = memnew(Array(p_from)); + void *args[1] = { &new_array }; + + MonoException *exc = NULL; + mono_runtime_invoke(m, mono_object, args, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + return mono_object; +} + +MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) { + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr()); + ERR_FAIL_NULL_V(mono_object, NULL); + + // Search constructor that takes a pointer as parameter + MonoMethod *m; + void *iter = NULL; + while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { + if (strcmp(mono_method_get_name(m), ".ctor") == 0) { + MonoMethodSignature *sig = mono_method_signature(m); + void *front = NULL; + if (mono_signature_get_param_count(sig) == 1 && + mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { + break; + } + } + } + + CRASH_COND(m == NULL); + + Dictionary *new_dict = memnew(Dictionary(p_from)); + void *args[1] = { &new_dict }; + + MonoException *exc = NULL; + mono_runtime_invoke(m, mono_object, args, (MonoObject **)&exc); + UNLIKELY_UNHANDLED_EXCEPTION(exc); + + return mono_object; +} + MonoDomain *create_domain(const String &p_friendly_name) { MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL); @@ -400,10 +463,10 @@ MonoDomain *create_domain(const String &p_friendly_name) { return domain; } -String get_exception_name_and_message(MonoException *p_ex) { +String get_exception_name_and_message(MonoException *p_exc) { String res; - MonoClass *klass = mono_object_get_class((MonoObject *)p_ex); + MonoClass *klass = mono_object_get_class((MonoObject *)p_exc); MonoType *type = mono_class_get_type(klass); char *full_name = mono_type_full_name(type); @@ -413,12 +476,24 @@ String get_exception_name_and_message(MonoException *p_ex) { res += ": "; MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); - MonoString *msg = (MonoString *)mono_property_get_value(prop, (MonoObject *)p_ex, NULL, NULL); + GD_MONO_BEGIN_RUNTIME_INVOKE; + MonoString *msg = (MonoString *)mono_property_get_value(prop, (MonoObject *)p_exc, NULL, NULL); + GD_MONO_END_RUNTIME_INVOKE; res += GDMonoMarshal::mono_string_to_godot(msg); return res; } +void set_exception_message(MonoException *p_exc, String message) { + MonoClass *klass = mono_object_get_class((MonoObject *)p_exc); + MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); + MonoString *msg = GDMonoMarshal::mono_string_from_godot(message); + void *params[1] = { msg }; + GD_MONO_BEGIN_RUNTIME_INVOKE; + mono_property_set_value(prop, (MonoObject *)p_exc, params, NULL); + GD_MONO_END_RUNTIME_INVOKE; +} + void debug_print_unhandled_exception(MonoException *p_exc) { print_unhandled_exception(p_exc); debug_send_unhandled_exception_error(p_exc); diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index 4f8e5932cd..d6774ed41d 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -41,14 +41,24 @@ #include "object.h" #include "reference.h" +#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \ + if (unlikely(m_exc != NULL)) { \ + GDMonoUtils::debug_unhandled_exception(m_exc); \ + _UNREACHABLE_(); \ + } + namespace GDMonoUtils { -typedef MonoObject *(*MarshalUtils_DictToArrays)(MonoObject *, MonoArray **, MonoArray **, MonoObject **); -typedef MonoObject *(*MarshalUtils_ArraysToDict)(MonoArray *, MonoArray *, MonoObject **); +typedef Array *(*Array_GetPtr)(MonoObject *, MonoObject **); +typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoObject **); typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoObject **); typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoObject **); typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoObject **); typedef MonoArray *(*StackTrace_GetFrames)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **); +typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **); typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, MonoString **, MonoObject **); struct MonoCache { @@ -79,6 +89,8 @@ struct MonoCache { GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool; #endif + GDMonoClass *class_KeyNotFoundException; + MonoClass *rawclass_Dictionary; // ----------------------------------------------- @@ -100,6 +112,8 @@ struct MonoCache { GDMonoClass *class_Control; GDMonoClass *class_Spatial; GDMonoClass *class_WeakRef; + GDMonoClass *class_Array; + GDMonoClass *class_Dictionary; GDMonoClass *class_MarshalUtils; #ifdef DEBUG_ENABLED @@ -127,8 +141,10 @@ struct MonoCache { GDMonoField *field_Image_ptr; GDMonoField *field_RID_ptr; - MarshalUtils_DictToArrays methodthunk_MarshalUtils_DictionaryToArrays; - MarshalUtils_ArraysToDict methodthunk_MarshalUtils_ArraysToDictionary; + Array_GetPtr methodthunk_Array_GetPtr; + Dictionary_GetPtr methodthunk_Dictionary_GetPtr; + IsArrayGenericType methodthunk_MarshalUtils_IsArrayGenericType; + IsDictionaryGenericType methodthunk_MarshalUtils_IsDictionaryGenericType; SignalAwaiter_SignalCallback methodthunk_SignalAwaiter_SignalCallback; SignalAwaiter_FailureCallback methodthunk_SignalAwaiter_FailureCallback; GodotTaskScheduler_Activate methodthunk_GodotTaskScheduler_Activate; @@ -175,6 +191,8 @@ _FORCE_INLINE_ bool is_main_thread() { return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current(); } +void runtime_object_init(MonoObject *p_this_obj); + GDMonoClass *get_object_class(MonoObject *p_object); GDMonoClass *type_get_proxy_class(const StringName &p_type); GDMonoClass *get_class_native_base(GDMonoClass *p_class); @@ -183,10 +201,13 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa MonoObject *create_managed_from(const NodePath &p_from); MonoObject *create_managed_from(const RID &p_from); +MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class); +MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class); MonoDomain *create_domain(const String &p_friendly_name); -String get_exception_name_and_message(MonoException *p_ex); +String get_exception_name_and_message(MonoException *p_exc); +void set_exception_message(MonoException *p_exc, String message); void debug_print_unhandled_exception(MonoException *p_exc); void debug_send_unhandled_exception_error(MonoException *p_exc); diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 8174bccdb7..f5d35714e1 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -240,11 +240,11 @@ ResourceFormatPVR::ResourceFormatPVR() { Image::_image_compress_pvrtc2_func = _compress_pvrtc4; } - ///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// - //PVRTC decompressor, Based on PVRTC decompressor by IMGTEC. +//PVRTC decompressor, Based on PVRTC decompressor by IMGTEC. - ///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// #define PT_INDEX 2 #define BLK_Y_SIZE 4 diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 9331092171..9dea7a9c9e 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -1333,6 +1333,19 @@ VisualScript::VisualScript() { base_type = "Object"; } +Set<int> VisualScript::get_output_sequence_ports_connected(const String &edited_func, int from_node) { + List<VisualScript::SequenceConnection> *sc = memnew(List<VisualScript::SequenceConnection>); + get_sequence_connection_list(edited_func, sc); + Set<int> connected; + for (List<VisualScript::SequenceConnection>::Element *E = sc->front(); E; E = E->next()) { + if (E->get().from_node == from_node) { + connected.insert(E->get().from_output); + } + } + memdelete(sc); + return connected; +} + VisualScript::~VisualScript() { while (!functions.empty()) { @@ -2402,7 +2415,7 @@ void VisualScriptLanguage::make_template(const String &p_class_name, const Strin script->set_instance_base_type(p_base_class_name); } -bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { +bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const { return false; } diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index aaa6dfea11..2ad72a40c0 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -319,6 +319,7 @@ public: void custom_signal_swap_argument(const StringName &p_func, int p_argidx, int p_with_argidx); void remove_custom_signal(const StringName &p_name); void rename_custom_signal(const StringName &p_name, const StringName &p_new_name); + Set<int> get_output_sequence_ports_connected(const String &edited_func, int from_node); void get_custom_signal_list(List<StringName> *r_custom_signals) const; @@ -563,7 +564,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const; + virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 873cc293c9..ad6d32b567 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -33,8 +33,10 @@ #include "core/script_language.h" #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" +#include "object.h" #include "os/input.h" #include "os/keyboard.h" +#include "variant.h" #include "visual_script_expression.h" #include "visual_script_flow_control.h" #include "visual_script_func_nodes.h" @@ -1325,6 +1327,12 @@ void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { revert_on_drag = String(); //so we can still drag functions } + + Ref<InputEventKey> k = p_event; + if (k.is_valid() && k->get_scancode() == KEY_A && k->get_shift() && k->is_pressed()) { + new_connect_node_select->select_from_visual_script(String("")); + accept_event(); + } } void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) { @@ -1780,7 +1788,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da call->set_base_type(node->get_class()); n = call; - method_select->select_method_from_instance(node); + method_select->select_from_instance(node); selecting_method_id = base_id; } @@ -1917,7 +1925,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } } -void VisualScriptEditor::_selected_method(const String &p_method) { +void VisualScriptEditor::_selected_method(const String &p_method, const String &p_type) { Ref<VisualScriptFunctionCall> vsfc = script->get_node(edited_func, selecting_method_id); if (!vsfc.is_valid()) @@ -1962,22 +1970,16 @@ void VisualScriptEditor::_button_resource_previewed(const String &p_path, const void VisualScriptEditor::apply_code() { } -Ref<Script> VisualScriptEditor::get_edited_script() const { - +RES VisualScriptEditor::get_edited_resource() const { return script; } -Vector<String> VisualScriptEditor::get_functions() { - - return Vector<String>(); -} +void VisualScriptEditor::set_edited_resource(const RES &p_res) { -void VisualScriptEditor::set_edited_script(const Ref<Script> &p_script) { - - script = p_script; - signal_editor->script = p_script; + script = p_res; + signal_editor->script = script; signal_editor->undo_redo = undo_redo; - variable_editor->script = p_script; + variable_editor->script = script; variable_editor->undo_redo = undo_redo; script->connect("node_ports_changed", this, "_node_ports_changed"); @@ -1986,6 +1988,11 @@ void VisualScriptEditor::set_edited_script(const Ref<Script> &p_script) { _update_available_nodes(); } +Vector<String> VisualScriptEditor::get_functions() { + + return Vector<String>(); +} + void VisualScriptEditor::reload_text() { } @@ -2436,33 +2443,19 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro if (!vsn.is_valid()) return; - if (p_from_slot < vsn->get_output_sequence_port_count()) { + port_action_pos = p_release_pos; - port_action_popup->clear(); - port_action_popup->add_item(TTR("Condition"), CREATE_COND); - port_action_popup->add_item(TTR("Sequence"), CREATE_SEQUENCE); - port_action_popup->add_item(TTR("Switch"), CREATE_SWITCH); - port_action_popup->add_item(TTR("Iterator"), CREATE_ITERATOR); - port_action_popup->add_item(TTR("While"), CREATE_WHILE); - port_action_popup->add_item(TTR("Return"), CREATE_RETURN); + if (p_from_slot < vsn->get_output_sequence_port_count()) { port_action_node = p_from.to_int(); port_action_output = p_from_slot; - + _port_action_menu(CREATE_ACTION); } else { - port_action_popup->clear(); - port_action_popup->add_item(TTR("Call"), CREATE_CALL); - port_action_popup->add_item(TTR("Get"), CREATE_GET); - port_action_popup->add_item(TTR("Set"), CREATE_SET); port_action_output = p_from_slot - vsn->get_output_sequence_port_count(); port_action_node = p_from.to_int(); + _port_action_menu(CREATE_CALL_SET_GET); } - - port_action_pos = p_release_pos; - port_action_popup->set_size(Size2(1, 1)); - port_action_popup->set_position(graph->get_global_position() + p_release_pos); - port_action_popup->popup(); } VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &visited_nodes) { @@ -2530,168 +2523,202 @@ void VisualScriptEditor::_port_action_menu(int p_option) { bool seq_connect = false; - Ref<VisualScriptNode> vnode; Set<int> vn; switch (p_option) { - case CREATE_CALL: { - + case CREATE_CALL_SET_GET: { Ref<VisualScriptFunctionCall> n; n.instance(); - vnode = n; VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); - if (tg.type == Variant::OBJECT) { - n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); - - if (tg.gdclass != StringName()) { - n->set_base_type(tg.gdclass); - } else { - n->set_base_type("Object"); - } + if (tg.gdclass != StringName()) { + n->set_base_type(tg.gdclass); + } else { + n->set_base_type("Object"); + } + String type_string = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + if (tg.type == Variant::OBJECT) { if (tg.script.is_valid()) { - n->set_base_script(tg.script->get_path()); - new_connect_node_select->select_method_from_script(tg.script); + new_connect_node_select->select_from_script(tg.script, ""); + } else if (type_string != String()) { + new_connect_node_select->select_from_base_type(type_string); } else { - new_connect_node_select->select_method_from_base_type(n->get_base_type()); + new_connect_node_select->select_from_base_type(n->get_base_type()); } - + } else if (tg.type == Variant::NIL) { + new_connect_node_select->select_from_base_type(""); } else { - n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); - n->set_basic_type(tg.type); - new_connect_node_select->select_method_from_basic_type(tg.type); + new_connect_node_select->select_from_basic_type(tg.type); } - } break; - case CREATE_SET: { - - Ref<VisualScriptPropertySet> n; - n.instance(); - vnode = n; - + case CREATE_ACTION: { + seq_connect = true; VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); - + PropertyInfo property_info = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output); if (tg.type == Variant::OBJECT) { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); - - if (tg.gdclass != StringName()) { - n->set_base_type(tg.gdclass); + if (property_info.type == Variant::OBJECT && property_info.hint_string != String()) { + new_connect_node_select->select_from_action(property_info.hint_string); } else { - n->set_base_type("Object"); + new_connect_node_select->select_from_action(""); } - - if (tg.script.is_valid()) { - n->set_base_script(tg.script->get_path()); - new_connect_node_select->select_property_from_script(tg.script); - } else { - new_connect_node_select->select_property_from_base_type(n->get_base_type()); - } - + } else if (tg.type == Variant::NIL) { + new_connect_node_select->select_from_action(""); } else { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE); - n->set_basic_type(tg.type); - new_connect_node_select->select_property_from_basic_type(tg.type); + new_connect_node_select->select_from_action(Variant::get_type_name(tg.type)); } } break; - case CREATE_GET: { + } +} - Ref<VisualScriptPropertyGet> n; - n.instance(); - vnode = n; +void VisualScriptEditor::new_node(Ref<VisualScriptNode> vnode, Vector2 ofs) { + Set<int> vn; + Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(this, "_update_graph", new_id); + undo_redo->add_undo_method(this, "_update_graph", new_id); + undo_redo->commit_action(); - VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + port_action_new_node = new_id; +} - if (tg.type == Variant::OBJECT) { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); +void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id) { + undo_redo->create_action(TTR("Connect Node Data")); + VisualScriptReturn *vnode_return = Object::cast_to<VisualScriptReturn>(vnode.ptr()); + if (vnode_return != NULL && vnode_old->get_output_value_port_count() > 0) { + vnode_return->set_enable_return_value(true); + } + if (vnode_old->get_output_value_port_count() <= 0) { + undo_redo->commit_action(); + return; + } + if (vnode->get_input_value_port_count() <= 0) { + undo_redo->commit_action(); + return; + } + int port = port_action_output; + int value_count = vnode_old->get_output_value_port_count(); + if (port >= value_count) { + port = 0; + } + int count = vnode_old->get_output_value_port_count() + vnode_old->get_output_sequence_port_count(); + undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, port_action_node, port, new_id, 0); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, port_action_node, port, new_id, 0); + undo_redo->commit_action(); +} - if (tg.gdclass != StringName()) { - n->set_base_type(tg.gdclass); - } else { - n->set_base_type("Object"); - } +void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category) { + Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap, snap)); + } + ofs /= EDSCALE; - if (tg.script.is_valid()) { - n->set_base_script(tg.script->get_path()); - new_connect_node_select->select_property_from_script(tg.script); - } else { - new_connect_node_select->select_property_from_base_type(n->get_base_type()); - } + Set<int> vn; + if (p_category == "visualscript") { + Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text); + Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); + int new_id = script->get_available_id(); + + if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr())) { + Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type; + Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type); + } + + if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())) { + Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type; + String hint_name = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (type == Variant::OBJECT) { + Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(hint_name); + } else if (type == Variant::NIL) { + Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(""); } else { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE); - n->set_basic_type(tg.type); - new_connect_node_select->select_property_from_basic_type(tg.type); + Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type)); } + } + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode_new, ofs); + connect_seq(vnode_old, vnode_new, new_id); + connect_data(vnode_old, vnode_new, new_id); + undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + return; + } - } break; - case CREATE_COND: { + Ref<VisualScriptNode> vnode; + + seq_connect = false; + if (p_category == String("method")) { + + Ref<VisualScriptFunctionCall> n; + n.instance(); + vnode = n; + } else if (p_category == String("set")) { + + Ref<VisualScriptPropertySet> n; + n.instance(); + n->set_property(p_text); + vnode = n; + } else if (p_category == String("get")) { + + Ref<VisualScriptPropertyGet> n; + n.instance(); + n->set_property(p_text); + vnode = n; + } + + if (p_category == String("action")) { + if (p_text == "VisualScriptCondition") { Ref<VisualScriptCondition> n; n.instance(); vnode = n; seq_connect = true; + } + if (p_text == "VisualScriptSwitch") { - } break; - case CREATE_SEQUENCE: { - - Ref<VisualScriptSequence> n; + Ref<VisualScriptSwitch> n; n.instance(); vnode = n; seq_connect = true; + } else if (p_text == "VisualScriptSequence") { - } break; - case CREATE_SWITCH: { - - Ref<VisualScriptSwitch> n; + Ref<VisualScriptSequence> n; n.instance(); vnode = n; seq_connect = true; - - } break; - case CREATE_ITERATOR: { + } else if (p_text == "VisualScriptIterator") { Ref<VisualScriptIterator> n; n.instance(); vnode = n; seq_connect = true; - - } break; - case CREATE_WHILE: { + } else if (p_text == "VisualScriptWhile") { Ref<VisualScriptWhile> n; n.instance(); vnode = n; seq_connect = true; - - } break; - case CREATE_RETURN: { + } else if (p_text == "VisualScriptReturn") { Ref<VisualScriptReturn> n; n.instance(); vnode = n; seq_connect = true; - - } break; - } - - int new_id = script->get_available_id(); - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - if (seq_connect) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id); + } } - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); - undo_redo->add_do_method(this, "_update_graph", new_id); - undo_redo->add_undo_method(this, "_update_graph", new_id); - undo_redo->commit_action(); - port_action_new_node = new_id; -} - -void VisualScriptEditor::_selected_connect_node_method_or_setget(const String &p_text) { + new_node(vnode, ofs); Ref<VisualScriptNode> vsn = script->get_node(edited_func, port_action_new_node); @@ -2699,28 +2726,152 @@ void VisualScriptEditor::_selected_connect_node_method_or_setget(const String &p Ref<VisualScriptFunctionCall> vsfc = vsn; vsfc->set_function(p_text); - script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0); + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + + if (tg.gdclass != StringName()) { + vsfc->set_base_type(tg.gdclass); + + } else { + PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { + vsfc->set_base_type(base_type); + } + if (p_text == "call" || p_text == "call_deferred") { + vsfc->set_function(""); + } + } + if (tg.script.is_valid()) { + vsfc->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + vsfc->set_base_type(script->get_instance_base_type()); + } else { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); + vsfc->set_basic_type(tg.type); + } } if (Object::cast_to<VisualScriptPropertySet>(vsn.ptr())) { Ref<VisualScriptPropertySet> vsp = vsn; - vsp->set_property(p_text); - script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0); + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + + if (tg.gdclass != StringName()) { + vsp->set_base_type(tg.gdclass); + + } else { + PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { + vsp->set_base_type(base_type); + } + } + if (tg.script.is_valid()) { + vsp->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + vsp->set_base_type(script->get_instance_base_type()); + } else { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE); + vsp->set_basic_type(tg.type); + } } if (Object::cast_to<VisualScriptPropertyGet>(vsn.ptr())) { - Ref<VisualScriptPropertyGet> vsp = vsn; - vsp->set_property(p_text); - script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0); + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + + if (tg.gdclass != StringName()) { + vsp->set_base_type(tg.gdclass); + + } else { + PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { + vsp->set_base_type(base_type); + } + } + if (tg.script.is_valid()) { + vsp->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + vsp->set_base_type(script->get_instance_base_type()); + } else { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE); + vsp->set_basic_type(tg.type); + } } + Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); + connect_seq(vnode_old, vnode, port_action_new_node); + connect_data(vnode_old, vnode, port_action_new_node); _update_graph(port_action_new_node); _update_graph_connections(); } -void VisualScriptEditor::_selected_new_virtual_method(const String &p_text) { +void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id) { + int seq_count = vnode_old->get_output_sequence_port_count(); + VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(vnode_new.ptr()); + if (vnode_operator != NULL && vnode_operator->has_input_sequence_port() == false) { + return; + } + VisualScriptConstructor *vnode_constructor = Object::cast_to<VisualScriptConstructor>(vnode_new.ptr()); + if (vnode_constructor != NULL) { + return; + } + if (vnode_old->get_output_sequence_port_count() <= 0) { + return; + } + if (vnode_new->has_input_sequence_port() == false) { + return; + } + VisualScriptFunction *vnode_function = Object::cast_to<VisualScriptFunction>(vnode_old.ptr()); + undo_redo->create_action(TTR("Connect Node Sequence")); + int pass_port = -vnode_old->get_output_sequence_port_count() + 1; + int return_port = port_action_output - 1; + if (vnode_old->get_output_value_port_info(port_action_output).name == String("pass") && + !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(pass_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, pass_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, pass_port, new_id); + } else if (vnode_old->get_output_value_port_info(port_action_output).name == String("return") && + !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(return_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, return_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, return_port, new_id); + } else { + for (int port = 0; port < vnode_old->get_output_sequence_port_count(); port++) { + int count = vnode_old->get_output_sequence_port_count(); + if (port_action_output < count && !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port_action_output)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port_action_output, new_id); + break; + } else if (!script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port, new_id); + break; + } + } + } + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, const String &p_category) { String name = p_text; if (script->has_function(name)) { @@ -2777,12 +2928,29 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text) { _update_graph(); } -void VisualScriptEditor::_cancel_connect_node_method_or_setget() { - - script->remove_node(edited_func, port_action_new_node); +void VisualScriptEditor::_cancel_connect_node() { + // Causes crashes + //script->remove_node(edited_func, port_action_new_node); _update_graph(); } +void VisualScriptEditor::_create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point) { + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap, snap)); + } + ofs /= EDSCALE; + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(p_text); + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + void VisualScriptEditor::_default_value_changed() { Ref<VisualScriptNode> vsn = script->get_node(edited_func, editing_id); @@ -3286,10 +3454,11 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_comment_node_resized", &VisualScriptEditor::_comment_node_resized); ClassDB::bind_method("_button_resource_previewed", &VisualScriptEditor::_button_resource_previewed); ClassDB::bind_method("_port_action_menu", &VisualScriptEditor::_port_action_menu); - ClassDB::bind_method("_selected_connect_node_method_or_setget", &VisualScriptEditor::_selected_connect_node_method_or_setget); + ClassDB::bind_method("_selected_connect_node", &VisualScriptEditor::_selected_connect_node); ClassDB::bind_method("_selected_new_virtual_method", &VisualScriptEditor::_selected_new_virtual_method); - ClassDB::bind_method("_cancel_connect_node_method_or_setget", &VisualScriptEditor::_cancel_connect_node_method_or_setget); + ClassDB::bind_method("_cancel_connect_node", &VisualScriptEditor::_cancel_connect_node); + ClassDB::bind_method("_create_new_node", &VisualScriptEditor::_create_new_node); ClassDB::bind_method("_expression_text_changed", &VisualScriptEditor::_expression_text_changed); ClassDB::bind_method("get_drag_data_fw", &VisualScriptEditor::get_drag_data_fw); @@ -3480,25 +3649,21 @@ VisualScriptEditor::VisualScriptEditor() { add_child(default_value_edit); default_value_edit->connect("variant_changed", this, "_default_value_changed"); - method_select = memnew(PropertySelector); + method_select = memnew(VisualScriptPropertySelector); add_child(method_select); method_select->connect("selected", this, "_selected_method"); error_line = -1; - new_connect_node_select = memnew(PropertySelector); + new_connect_node_select = memnew(VisualScriptPropertySelector); add_child(new_connect_node_select); - new_connect_node_select->connect("selected", this, "_selected_connect_node_method_or_setget"); - new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node_method_or_setget"); + new_connect_node_select->connect("selected", this, "_selected_connect_node"); + new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node"); - new_virtual_method_select = memnew(PropertySelector); + new_virtual_method_select = memnew(VisualScriptPropertySelector); add_child(new_virtual_method_select); new_virtual_method_select->connect("selected", this, "_selected_new_virtual_method"); new_virtual_method_select->get_cancel()->connect("pressed", this, "_selected_new_virtual_method"); - port_action_popup = memnew(PopupMenu); - add_child(port_action_popup); - port_action_popup->connect("id_pressed", this, "_port_action_menu"); - member_popup = memnew(PopupMenu); add_child(member_popup); members->connect("item_rmb_selected", this, "_member_rmb_selected"); @@ -3515,9 +3680,9 @@ VisualScriptEditor::~VisualScriptEditor() { memdelete(variable_editor); } -static ScriptEditorBase *create_editor(const Ref<Script> &p_script) { +static ScriptEditorBase *create_editor(const RES &p_resource) { - if (Object::cast_to<VisualScript>(*p_script)) { + if (Object::cast_to<VisualScript>(*p_resource)) { return memnew(VisualScriptEditor); } diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 0bd64d6a1d..0283f7b162 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -34,9 +34,9 @@ #include "editor/create_dialog.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/property_editor.h" -#include "editor/property_selector.h" #include "scene/gui/graph_edit.h" #include "visual_script.h" +#include "visual_script_property_selector.h" class VisualScriptEditorSignalEdit; class VisualScriptEditorVariableEdit; @@ -62,15 +62,8 @@ class VisualScriptEditor : public ScriptEditorBase { enum PortAction { - CREATE_CALL, - CREATE_SET, - CREATE_GET, - CREATE_COND, - CREATE_SEQUENCE, - CREATE_SWITCH, - CREATE_ITERATOR, - CREATE_WHILE, - CREATE_RETURN, + CREATE_CALL_SET_GET, + CREATE_ACTION, }; enum MemberAction { @@ -102,9 +95,9 @@ class VisualScriptEditor : public ScriptEditorBase { AcceptDialog *edit_signal_dialog; PropertyEditor *edit_signal_edit; - PropertySelector *method_select; - PropertySelector *new_connect_node_select; - PropertySelector *new_virtual_method_select; + VisualScriptPropertySelector *method_select; + VisualScriptPropertySelector *new_connect_node_select; + VisualScriptPropertySelector *new_virtual_method_select; VisualScriptEditorVariableEdit *variable_editor; @@ -162,21 +155,29 @@ class VisualScriptEditor : public ScriptEditorBase { static Clipboard *clipboard; - PopupMenu *port_action_popup; PopupMenu *member_popup; - MemberType member_type; String member_name; + bool seq_connect = false; + PortAction port_action; int port_action_node; int port_action_output; Vector2 port_action_pos; int port_action_new_node; void _port_action_menu(int p_option); - void _selected_connect_node_method_or_setget(const String &p_text); - void _cancel_connect_node_method_or_setget(); - void _selected_new_virtual_method(const String &p_text); + + void new_node(Ref<VisualScriptNode> vnode, Vector2 ofs); + + void connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id); + + void _selected_connect_node(const String &p_text, const String &p_category); + void connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id); + + void _cancel_connect_node(); + void _create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point); + void _selected_new_virtual_method(const String &p_text, const String &p_category); int error_line; @@ -231,7 +232,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _comment_node_resized(const Vector2 &p_new_size, int p_node); int selecting_method_id; - void _selected_method(const String &p_method); + void _selected_method(const String &p_method, const String &p_type); void _draw_color_over_button(Object *obj, Color p_color); void _button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, Variant p_ud); @@ -250,9 +251,9 @@ public: virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter); virtual void apply_code(); - virtual Ref<Script> get_edited_script() const; + virtual RES get_edited_resource() const; + virtual void set_edited_resource(const RES &p_res); virtual Vector<String> get_functions(); - virtual void set_edited_script(const Ref<Script> &p_script); virtual void reload_text(); virtual String get_name(); virtual Ref<Texture> get_icon(); diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp index ea23ab1b2a..0f58a20c00 100644 --- a/modules/visual_script/visual_script_flow_control.cpp +++ b/modules/visual_script/visual_script_flow_control.cpp @@ -767,7 +767,7 @@ PropertyInfo VisualScriptTypeCast::get_input_value_port_info(int p_idx) const { PropertyInfo VisualScriptTypeCast::get_output_value_port_info(int p_idx) const { - return PropertyInfo(Variant::OBJECT, ""); + return PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_TYPE_STRING, get_base_type()); } String VisualScriptTypeCast::get_caption() const { diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index bdf5705ecd..ad886bc758 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -43,7 +43,7 @@ int VisualScriptFunctionCall::get_output_sequence_port_count() const { - if (method_cache.flags & METHOD_FLAG_CONST || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) + if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) return 0; else return 1; @@ -51,7 +51,7 @@ int VisualScriptFunctionCall::get_output_sequence_port_count() const { bool VisualScriptFunctionCall::has_input_sequence_port() const { - if (method_cache.flags & METHOD_FLAG_CONST || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) + if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) return false; else return true; @@ -231,7 +231,7 @@ PropertyInfo VisualScriptFunctionCall::get_output_value_port_info(int p_idx) con if (call_mode == CALL_MODE_INSTANCE) { if (p_idx == 0) { - return PropertyInfo(Variant::OBJECT, "pass"); + return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type()); } else { p_idx--; } @@ -1055,7 +1055,7 @@ PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) cons if (call_mode == CALL_MODE_BASIC_TYPE) { return PropertyInfo(basic_type, "out"); } else if (call_mode == CALL_MODE_INSTANCE) { - return PropertyInfo(Variant::OBJECT, "pass"); + return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type()); } else { return PropertyInfo(); } diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 8b7b809ec0..f174300a4c 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -2198,7 +2198,7 @@ PropertyInfo VisualScriptSceneTree::get_input_value_port_info(int p_idx) const { PropertyInfo VisualScriptSceneTree::get_output_value_port_info(int p_idx) const { - return PropertyInfo(Variant::OBJECT, "Scene Tree"); + return PropertyInfo(Variant::OBJECT, "Scene Tree", PROPERTY_HINT_TYPE_STRING, "SceneTree"); } String VisualScriptSceneTree::get_caption() const { diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp new file mode 100644 index 0000000000..994ea1f791 --- /dev/null +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -0,0 +1,705 @@ +/*************************************************************************/ +/* visual_script_property_selector.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "visual_script_property_selector.h" + +#include "editor_scale.h" +#include "modules/visual_script/visual_script.h" +#include "modules/visual_script/visual_script_builtin_funcs.h" +#include "modules/visual_script/visual_script_flow_control.h" +#include "modules/visual_script/visual_script_func_nodes.h" +#include "modules/visual_script/visual_script_nodes.h" +#include "os/keyboard.h" +#include "scene/main/node.h" +#include "scene/main/viewport.h" + +void VisualScriptPropertySelector::_text_changed(const String &p_newtext) { + _update_search(); +} + +void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { + + Ref<InputEventKey> k = p_ie; + + if (k.is_valid()) { + + switch (k->get_scancode()) { + case KEY_UP: + case KEY_DOWN: + case KEY_PAGEUP: + case KEY_PAGEDOWN: { + + search_options->call("_gui_input", k); + search_box->accept_event(); + + TreeItem *root = search_options->get_root(); + if (!root->get_children()) + break; + + TreeItem *current = search_options->get_selected(); + + TreeItem *item = search_options->get_next_selected(root); + while (item) { + item->deselect(0); + item = search_options->get_next_selected(item); + } + + current->select(0); + + } break; + } + } +} + +void VisualScriptPropertySelector::_update_search() { + set_title(TTR("Search VisualScript")); + + search_options->clear(); + help_bit->set_text(""); + + TreeItem *root = search_options->create_item(); + bool found = false; + + if (properties) { + + List<PropertyInfo> props; + + if (instance) { + instance->get_property_list(&props, true); + } else if (type != Variant::NIL) { + Variant v; + Variant::CallError ce; + v = Variant::construct(type, NULL, 0, ce); + + v.get_property_list(&props); + } else { + + Object *obj = ObjectDB::get_instance(script); + if (Object::cast_to<Script>(obj)) { + + props.push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY)); + Object::cast_to<Script>(obj)->get_script_property_list(&props); + } + + StringName base = base_type; + while (base) { + props.push_back(PropertyInfo(Variant::NIL, base, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY)); + ClassDB::get_property_list(base, &props, true); + base = ClassDB::get_parent_class_nocheck(base); + } + } + + TreeItem *category = NULL; + + Ref<Texture> type_icons[Variant::VARIANT_MAX] = { + Control::get_icon("Variant", "EditorIcons"), + Control::get_icon("bool", "EditorIcons"), + Control::get_icon("int", "EditorIcons"), + Control::get_icon("float", "EditorIcons"), + Control::get_icon("String", "EditorIcons"), + Control::get_icon("Vector2", "EditorIcons"), + Control::get_icon("Rect2", "EditorIcons"), + Control::get_icon("Vector3", "EditorIcons"), + Control::get_icon("Transform2D", "EditorIcons"), + Control::get_icon("Plane", "EditorIcons"), + Control::get_icon("Quat", "EditorIcons"), + Control::get_icon("AABB", "EditorIcons"), + Control::get_icon("Basis", "EditorIcons"), + Control::get_icon("Transform", "EditorIcons"), + Control::get_icon("Color", "EditorIcons"), + Control::get_icon("Path", "EditorIcons"), + Control::get_icon("RID", "EditorIcons"), + Control::get_icon("Object", "EditorIcons"), + Control::get_icon("Dictionary", "EditorIcons"), + Control::get_icon("Array", "EditorIcons"), + Control::get_icon("PoolByteArray", "EditorIcons"), + Control::get_icon("PoolIntArray", "EditorIcons"), + Control::get_icon("PoolRealArray", "EditorIcons"), + Control::get_icon("PoolStringArray", "EditorIcons"), + Control::get_icon("PoolVector2Array", "EditorIcons"), + Control::get_icon("PoolVector3Array", "EditorIcons"), + Control::get_icon("PoolColorArray", "EditorIcons") + }; + + if (!seq_connect && visual_script_generic == false) { + get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); + get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); + get_visual_node_names("functions/by_type/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); + get_visual_node_names("operators/compare/", Set<String>(), found, root, search_box); + if (type == Variant::INT) { + get_visual_node_names("operators/bitwise/", Set<String>(), found, root, search_box); + } + if (type == Variant::BOOL) { + get_visual_node_names("operators/logic/", Set<String>(), found, root, search_box); + } + if (type == Variant::BOOL || type == Variant::INT || type == Variant::REAL || type == Variant::VECTOR2 || type == Variant::VECTOR3) { + get_visual_node_names("operators/math/", Set<String>(), found, root, search_box); + } + } + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().usage == PROPERTY_USAGE_CATEGORY) { + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + category = search_options->create_item(root); + category->set_text(0, E->get().name); + category->set_selectable(0, false); + + Ref<Texture> icon; + if (E->get().name == "Script Variables") { + icon = get_icon("Script", "EditorIcons"); + } else if (has_icon(E->get().name, "EditorIcons")) { + icon = get_icon(E->get().name, "EditorIcons"); + } else { + icon = get_icon("Object", "EditorIcons"); + } + category->set_icon(0, icon); + continue; + } + + if (!(E->get().usage & PROPERTY_USAGE_EDITOR) && !(E->get().usage & PROPERTY_USAGE_SCRIPT_VARIABLE)) + continue; + + if (type_filter.size() && type_filter.find(E->get().type) == -1) + continue; + + String get_text_raw = String(TTR("Get")) + String(" ") + E->get().name; + String get_text = get_text_raw.capitalize(); + + String set_text_raw = String(TTR("Set ")) + String(" ") + E->get().name; + String set_text = set_text_raw.capitalize(); + String input = search_box->get_text().capitalize(); + if (input == String() || + get_text_raw.findn(input) != -1 || + get_text.findn(input) != -1) { + TreeItem *item = search_options->create_item(category ? category : root); + item->set_text(0, get_text); + item->set_metadata(0, E->get().name); + item->set_icon(0, type_icons[E->get().type]); + item->set_metadata(1, "get"); + item->set_collapsed(1); + item->set_selectable(1, false); + item->set_selectable(0, true); + } + + if (input == String() || + set_text_raw.findn(input) != -1 && + set_text.findn(input) != -1) { + TreeItem *item = search_options->create_item(category ? category : root); + item->set_text(0, set_text); + item->set_metadata(0, E->get().name); + item->set_icon(0, type_icons[E->get().type]); + item->set_metadata(1, "set"); + item->set_selectable(1, false); + item->set_selectable(0, true); + } + } + } + + if (seq_connect == true && visual_script_generic == false) { + String text = search_box->get_text(); + create_visualscript_item(String("VisualScriptCondition"), root, text, String("Condition")); + create_visualscript_item(String("VisualScriptSwitch"), root, text, String("Switch")); + create_visualscript_item(String("VisualScriptSequence"), root, text, String("Sequence")); + create_visualscript_item(String("VisualScriptIterator"), root, text, String("Iterator")); + create_visualscript_item(String("VisualScriptWhile"), root, text, String("While")); + create_visualscript_item(String("VisualScriptReturn"), root, text, String("Return")); + get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); + get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); + } + + if (visual_script_generic) { + get_visual_node_names("", Set<String>(), found, root, search_box); + } + + List<MethodInfo> methods; + + if (type != Variant::NIL) { + Variant v; + Variant::CallError ce; + v = Variant::construct(type, NULL, 0, ce); + v.get_method_list(&methods); + } else { + + Object *obj = ObjectDB::get_instance(script); + if (Object::cast_to<Script>(obj)) { + + methods.push_back(MethodInfo("*Script Methods")); + Object::cast_to<Script>(obj)->get_script_method_list(&methods); + } + + StringName base = base_type; + while (base) { + methods.push_back(MethodInfo("*" + String(base))); + ClassDB::get_method_list(base, &methods, true, true); + base = ClassDB::get_parent_class_nocheck(base); + } + } + TreeItem *category = NULL; + bool script_methods = false; + + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name.begins_with("*")) { + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + category = search_options->create_item(root); + category->set_text(0, E->get().name.replace_first("*", "")); + category->set_selectable(0, false); + + Ref<Texture> icon; + script_methods = false; + print_line("name: " + E->get().name); + String rep = E->get().name.replace("*", ""); + if (E->get().name == "*Script Methods") { + icon = get_icon("Script", "EditorIcons"); + script_methods = true; + } else if (has_icon(rep, "EditorIcons")) { + icon = get_icon(rep, "EditorIcons"); + } else { + icon = get_icon("Object", "EditorIcons"); + } + category->set_icon(0, icon); + + continue; + } + + String name = E->get().name.get_slice(":", 0); + if (!script_methods && name.begins_with("_") && !(E->get().flags & METHOD_FLAG_VIRTUAL)) + continue; + + if (virtuals_only && !(E->get().flags & METHOD_FLAG_VIRTUAL)) + continue; + + if (!virtuals_only && (E->get().flags & METHOD_FLAG_VIRTUAL)) + continue; + + MethodInfo mi = E->get(); + String desc = mi.name.capitalize() + " ("; + + if (search_box->get_text() != String() && + name.findn(search_box->get_text()) == -1 && + desc.findn(search_box->get_text()) == -1) + continue; + + TreeItem *item = search_options->create_item(category ? category : root); + + for (int i = 0; i < mi.arguments.size(); i++) { + + if (i > 0) + desc += ", "; + + if (mi.arguments[i].type == Variant::NIL) + desc += "var"; + else if (mi.arguments[i].name.find(":") != -1) { + desc += mi.arguments[i].name.get_slice(":", 1); + mi.arguments[i].name = mi.arguments[i].name.get_slice(":", 0); + } else + desc += Variant::get_type_name(mi.arguments[i].type); + } + + desc += ")"; + + item->set_text(0, desc); + item->set_icon(0, get_icon("MemberMethod", "EditorIcons")); + item->set_metadata(0, name); + item->set_selectable(0, true); + + item->set_metadata(1, "method"); + item->set_collapsed(1); + item->set_selectable(1, false); + + if (category && category->get_children() == NULL) { + memdelete(category); //old category was unused + } + } + + TreeItem *selected_item = search_options->search_item_text(search_box->get_text()); + if (!found && selected_item != NULL) { + selected_item->select(0); + found = true; + } + + get_ok()->set_disabled(root->get_children() == NULL); +} + +void VisualScriptPropertySelector::create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text) { + if (search_input == String() || text.findn(search_input) != -1) { + TreeItem *item = search_options->create_item(root); + item->set_text(0, text); + item->set_icon(0, get_icon("VisualScript", "EditorIcons")); + item->set_metadata(0, name); + item->set_metadata(1, "action"); + item->set_selectable(0, true); + item->set_collapsed(1); + item->set_selectable(1, false); + } +} + +void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box) { + Map<String, TreeItem *> path_cache; + + List<String> fnodes; + VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); + + for (List<String>::Element *E = fnodes.front(); E; E = E->next()) { + if (!E->get().begins_with(root_filter)) { + continue; + } + Vector<String> path = E->get().split("/"); + bool is_filter = false; + for (Set<String>::Element *E = filter.front(); E; E = E->next()) { + if (path.size() >= 2 && path[1].findn(E->get()) != -1) { + is_filter = true; + break; + } + } + if (is_filter == true) { + continue; + } + + if (search_box->get_text() != String() && E->get().findn(search_box->get_text()) == -1) { + continue; + } + TreeItem *item = search_options->create_item(root); + VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(*VisualScriptLanguage::singleton->create_node_from_name(E->get())); + String type_name; + if (vnode_operator != NULL) { + String type; + if (path.size() >= 2) { + type = path[1]; + } + type_name = type.capitalize() + " "; + } + VisualScriptFunctionCall *vnode_function_call = Object::cast_to<VisualScriptFunctionCall>(*VisualScriptLanguage::singleton->create_node_from_name(E->get())); + if (vnode_function_call != NULL) { + String basic_type = Variant::get_type_name(vnode_function_call->get_basic_type()); + type_name = basic_type.capitalize() + " "; + } + VisualScriptBuiltinFunc *vnode_builtin_function_call = Object::cast_to<VisualScriptBuiltinFunc>(*VisualScriptLanguage::singleton->create_node_from_name(E->get())); + if (vnode_builtin_function_call != NULL) { + type_name = "Builtin "; + } + item->set_text(0, type_name + path[path.size() - 1].capitalize()); + item->set_icon(0, get_icon("VisualScript", "EditorIcons")); + item->set_selectable(0, true); + item->set_metadata(0, E->get()); + item->set_selectable(0, true); + item->set_metadata(1, "visualscript"); + item->set_selectable(1, false); + } +} + +void VisualScriptPropertySelector::_confirmed() { + + TreeItem *ti = search_options->get_selected(); + if (!ti) + return; + emit_signal("selected", ti->get_metadata(0), ti->get_metadata(1)); + hide(); +} + +void VisualScriptPropertySelector::_item_selected() { + + help_bit->set_text(""); + + TreeItem *item = search_options->get_selected(); + if (!item) + return; + String name = item->get_metadata(0); + + String class_type; + if (type) { + class_type = Variant::get_type_name(type); + + } else { + class_type = base_type; + } + + DocData *dd = EditorHelp::get_doc_data(); + String text; + + String at_class = class_type; + + while (at_class != String()) { + + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class); + if (E) { + for (int i = 0; i < E->get().properties.size(); i++) { + if (E->get().properties[i].name == name) { + text = E->get().properties[i].description; + } + } + } + + at_class = ClassDB::get_parent_class_nocheck(at_class); + } + at_class = class_type; + + while (at_class != String()) { + + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class); + if (E) { + for (int i = 0; i < E->get().methods.size(); i++) { + if (E->get().methods[i].name == name) { + text = E->get().methods[i].description; + } + } + } + + at_class = ClassDB::get_parent_class_nocheck(at_class); + } + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(class_type); + if (E) { + for (int i = 0; i < E->get().methods.size(); i++) { + Vector<String> functions = name.rsplit("/", false, 1); + if (E->get().methods[i].name == functions[functions.size() - 1]) { + text = E->get().methods[i].description; + } + } + } + + List<String> *names = memnew(List<String>); + VisualScriptLanguage::singleton->get_registered_node_names(names); + if (names->find(name) != NULL) { + Ref<VisualScriptOperator> operator_node = VisualScriptLanguage::singleton->create_node_from_name(name); + if (operator_node.is_valid()) { + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(operator_node->get_class_name()); + if (E) { + text = Variant::get_operator_name(operator_node->get_operator()); + } + } + Ref<VisualScriptTypeCast> typecast_node = VisualScriptLanguage::singleton->create_node_from_name(name); + if (typecast_node.is_valid()) { + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(typecast_node->get_class_name()); + if (E) { + text = E->get().description; + } + } + + Ref<VisualScriptBuiltinFunc> builtin_node = VisualScriptLanguage::singleton->create_node_from_name(name); + if (builtin_node.is_valid()) { + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(builtin_node->get_class_name()); + if (E) { + for (int i = 0; i < E->get().constants.size(); i++) { + if (E->get().constants[i].value.to_int() == int(builtin_node->get_func())) { + text = E->get().constants[i].description; + } + } + } + } + } + + memdelete(names); + + if (text == String()) + return; + + help_bit->set_text(text); +} + +void VisualScriptPropertySelector::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + connect("confirmed", this, "_confirmed"); + } +} + +void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only) { + + base_type = p_base; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = false; + instance = NULL; + virtuals_only = p_virtuals_only; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + _update_search(); +} + +void VisualScriptPropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) { + type_filter = p_type_filter; +} + +void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current /*= ""*/, bool p_virtuals_only /*= false*/, bool p_seq_connect /*= false*/) { + + base_type = p_base; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = true; + instance = NULL; + virtuals_only = p_virtuals_only; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = p_seq_connect; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current /*= ""*/) { + ERR_FAIL_COND(p_script.is_null()); + + base_type = p_script->get_instance_base_type(); + selected = p_current; + type = Variant::NIL; + script = p_script->get_instance_id(); + properties = true; + instance = NULL; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = false; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current /*= ""*/) { + ERR_FAIL_COND(p_type == Variant::NIL); + base_type = ""; + selected = p_current; + type = p_type; + script = 0; + properties = true; + instance = NULL; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = false; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current /*= ""*/) { + base_type = p_type; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = false; + instance = NULL; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = true; + _update_search(); +} + +void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current /*= ""*/) { + base_type = ""; + selected = p_current; + type = Variant::NIL; + script = 0; + properties = true; + instance = p_instance; + virtuals_only = false; + + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + seq_connect = false; + + _update_search(); +} + +void VisualScriptPropertySelector::select_from_visual_script(const String &p_base) { + base_type = p_base; + selected = ""; + type = Variant::NIL; + script = 0; + properties = true; + visual_script_generic = true; + instance = NULL; + virtuals_only = false; + show_window(.5f); + search_box->set_text(""); + search_box->grab_focus(); + + _update_search(); +} + +void VisualScriptPropertySelector::show_window(float p_screen_ratio) { + Rect2 rect; + Point2 window_size = get_viewport_rect().size; + rect.size = (window_size * p_screen_ratio).floor(); + rect.size.x = rect.size.x / 1.25f; + rect.position = ((window_size - rect.size) / 2.0f).floor(); + popup(rect); +} + +void VisualScriptPropertySelector::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_text_changed"), &VisualScriptPropertySelector::_text_changed); + ClassDB::bind_method(D_METHOD("_confirmed"), &VisualScriptPropertySelector::_confirmed); + ClassDB::bind_method(D_METHOD("_sbox_input"), &VisualScriptPropertySelector::_sbox_input); + ClassDB::bind_method(D_METHOD("_item_selected"), &VisualScriptPropertySelector::_item_selected); + + ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name"))); +} + +VisualScriptPropertySelector::VisualScriptPropertySelector() { + + VBoxContainer *vbc = memnew(VBoxContainer); + add_child(vbc); + //set_child_rect(vbc); + search_box = memnew(LineEdit); + vbc->add_margin_child(TTR("Search:"), search_box); + search_box->connect("text_changed", this, "_text_changed"); + search_box->connect("gui_input", this, "_sbox_input"); + search_options = memnew(Tree); + vbc->add_margin_child(TTR("Matches:"), search_options, true); + get_ok()->set_text(TTR("Open")); + get_ok()->set_disabled(true); + register_text_enter(search_box); + set_hide_on_ok(false); + search_options->connect("item_activated", this, "_confirmed"); + search_options->connect("cell_selected", this, "_item_selected"); + search_options->set_hide_root(true); + search_options->set_hide_folding(true); + virtuals_only = false; + help_bit = memnew(EditorHelpBit); + vbc->add_margin_child(TTR("Description:"), help_bit); + help_bit->connect("request_hide", this, "_closed"); + search_options->set_columns(2); + search_options->set_column_expand(1, false); +} diff --git a/modules/visual_script/visual_script_property_selector.h b/modules/visual_script/visual_script_property_selector.h new file mode 100644 index 0000000000..ec536f86a8 --- /dev/null +++ b/modules/visual_script/visual_script_property_selector.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* visual_script_property_selector.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 VISUALSCRIPT_PROPERTYSELECTOR_H +#define VISUALSCRIPT_PROPERTYSELECTOR_H + +#include "editor/property_editor.h" +#include "editor_help.h" +#include "scene/gui/rich_text_label.h" + +class VisualScriptPropertySelector : public ConfirmationDialog { + GDCLASS(VisualScriptPropertySelector, ConfirmationDialog) + + LineEdit *search_box; + Tree *search_options; + + void _update_search(); + + void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text); + + void get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box); + + void _sbox_input(const Ref<InputEvent> &p_ie); + + void _confirmed(); + void _text_changed(const String &p_newtext); + + EditorHelpBit *help_bit; + + bool properties; + bool visual_script_generic; + String selected; + Variant::Type type; + String base_type; + ObjectID script; + Object *instance; + bool virtuals_only; + + bool seq_connect = false; + + void _item_selected(); + + Vector<Variant::Type> type_filter; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void select_method_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false); + void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false); + void select_from_script(const Ref<Script> &p_script, const String &p_current /*= ""*/); + void select_from_basic_type(Variant::Type p_type, const String &p_current = ""); + void select_from_action(const String &p_type, const String &p_current = ""); + void select_from_instance(Object *p_instance, const String &p_current = ""); + void select_from_visual_script(const String &p_base); + + void show_window(float p_screen_ratio); + + void set_type_filter(const Vector<Variant::Type> &p_type_filter); + + VisualScriptPropertySelector(); +}; + +#endif // VISUALSCRIPT_PROPERTYSELECTOR_H diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index bae8f7be5f..5bc82f267f 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -48,7 +48,7 @@ size_t AudioStreamPlaybackOGGVorbis::_ov_read_func(void *p_dst, size_t p_data, s int AudioStreamPlaybackOGGVorbis::_ov_seek_func(void *_f, ogg_int64_t offs, int whence) { -//printf("seek to %p, offs %i, whence %i\n",_f,(int)offs,whence); + //printf("seek to %p, offs %i, whence %i\n",_f,(int)offs,whence); #ifdef SEEK_SET //printf("seek set defined\n"); diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index cdf2d75e96..42b2c77777 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -116,64 +116,73 @@ static Ref<Image> _webp_lossy_unpack(const PoolVector<uint8_t> &p_buffer) { return img; } -Error ImageLoaderWEBP::load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale) { +Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) { - uint32_t size = f->get_len(); - PoolVector<uint8_t> src_image; - src_image.resize(size); + ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER); WebPBitstreamFeatures features; - - PoolVector<uint8_t>::Write src_w = src_image.write(); - f->get_buffer(src_w.ptr(), size); - ERR_FAIL_COND_V(f->eof_reached(), ERR_FILE_EOF); - - if (WebPGetFeatures(src_w.ptr(), size, &features) != VP8_STATUS_OK) { - f->close(); - //ERR_EXPLAIN("Error decoding WEBP image: "+p_file); + if (WebPGetFeatures(p_buffer, p_buffer_len, &features) != VP8_STATUS_OK) { + // ERR_EXPLAIN("Error decoding WEBP image"); ERR_FAIL_V(ERR_FILE_CORRUPT); } - /* - print_line("width: " + itos(features.width)); - print_line("height: " + itos(features.height)); - print_line("alpha: " + itos(features.has_alpha)); - */ - - src_w = PoolVector<uint8_t>::Write(); - PoolVector<uint8_t> dst_image; int datasize = features.width * features.height * (features.has_alpha ? 4 : 3); dst_image.resize(datasize); - - PoolVector<uint8_t>::Read src_r = src_image.read(); PoolVector<uint8_t>::Write dst_w = dst_image.write(); bool errdec = false; if (features.has_alpha) { - errdec = WebPDecodeRGBAInto(src_r.ptr(), size, dst_w.ptr(), datasize, 4 * features.width) == NULL; + errdec = WebPDecodeRGBAInto(p_buffer, p_buffer_len, dst_w.ptr(), datasize, 4 * features.width) == NULL; } else { - errdec = WebPDecodeRGBInto(src_r.ptr(), size, dst_w.ptr(), datasize, 3 * features.width) == NULL; + errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w.ptr(), datasize, 3 * features.width) == NULL; } + dst_w = PoolVector<uint8_t>::Write(); - //ERR_EXPLAIN("Error decoding webp! - "+p_file); + //ERR_EXPLAIN("Error decoding webp!"); ERR_FAIL_COND_V(errdec, ERR_FILE_CORRUPT); - src_r = PoolVector<uint8_t>::Read(); - dst_w = PoolVector<uint8_t>::Write(); - p_image->create(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image); return OK; } +static Ref<Image> _webp_mem_loader_func(const uint8_t *p_png, int p_size) { + + Ref<Image> img; + img.instance(); + Error err = webp_load_image_from_buffer(img.ptr(), p_png, p_size); + ERR_FAIL_COND_V(err, Ref<Image>()); + return img; +} + +Error ImageLoaderWEBP::load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale) { + + PoolVector<uint8_t> src_image; + int src_image_len = f->get_len(); + ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT); + src_image.resize(src_image_len); + + PoolVector<uint8_t>::Write w = src_image.write(); + + f->get_buffer(&w[0], src_image_len); + + f->close(); + + Error err = webp_load_image_from_buffer(p_image.ptr(), w.ptr(), src_image_len); + + w = PoolVector<uint8_t>::Write(); + + return err; +} + void ImageLoaderWEBP::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("webp"); } ImageLoaderWEBP::ImageLoaderWEBP() { - + Image::_webp_mem_loader_func = _webp_mem_loader_func; Image::lossy_packer = _webp_lossy_pack; Image::lossy_unpacker = _webp_lossy_unpack; } diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp index 1405fa98b0..00c36ebb47 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -64,7 +64,6 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, String str = "ws://"; String proto_string = ""; - int i = 0; if (p_ssl) str = "wss://"; diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h index a4920c3d54..85a1e3769f 100644 --- a/modules/websocket/lws_helper.h +++ b/modules/websocket/lws_helper.h @@ -215,6 +215,6 @@ public: \ \ protected: - /* clang-format on */ +/* clang-format on */ #endif // LWS_HELPER_H diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp index 3d80e76707..b9f1f1eab0 100644 --- a/platform/android/audio_driver_jandroid.cpp +++ b/platform/android/audio_driver_jandroid.cpp @@ -78,9 +78,9 @@ Error AudioDriverAndroid::init() { // __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device"); JNIEnv *env = ThreadAndroid::get_env(); - int mix_rate = GLOBAL_DEF("audio/mix_rate", 44100); + int mix_rate = GLOBAL_DEF_RST("audio/mix_rate", 44100); - int latency = GLOBAL_DEF("audio/output_latency", 25); + int latency = GLOBAL_DEF_RST("audio/output_latency", 25); unsigned int buffer_size = next_power_of_2(latency * mix_rate / 1000); if (OS::get_singleton()->is_stdout_verbose()) { print_line("audio buffer size: " + itos(buffer_size)); diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index e6bd3ff253..28e3ea962f 100644 --- a/platform/android/audio_driver_opensl.cpp +++ b/platform/android/audio_driver_opensl.cpp @@ -42,7 +42,8 @@ void AudioDriverOpenSL::_buffer_callback( /* SLuint32 eventFlags, const void * pBuffer, SLuint32 bufferSize, - SLuint32 dataUsed*/) { + SLuint32 dataUsed*/ +) { bool mix = true; @@ -145,9 +146,6 @@ void AudioDriverOpenSL::start() { res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf); ERR_FAIL_COND(res != SL_RESULT_SUCCESS); - /* Initialize arrays required[] and iidArray[] */ - SLboolean required[MAX_NUMBER_INTERFACES]; - SLInterfaceID iidArray[MAX_NUMBER_INTERFACES]; { const SLInterfaceID ids[1] = { SL_IID_ENVIRONMENTALREVERB }; @@ -187,10 +185,7 @@ void AudioDriverOpenSL::start() { //cntxt.pDataBase = (void*)&pcmData; //cntxt.pData = cntxt.pDataBase; //cntxt.size = sizeof(pcmData); - /* Set arrays required[] and iidArray[] for SEEK interface - (PlayItf is implicit) */ - required[0] = SL_BOOLEAN_TRUE; - iidArray[0] = SL_IID_BUFFERQUEUE; + /* Create the music player */ { diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h index 2022bad02a..88cb122414 100644 --- a/platform/android/audio_driver_opensl.h +++ b/platform/android/audio_driver_opensl.h @@ -74,7 +74,8 @@ class AudioDriverOpenSL : public AudioDriver { /* SLuint32 eventFlags, const void * pBuffer, SLuint32 bufferSize, - SLuint32 dataUsed*/); + SLuint32 dataUsed*/ + ); static void _buffer_callbacks( SLAndroidSimpleBufferQueueItf queueItf, diff --git a/platform/android/detect.py b/platform/android/detect.py index 971368db17..ada36e2814 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -139,8 +139,13 @@ def configure(env): ## Build type if (env["target"].startswith("release")): - env.Append(LINKFLAGS=['-O2']) - env.Append(CPPFLAGS=['-O2', '-DNDEBUG', '-ffast-math', '-funsafe-math-optimizations', '-fomit-frame-pointer']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Append(LINKFLAGS=['-O2']) + env.Append(CPPFLAGS=['-O2', '-DNDEBUG', '-ffast-math', '-funsafe-math-optimizations', '-fomit-frame-pointer']) + else: #optimize for size + env.Append(CPPFLAGS=['-Os', '-DNDEBUG']) + env.Append(LINKFLAGS=['-Os']) + if (can_vectorize): env.Append(CPPFLAGS=['-ftree-vectorize']) if (env["target"] == "release_debug"): diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 3e40b59de9..ee5ae156b7 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -153,7 +153,6 @@ String DirAccessJAndroid::get_current_dir() { bool DirAccessJAndroid::file_exists(String p_file) { - JNIEnv *env = ThreadAndroid::get_env(); String sd; if (current_dir == "") sd = p_file; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index c3ff157f99..59e35884d1 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -228,7 +228,7 @@ class EditorExportAndroid : public EditorExportPlatform { }; Vector<Device> devices; - bool devices_changed; + volatile bool devices_changed; Mutex *device_lock; Thread *device_thread; volatile bool quit_request; @@ -528,11 +528,9 @@ class EditorExportAndroid : public EditorExportPlatform { bool exported = false; for (int i = 0; i < p_so.tags.size(); ++i) { // shared objects can be fat (compatible with multiple ABIs) - int start_pos = 0; int abi_index = abis.find(p_so.tags[i]); if (abi_index != -1) { exported = true; - start_pos = abi_index + 1; String abi = abis[abi_index]; String dst_path = "lib/" + abi + "/" + p_so.path.get_file(); Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path); @@ -1154,7 +1152,10 @@ public: virtual bool poll_devices() { bool dc = devices_changed; - devices_changed = false; + if (dc) { + // don't clear unless we're reporting true, to avoid race + devices_changed = false; + } return dc; } @@ -1857,9 +1858,9 @@ public: run_icon->create_from_image(img); device_lock = Mutex::create(); - device_thread = Thread::create(_device_poll_thread, this); devices_changed = true; quit_request = false; + device_thread = Thread::create(_device_poll_thread, this); } ~EditorExportAndroid() { diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java index 8a2d789dc5..ef798fc790 100644 --- a/platform/android/java/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/src/org/godotengine/godot/Godot.java @@ -297,7 +297,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC runOnUiThread(new Runnable() { @Override public void run() { - view.setKeepScreenOn("True".equals(GodotLib.getGlobal("display/driver/keep_screen_on"))); + view.setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); } }); } diff --git a/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java b/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java index afe5f81b6d..5d94e77cd7 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java @@ -66,7 +66,6 @@ abstract public class ConsumeTask { } final String token = _token; new AsyncTask<String, String, String>() { - @Override protected String doInBackground(String... params) { try { @@ -89,7 +88,6 @@ abstract public class ConsumeTask { error(param); } } - } .execute(); } diff --git a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java index b7bf2362cc..d4c7380424 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java +++ b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java @@ -113,7 +113,6 @@ public class PaymentsManager { public void requestPurchase(final String sku, String transactionId) { new PurchaseTask(mService, Godot.getInstance()) { - @Override protected void error(String message) { godotPaymentV3.callbackFail(message); @@ -128,7 +127,6 @@ public class PaymentsManager { protected void alreadyOwned() { godotPaymentV3.callbackAlreadyOwned(sku); } - } .purchase(sku, transactionId); } @@ -139,7 +137,6 @@ public class PaymentsManager { public void consumeUnconsumedPurchases() { new ReleaseAllConsumablesTask(mService, activity) { - @Override protected void success(String sku, String receipt, String signature, String token) { godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku); @@ -208,14 +205,12 @@ public class PaymentsManager { public void processPurchaseResponse(int resultCode, Intent data) { new HandlePurchaseTask(activity) { - @Override protected void success(final String sku, final String signature, final String ticket) { godotPaymentV3.callbackSuccess(ticket, signature, sku); if (auto_consume) { new ConsumeTask(mService, activity) { - @Override protected void success(String ticket) { } @@ -245,12 +240,10 @@ public class PaymentsManager { public void validatePurchase(String purchaseToken, final String sku) { new ValidateTask(activity, godotPaymentV3) { - @Override protected void success() { new ConsumeTask(mService, activity) { - @Override protected void success(String ticket) { godotPaymentV3.callbackSuccess(ticket, null, sku); @@ -283,7 +276,6 @@ public class PaymentsManager { public void consume(final String sku) { new ConsumeTask(mService, activity) { - @Override protected void success(String ticket) { godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku); diff --git a/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java b/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java index e00e37f9d1..eccc6f671b 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java @@ -88,7 +88,6 @@ abstract public class ReleaseAllConsumablesTask { String signature = mySignatures.get(i); //Log.d("godot", "A punto de consumir un item con token:" + token + "\n" + receipt); new GenericConsumeTask(context, mService, sku, receipt, signature, token) { - @Override public void onSuccess(String sku, String receipt, String signature, String token) { ReleaseAllConsumablesTask.this.success(sku, receipt, signature, token); diff --git a/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java index 1eb9d001e0..0626e50bb1 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java @@ -63,7 +63,6 @@ abstract public class ValidateTask { public void validatePurchase(final String sku) { new AsyncTask<String, String, String>() { - private ProgressDialog dialog; @Override @@ -113,7 +112,6 @@ abstract public class ValidateTask { error(e.getMessage()); } } - } .execute(); } diff --git a/platform/android/java_glue.cpp b/platform/android/java_glue.cpp index e6240ad9e9..8bb1c38345 100644 --- a/platform/android/java_glue.cpp +++ b/platform/android/java_glue.cpp @@ -880,7 +880,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo const char **cmdline = NULL; int cmdlen = 0; - bool use_apk_expansion = false; if (p_cmdline) { cmdlen = env->GetArrayLength(p_cmdline); if (cmdlen) { @@ -891,9 +890,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i); const char *rawString = env->GetStringUTFChars(string, 0); - if (rawString && strcmp(rawString, "--main-pack") == 0) { - use_apk_expansion = true; - } cmdline[i] = rawString; } diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 9188f09f21..cc512263bc 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -124,6 +124,10 @@ void OS_Android::set_opengl_extensions(const char *p_gl_extensions) { gl_extensions = p_gl_extensions; } +int OS_Android::get_current_video_driver() const { + return video_driver_index; +} + Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { bool use_gl3 = get_gl_version_code_func() >= 0x00030000; @@ -136,9 +140,11 @@ Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int if (use_gl2) { RasterizerGLES2::register_config(); RasterizerGLES2::make_current(); + video_driver_index = VIDEO_DRIVER_GLES2; } else { RasterizerGLES3::register_config(); RasterizerGLES3::make_current(); + video_driver_index = VIDEO_DRIVER_GLES3; } visual_server = memnew(VisualServerRaster); diff --git a/platform/android/os_android.h b/platform/android/os_android.h index ac901d4832..c4220906a3 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -137,6 +137,7 @@ private: AlertFunc alert_func; //power_android *power_manager; + int video_driver_index; public: // functions used by main to initialize/deintialize the OS @@ -146,6 +147,8 @@ public: virtual int get_audio_driver_count() const; virtual const char *get_audio_driver_name(int p_driver) const; + virtual int get_current_video_driver() const; + virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index e85813605f..b13baf69c2 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -132,7 +132,7 @@ JNIEnv *ThreadAndroid::get_env() { } JNIEnv *env = NULL; - int status = java_vm->AttachCurrentThread(&env, NULL); + java_vm->AttachCurrentThread(&env, NULL); return env; } diff --git a/platform/haiku/audio_driver_media_kit.cpp b/platform/haiku/audio_driver_media_kit.cpp index 278a994c54..1f901c4919 100644 --- a/platform/haiku/audio_driver_media_kit.cpp +++ b/platform/haiku/audio_driver_media_kit.cpp @@ -43,7 +43,7 @@ Error AudioDriverMediaKit::init() { speaker_mode = SPEAKER_MODE_STEREO; channels = 2; - int latency = GLOBAL_DEF("audio/output_latency", 25); + int latency = GLOBAL_DEF_RST("audio/output_latency", 25); buffer_size = next_power_of_2(latency * mix_rate / 1000); samples_in = memnew_arr(int32_t, buffer_size * channels); diff --git a/platform/haiku/os_haiku.cpp b/platform/haiku/os_haiku.cpp index 97fab5ca0d..209cb5cec4 100644 --- a/platform/haiku/os_haiku.cpp +++ b/platform/haiku/os_haiku.cpp @@ -80,6 +80,10 @@ const char *OS_Haiku::get_video_driver_name(int p_driver) const { return "GLES3"; } +int OS_Haiku::get_current_video_driver() const { + return video_driver_index; +} + Error OS_Haiku::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { main_loop = NULL; current_video_mode = p_desired; @@ -124,6 +128,8 @@ Error OS_Haiku::initialize(const VideoMode &p_desired, int p_video_driver, int p } */ + video_driver_index = p_video_driver; + input = memnew(InputDefault); window->SetInput(input); diff --git a/platform/haiku/os_haiku.h b/platform/haiku/os_haiku.h index 615ae682ef..13d4420bde 100644 --- a/platform/haiku/os_haiku.h +++ b/platform/haiku/os_haiku.h @@ -51,6 +51,7 @@ private: Rasterizer *rasterizer; VisualServer *visual_server; VideoMode current_video_mode; + int video_driver_index; PowerHaiku *power_manager; #ifdef MEDIA_KIT_ENABLED @@ -66,6 +67,7 @@ private: protected: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; + virtual int get_current_video_driver() const; virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); virtual void finalize(); diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index dd5ce4ab10..cc4985eb0c 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -643,7 +643,7 @@ static int frame_count = 0; view_controller.view = glView; window.rootViewController = view_controller; - _set_keep_screen_on(bool(GLOBAL_DEF("display/window/keep_screen_on", true)) ? YES : NO); + _set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO); glView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; printf("cadisaplylink: %d", glView.useCADisplayLink); diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 25674c2b47..b13a1e9643 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -47,8 +47,12 @@ def configure(env): if (env["target"].startswith("release")): env.Append(CPPFLAGS=['-DNDEBUG', '-DNS_BLOCK_ASSERTIONS=1']) - env.Append(CPPFLAGS=['-O2', '-ftree-vectorize', '-fomit-frame-pointer', '-ffast-math', '-funsafe-math-optimizations']) - env.Append(LINKFLAGS=['-O2']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Append(CPPFLAGS=['-O2', '-ftree-vectorize', '-fomit-frame-pointer', '-ffast-math', '-funsafe-math-optimizations']) + env.Append(LINKFLAGS=['-O2']) + else: #optimize for size + env.Append(CPPFLAGS=['-Os', '-ftree-vectorize']) + env.Append(LINKFLAGS=['-Os']) if env["target"] == "release_debug": env.Append(CPPFLAGS=['-DDEBUG_ENABLED']) diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 4c1e02baf7..5480d30e7a 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -781,7 +781,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p int ret = unzGoToFirstFile(src_pkg_zip); Vector<uint8_t> project_file_data; while (ret == UNZ_OK) { +#if defined(OSX_ENABLED) || defined(X11_ENABLED) bool is_execute = false; +#endif //get filename unz_file_info info; @@ -812,7 +814,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p continue; //ignore! } found_library = true; +#if defined(OSX_ENABLED) || defined(X11_ENABLED) is_execute = true; +#endif file = "godot_ios.a"; } if (file == project_file) { @@ -855,7 +859,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p f->close(); memdelete(f); -#ifdef OSX_ENABLED +#if defined(OSX_ENABLED) || defined(X11_ENABLED) if (is_execute) { // we need execute rights on this file chmod(file.utf8().get_data(), 0755); diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index 57ff79f7bc..e210bfb862 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -139,7 +139,6 @@ Error GameCenter::post_score(Variant p_score) { [GKScore reportScores:@[ reporter ] withCompletionHandler:^(NSError *error) { - Dictionary ret; ret["type"] = "post_score"; if (error == nil) { @@ -177,7 +176,6 @@ Error GameCenter::award_achievement(Variant p_params) { [GKAchievement reportAchievements:@[ achievement ] withCompletionHandler:^(NSError *error) { - Dictionary ret; ret["type"] = "award_achievement"; if (error == nil) { @@ -196,7 +194,6 @@ Error GameCenter::award_achievement(Variant p_params) { void GameCenter::request_achievement_descriptions() { [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) { - Dictionary ret; ret["type"] = "achievement_descriptions"; if (error == nil) { @@ -252,7 +249,6 @@ void GameCenter::request_achievement_descriptions() { void GameCenter::request_achievements() { [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) { - Dictionary ret; ret["type"] = "achievements"; if (error == nil) { @@ -347,7 +343,6 @@ Error GameCenter::request_identity_verification_signature() { GKLocalPlayer *player = [GKLocalPlayer localPlayer]; [player generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) { - Dictionary ret; ret["type"] = "identity_verification_signature"; if (error == nil) { diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp index 4caf4bd933..a4538a6673 100644 --- a/platform/iphone/os_iphone.cpp +++ b/platform/iphone/os_iphone.cpp @@ -93,8 +93,14 @@ void OSIPhone::initialize_core() { set_data_dir(data_dir); }; +int OSIPhone::get_current_video_driver() const { + return video_driver_index; +} + Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { + video_driver_index = p_video_driver; //this may be misleading + RasterizerGLES3::register_config(); RasterizerGLES3::make_current(); diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 8dc1ae6dc2..db2912ad93 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -77,6 +77,8 @@ private: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; + virtual int get_current_video_driver() const; + virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); @@ -112,6 +114,8 @@ private: int virtual_keyboard_height; + int video_driver_index; + public: bool iterate(); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index c05ae03ec6..b9d586e233 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -643,6 +643,9 @@ const char *OS_JavaScript::get_audio_driver_name(int p_driver) const { } // Lifecycle +int OS_JavaScript::get_current_video_driver() const { + return video_driver_index; +} void OS_JavaScript::initialize_core() { @@ -669,6 +672,8 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, RasterizerGLES2::make_current(); break; } + + video_driver_index = p_video_driver; EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(NULL, &attributes); ERR_EXPLAIN("WebGL " + itos(attributes.majorVersion) + ".0 not available"); ERR_FAIL_COND_V(emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS, ERR_UNAVAILABLE); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 503c92585b..915320fe39 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -81,7 +81,11 @@ class OS_JavaScript : public OS_Unix { static void file_access_close_callback(const String &p_file, int p_flags); + int video_driver_index; + protected: + virtual int get_current_video_driver() const; + virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 72b8aa99f8..af96659239 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -39,14 +39,21 @@ def configure(env): ## Build type if (env["target"] == "release"): - env.Prepend(CCFLAGS=['-O3', '-ffast-math', '-fomit-frame-pointer', '-ftree-vectorize', '-msse2']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Prepend(CCFLAGS=['-O3', '-ffast-math', '-fomit-frame-pointer', '-ftree-vectorize', '-msse2']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os','-ftree-vectorize', '-msse2']) + if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "release_debug"): - env.Prepend(CCFLAGS=['-O2', '-DDEBUG_ENABLED']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Prepend(CCFLAGS=['-O2', '-DDEBUG_ENABLED']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os', '-DDEBUG_ENABLED']) if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): @@ -108,8 +115,8 @@ def configure(env): ## Flags env.Append(CPPPATH=['#platform/osx']) - env.Append(CPPFLAGS=['-DOSX_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DAPPLE_STYLE_KEYS', '-DCOREAUDIO_ENABLED']) - env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback']) + env.Append(CPPFLAGS=['-DOSX_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DAPPLE_STYLE_KEYS', '-DCOREAUDIO_ENABLED', '-DCOREMIDI_ENABLED']) + env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMidi', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback']) env.Append(LIBS=['pthread']) env.Append(CPPFLAGS=['-mmacosx-version-min=10.9']) diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 7bd5b16f36..686e3f8c90 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -33,6 +33,7 @@ #include "crash_handler_osx.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "drivers/coremidi/core_midi.h" #include "drivers/unix/os_unix.h" #include "joypad_osx.h" #include "main/input_default.h" @@ -74,6 +75,7 @@ public: IP_Unix *ip_unix; AudioDriverCoreAudio audio_driver; + MIDIDriverCoreMidi midi_driver; InputDefault *input; JoypadOSX *joypad_osx; @@ -137,6 +139,9 @@ public: void _update_window(); + int video_driver_index; + virtual int get_current_video_driver() const; + protected: virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 41a19ac992..e77f8b3173 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -1176,6 +1176,10 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay displays_arrangement_dirty = true; } +int OS_OSX::get_current_video_driver() const { + return video_driver_index; +} + Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { /*** OSX INITIALIZATION ***/ @@ -1272,6 +1276,8 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core); } + video_driver_index = p_video_driver; + ADD_ATTR2(NSOpenGLPFAColorSize, colorBits); /* @@ -1345,6 +1351,8 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a AudioDriverManager::initialize(p_audio_driver); + midi_driver.open(); + input = memnew(InputDefault); joypad_osx = memnew(JoypadOSX); diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index 3b1be780d4..1c17780ad7 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -28,16 +28,17 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "os_server.h" + #include "drivers/dummy/audio_driver_dummy.h" #include "drivers/dummy/rasterizer_dummy.h" #include "drivers/dummy/texture_loader_dummy.h" #include "print_string.h" #include "servers/visual/visual_server_raster.h" -#include <stdio.h> -#include <stdlib.h> #include "main/main.h" +#include <stdio.h> +#include <stdlib.h> #include <unistd.h> int OS_Server::get_video_driver_count() const { @@ -58,6 +59,10 @@ const char *OS_Server::get_audio_driver_name(int p_driver) const { return "Dummy"; } +int OS_Server::get_current_video_driver() const { + return video_driver_index; +} + void OS_Server::initialize_core() { crash_handler.initialize(); @@ -73,6 +78,8 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int RasterizerDummy::make_current(); + video_driver_index = p_video_driver; // unused in server platform, but should still be initialized + visual_server = memnew(VisualServerRaster); visual_server->init(); diff --git a/platform/server/os_server.h b/platform/server/os_server.h index f1a880ecc2..07d70e5236 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -30,12 +30,12 @@ #ifndef OS_SERVER_H #define OS_SERVER_H -#include "../x11/crash_handler_x11.h" -#include "../x11/power_x11.h" #include "drivers/dummy/texture_loader_dummy.h" #include "drivers/rtaudio/audio_driver_rtaudio.h" #include "drivers/unix/os_unix.h" #include "main/input_default.h" +#include "platform/x11/crash_handler_x11.h" +#include "platform/x11/power_x11.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" #include "servers/visual_server.h" @@ -47,7 +47,6 @@ class OS_Server : public OS_Unix { - //Rasterizer *rasterizer; VisualServer *visual_server; VideoMode current_videomode; List<String> args; @@ -66,12 +65,14 @@ class OS_Server : public OS_Unix { CrashHandler crash_handler; + int video_driver_index; + ResourceFormatDummyTexture *resource_loader_dummy; protected: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; - + virtual int get_current_video_driver() const; virtual int get_audio_driver_count() const; virtual const char *get_audio_driver_name(int p_driver) const; diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index 8cdd13df90..8549a44ce5 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -77,6 +77,10 @@ Size2 OSUWP::get_window_size() const { return size; } +int OSUWP::get_current_video_driver() const { + return video_driver_index; +} + void OSUWP::set_window_size(const Size2 p_size) { Windows::Foundation::Size new_size; @@ -237,6 +241,8 @@ Error OSUWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_au } gl_context->set_use_vsync(vm.use_vsync); + video_driver_index = p_video_driver; + visual_server = memnew(VisualServerRaster); // FIXME: Reimplement threaded rendering? Or remove? /* @@ -290,7 +296,7 @@ Error OSUWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_au if (is_keep_screen_on()) display_request->RequestActive(); - set_keep_screen_on(GLOBAL_DEF("display/window/keep_screen_on", true)); + set_keep_screen_on(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)); return OK; } @@ -385,7 +391,6 @@ void OSUWP::ManagedType::update_clipboard() { if (data->Contains(StandardDataFormats::Text)) { create_task(data->GetTextAsync()).then([this](Platform::String ^ clipboard_content) { - this->clipboard = clipboard_content; }); } diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index 0f7f068652..3b48063fe9 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -99,6 +99,7 @@ private: Windows::UI::Core::CoreWindow ^ window; VideoMode video_mode; + int video_driver_index; MainLoop *main_loop; @@ -154,6 +155,7 @@ private: // functions used by main to initialize/deintialize the OS protected: virtual int get_video_driver_count() const; + virtual int get_current_video_driver() const; virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); diff --git a/platform/windows/context_gl_win.cpp b/platform/windows/context_gl_win.cpp index d312fbcb12..a158237418 100644 --- a/platform/windows/context_gl_win.cpp +++ b/platform/windows/context_gl_win.cpp @@ -106,9 +106,9 @@ Error ContextGL_Win::initialize() { PFD_SUPPORT_OPENGL | // Format Must Support OpenGL PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, - 24, + OS::get_singleton()->is_layered_allowed() ? 32 : 24, 0, 0, 0, 0, 0, 0, // Color Bits Ignored - 0, // No Alpha Buffer + OS::get_singleton()->is_layered_allowed() ? 8 : 0, // Alpha Buffer 0, // Shift Bit Ignored 0, // No Accumulation Buffer 0, 0, 0, 0, // Accumulation Bits Ignored diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 05806d2fe8..34fc3e09b5 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -166,12 +166,18 @@ def configure_msvc(env, manual_msvc_config): # Build type if (env["target"] == "release"): - env.Append(CCFLAGS=['/O2']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Append(CCFLAGS=['/O2']) + else: # optimize for size + env.Append(CCFLAGS=['/O1']) env.Append(LINKFLAGS=['/SUBSYSTEM:WINDOWS']) env.Append(LINKFLAGS=['/ENTRY:mainCRTStartup']) elif (env["target"] == "release_debug"): - env.Append(CCFLAGS=['/O2']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Append(CCFLAGS=['/O2']) + else: # optimize for size + env.Append(CCFLAGS=['/O1']) env.AppendUnique(CPPDEFINES = ['DEBUG_ENABLED']) env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE']) @@ -200,7 +206,8 @@ def configure_msvc(env, manual_msvc_config): env.AppendUnique(CPPDEFINES = ['WINDOWS_ENABLED', 'OPENGL_ENABLED', 'RTAUDIO_ENABLED', 'WASAPI_ENABLED', - 'TYPED_METHOD_BIND', 'WIN32', 'MSVC', + 'WINMIDI_ENABLED', 'TYPED_METHOD_BIND', + 'WIN32', 'MSVC', {'WINVER' : '$target_win_version', '_WIN32_WINNT': '$target_win_version'}]) if env["bits"] == "64": @@ -247,10 +254,14 @@ def configure_mingw(env): if (env["target"] == "release"): env.Append(CCFLAGS=['-msse2']) - if (env["bits"] == "64"): - env.Append(CCFLAGS=['-O3']) - else: - env.Append(CCFLAGS=['-O2']) + if (env["optimize"] == "speed"): #optimize for speed (default) + if (env["bits"] == "64"): + env.Append(CCFLAGS=['-O3']) + else: + env.Append(CCFLAGS=['-O2']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os']) + env.Append(LINKFLAGS=['-Wl,--subsystem,windows']) @@ -265,7 +276,11 @@ def configure_mingw(env): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): env.Prepend(CCFLAGS=['-g2']) - + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Append(CCFLAGS=['-O2']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os']) + elif (env["target"] == "debug"): env.Append(CCFLAGS=['-g3', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) diff --git a/platform/windows/godot_win.cpp b/platform/windows/godot_win.cpp index 80f53dd1a1..504a9a0380 100644 --- a/platform/windows/godot_win.cpp +++ b/platform/windows/godot_win.cpp @@ -176,8 +176,8 @@ int _main() { } int main(int _argc, char **_argv) { -// _argc and _argv are ignored -// we are going to use the WideChar version of them instead + // _argc and _argv are ignored + // we are going to use the WideChar version of them instead #ifdef CRASH_HANDLER_EXCEPTION __try { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 05d16a5964..e083fd7323 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1012,6 +1012,10 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS { SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2 } SHC_PROCESS_DPI_AWARENESS; +int OS_Windows::get_current_video_driver() const { + return video_driver_index; +} + Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { main_loop = NULL; @@ -1181,6 +1185,8 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int RasterizerGLES3::make_current(); } + video_driver_index = p_video_driver; // FIXME TODO - FIX IF DRIVER DETECTION HAPPENS AND GLES2 MUST BE USED + gl_context->set_use_vsync(video_mode.use_vsync); #endif @@ -1213,6 +1219,10 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int AudioDriverManager::initialize(p_audio_driver); +#ifdef WINMIDI_ENABLED + driver_midi.open(); +#endif + TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; @@ -1341,6 +1351,10 @@ void OS_Windows::set_main_loop(MainLoop *p_main_loop) { void OS_Windows::finalize() { +#ifdef WINMIDI_ENABLED + driver_midi.close(); +#endif + if (main_loop) memdelete(main_loop); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 19af63bae0..69c7d851b8 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -35,6 +35,7 @@ #include "crash_handler_win.h" #include "drivers/rtaudio/audio_driver_rtaudio.h" #include "drivers/wasapi/audio_driver_wasapi.h" +#include "drivers/winmidi/win_midi.h" #include "os/input.h" #include "os/os.h" #include "power_windows.h" @@ -134,6 +135,7 @@ class OS_Windows : public OS { PowerWindows *power_manager; + int video_driver_index; #ifdef WASAPI_ENABLED AudioDriverWASAPI driver_wasapi; #endif @@ -143,6 +145,9 @@ class OS_Windows : public OS { #ifdef XAUDIO2_ENABLED AudioDriverXAudio2 driver_xaudio2; #endif +#ifdef WINMIDI_ENABLED + MIDIDriverWinMidi driver_midi; +#endif CrashHandler crash_handler; @@ -153,6 +158,8 @@ class OS_Windows : public OS { // functions used by main to initialize/deintialize the OS protected: + virtual int get_current_video_driver() const; + virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); diff --git a/platform/x11/detect.py b/platform/x11/detect.py index 09e16ad078..6a7a426804 100644 --- a/platform/x11/detect.py +++ b/platform/x11/detect.py @@ -62,6 +62,7 @@ def get_opts(): EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), BoolVariable('touch', 'Enable touch events', True), + BoolVariable('execinfo', 'Use libexecinfo on systems where glibc is not available', False), ] @@ -81,14 +82,22 @@ def configure(env): if (env["target"] == "release"): # -O3 -ffast-math is identical to -Ofast. We need to split it out so we can selectively disable # -ffast-math in code for which it generates wrong results. - env.Prepend(CCFLAGS=['-O3', '-ffast-math']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Prepend(CCFLAGS=['-O3', '-ffast-math']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os']) + if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "release_debug"): - env.Prepend(CCFLAGS=['-O2', '-ffast-math', '-DDEBUG_ENABLED']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Prepend(CCFLAGS=['-O2', '-ffast-math', '-DDEBUG_ENABLED']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os', '-DDEBUG_ENABLED']) + if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): @@ -240,7 +249,7 @@ def configure(env): if (os.system("pkg-config --exists alsa") == 0): # 0 means found print("Enabling ALSA") - env.Append(CPPFLAGS=["-DALSA_ENABLED"]) + env.Append(CPPFLAGS=["-DALSA_ENABLED", "-DALSAMIDI_ENABLED"]) env.ParseConfig('pkg-config alsa --cflags --libs') else: print("ALSA libraries not found, disabling driver") @@ -276,6 +285,9 @@ def configure(env): env.Append(LIBS=['dl']) if (platform.system().find("BSD") >= 0): + env["execinfo"] = True + + if env["execinfo"]: env.Append(LIBS=['execinfo']) ## Cross-compilation diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 2bc85f76c9..260ce57732 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -84,6 +84,10 @@ void OS_X11::initialize_core() { OS_Unix::initialize_core(); } +int OS_X11::get_current_video_driver() const { + return video_driver_index; +} + Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { long im_event_mask = 0; @@ -285,6 +289,8 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a } break; } + video_driver_index = p_video_driver; // FIXME TODO - FIX IF DRIVER DETECTION HAPPENS AND GLES2 MUST BE USED + context_gl->set_use_vsync(current_videomode.use_vsync); #endif @@ -336,6 +342,10 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a AudioDriverManager::initialize(p_audio_driver); +#ifdef ALSAMIDI_ENABLED + driver_alsamidi.open(); +#endif + ERR_FAIL_COND_V(!visual_server, ERR_UNAVAILABLE); ERR_FAIL_COND_V(x11_window == 0, ERR_UNAVAILABLE); @@ -600,6 +610,9 @@ void OS_X11::finalize() { memdelete(debugger_connection_console); } */ +#ifdef ALSAMIDI_ENABLED + driver_alsamidi.close(); +#endif #ifdef JOYDEV_ENABLED memdelete(joypad); diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 8cab23fe63..44455a2d8d 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -38,6 +38,7 @@ #include "servers/visual_server.h" //#include "servers/visual/visual_server_wrap_mt.h" #include "drivers/alsa/audio_driver_alsa.h" +#include "drivers/alsamidi/alsa_midi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" #include "joypad_linux.h" #include "main/input_default.h" @@ -168,6 +169,10 @@ class OS_X11 : public OS_Unix { AudioDriverALSA driver_alsa; #endif +#ifdef ALSAMIDI_ENABLED + MIDIDriverALSAMidi driver_alsamidi; +#endif + #ifdef PULSEAUDIO_ENABLED AudioDriverPulseAudio driver_pulseaudio; #endif @@ -180,6 +185,7 @@ class OS_X11 : public OS_Unix { CrashHandler crash_handler; + int video_driver_index; int audio_driver_index; unsigned int capture_idle; bool maximized; @@ -195,6 +201,8 @@ class OS_X11 : public OS_Unix { Bool xrandr_ext_ok; protected: + virtual int get_current_video_driver() const; + virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); virtual void finalize(); diff --git a/scene/2d/animated_sprite.cpp b/scene/2d/animated_sprite.cpp index e3d1592be0..b56eedabc7 100644 --- a/scene/2d/animated_sprite.cpp +++ b/scene/2d/animated_sprite.cpp @@ -192,6 +192,16 @@ void SpriteFrames::get_animation_list(List<StringName> *r_animations) const { } } +Vector<String> SpriteFrames::get_animation_names() const { + + Vector<String> names; + for (const Map<StringName, Anim>::Element *E = animations.front(); E; E = E->next()) { + names.push_back(E->key()); + } + names.sort(); + return names; +} + void SpriteFrames::set_animation_speed(const StringName &p_anim, float p_fps) { ERR_FAIL_COND(p_fps < 0); @@ -283,6 +293,8 @@ void SpriteFrames::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_animation", "anim"), &SpriteFrames::remove_animation); ClassDB::bind_method(D_METHOD("rename_animation", "anim", "newname"), &SpriteFrames::rename_animation); + ClassDB::bind_method(D_METHOD("get_animation_names"), &SpriteFrames::get_animation_names); + ClassDB::bind_method(D_METHOD("set_animation_speed", "anim", "speed"), &SpriteFrames::set_animation_speed); ClassDB::bind_method(D_METHOD("get_animation_speed", "anim"), &SpriteFrames::get_animation_speed); diff --git a/scene/2d/animated_sprite.h b/scene/2d/animated_sprite.h index 806052a696..f6586aff36 100644 --- a/scene/2d/animated_sprite.h +++ b/scene/2d/animated_sprite.h @@ -72,6 +72,7 @@ public: void rename_animation(const StringName &p_prev, const StringName &p_next); void get_animation_list(List<StringName> *r_animations) const; + Vector<String> get_animation_names() const; void set_animation_speed(const StringName &p_anim, float p_fps); float get_animation_speed(const StringName &p_anim) const; diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index 47326b9be2..a035d9021f 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -411,6 +411,9 @@ void CanvasItem::_enter_canvas() { if (canvas_layer) { break; } + if (Object::cast_to<Viewport>(n)) { + break; + } n = n->get_parent(); } diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index d05c818ae1..cabd7fddc2 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -38,10 +38,14 @@ void CollisionObject2D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { + Transform2D global_transform = get_global_transform(); + if (area) - Physics2DServer::get_singleton()->area_set_transform(rid, get_global_transform()); + Physics2DServer::get_singleton()->area_set_transform(rid, global_transform); else - Physics2DServer::get_singleton()->body_set_state(rid, Physics2DServer::BODY_STATE_TRANSFORM, get_global_transform()); + Physics2DServer::get_singleton()->body_set_state(rid, Physics2DServer::BODY_STATE_TRANSFORM, global_transform); + + last_transform = global_transform; RID space = get_world_2d()->get_space(); if (area) { @@ -60,10 +64,18 @@ void CollisionObject2D::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { + Transform2D global_transform = get_global_transform(); + + if (only_update_transform_changes && global_transform == last_transform) { + return; + } + if (area) - Physics2DServer::get_singleton()->area_set_transform(rid, get_global_transform()); + Physics2DServer::get_singleton()->area_set_transform(rid, global_transform); else - Physics2DServer::get_singleton()->body_set_state(rid, Physics2DServer::BODY_STATE_TRANSFORM, get_global_transform()); + Physics2DServer::get_singleton()->body_set_state(rid, Physics2DServer::BODY_STATE_TRANSFORM, global_transform); + + last_transform = global_transform; } break; case NOTIFICATION_EXIT_TREE: { @@ -318,6 +330,10 @@ void CollisionObject2D::_mouse_exit() { emit_signal(SceneStringNames::get_singleton()->mouse_exited); } +void CollisionObject2D::set_only_update_transform_changes(bool p_enable) { + only_update_transform_changes = p_enable; +} + void CollisionObject2D::_update_pickable() { if (!is_inside_tree()) return; @@ -384,6 +400,7 @@ CollisionObject2D::CollisionObject2D(RID p_rid, bool p_area) { pickable = true; set_notify_transform(true); total_subshapes = 0; + only_update_transform_changes = false; if (p_area) { diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 6da63d1a0b..29a00bd9f9 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -65,6 +65,8 @@ class CollisionObject2D : public Node2D { int total_subshapes; Map<uint32_t, ShapeData> shapes; + Transform2D last_transform; + bool only_update_transform_changes; //this is used for sync physics in KinematicBody protected: CollisionObject2D(RID p_rid, bool p_area); @@ -78,6 +80,8 @@ protected: void _mouse_enter(); void _mouse_exit(); + void set_only_update_transform_changes(bool p_enable); + public: uint32_t create_shape_owner(Object *p_owner); void remove_shape_owner(uint32_t owner); diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index 3e61dd05f4..e9e895b5bb 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -349,7 +349,7 @@ void Line2D::_bind_methods() { ADD_GROUP("Fill", ""); ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient"); ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); - ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "None,Tile"), "set_texture_mode", "get_texture_mode"); + ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "None,Tile,Stretch"), "set_texture_mode", "get_texture_mode"); ADD_GROUP("Capping", ""); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "joint_mode", PROPERTY_HINT_ENUM, "Sharp,Bevel,Round"), "set_joint_mode", "get_joint_mode"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "begin_cap_mode", PROPERTY_HINT_ENUM, "None,Box,Round"), "set_begin_cap_mode", "get_begin_cap_mode"); @@ -368,6 +368,7 @@ void Line2D::_bind_methods() { BIND_ENUM_CONSTANT(LINE_TEXTURE_NONE); BIND_ENUM_CONSTANT(LINE_TEXTURE_TILE); + BIND_ENUM_CONSTANT(LINE_TEXTURE_STRETCH); ClassDB::bind_method(D_METHOD("_gradient_changed"), &Line2D::_gradient_changed); } diff --git a/scene/2d/line_2d.h b/scene/2d/line_2d.h index 24c48982cd..6918018c12 100644 --- a/scene/2d/line_2d.h +++ b/scene/2d/line_2d.h @@ -52,8 +52,8 @@ public: enum LineTextureMode { LINE_TEXTURE_NONE = 0, - LINE_TEXTURE_TILE - // TODO STRETCH mode + LINE_TEXTURE_TILE, + LINE_TEXTURE_STRETCH }; Line2D(); diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index 845788bada..a3f1b25e05 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -146,7 +146,9 @@ void LineBuilder::build() { float current_distance1 = 0.f; float total_distance = 0.f; _interpolate_color = gradient != NULL; - bool distance_required = _interpolate_color || texture_mode == Line2D::LINE_TEXTURE_TILE; + bool distance_required = _interpolate_color || + texture_mode == Line2D::LINE_TEXTURE_TILE || + texture_mode == Line2D::LINE_TEXTURE_STRETCH; if (distance_required) total_distance = calculate_total_distance(points); if (_interpolate_color) @@ -170,7 +172,7 @@ void LineBuilder::build() { if (texture_mode == Line2D::LINE_TEXTURE_TILE) { uvx0 = 0.5f / tile_aspect; } - new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, 1.f, 1.f)); + new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, fmin(uvx0 * 2, 1.f), 1.f)); total_distance += width; current_distance0 += hw; current_distance1 = current_distance0; @@ -290,8 +292,10 @@ void LineBuilder::build() { color1 = gradient->get_color_at_offset(current_distance1 / total_distance); } if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx0 = current_distance0 / (width * tile_aspect); uvx1 = current_distance1 / (width * tile_aspect); + } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { + uvx0 = current_distance0 / total_distance; + uvx1 = current_distance1 / total_distance; } strip_add_quad(pos_up1, pos_down1, color1, uvx1); @@ -301,7 +305,6 @@ void LineBuilder::build() { u0 = u1; f0 = f1; pos0 = pos1; - current_distance0 = current_distance1; if (intersection_result == SEGMENT_INTERSECT) { if (current_joint_mode == Line2D::LINE_JOINT_SHARP) { pos_up0 = pos_up1; @@ -378,6 +381,8 @@ void LineBuilder::build() { } if (texture_mode == Line2D::LINE_TEXTURE_TILE) { uvx1 = current_distance1 / (width * tile_aspect); + } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { + uvx1 = current_distance1 / total_distance; } strip_add_quad(pos_up1, pos_down1, color1, uvx1); @@ -386,7 +391,7 @@ void LineBuilder::build() { if (end_cap_mode == Line2D::LINE_CAP_ROUND) { // Note: color is not used in case we don't interpolate... Color color = _interpolate_color ? gradient->get_color(gradient->get_points_count() - 1) : Color(0, 0, 0); - new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1 - 0.5f, 0.f, 1.f, 1.f)); + new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1 - 0.5f / tile_aspect, 0.f, 1.0f / tile_aspect, 1.f)); } } diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 3813bd96fe..7252602a93 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -130,7 +130,6 @@ void Node2D::_update_xform_values() { void Node2D::_update_transform() { - Transform2D mat(angle, pos); _mat.set_rotation_and_scale(angle, _scale); _mat.elements[2] = pos; diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index feb11089d0..d5a61b0b6b 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -30,10 +30,11 @@ #include "physics_body_2d.h" +#include "core/core_string_names.h" #include "core/method_bind_ext.gen.inc" #include "engine.h" +#include "math_funcs.h" #include "scene/scene_string_names.h" - void PhysicsBody2D::_notification(int p_what) { /* @@ -186,28 +187,75 @@ real_t StaticBody2D::get_constant_angular_velocity() const { return constant_angular_velocity; } +#ifndef DISABLE_DEPRECATED void StaticBody2D::set_friction(real_t p_friction) { + ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + ERR_FAIL_COND(p_friction < 0 || p_friction > 1); - friction = p_friction; - Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, friction); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_friction(p_friction); + _reload_physics_characteristics(); } + real_t StaticBody2D::get_friction() const { - return friction; + ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + + if (physics_material_override.is_null()) { + return 1; + } + + return physics_material_override->get_friction(); } void StaticBody2D::set_bounce(real_t p_bounce) { + ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); - bounce = p_bounce; - Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, bounce); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_bounce(p_bounce); + _reload_physics_characteristics(); } + real_t StaticBody2D::get_bounce() const { - return bounce; + ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + + if (physics_material_override.is_null()) { + return 0; + } + + return physics_material_override->get_bounce(); +} +#endif + +void StaticBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { + if (physics_material_override.is_valid()) { + physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + + physics_material_override = p_physics_material_override; + + if (physics_material_override.is_valid()) { + physics_material_override->connect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + _reload_physics_characteristics(); +} + +Ref<PhysicsMaterial> StaticBody2D::get_physics_material_override() const { + return physics_material_override; } void StaticBody2D::_bind_methods() { @@ -222,23 +270,41 @@ void StaticBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bounce", "bounce"), &StaticBody2D::set_bounce); ClassDB::bind_method(D_METHOD("get_bounce"), &StaticBody2D::get_bounce); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override); + + ClassDB::bind_method(D_METHOD("_reload_physics_characteristics"), &StaticBody2D::_reload_physics_characteristics); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); } StaticBody2D::StaticBody2D() : PhysicsBody2D(Physics2DServer::BODY_MODE_STATIC) { constant_angular_velocity = 0; - bounce = 0; - friction = 1; } StaticBody2D::~StaticBody2D() { } +void StaticBody2D::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, 0); + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, 1); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, Physics2DServer::COMBINE_MODE_INHERIT); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, Physics2DServer::COMBINE_MODE_INHERIT); + } else { + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, physics_material_override->get_bounce()); + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, physics_material_override->get_friction()); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, (Physics2DServer::CombineMode)physics_material_override->get_bounce_combine_mode()); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, (Physics2DServer::CombineMode)physics_material_override->get_friction_combine_mode()); + } +} + void RigidBody2D::_body_enter_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); @@ -545,28 +611,72 @@ real_t RigidBody2D::get_weight() const { return mass * real_t(GLOBAL_DEF("physics/2d/default_gravity", 98)) / 10; } +#ifndef DISABLE_DEPRECATED void RigidBody2D::set_friction(real_t p_friction) { + ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED ERR_FAIL_COND(p_friction < 0 || p_friction > 1); - friction = p_friction; - Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, friction); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_friction(p_friction); + _reload_physics_characteristics(); } real_t RigidBody2D::get_friction() const { - return friction; + ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + + if (physics_material_override.is_null()) { + return 1; + } + + return physics_material_override->get_friction(); } void RigidBody2D::set_bounce(real_t p_bounce) { + ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); - bounce = p_bounce; - Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, bounce); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_bounce(p_bounce); + _reload_physics_characteristics(); } real_t RigidBody2D::get_bounce() const { - return bounce; + ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + + if (physics_material_override.is_null()) { + return 0; + } + + return physics_material_override->get_bounce(); +} +#endif + +void RigidBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { + if (physics_material_override.is_valid()) { + physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + + physics_material_override = p_physics_material_override; + + if (physics_material_override.is_valid()) { + physics_material_override->connect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + _reload_physics_characteristics(); +} + +Ref<PhysicsMaterial> RigidBody2D::get_physics_material_override() const { + return physics_material_override; } void RigidBody2D::set_gravity_scale(real_t p_gravity_scale) { @@ -843,6 +953,11 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bounce", "bounce"), &RigidBody2D::set_bounce); ClassDB::bind_method(D_METHOD("get_bounce"), &RigidBody2D::get_bounce); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody2D::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody2D::get_physics_material_override); + + ClassDB::bind_method(D_METHOD("_reload_physics_characteristics"), &RigidBody2D::_reload_physics_characteristics); + ClassDB::bind_method(D_METHOD("set_gravity_scale", "gravity_scale"), &RigidBody2D::set_gravity_scale); ClassDB::bind_method(D_METHOD("get_gravity_scale"), &RigidBody2D::get_gravity_scale); @@ -903,6 +1018,7 @@ void RigidBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "weight", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01", PROPERTY_USAGE_EDITOR), "set_weight", "get_weight"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); ADD_PROPERTY(PropertyInfo(Variant::INT, "continuous_cd", PROPERTY_HINT_ENUM, "Disabled,Cast Ray,Cast Shape"), "set_continuous_collision_detection_mode", "get_continuous_collision_detection_mode"); @@ -941,9 +1057,7 @@ RigidBody2D::RigidBody2D() : mode = MODE_RIGID; - bounce = 0; mass = 1; - friction = 1; gravity_scale = 1; linear_damp = -1; @@ -969,13 +1083,27 @@ RigidBody2D::~RigidBody2D() { memdelete(contact_monitor); } +void RigidBody2D::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, 0); + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, 1); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, Physics2DServer::COMBINE_MODE_INHERIT); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, Physics2DServer::COMBINE_MODE_INHERIT); + } else { + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, physics_material_override->get_bounce()); + Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, physics_material_override->get_friction()); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_BOUNCE, (Physics2DServer::CombineMode)physics_material_override->get_bounce_combine_mode()); + Physics2DServer::get_singleton()->body_set_combine_mode(get_rid(), Physics2DServer::BODY_PARAM_FRICTION, (Physics2DServer::CombineMode)physics_material_override->get_friction_combine_mode()); + } +} + ////////////////////////// -Ref<KinematicCollision2D> KinematicBody2D::_move(const Vector2 &p_motion, bool p_infinite_inertia) { +Ref<KinematicCollision2D> KinematicBody2D::_move(const Vector2 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, bool p_test_only) { Collision col; - if (move_and_collide(p_motion, p_infinite_inertia, col)) { + if (move_and_collide(p_motion, p_infinite_inertia, col, p_exclude_raycast_shapes, p_test_only)) { if (motion_cache.is_null()) { motion_cache.instance(); motion_cache->owner = this; @@ -989,11 +1117,48 @@ Ref<KinematicCollision2D> KinematicBody2D::_move(const Vector2 &p_motion, bool p return Ref<KinematicCollision2D>(); } -bool KinematicBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision) { +bool KinematicBody2D::separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision) { + + Physics2DServer::SeparationResult sep_res[8]; //max 8 rays + + Transform2D gt = get_global_transform(); + + Vector2 recover; + int hits = Physics2DServer::get_singleton()->body_test_ray_separation(get_rid(), gt, p_infinite_inertia, recover, sep_res, 8, margin); + int deepest = -1; + float deepest_depth; + for (int i = 0; i < hits; i++) { + if (deepest == -1 || sep_res[i].collision_depth > deepest_depth) { + deepest = i; + deepest_depth = sep_res[i].collision_depth; + } + } + + gt.elements[2] += recover; + set_global_transform(gt); + + if (deepest != -1) { + r_collision.collider = sep_res[deepest].collider_id; + r_collision.collider_metadata = sep_res[deepest].collider_metadata; + r_collision.collider_shape = sep_res[deepest].collider_shape; + r_collision.collider_vel = sep_res[deepest].collider_velocity; + r_collision.collision = sep_res[deepest].collision_point; + r_collision.normal = sep_res[deepest].collision_normal; + r_collision.local_shape = sep_res[deepest].collision_local_shape; + r_collision.travel = recover; + r_collision.remainder = Vector2(); + + return true; + } else { + return false; + } +} + +bool KinematicBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes, bool p_test_only) { Transform2D gt = get_global_transform(); Physics2DServer::MotionResult result; - bool colliding = Physics2DServer::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, margin, &result); + bool colliding = Physics2DServer::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, margin, &result, p_exclude_raycast_shapes); if (colliding) { r_collision.collider_metadata = result.collider_metadata; @@ -1002,23 +1167,36 @@ bool KinematicBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_ r_collision.collision = result.collision_point; r_collision.normal = result.collision_normal; r_collision.collider = result.collider_id; + r_collision.collider_rid = result.collider; r_collision.travel = result.motion; r_collision.remainder = result.remainder; r_collision.local_shape = result.collision_local_shape; } - gt.elements[2] += result.motion; - set_global_transform(gt); + if (!p_test_only) { + gt.elements[2] += result.motion; + set_global_transform(gt); + } return colliding; } Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const Vector2 &p_floor_direction, bool p_infinite_inertia, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle) { - Vector2 motion = (floor_velocity + p_linear_velocity) * get_physics_process_delta_time(); + Vector2 floor_motion = floor_velocity; + if (on_floor && on_floor_body.is_valid()) { + //this approach makes sure there is less delay between the actual body velocity and the one we saved + Physics2DDirectBodyState *bs = Physics2DServer::get_singleton()->body_get_direct_state(on_floor_body); + if (bs) { + floor_motion = bs->get_linear_velocity(); + } + } + + Vector2 motion = (floor_motion + p_linear_velocity) * get_physics_process_delta_time(); Vector2 lv = p_linear_velocity; on_floor = false; + on_floor_body = RID(); on_ceiling = false; on_wall = false; colliders.clear(); @@ -1027,48 +1205,68 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const while (p_max_slides) { Collision collision; + bool found_collision = false; + + for (int i = 0; i < 2; i++) { + bool collided; + if (i == 0) { //collide + collided = move_and_collide(motion, p_infinite_inertia, collision); + if (!collided) { + motion = Vector2(); //clear because no collision happened and motion completed + } + } else { //separate raycasts (if any) + collided = separate_raycast_shapes(p_infinite_inertia, collision); + if (collided) { + collision.remainder = motion; //keep + collision.travel = Vector2(); + } + } - bool collided = move_and_collide(motion, p_infinite_inertia, collision); - - if (collided) { - - motion = collision.remainder; - - if (p_floor_direction == Vector2()) { - //all is a wall - on_wall = true; - } else { - if (collision.normal.dot(p_floor_direction) >= Math::cos(p_floor_max_angle)) { //floor + if (collided) { + found_collision = true; + } - on_floor = true; - floor_velocity = collision.collider_vel; + if (collided) { - Vector2 rel_v = lv - floor_velocity; - Vector2 hv = rel_v - p_floor_direction * p_floor_direction.dot(rel_v); + motion = collision.remainder; - if (collision.travel.length() < 1 && hv.length() < p_slope_stop_min_velocity) { - Transform2D gt = get_global_transform(); - gt.elements[2] -= collision.travel; - set_global_transform(gt); - return Vector2(); - } - } else if (collision.normal.dot(-p_floor_direction) >= Math::cos(p_floor_max_angle)) { //ceiling - on_ceiling = true; - } else { + if (p_floor_direction == Vector2()) { + //all is a wall on_wall = true; + } else { + if (collision.normal.dot(p_floor_direction) >= Math::cos(p_floor_max_angle)) { //floor + + on_floor = true; + on_floor_body = collision.collider_rid; + floor_velocity = collision.collider_vel; + + Vector2 rel_v = lv - floor_velocity; + Vector2 hv = rel_v - p_floor_direction * p_floor_direction.dot(rel_v); + + if (collision.travel.length() < 1 && hv.length() < p_slope_stop_min_velocity) { + Transform2D gt = get_global_transform(); + gt.elements[2] -= collision.travel; + set_global_transform(gt); + return Vector2(); + } + } else if (collision.normal.dot(-p_floor_direction) >= Math::cos(p_floor_max_angle)) { //ceiling + on_ceiling = true; + } else { + on_wall = true; + } } - } - Vector2 n = collision.normal; - motion = motion.slide(n); - lv = lv.slide(n); + Vector2 n = collision.normal; + motion = motion.slide(n); + lv = lv.slide(n); - colliders.push_back(collision); + colliders.push_back(collision); + } + } - } else { + if (!found_collision) { break; } - p_max_slides--; if (motion == Vector2()) break; @@ -1077,6 +1275,31 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const return lv; } +Vector2 KinematicBody2D::move_and_slide_with_snap(const Vector2 &p_linear_velocity, const Vector2 &p_snap, const Vector2 &p_floor_direction, bool p_infinite_inertia, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle) { + + bool was_on_floor = on_floor; + + Vector2 ret = move_and_slide(p_linear_velocity, p_floor_direction, p_infinite_inertia, p_slope_stop_min_velocity, p_max_slides, p_floor_max_angle); + if (!was_on_floor || p_snap == Vector2()) { + return ret; + } + + Collision col; + Transform2D gt = get_global_transform(); + + if (move_and_collide(p_snap, p_infinite_inertia, col, false, true)) { + gt.elements[2] += col.travel; + if (p_floor_direction != Vector2() && Math::acos(p_floor_direction.normalized().dot(col.normal)) < p_floor_max_angle) { + on_floor = true; + on_floor_body = col.collider_rid; + floor_velocity = col.collider_vel; + } + set_global_transform(gt); + } + + return ret; +} + bool KinematicBody2D::is_on_floor() const { return on_floor; @@ -1138,10 +1361,60 @@ Ref<KinematicCollision2D> KinematicBody2D::_get_slide_collision(int p_bounce) { return slide_colliders[p_bounce]; } +void KinematicBody2D::set_sync_to_physics(bool p_enable) { + + if (sync_to_physics == p_enable) { + return; + } + sync_to_physics = p_enable; + if (p_enable) { + Physics2DServer::get_singleton()->body_set_force_integration_callback(get_rid(), this, "_direct_state_changed"); + set_only_update_transform_changes(true); + set_notify_local_transform(true); + } else { + Physics2DServer::get_singleton()->body_set_force_integration_callback(get_rid(), NULL, ""); + set_only_update_transform_changes(false); + set_notify_local_transform(false); + } +} + +bool KinematicBody2D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void KinematicBody2D::_direct_state_changed(Object *p_state) { + + if (!sync_to_physics) + return; + + Physics2DDirectBodyState *state = Object::cast_to<Physics2DDirectBodyState>(p_state); + + last_valid_transform = state->get_transform(); + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); +} + +void KinematicBody2D::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE) { + last_valid_transform = get_global_transform(); + } + + if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { + //used by sync to physics, send the new transform to the physics + Transform2D new_transform = get_global_transform(); + Physics2DServer::get_singleton()->body_set_state(get_rid(), Physics2DServer::BODY_STATE_TRANSFORM, new_transform); + //but then revert changes + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); + } +} void KinematicBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia"), &KinematicBody2D::_move, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "test_only"), &KinematicBody2D::_move, DEFVAL(true), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "infinite_inertia", "slope_stop_min_velocity", "max_bounces", "floor_max_angle"), &KinematicBody2D::move_and_slide, DEFVAL(Vector2(0, 0)), DEFVAL(true), DEFVAL(5), DEFVAL(4), DEFVAL(Math::deg2rad((float)45))); + ClassDB::bind_method(D_METHOD("move_and_slide_with_snap", "linear_velocity", "snap", "floor_normal", "infinite_inertia", "slope_stop_min_velocity", "max_bounces", "floor_max_angle"), &KinematicBody2D::move_and_slide_with_snap, DEFVAL(Vector2(0, 0)), DEFVAL(true), DEFVAL(5), DEFVAL(4), DEFVAL(Math::deg2rad((float)45))); ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia"), &KinematicBody2D::test_move); @@ -1156,7 +1429,13 @@ void KinematicBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_slide_count"), &KinematicBody2D::get_slide_count); ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &KinematicBody2D::_get_slide_collision); + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &KinematicBody2D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &KinematicBody2D::is_sync_to_physics_enabled); + + ClassDB::bind_method(D_METHOD("_direct_state_changed"), &KinematicBody2D::_direct_state_changed); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "motion/sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); } KinematicBody2D::KinematicBody2D() : @@ -1167,6 +1446,7 @@ KinematicBody2D::KinematicBody2D() : on_floor = false; on_ceiling = false; on_wall = false; + sync_to_physics = false; } KinematicBody2D::~KinematicBody2D() { if (motion_cache.is_valid()) { diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 0fda3c5c05..bd100f6228 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -32,6 +32,7 @@ #define PHYSICS_BODY_2D_H #include "scene/2d/collision_object_2d.h" +#include "scene/resources/physics_material.h" #include "servers/physics_2d_server.h" #include "vset.h" @@ -79,18 +80,21 @@ class StaticBody2D : public PhysicsBody2D { Vector2 constant_linear_velocity; real_t constant_angular_velocity; - real_t bounce; - real_t friction; + Ref<PhysicsMaterial> physics_material_override; protected: static void _bind_methods(); public: +#ifndef DISABLE_DEPRECATED void set_friction(real_t p_friction); real_t get_friction() const; void set_bounce(real_t p_bounce); real_t get_bounce() const; +#endif + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); + Ref<PhysicsMaterial> get_physics_material_override() const; void set_constant_linear_velocity(const Vector2 &p_vel); void set_constant_angular_velocity(real_t p_vel); @@ -100,6 +104,9 @@ public: StaticBody2D(); ~StaticBody2D(); + +private: + void _reload_physics_characteristics(); }; class RigidBody2D : public PhysicsBody2D { @@ -125,9 +132,8 @@ private: Physics2DDirectBodyState *state; Mode mode; - real_t bounce; real_t mass; - real_t friction; + Ref<PhysicsMaterial> physics_material_override; real_t gravity_scale; real_t linear_damp; real_t angular_damp; @@ -204,11 +210,16 @@ public: void set_weight(real_t p_weight); real_t get_weight() const; +#ifndef DISABLE_DEPRECATED void set_friction(real_t p_friction); real_t get_friction() const; void set_bounce(real_t p_bounce); real_t get_bounce() const; +#endif + + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); + Ref<PhysicsMaterial> get_physics_material_override() const; void set_gravity_scale(real_t p_gravity_scale); real_t get_gravity_scale() const; @@ -261,6 +272,9 @@ public: RigidBody2D(); ~RigidBody2D(); + +private: + void _reload_physics_characteristics(); }; VARIANT_ENUM_CAST(RigidBody2D::Mode); @@ -276,6 +290,7 @@ public: Vector2 normal; Vector2 collider_vel; ObjectID collider; + RID collider_rid; int collider_shape; Variant collider_metadata; Vector2 remainder; @@ -287,29 +302,40 @@ private: float margin; Vector2 floor_velocity; + RID on_floor_body; bool on_floor; bool on_ceiling; bool on_wall; + bool sync_to_physics; + Vector<Collision> colliders; Vector<Ref<KinematicCollision2D> > slide_colliders; Ref<KinematicCollision2D> motion_cache; _FORCE_INLINE_ bool _ignores_mode(Physics2DServer::BodyMode) const; - Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_infinite_inertia = true); + Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false); Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); + Transform2D last_valid_transform; + void _direct_state_changed(Object *p_state); + protected: + void _notification(int p_what); static void _bind_methods(); public: - bool move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision); + bool move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes = true, bool p_test_only = false); + bool test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia); + bool separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision); + void set_safe_margin(float p_margin); float get_safe_margin() const; Vector2 move_and_slide(const Vector2 &p_linear_velocity, const Vector2 &p_floor_direction = Vector2(0, 0), bool p_infinite_inertia = true, float p_slope_stop_min_velocity = 5, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45)); + Vector2 move_and_slide_with_snap(const Vector2 &p_linear_velocity, const Vector2 &p_snap, const Vector2 &p_floor_direction = Vector2(0, 0), bool p_infinite_inertia = true, float p_slope_stop_min_velocity = 5, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45)); bool is_on_floor() const; bool is_on_wall() const; bool is_on_ceiling() const; @@ -318,6 +344,9 @@ public: int get_slide_count() const; Collision get_slide_collision(int p_bounce) const; + void set_sync_to_physics(bool p_enable); + bool is_sync_to_physics_enabled() const; + KinematicBody2D(); ~KinematicBody2D(); }; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 9a343ca0f0..72c3ed7425 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -62,7 +62,7 @@ void TileMap::_notification(int p_what) { pending_update = true; _recreate_quadrants(); - _update_dirty_quadrants(); + update_dirty_quadrants(); RID space = get_world_2d()->get_space(); _update_quadrant_transform(); _update_quadrant_space(space); @@ -245,7 +245,7 @@ void TileMap::_fix_cell_transform(Transform2D &xform, const Cell &p_cell, const xform.elements[2].y += offset.y; } -void TileMap::_update_dirty_quadrants() { +void TileMap::update_dirty_quadrants() { if (!pending_update) return; @@ -721,7 +721,7 @@ void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool updat return; if (update) { - _update_dirty_quadrants(); + call_deferred("update_dirty_quadrants"); } } @@ -1026,7 +1026,7 @@ void TileMap::_recreate_quadrants() { Q->get().cells.insert(E->key()); _make_quadrant_dirty(Q, false); } - _update_dirty_quadrants(); + update_dirty_quadrants(); } void TileMap::_clear_quadrants() { @@ -1630,7 +1630,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("_clear_quadrants"), &TileMap::_clear_quadrants); ClassDB::bind_method(D_METHOD("_recreate_quadrants"), &TileMap::_recreate_quadrants); - ClassDB::bind_method(D_METHOD("_update_dirty_quadrants"), &TileMap::_update_dirty_quadrants); + ClassDB::bind_method(D_METHOD("update_dirty_quadrants"), &TileMap::update_dirty_quadrants); ClassDB::bind_method(D_METHOD("update_bitmask_area", "position"), &TileMap::update_bitmask_area); ClassDB::bind_method(D_METHOD("update_bitmask_region", "start", "end"), &TileMap::update_bitmask_region, DEFVAL(Vector2()), DEFVAL(Vector2())); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 79d79ca59f..c8aceac17d 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -191,7 +191,6 @@ private: void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update = true); void _recreate_quadrants(); void _clear_quadrants(); - void _update_dirty_quadrants(); void _update_quadrant_space(const RID &p_space); void _update_quadrant_transform(); void _recompute_rect_cache(); @@ -251,6 +250,8 @@ public: void update_cell_bitmask(int p_x, int p_y); void update_dirty_bitmask(); + void update_dirty_quadrants(); + void set_collision_layer(uint32_t p_layer); uint32_t get_collision_layer() const; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index d46231a677..5f0ac3dd80 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -991,6 +991,7 @@ AudioStreamPlayer3D::AudioStreamPlayer3D() { velocity_tracker.instance(); AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); + set_disable_scale(true); } AudioStreamPlayer3D::~AudioStreamPlayer3D() { } diff --git a/scene/3d/baked_lightmap.cpp b/scene/3d/baked_lightmap.cpp index 204aaef7ec..26fd5ed658 100644 --- a/scene/3d/baked_lightmap.cpp +++ b/scene/3d/baked_lightmap.cpp @@ -811,4 +811,5 @@ BakedLightmap::BakedLightmap() { propagation = 1; hdr = false; image_path = "."; + set_disable_scale(true); } diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp index e11e8abe5b..9a2046991b 100644 --- a/scene/3d/camera.cpp +++ b/scene/3d/camera.cpp @@ -613,6 +613,7 @@ Camera::Camera() { velocity_tracker.instance(); doppler_tracking = DOPPLER_TRACKING_DISABLED; set_notify_transform(true); + set_disable_scale(true); } Camera::~Camera() { diff --git a/scene/3d/collision_object.h b/scene/3d/collision_object.h index f31d65e411..f8ef04b78f 100644 --- a/scene/3d/collision_object.h +++ b/scene/3d/collision_object.h @@ -39,6 +39,7 @@ class CollisionObject : public Spatial { GDCLASS(CollisionObject, Spatial); bool area; + RID rid; struct ShapeData { diff --git a/scene/3d/collision_shape.h b/scene/3d/collision_shape.h index c9c91a5824..6ca8e80ea1 100644 --- a/scene/3d/collision_shape.h +++ b/scene/3d/collision_shape.h @@ -49,6 +49,7 @@ class CollisionShape : public Spatial { void resource_changed(RES res); bool disabled; +protected: void _create_debug_shape(); void _update_in_shape_owner(bool p_xform_only = false); diff --git a/scene/3d/cpu_particles.cpp b/scene/3d/cpu_particles.cpp index 2e897c1c73..c3b0196316 100644 --- a/scene/3d/cpu_particles.cpp +++ b/scene/3d/cpu_particles.cpp @@ -25,6 +25,8 @@ void CPUParticles::set_emitting(bool p_emitting) { update_mutex->lock(); #endif VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_REDRAW_FRAME_IF_VISIBLE, true); + #ifndef NO_THREADS update_mutex->unlock(); #endif @@ -446,6 +448,8 @@ float rand_from_seed_m1_p1(uint32_t &seed) { void CPUParticles::_particles_process(float p_delta) { + p_delta *= speed_scale; + int pcount = particles.size(); PoolVector<Particle>::Write w = particles.write(); @@ -475,7 +479,7 @@ void CPUParticles::_particles_process(float p_delta) { if (!emitting && !p.active) continue; - float restart_time = float(i) / float(pcount); + float restart_time = (float(i) / float(pcount)) * lifetime; float local_delta = p_delta; if (randomness_ratio > 0.0) { @@ -643,7 +647,7 @@ void CPUParticles::_particles_process(float p_delta) { uint32_t alt_seed = p.seed; p.time += local_delta; - p.custom[1] += p.time / lifetime; + p.custom[1] = p.time / lifetime; float tex_linear_velocity = 0.0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { @@ -979,6 +983,7 @@ void CPUParticles::_notification(int p_what) { update_mutex->lock(); #endif VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_REDRAW_FRAME_IF_VISIBLE, true); #ifndef NO_THREADS update_mutex->unlock(); #endif @@ -992,6 +997,7 @@ void CPUParticles::_notification(int p_what) { update_mutex->lock(); #endif VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_REDRAW_FRAME_IF_VISIBLE, false); #ifndef NO_THREADS update_mutex->unlock(); #endif @@ -1018,6 +1024,8 @@ void CPUParticles::_notification(int p_what) { update_mutex->lock(); #endif VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_REDRAW_FRAME_IF_VISIBLE, false); + #ifndef NO_THREADS update_mutex->unlock(); #endif diff --git a/scene/3d/gi_probe.cpp b/scene/3d/gi_probe.cpp index 4ad2eb60ee..6276d02eff 100644 --- a/scene/3d/gi_probe.cpp +++ b/scene/3d/gi_probe.cpp @@ -557,6 +557,7 @@ GIProbe::GIProbe() { compress = false; gi_probe = VS::get_singleton()->gi_probe_create(); + set_disable_scale(true); } GIProbe::~GIProbe() { diff --git a/scene/3d/light.cpp b/scene/3d/light.cpp index 7c42638107..16164cf3bf 100644 --- a/scene/3d/light.cpp +++ b/scene/3d/light.cpp @@ -306,6 +306,7 @@ Light::Light(VisualServer::LightType p_type) { set_param(PARAM_SHADOW_SPLIT_3_OFFSET, 0.5); set_param(PARAM_SHADOW_NORMAL_BIAS, 0.0); set_param(PARAM_SHADOW_BIAS, 0.15); + set_disable_scale(true); } Light::Light() { diff --git a/scene/3d/mesh_instance.cpp b/scene/3d/mesh_instance.cpp index e836a6154a..722eadb9ca 100644 --- a/scene/3d/mesh_instance.cpp +++ b/scene/3d/mesh_instance.cpp @@ -36,6 +36,7 @@ #include "scene/resources/material.h" #include "scene/scene_string_names.h" #include "skeleton.h" + bool MeshInstance::_set(const StringName &p_name, const Variant &p_value) { //this is not _too_ bad performance wise, really. it only arrives here if the property was not set anywhere else. diff --git a/scene/3d/mesh_instance.h b/scene/3d/mesh_instance.h index 5d359cd4d5..0dfec538f9 100644 --- a/scene/3d/mesh_instance.h +++ b/scene/3d/mesh_instance.h @@ -41,6 +41,7 @@ class MeshInstance : public GeometryInstance { GDCLASS(MeshInstance, GeometryInstance); +protected: Ref<Mesh> mesh; NodePath skeleton_path; diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index e851c8d643..7529a0e524 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -30,6 +30,7 @@ #include "physics_body.h" +#include "core/core_string_names.h" #include "engine.h" #include "method_bind_ext.gen.inc" #include "scene/scene_string_names.h" @@ -121,23 +122,23 @@ bool PhysicsBody::get_collision_layer_bit(int p_bit) const { void PhysicsBody::add_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); - PhysicsBody *physics_body = Object::cast_to<PhysicsBody>(p_node); - if (!physics_body) { - ERR_EXPLAIN("Collision exception only works between two objects of PhysicsBody type"); + CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); + if (!collision_object) { + ERR_EXPLAIN("Collision exception only works between two CollisionObject"); } - ERR_FAIL_COND(!physics_body); - PhysicsServer::get_singleton()->body_add_collision_exception(get_rid(), physics_body->get_rid()); + ERR_FAIL_COND(!collision_object); + PhysicsServer::get_singleton()->body_add_collision_exception(get_rid(), collision_object->get_rid()); } void PhysicsBody::remove_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); - PhysicsBody *physics_body = Object::cast_to<PhysicsBody>(p_node); - if (!physics_body) { - ERR_EXPLAIN("Collision exception only works between two objects of PhysicsBody type"); + CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); + if (!collision_object) { + ERR_EXPLAIN("Collision exception only works between two CollisionObject"); } - ERR_FAIL_COND(!physics_body); - PhysicsServer::get_singleton()->body_remove_collision_exception(get_rid(), physics_body->get_rid()); + ERR_FAIL_COND(!collision_object); + PhysicsServer::get_singleton()->body_remove_collision_exception(get_rid(), collision_object->get_rid()); } void PhysicsBody::_set_layers(uint32_t p_mask) { @@ -178,28 +179,75 @@ PhysicsBody::PhysicsBody(PhysicsServer::BodyMode p_mode) : collision_mask = 1; } +#ifndef DISABLE_DEPRECATED void StaticBody::set_friction(real_t p_friction) { + ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + ERR_FAIL_COND(p_friction < 0 || p_friction > 1); - friction = p_friction; - PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, friction); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_friction(p_friction); + _reload_physics_characteristics(); } + real_t StaticBody::get_friction() const { - return friction; + ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + + if (physics_material_override.is_null()) { + return 1; + } + + return physics_material_override->get_friction(); } void StaticBody::set_bounce(real_t p_bounce) { + ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); - bounce = p_bounce; - PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, bounce); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_bounce(p_bounce); + _reload_physics_characteristics(); } + real_t StaticBody::get_bounce() const { - return bounce; + ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + + if (physics_material_override.is_null()) { + return 0; + } + + return physics_material_override->get_bounce(); +} +#endif + +void StaticBody::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { + if (physics_material_override.is_valid()) { + physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + + physics_material_override = p_physics_material_override; + + if (physics_material_override.is_valid()) { + physics_material_override->connect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + _reload_physics_characteristics(); +} + +Ref<PhysicsMaterial> StaticBody::get_physics_material_override() const { + return physics_material_override; } void StaticBody::set_constant_linear_velocity(const Vector3 &p_vel) { @@ -236,24 +284,39 @@ void StaticBody::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bounce", "bounce"), &StaticBody::set_bounce); ClassDB::bind_method(D_METHOD("get_bounce"), &StaticBody::get_bounce); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody::get_physics_material_override); + + ClassDB::bind_method(D_METHOD("_reload_physics_characteristics"), &StaticBody::_reload_physics_characteristics); + ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody::add_collision_exception_with); ClassDB::bind_method(D_METHOD("remove_collision_exception_with", "body"), &PhysicsBody::remove_collision_exception_with); ADD_PROPERTY(PropertyInfo(Variant::REAL, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); - + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); } StaticBody::StaticBody() : PhysicsBody(PhysicsServer::BODY_MODE_STATIC) { - - bounce = 0; - friction = 1; } -StaticBody::~StaticBody() { +StaticBody::~StaticBody() {} + +void StaticBody::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, 0); + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, 1); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, PhysicsServer::COMBINE_MODE_INHERIT); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, PhysicsServer::COMBINE_MODE_INHERIT); + } else { + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, physics_material_override->get_bounce()); + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, physics_material_override->get_friction()); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, physics_material_override->get_bounce_combine_mode()); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, physics_material_override->get_friction_combine_mode()); + } } void RigidBody::_body_enter_tree(ObjectID p_id) { @@ -550,28 +613,67 @@ real_t RigidBody::get_weight() const { return mass * real_t(GLOBAL_DEF("physics/3d/default_gravity", 9.8)); } +#ifndef DISABLE_DEPRECATED void RigidBody::set_friction(real_t p_friction) { + ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED ERR_FAIL_COND(p_friction < 0 || p_friction > 1); - friction = p_friction; - PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, friction); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_friction(p_friction); + _reload_physics_characteristics(); } real_t RigidBody::get_friction() const { - return friction; + ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + if (physics_material_override.is_null()) { + return 1; + } + + return physics_material_override->get_friction(); } void RigidBody::set_bounce(real_t p_bounce) { - + ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); - bounce = p_bounce; - PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, bounce); + if (physics_material_override.is_null()) { + physics_material_override.instance(); + } + physics_material_override->set_bounce(p_bounce); + _reload_physics_characteristics(); } real_t RigidBody::get_bounce() const { + ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physical material") + WARN_DEPRECATED + if (physics_material_override.is_null()) { + return 0; + } - return bounce; + return physics_material_override->get_bounce(); +} +#endif + +void RigidBody::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { + if (physics_material_override.is_valid()) { + physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + + physics_material_override = p_physics_material_override; + + if (physics_material_override.is_valid()) { + physics_material_override->connect(CoreStringNames::get_singleton()->changed, this, "_reload_physics_characteristics"); + } + _reload_physics_characteristics(); +} + +Ref<PhysicsMaterial> RigidBody::get_physics_material_override() const { + return physics_material_override; } void RigidBody::set_gravity_scale(real_t p_gravity_scale) { @@ -812,6 +914,11 @@ void RigidBody::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bounce", "bounce"), &RigidBody::set_bounce); ClassDB::bind_method(D_METHOD("get_bounce"), &RigidBody::get_bounce); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody::get_physics_material_override); + + ClassDB::bind_method(D_METHOD("_reload_physics_characteristics"), &RigidBody::_reload_physics_characteristics); + ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &RigidBody::set_linear_velocity); ClassDB::bind_method(D_METHOD("get_linear_velocity"), &RigidBody::get_linear_velocity); @@ -865,6 +972,7 @@ void RigidBody::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "weight", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01", PROPERTY_USAGE_EDITOR), "set_weight", "get_weight"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "continuous_cd"), "set_use_continuous_collision_detection", "is_using_continuous_collision_detection"); @@ -903,9 +1011,7 @@ RigidBody::RigidBody() : mode = MODE_RIGID; - bounce = 0; mass = 1; - friction = 1; max_contacts_reported = 0; state = NULL; @@ -929,6 +1035,21 @@ RigidBody::~RigidBody() { if (contact_monitor) memdelete(contact_monitor); } + +void RigidBody::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, 0); + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, 1); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, PhysicsServer::COMBINE_MODE_INHERIT); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, PhysicsServer::COMBINE_MODE_INHERIT); + } else { + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, physics_material_override->get_bounce()); + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, physics_material_override->get_friction()); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, physics_material_override->get_bounce_combine_mode()); + PhysicsServer::get_singleton()->body_set_combine_mode(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, physics_material_override->get_friction_combine_mode()); + } +} + ////////////////////////////////////////////////////// ////////////////////////// @@ -2228,6 +2349,7 @@ void PhysicalBone::set_bounce(real_t p_bounce) { bounce = p_bounce; PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, bounce); } + real_t PhysicalBone::get_bounce() const { return bounce; diff --git a/scene/3d/physics_body.h b/scene/3d/physics_body.h index 0190dcbfc3..44d6502be1 100644 --- a/scene/3d/physics_body.h +++ b/scene/3d/physics_body.h @@ -32,6 +32,7 @@ #define PHYSICS_BODY__H #include "scene/3d/collision_object.h" +#include "scene/resources/physics_material.h" #include "servers/physics_server.h" #include "skeleton.h" #include "vset.h" @@ -81,18 +82,22 @@ class StaticBody : public PhysicsBody { Vector3 constant_linear_velocity; Vector3 constant_angular_velocity; - real_t bounce; - real_t friction; + Ref<PhysicsMaterial> physics_material_override; protected: static void _bind_methods(); public: +#ifndef DISABLE_DEPRECATED void set_friction(real_t p_friction); real_t get_friction() const; void set_bounce(real_t p_bounce); real_t get_bounce() const; +#endif + + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); + Ref<PhysicsMaterial> get_physics_material_override() const; void set_constant_linear_velocity(const Vector3 &p_vel); void set_constant_angular_velocity(const Vector3 &p_vel); @@ -102,6 +107,9 @@ public: StaticBody(); ~StaticBody(); + +private: + void _reload_physics_characteristics(); }; class RigidBody : public PhysicsBody { @@ -121,9 +129,8 @@ protected: PhysicsDirectBodyState *state; Mode mode; - real_t bounce; real_t mass; - real_t friction; + Ref<PhysicsMaterial> physics_material_override; Vector3 linear_velocity; Vector3 angular_velocity; @@ -196,11 +203,16 @@ public: void set_weight(real_t p_weight); real_t get_weight() const; +#ifndef DISABLE_DEPRECATED void set_friction(real_t p_friction); real_t get_friction() const; void set_bounce(real_t p_bounce); real_t get_bounce() const; +#endif + + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); + Ref<PhysicsMaterial> get_physics_material_override() const; void set_linear_velocity(const Vector3 &p_velocity); Vector3 get_linear_velocity() const; @@ -249,6 +261,9 @@ public: RigidBody(); ~RigidBody(); + +private: + void _reload_physics_characteristics(); }; VARIANT_ENUM_CAST(RigidBody::Mode); @@ -294,7 +309,7 @@ protected: static void _bind_methods(); public: - bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision); + bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collisionz); bool test_move(const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia); void set_axis_lock(PhysicsServer::BodyAxis p_axis, bool p_lock); diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index 4d50945062..fe522bbe97 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -274,6 +274,7 @@ ReflectionProbe::ReflectionProbe() { probe = VisualServer::get_singleton()->reflection_probe_create(); VS::get_singleton()->instance_set_base(get_instance(), probe); + set_disable_scale(true); } ReflectionProbe::~ReflectionProbe() { diff --git a/scene/3d/soft_body.cpp b/scene/3d/soft_body.cpp new file mode 100644 index 0000000000..8498dc34c0 --- /dev/null +++ b/scene/3d/soft_body.cpp @@ -0,0 +1,740 @@ +/*************************************************************************/ +/* soft_physics_body.cpp */ +/* Author: AndreaCatania */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "soft_body.h" +#include "os/os.h" +#include "scene/3d/collision_object.h" +#include "scene/3d/skeleton.h" +#include "servers/physics_server.h" + +SoftBodyVisualServerHandler::SoftBodyVisualServerHandler() {} + +void SoftBodyVisualServerHandler::prepare(RID p_mesh, int p_surface) { + clear(); + + ERR_FAIL_COND(!p_mesh.is_valid()); + + mesh = p_mesh; + surface = p_surface; + + const uint32_t surface_format = VS::get_singleton()->mesh_surface_get_format(mesh, surface); + const int surface_vertex_len = VS::get_singleton()->mesh_surface_get_array_len(mesh, p_surface); + const int surface_index_len = VS::get_singleton()->mesh_surface_get_array_index_len(mesh, p_surface); + uint32_t surface_offsets[VS::ARRAY_MAX]; + + buffer = VS::get_singleton()->mesh_surface_get_array(mesh, surface); + stride = VS::get_singleton()->mesh_surface_make_offsets_from_format(surface_format, surface_vertex_len, surface_index_len, surface_offsets); + offset_vertices = surface_offsets[VS::ARRAY_VERTEX]; + offset_normal = surface_offsets[VS::ARRAY_NORMAL]; +} + +void SoftBodyVisualServerHandler::clear() { + + if (mesh.is_valid()) { + buffer.resize(0); + } + + mesh = RID(); +} + +void SoftBodyVisualServerHandler::open() { + write_buffer = buffer.write(); +} + +void SoftBodyVisualServerHandler::close() { + write_buffer = PoolVector<uint8_t>::Write(); +} + +void SoftBodyVisualServerHandler::commit_changes() { + VS::get_singleton()->mesh_surface_update_region(mesh, surface, 0, buffer); +} + +void SoftBodyVisualServerHandler::set_vertex(int p_vertex_id, const void *p_vector3) { + copymem(&write_buffer[p_vertex_id * stride + offset_vertices], p_vector3, sizeof(float) * 3); +} + +void SoftBodyVisualServerHandler::set_normal(int p_vertex_id, const void *p_vector3) { + copymem(&write_buffer[p_vertex_id * stride + offset_normal], p_vector3, sizeof(float) * 3); +} + +void SoftBodyVisualServerHandler::set_aabb(const AABB &p_aabb) { + VS::get_singleton()->mesh_set_custom_aabb(mesh, p_aabb); +} + +SoftBody::PinnedPoint::PinnedPoint() : + point_index(-1), + spatial_attachment(NULL) { +} + +SoftBody::PinnedPoint::PinnedPoint(const PinnedPoint &obj_tocopy) { + point_index = obj_tocopy.point_index; + spatial_attachment_path = obj_tocopy.spatial_attachment_path; + spatial_attachment = obj_tocopy.spatial_attachment; + vertex_offset_transform = obj_tocopy.vertex_offset_transform; +} + +void SoftBody::_update_pickable() { + if (!is_inside_tree()) + return; + bool pickable = ray_pickable && is_inside_tree() && is_visible_in_tree(); + PhysicsServer::get_singleton()->soft_body_set_ray_pickable(physics_rid, pickable); +} + +bool SoftBody::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + String which = name.get_slicec('/', 0); + + if ("pinned_points" == which) { + + return _set_property_pinned_points_indices(p_value); + + } else if ("attachments" == which) { + + int idx = name.get_slicec('/', 1).to_int(); + String what = name.get_slicec('/', 2); + + return _set_property_pinned_points_attachment(idx, what, p_value); + } + + return false; +} + +bool SoftBody::_get(const StringName &p_name, Variant &r_ret) const { + String name = p_name; + String which = name.get_slicec('/', 0); + + if ("pinned_points" == which) { + Array arr_ret; + const int pinned_points_indices_size = pinned_points_indices.size(); + PoolVector<PinnedPoint>::Read r = pinned_points_indices.read(); + arr_ret.resize(pinned_points_indices_size); + + for (int i = 0; i < pinned_points_indices_size; ++i) { + arr_ret[i] = r[i].point_index; + } + + r_ret = arr_ret; + return true; + + } else if ("attachments" == which) { + + int idx = name.get_slicec('/', 1).to_int(); + String what = name.get_slicec('/', 2); + + return _get_property_pinned_points(idx, what, r_ret); + } + + return false; +} + +void SoftBody::_get_property_list(List<PropertyInfo> *p_list) const { + + const int pinned_points_indices_size = pinned_points_indices.size(); + + p_list->push_back(PropertyInfo(Variant::POOL_INT_ARRAY, "pinned_points")); + + for (int i = 0; i < pinned_points_indices_size; ++i) { + p_list->push_back(PropertyInfo(Variant::INT, "attachments/" + itos(i) + "/point_index")); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "attachments/" + itos(i) + "/spatial_attachment_path")); + } +} + +bool SoftBody::_set_property_pinned_points_indices(const Array &p_indices) { + + const int p_indices_size = p_indices.size(); + + { // Remove the pined points on physics server that will be removed by resize + PoolVector<PinnedPoint>::Read r = pinned_points_indices.read(); + if (p_indices_size < pinned_points_indices.size()) { + for (int i = pinned_points_indices.size() - 1; i >= p_indices_size; --i) { + pin_point(r[i].point_index, false); + } + } + } + + pinned_points_indices.resize(p_indices_size); + + PoolVector<PinnedPoint>::Write w = pinned_points_indices.write(); + int point_index; + for (int i = 0; i < p_indices_size; ++i) { + point_index = p_indices.get(i); + if (w[i].point_index != point_index) { + if (-1 != w[i].point_index) + pin_point(w[i].point_index, false); + w[i].point_index = point_index; + pin_point(w[i].point_index, true); + } + } + return true; +} + +bool SoftBody::_set_property_pinned_points_attachment(int p_item, const String &p_what, const Variant &p_value) { + if (pinned_points_indices.size() <= p_item) { + return false; + } + + if ("spatial_attachment_path" == p_what) { + PoolVector<PinnedPoint>::Write w = pinned_points_indices.write(); + pin_point(w[p_item].point_index, true, p_value); + } else { + return false; + } + + return true; +} + +bool SoftBody::_get_property_pinned_points(int p_item, const String &p_what, Variant &r_ret) const { + if (pinned_points_indices.size() <= p_item) { + return false; + } + PoolVector<PinnedPoint>::Read r = pinned_points_indices.read(); + + if ("point_index" == p_what) { + r_ret = r[p_item].point_index; + } else if ("spatial_attachment_path" == p_what) { + r_ret = r[p_item].spatial_attachment_path; + } else { + return false; + } + + return true; +} + +void SoftBody::_changed_callback(Object *p_changed, const char *p_prop) { +#ifdef TOOLS_ENABLED + if (p_changed == this) { + update_configuration_warning(); + } +#endif +} + +void SoftBody::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_WORLD: { + + if (Engine::get_singleton()->is_editor_hint()) + add_change_receptor(this); + + RID space = get_world()->get_space(); + PhysicsServer::get_singleton()->soft_body_set_space(physics_rid, space); + PhysicsServer::get_singleton()->soft_body_set_transform(physics_rid, get_global_transform()); + update_physics_server(); + } break; + case NOTIFICATION_READY: { + if (!parent_collision_ignore.is_empty()) + add_collision_exception_with(get_node(parent_collision_ignore)); + + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + + if (!simulation_started) { + PhysicsServer::get_singleton()->soft_body_set_transform(physics_rid, get_global_transform()); + + _update_cache_pin_points_datas(); + // Submit bone attachment + const int pinned_points_indices_size = pinned_points_indices.size(); + PoolVector<PinnedPoint>::Read r = pinned_points_indices.read(); + for (int i = 0; i < pinned_points_indices_size; ++i) { + if (!r[i].spatial_attachment) { + // Use soft body position to update the point position + PhysicsServer::get_singleton()->soft_body_move_point(physics_rid, r[i].point_index, (get_global_transform() * r[i].vertex_offset_transform).origin); + } else { + PhysicsServer::get_singleton()->soft_body_move_point(physics_rid, r[i].point_index, (r[i].spatial_attachment->get_global_transform() * r[i].vertex_offset_transform).origin); + } + } + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + + _update_pickable(); + + } break; + case NOTIFICATION_EXIT_WORLD: { + + PhysicsServer::get_singleton()->soft_body_set_space(physics_rid, RID()); + + } break; + } + +#ifdef TOOLS_ENABLED + + if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { + if (Engine::get_singleton()->is_editor_hint()) { + update_configuration_warning(); + } + } + +#endif +} + +void SoftBody::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_draw_soft_mesh"), &SoftBody::_draw_soft_mesh); + + ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SoftBody::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &SoftBody::get_collision_mask); + + ClassDB::bind_method(D_METHOD("set_collision_layer", "collision_layer"), &SoftBody::set_collision_layer); + ClassDB::bind_method(D_METHOD("get_collision_layer"), &SoftBody::get_collision_layer); + + ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &SoftBody::set_collision_mask_bit); + ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &SoftBody::get_collision_mask_bit); + + ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &SoftBody::set_collision_layer_bit); + ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &SoftBody::get_collision_layer_bit); + + ClassDB::bind_method(D_METHOD("set_parent_collision_ignore", "parent_collision_ignore"), &SoftBody::set_parent_collision_ignore); + ClassDB::bind_method(D_METHOD("get_parent_collision_ignore"), &SoftBody::get_parent_collision_ignore); + + ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &SoftBody::add_collision_exception_with); + ClassDB::bind_method(D_METHOD("remove_collision_exception_with", "body"), &SoftBody::remove_collision_exception_with); + + ClassDB::bind_method(D_METHOD("set_simulation_precision", "simulation_precision"), &SoftBody::set_simulation_precision); + ClassDB::bind_method(D_METHOD("get_simulation_precision"), &SoftBody::get_simulation_precision); + + ClassDB::bind_method(D_METHOD("set_total_mass", "mass"), &SoftBody::set_total_mass); + ClassDB::bind_method(D_METHOD("get_total_mass"), &SoftBody::get_total_mass); + + ClassDB::bind_method(D_METHOD("set_linear_stiffness", "linear_stiffness"), &SoftBody::set_linear_stiffness); + ClassDB::bind_method(D_METHOD("get_linear_stiffness"), &SoftBody::get_linear_stiffness); + + ClassDB::bind_method(D_METHOD("set_areaAngular_stiffness", "areaAngular_stiffness"), &SoftBody::set_areaAngular_stiffness); + ClassDB::bind_method(D_METHOD("get_areaAngular_stiffness"), &SoftBody::get_areaAngular_stiffness); + + ClassDB::bind_method(D_METHOD("set_volume_stiffness", "volume_stiffness"), &SoftBody::set_volume_stiffness); + ClassDB::bind_method(D_METHOD("get_volume_stiffness"), &SoftBody::get_volume_stiffness); + + ClassDB::bind_method(D_METHOD("set_pressure_coefficient", "pressure_coefficient"), &SoftBody::set_pressure_coefficient); + ClassDB::bind_method(D_METHOD("get_pressure_coefficient"), &SoftBody::get_pressure_coefficient); + + ClassDB::bind_method(D_METHOD("set_pose_matching_coefficient", "pose_matching_coefficient"), &SoftBody::set_pose_matching_coefficient); + ClassDB::bind_method(D_METHOD("get_pose_matching_coefficient"), &SoftBody::get_pose_matching_coefficient); + + ClassDB::bind_method(D_METHOD("set_damping_coefficient", "damping_coefficient"), &SoftBody::set_damping_coefficient); + ClassDB::bind_method(D_METHOD("get_damping_coefficient"), &SoftBody::get_damping_coefficient); + + ClassDB::bind_method(D_METHOD("set_drag_coefficient", "drag_coefficient"), &SoftBody::set_drag_coefficient); + ClassDB::bind_method(D_METHOD("get_drag_coefficient"), &SoftBody::get_drag_coefficient); + + ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &SoftBody::set_ray_pickable); + ClassDB::bind_method(D_METHOD("is_ray_pickable"), &SoftBody::is_ray_pickable); + + ADD_GROUP("Collision", "collision_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer", "get_collision_layer"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "parent_collision_ignore", PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE, "Parent collision object"), "set_parent_collision_ignore", "get_parent_collision_ignore"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "simulation_precision", PROPERTY_HINT_RANGE, "1,100,1"), "set_simulation_precision", "get_simulation_precision"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "total_mass", PROPERTY_HINT_RANGE, "0.01,10000,1"), "set_total_mass", "get_total_mass"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_stiffness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_linear_stiffness", "get_linear_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "areaAngular_stiffness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_areaAngular_stiffness", "get_areaAngular_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "volume_stiffness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_volume_stiffness", "get_volume_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "pressure_coefficient"), "set_pressure_coefficient", "get_pressure_coefficient"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "damping_coefficient", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_damping_coefficient", "get_damping_coefficient"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "drag_coefficient", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_coefficient", "get_drag_coefficient"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "pose_matching_coefficient", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_pose_matching_coefficient", "get_pose_matching_coefficient"); +} + +String SoftBody::get_configuration_warning() const { + + String warning = MeshInstance::get_configuration_warning(); + + if (get_mesh().is_null()) { + if (!warning.empty()) + warning += "\n\n"; + + warning += TTR("This body will be ignored until you set a mesh"); + } + + Transform t = get_transform(); + if ((ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(0).length() - 1.0) > 0.05)) { + if (!warning.empty()) + warning += "\n\n"; + + warning += TTR("Size changes to SoftBody will be overriden by the physics engine when running.\nChange the size in children collision shapes instead."); + } + + return warning; +} + +void SoftBody::_draw_soft_mesh() { + if (get_mesh().is_null()) + return; + + if (!visual_server_handler.is_ready()) { + + visual_server_handler.prepare(get_mesh()->get_rid(), 0); + + /// Necessary in order to render the mesh correctly (Soft body nodes are in global space) + simulation_started = true; + call_deferred("set_as_toplevel", true); + call_deferred("set_transform", Transform()); + } + + visual_server_handler.open(); + PhysicsServer::get_singleton()->soft_body_update_visual_server(physics_rid, &visual_server_handler); + visual_server_handler.close(); + + visual_server_handler.commit_changes(); +} + +void SoftBody::update_physics_server() { + + if (Engine::get_singleton()->is_editor_hint()) + return; + + if (get_mesh().is_valid()) { + + become_mesh_owner(); + PhysicsServer::get_singleton()->soft_body_set_mesh(physics_rid, get_mesh()); + VS::get_singleton()->connect("frame_pre_draw", this, "_draw_soft_mesh"); + } else { + + PhysicsServer::get_singleton()->soft_body_set_mesh(physics_rid, NULL); + VS::get_singleton()->disconnect("frame_pre_draw", this, "_draw_soft_mesh"); + } +} + +void SoftBody::become_mesh_owner() { + if (mesh.is_null()) + return; + + if (!mesh_owner) { + mesh_owner = true; + + ERR_FAIL_COND(!mesh->get_surface_count()); + + // Get current mesh array and create new mesh array with necessary flag for softbody + Array surface_arrays = mesh->surface_get_arrays(0); + Array surface_blend_arrays = mesh->surface_get_blend_shape_arrays(0); + uint32_t surface_format = mesh->surface_get_format(0); + + surface_format &= ~(Mesh::ARRAY_COMPRESS_VERTEX | Mesh::ARRAY_COMPRESS_NORMAL); + surface_format |= Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE; + + Ref<ArrayMesh> soft_mesh; + soft_mesh.instance(); + soft_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_arrays, surface_blend_arrays, surface_format); + + set_mesh(soft_mesh); + + Vector<Ref<Material> > copy_materials; + copy_materials.append_array(materials); + for (int i = copy_materials.size() - 1; 0 <= i; --i) { + set_surface_material(i, copy_materials[i]); + } + } +} + +void SoftBody::set_collision_mask(uint32_t p_mask) { + collision_mask = p_mask; + PhysicsServer::get_singleton()->soft_body_set_collision_mask(physics_rid, p_mask); +} + +uint32_t SoftBody::get_collision_mask() const { + return collision_mask; +} +void SoftBody::set_collision_layer(uint32_t p_layer) { + collision_layer = p_layer; + PhysicsServer::get_singleton()->soft_body_set_collision_layer(physics_rid, p_layer); +} + +uint32_t SoftBody::get_collision_layer() const { + return collision_layer; +} + +void SoftBody::set_collision_mask_bit(int p_bit, bool p_value) { + uint32_t mask = get_collision_mask(); + if (p_value) + mask |= 1 << p_bit; + else + mask &= ~(1 << p_bit); + set_collision_mask(mask); +} + +bool SoftBody::get_collision_mask_bit(int p_bit) const { + return get_collision_mask() & (1 << p_bit); +} + +void SoftBody::set_collision_layer_bit(int p_bit, bool p_value) { + uint32_t layer = get_collision_layer(); + if (p_value) + layer |= 1 << p_bit; + else + layer &= ~(1 << p_bit); + set_collision_layer(layer); +} + +bool SoftBody::get_collision_layer_bit(int p_bit) const { + return get_collision_layer() & (1 << p_bit); +} + +void SoftBody::set_parent_collision_ignore(const NodePath &p_parent_collision_ignore) { + parent_collision_ignore = p_parent_collision_ignore; +} + +const NodePath &SoftBody::get_parent_collision_ignore() const { + return parent_collision_ignore; +} + +void SoftBody::set_pinned_points_indices(PoolVector<SoftBody::PinnedPoint> p_pinned_points_indices) { + pinned_points_indices = p_pinned_points_indices; + PoolVector<PinnedPoint>::Read w = pinned_points_indices.read(); + for (int i = pinned_points_indices.size() - 1; 0 <= i; --i) { + pin_point(p_pinned_points_indices[i].point_index, true); + } +} + +PoolVector<SoftBody::PinnedPoint> SoftBody::get_pinned_points_indices() { + return pinned_points_indices; +} + +void SoftBody::add_collision_exception_with(Node *p_node) { + ERR_FAIL_NULL(p_node); + CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); + if (!collision_object) { + ERR_EXPLAIN("Collision exception only works between two CollisionObject"); + } + ERR_FAIL_COND(!collision_object); + PhysicsServer::get_singleton()->soft_body_add_collision_exception(physics_rid, collision_object->get_rid()); +} + +void SoftBody::remove_collision_exception_with(Node *p_node) { + ERR_FAIL_NULL(p_node); + CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); + if (!collision_object) { + ERR_EXPLAIN("Collision exception only works between two CollisionObject"); + } + ERR_FAIL_COND(!collision_object); + PhysicsServer::get_singleton()->soft_body_remove_collision_exception(physics_rid, collision_object->get_rid()); +} + +int SoftBody::get_simulation_precision() { + return PhysicsServer::get_singleton()->soft_body_get_simulation_precision(physics_rid); +} + +void SoftBody::set_simulation_precision(int p_simulation_precision) { + PhysicsServer::get_singleton()->soft_body_set_simulation_precision(physics_rid, p_simulation_precision); +} + +real_t SoftBody::get_total_mass() { + return PhysicsServer::get_singleton()->soft_body_get_total_mass(physics_rid); +} + +void SoftBody::set_total_mass(real_t p_total_mass) { + PhysicsServer::get_singleton()->soft_body_set_total_mass(physics_rid, p_total_mass); +} + +void SoftBody::set_linear_stiffness(real_t p_linear_stiffness) { + PhysicsServer::get_singleton()->soft_body_set_linear_stiffness(physics_rid, p_linear_stiffness); +} + +real_t SoftBody::get_linear_stiffness() { + return PhysicsServer::get_singleton()->soft_body_get_linear_stiffness(physics_rid); +} + +void SoftBody::set_areaAngular_stiffness(real_t p_areaAngular_stiffness) { + PhysicsServer::get_singleton()->soft_body_set_areaAngular_stiffness(physics_rid, p_areaAngular_stiffness); +} + +real_t SoftBody::get_areaAngular_stiffness() { + return PhysicsServer::get_singleton()->soft_body_get_areaAngular_stiffness(physics_rid); +} + +void SoftBody::set_volume_stiffness(real_t p_volume_stiffness) { + PhysicsServer::get_singleton()->soft_body_set_volume_stiffness(physics_rid, p_volume_stiffness); +} + +real_t SoftBody::get_volume_stiffness() { + return PhysicsServer::get_singleton()->soft_body_get_volume_stiffness(physics_rid); +} + +real_t SoftBody::get_pressure_coefficient() { + return PhysicsServer::get_singleton()->soft_body_get_pressure_coefficient(physics_rid); +} + +void SoftBody::set_pose_matching_coefficient(real_t p_pose_matching_coefficient) { + PhysicsServer::get_singleton()->soft_body_set_pose_matching_coefficient(physics_rid, p_pose_matching_coefficient); +} + +real_t SoftBody::get_pose_matching_coefficient() { + return PhysicsServer::get_singleton()->soft_body_get_pose_matching_coefficient(physics_rid); +} + +void SoftBody::set_pressure_coefficient(real_t p_pressure_coefficient) { + PhysicsServer::get_singleton()->soft_body_set_pressure_coefficient(physics_rid, p_pressure_coefficient); +} + +real_t SoftBody::get_damping_coefficient() { + return PhysicsServer::get_singleton()->soft_body_get_damping_coefficient(physics_rid); +} + +void SoftBody::set_damping_coefficient(real_t p_damping_coefficient) { + PhysicsServer::get_singleton()->soft_body_set_damping_coefficient(physics_rid, p_damping_coefficient); +} + +real_t SoftBody::get_drag_coefficient() { + return PhysicsServer::get_singleton()->soft_body_get_drag_coefficient(physics_rid); +} + +void SoftBody::set_drag_coefficient(real_t p_drag_coefficient) { + PhysicsServer::get_singleton()->soft_body_set_drag_coefficient(physics_rid, p_drag_coefficient); +} + +Vector3 SoftBody::get_point_transform(int p_point_index) { + return PhysicsServer::get_singleton()->soft_body_get_point_global_position(physics_rid, p_point_index); +} + +void SoftBody::pin_point_toggle(int p_point_index) { + pin_point(p_point_index, !(-1 != _has_pinned_point(p_point_index))); +} + +void SoftBody::pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path) { + _pin_point_on_physics_server(p_point_index, pin); + if (pin) { + _add_pinned_point(p_point_index, p_spatial_attachment_path); + } else { + _remove_pinned_point(p_point_index); + } +} + +bool SoftBody::is_point_pinned(int p_point_index) const { + return -1 != _has_pinned_point(p_point_index); +} + +void SoftBody::set_ray_pickable(bool p_ray_pickable) { + + ray_pickable = p_ray_pickable; + _update_pickable(); +} + +bool SoftBody::is_ray_pickable() const { + + return ray_pickable; +} + +SoftBody::SoftBody() : + MeshInstance(), + physics_rid(PhysicsServer::get_singleton()->soft_body_create()), + mesh_owner(false), + collision_mask(1), + collision_layer(1), + simulation_started(false), + pinned_points_cache_dirty(true) { + + PhysicsServer::get_singleton()->body_attach_object_instance_id(physics_rid, get_instance_id()); +} + +SoftBody::~SoftBody() { +} + +void SoftBody::reset_softbody_pin() { + PhysicsServer::get_singleton()->soft_body_remove_all_pinned_points(physics_rid); + PoolVector<PinnedPoint>::Read pps = pinned_points_indices.read(); + for (int i = pinned_points_indices.size() - 1; 0 < i; --i) { + PhysicsServer::get_singleton()->soft_body_pin_point(physics_rid, pps[i].point_index, true); + } +} + +void SoftBody::_update_cache_pin_points_datas() { + if (pinned_points_cache_dirty) { + pinned_points_cache_dirty = false; + + PoolVector<PinnedPoint>::Write w = pinned_points_indices.write(); + for (int i = pinned_points_indices.size() - 1; 0 <= i; --i) { + + if (!w[i].spatial_attachment_path.is_empty()) { + w[i].spatial_attachment = Object::cast_to<Spatial>(get_node(w[i].spatial_attachment_path)); + if (w[i].spatial_attachment) { + + Transform point_global_transform(get_global_transform()); + point_global_transform.translate(PhysicsServer::get_singleton()->soft_body_get_point_offset(physics_rid, w[i].point_index)); + + // Local transform relative to spatial attachment node + w[i].vertex_offset_transform = w[i].spatial_attachment->get_global_transform().affine_inverse() * point_global_transform; + continue; + } else { + ERR_PRINTS("The node with path: " + String(w[i].spatial_attachment_path) + " was not found or is not a spatial node."); + } + } + // Local transform relative to Soft body + w[i].vertex_offset_transform.origin = PhysicsServer::get_singleton()->soft_body_get_point_offset(physics_rid, w[i].point_index); + w[i].vertex_offset_transform.basis = Basis(); + } + } +} + +void SoftBody::_pin_point_on_physics_server(int p_point_index, bool pin) { + PhysicsServer::get_singleton()->soft_body_pin_point(physics_rid, p_point_index, pin); +} + +void SoftBody::_add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path) { + SoftBody::PinnedPoint *pinned_point; + if (-1 == _get_pinned_point(p_point_index, pinned_point)) { + // Create new + PinnedPoint pp; + pp.point_index = p_point_index; + pp.spatial_attachment_path = p_spatial_attachment_path; + pinned_points_indices.push_back(pp); + } else { + // Update + pinned_point->point_index = p_point_index; + pinned_point->spatial_attachment_path = p_spatial_attachment_path; + } +} + +void SoftBody::_remove_pinned_point(int p_point_index) { + const int id(_has_pinned_point(p_point_index)); + if (-1 != id) { + pinned_points_indices.remove(id); + } +} + +int SoftBody::_get_pinned_point(int p_point_index, SoftBody::PinnedPoint *&r_point) const { + const int id = _has_pinned_point(p_point_index); + if (-1 == id) { + r_point = NULL; + return -1; + } else { + r_point = const_cast<SoftBody::PinnedPoint *>(&pinned_points_indices.read()[id]); + return id; + } +} + +int SoftBody::_has_pinned_point(int p_point_index) const { + PoolVector<PinnedPoint>::Read r = pinned_points_indices.read(); + for (int i = pinned_points_indices.size() - 1; 0 <= i; --i) { + if (p_point_index == r[i].point_index) { + return i; + } + } + return -1; +} diff --git a/scene/3d/soft_body.h b/scene/3d/soft_body.h new file mode 100644 index 0000000000..f44f337698 --- /dev/null +++ b/scene/3d/soft_body.h @@ -0,0 +1,197 @@ +/*************************************************************************/ +/* soft_physics_body.h */ +/* Author: AndreaCatania */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 SOFT_PHYSICS_BODY_H +#define SOFT_PHYSICS_BODY_H + +#include "scene/3d/mesh_instance.h" + +class SoftBody; + +class SoftBodyVisualServerHandler { + + friend class SoftBody; + + RID mesh; + int surface; + PoolVector<uint8_t> buffer; + uint32_t stride; + uint32_t offset_vertices; + uint32_t offset_normal; + + PoolVector<uint8_t>::Write write_buffer; + +private: + SoftBodyVisualServerHandler(); + bool is_ready() { return mesh.is_valid(); } + void prepare(RID p_mesh_rid, int p_surface); + void clear(); + void open(); + void close(); + void commit_changes(); + +public: + void set_vertex(int p_vertex_id, const void *p_vector3); + void set_normal(int p_vertex_id, const void *p_vector3); + void set_aabb(const AABB &p_aabb); +}; + +class SoftBody : public MeshInstance { + GDCLASS(SoftBody, MeshInstance); + +public: + struct PinnedPoint { + int point_index; + NodePath spatial_attachment_path; + Spatial *spatial_attachment; // Cache + /// This is the offset from the soft body to point or attachment to point + /// Depend if the spatial_attachment_node is NULL or not + Transform vertex_offset_transform; // Cache + + PinnedPoint(); + PinnedPoint(const PinnedPoint &obj_tocopy); + }; + +private: + SoftBodyVisualServerHandler visual_server_handler; + + RID physics_rid; + + bool mesh_owner; + uint32_t collision_mask; + uint32_t collision_layer; + NodePath parent_collision_ignore; + PoolVector<PinnedPoint> pinned_points_indices; + bool simulation_started; + bool pinned_points_cache_dirty; + + Ref<ArrayMesh> debug_mesh_cache; + class MeshInstance *debug_mesh; + + bool capture_input_on_drag; + bool ray_pickable; + + void _update_pickable(); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + bool _set_property_pinned_points_indices(const Array &p_indices); + bool _set_property_pinned_points_attachment(int p_item, const String &p_what, const Variant &p_value); + bool _get_property_pinned_points(int p_item, const String &p_what, Variant &r_ret) const; + + virtual void _changed_callback(Object *p_changed, const char *p_prop); + + void _notification(int p_what); + static void _bind_methods(); + + virtual String get_configuration_warning() const; + +protected: + void _draw_soft_mesh(); + +public: + void update_physics_server(); + void become_mesh_owner(); + + void set_collision_mask(uint32_t p_mask); + uint32_t get_collision_mask() const; + + void set_collision_layer(uint32_t p_layer); + uint32_t get_collision_layer() const; + + void set_collision_mask_bit(int p_bit, bool p_value); + bool get_collision_mask_bit(int p_bit) const; + + void set_collision_layer_bit(int p_bit, bool p_value); + bool get_collision_layer_bit(int p_bit) const; + + void set_parent_collision_ignore(const NodePath &p_parent_collision_ignore); + const NodePath &get_parent_collision_ignore() const; + + void set_pinned_points_indices(PoolVector<PinnedPoint> p_pinned_points_indices); + PoolVector<PinnedPoint> get_pinned_points_indices(); + + void set_simulation_precision(int p_simulation_precision); + int get_simulation_precision(); + + void set_total_mass(real_t p_total_mass); + real_t get_total_mass(); + + void set_linear_stiffness(real_t p_linear_stiffness); + real_t get_linear_stiffness(); + + void set_areaAngular_stiffness(real_t p_areaAngular_stiffness); + real_t get_areaAngular_stiffness(); + + void set_volume_stiffness(real_t p_volume_stiffness); + real_t get_volume_stiffness(); + + void set_pressure_coefficient(real_t p_pressure_coefficient); + real_t get_pressure_coefficient(); + + void set_pose_matching_coefficient(real_t p_pose_matching_coefficient); + real_t get_pose_matching_coefficient(); + + void set_damping_coefficient(real_t p_damping_coefficient); + real_t get_damping_coefficient(); + + void set_drag_coefficient(real_t p_drag_coefficient); + real_t get_drag_coefficient(); + + void add_collision_exception_with(Node *p_node); + void remove_collision_exception_with(Node *p_node); + + Vector3 get_point_transform(int p_point_index); + + void pin_point_toggle(int p_point_index); + void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath()); + bool is_point_pinned(int p_point_index) const; + + void set_ray_pickable(bool p_ray_pickable); + bool is_ray_pickable() const; + + SoftBody(); + ~SoftBody(); + +private: + void reset_softbody_pin(); + void _update_cache_pin_points_datas(); + void _pin_point_on_physics_server(int p_point_index, bool pin); + void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path); + void _remove_pinned_point(int p_point_index); + int _get_pinned_point(int p_point_index, PinnedPoint *&r_point) const; + int _has_pinned_point(int p_point_index) const; +}; + +#endif // SOFT_PHYSICS_BODY_H diff --git a/scene/3d/spatial.cpp b/scene/3d/spatial.cpp index 748aa8aad4..9b27faed6a 100644 --- a/scene/3d/spatial.cpp +++ b/scene/3d/spatial.cpp @@ -280,6 +280,10 @@ Transform Spatial::get_global_transform() const { data.global_transform = data.local_transform; } + if (data.disable_scale) { + data.global_transform.basis.orthonormalize(); + } + data.dirty &= ~DIRTY_GLOBAL; } @@ -467,6 +471,15 @@ void Spatial::set_disable_gizmo(bool p_enabled) { #endif +void Spatial::set_disable_scale(bool p_enabled) { + + data.disable_scale = p_enabled; +} + +bool Spatial::is_scale_disabled() const { + return data.disable_scale; +} + void Spatial::set_as_toplevel(bool p_enabled) { if (data.toplevel == p_enabled) @@ -735,6 +748,8 @@ void Spatial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_ignore_transform_notification", "enabled"), &Spatial::set_ignore_transform_notification); ClassDB::bind_method(D_METHOD("set_as_toplevel", "enable"), &Spatial::set_as_toplevel); ClassDB::bind_method(D_METHOD("is_set_as_toplevel"), &Spatial::is_set_as_toplevel); + ClassDB::bind_method(D_METHOD("set_disable_scale", "disable"), &Spatial::set_disable_scale); + ClassDB::bind_method(D_METHOD("is_scale_disabled"), &Spatial::is_scale_disabled); ClassDB::bind_method(D_METHOD("get_world"), &Spatial::get_world); ClassDB::bind_method(D_METHOD("_update_gizmo"), &Spatial::_update_gizmo); @@ -755,15 +770,6 @@ void Spatial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_notify_transform", "enable"), &Spatial::set_notify_transform); ClassDB::bind_method(D_METHOD("is_transform_notification_enabled"), &Spatial::is_transform_notification_enabled); - void rotate(const Vector3 &p_axis, float p_angle); - void rotate_x(float p_angle); - void rotate_y(float p_angle); - void rotate_z(float p_angle); - void translate(const Vector3 &p_offset); - void scale(const Vector3 &p_ratio); - void global_rotate(const Vector3 &p_axis, float p_angle); - void global_translate(const Vector3 &p_offset); - ClassDB::bind_method(D_METHOD("rotate", "axis", "angle"), &Spatial::rotate); ClassDB::bind_method(D_METHOD("global_rotate", "axis", "angle"), &Spatial::global_rotate); ClassDB::bind_method(D_METHOD("global_scale", "scale"), &Spatial::global_scale); @@ -797,6 +803,7 @@ void Spatial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_NONE, "", 0), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale"); + ADD_GROUP("Visibility", ""); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "SpatialGizmo", 0), "set_gizmo", "get_gizmo"); @@ -817,6 +824,7 @@ Spatial::Spatial() : data.viewport = NULL; data.inside_world = false; data.visible = true; + data.disable_scale = false; #ifdef TOOLS_ENABLED data.gizmo_disabled = false; diff --git a/scene/3d/spatial.h b/scene/3d/spatial.h index a43bed3e4a..653714dc98 100644 --- a/scene/3d/spatial.h +++ b/scene/3d/spatial.h @@ -92,6 +92,7 @@ class Spatial : public Node { bool notify_transform; bool visible; + bool disable_scale; #ifdef TOOLS_ENABLED Ref<SpatialGizmo> gizmo; @@ -153,6 +154,9 @@ public: void set_as_toplevel(bool p_enabled); bool is_set_as_toplevel() const; + void set_disable_scale(bool p_enabled); + bool is_scale_disabled() const; + void set_disable_gizmo(bool p_enabled); void update_gizmo(); void set_gizmo(const Ref<SpatialGizmo> &p_gizmo); diff --git a/scene/3d/vehicle_body.cpp b/scene/3d/vehicle_body.cpp index 385956dc16..4b870ca54c 100644 --- a/scene/3d/vehicle_body.cpp +++ b/scene/3d/vehicle_body.cpp @@ -942,8 +942,6 @@ VehicleBody::VehicleBody() : engine_force = 0; brake = 0; - friction = 1; - state = NULL; ccd = false; diff --git a/scene/3d/voxel_light_baker.cpp b/scene/3d/voxel_light_baker.cpp index 670df5cc7f..ba2807d4e8 100644 --- a/scene/3d/voxel_light_baker.cpp +++ b/scene/3d/voxel_light_baker.cpp @@ -113,7 +113,7 @@ static bool planeBoxOverlap(Vector3 normal, float d, Vector3 maxbox) { rad = fa * boxhalfsize.x + fb * boxhalfsize.z; \ if (min > rad || max < -rad) return false; - /*======================== Z-tests ========================*/ +/*======================== Z-tests ========================*/ #define AXISTEST_Z12(a, b, fa, fb) \ p1 = a * v1.x - b * v1.y; \ @@ -1961,7 +1961,7 @@ Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh #endif for (int i = 0; i < height; i++) { - //print_line("bake line " + itos(i) + " / " + itos(height)); + //print_line("bake line " + itos(i) + " / " + itos(height)); #ifdef _OPENMP #pragma omp parallel for schedule(dynamic, 1) #endif diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index d3d2870c3f..1bc9fa4b12 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -4,10 +4,9 @@ void AnimationNodeBlendSpace1D::set_tree(AnimationTree *p_player) { AnimationRootNode::set_tree(p_player); - for(int i=0;i<blend_points_used;i++) { + for (int i = 0; i < blend_points_used; i++) { blend_points[i].node->set_tree(p_player); } - } void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &property) const { diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 774894ef4b..d1ed4c6a1f 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -34,7 +34,6 @@ protected: static void _bind_methods(); public: - virtual void set_tree(AnimationTree *p_player); void add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index = -1); diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index 82db647124..bba25d64d9 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -4,12 +4,11 @@ void AnimationNodeBlendSpace2D::set_tree(AnimationTree *p_player) { AnimationRootNode::set_tree(p_player); - for(int i=0;i<blend_points_used;i++) { + for (int i = 0; i < blend_points_used; i++) { blend_points[i].node->set_tree(p_player); } } - void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index) { ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); ERR_FAIL_COND(p_node.is_null()); diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 4778299df1..74d20b6013 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -47,7 +47,6 @@ protected: static void _bind_methods(); public: - virtual void set_tree(AnimationTree *p_player); void add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index = -1); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 6dcd5ca8ea..65904410d3 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -288,8 +288,8 @@ void AnimationNodeOneShot::_bind_methods() { ADD_GROUP("", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); - BIND_CONSTANT(MIX_MODE_BLEND) - BIND_CONSTANT(MIX_MODE_ADD) + BIND_ENUM_CONSTANT(MIX_MODE_BLEND) + BIND_ENUM_CONSTANT(MIX_MODE_ADD) } AnimationNodeOneShot::AnimationNodeOneShot() { diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index c5ad980806..36587a1e91 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -71,9 +71,9 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); - BIND_CONSTANT(SWITCH_MODE_IMMEDIATE); - BIND_CONSTANT(SWITCH_MODE_SYNC); - BIND_CONSTANT(SWITCH_MODE_AT_END); + BIND_ENUM_CONSTANT(SWITCH_MODE_IMMEDIATE); + BIND_ENUM_CONSTANT(SWITCH_MODE_SYNC); + BIND_ENUM_CONSTANT(SWITCH_MODE_AT_END); } AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index eac2c8d0c1..111620dac1 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -549,6 +549,12 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float int s = params.size(); ERR_CONTINUE(s > VARIANT_ARG_MAX); +#ifdef DEBUG_ENABLED + if (!nc->node->has_method(method)) { + ERR_PRINTS("Invalid method call '" + method + "'. '" + a->get_name() + "' at node '" + get_path() + "'."); + } +#endif + if (can_call) { MessageQueue::get_singleton()->push_call( nc->node, diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 83ec9f819b..4fa66e8ede 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -877,7 +877,7 @@ void AnimationTree::_process_graph(float p_delta) { continue; t->loc = t->loc.linear_interpolate(loc, blend); - if (t->rot_blend_accum==0) { + if (t->rot_blend_accum == 0) { t->rot = rot; t->rot_blend_accum = blend; } else { @@ -1052,7 +1052,7 @@ void AnimationTree::_process_graph(float p_delta) { float len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; if (len > t->len) { - stop=true; + stop = true; } } @@ -1065,7 +1065,7 @@ void AnimationTree::_process_graph(float p_delta) { } } - float db = Math::linear2db(MAX(blend,0.00001)); + float db = Math::linear2db(MAX(blend, 0.00001)); if (t->object->has_method("set_unit_db")) { t->object->call("set_unit_db", db); } else { @@ -1310,16 +1310,17 @@ void AnimationTree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_root_motion_transform"), &AnimationTree::get_root_motion_transform); - - ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_tree_root", "get_tree_root"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player",PROPERTY_HINT_NODE_PATH_VALID_TYPES,"AnimationPlayer"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode"); ADD_GROUP("Root Motion", "root_motion_"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); + + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); } AnimationTree::AnimationTree() { diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 9f7503577b..81fdc32788 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -150,7 +150,7 @@ void Tween::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { - if (!processing) { + if (!is_active()) { //make sure that a previous process state was not saved //only process if "processing" is set set_physics_process_internal(false); @@ -164,7 +164,7 @@ void Tween::_notification(int p_what) { if (tween_process_mode == TWEEN_PROCESS_PHYSICS) break; - if (processing) + if (is_active()) _tween_process(get_process_delta_time()); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -172,7 +172,7 @@ void Tween::_notification(int p_what) { if (tween_process_mode == TWEEN_PROCESS_IDLE) break; - if (processing) + if (is_active()) _tween_process(get_physics_process_delta_time()); } break; case NOTIFICATION_EXIT_TREE: { @@ -201,7 +201,6 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all); ClassDB::bind_method(D_METHOD("stop", "object", "key"), &Tween::stop, DEFVAL("")); ClassDB::bind_method(D_METHOD("stop_all"), &Tween::stop_all); - ClassDB::bind_method(D_METHOD("is_stopped"), &Tween::is_stopped); ClassDB::bind_method(D_METHOD("resume", "object", "key"), &Tween::resume, DEFVAL("")); ClassDB::bind_method(D_METHOD("resume_all"), &Tween::resume_all); ClassDB::bind_method(D_METHOD("remove", "object", "key"), &Tween::remove, DEFVAL("")); @@ -522,8 +521,8 @@ void Tween::_tween_process(float p_delta) { pending_update++; // if repeat and all interpolates was finished then reset all interpolates + bool all_finished = true; if (repeat) { - bool all_finished = true; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { @@ -539,9 +538,12 @@ void Tween::_tween_process(float p_delta) { reset_all(); } + all_finished = true; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { InterpolateData &data = E->get(); + all_finished = all_finished && data.finish; + if (!data.active || data.finish) continue; @@ -555,8 +557,8 @@ void Tween::_tween_process(float p_delta) { continue; else if (prev_delaying) { - emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false)); _apply_tween_value(data, data.initial_val); + emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false)); } if (data.elapsed > (data.delay + data.duration)) { @@ -603,32 +605,29 @@ void Tween::_tween_process(float p_delta) { } } else { Variant result = _run_equation(data); - emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result); _apply_tween_value(data, result); + emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result); } if (data.finish) { _apply_tween_value(data, data.final_val); + data.elapsed = 0; emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false)); // not repeat mode, remove completed action if (!repeat) call_deferred("_remove", object, NodePath(Vector<StringName>(), data.key, false), true); - } + } else if (!repeat) + all_finished = all_finished && data.finish; } pending_update--; + + if (all_finished) + set_active(false); } void Tween::set_tween_process_mode(TweenProcessMode p_mode) { - if (tween_process_mode == p_mode) - return; - - bool pr = processing; - if (pr) - _set_process(false); tween_process_mode = p_mode; - if (pr) - _set_process(true); } Tween::TweenProcessMode Tween::get_tween_process_mode() const { @@ -636,32 +635,21 @@ Tween::TweenProcessMode Tween::get_tween_process_mode() const { return tween_process_mode; } -void Tween::_set_process(bool p_process, bool p_force) { - - if (processing == p_process && !p_force) - return; - - switch (tween_process_mode) { - - case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_process && active); break; - case TWEEN_PROCESS_IDLE: set_process_internal(p_process && active); break; - } - - processing = p_process; -} - bool Tween::is_active() const { - return active; + return is_processing_internal() || is_physics_processing_internal(); } void Tween::set_active(bool p_active) { - if (active == p_active) + if (is_active() == p_active) return; - active = p_active; - _set_process(processing, true); + switch (tween_process_mode) { + + case TWEEN_PROCESS_IDLE: set_process_internal(p_active); break; + case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_active); break; + } } bool Tween::is_repeat() const { @@ -687,7 +675,6 @@ float Tween::get_speed_scale() const { bool Tween::start() { set_active(true); - _set_process(true); return true; } @@ -744,14 +731,9 @@ bool Tween::stop(Object *p_object, StringName p_key) { return true; } -bool Tween::is_stopped() const { - return tell() >= get_runtime(); -} - bool Tween::stop_all() { set_active(false); - _set_process(false); pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { @@ -766,7 +748,6 @@ bool Tween::stop_all() { bool Tween::resume(Object *p_object, StringName p_key) { set_active(true); - _set_process(true); pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { @@ -785,7 +766,6 @@ bool Tween::resume(Object *p_object, StringName p_key) { bool Tween::resume_all() { set_active(true); - _set_process(true); pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { @@ -834,7 +814,6 @@ bool Tween::remove_all() { return true; } set_active(false); - _set_process(false); interpolates.clear(); return true; } @@ -1425,8 +1404,6 @@ Tween::Tween() { //String autoplay; tween_process_mode = TWEEN_PROCESS_IDLE; - processing = false; - active = false; repeat = false; speed_scale = 1; pending_update = 0; diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 36094bf294..9997349c64 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -104,8 +104,6 @@ private: String autoplay; TweenProcessMode tween_process_mode; - bool processing; - bool active; bool repeat; float speed_scale; mutable int pending_update; @@ -133,7 +131,6 @@ private: bool _apply_tween_value(InterpolateData &p_data, Variant &value); void _tween_process(float p_delta); - void _set_process(bool p_process, bool p_force = false); void _remove(Object *p_object, StringName p_key, bool first_only); protected: @@ -162,7 +159,6 @@ public: bool reset_all(); bool stop(Object *p_object, StringName p_key); bool stop_all(); - bool is_stopped() const; bool resume(Object *p_object, StringName p_key); bool resume_all(); bool remove(Object *p_object, StringName p_key); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 068af42260..12aeed1520 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2188,10 +2188,17 @@ void Control::set_tooltip(const String &p_tooltip) { data.tooltip = p_tooltip; } + String Control::get_tooltip(const Point2 &p_pos) const { return data.tooltip; } +Control *Control::make_custom_tooltip(const String &p_text) const { + if (get_script_instance()) { + return const_cast<Control *>(this)->call("_make_custom_tooltip", p_text); + } + return NULL; +} void Control::set_default_cursor_shape(CursorShape p_shape) { @@ -2820,6 +2827,7 @@ void Control::_bind_methods() { BIND_VMETHOD(MethodInfo(Variant::OBJECT, "get_drag_data", PropertyInfo(Variant::VECTOR2, "position"))); BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); BIND_VMETHOD(MethodInfo("drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); + BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text"))); ADD_GROUP("Anchor", "anchor_"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_LEFT); @@ -2964,7 +2972,6 @@ Control::Control() { data.SI = NULL; data.MI = NULL; data.RI = NULL; - data.modal = false; data.theme_owner = NULL; data.modal_exclusive = false; data.default_cursor = CURSOR_ARROW; diff --git a/scene/gui/control.h b/scene/gui/control.h index fa5274d854..6bea04345b 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -182,7 +182,6 @@ private: Control *parent; ObjectID drag_owner; - bool modal; bool modal_exclusive; uint64_t modal_frame; //frame used to put something as modal Ref<Theme> theme; @@ -454,6 +453,7 @@ public: void set_tooltip(const String &p_tooltip); virtual String get_tooltip(const Point2 &p_pos) const; + virtual Control *make_custom_tooltip(const String &p_text) const; /* CURSOR */ diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index e2c730a56e..d95ec9e495 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -268,6 +268,10 @@ void GraphEdit::remove_child_notify(Node *p_child) { void GraphEdit::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + port_grab_distance_horizontal = get_constant("port_grab_distance_horizontal"); + port_grab_distance_vertical = get_constant("port_grab_distance_vertical"); + } if (p_what == NOTIFICATION_READY) { Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); @@ -343,8 +347,6 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { Ref<Texture> port = get_icon("port", "GraphNode"); - float grab_r_extend = 2.0; - float grab_r = port->get_width() * 0.5 * grab_r_extend; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -354,14 +356,14 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); - if (pos.distance_to(p_point) < grab_r) + if (create_hot_zone(pos).has_point(p_point)) return true; } for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); - if (pos.distance_to(p_point) < grab_r) { + if (create_hot_zone(pos).has_point(p_point)) { return true; } } @@ -372,13 +374,11 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { - float grab_r_extend = 2.0; Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { Ref<Texture> port = get_icon("port", "GraphNode"); Vector2 mpos(mb->get_position().x, mb->get_position().y); - float grab_r = port->get_width() * 0.5 * grab_r_extend; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -388,7 +388,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); - if (pos.distance_to(mpos) < grab_r) { + if (create_hot_zone(pos).has_point(mpos)) { if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) { //check disconnect @@ -435,8 +435,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); - - if (pos.distance_to(mpos) < grab_r) { + if (create_hot_zone(pos).has_point(mpos)) { if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) { //check disconnect @@ -492,7 +491,6 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Ref<Texture> port = get_icon("port", "GraphNode"); Vector2 mpos = mm->get_position(); - float grab_r = port->get_width() * 0.5 * grab_r_extend; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -504,7 +502,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); int type = gn->get_connection_output_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && pos.distance_to(mpos) < grab_r) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && create_hot_zone(pos).has_point(mpos)) { connecting_target = true; connecting_to = pos; @@ -519,7 +517,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); int type = gn->get_connection_input_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && pos.distance_to(mpos) < grab_r) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && create_hot_zone(pos).has_point(mpos)) { connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -559,6 +557,10 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } +Rect2 GraphEdit::create_hot_zone(const Vector2 &pos) { + return Rect2(pos.x - port_grab_distance_horizontal, pos.y - port_grab_distance_vertical, port_grab_distance_horizontal * 2, port_grab_distance_vertical * 2); +} + template <class Vector2> static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, Vector2 start, Vector2 control_1, Vector2 control_2, Vector2 end) { /* Formula from Wikipedia article on Bezier curves. */ diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 14789001e4..64ba18681e 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -81,6 +81,9 @@ private: HScrollBar *h_scroll; VScrollBar *v_scroll; + float port_grab_distance_horizontal; + float port_grab_distance_vertical; + bool connecting; String connecting_from; bool connecting_out; @@ -127,6 +130,9 @@ private: Control *connections_layer; GraphEditFilter *top_layer; void _top_layer_input(const Ref<InputEvent> &p_ev); + + Rect2 create_hot_zone(const Vector2 &pos); + void _top_layer_draw(); void _connections_layer_draw(); void _update_scroll_offset(); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 72ed0e9b81..aa52739b0a 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -285,7 +285,7 @@ void ItemList::unselect_all() { items[i].selected = false; } - + current = -1; update(); } diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 9af479c1cc..0b36e1663c 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -75,7 +75,7 @@ void Label::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { - if (clip || autowrap) { + if (clip) { VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); } diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index 2356444ecb..0636accfee 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -43,7 +43,6 @@ class MenuButton : public Button { bool clicked; bool disable_shortcuts; PopupMenu *popup; - virtual void pressed(); void _unhandled_key_input(Ref<InputEvent> p_event); Array _get_items() const; @@ -55,6 +54,8 @@ protected: static void _bind_methods(); public: + virtual void pressed(); + PopupMenu *get_popup() const; void set_disable_shortcuts(bool p_disabled); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index c5e4149782..2901176a69 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -36,7 +36,7 @@ Size2 OptionButton::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); if (has_icon("arrow")) - minsize.width += Control::get_icon("arrow")->get_width(); + minsize.width += Control::get_icon("arrow")->get_width() + get_constant("hseparation"); return minsize; } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index f5890fa2ee..ebec61ee6d 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -430,6 +430,8 @@ void PopupMenu::_notification(int p_what) { Ref<Texture> uncheck[] = { get_icon("unchecked"), get_icon("radio_unchecked") }; Ref<Texture> submenu = get_icon("submenu"); Ref<StyleBox> separator = get_stylebox("separator"); + Ref<StyleBox> labeled_separator_left = get_stylebox("labeled_separator_left"); + Ref<StyleBox> labeled_separator_right = get_stylebox("labeled_separator_right"); style->draw(ci, Rect2(Point2(), get_size())); Point2 ofs = style->get_offset(); @@ -466,10 +468,25 @@ void PopupMenu::_notification(int p_what) { hover->draw(ci, Rect2(item_ofs + Point2(-hseparation, -vseparation / 2), Size2(get_size().width - style->get_minimum_size().width + hseparation * 2, h + vseparation))); } + String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].xl_text; + if (items[i].separator) { int sep_h = separator->get_center_size().height + separator->get_minimum_size().height; - separator->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h))); + if (text != String()) { + int ss = font->get_string_size(text).width; + int center = (get_size().width) / 2; + int l = center - ss / 2; + int r = center + ss / 2; + if (l > item_ofs.x) { + labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, l - item_ofs.x), sep_h))); + } + if (r < get_size().width - style->get_margin(MARGIN_RIGHT)) { + labeled_separator_right->draw(ci, Rect2(Point2(r, item_ofs.y + Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, get_size().width - style->get_margin(MARGIN_RIGHT) - r), sep_h))); + } + } else { + separator->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h))); + } } if (items[i].checkable_type) { @@ -489,8 +506,13 @@ void PopupMenu::_notification(int p_what) { } item_ofs.y += font->get_ascent(); - String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].xl_text; - if (!items[i].separator) { + if (items[i].separator) { + + if (text != String()) { + int center = (get_size().width - font->get_string_size(text).width) / 2; + font->draw(ci, Point2(center, item_ofs.y + Math::floor((h - font_h) / 2.0)), text, font_color_disabled); + } + } else { font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color)); } @@ -1073,11 +1095,15 @@ void PopupMenu::remove_item(int p_idx) { update(); } -void PopupMenu::add_separator() { +void PopupMenu::add_separator(const String &p_text) { Item sep; sep.separator = true; sep.ID = -1; + if (p_text != String()) { + sep.text = p_text; + sep.xl_text = tr(p_text); + } items.push_back(sep); update(); } @@ -1310,7 +1336,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_item", "idx"), &PopupMenu::remove_item); - ClassDB::bind_method(D_METHOD("add_separator"), &PopupMenu::add_separator); + ClassDB::bind_method(D_METHOD("add_separator", "label"), &PopupMenu::add_separator, DEFVAL(String())); ClassDB::bind_method(D_METHOD("clear"), &PopupMenu::clear); ClassDB::bind_method(D_METHOD("_set_items"), &PopupMenu::_set_items); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 44f02a9d3d..8ec51c7d3a 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -180,7 +180,7 @@ public: void remove_item(int p_idx); - void add_separator(); + void add_separator(const String &p_text = String()); void clear(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index ce2e3538da..857ae8ff4c 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1253,6 +1253,9 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { //validate invalid lines Size2 size = get_size(); + if (fixed_width != -1) { + size.width = fixed_width; + } Rect2 text_rect = _get_text_rect(); Color font_color_shadow = get_color("font_color_shadow"); bool use_outline = get_constant("shadow_as_outline"); @@ -2245,6 +2248,21 @@ int RichTextLabel::get_total_character_count() const { return tc; } +void RichTextLabel::set_fixed_size_to_width(int p_width) { + fixed_width = p_width; + minimum_size_changed(); +} + +Size2 RichTextLabel::get_minimum_size() const { + + if (fixed_width != -1) { + const_cast<RichTextLabel *>(this)->_validate_line_caches(main); + return Size2(fixed_width, const_cast<RichTextLabel *>(this)->get_content_height()); + } + + return Size2(); +} + RichTextLabel::RichTextLabel() { main = memnew(ItemFrame); @@ -2287,6 +2305,7 @@ RichTextLabel::RichTextLabel() { percent_visible = 1; visible_line_count = 0; + fixed_width = -1; set_clip_contents(true); } diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index af368af46a..06e9b8efe3 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -293,6 +293,8 @@ private: void _update_all_lines(); + int fixed_width; + protected: void _notification(int p_what); @@ -368,6 +370,9 @@ public: void set_percent_visible(float p_percent); float get_percent_visible() const; + void set_fixed_size_to_width(int p_width); + virtual Size2 get_minimum_size() const; + RichTextLabel(); ~RichTextLabel(); }; diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 6ec67aca6b..e5bd1c453d 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -257,9 +257,7 @@ void ScrollBar::_notification(int p_what) { Point2 ofs; - VisualServer *vs = VisualServer::get_singleton(); - - vs->canvas_item_add_texture_rect(ci, Rect2(Point2(), decr->get_size()), decr->get_rid()); + decr->draw(ci, Point2()); if (orientation == HORIZONTAL) ofs.x += decr->get_width(); @@ -280,7 +278,7 @@ void ScrollBar::_notification(int p_what) { else ofs.height += area.height; - vs->canvas_item_add_texture_rect(ci, Rect2(ofs, decr->get_size()), incr->get_rid()); + incr->draw(ci, ofs); Rect2 grabber_rect; if (orientation == HORIZONTAL) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 218b5060a1..cccd1bd197 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -290,6 +290,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) { Line line; line.marked = false; + line.safe = false; line.breakpoint = false; line.hidden = false; line.width_cache = -1; @@ -336,10 +337,6 @@ void TextEdit::_update_scrollbars() { int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1; int visible_rows = get_visible_rows(); - int first_vis_line = get_first_visible_line(); - int wi; - int num_rows = MAX(visible_rows, num_lines_from_rows(first_vis_line, cursor.wrap_ofs, visible_rows, wi)); - int total_rows = get_total_visible_rows(); if (scroll_past_end_of_file_enabled) { total_rows += visible_rows - 1; @@ -972,7 +969,7 @@ void TextEdit::_notification(int p_what) { fc = line_num_padding + fc; } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, cache.line_number_color); + cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color); } } @@ -1672,7 +1669,6 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co rows /= get_row_height(); rows += get_v_scroll_offset(); int first_vis_line = get_first_visible_line(); - int last_vis_line = get_last_visible_line(); int row = first_vis_line + Math::floor(rows); int wrap_index = 0; @@ -3799,7 +3795,6 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { } // line ends before hit wrap_at; add this word to the substring wrap_substring += word_str; - px += word_px; lines.push_back(wrap_substring); return lines; } @@ -4314,6 +4309,7 @@ void TextEdit::_update_caches() { cache.caret_color = get_color("caret_color"); cache.caret_background_color = get_color("caret_background_color"); cache.line_number_color = get_color("line_number_color"); + cache.safe_line_number_color = get_color("safe_line_number_color"); cache.font_color = get_color("font_color"); cache.font_selected_color = get_color("font_selected_color"); cache.keyword_color = get_color("keyword_color"); @@ -4885,6 +4881,17 @@ void TextEdit::set_line_as_marked(int p_line, bool p_marked) { update(); } +void TextEdit::set_line_as_safe(int p_line, bool p_safe) { + ERR_FAIL_INDEX(p_line, text.size()); + text.set_safe(p_line, p_safe); + update(); +} + +bool TextEdit::is_line_set_as_safe(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), false); + return text.is_safe(p_line); +} + bool TextEdit::is_line_set_as_breakpoint(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), false); @@ -5506,9 +5513,8 @@ int TextEdit::get_last_visible_line() const { int TextEdit::get_last_visible_line_wrap_index() const { int first_vis_line = get_first_visible_line(); - int last_vis_line = 0; int wi; - last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1; + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi); return wi; } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 586f4c8e93..34d69bb508 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -76,6 +76,7 @@ public: bool marked : 1; bool breakpoint : 1; bool hidden : 1; + bool safe : 1; int wrap_amount_cache : 24; Map<int, ColorRegionInfo> region_info; String data; @@ -106,6 +107,8 @@ public: bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; } void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; } bool is_hidden(int p_line) const { return text[p_line].hidden; } + void set_safe(int p_line, bool p_safe) { text[p_line].safe = p_safe; } + bool is_safe(int p_line) const { return text[p_line].safe; } void insert(int p_at, const String &p_text); void remove(int p_at); int size() const { return text.size(); } @@ -165,6 +168,7 @@ private: Color caret_color; Color caret_background_color; Color line_number_color; + Color safe_line_number_color; Color font_color; Color font_selected_color; Color keyword_color; @@ -472,6 +476,8 @@ public: void set_line_as_marked(int p_line, bool p_marked); void set_line_as_breakpoint(int p_line, bool p_breakpoint); bool is_line_set_as_breakpoint(int p_line) const; + void set_line_as_safe(int p_line, bool p_safe); + bool is_line_set_as_safe(int p_line) const; void get_breakpoints(List<int> *p_breakpoints) const; Array get_breakpoints_array() const; void remove_breakpoints(); diff --git a/scene/gui/texture_progress.cpp b/scene/gui/texture_progress.cpp index 82d983184b..6e4fe88dbf 100644 --- a/scene/gui/texture_progress.cpp +++ b/scene/gui/texture_progress.cpp @@ -309,15 +309,23 @@ void TextureProgress::_notification(int p_what) { draw_texture_rect_region(progress, region, region, tint_progress); } break; case FILL_CLOCKWISE: - case FILL_COUNTER_CLOCKWISE: { + case FILL_COUNTER_CLOCKWISE: + case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: { float val = get_as_ratio() * rad_max_degrees / 360; if (val == 1) { Rect2 region = Rect2(Point2(), s); draw_texture_rect_region(progress, region, region, tint_progress); } else if (val != 0) { Array pts; - float direction = mode == FILL_CLOCKWISE ? 1 : -1; - float start = rad_init_angle / 360; + float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1; + float start; + + if (mode == FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE) { + start = rad_init_angle / 360 - val / 2; + } else { + start = rad_init_angle / 360; + } + float end = start + direction * val; pts.append(start); pts.append(end); @@ -351,6 +359,14 @@ void TextureProgress::_notification(int p_what) { draw_line(p - Point2(0, 8), p + Point2(0, 8), Color(0.9, 0.5, 0.5), 2); } } break; + case FILL_BILINEAR_LEFT_AND_RIGHT: { + Rect2 region = Rect2(Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); + draw_texture_rect_region(progress, region, region, tint_progress); + } break; + case FILL_BILINEAR_TOP_AND_BOTTOM: { + Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); + draw_texture_rect_region(progress, region, region, tint_progress); + } break; default: draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); } @@ -364,7 +380,7 @@ void TextureProgress::_notification(int p_what) { } void TextureProgress::set_fill_mode(int p_fill) { - ERR_FAIL_INDEX(p_fill, 6); + ERR_FAIL_INDEX(p_fill, 9); mode = (FillMode)p_fill; update(); } @@ -446,7 +462,7 @@ void TextureProgress::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_under_texture", "get_under_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_over_texture", "get_over_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_progress_texture", "get_progress_texture"); - ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise"), "set_fill_mode", "get_fill_mode"); + ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom), Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); ADD_GROUP("Tint", "tint_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_under", "get_tint_under"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_over", "get_tint_over"); @@ -468,6 +484,9 @@ void TextureProgress::_bind_methods() { BIND_ENUM_CONSTANT(FILL_BOTTOM_TO_TOP); BIND_ENUM_CONSTANT(FILL_CLOCKWISE); BIND_ENUM_CONSTANT(FILL_COUNTER_CLOCKWISE); + BIND_ENUM_CONSTANT(FILL_BILINEAR_LEFT_AND_RIGHT); + BIND_ENUM_CONSTANT(FILL_BILINEAR_TOP_AND_BOTTOM); + BIND_ENUM_CONSTANT(FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE); } TextureProgress::TextureProgress() { diff --git a/scene/gui/texture_progress.h b/scene/gui/texture_progress.h index 34158b5db5..a11e55234a 100644 --- a/scene/gui/texture_progress.h +++ b/scene/gui/texture_progress.h @@ -52,7 +52,10 @@ public: FILL_TOP_TO_BOTTOM, FILL_BOTTOM_TO_TOP, FILL_CLOCKWISE, - FILL_COUNTER_CLOCKWISE + FILL_COUNTER_CLOCKWISE, + FILL_BILINEAR_LEFT_AND_RIGHT, + FILL_BILINEAR_TOP_AND_BOTTOM, + FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE }; void set_fill_mode(int p_fill); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 6d18cce21d..b7b26d1c55 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -240,7 +240,7 @@ void Node::_propagate_enter_tree() { void Node::_propagate_exit_tree() { -//block while removing children + //block while removing children #ifdef DEBUG_ENABLED @@ -809,6 +809,22 @@ bool Node::is_processing_internal() const { return data.idle_process_internal; } +void Node::set_process_priority(int p_priority) { + data.process_priority = p_priority; + + if (is_processing()) + data.tree->make_group_changed("idle_process"); + + if (is_processing_internal()) + data.tree->make_group_changed("idle_process_internal"); + + if (is_physics_processing()) + data.tree->make_group_changed("physics_process"); + + if (is_physics_processing_internal()) + data.tree->make_group_changed("physics_process_internal"); +} + void Node::set_process_input(bool p_enable) { if (p_enable == data.input) @@ -1388,6 +1404,11 @@ bool Node::is_greater_than(const Node *p_node) const { return res; } +bool Node::has_priority_higher_than(const Node *p_node) const { + ERR_FAIL_NULL_V(p_node, false); + return data.process_priority > p_node->data.process_priority; +} + void Node::get_owned_by(Node *p_by, List<Node *> *p_owned) { if (data.owner == p_by) @@ -2608,6 +2629,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("is_physics_processing"), &Node::is_physics_processing); ClassDB::bind_method(D_METHOD("get_process_delta_time"), &Node::get_process_delta_time); ClassDB::bind_method(D_METHOD("set_process", "enable"), &Node::set_process); + ClassDB::bind_method(D_METHOD("set_process_priority", "priority"), &Node::set_process_priority); ClassDB::bind_method(D_METHOD("is_processing"), &Node::is_processing); ClassDB::bind_method(D_METHOD("set_process_input", "enable"), &Node::set_process_input); ClassDB::bind_method(D_METHOD("is_processing_input"), &Node::is_processing_input); @@ -2759,6 +2781,7 @@ Node::Node() { data.tree = NULL; data.physics_process = false; data.idle_process = false; + data.process_priority = 0; data.physics_process_internal = false; data.idle_process_internal = false; data.inside_tree = false; diff --git a/scene/main/node.h b/scene/main/node.h index 341349de79..4b8f584ba7 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -70,6 +70,11 @@ public: bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); } }; + struct ComparatorWithPriority { + + bool operator()(const Node *p_a, const Node *p_b) const { return p_b->has_priority_higher_than(p_a) || p_b->is_greater_than(p_a); } + }; + private: struct GroupData { @@ -118,6 +123,7 @@ private: //should move all the stuff below to bits bool physics_process; bool idle_process; + int process_priority; bool physics_process_internal; bool idle_process_internal; @@ -259,6 +265,7 @@ public: bool is_a_parent_of(const Node *p_node) const; bool is_greater_than(const Node *p_node) const; + bool has_priority_higher_than(const Node *p_node) const; NodePath get_path() const; NodePath get_path_to(const Node *p_node) const; @@ -319,6 +326,8 @@ public: void set_process_internal(bool p_idle_process_internal); bool is_processing_internal() const; + void set_process_priority(int p_priority); + void set_process_input(bool p_enable); bool is_processing_input() const; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 6438616cf2..3424c4edac 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -132,6 +132,12 @@ void SceneTree::remove_from_group(const StringName &p_group, Node *p_node) { group_map.erase(E); } +void SceneTree::make_group_changed(const StringName &p_group) { + Map<StringName, Group>::Element *E = group_map.find(p_group); + if (E) + E->get().changed = true; +} + void SceneTree::flush_transform_notifications() { SelfList<Node> *n = xform_change_list.first(); @@ -165,7 +171,7 @@ void SceneTree::_flush_ugc() { ugc_locked = false; } -void SceneTree::_update_group_order(Group &g) { +void SceneTree::_update_group_order(Group &g, bool p_use_priority) { if (!g.changed) return; @@ -175,8 +181,13 @@ void SceneTree::_update_group_order(Group &g) { Node **nodes = &g.nodes[0]; int node_count = g.nodes.size(); - SortArray<Node *, Node::Comparator> node_sort; - node_sort.sort(nodes, node_count); + if (p_use_priority) { + SortArray<Node *, Node::ComparatorWithPriority> node_sort; + node_sort.sort(nodes, node_count); + } else { + SortArray<Node *, Node::Comparator> node_sort; + node_sort.sort(nodes, node_count); + } g.changed = false; } @@ -921,7 +932,7 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio if (g.nodes.empty()) return; - _update_group_order(g); + _update_group_order(g, p_notification == Node::NOTIFICATION_PROCESS || p_notification == Node::NOTIFICATION_INTERNAL_PROCESS || p_notification == Node::NOTIFICATION_PHYSICS_PROCESS || p_notification == Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); //copy, so copy on write happens in case something is removed from process while being called //performance is not lost because only if something is added/removed the vector is copied. diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index aa8d78b1e1..11201097d4 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -161,7 +161,7 @@ private: bool ugc_locked; void _flush_ugc(); - _FORCE_INLINE_ void _update_group_order(Group &g); + _FORCE_INLINE_ void _update_group_order(Group &g, bool p_use_priority = false); void _update_listener(); Array _get_nodes_in_group(const StringName &p_group); @@ -204,6 +204,7 @@ private: Group *add_to_group(const StringName &p_group, Node *p_node); void remove_from_group(const StringName &p_group, Node *p_node); + void make_group_changed(const StringName &p_group); void _notify_group_pause(const StringName &p_group, int p_notification); void _call_input_pause(const StringName &p_group, const StringName &p_method, const Ref<InputEvent> &p_input); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 01302d4214..573c401290 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -41,7 +41,10 @@ #include "scene/3d/spatial.h" #include "scene/gui/control.h" #include "scene/gui/label.h" +#include "scene/gui/menu_button.h" #include "scene/gui/panel.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/popup_menu.h" #include "scene/main/timer.h" #include "scene/resources/mesh.h" #include "scene/scene_string_names.h" @@ -166,9 +169,9 @@ ViewportTexture::~ViewportTexture() { ///////////////////////////////////// -class TooltipPanel : public Panel { +class TooltipPanel : public PanelContainer { - GDCLASS(TooltipPanel, Panel) + GDCLASS(TooltipPanel, PanelContainer) public: TooltipPanel(){}; }; @@ -1305,10 +1308,11 @@ void Viewport::_gui_cancel_tooltip() { if (gui.tooltip_popup) { gui.tooltip_popup->queue_delete(); gui.tooltip_popup = NULL; + gui.tooltip_label = NULL; } } -String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos) { +String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Control **r_which) { Vector2 pos = p_pos; String tooltip; @@ -1317,6 +1321,10 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos) { tooltip = p_control->get_tooltip(pos); + if (r_which) { + *r_which = p_control; + } + if (tooltip != String()) break; pos = p_control->get_transform().xform(pos); @@ -1338,41 +1346,49 @@ void Viewport::_gui_show_tooltip() { return; } - String tooltip = _gui_get_tooltip(gui.tooltip, gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos)); + Control *which = NULL; + String tooltip = _gui_get_tooltip(gui.tooltip, gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos), &which); if (tooltip.length() == 0) return; // bye if (gui.tooltip_popup) { memdelete(gui.tooltip_popup); gui.tooltip_popup = NULL; + gui.tooltip_label = NULL; } - if (!gui.tooltip) { + if (!which) { return; } - Control *rp = gui.tooltip->get_root_parent_control(); + Control *rp = which; //->get_root_parent_control(); if (!rp) return; - gui.tooltip_popup = memnew(TooltipPanel); + gui.tooltip_popup = which->make_custom_tooltip(tooltip); + + if (!gui.tooltip_popup) { + gui.tooltip_popup = memnew(TooltipPanel); + + gui.tooltip_label = memnew(TooltipLabel); + gui.tooltip_popup->add_child(gui.tooltip_label); + + Ref<StyleBox> ttp = gui.tooltip_label->get_stylebox("panel", "TooltipPanel"); + + gui.tooltip_label->set_anchor_and_margin(MARGIN_LEFT, Control::ANCHOR_BEGIN, ttp->get_margin(MARGIN_LEFT)); + gui.tooltip_label->set_anchor_and_margin(MARGIN_TOP, Control::ANCHOR_BEGIN, ttp->get_margin(MARGIN_TOP)); + gui.tooltip_label->set_anchor_and_margin(MARGIN_RIGHT, Control::ANCHOR_END, -ttp->get_margin(MARGIN_RIGHT)); + gui.tooltip_label->set_anchor_and_margin(MARGIN_BOTTOM, Control::ANCHOR_END, -ttp->get_margin(MARGIN_BOTTOM)); + gui.tooltip_label->set_text(tooltip.strip_edges()); + } rp->add_child(gui.tooltip_popup); gui.tooltip_popup->force_parent_owned(); - gui.tooltip_label = memnew(TooltipLabel); - gui.tooltip_popup->add_child(gui.tooltip_label); gui.tooltip_popup->set_as_toplevel(true); - gui.tooltip_popup->hide(); + //gui.tooltip_popup->hide(); - Ref<StyleBox> ttp = gui.tooltip_label->get_stylebox("panel", "TooltipPanel"); - - gui.tooltip_label->set_anchor_and_margin(MARGIN_LEFT, Control::ANCHOR_BEGIN, ttp->get_margin(MARGIN_LEFT)); - gui.tooltip_label->set_anchor_and_margin(MARGIN_TOP, Control::ANCHOR_BEGIN, ttp->get_margin(MARGIN_TOP)); - gui.tooltip_label->set_anchor_and_margin(MARGIN_RIGHT, Control::ANCHOR_END, -ttp->get_margin(MARGIN_RIGHT)); - gui.tooltip_label->set_anchor_and_margin(MARGIN_BOTTOM, Control::ANCHOR_END, -ttp->get_margin(MARGIN_BOTTOM)); - gui.tooltip_label->set_text(tooltip.strip_edges()); - Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_label->get_minimum_size() + ttp->get_minimum_size()); - Rect2 vr = gui.tooltip_label->get_viewport_rect(); + Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_popup->get_minimum_size()); + Rect2 vr = gui.tooltip_popup->get_viewport_rect(); if (r.size.x + r.position.x > vr.size.x) r.position.x = vr.size.x - r.size.x; else if (r.position.x < 0) @@ -1840,8 +1856,32 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.drag_data.get_type() == Variant::NIL && over && !gui.modal_stack.empty()) { Control *top = gui.modal_stack.back()->get(); + if (over != top && !top->is_a_parent_of(over)) { - over = NULL; //nothing can be found outside the modal stack + + PopupMenu *popup_menu = Object::cast_to<PopupMenu>(top); + MenuButton *popup_menu_parent; + MenuButton *menu_button = Object::cast_to<MenuButton>(over); + + if (popup_menu) + popup_menu_parent = Object::cast_to<MenuButton>(popup_menu->get_parent()); + + // If the mouse is over a menu button, this menu will open automatically + // if there is already a pop-up menu open at the same hierarchical level. + if (popup_menu_parent && menu_button && + popup_menu_parent->get_icon().is_null() && + menu_button->get_icon().is_null() && + (popup_menu->get_parent()->get_parent()->is_a_parent_of(menu_button) || + menu_button->get_parent()->is_a_parent_of(popup_menu))) { + + popup_menu->notification(Control::NOTIFICATION_MODAL_CLOSE); + popup_menu->_modal_stack_remove(); + popup_menu->hide(); + + menu_button->pressed(); + } else { + over = NULL; //nothing can be found outside the modal stack + } } } @@ -1891,13 +1931,18 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { bool is_tooltip_shown = false; if (gui.tooltip_popup) { - if (can_tooltip) { + if (can_tooltip && gui.tooltip) { String tooltip = _gui_get_tooltip(over, gui.tooltip->get_global_transform().xform_inv(mpos)); if (tooltip.length() == 0) _gui_cancel_tooltip(); - else if (tooltip == gui.tooltip_label->get_text()) + else if (gui.tooltip_label) { + if (tooltip == gui.tooltip_label->get_text()) { + is_tooltip_shown = true; + } + } else if (tooltip == String(gui.tooltip_popup->call("get_tooltip_text"))) { is_tooltip_shown = true; + } } else _gui_cancel_tooltip(); } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 3000398540..e717d27069 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -255,7 +255,7 @@ private: Control *key_focus; Control *mouse_over; Control *tooltip; - Panel *tooltip_popup; + Control *tooltip_popup; Label *tooltip_label; Point2 tooltip_pos; Point2 last_mouse_pos; @@ -312,7 +312,7 @@ private: void _gui_remove_root_control(List<Control *>::Element *RI); void _gui_remove_subwindow_control(List<Control *>::Element *SI); - String _gui_get_tooltip(Control *p_control, const Vector2 &p_pos); + String _gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Control **r_which = NULL); void _gui_cancel_tooltip(); void _gui_show_tooltip(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index a0bac09442..a4fd35304a 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -156,9 +156,12 @@ #include "scene/resources/sky_box.h" #include "scene/resources/sphere_shape.h" #include "scene/resources/surface_tool.h" +#include "scene/resources/text_file.h" #include "scene/resources/texture.h" #include "scene/resources/tile_set.h" #include "scene/resources/video_stream.h" +#include "scene/resources/visual_shader.h" +#include "scene/resources/visual_shader_nodes.h" #include "scene/resources/world.h" #include "scene/resources/world_2d.h" #include "scene/scene_string_names.h" @@ -197,10 +200,12 @@ #include "scene/3d/remote_transform.h" #include "scene/3d/room_instance.h" #include "scene/3d/skeleton.h" +#include "scene/3d/soft_body.h" #include "scene/3d/sprite_3d.h" #include "scene/3d/vehicle_body.h" #include "scene/3d/visibility_notifier.h" #include "scene/resources/environment.h" +#include "scene/resources/physics_material.h" #endif static ResourceFormatLoaderTheme *resource_loader_theme = NULL; @@ -310,21 +315,19 @@ void register_scene_types() { ClassDB::register_class<CenterContainer>(); ClassDB::register_class<ScrollContainer>(); ClassDB::register_class<PanelContainer>(); - ClassDB::register_virtual_class<SplitContainer>(); - ClassDB::register_class<HSplitContainer>(); - ClassDB::register_class<VSplitContainer>(); - ClassDB::register_class<GraphNode>(); - ClassDB::register_class<GraphEdit>(); OS::get_singleton()->yield(); //may take time to init ClassDB::register_class<TextureProgress>(); ClassDB::register_class<ItemList>(); + ClassDB::register_class<LineEdit>(); + ClassDB::register_class<VideoPlayer>(); + #ifndef ADVANCED_GUI_DISABLED ClassDB::register_class<FileDialog>(); - ClassDB::register_class<LineEdit>(); + ClassDB::register_class<PopupMenu>(); ClassDB::register_class<Tree>(); @@ -341,9 +344,13 @@ void register_scene_types() { ClassDB::register_class<WindowDialog>(); ClassDB::register_class<AcceptDialog>(); ClassDB::register_class<ConfirmationDialog>(); - ClassDB::register_class<VideoPlayer>(); ClassDB::register_class<MarginContainer>(); ClassDB::register_class<ViewportContainer>(); + ClassDB::register_virtual_class<SplitContainer>(); + ClassDB::register_class<HSplitContainer>(); + ClassDB::register_class<VSplitContainer>(); + ClassDB::register_class<GraphNode>(); + ClassDB::register_class<GraphEdit>(); OS::get_singleton()->yield(); //may take time to init @@ -423,6 +430,7 @@ void register_scene_types() { ClassDB::register_class<KinematicCollision>(); ClassDB::register_class<KinematicBody>(); ClassDB::register_class<PhysicalBone>(); + ClassDB::register_class<SoftBody>(); ClassDB::register_class<VehicleBody>(); ClassDB::register_class<VehicleWheel>(); @@ -456,6 +464,39 @@ void register_scene_types() { AcceptDialog::set_swap_ok_cancel(GLOBAL_DEF("gui/common/swap_ok_cancel", bool(OS::get_singleton()->get_swap_ok_cancel()))); ClassDB::register_class<Shader>(); + ClassDB::register_class<VisualShader>(); + ClassDB::register_virtual_class<VisualShaderNode>(); + ClassDB::register_class<VisualShaderNodeInput>(); + ClassDB::register_virtual_class<VisualShaderNodeOutput>(); + ClassDB::register_class<VisualShaderNodeScalarConstant>(); + ClassDB::register_class<VisualShaderNodeColorConstant>(); + ClassDB::register_class<VisualShaderNodeVec3Constant>(); + ClassDB::register_class<VisualShaderNodeTransformConstant>(); + ClassDB::register_class<VisualShaderNodeScalarOp>(); + ClassDB::register_class<VisualShaderNodeVectorOp>(); + ClassDB::register_class<VisualShaderNodeColorOp>(); + ClassDB::register_class<VisualShaderNodeTransformMult>(); + ClassDB::register_class<VisualShaderNodeTransformVecMult>(); + ClassDB::register_class<VisualShaderNodeScalarFunc>(); + ClassDB::register_class<VisualShaderNodeVectorFunc>(); + ClassDB::register_class<VisualShaderNodeDotProduct>(); + ClassDB::register_class<VisualShaderNodeVectorLen>(); + ClassDB::register_class<VisualShaderNodeScalarInterp>(); + ClassDB::register_class<VisualShaderNodeVectorInterp>(); + ClassDB::register_class<VisualShaderNodeVectorCompose>(); + ClassDB::register_class<VisualShaderNodeTransformCompose>(); + ClassDB::register_class<VisualShaderNodeVectorDecompose>(); + ClassDB::register_class<VisualShaderNodeTransformDecompose>(); + ClassDB::register_class<VisualShaderNodeTexture>(); + ClassDB::register_class<VisualShaderNodeCubeMap>(); + ClassDB::register_virtual_class<VisualShaderNodeUniform>(); + ClassDB::register_class<VisualShaderNodeScalarUniform>(); + ClassDB::register_class<VisualShaderNodeColorUniform>(); + ClassDB::register_class<VisualShaderNodeVec3Uniform>(); + ClassDB::register_class<VisualShaderNodeTransformUniform>(); + ClassDB::register_class<VisualShaderNodeTextureUniform>(); + ClassDB::register_class<VisualShaderNodeCubeMapUniform>(); + ClassDB::register_class<ShaderMaterial>(); ClassDB::register_virtual_class<CanvasItem>(); ClassDB::register_class<CanvasItemMaterial>(); @@ -553,6 +594,8 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init ClassDB::register_class<SpatialVelocityTracker>(); + + ClassDB::register_class<PhysicsMaterial>(); #endif ClassDB::register_class<World>(); ClassDB::register_class<Environment>(); @@ -568,12 +611,15 @@ void register_scene_types() { ClassDB::register_class<CurveTexture>(); ClassDB::register_class<GradientTexture>(); ClassDB::register_class<ProxyTexture>(); + ClassDB::register_class<AnimatedTexture>(); ClassDB::register_class<CubeMap>(); ClassDB::register_class<Animation>(); ClassDB::register_virtual_class<Font>(); ClassDB::register_class<BitmapFont>(); ClassDB::register_class<Curve>(); + ClassDB::register_class<TextFile>(); + ClassDB::register_class<DynamicFontData>(); ClassDB::register_class<DynamicFont>(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index e57a2b68c8..8acfdc482a 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -2329,13 +2329,14 @@ float Animation::bezier_track_interpolate(int p_track, float p_time) const { int iterations = 10; - float low = 0; - float high = bt->values[idx + 1].time - bt->values[idx].time; + float duration = bt->values[idx + 1].time - bt->values[idx].time; // time duration between our two keyframes + float low = 0; // 0% of the current animation segment + float high = 1; // 100% of the current animation segment float middle = 0; Vector2 start(0, bt->values[idx].value.value); Vector2 start_out = start + bt->values[idx].value.out_handle; - Vector2 end(high, bt->values[idx + 1].value.value); + Vector2 end(duration, bt->values[idx + 1].value.value); Vector2 end_in = end + bt->values[idx + 1].value.in_handle; //narrow high and low as much as possible @@ -2355,7 +2356,6 @@ float Animation::bezier_track_interpolate(int p_track, float p_time) const { //interpolate the result: Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end); Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end); - float c = (t - low_pos.x) / (high_pos.x - low_pos.x); return low_pos.linear_interpolate(high_pos, c).y; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 702953fa40..601f6fb558 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -476,6 +476,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("symbol_color", "TextEdit", control_font_color_hover); theme->set_color("brace_mismatch_color", "TextEdit", Color(1, 0.2, 0.2)); theme->set_color("line_number_color", "TextEdit", Color::html("66aaaaaa")); + theme->set_color("safe_line_number_color", "TextEdit", Color::html("99aac8aa")); theme->set_color("function_color", "TextEdit", Color::html("66a2ce")); theme->set_color("member_variable_color", "TextEdit", Color::html("e64e59")); theme->set_color("number_color", "TextEdit", Color::html("EB9532")); @@ -585,6 +586,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("panel_disabled", "PopupMenu", make_stylebox(popup_bg_disabled_png, 4, 4, 4, 4)); theme->set_stylebox("hover", "PopupMenu", selected); theme->set_stylebox("separator", "PopupMenu", make_stylebox(vseparator_png, 3, 3, 3, 3)); + theme->set_stylebox("labeled_separator_left", "PopupMenu", make_stylebox(vseparator_png, 0, 0, 0, 0)); + theme->set_stylebox("labeled_separator_right", "PopupMenu", make_stylebox(vseparator_png, 0, 0, 0, 0)); theme->set_icon("checked", "PopupMenu", make_icon(checked_png)); theme->set_icon("unchecked", "PopupMenu", make_icon(unchecked_png)); @@ -880,6 +883,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("logo", "Icons", make_icon(logo_png)); + // Visual Node Ports + theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 48 * scale); + theme->set_constant("port_grab_distance_vertical", "GraphEdit", 6 * scale); + // Theme default_icon = make_icon(error_icon_png); diff --git a/scene/resources/default_theme/make_header.py b/scene/resources/default_theme/make_header.py index db449f9417..73b1ae0b0b 100755 --- a/scene/resources/default_theme/make_header.py +++ b/scene/resources/default_theme/make_header.py @@ -4,15 +4,16 @@ import os import glob import string +enc = "utf-8" # Generate include files f = open("theme_data.h", "wb") -f.write("// THIS FILE HAS BEEN AUTOGENERATED, DON'T EDIT!!\n") +f.write(b"// THIS FILE HAS BEEN AUTOGENERATED, DON\'T EDIT!!\n") # Generate png image block -f.write("\n// png image block\n"); +f.write(b"\n// png image block\n") pixmaps = glob.glob("*.png") pixmaps.sort() @@ -21,22 +22,23 @@ for x in pixmaps: var_str = x[:-4] + "_png" - f.write("\nstatic const unsigned char " + var_str + "[] = {\n\t") + s = "\nstatic const unsigned char " + var_str + "[] = {\n\t" + f.write(s.encode(enc)) pngf = open(x, "rb") b = pngf.read(1) while(len(b) == 1): - f.write(hex(ord(b))) + f.write(hex(ord(b)).encode(enc)) b = pngf.read(1) if (len(b) == 1): - f.write(", ") + f.write(b", ") - f.write("\n};\n") + f.write(b"\n};\n") pngf.close() # Generate shaders block -f.write("\n// shaders block\n"); +f.write(b"\n// shaders block\n"); shaders = glob.glob("*.gsl") shaders.sort() @@ -45,7 +47,8 @@ for x in shaders: var_str = x[:-4] + "_shader_code" - f.write("\nstatic const char *" + var_str + " = \n") + s = "\nstatic const char *" + var_str + " = \n" + f.write(s.encode(enc)) sf = open(x, "rb") @@ -55,12 +58,13 @@ for x in shaders: b = b[:-2] if (b.endswith("\n")): b = b[:-1] - f.write(" \"" + b) + s = ' \"' + b + f.write(s.encode(enc)) b = sf.readline() if (b != ""): - f.write("\"\n") + f.write(b'"\n') - f.write("\";\n") + f.write(b'";\n') sf.close() f.close() diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index e5d463d391..eb7d517841 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -211,9 +211,9 @@ Error DynamicFontAtSize::_load() { scale_color_font = float(id.size) / face->available_sizes[i].width; } } - error = FT_Select_Size(face, best_match); + FT_Select_Size(face, best_match); } else { - error = FT_Set_Pixel_Sizes(face, 0, id.size * oversampling); + FT_Set_Pixel_Sizes(face, 0, id.size * oversampling); } ascent = (face->size->metrics.ascender / 64.0) / oversampling * scale_color_font; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 654d7b884e..56b3ee70bb 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -145,11 +145,17 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const { void ShaderMaterial::set_shader(const Ref<Shader> &p_shader) { + if (shader.is_valid()) { + shader->disconnect("changed", this, "_shader_changed"); + } + shader = p_shader; RID rid; - if (shader.is_valid()) + if (shader.is_valid()) { rid = shader->get_rid(); + shader->connect("changed", this, "_shader_changed"); + } VS::get_singleton()->material_set_shader(_get_material(), rid); _change_notify(); //properties for shader exposed @@ -171,12 +177,17 @@ Variant ShaderMaterial::get_shader_param(const StringName &p_param) const { return VS::get_singleton()->material_get_param(_get_material(), p_param); } +void ShaderMaterial::_shader_changed() { + _change_notify(); //update all properties +} + void ShaderMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shader", "shader"), &ShaderMaterial::set_shader); ClassDB::bind_method(D_METHOD("get_shader"), &ShaderMaterial::get_shader); ClassDB::bind_method(D_METHOD("set_shader_param", "param", "value"), &ShaderMaterial::set_shader_param); ClassDB::bind_method(D_METHOD("get_shader_param", "param"), &ShaderMaterial::get_shader_param); + ClassDB::bind_method(D_METHOD("_shader_changed"), &ShaderMaterial::_shader_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shader", PROPERTY_HINT_RESOURCE_TYPE, "Shader"), "set_shader", "get_shader"); } @@ -393,6 +404,9 @@ void SpatialMaterial::_update_shader() { if (flags[FLAG_DONT_RECEIVE_SHADOWS]) { code += ",shadows_disabled"; } + if (flags[FLAG_DISABLE_AMBIENT_LIGHT]) { + code += ",ambient_light_disabled"; + } if (flags[FLAG_ENSURE_CORRECT_NORMALS]) { code += ",ensure_correct_normals"; } @@ -1855,6 +1869,7 @@ void SpatialMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_fixed_size"), "set_flag", "get_flag", FLAG_FIXED_SIZE); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_albedo_tex_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_do_not_receive_shadows"), "set_flag", "get_flag", FLAG_DONT_RECEIVE_SHADOWS); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_disable_ambient_light"), "set_flag", "get_flag", FLAG_DISABLE_AMBIENT_LIGHT); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_ensure_correct_normals"), "set_flag", "get_flag", FLAG_ENSURE_CORRECT_NORMALS); ADD_GROUP("Vertex Color", "vertex_color"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_use_as_albedo"), "set_flag", "get_flag", FLAG_ALBEDO_FROM_VERTEX_COLOR); @@ -2046,6 +2061,7 @@ void SpatialMaterial::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_TRIPLANAR_USE_WORLD); BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_FORCE_SRGB); BIND_ENUM_CONSTANT(FLAG_DONT_RECEIVE_SHADOWS); + BIND_ENUM_CONSTANT(FLAG_DISABLE_AMBIENT_LIGHT); BIND_ENUM_CONSTANT(FLAG_ENSURE_CORRECT_NORMALS); BIND_ENUM_CONSTANT(FLAG_MAX); diff --git a/scene/resources/material.h b/scene/resources/material.h index 87594213bc..84f1005b03 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -92,6 +92,8 @@ protected: virtual bool _can_do_next_pass() const; + void _shader_changed(); + public: void set_shader(const Ref<Shader> &p_shader); Ref<Shader> get_shader() const; @@ -190,6 +192,7 @@ public: FLAG_ALBEDO_TEXTURE_FORCE_SRGB, FLAG_DONT_RECEIVE_SHADOWS, FLAG_ENSURE_CORRECT_NORMALS, + FLAG_DISABLE_AMBIENT_LIGHT, FLAG_MAX }; @@ -238,7 +241,7 @@ private: uint64_t blend_mode : 2; uint64_t depth_draw_mode : 2; uint64_t cull_mode : 2; - uint64_t flags : 16; + uint64_t flags : 17; uint64_t detail_blend_mode : 2; uint64_t diffuse_mode : 3; uint64_t specular_mode : 2; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index b0620d3363..fe87dcdd2c 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -40,7 +40,6 @@ void Mesh::_clear_triangle_mesh() const { triangle_mesh.unref(); - ; } Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { @@ -110,6 +109,54 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { return triangle_mesh; } +void Mesh::generate_debug_mesh_lines(Vector<Vector3> &r_lines) { + + Ref<TriangleMesh> tm = generate_triangle_mesh(); + if (tm.is_null()) + return; + + PoolVector<int> triangle_indices; + tm->get_indices(&triangle_indices); + const int triangles_num = tm->get_triangles().size(); + PoolVector<Vector3> vertices = tm->get_vertices(); + + r_lines.resize(tm->get_triangles().size() * 6); // 3 lines x 2 points each line + + PoolVector<int>::Read ind_r = triangle_indices.read(); + PoolVector<Vector3>::Read ver_r = vertices.read(); + for (int j = 0, x = 0, i = 0; i < triangles_num; j += 6, x += 3, ++i) { + // Triangle line 1 + r_lines[j + 0] = ver_r[ind_r[x + 0]]; + r_lines[j + 1] = ver_r[ind_r[x + 1]]; + + // Triangle line 2 + r_lines[j + 2] = ver_r[ind_r[x + 1]]; + r_lines[j + 3] = ver_r[ind_r[x + 2]]; + + // Triangle line 3 + r_lines[j + 4] = ver_r[ind_r[x + 2]]; + r_lines[j + 5] = ver_r[ind_r[x + 0]]; + } +} +void Mesh::generate_debug_mesh_indices(Vector<Vector3> &r_points) { + Ref<TriangleMesh> tm = generate_triangle_mesh(); + if (tm.is_null()) + return; + + PoolVector<Vector3> vertices = tm->get_vertices(); + + int vertices_size = vertices.size(); + r_points.resize(vertices_size); + for (int i = 0; i < vertices_size; ++i) { + r_points[i] = vertices[i]; + } +} + +bool Mesh::surface_is_softbody_friendly(int p_idx) const { + const uint32_t surface_format = surface_get_format(p_idx); + return (surface_format & Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE && (!(surface_format & Mesh::ARRAY_COMPRESS_VERTEX)) && (!(surface_format & Mesh::ARRAY_COMPRESS_NORMAL))); +} + PoolVector<Face3> Mesh::get_faces() const { Ref<TriangleMesh> tm = generate_triangle_mesh(); @@ -484,6 +531,10 @@ void Mesh::_bind_methods() { BIND_ENUM_CONSTANT(ARRAY_MAX); } +void Mesh::clear_cache() { + _clear_triangle_mesh(); +} + Mesh::Mesh() { } diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index a3fb068569..2127eaae4c 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -123,6 +123,7 @@ public: virtual int get_surface_count() const = 0; virtual int surface_get_array_len(int p_idx) const = 0; virtual int surface_get_array_index_len(int p_idx) const = 0; + virtual bool surface_is_softbody_friendly(int p_idx) const; virtual Array surface_get_arrays(int p_surface) const = 0; virtual Array surface_get_blend_shape_arrays(int p_surface) const = 0; virtual uint32_t surface_get_format(int p_idx) const = 0; @@ -133,6 +134,8 @@ public: PoolVector<Face3> get_faces() const; Ref<TriangleMesh> generate_triangle_mesh() const; + void generate_debug_mesh_lines(Vector<Vector3> &r_lines); + void generate_debug_mesh_indices(Vector<Vector3> &r_points); Ref<Shape> create_trimesh_shape() const; Ref<Shape> create_convex_shape() const; @@ -143,6 +146,7 @@ public: void set_lightmap_size_hint(const Vector2 &p_size); Size2 get_lightmap_size_hint() const; + void clear_cache(); Mesh(); }; diff --git a/scene/resources/physics_material.cpp b/scene/resources/physics_material.cpp new file mode 100644 index 0000000000..de3cfd1371 --- /dev/null +++ b/scene/resources/physics_material.cpp @@ -0,0 +1,94 @@ +/*************************************************************************/ +/* physics_material.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "physics_material.h" + +bool PhysicsMaterial::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "bounce") { + set_bounce(p_value); + } else if (p_name == "bounce_combine_mode") { + set_bounce_combine_mode(static_cast<PhysicsServer::CombineMode>(int(p_value))); + } else if (p_name == "friction") { + set_friction(p_value); + } else if (p_name == "friction_combine_mode") { + set_friction_combine_mode(static_cast<PhysicsServer::CombineMode>(int(p_value))); + } else { + return false; + } + + emit_changed(); + return true; +} + +bool PhysicsMaterial::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "bounce") { + r_ret = bounce; + } else if (p_name == "bounce_combine_mode") { + r_ret = int(bounce_combine_mode); + } else if (p_name == "friction") { + r_ret = friction; + } else if (p_name == "friction_combine_mode") { + r_ret = int(friction_combine_mode); + } else { + return false; + } + + return true; +} + +void PhysicsMaterial::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::REAL, "bounce")); + p_list->push_back(PropertyInfo(Variant::INT, "bounce_combine_mode", PROPERTY_HINT_ENUM, "Max,Min,Multiply,Average")); + p_list->push_back(PropertyInfo(Variant::REAL, "friction")); + p_list->push_back(PropertyInfo(Variant::INT, "friction_combine_mode", PROPERTY_HINT_ENUM, "Max,Min,Multiply,Average")); +} + +void PhysicsMaterial::_bind_methods() {} + +void PhysicsMaterial::set_bounce(real_t p_val) { + bounce = p_val; +} + +void PhysicsMaterial::set_bounce_combine_mode(PhysicsServer::CombineMode p_val) { + bounce_combine_mode = p_val; +} + +void PhysicsMaterial::set_friction(real_t p_val) { + friction = p_val; +} + +void PhysicsMaterial::set_friction_combine_mode(PhysicsServer::CombineMode p_val) { + friction_combine_mode = p_val; +} + +PhysicsMaterial::PhysicsMaterial() : + bounce(0), + bounce_combine_mode(PhysicsServer::COMBINE_MODE_MAX), + friction(0), + friction_combine_mode(PhysicsServer::COMBINE_MODE_MULTIPLY) {} diff --git a/scene/resources/physics_material.h b/scene/resources/physics_material.h new file mode 100644 index 0000000000..a6cb8c288e --- /dev/null +++ b/scene/resources/physics_material.h @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* physics_material.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 physics_material_override_H +#define physics_material_override_H + +#include "resource.h" +#include "servers/physics_server.h" + +class PhysicsMaterial : public Resource { + + GDCLASS(PhysicsMaterial, Resource); + OBJ_SAVE_TYPE(PhysicsMaterial); + RES_BASE_EXTENSION("PhyMat"); + + real_t bounce; + PhysicsServer::CombineMode bounce_combine_mode; + real_t friction; + PhysicsServer::CombineMode friction_combine_mode; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + +public: + void set_bounce(real_t p_val); + _FORCE_INLINE_ real_t get_bounce() const { return bounce; } + + void set_bounce_combine_mode(PhysicsServer::CombineMode p_val); + _FORCE_INLINE_ PhysicsServer::CombineMode get_bounce_combine_mode() const { return bounce_combine_mode; } + + void set_friction(real_t p_val); + _FORCE_INLINE_ real_t get_friction() const { return friction; } + + void set_friction_combine_mode(PhysicsServer::CombineMode p_val); + _FORCE_INLINE_ PhysicsServer::CombineMode get_friction_combine_mode() const { return friction_combine_mode; } + + PhysicsMaterial(); +}; + +#endif // physics_material_override_H diff --git a/scene/resources/scene_format_text.cpp b/scene/resources/scene_format_text.cpp index 597866eb74..9df117d09c 100644 --- a/scene/resources/scene_format_text.cpp +++ b/scene/resources/scene_format_text.cpp @@ -896,7 +896,7 @@ static void bs_save_unicode_string(FileAccess *f, const String &p_string, bool p CharString utf8 = p_string.utf8(); if (p_bit_on_len) { - f->store_32(utf8.length() + 1 | 0x80000000); + f->store_32((utf8.length() + 1) | 0x80000000); } else { f->store_32(utf8.length() + 1); } diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 36740a307b..f53f03c1c8 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -54,16 +54,20 @@ void Shader::set_code(const String &p_code) { VisualServer::get_singleton()->shader_set_code(shader, p_code); params_cache_dirty = true; - emit_signal(SceneStringNames::get_singleton()->changed); + + emit_changed(); } String Shader::get_code() const { + _update_shader(); return VisualServer::get_singleton()->shader_get_code(shader); } void Shader::get_param_list(List<PropertyInfo> *p_params) const { + _update_shader(); + List<PropertyInfo> local; VisualServer::get_singleton()->shader_get_param_list(shader, &local); params_cache.clear(); @@ -72,6 +76,9 @@ void Shader::get_param_list(List<PropertyInfo> *p_params) const { for (List<PropertyInfo>::Element *E = local.front(); E; E = E->next()) { PropertyInfo pi = E->get(); + if (default_textures.has(pi.name)) { //do not show default textures + continue; + } pi.name = "shader_param/" + pi.name; params_cache[pi.name] = E->get().name; if (p_params) { @@ -86,6 +93,8 @@ void Shader::get_param_list(List<PropertyInfo> *p_params) const { RID Shader::get_rid() const { + _update_shader(); + return shader; } @@ -98,6 +107,8 @@ void Shader::set_default_texture_param(const StringName &p_param, const Ref<Text default_textures.erase(p_param); VS::get_singleton()->shader_set_default_texture_param(shader, p_param, RID()); } + + emit_changed(); } Ref<Texture> Shader::get_default_texture_param(const StringName &p_param) const { @@ -120,6 +131,9 @@ bool Shader::has_param(const StringName &p_param) const { return params_cache.has(p_param); } +void Shader::_update_shader() const { +} + void Shader::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mode"), &Shader::get_mode); @@ -227,5 +241,5 @@ void ResourceFormatSaverShader::get_recognized_extensions(const RES &p_resource, } bool ResourceFormatSaverShader::recognize(const RES &p_resource) const { - return Object::cast_to<Shader>(*p_resource) != NULL; + return p_resource->get_class_name() == "Shader"; //only shader, not inherited } diff --git a/scene/resources/shader.h b/scene/resources/shader.h index 248a6f0125..efc5da7753 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -61,12 +61,13 @@ private: mutable Map<StringName, StringName> params_cache; //map a shader param to a material param.. Map<StringName, Ref<Texture> > default_textures; + virtual void _update_shader() const; //used for visual shader protected: static void _bind_methods(); public: //void set_mode(Mode p_mode); - Mode get_mode() const; + virtual Mode get_mode() const; void set_code(const String &p_code); String get_code() const; diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index ebad00b068..fb81375b0a 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -905,12 +905,20 @@ bool StyleBoxLine::is_vertical() const { return vertical; } -void StyleBoxLine::set_grow(float p_grow) { - grow = p_grow; +void StyleBoxLine::set_grow_end(float p_grow_end) { + grow_end = p_grow_end; emit_changed(); } -float StyleBoxLine::get_grow() const { - return grow; +float StyleBoxLine::get_grow_end() const { + return grow_end; +} + +void StyleBoxLine::set_grow_begin(float p_grow_begin) { + grow_begin = p_grow_begin; + emit_changed(); +} +float StyleBoxLine::get_grow_begin() const { + return grow_begin; } void StyleBoxLine::_bind_methods() { @@ -919,13 +927,16 @@ void StyleBoxLine::_bind_methods() { ClassDB::bind_method(D_METHOD("get_color"), &StyleBoxLine::get_color); ClassDB::bind_method(D_METHOD("set_thickness", "thickness"), &StyleBoxLine::set_thickness); ClassDB::bind_method(D_METHOD("get_thickness"), &StyleBoxLine::get_thickness); - ClassDB::bind_method(D_METHOD("set_grow", "grow"), &StyleBoxLine::set_grow); - ClassDB::bind_method(D_METHOD("get_grow"), &StyleBoxLine::get_grow); + ClassDB::bind_method(D_METHOD("set_grow_begin", "offset"), &StyleBoxLine::set_grow_begin); + ClassDB::bind_method(D_METHOD("get_grow_begin"), &StyleBoxLine::get_grow_begin); + ClassDB::bind_method(D_METHOD("set_grow_end", "offset"), &StyleBoxLine::set_grow_end); + ClassDB::bind_method(D_METHOD("get_grow_end"), &StyleBoxLine::get_grow_end); ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &StyleBoxLine::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &StyleBoxLine::is_vertical); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "grow", PROPERTY_HINT_RANGE, "-300,300,1"), "set_grow", "get_grow"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "grow_begin", PROPERTY_HINT_RANGE, "-300,300,1"), "set_grow_begin", "get_grow_begin"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "grow_end", PROPERTY_HINT_RANGE, "-300,300,1"), "set_grow_end", "get_grow_end"); ADD_PROPERTY(PropertyInfo(Variant::INT, "thickness", PROPERTY_HINT_RANGE, "0,10"), "set_thickness", "get_thickness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); } @@ -941,12 +952,12 @@ void StyleBoxLine::draw(RID p_canvas_item, const Rect2 &p_rect) const { Rect2i r = p_rect; if (vertical) { - r.position.y -= grow; - r.size.y += grow * 2; + r.position.y -= grow_begin; + r.size.y += (grow_begin + grow_end); r.size.x = thickness; } else { - r.position.x -= grow; - r.size.x += grow * 2; + r.position.x -= grow_begin; + r.size.x += (grow_begin + grow_end); r.size.y = thickness; } @@ -954,7 +965,8 @@ void StyleBoxLine::draw(RID p_canvas_item, const Rect2 &p_rect) const { } StyleBoxLine::StyleBoxLine() { - grow = 1.0; + grow_begin = 1.0; + grow_end = 1.0; thickness = 1; color = Color(0.0, 0.0, 0.0); vertical = false; diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index c1d84fe19f..ed193a1ab4 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -236,7 +236,8 @@ class StyleBoxLine : public StyleBox { Color color; int thickness; bool vertical; - float grow; + float grow_begin; + float grow_end; protected: virtual float get_style_margin(Margin p_margin) const; @@ -252,8 +253,11 @@ public: void set_vertical(bool p_vertical); bool is_vertical() const; - void set_grow(float p_grow); - float get_grow() const; + void set_grow_begin(float p_grow); + float get_grow_begin() const; + + void set_grow_end(float p_grow); + float get_grow_end() const; virtual Size2 get_center_size() const; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 5a42873d79..3df9ab26a4 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -421,6 +421,7 @@ Ref<ArrayMesh> SurfaceTool::commit(const Ref<ArrayMesh> &p_existing, uint32_t p_ Array a = commit_to_arrays(); mesh->add_surface_from_arrays(primitive, a, Array(), p_flags); + if (material.is_valid()) mesh->surface_set_material(surface, material); diff --git a/scene/resources/text_file.cpp b/scene/resources/text_file.cpp new file mode 100644 index 0000000000..e2fe0adfc5 --- /dev/null +++ b/scene/resources/text_file.cpp @@ -0,0 +1,77 @@ +/*************************************************************************/ +/* text_file.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "text_file.h" + +#include "os/file_access.h" + +bool TextFile::has_text() const { + return text != ""; +} + +String TextFile::get_text() const { + return text; +} + +void TextFile::set_text(const String &p_code) { + text = p_code; +} + +void TextFile::reload_from_file() { + load_text(path); +} + +Error TextFile::load_text(const String &p_path) { + + PoolVector<uint8_t> sourcef; + Error err; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); + if (err) { + ERR_FAIL_COND_V(err, err); + } + + int len = f->get_len(); + sourcef.resize(len + 1); + PoolVector<uint8_t>::Write w = sourcef.write(); + int r = f->get_buffer(w.ptr(), len); + f->close(); + memdelete(f); + ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); + w[len] = 0; + + String s; + if (s.parse_utf8((const char *)w.ptr())) { + ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); + ERR_FAIL_V(ERR_INVALID_DATA); + } + text = s; + path = p_path; + return OK; +} diff --git a/scene/resources/text_file.h b/scene/resources/text_file.h new file mode 100644 index 0000000000..40b648eebb --- /dev/null +++ b/scene/resources/text_file.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* text_file.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 TEXTFILE_H +#define TEXTFILE_H + +#include "io/resource_loader.h" +#include "io/resource_saver.h" + +class TextFile : public Resource { + + GDCLASS(TextFile, Resource) + +private: + String text; + String path; + +public: + virtual bool has_text() const; + virtual String get_text() const; + virtual void set_text(const String &p_code); + virtual void reload_from_file(); + + void set_file_path(const String &p_path) { path = p_path; } + Error load_text(const String &p_path); +}; + +#endif // TEXTFILE_H diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 54f5aea160..2baad555c0 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1669,3 +1669,208 @@ ProxyTexture::~ProxyTexture() { VS::get_singleton()->free(proxy); } +////////////////////////////////////////////// + +void AnimatedTexture::_update_proxy() { + + _THREAD_SAFE_METHOD_ + + float delta; + if (prev_ticks == 0) { + delta = 0; + prev_ticks = OS::get_singleton()->get_ticks_usec(); + } else { + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + delta = float(double(ticks - prev_ticks) / 1000000.0); + prev_ticks = ticks; + } + + time += delta; + + float limit; + + if (fps == 0) { + limit = 0; + } else { + limit = 1.0 / fps; + } + + int iter_max = frame_count; + while (iter_max) { + float frame_limit = limit + frames[current_frame].delay_sec; + + if (time > frame_limit) { + current_frame++; + if (current_frame >= frame_count) { + current_frame = 0; + } + time -= frame_limit; + } else { + break; + } + iter_max--; + } + + if (frames[current_frame].texture.is_valid()) { + VisualServer::get_singleton()->texture_set_proxy(proxy, frames[current_frame].texture->get_rid()); + } +} + +void AnimatedTexture::set_frames(int p_frames) { + ERR_FAIL_COND(p_frames < 1 || p_frames > MAX_FRAMES); + + _THREAD_SAFE_METHOD_ + + frame_count = p_frames; +} +int AnimatedTexture::get_frames() const { + return frame_count; +} + +void AnimatedTexture::set_frame_texture(int p_frame, const Ref<Texture> &p_texture) { + ERR_FAIL_INDEX(p_frame, MAX_FRAMES); + + _THREAD_SAFE_METHOD_ + + frames[p_frame].texture = p_texture; +} +Ref<Texture> AnimatedTexture::get_frame_texture(int p_frame) const { + ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, Ref<Texture>()); + + _THREAD_SAFE_METHOD_ + + return frames[p_frame].texture; +} + +void AnimatedTexture::set_frame_delay(int p_frame, float p_delay_sec) { + ERR_FAIL_INDEX(p_frame, MAX_FRAMES); + + _THREAD_SAFE_METHOD_ + + frames[p_frame].delay_sec = p_delay_sec; +} +float AnimatedTexture::get_frame_delay(int p_frame) const { + ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, 0); + + _THREAD_SAFE_METHOD_ + + return frames[p_frame].delay_sec; +} + +void AnimatedTexture::set_fps(float p_fps) { + ERR_FAIL_COND(p_fps < 0 || p_fps >= 1000); + + fps = p_fps; +} +float AnimatedTexture::get_fps() const { + return fps; +} + +int AnimatedTexture::get_width() const { + + _THREAD_SAFE_METHOD_ + + if (!frames[current_frame].texture.is_valid()) { + return 1; + } + + return frames[current_frame].texture->get_width(); +} +int AnimatedTexture::get_height() const { + + _THREAD_SAFE_METHOD_ + + if (!frames[current_frame].texture.is_valid()) { + return 1; + } + + return frames[current_frame].texture->get_height(); +} +RID AnimatedTexture::get_rid() const { + return proxy; +} + +bool AnimatedTexture::has_alpha() const { + + _THREAD_SAFE_METHOD_ + + if (!frames[current_frame].texture.is_valid()) { + return false; + } + + return frames[current_frame].texture->has_alpha(); +} + +Ref<Image> AnimatedTexture::get_data() const { + + _THREAD_SAFE_METHOD_ + + if (!frames[current_frame].texture.is_valid()) { + return Ref<Image>(); + } + + return frames[current_frame].texture->get_data(); +} + +void AnimatedTexture::set_flags(uint32_t p_flags) { +} +uint32_t AnimatedTexture::get_flags() const { + + _THREAD_SAFE_METHOD_ + + if (!frames[current_frame].texture.is_valid()) { + return 0; + } + + return frames[current_frame].texture->get_flags(); +} + +void AnimatedTexture::_validate_property(PropertyInfo &property) const { + + String prop = property.name; + if (prop.begins_with("frame_")) { + int frame = prop.get_slicec('/', 0).get_slicec('_', 1).to_int(); + if (frame >= frame_count) { + property.usage = 0; + } + } +} + +void AnimatedTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_frames", "frames"), &AnimatedTexture::set_frames); + ClassDB::bind_method(D_METHOD("get_frames"), &AnimatedTexture::get_frames); + + ClassDB::bind_method(D_METHOD("set_fps", "fps"), &AnimatedTexture::set_fps); + ClassDB::bind_method(D_METHOD("get_fps"), &AnimatedTexture::get_fps); + + ClassDB::bind_method(D_METHOD("set_frame_texture", "frame", "texture"), &AnimatedTexture::set_frame_texture); + ClassDB::bind_method(D_METHOD("get_frame_texture", "frame"), &AnimatedTexture::get_frame_texture); + + ClassDB::bind_method(D_METHOD("set_frame_delay", "frame", "delay"), &AnimatedTexture::set_frame_delay); + ClassDB::bind_method(D_METHOD("get_frame_delay", "frame"), &AnimatedTexture::get_frame_delay); + + ClassDB::bind_method(D_METHOD("_update_proxy"), &AnimatedTexture::_update_proxy); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "frames", PROPERTY_HINT_RANGE, "1," + itos(MAX_FRAMES), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frames", "get_frames"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fps", PROPERTY_HINT_RANGE, "0,1024,0.1"), "set_fps", "get_fps"); + + for (int i = 0; i < MAX_FRAMES; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "frame_" + itos(i) + "/texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_frame_texture", "get_frame_texture", i); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "frame_" + itos(i) + "/delay_sec", PROPERTY_HINT_RANGE, "0.0,16.0,0.01"), "set_frame_delay", "get_frame_delay", i); + } +} + +AnimatedTexture::AnimatedTexture() { + proxy = VS::get_singleton()->texture_create(); + VisualServer::get_singleton()->texture_set_force_redraw_if_visible(proxy, true); + time = 0; + frame_count = 1; + fps = 4; + prev_ticks = 0; + current_frame = 0; + VisualServer::get_singleton()->connect("frame_pre_draw", this, "_update_proxy"); +} + +AnimatedTexture::~AnimatedTexture() { + VS::get_singleton()->free(proxy); +} diff --git a/scene/resources/texture.h b/scene/resources/texture.h index d81fd3b19b..c994bdad5f 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -34,10 +34,11 @@ #include "curve.h" #include "io/resource_loader.h" #include "math_2d.h" +#include "os/mutex.h" +#include "os/thread_safe.h" #include "resource.h" #include "scene/resources/color_ramp.h" #include "servers/visual_server.h" - /** @author Juan Linietsky <reduzio@gmail.com> */ @@ -521,4 +522,70 @@ public: ~ProxyTexture(); }; +class AnimatedTexture : public Texture { + GDCLASS(AnimatedTexture, Texture) + + _THREAD_SAFE_CLASS_ + +private: + enum { + MAX_FRAMES = 256 + }; + + RID proxy; + + struct Frame { + + Ref<Texture> texture; + float delay_sec; + + Frame() { + delay_sec = 0; + } + }; + + Frame frames[MAX_FRAMES]; + int frame_count; + int current_frame; + + float fps; + + float time; + + uint64_t prev_ticks; + + void _update_proxy(); + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; + +public: + void set_frames(int p_frames); + int get_frames() const; + + void set_frame_texture(int p_frame, const Ref<Texture> &p_texture); + Ref<Texture> get_frame_texture(int p_frame) const; + + void set_frame_delay(int p_frame, float p_delay_sec); + float get_frame_delay(int p_frame) const; + + void set_fps(float p_fps); + float get_fps() const; + + virtual int get_width() const; + virtual int get_height() const; + virtual RID get_rid() const; + + virtual bool has_alpha() const; + + virtual void set_flags(uint32_t p_flags); + virtual uint32_t get_flags() const; + + virtual Ref<Image> get_data() const; + + AnimatedTexture(); + ~AnimatedTexture(); +}; + #endif diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 58057cda0c..f9df6b4304 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -923,6 +923,8 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("tile_get_normal_map", "id"), &TileSet::tile_get_normal_map); ClassDB::bind_method(D_METHOD("tile_set_material", "id", "material"), &TileSet::tile_set_material); ClassDB::bind_method(D_METHOD("tile_get_material", "id"), &TileSet::tile_get_material); + ClassDB::bind_method(D_METHOD("tile_set_modulate", "id", "color"), &TileSet::tile_set_modulate); + ClassDB::bind_method(D_METHOD("tile_get_modulate", "id"), &TileSet::tile_get_modulate); ClassDB::bind_method(D_METHOD("tile_set_texture_offset", "id", "texture_offset"), &TileSet::tile_set_texture_offset); ClassDB::bind_method(D_METHOD("tile_get_texture_offset", "id"), &TileSet::tile_get_texture_offset); ClassDB::bind_method(D_METHOD("tile_set_region", "id", "region"), &TileSet::tile_set_region); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp new file mode 100644 index 0000000000..b7b7802d1b --- /dev/null +++ b/scene/resources/visual_shader.cpp @@ -0,0 +1,1555 @@ +#include "visual_shader.h" +#include "servers/visual/shader_types.h" +#include "vmap.h" + +void VisualShaderNode::set_output_port_for_preview(int p_index) { + + port_preview = p_index; +} + +int VisualShaderNode::get_output_port_for_preview() const { + + return port_preview; +} + +void VisualShaderNode::set_input_port_default_value(int p_port, const Variant &p_value) { + default_input_values[p_port] = p_value; + emit_changed(); +} + +Variant VisualShaderNode::get_input_port_default_value(int p_port) const { + if (default_input_values.has(p_port)) { + return default_input_values[p_port]; + } + + return Variant(); +} + +bool VisualShaderNode::is_port_separator(int p_index) const { + return false; +} + +Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { + return Vector<VisualShader::DefaultTextureParam>(); +} +String VisualShaderNode::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return String(); +} + +Vector<StringName> VisualShaderNode::get_editable_properties() const { + return Vector<StringName>(); +} + +Array VisualShaderNode::_get_default_input_values() const { + + Array ret; + for (Map<int, Variant>::Element *E = default_input_values.front(); E; E = E->next()) { + ret.push_back(E->key()); + ret.push_back(E->get()); + } + return ret; +} +void VisualShaderNode::_set_default_input_values(const Array &p_values) { + + if (p_values.size() % 2 == 0) { + for (int i = 0; i < p_values.size(); i += 2) { + default_input_values[p_values[i + 0]] = p_values[i + 1]; + } + } + + emit_changed(); +} + +String VisualShaderNode::get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const { + return String(); +} + +void VisualShaderNode::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_output_port_for_preview", "port"), &VisualShaderNode::set_output_port_for_preview); + ClassDB::bind_method(D_METHOD("get_output_port_for_preview"), &VisualShaderNode::get_output_port_for_preview); + + ClassDB::bind_method(D_METHOD("set_input_port_default_value", "port", "value"), &VisualShaderNode::set_input_port_default_value); + ClassDB::bind_method(D_METHOD("get_input_port_default_value", "port"), &VisualShaderNode::get_input_port_default_value); + + ClassDB::bind_method(D_METHOD("_set_default_input_values", "values"), &VisualShaderNode::_set_default_input_values); + ClassDB::bind_method(D_METHOD("_get_default_input_values"), &VisualShaderNode::_get_default_input_values); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "output_port_for_preview"), "set_output_port_for_preview", "get_output_port_for_preview"); + ADD_PROPERTYNZ(PropertyInfo(Variant::ARRAY, "default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_default_input_values", "_get_default_input_values"); + ADD_SIGNAL(MethodInfo("editor_refresh_request")); +} + +VisualShaderNode::VisualShaderNode() { + port_preview = -1; +} + +///////////////////////////////////////////////////////// + +void VisualShader::add_node(Type p_type, const Ref<VisualShaderNode> &p_node, const Vector2 &p_position, int p_id) { + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_id < 2); + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Graph *g = &graph[p_type]; + ERR_FAIL_COND(g->nodes.has(p_id)); + Node n; + n.node = p_node; + n.position = p_position; + + Ref<VisualShaderNodeUniform> uniform = n.node; + if (uniform.is_valid()) { + String valid_name = validate_uniform_name(uniform->get_uniform_name(), uniform); + uniform->set_uniform_name(valid_name); + } + + Ref<VisualShaderNodeInput> input = n.node; + if (input.is_valid()) { + input->shader_mode = shader_mode; + input->shader_type = p_type; + input->connect("input_type_changed", this, "_input_type_changed", varray(p_type, p_id)); + } + + n.node->connect("changed", this, "_queue_update"); + + g->nodes[p_id] = n; + + _queue_update(); +} + +void VisualShader::set_node_position(Type p_type, int p_id, const Vector2 &p_position) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Graph *g = &graph[p_type]; + ERR_FAIL_COND(!g->nodes.has(p_id)); + g->nodes[p_id].position = p_position; +} + +Vector2 VisualShader::get_node_position(Type p_type, int p_id) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Vector2()); + const Graph *g = &graph[p_type]; + ERR_FAIL_COND_V(!g->nodes.has(p_id), Vector2()); + return g->nodes[p_id].position; +} +Ref<VisualShaderNode> VisualShader::get_node(Type p_type, int p_id) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Ref<VisualShaderNode>()); + const Graph *g = &graph[p_type]; + ERR_FAIL_COND_V(!g->nodes.has(p_id), Ref<VisualShaderNode>()); + return g->nodes[p_id].node; +} + +Vector<int> VisualShader::get_node_list(Type p_type) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Vector<int>()); + const Graph *g = &graph[p_type]; + + Vector<int> ret; + for (Map<int, Node>::Element *E = g->nodes.front(); E; E = E->next()) { + ret.push_back(E->key()); + } + + return ret; +} +int VisualShader::get_valid_node_id(Type p_type) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, NODE_ID_INVALID); + const Graph *g = &graph[p_type]; + return g->nodes.size() ? MAX(2, g->nodes.back()->key() + 1) : 2; +} + +int VisualShader::find_node_id(Type p_type, const Ref<VisualShaderNode> &p_node) const { + for (const Map<int, Node>::Element *E = graph[p_type].nodes.front(); E; E = E->next()) { + if (E->get().node == p_node) + return E->key(); + } + + return NODE_ID_INVALID; +} + +void VisualShader::remove_node(Type p_type, int p_id) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + ERR_FAIL_COND(p_id < 2); + Graph *g = &graph[p_type]; + ERR_FAIL_COND(!g->nodes.has(p_id)); + + Ref<VisualShaderNodeInput> input = g->nodes[p_id].node; + if (input.is_valid()) { + input->disconnect("input_type_changed", this, "_input_type_changed"); + } + + g->nodes[p_id].node->disconnect("changed", this, "_queue_update"); + + g->nodes.erase(p_id); + + for (List<Connection>::Element *E = g->connections.front(); E;) { + List<Connection>::Element *N = E->next(); + if (E->get().from_node == p_id || E->get().to_node == p_id) { + g->connections.erase(E); + } + E = N; + } + + _queue_update(); +} + +bool VisualShader::is_node_connection(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, false); + const Graph *g = &graph[p_type]; + + for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + + if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + return true; + } + } + + return false; +} + +bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { + + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, false); + const Graph *g = &graph[p_type]; + + if (!g->nodes.has(p_from_node)) + return false; + + if (p_from_port < 0 || p_from_port >= g->nodes[p_from_node].node->get_output_port_count()) + return false; + + if (!g->nodes.has(p_to_node)) + return false; + + if (p_to_port < 0 || p_to_port >= g->nodes[p_to_node].node->get_input_port_count()) + return false; + + VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port); + VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port); + + if (MAX(0, from_port_type - 1) != (MAX(0, to_port_type - 1))) { + return false; + } + + for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + + if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + return false; + } + } + + return true; +} + +Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ERR_CANT_CONNECT); + Graph *g = &graph[p_type]; + + ERR_FAIL_COND_V(!g->nodes.has(p_from_node), ERR_INVALID_PARAMETER); + ERR_FAIL_INDEX_V(p_from_port, g->nodes[p_from_node].node->get_output_port_count(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!g->nodes.has(p_to_node), ERR_INVALID_PARAMETER); + ERR_FAIL_INDEX_V(p_to_port, g->nodes[p_to_node].node->get_input_port_count(), ERR_INVALID_PARAMETER); + + VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port); + VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port); + + if (MAX(0, from_port_type - 1) != (MAX(0, to_port_type - 1))) { + ERR_EXPLAIN("Incompatible port types (scalar/vec with transform"); + ERR_FAIL_V(ERR_INVALID_PARAMETER) + return ERR_INVALID_PARAMETER; + } + + for (List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + + if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + ERR_FAIL_V(ERR_ALREADY_EXISTS); + } + } + + Connection c; + c.from_node = p_from_node; + c.from_port = p_from_port; + c.to_node = p_to_node; + c.to_port = p_to_port; + g->connections.push_back(c); + + _queue_update(); + return OK; +} +void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Graph *g = &graph[p_type]; + + for (List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + + if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + g->connections.erase(E); + _queue_update(); + return; + } + } +} + +Array VisualShader::_get_node_connections(Type p_type) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Array()); + const Graph *g = &graph[p_type]; + + Array ret; + for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + Dictionary d; + d["from_node"] = E->get().from_node; + d["from_port"] = E->get().from_port; + d["to_node"] = E->get().to_node; + d["to_port"] = E->get().to_port; + ret.push_back(d); + } + + return ret; +} +void VisualShader::get_node_connections(Type p_type, List<Connection> *r_connections) const { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + const Graph *g = &graph[p_type]; + + for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + r_connections->push_back(E->get()); + } +} + +void VisualShader::set_mode(Mode p_mode) { + if (shader_mode == p_mode) { + return; + } + + //erase input/output connections + modes.clear(); + flags.clear(); + shader_mode = p_mode; + for (int i = 0; i < TYPE_MAX; i++) { + + for (Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { + + Ref<VisualShaderNodeInput> input = E->get().node; + if (input.is_valid()) { + input->shader_mode = shader_mode; + //input->input_index = 0; + } + } + + Ref<VisualShaderNodeOutput> output = graph[i].nodes[NODE_ID_OUTPUT].node; + output->shader_mode = shader_mode; + + // clear connections since they are no longer valid + for (List<Connection>::Element *E = graph[i].connections.front(); E;) { + + bool keep = true; + + List<Connection>::Element *N = E->next(); + + int from = E->get().from_node; + int to = E->get().to_node; + + if (!graph[i].nodes.has(from)) { + keep = false; + } else { + Ref<VisualShaderNode> from_node = graph[i].nodes[from].node; + if (from_node->is_class("VisualShaderNodeOutput") || from_node->is_class("VisualShaderNodeInput")) { + keep = false; + } + } + + if (!graph[i].nodes.has(to)) { + keep = false; + } else { + Ref<VisualShaderNode> to_node = graph[i].nodes[to].node; + if (to_node->is_class("VisualShaderNodeOutput") || to_node->is_class("VisualShaderNodeInput")) { + keep = false; + } + } + + if (!keep) { + graph[i].connections.erase(E); + } + E = N; + } + } + + _queue_update(); + _change_notify(); +} + +void VisualShader::set_graph_offset(const Vector2 &p_offset) { + graph_offset = p_offset; +} + +Vector2 VisualShader::get_graph_offset() const { + return graph_offset; +} + +Shader::Mode VisualShader::get_mode() const { + return shader_mode; +} + +String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port, Vector<DefaultTextureParam> &default_tex_params) const { + + Ref<VisualShaderNode> node = get_node(p_type, p_node); + ERR_FAIL_COND_V(!node.is_valid(), String()); + ERR_FAIL_COND_V(p_port < 0 || p_port >= node->get_output_port_count(), String()); + ERR_FAIL_COND_V(node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_TRANSFORM, String()); + + StringBuilder global_code; + StringBuilder code; + + global_code += String() + "shader_type canvas_item;\n"; + + //make it faster to go around through shader + VMap<ConnectionKey, const List<Connection>::Element *> input_connections; + VMap<ConnectionKey, const List<Connection>::Element *> output_connections; + + for (const List<Connection>::Element *E = graph[p_type].connections.front(); E; E = E->next()) { + ConnectionKey from_key; + from_key.node = E->get().from_node; + from_key.port = E->get().from_port; + + output_connections.insert(from_key, E); + + ConnectionKey to_key; + to_key.node = E->get().to_node; + to_key.port = E->get().to_port; + + input_connections.insert(to_key, E); + } + + code += "\nvoid fragment() {\n"; + + Set<int> processed; + Error err = _write_node(p_type, global_code, code, default_tex_params, input_connections, output_connections, p_node, processed, true); + ERR_FAIL_COND_V(err != OK, String()); + + if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_SCALAR) { + code += "\tCOLOR.rgb = vec3( n_out" + itos(p_node) + "p" + itos(p_port) + " );\n"; + } else { + code += "\tCOLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; + } + code += "}\n"; + + //set code secretly + global_code += "\n\n"; + String final_code = global_code; + final_code += code; + //print_line(final_code); + return final_code; +} + +#define IS_INITIAL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z')) + +#define IS_SYMBOL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z') || ((m_d) >= '0' && (m_d) <= '9') || (m_d) == '_') + +String VisualShader::validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const { + + String name = p_name; //validate name first + while (name.length() && !IS_INITIAL_CHAR(name[0])) { + name = name.substr(1, name.length() - 1); + } + if (name != String()) { + + String valid_name; + + for (int i = 0; i < name.length(); i++) { + if (IS_SYMBOL_CHAR(name[i])) { + valid_name += String::chr(name[i]); + } else if (name[i] == ' ') { + valid_name += "_"; + } + } + + name = valid_name; + } + + if (name == String()) { + name = p_uniform->get_caption(); + } + + int attempt = 1; + + while (true) { + + bool exists = false; + for (int i = 0; i < TYPE_MAX; i++) { + for (const Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { + Ref<VisualShaderNodeUniform> node = E->get().node; + if (node == p_uniform) { //do not test on self + continue; + } + if (node.is_valid() && node->get_uniform_name() == name) { + exists = true; + break; + } + } + if (exists) { + break; + } + } + + if (exists) { + //remove numbers, put new and try again + attempt++; + while (name.length() && name[name.length() - 1] >= '0' && name[name.length() - 1] <= '9') { + name = name.substr(0, name.length() - 1); + } + ERR_FAIL_COND_V(name == String(), String()); + name += itos(attempt); + } else { + break; + } + } + + return name; +} + +VisualShader::RenderModeEnums VisualShader::render_mode_enums[] = { + { Shader::MODE_SPATIAL, "blend" }, + { Shader::MODE_SPATIAL, "depth_draw" }, + { Shader::MODE_SPATIAL, "cull" }, + { Shader::MODE_SPATIAL, "diffuse" }, + { Shader::MODE_SPATIAL, "specular" }, + { Shader::MODE_CANVAS_ITEM, "blend" }, + { Shader::MODE_CANVAS_ITEM, NULL } +}; + +static const char *type_string[VisualShader::TYPE_MAX] = { + "vertex", + "fragment", + "light" +}; +bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { + + String name = p_name; + if (name == "mode") { + set_mode(Shader::Mode(int(p_value))); + return true; + } else if (name.begins_with("flags/")) { + StringName flag = name.get_slicec('/', 1); + bool enable = p_value; + if (enable) { + flags.insert(flag); + } else { + flags.erase(flag); + } + _queue_update(); + return true; + } else if (name.begins_with("modes/")) { + String mode = name.get_slicec('/', 1); + int value = p_value; + if (value == 0) { + modes.erase(mode); //means its default anyway, so dont store it + } else { + modes[mode] = value; + } + _queue_update(); + return true; + } else if (name.begins_with("nodes/")) { + String typestr = name.get_slicec('/', 1); + Type type = TYPE_VERTEX; + for (int i = 0; i < TYPE_MAX; i++) { + if (typestr == type_string[i]) { + type = Type(i); + break; + } + } + + String index = name.get_slicec('/', 2); + if (index == "connections") { + + Vector<int> conns = p_value; + if (conns.size() % 4 == 0) { + for (int i = 0; i < conns.size(); i += 4) { + connect_nodes(type, conns[i + 0], conns[i + 1], conns[i + 2], conns[i + 3]); + } + } + return true; + } + + int id = index.to_int(); + String what = name.get_slicec('/', 3); + + if (what == "node") { + add_node(type, p_value, Vector2(), id); + return true; + } else if (what == "position") { + set_node_position(type, id, p_value); + return true; + } + } + return false; +} + +bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { + + String name = p_name; + if (name == "mode") { + r_ret = get_mode(); + return true; + } else if (name.begins_with("flags/")) { + StringName flag = name.get_slicec('/', 1); + r_ret = flags.has(flag); + return true; + } else if (name.begins_with("modes/")) { + String mode = name.get_slicec('/', 1); + if (modes.has(mode)) { + r_ret = modes[mode]; + } else { + r_ret = 0; + } + return true; + } else if (name.begins_with("nodes/")) { + String typestr = name.get_slicec('/', 1); + Type type = TYPE_VERTEX; + for (int i = 0; i < TYPE_MAX; i++) { + if (typestr == type_string[i]) { + type = Type(i); + break; + } + } + + String index = name.get_slicec('/', 2); + if (index == "connections") { + + Vector<int> conns; + for (const List<Connection>::Element *E = graph[type].connections.front(); E; E = E->next()) { + conns.push_back(E->get().from_node); + conns.push_back(E->get().from_port); + conns.push_back(E->get().to_node); + conns.push_back(E->get().to_port); + } + + r_ret = conns; + return true; + } + + int id = index.to_int(); + String what = name.get_slicec('/', 3); + + if (what == "node") { + r_ret = get_node(type, id); + return true; + } else if (what == "position") { + r_ret = get_node_position(type, id); + return true; + } + } + return false; +} +void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { + + //mode + p_list->push_back(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Spatial,CanvasItem,Particles")); + //render modes + + Map<String, String> blend_mode_enums; + Set<String> toggles; + + for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader_mode)).size(); i++) { + + String mode = ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader_mode))[i]; + int idx = 0; + bool in_enum = false; + while (render_mode_enums[idx].string) { + if (mode.begins_with(render_mode_enums[idx].string)) { + String begin = render_mode_enums[idx].string; + String option = mode.replace_first(begin + "_", ""); + if (!blend_mode_enums.has(begin)) { + blend_mode_enums[begin] = option; + } else { + blend_mode_enums[begin] += "," + option; + } + in_enum = true; + break; + } + idx++; + } + + if (!in_enum) { + toggles.insert(mode); + } + } + + for (Map<String, String>::Element *E = blend_mode_enums.front(); E; E = E->next()) { + + p_list->push_back(PropertyInfo(Variant::INT, "modes/" + E->key(), PROPERTY_HINT_ENUM, E->get())); + } + + for (Set<String>::Element *E = toggles.front(); E; E = E->next()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "flags/" + E->get())); + } + + for (int i = 0; i < TYPE_MAX; i++) { + for (Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { + + String prop_name = "nodes/"; + prop_name += type_string[i]; + prop_name += "/" + itos(E->key()); + + if (E->key() != NODE_ID_OUTPUT) { + + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } + p_list->push_back(PropertyInfo(Variant::POOL_INT_ARRAY, "nodes/" + String(type_string[i]) + "/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } +} + +Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview) const { + + const Ref<VisualShaderNode> vsnode = graph[type].nodes[node].node; + + //check inputs recursively first + int input_count = vsnode->get_input_port_count(); + for (int i = 0; i < input_count; i++) { + ConnectionKey ck; + ck.node = node; + ck.port = i; + + if (input_connections.has(ck)) { + int from_node = input_connections[ck]->get().from_node; + if (processed.has(from_node)) { + continue; + } + + Error err = _write_node(type, global_code, code, def_tex_params, input_connections, output_connections, from_node, processed, for_preview); + if (err) + return err; + } + } + + // then this node + + code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n"; + Vector<String> input_vars; + + input_vars.resize(vsnode->get_input_port_count()); + String *inputs = input_vars.ptrw(); + + for (int i = 0; i < input_count; i++) { + ConnectionKey ck; + ck.node = node; + ck.port = i; + + if (input_connections.has(ck)) { + //connected to something, use that output + int from_node = input_connections[ck]->get().from_node; + int from_port = input_connections[ck]->get().from_port; + + VisualShaderNode::PortType in_type = vsnode->get_input_port_type(i); + VisualShaderNode::PortType out_type = graph[type].nodes[from_node].node->get_output_port_type(from_port); + + String src_var = "n_out" + itos(from_node) + "p" + itos(from_port); + + if (in_type == out_type) { + inputs[i] = src_var; + } else if (in_type == VisualShaderNode::PORT_TYPE_SCALAR && out_type == VisualShaderNode::PORT_TYPE_VECTOR) { + inputs[i] = "dot(" + src_var + ",vec3(0.333333,0.333333,0.333333))"; + } else if (in_type == VisualShaderNode::PORT_TYPE_VECTOR && out_type == VisualShaderNode::PORT_TYPE_SCALAR) { + inputs[i] = "vec3(" + src_var + ")"; + } + } else { + + Variant defval = vsnode->get_input_port_default_value(i); + if (defval.get_type() == Variant::REAL || defval.get_type() == Variant::INT) { + float val = defval; + inputs[i] = "n_in" + itos(node) + "p" + itos(i); + code += "\tfloat " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; + } else if (defval.get_type() == Variant::VECTOR3) { + Vector3 val = defval; + inputs[i] = "n_in" + itos(node) + "p" + itos(i); + code += "\tvec3 " + inputs[i] + " = " + vformat("vec3(%.5f,%.5f,%.5f);\n", val.x, val.y, val.z); + } else if (defval.get_type() == Variant::TRANSFORM) { + Transform val = defval; + val.basis.transpose(); + inputs[i] = "n_in" + itos(node) + "p" + itos(i); + Array values; + for (int i = 0; i < 3; i++) { + values.push_back(val.basis[i].x); + values.push_back(val.basis[i].y); + values.push_back(val.basis[i].z); + } + values.push_back(val.origin.x); + values.push_back(val.origin.y); + values.push_back(val.origin.z); + bool err = false; + code += "\tmat4 " + inputs[i] + " = " + String("mat4( vec4(%.5f,%.5f,%.5f,0.0),vec4(%.5f,%.5f,%.5f,0.0),vec4(%.5f,%.5f,%.5f,0.0),vec4(%.5f,%.5f,%.5f,1.0) );\n").sprintf(values, &err); + } else { + //will go empty, node is expected to know what it is doing at this point and handle it + } + } + } + + int output_count = vsnode->get_output_port_count(); + Vector<String> output_vars; + output_vars.resize(vsnode->get_output_port_count()); + String *outputs = output_vars.ptrw(); + + for (int i = 0; i < output_count; i++) { + + outputs[i] = "n_out" + itos(node) + "p" + itos(i); + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_SCALAR: code += String() + "\tfloat " + outputs[i] + ";\n"; break; + case VisualShaderNode::PORT_TYPE_VECTOR: code += String() + "\tvec3 " + outputs[i] + ";\n"; break; + case VisualShaderNode::PORT_TYPE_TRANSFORM: code += String() + "\tmat4 " + outputs[i] + ";\n"; break; + default: {} + } + } + + Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node); + for (int i = 0; i < params.size(); i++) { + def_tex_params.push_back(params[i]); + } + + Ref<VisualShaderNodeInput> input = vsnode; + + if (input.is_valid() && for_preview) { + //handle for preview + code += input->generate_code_for_preview(type, node, inputs, outputs); + } else { + //handle normally + global_code += vsnode->generate_global(get_mode(), type, node); + code += vsnode->generate_code(get_mode(), type, node, inputs, outputs); + } + code += "\n"; // + processed.insert(node); + + return OK; +} + +void VisualShader::_update_shader() const { + if (!dirty) + return; + + dirty = false; + + StringBuilder global_code; + StringBuilder code; + Vector<VisualShader::DefaultTextureParam> default_tex_params; + static const char *shader_mode_str[Shader::MODE_MAX] = { "spatial", "canvas_item", "particles" }; + + global_code += String() + "shader_type " + shader_mode_str[shader_mode] + ";\n"; + + String render_mode; + + { + //fill render mode enums + int idx = 0; + while (render_mode_enums[idx].string) { + + if (shader_mode == render_mode_enums[idx].mode) { + + if (modes.has(render_mode_enums[idx].string)) { + + int which = modes[render_mode_enums[idx].string]; + int count = 0; + for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader_mode)).size(); i++) { + String mode = ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader_mode))[i]; + if (mode.begins_with(render_mode_enums[idx].string)) { + if (count == which) { + if (render_mode != String()) { + render_mode += ", "; + } + render_mode += mode; + break; + } + count++; + } + } + } + } + idx++; + } + + //fill render mode flags + for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader_mode)).size(); i++) { + + String mode = ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader_mode))[i]; + if (flags.has(mode)) { + if (render_mode != String()) { + render_mode += ", "; + } + render_mode += mode; + } + } + } + + if (render_mode != String()) { + + global_code += "render_mode " + render_mode + ";\n\n"; + } + + static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light" }; + + for (int i = 0; i < TYPE_MAX; i++) { + + //make it faster to go around through shader + VMap<ConnectionKey, const List<Connection>::Element *> input_connections; + VMap<ConnectionKey, const List<Connection>::Element *> output_connections; + + for (const List<Connection>::Element *E = graph[i].connections.front(); E; E = E->next()) { + ConnectionKey from_key; + from_key.node = E->get().from_node; + from_key.port = E->get().from_port; + + output_connections.insert(from_key, E); + + ConnectionKey to_key; + to_key.node = E->get().to_node; + to_key.port = E->get().to_port; + + input_connections.insert(to_key, E); + } + + code += "\nvoid " + String(func_name[i]) + "() {\n"; + + Set<int> processed; + Error err = _write_node(Type(i), global_code, code, default_tex_params, input_connections, output_connections, NODE_ID_OUTPUT, processed, false); + ERR_FAIL_COND(err != OK); + + code += "}\n"; + } + + //set code secretly + global_code += "\n\n"; + String final_code = global_code; + final_code += code; + const_cast<VisualShader *>(this)->set_code(final_code); + //print_line(final_code); + for (int i = 0; i < default_tex_params.size(); i++) { + const_cast<VisualShader *>(this)->set_default_texture_param(default_tex_params[i].name, default_tex_params[i].param); + } +} + +void VisualShader::_queue_update() { + if (dirty) { + return; + } + + dirty = true; + call_deferred("_update_shader"); +} + +void VisualShader::_input_type_changed(Type p_type, int p_id) { + //erase connections using this input, as type changed + Graph *g = &graph[p_type]; + + for (List<Connection>::Element *E = g->connections.front(); E;) { + List<Connection>::Element *N = E->next(); + if (E->get().from_node == p_id) { + g->connections.erase(E); + } + E = N; + } +} + +void VisualShader::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &VisualShader::set_mode); + + ClassDB::bind_method(D_METHOD("add_node", "type", "node", "position", "id"), &VisualShader::add_node); + ClassDB::bind_method(D_METHOD("set_node_position", "type", "id", "position"), &VisualShader::set_node_position); + + ClassDB::bind_method(D_METHOD("get_node_position", "type", "id"), &VisualShader::get_node_position); + ClassDB::bind_method(D_METHOD("get_node", "type"), &VisualShader::get_node); + + ClassDB::bind_method(D_METHOD("get_node_list", "type"), &VisualShader::get_node_list); + ClassDB::bind_method(D_METHOD("get_valid_node_id", "type"), &VisualShader::get_valid_node_id); + + ClassDB::bind_method(D_METHOD("remove_node", "type", "id"), &VisualShader::remove_node); + + ClassDB::bind_method(D_METHOD("is_node_connection", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::is_node_connection); + ClassDB::bind_method(D_METHOD("can_connect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::is_node_connection); + + ClassDB::bind_method(D_METHOD("connect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::connect_nodes); + ClassDB::bind_method(D_METHOD("disconnect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::disconnect_nodes); + + ClassDB::bind_method(D_METHOD("get_node_connections", "type"), &VisualShader::_get_node_connections); + + ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &VisualShader::set_graph_offset); + ClassDB::bind_method(D_METHOD("get_graph_offset"), &VisualShader::get_graph_offset); + + ClassDB::bind_method(D_METHOD("_queue_update"), &VisualShader::_queue_update); + ClassDB::bind_method(D_METHOD("_update_shader"), &VisualShader::_update_shader); + + ClassDB::bind_method(D_METHOD("_input_type_changed"), &VisualShader::_input_type_changed); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_graph_offset", "get_graph_offset"); + + BIND_ENUM_CONSTANT(TYPE_VERTEX); + BIND_ENUM_CONSTANT(TYPE_FRAGMENT); + BIND_ENUM_CONSTANT(TYPE_LIGHT); + BIND_ENUM_CONSTANT(TYPE_MAX); + + BIND_CONSTANT(NODE_ID_INVALID); + BIND_CONSTANT(NODE_ID_OUTPUT); +} + +VisualShader::VisualShader() { + shader_mode = Shader::MODE_SPATIAL; + + for (int i = 0; i < TYPE_MAX; i++) { + Ref<VisualShaderNodeOutput> output; + output.instance(); + output->shader_type = Type(i); + output->shader_mode = shader_mode; + graph[i].nodes[NODE_ID_OUTPUT].node = output; + graph[i].nodes[NODE_ID_OUTPUT].position = Vector2(400, 150); + } + + dirty = true; +} + +/////////////////////////////////////////////////////////// + +const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { + // Spatial, Vertex + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV2,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(VIEWPORT_SIZE, 0)" }, + + // Spatial, Fragment + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV2,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec2(POINT_COORD,0.0)" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "float(FRONT_FACING ? 1.0 : 0.0)" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(VIEWPORT_SIZE, 0.0)" }, + + // Spatial, Light + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "view", "VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_color", "LIGHT_COLOR" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "attenuation", "ATTENUATION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "transmission", "TRANSMISSION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(VIEWPORT_SIZE, 0.0)" }, + // Canvas Item, Vertex + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, + + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "extra", "EXTRA_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "light_pass", "float(AT_LIGHT_PASS ? 1.0 : 0.0)" }, + // Canvas Item, Fragment + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec2(POINT_COORD,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "light_pass", "float(AT_LIGHT_PASS ? 1.0 : 0.0)" }, + // Canvas Item, Light + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_vec", "vec3(LIGHT_VEC,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_height", "LIGHT_HEIGHT" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_color", "LIGHT_COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_alpha", "LIGHT_COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_uv", "vec3(LIGHT_UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow_color", "SHADOW_COLOR.rgb" }, + + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec2(POINT_COORD,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + // Particles, Vertex + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "restart", "float(RESTART ? 1.0 : 0.0)" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "active", "float(ACTIVE ? 1.0 : 0.0)" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "index", "float(INDEX)" }, + + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, NULL, NULL }, +}; + +const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { + + // Spatial, Fragment + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0,0.0,1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "vec3(0.0,1.0,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "vec3(1.0,0.0,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(UV,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "1.0" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(1.0,1.0, 0.0)" }, + + // Spatial, Light + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0,0.0,1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(1.0, 1.0, 0.0)" }, + // Canvas Item, Vertex + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + // Canvas Item, Fragment + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + // Canvas Item, Light + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0,0.0,1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, + + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + // Particles, Vertex + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "vec3(0.0,0.0,1.0)" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, NULL, NULL }, +}; + +int VisualShaderNodeInput::get_input_port_count() const { + + return 0; +} +VisualShaderNodeInput::PortType VisualShaderNodeInput::get_input_port_type(int p_port) const { + + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeInput::get_input_port_name(int p_port) const { + + return ""; +} + +int VisualShaderNodeInput::get_output_port_count() const { + + return 1; +} +VisualShaderNodeInput::PortType VisualShaderNodeInput::get_output_port_type(int p_port) const { + + return get_input_type_by_name(input_name); +} +String VisualShaderNodeInput::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeInput::get_caption() const { + return TTR("Input"); +} + +String VisualShaderNodeInput::generate_code_for_preview(VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + int idx = 0; + + String code; + + while (preview_ports[idx].mode != Shader::MODE_MAX) { + if (preview_ports[idx].mode == shader_mode && preview_ports[idx].shader_type == shader_type && preview_ports[idx].name == input_name) { + code = "\t" + p_output_vars[0] + " = " + preview_ports[idx].string + ";\n"; + break; + } + idx++; + } + + if (code == String()) { + switch (get_output_port_type(0)) { + case PORT_TYPE_SCALAR: { + code = "\t" + p_output_vars[0] + " = 0.0;\n"; + } break; //default (none found) is scalar + case PORT_TYPE_VECTOR: { + code = "\t" + p_output_vars[0] + " = vec3(0.0);\n"; + } break; //default (none found) is scalar + case PORT_TYPE_TRANSFORM: { + code = "\t" + p_output_vars[0] + " = mat4( vec4(1.0,0.0,0.0,0.0), vec4(0.0,1.0,0.0,0.0), vec4(0.0,0.0,1.0,0.0), vec4(0.0,0.0,0.0,1.0) );\n"; + } break; //default (none found) is scalar + } + } + + return code; +} + +String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + int idx = 0; + + String code; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type && ports[idx].name == input_name) { + code = "\t" + p_output_vars[0] + " = " + ports[idx].string + ";\n"; + break; + } + idx++; + } + + if (code == String()) { + code = "\t" + p_output_vars[0] + " = 0.0;\n"; //default (none found) is scalar + } + + return code; +} + +void VisualShaderNodeInput::set_input_name(String p_name) { + PortType prev_type = get_input_type_by_name(input_name); + input_name = p_name; + emit_changed(); + if (get_input_type_by_name(input_name) != prev_type) { + emit_signal("input_type_changed"); + } +} + +String VisualShaderNodeInput::get_input_name() const { + return input_name; +} + +VisualShaderNodeInput::PortType VisualShaderNodeInput::get_input_type_by_name(String p_name) const { + + int idx = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type && ports[idx].name == p_name) { + return ports[idx].type; + } + idx++; + } + + return PORT_TYPE_SCALAR; +} + +int VisualShaderNodeInput::get_input_index_count() const { + int idx = 0; + int count = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + count++; + } + idx++; + } + + return count; +} + +VisualShaderNodeInput::PortType VisualShaderNodeInput::get_input_index_type(int p_index) const { + int idx = 0; + int count = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + if (count == p_index) { + return ports[idx].type; + } + count++; + } + idx++; + } + + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeInput::get_input_index_name(int p_index) const { + int idx = 0; + int count = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + if (count == p_index) { + return ports[idx].name; + } + count++; + } + idx++; + } + + return ""; +} + +void VisualShaderNodeInput::_validate_property(PropertyInfo &property) const { + + if (property.name == "input_name") { + String port_list; + + int idx = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + if (port_list != String()) { + port_list += ","; + } + port_list += ports[idx].name; + } + idx++; + } + + if (port_list == "") { + port_list = TTR("None"); + } + property.hint_string = port_list; + } +} + +Vector<StringName> VisualShaderNodeInput::get_editable_properties() const { + Vector<StringName> props; + props.push_back("input_name"); + return props; +} + +void VisualShaderNodeInput::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_input_name", "name"), &VisualShaderNodeInput::set_input_name); + ClassDB::bind_method(D_METHOD("get_input_name"), &VisualShaderNodeInput::get_input_name); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "input_name", PROPERTY_HINT_ENUM, ""), "set_input_name", "get_input_name"); + ADD_SIGNAL(MethodInfo("input_type_changed")); +} +VisualShaderNodeInput::VisualShaderNodeInput() { + input_name = "[None]"; + // changed when set + shader_type = VisualShader::TYPE_MAX; + shader_mode = Shader::MODE_MAX; +} + +//////////////////////////////////////////// + +const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { + // Spatial, Vertex + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "UV:xy" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "UV2:xy" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + // Spatial, Fragment + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "metallic", "METALLIC" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "specular", "SPECULAR" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "emission", "EMISSION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "ao", "AO" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normalmap", "NORMALMAP" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "normalmap_depth", "NORMALMAP_DEPTH" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "rim", "RIM" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "rim_tint", "RIM_TINT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "clearcoat", "CLEARCOAT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "clearcoat_gloss", "CLEARCOAT_GLOSS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "anisotropy", "ANISOTROPY" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "anisotropy_flow", "ANISOTROPY_FLOW:xy" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "subsurf_scatter", "SSS_STRENGTH" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "transmission", "TRANSMISSION" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha_scissor", "ALPHA_SCISSOR" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "ao_light_affect", "AO_LIGHT_AFFECT" }, + + // Spatial, Light + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "diffuse", "DIFFUSE_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular", "SPECULAR_LIGHT" }, + // Canvas Item, Vertex + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX:xy" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "UV:xy" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + // Canvas Item, Fragment + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normalmap", "NORMALMAP" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "normalmap_depth", "NORMALMAP_DEPTH" }, + // Canvas Item, Light + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.rgb" }, + // Particles, Vertex + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, NULL, NULL }, +}; + +int VisualShaderNodeOutput::get_input_port_count() const { + + int idx = 0; + int count = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + count++; + } + idx++; + } + + return count; +} + +VisualShaderNodeOutput::PortType VisualShaderNodeOutput::get_input_port_type(int p_port) const { + + int idx = 0; + int count = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + if (count == p_port) { + return ports[idx].type; + } + count++; + } + idx++; + } + + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeOutput::get_input_port_name(int p_port) const { + + int idx = 0; + int count = 0; + + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + if (count == p_port) { + return String(ports[idx].name).capitalize(); + } + count++; + } + idx++; + } + + return String(); +} + +Variant VisualShaderNodeOutput::get_input_port_default_value(int p_port) const { + return Variant(); +} + +int VisualShaderNodeOutput::get_output_port_count() const { + + return 0; +} +VisualShaderNodeOutput::PortType VisualShaderNodeOutput::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeOutput::get_output_port_name(int p_port) const { + return String(); +} + +bool VisualShaderNodeOutput::is_port_separator(int p_index) const { + + if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_FRAGMENT) { + String name = get_input_port_name(p_index); + return (name == "Normal" || name == "Rim" || name == "Alpha Scissor"); + } + return false; +} + +String VisualShaderNodeOutput::get_caption() const { + return TTR("Output"); +} + +String VisualShaderNodeOutput::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + int idx = 0; + int count = 0; + + String code; + while (ports[idx].mode != Shader::MODE_MAX) { + if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { + + if (p_input_vars[count] != String()) { + String s = ports[idx].string; + if (s.find(":") != -1) { + code += "\t" + s.get_slicec(':', 0) + " = " + p_input_vars[count] + "." + s.get_slicec(':', 1) + ";\n"; + } else { + code += "\t" + s + " = " + p_input_vars[count] + ";\n"; + } + } + count++; + } + idx++; + } + + return code; +} + +VisualShaderNodeOutput::VisualShaderNodeOutput() { +} + +/////////////////////////// + +void VisualShaderNodeUniform::set_uniform_name(const String &p_name) { + uniform_name = p_name; + emit_signal("name_changed"); + emit_changed(); +} + +String VisualShaderNodeUniform::get_uniform_name() const { + return uniform_name; +} + +void VisualShaderNodeUniform::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_uniform_name", "name"), &VisualShaderNodeUniform::set_uniform_name); + ClassDB::bind_method(D_METHOD("get_uniform_name"), &VisualShaderNodeUniform::get_uniform_name); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "uniform_name"), "set_uniform_name", "get_uniform_name"); +} + +VisualShaderNodeUniform::VisualShaderNodeUniform() { +} diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h new file mode 100644 index 0000000000..6ff1c9a9fe --- /dev/null +++ b/scene/resources/visual_shader.h @@ -0,0 +1,284 @@ +#ifndef VISUAL_SHADER_H +#define VISUAL_SHADER_H + +#include "scene/resources/shader.h" +#include "string_builder.h" + +class VisualShaderNodeUniform; +class VisualShaderNode; + +class VisualShader : public Shader { + GDCLASS(VisualShader, Shader) +public: + enum Type { + TYPE_VERTEX, + TYPE_FRAGMENT, + TYPE_LIGHT, + TYPE_MAX + }; + + struct Connection { + int from_node; + int from_port; + int to_node; + int to_port; + }; + + struct DefaultTextureParam { + StringName name; + Ref<Texture> param; + }; + +private: + struct Node { + Ref<VisualShaderNode> node; + Vector2 position; + }; + + struct Graph { + Map<int, Node> nodes; + List<Connection> connections; + } graph[TYPE_MAX]; + + Shader::Mode shader_mode; + + Array _get_node_connections(Type p_type) const; + + Vector2 graph_offset; + + struct RenderModeEnums { + Shader::Mode mode; + const char *string; + }; + + HashMap<String, int> modes; + Set<StringName> flags; + + static RenderModeEnums render_mode_enums[]; + + volatile mutable bool dirty; + void _queue_update(); + + union ConnectionKey { + + struct { + uint64_t node : 32; + uint64_t port : 32; + }; + uint64_t key; + bool operator<(const ConnectionKey &p_key) const { + return key < p_key.key; + } + }; + + Error _write_node(Type p_type, StringBuilder &global_code, StringBuilder &code, Vector<DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview) const; + + void _input_type_changed(Type p_type, int p_id); + +protected: + virtual void _update_shader() const; + static void _bind_methods(); + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + enum { + NODE_ID_INVALID = -1, + NODE_ID_OUTPUT = 0, + }; + + void add_node(Type p_type, const Ref<VisualShaderNode> &p_node, const Vector2 &p_position, int p_id); + void set_node_position(Type p_type, int p_id, const Vector2 &p_position); + + Vector2 get_node_position(Type p_type, int p_id) const; + Ref<VisualShaderNode> get_node(Type p_type, int p_id) const; + + Vector<int> get_node_list(Type p_type) const; + int get_valid_node_id(Type p_type) const; + + int find_node_id(Type p_type, const Ref<VisualShaderNode> &p_node) const; + void remove_node(Type p_type, int p_id); + + bool is_node_connection(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const; + bool can_connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const; + Error connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); + void disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); + + void get_node_connections(Type p_type, List<Connection> *r_connections) const; + + void set_mode(Mode p_mode); + virtual Mode get_mode() const; + + void set_graph_offset(const Vector2 &p_offset); + Vector2 get_graph_offset() const; + + String generate_preview_shader(Type p_type, int p_node, int p_port, Vector<DefaultTextureParam> &r_default_tex_params) const; + + String validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const; + + VisualShader(); +}; + +VARIANT_ENUM_CAST(VisualShader::Type) +/// +/// +/// + +class VisualShaderNode : public Resource { + GDCLASS(VisualShaderNode, Resource) + + int port_preview; + + Map<int, Variant> default_input_values; + + Array _get_default_input_values() const; + void _set_default_input_values(const Array &p_values); + +protected: + static void _bind_methods(); + +public: + enum PortType { + PORT_TYPE_SCALAR, + PORT_TYPE_VECTOR, + PORT_TYPE_TRANSFORM, + }; + + virtual String get_caption() const = 0; + + virtual int get_input_port_count() const = 0; + virtual PortType get_input_port_type(int p_port) const = 0; + virtual String get_input_port_name(int p_port) const = 0; + + void set_input_port_default_value(int p_port, const Variant &p_value); + Variant get_input_port_default_value(int p_port) const; // if NIL (default if node does not set anything) is returned, it means no default value is wanted if disconnected, thus no input var must be supplied (empty string will be supplied) + + virtual int get_output_port_count() const = 0; + virtual PortType get_output_port_type(int p_port) const = 0; + virtual String get_output_port_name(int p_port) const = 0; + + void set_output_port_for_preview(int p_index); + int get_output_port_for_preview() const; + + virtual bool is_port_separator(int p_index) const; + + virtual Vector<StringName> get_editable_properties() const; + + virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const = 0; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + virtual String get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const; + + VisualShaderNode(); +}; +///// + +class VisualShaderNodeInput : public VisualShaderNode { + GDCLASS(VisualShaderNodeInput, VisualShaderNode) + + friend class VisualShader; + VisualShader::Type shader_type; + Shader::Mode shader_mode; + + struct Port { + Shader::Mode mode; + VisualShader::Type shader_type; + PortType type; + const char *name; + const char *string; + }; + + static const Port ports[]; + static const Port preview_ports[]; + + String input_name; + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; + +public: + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String get_caption() const; + + virtual String generate_code_for_preview(VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; + + void set_input_name(String p_name); + String get_input_name() const; + + int get_input_index_count() const; + PortType get_input_index_type(int p_index) const; + String get_input_index_name(int p_index) const; + + PortType get_input_type_by_name(String p_name) const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeInput(); +}; + +/// + +class VisualShaderNodeOutput : public VisualShaderNode { + GDCLASS(VisualShaderNodeOutput, VisualShaderNode) +public: + friend class VisualShader; + VisualShader::Type shader_type; + Shader::Mode shader_mode; + + struct Port { + Shader::Mode mode; + VisualShader::Type shader_type; + PortType type; + const char *name; + const char *string; + }; + + static const Port ports[]; + +public: + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + Variant get_input_port_default_value(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual bool is_port_separator(int p_index) const; + + virtual String get_caption() const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; + + VisualShaderNodeOutput(); +}; + +class VisualShaderNodeUniform : public VisualShaderNode { + GDCLASS(VisualShaderNodeUniform, VisualShaderNode) + + String uniform_name; + +protected: + static void _bind_methods(); + +public: + void set_uniform_name(const String &p_name); + String get_uniform_name() const; + + VisualShaderNodeUniform(); +}; + +#endif // VISUAL_SHADER_H diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp new file mode 100644 index 0000000000..98ecdbdf30 --- /dev/null +++ b/scene/resources/visual_shader_nodes.cpp @@ -0,0 +1,1896 @@ +#include "visual_shader_nodes.h" +////////////// Scalar + +String VisualShaderNodeScalarConstant::get_caption() const { + return "Scalar"; +} + +int VisualShaderNodeScalarConstant::get_input_port_count() const { + return 0; +} +VisualShaderNodeScalarConstant::PortType VisualShaderNodeScalarConstant::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarConstant::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeScalarConstant::get_output_port_count() const { + return 1; +} +VisualShaderNodeScalarConstant::PortType VisualShaderNodeScalarConstant::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarConstant::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeScalarConstant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = " + vformat("%.6f", constant) + ";\n"; +} + +void VisualShaderNodeScalarConstant::set_constant(float p_value) { + + constant = p_value; + emit_changed(); +} + +float VisualShaderNodeScalarConstant::get_constant() const { + + return constant; +} + +Vector<StringName> VisualShaderNodeScalarConstant::get_editable_properties() const { + Vector<StringName> props; + props.push_back("constant"); + return props; +} + +void VisualShaderNodeScalarConstant::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeScalarConstant::set_constant); + ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeScalarConstant::get_constant); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "constant"), "set_constant", "get_constant"); +} + +VisualShaderNodeScalarConstant::VisualShaderNodeScalarConstant() { + constant = 0; +} + +////////////// Color + +String VisualShaderNodeColorConstant::get_caption() const { + return "Color"; +} + +int VisualShaderNodeColorConstant::get_input_port_count() const { + return 0; +} +VisualShaderNodeColorConstant::PortType VisualShaderNodeColorConstant::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeColorConstant::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeColorConstant::get_output_port_count() const { + return 2; +} +VisualShaderNodeColorConstant::PortType VisualShaderNodeColorConstant::get_output_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeColorConstant::get_output_port_name(int p_port) const { + return p_port == 0 ? "" : "alpha"; //no output port means the editor will be used as port +} + +String VisualShaderNodeColorConstant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + String code; + code += "\t" + p_output_vars[0] + " = " + vformat("vec3(%.6f,%.6f,%.6f)", constant.r, constant.g, constant.b) + ";\n"; + code += "\t" + p_output_vars[1] + " = " + vformat("%.6f", constant.a) + ";\n"; + + return code; +} + +void VisualShaderNodeColorConstant::set_constant(Color p_value) { + + constant = p_value; + emit_changed(); +} + +Color VisualShaderNodeColorConstant::get_constant() const { + + return constant; +} + +Vector<StringName> VisualShaderNodeColorConstant::get_editable_properties() const { + Vector<StringName> props; + props.push_back("constant"); + return props; +} + +void VisualShaderNodeColorConstant::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeColorConstant::set_constant); + ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeColorConstant::get_constant); + + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "constant"), "set_constant", "get_constant"); +} + +VisualShaderNodeColorConstant::VisualShaderNodeColorConstant() { + constant = Color(1, 1, 1, 1); +} + +////////////// Vector + +String VisualShaderNodeVec3Constant::get_caption() const { + return "Vector"; +} + +int VisualShaderNodeVec3Constant::get_input_port_count() const { + return 0; +} +VisualShaderNodeVec3Constant::PortType VisualShaderNodeVec3Constant::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVec3Constant::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeVec3Constant::get_output_port_count() const { + return 1; +} +VisualShaderNodeVec3Constant::PortType VisualShaderNodeVec3Constant::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVec3Constant::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeVec3Constant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = " + vformat("vec3(%.6f,%.6f,%.6f)", constant.x, constant.y, constant.z) + ";\n"; +} + +void VisualShaderNodeVec3Constant::set_constant(Vector3 p_value) { + + constant = p_value; + emit_changed(); +} + +Vector3 VisualShaderNodeVec3Constant::get_constant() const { + + return constant; +} + +Vector<StringName> VisualShaderNodeVec3Constant::get_editable_properties() const { + Vector<StringName> props; + props.push_back("constant"); + return props; +} + +void VisualShaderNodeVec3Constant::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeVec3Constant::set_constant); + ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeVec3Constant::get_constant); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant"), "set_constant", "get_constant"); +} + +VisualShaderNodeVec3Constant::VisualShaderNodeVec3Constant() { +} + +////////////// Transform + +String VisualShaderNodeTransformConstant::get_caption() const { + return "Transform"; +} + +int VisualShaderNodeTransformConstant::get_input_port_count() const { + return 0; +} +VisualShaderNodeTransformConstant::PortType VisualShaderNodeTransformConstant::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeTransformConstant::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeTransformConstant::get_output_port_count() const { + return 1; +} +VisualShaderNodeTransformConstant::PortType VisualShaderNodeTransformConstant::get_output_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} +String VisualShaderNodeTransformConstant::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeTransformConstant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + Transform t = constant; + t.basis.transpose(); + + String code = "\t" + p_output_vars[0] + " = mat4("; + code += vformat("vec4(%.6f,%.6f,%.6f,0.0),", t.basis[0].x, t.basis[0].y, t.basis[0].z); + code += vformat("vec4(%.6f,%.6f,%.6f,0.0),", t.basis[1].x, t.basis[1].y, t.basis[1].z); + code += vformat("vec4(%.6f,%.6f,%.6f,0.0),", t.basis[2].x, t.basis[2].y, t.basis[2].z); + code += vformat("vec4(%.6f,%.6f,%.6f,1.0) );\n", t.origin.x, t.origin.y, t.origin.z); + return code; +} + +void VisualShaderNodeTransformConstant::set_constant(Transform p_value) { + + constant = p_value; + emit_changed(); +} + +Transform VisualShaderNodeTransformConstant::get_constant() const { + + return constant; +} + +Vector<StringName> VisualShaderNodeTransformConstant::get_editable_properties() const { + Vector<StringName> props; + props.push_back("constant"); + return props; +} + +void VisualShaderNodeTransformConstant::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeTransformConstant::set_constant); + ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeTransformConstant::get_constant); + + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "constant"), "set_constant", "get_constant"); +} + +VisualShaderNodeTransformConstant::VisualShaderNodeTransformConstant() { +} + +////////////// Texture + +String VisualShaderNodeTexture::get_caption() const { + return "Texture"; +} + +int VisualShaderNodeTexture::get_input_port_count() const { + return 2; +} +VisualShaderNodeTexture::PortType VisualShaderNodeTexture::get_input_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeTexture::get_input_port_name(int p_port) const { + return p_port == 0 ? "uv" : "lod"; +} + +int VisualShaderNodeTexture::get_output_port_count() const { + return 2; +} +VisualShaderNodeTexture::PortType VisualShaderNodeTexture::get_output_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeTexture::get_output_port_name(int p_port) const { + return p_port == 0 ? "rgb" : "alpha"; +} + +static String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) { + + static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt" }; + return p_name + "_" + String(typepf[p_type]) + "_" + itos(p_id); +} + +Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "tex"); + dtp.param = texture; + Vector<VisualShader::DefaultTextureParam> ret; + ret.push_back(dtp); + return ret; +} + +String VisualShaderNodeTexture::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + + if (source == SOURCE_TEXTURE) { + + String u = "uniform sampler2D " + make_unique_id(p_type, p_id, "tex"); + switch (texture_type) { + case TYPE_DATA: break; + case TYPE_COLOR: u += " : hint_color"; break; + case TYPE_NORMALMAP: u += " : hint_normal"; break; + } + return u + ";"; + } + + return String(); +} + +String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + if (source == SOURCE_TEXTURE) { + String id = make_unique_id(p_type, p_id, "tex"); + String code; + if (p_input_vars[0] == String()) { //none bound, do nothing + + code += "\tvec4 " + id + "_read = vec4(0.0);\n"; + + } else if (p_input_vars[1] == String()) { + //no lod + code += "\tvec4 " + id + "_read = texture( " + id + " , " + p_input_vars[0] + ".xy );\n"; + } else { + code += "\tvec4 " + id + "_read = textureLod( " + id + " , " + p_input_vars[0] + ".xy , " + p_input_vars[1] + " );\n"; + } + + code += "\t" + p_output_vars[0] + " = " + id + "_read.rgb;\n"; + code += "\t" + p_output_vars[1] + " = " + id + "_read.a;\n"; + return code; + } + + if (source == SOURCE_SCREEN && (p_mode == Shader::MODE_SPATIAL || p_mode == Shader::MODE_CANVAS_ITEM) && p_type == VisualShader::TYPE_FRAGMENT) { + + String code = "\t{\n"; + if (p_input_vars[0] == String()) { //none bound, do nothing + + code += "\t\tvec4 _tex_read = vec4(0.0);\n"; + + } else if (p_input_vars[1] == String()) { + //no lod + code += "\t\tvec4 _tex_read = textureLod( SCREEN_TEXTURE , " + p_input_vars[0] + ".xy, 0.0 );\n"; + } else { + code += "\t\tvec4 _tex_read = textureLod( SCREEN_TEXTURE , " + p_input_vars[0] + ".xy , " + p_input_vars[1] + " );\n"; + } + + code += "\t\t" + p_output_vars[0] + " = _tex_read.rgb;\n"; + code += "\t\t" + p_output_vars[1] + " = _tex_read.a;\n"; + code += "\t}\n"; + return code; + } + + if (source == SOURCE_2D_TEXTURE && p_mode == Shader::MODE_CANVAS_ITEM && p_type == VisualShader::TYPE_FRAGMENT) { + + String code = "\t{\n"; + if (p_input_vars[0] == String()) { //none bound, do nothing + + code += "\t\tvec4 _tex_read = vec4(0.0);\n"; + + } else if (p_input_vars[1] == String()) { + //no lod + code += "\t\tvec4 _tex_read = texture( TEXTURE , " + p_input_vars[0] + ".xy );\n"; + } else { + code += "\t\tvec4 _tex_read = textureLod( TEXTURE , " + p_input_vars[0] + ".xy , " + p_input_vars[1] + " );\n"; + } + + code += "\t\t" + p_output_vars[0] + " = _tex_read.rgb;\n"; + code += "\t\t" + p_output_vars[1] + " = _tex_read.a;\n"; + code += "\t}\n"; + return code; + } + + if (source == SOURCE_2D_NORMAL && p_mode == Shader::MODE_CANVAS_ITEM && p_type == VisualShader::TYPE_FRAGMENT) { + + String code = "\t{\n"; + if (p_input_vars[0] == String()) { //none bound, do nothing + + code += "\t\tvec4 _tex_read = vec4(0.0);\n"; + + } else if (p_input_vars[1] == String()) { + //no lod + code += "\t\tvec4 _tex_read = texture( NORMAL_TEXTURE , " + p_input_vars[0] + ".xy );\n"; + } else { + code += "\t\tvec4 _tex_read = textureLod( NORMAL_TEXTURE , " + p_input_vars[0] + ".xy , " + p_input_vars[1] + " );\n"; + } + + code += "\t\t" + p_output_vars[0] + " = _tex_read.rgb;\n"; + code += "\t\t" + p_output_vars[1] + " = _tex_read.a;\n"; + code += "\t}\n"; + return code; + } + + //none + String code; + code += "\t" + p_output_vars[0] + " = vec3(0.0);\n"; + code += "\t" + p_output_vars[1] + " = 1.0;\n"; + return code; +} + +void VisualShaderNodeTexture::set_source(Source p_source) { + source = p_source; + emit_changed(); + emit_signal("editor_refresh_request"); +} + +VisualShaderNodeTexture::Source VisualShaderNodeTexture::get_source() const { + return source; +} + +void VisualShaderNodeTexture::set_texture(Ref<Texture> p_value) { + + texture = p_value; + emit_changed(); +} + +Ref<Texture> VisualShaderNodeTexture::get_texture() const { + + return texture; +} + +void VisualShaderNodeTexture::set_texture_type(TextureType p_type) { + texture_type = p_type; + emit_changed(); +} + +VisualShaderNodeTexture::TextureType VisualShaderNodeTexture::get_texture_type() const { + return texture_type; +} + +Vector<StringName> VisualShaderNodeTexture::get_editable_properties() const { + Vector<StringName> props; + props.push_back("source"); + if (source == SOURCE_TEXTURE) { + props.push_back("texture"); + props.push_back("texture_type"); + } + return props; +} + +String VisualShaderNodeTexture::get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const { + + if (source == SOURCE_TEXTURE) { + return String(); // all good + } + + if (source == SOURCE_SCREEN && (p_mode == Shader::MODE_SPATIAL || p_mode == Shader::MODE_CANVAS_ITEM) && p_type == VisualShader::TYPE_FRAGMENT) { + + return String(); // all good + } + + if (source == SOURCE_2D_TEXTURE && p_mode == Shader::MODE_CANVAS_ITEM && p_type == VisualShader::TYPE_FRAGMENT) { + + return String(); // all good + } + + if (source == SOURCE_2D_NORMAL && p_mode == Shader::MODE_CANVAS_ITEM) { + + return String(); // all good + } + + return TTR("Invalid source for shader."); +} + +void VisualShaderNodeTexture::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_source", "value"), &VisualShaderNodeTexture::set_source); + ClassDB::bind_method(D_METHOD("get_source"), &VisualShaderNodeTexture::get_source); + + ClassDB::bind_method(D_METHOD("set_texture", "value"), &VisualShaderNodeTexture::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &VisualShaderNodeTexture::get_texture); + + ClassDB::bind_method(D_METHOD("set_texture_type", "value"), &VisualShaderNodeTexture::set_texture_type); + ClassDB::bind_method(D_METHOD("get_texture_type"), &VisualShaderNodeTexture::get_texture_type); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "source", PROPERTY_HINT_ENUM, "Texture,Screen,Texture2D,NormalMap2D"), "set_source", "get_source"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normalmap"), "set_texture_type", "get_texture_type"); + + BIND_ENUM_CONSTANT(SOURCE_TEXTURE); + BIND_ENUM_CONSTANT(SOURCE_SCREEN); + BIND_ENUM_CONSTANT(SOURCE_2D_TEXTURE); + BIND_ENUM_CONSTANT(SOURCE_2D_NORMAL); + BIND_ENUM_CONSTANT(TYPE_DATA); + BIND_ENUM_CONSTANT(TYPE_COLOR); + BIND_ENUM_CONSTANT(TYPE_NORMALMAP); +} + +VisualShaderNodeTexture::VisualShaderNodeTexture() { + texture_type = TYPE_DATA; + source = SOURCE_TEXTURE; +} + +////////////// CubeMap + +String VisualShaderNodeCubeMap::get_caption() const { + return "CubeMap"; +} + +int VisualShaderNodeCubeMap::get_input_port_count() const { + return 2; +} +VisualShaderNodeCubeMap::PortType VisualShaderNodeCubeMap::get_input_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeCubeMap::get_input_port_name(int p_port) const { + return p_port == 0 ? "uv" : "lod"; +} + +int VisualShaderNodeCubeMap::get_output_port_count() const { + return 2; +} +VisualShaderNodeCubeMap::PortType VisualShaderNodeCubeMap::get_output_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeCubeMap::get_output_port_name(int p_port) const { + return p_port == 0 ? "rgb" : "alpha"; +} + +Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCubeMap::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "cube"); + dtp.param = cube_map; + Vector<VisualShader::DefaultTextureParam> ret; + ret.push_back(dtp); + return ret; +} + +String VisualShaderNodeCubeMap::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + + String u = "uniform sampler2DCube " + make_unique_id(p_type, p_id, "cube"); + switch (texture_type) { + case TYPE_DATA: break; + case TYPE_COLOR: u += " : hint_color"; break; + case TYPE_NORMALMAP: u += " : hint_normal"; break; + } + return u + ";"; +} + +String VisualShaderNodeCubeMap::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + String id = make_unique_id(p_type, p_id, "cube"); + String code; + if (p_input_vars[0] == String()) { //none bound, do nothing + + code += "\tvec4 " + id + "_read = vec4(0.0);\n"; + + } else if (p_input_vars[1] == String()) { + //no lod + code += "\tvec4 " + id + "_read = texture( " + id + " , " + p_input_vars[0] + " );\n"; + } else { + code += "\tvec4 " + id + "_read = textureLod( " + id + " , " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; + } + + code += "\t" + p_output_vars[0] + " = " + id + "_read.rgb;\n"; + code += "\t" + p_output_vars[1] + " = " + id + "_read.a;\n"; + return code; +} + +void VisualShaderNodeCubeMap::set_cube_map(Ref<CubeMap> p_value) { + + cube_map = p_value; + emit_changed(); +} + +Ref<CubeMap> VisualShaderNodeCubeMap::get_cube_map() const { + + return cube_map; +} + +void VisualShaderNodeCubeMap::set_texture_type(TextureType p_type) { + texture_type = p_type; + emit_changed(); +} + +VisualShaderNodeCubeMap::TextureType VisualShaderNodeCubeMap::get_texture_type() const { + return texture_type; +} + +Vector<StringName> VisualShaderNodeCubeMap::get_editable_properties() const { + Vector<StringName> props; + props.push_back("cube_map"); + props.push_back("texture_type"); + return props; +} + +void VisualShaderNodeCubeMap::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_cube_map", "value"), &VisualShaderNodeCubeMap::set_cube_map); + ClassDB::bind_method(D_METHOD("get_cube_map"), &VisualShaderNodeCubeMap::get_cube_map); + + ClassDB::bind_method(D_METHOD("set_texture_type", "value"), &VisualShaderNodeCubeMap::set_texture_type); + ClassDB::bind_method(D_METHOD("get_texture_type"), &VisualShaderNodeCubeMap::get_texture_type); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "cube_map", PROPERTY_HINT_RESOURCE_TYPE, "CubeMap"), "set_cube_map", "get_cube_map"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normalmap"), "set_texture_type", "get_texture_type"); + + BIND_ENUM_CONSTANT(TYPE_DATA); + BIND_ENUM_CONSTANT(TYPE_COLOR); + BIND_ENUM_CONSTANT(TYPE_NORMALMAP); +} + +VisualShaderNodeCubeMap::VisualShaderNodeCubeMap() { + texture_type = TYPE_DATA; +} +////////////// Scalar Op + +String VisualShaderNodeScalarOp::get_caption() const { + return "ScalarOp"; +} + +int VisualShaderNodeScalarOp::get_input_port_count() const { + return 2; +} +VisualShaderNodeScalarOp::PortType VisualShaderNodeScalarOp::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarOp::get_input_port_name(int p_port) const { + return p_port == 0 ? "a" : "b"; +} + +int VisualShaderNodeScalarOp::get_output_port_count() const { + return 1; +} +VisualShaderNodeScalarOp::PortType VisualShaderNodeScalarOp::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarOp::get_output_port_name(int p_port) const { + return "op"; //no output port means the editor will be used as port +} + +String VisualShaderNodeScalarOp::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + String code = "\t" + p_output_vars[0] + " = "; + switch (op) { + + case OP_ADD: code += p_input_vars[0] + " + " + p_input_vars[1] + ";\n"; break; + case OP_SUB: code += p_input_vars[0] + " - " + p_input_vars[1] + ";\n"; break; + case OP_MUL: code += p_input_vars[0] + " * " + p_input_vars[1] + ";\n"; break; + case OP_DIV: code += p_input_vars[0] + " / " + p_input_vars[1] + ";\n"; break; + case OP_MOD: code += "mod( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_POW: code += "pow( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_MAX: code += "max( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_MIN: code += "min( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_ATAN2: code += "atan( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + } + + return code; +} + +void VisualShaderNodeScalarOp::set_operator(Operator p_op) { + + op = p_op; + emit_changed(); +} + +VisualShaderNodeScalarOp::Operator VisualShaderNodeScalarOp::get_operator() const { + + return op; +} + +Vector<StringName> VisualShaderNodeScalarOp::get_editable_properties() const { + Vector<StringName> props; + props.push_back("operator"); + return props; +} + +void VisualShaderNodeScalarOp::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeScalarOp::set_operator); + ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeScalarOp::get_operator); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Atan2"), "set_operator", "get_operator"); + + BIND_ENUM_CONSTANT(OP_ADD); + BIND_ENUM_CONSTANT(OP_SUB); + BIND_ENUM_CONSTANT(OP_MUL); + BIND_ENUM_CONSTANT(OP_DIV); + BIND_ENUM_CONSTANT(OP_MOD); + BIND_ENUM_CONSTANT(OP_POW); + BIND_ENUM_CONSTANT(OP_MAX); + BIND_ENUM_CONSTANT(OP_MIN); + BIND_ENUM_CONSTANT(OP_ATAN2); +} + +VisualShaderNodeScalarOp::VisualShaderNodeScalarOp() { + op = OP_ADD; + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); +} + +////////////// Vector Op + +String VisualShaderNodeVectorOp::get_caption() const { + return "VectorOp"; +} + +int VisualShaderNodeVectorOp::get_input_port_count() const { + return 2; +} +VisualShaderNodeVectorOp::PortType VisualShaderNodeVectorOp::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorOp::get_input_port_name(int p_port) const { + return p_port == 0 ? "a" : "b"; +} + +int VisualShaderNodeVectorOp::get_output_port_count() const { + return 1; +} +VisualShaderNodeVectorOp::PortType VisualShaderNodeVectorOp::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorOp::get_output_port_name(int p_port) const { + return "op"; //no output port means the editor will be used as port +} + +String VisualShaderNodeVectorOp::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + String code = "\t" + p_output_vars[0] + " = "; + switch (op) { + + case OP_ADD: code += p_input_vars[0] + " + " + p_input_vars[1] + ";\n"; break; + case OP_SUB: code += p_input_vars[0] + " - " + p_input_vars[1] + ";\n"; break; + case OP_MUL: code += p_input_vars[0] + " * " + p_input_vars[1] + ";\n"; break; + case OP_DIV: code += p_input_vars[0] + " / " + p_input_vars[1] + ";\n"; break; + case OP_MOD: code += "mod( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_POW: code += "pow( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_MAX: code += "max( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_MIN: code += "min( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_CROSS: code += "cross( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + } + + return code; +} + +void VisualShaderNodeVectorOp::set_operator(Operator p_op) { + + op = p_op; + emit_changed(); +} + +VisualShaderNodeVectorOp::Operator VisualShaderNodeVectorOp::get_operator() const { + + return op; +} + +Vector<StringName> VisualShaderNodeVectorOp::get_editable_properties() const { + Vector<StringName> props; + props.push_back("operator"); + return props; +} + +void VisualShaderNodeVectorOp::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeVectorOp::set_operator); + ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeVectorOp::get_operator); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Cross"), "set_operator", "get_operator"); + + BIND_ENUM_CONSTANT(OP_ADD); + BIND_ENUM_CONSTANT(OP_SUB); + BIND_ENUM_CONSTANT(OP_MUL); + BIND_ENUM_CONSTANT(OP_DIV); + BIND_ENUM_CONSTANT(OP_MOD); + BIND_ENUM_CONSTANT(OP_POW); + BIND_ENUM_CONSTANT(OP_MAX); + BIND_ENUM_CONSTANT(OP_MIN); + BIND_ENUM_CONSTANT(OP_CROSS); +} + +VisualShaderNodeVectorOp::VisualShaderNodeVectorOp() { + op = OP_ADD; + set_input_port_default_value(0, Vector3()); + set_input_port_default_value(1, Vector3()); +} + +////////////// Color Op + +String VisualShaderNodeColorOp::get_caption() const { + return "ColorOp"; +} + +int VisualShaderNodeColorOp::get_input_port_count() const { + return 2; +} +VisualShaderNodeColorOp::PortType VisualShaderNodeColorOp::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeColorOp::get_input_port_name(int p_port) const { + return p_port == 0 ? "a" : "b"; +} + +int VisualShaderNodeColorOp::get_output_port_count() const { + return 1; +} +VisualShaderNodeColorOp::PortType VisualShaderNodeColorOp::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeColorOp::get_output_port_name(int p_port) const { + return "op"; //no output port means the editor will be used as port +} + +String VisualShaderNodeColorOp::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + String code; + static const char *axisn[3] = { "x", "y", "z" }; + switch (op) { + case OP_SCREEN: { + + code += "\t" + p_output_vars[0] + "=vec3(1.0)-(vec3(1.0)-" + p_input_vars[0] + ")*(vec3(1.0)-" + p_input_vars[1] + ");\n"; + } break; + case OP_DIFFERENCE: { + + code += "\t" + p_output_vars[0] + "=abs(" + p_input_vars[0] + "-" + p_input_vars[1] + ");\n"; + } break; + case OP_DARKEN: { + + code += "\t" + p_output_vars[0] + "=min(" + p_input_vars[0] + "," + p_input_vars[1] + ");\n"; + } break; + case OP_LIGHTEN: { + + code += "\t" + p_output_vars[0] + "=max(" + p_input_vars[0] + "," + p_input_vars[1] + ");\n"; + + } break; + case OP_OVERLAY: { + + for (int i = 0; i < 3; i++) { + code += "\t{\n"; + code += "\t\tfloat base=" + p_input_vars[0] + "." + axisn[i] + ";\n"; + code += "\t\tfloat blend=" + p_input_vars[1] + "." + axisn[i] + ";\n"; + code += "\t\tif (base < 0.5) {\n"; + code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = 2.0 * base * blend;\n"; + code += "\t\t} else {\n"; + code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = 1.0 - 2.0 * (1.0 - blend) * (1.0 - base);\n"; + code += "\t\t}\n"; + code += "\t}\n"; + } + + } break; + case OP_DODGE: { + + code += "\t" + p_output_vars[0] + "=(" + p_input_vars[0] + ")/(vec3(1.0)-" + p_input_vars[1] + ");\n"; + + } break; + case OP_BURN: { + + code += "\t" + p_output_vars[0] + "=vec3(1.0)-(vec3(1.0)-" + p_input_vars[0] + ")/(" + p_input_vars[1] + ");\n"; + } break; + case OP_SOFT_LIGHT: { + + for (int i = 0; i < 3; i++) { + code += "\t{\n"; + code += "\t\tfloat base=" + p_input_vars[0] + "." + axisn[i] + ";\n"; + code += "\t\tfloat blend=" + p_input_vars[1] + "." + axisn[i] + ";\n"; + code += "\t\tif (base < 0.5) {\n"; + code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (base * (blend+0.5));\n"; + code += "\t\t} else {\n"; + code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (1.0 - (1.0-base) * (1.0-(blend-0.5)));\n"; + code += "\t\t}\n"; + code += "\t}\n"; + } + + } break; + case OP_HARD_LIGHT: { + + for (int i = 0; i < 3; i++) { + code += "\t{\n"; + code += "\t\tfloat base=" + p_input_vars[0] + "." + axisn[i] + ";\n"; + code += "\t\tfloat blend=" + p_input_vars[1] + "." + axisn[i] + ";\n"; + code += "\t\tif (base < 0.5) {\n"; + code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (base * (2.0*blend));\n"; + code += "\t\t} else {\n"; + code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (1.0 - (1.0-base) * (1.0-2.0*(blend-0.5)));\n"; + code += "\t\t}\n"; + code += "\t}\n"; + } + + } break; + } + + return code; +} + +void VisualShaderNodeColorOp::set_operator(Operator p_op) { + + op = p_op; + emit_changed(); +} + +VisualShaderNodeColorOp::Operator VisualShaderNodeColorOp::get_operator() const { + + return op; +} + +Vector<StringName> VisualShaderNodeColorOp::get_editable_properties() const { + Vector<StringName> props; + props.push_back("operator"); + return props; +} + +void VisualShaderNodeColorOp::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeColorOp::set_operator); + ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeColorOp::get_operator); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Screen,Difference,Darken,Lighten,Overlay,Dodge,Burn,SoftLight,HardLight"), "set_operator", "get_operator"); + + BIND_ENUM_CONSTANT(OP_SCREEN); + BIND_ENUM_CONSTANT(OP_DIFFERENCE); + BIND_ENUM_CONSTANT(OP_DARKEN); + BIND_ENUM_CONSTANT(OP_LIGHTEN); + BIND_ENUM_CONSTANT(OP_OVERLAY); + BIND_ENUM_CONSTANT(OP_DODGE); + BIND_ENUM_CONSTANT(OP_BURN); + BIND_ENUM_CONSTANT(OP_SOFT_LIGHT); + BIND_ENUM_CONSTANT(OP_HARD_LIGHT); +} + +VisualShaderNodeColorOp::VisualShaderNodeColorOp() { + op = OP_SCREEN; + set_input_port_default_value(0, Vector3()); + set_input_port_default_value(1, Vector3()); +} + +////////////// Transform Mult + +String VisualShaderNodeTransformMult::get_caption() const { + return "TransformMult"; +} + +int VisualShaderNodeTransformMult::get_input_port_count() const { + return 2; +} +VisualShaderNodeTransformMult::PortType VisualShaderNodeTransformMult::get_input_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} +String VisualShaderNodeTransformMult::get_input_port_name(int p_port) const { + return p_port == 0 ? "a" : "b"; +} + +int VisualShaderNodeTransformMult::get_output_port_count() const { + return 1; +} +VisualShaderNodeTransformMult::PortType VisualShaderNodeTransformMult::get_output_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} +String VisualShaderNodeTransformMult::get_output_port_name(int p_port) const { + return "mult"; //no output port means the editor will be used as port +} + +String VisualShaderNodeTransformMult::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + if (op == OP_AxB) { + return "\t" + p_output_vars[0] + " = " + p_input_vars[0] + " * " + p_input_vars[1] + ";\n"; + } else { + return "\t" + p_output_vars[0] + " = " + p_input_vars[1] + " * " + p_input_vars[0] + ";\n"; + } +} + +void VisualShaderNodeTransformMult::set_operator(Operator p_op) { + + op = p_op; + emit_changed(); +} + +VisualShaderNodeTransformMult::Operator VisualShaderNodeTransformMult::get_operator() const { + + return op; +} + +Vector<StringName> VisualShaderNodeTransformMult::get_editable_properties() const { + Vector<StringName> props; + props.push_back("operator"); + return props; +} + +void VisualShaderNodeTransformMult::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeTransformMult::set_operator); + ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeTransformMult::get_operator); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "A x B,B x A"), "set_operator", "get_operator"); + + BIND_ENUM_CONSTANT(OP_AxB); + BIND_ENUM_CONSTANT(OP_BxA); +} + +VisualShaderNodeTransformMult::VisualShaderNodeTransformMult() { + op = OP_AxB; + set_input_port_default_value(0, Transform()); + set_input_port_default_value(1, Transform()); +} + +////////////// TransformVec Mult + +String VisualShaderNodeTransformVecMult::get_caption() const { + return "TransformVectorMult"; +} + +int VisualShaderNodeTransformVecMult::get_input_port_count() const { + return 2; +} +VisualShaderNodeTransformVecMult::PortType VisualShaderNodeTransformVecMult::get_input_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_TRANSFORM : PORT_TYPE_VECTOR; +} +String VisualShaderNodeTransformVecMult::get_input_port_name(int p_port) const { + return p_port == 0 ? "a" : "b"; +} + +int VisualShaderNodeTransformVecMult::get_output_port_count() const { + return 1; +} +VisualShaderNodeTransformVecMult::PortType VisualShaderNodeTransformVecMult::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeTransformVecMult::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeTransformVecMult::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + if (op == OP_AxB) { + return "\t" + p_output_vars[0] + " = ( " + p_input_vars[0] + " * vec4(" + p_input_vars[1] + ", 1.0) ).xyz;\n"; + } else if (op == OP_BxA) { + return "\t" + p_output_vars[0] + " = ( vec4(" + p_input_vars[1] + ", 1.0) * " + p_input_vars[0] + " ).xyz;\n"; + } else if (op == OP_3x3_AxB) { + return "\t" + p_output_vars[0] + " = ( " + p_input_vars[0] + " * vec4(" + p_input_vars[1] + ", 0.0) ).xyz;\n"; + } else { + return "\t" + p_output_vars[0] + " = ( vec4(" + p_input_vars[1] + ", 0.0) * " + p_input_vars[0] + " ).xyz;\n"; + } +} + +void VisualShaderNodeTransformVecMult::set_operator(Operator p_op) { + + op = p_op; + emit_changed(); +} + +VisualShaderNodeTransformVecMult::Operator VisualShaderNodeTransformVecMult::get_operator() const { + + return op; +} + +Vector<StringName> VisualShaderNodeTransformVecMult::get_editable_properties() const { + Vector<StringName> props; + props.push_back("operator"); + return props; +} + +void VisualShaderNodeTransformVecMult::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeTransformVecMult::set_operator); + ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeTransformVecMult::get_operator); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "A x B,B x A,A x B (3x3),B x A (3x3)"), "set_operator", "get_operator"); + + BIND_ENUM_CONSTANT(OP_AxB); + BIND_ENUM_CONSTANT(OP_BxA); + BIND_ENUM_CONSTANT(OP_3x3_AxB); + BIND_ENUM_CONSTANT(OP_3x3_BxA); +} + +VisualShaderNodeTransformVecMult::VisualShaderNodeTransformVecMult() { + op = OP_AxB; + set_input_port_default_value(0, Transform()); + set_input_port_default_value(1, Vector3()); +} + +////////////// Scalar Func + +String VisualShaderNodeScalarFunc::get_caption() const { + return "ScalarFunc"; +} + +int VisualShaderNodeScalarFunc::get_input_port_count() const { + return 1; +} +VisualShaderNodeScalarFunc::PortType VisualShaderNodeScalarFunc::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarFunc::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeScalarFunc::get_output_port_count() const { + return 1; +} +VisualShaderNodeScalarFunc::PortType VisualShaderNodeScalarFunc::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarFunc::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeScalarFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + static const char *scalar_func_id[FUNC_NEGATE + 1] = { + "sin($)", + "cos($)", + "tan($)", + "asin($)", + "acos($)", + "atan($)", + "sinh($)", + "cosh($)", + "tanh($)", + "log($)", + "exp($)", + "sqrt($)", + "abs($)", + "sign($)", + "floor($)", + "round($)", + "ceil($)", + "fract($)", + "min(max($,0),1)", + "-($)", + }; + + return "\t" + p_output_vars[0] + " = " + String(scalar_func_id[func]).replace("$", p_input_vars[0]) + ";\n"; +} + +void VisualShaderNodeScalarFunc::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeScalarFunc::Function VisualShaderNodeScalarFunc::get_function() const { + + return func; +} + +Vector<StringName> VisualShaderNodeScalarFunc::get_editable_properties() const { + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeScalarFunc::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeScalarFunc::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeScalarFunc::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sin,Cos,Tan,ASin,ACos,ATan,SinH,CosH,TanH,Log,Exp,Sqrt,Abs,Sign,Floor,Round,Ceil,Frac,Saturate,Negate"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_SIN); + BIND_ENUM_CONSTANT(FUNC_COS); + BIND_ENUM_CONSTANT(FUNC_TAN); + BIND_ENUM_CONSTANT(FUNC_ASIN); + BIND_ENUM_CONSTANT(FUNC_ACOS); + BIND_ENUM_CONSTANT(FUNC_ATAN); + BIND_ENUM_CONSTANT(FUNC_SINH); + BIND_ENUM_CONSTANT(FUNC_COSH); + BIND_ENUM_CONSTANT(FUNC_TANH); + BIND_ENUM_CONSTANT(FUNC_LOG); + BIND_ENUM_CONSTANT(FUNC_EXP); + BIND_ENUM_CONSTANT(FUNC_SQRT); + BIND_ENUM_CONSTANT(FUNC_ABS); + BIND_ENUM_CONSTANT(FUNC_SIGN); + BIND_ENUM_CONSTANT(FUNC_FLOOR); + BIND_ENUM_CONSTANT(FUNC_ROUND); + BIND_ENUM_CONSTANT(FUNC_CEIL); + BIND_ENUM_CONSTANT(FUNC_FRAC); + BIND_ENUM_CONSTANT(FUNC_SATURATE); + BIND_ENUM_CONSTANT(FUNC_NEGATE); +} + +VisualShaderNodeScalarFunc::VisualShaderNodeScalarFunc() { + func = FUNC_SIGN; + set_input_port_default_value(0, 0.0); +} + +////////////// Vector Func + +String VisualShaderNodeVectorFunc::get_caption() const { + return "VectorFunc"; +} + +int VisualShaderNodeVectorFunc::get_input_port_count() const { + return 1; +} +VisualShaderNodeVectorFunc::PortType VisualShaderNodeVectorFunc::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorFunc::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeVectorFunc::get_output_port_count() const { + return 1; +} +VisualShaderNodeVectorFunc::PortType VisualShaderNodeVectorFunc::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorFunc::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeVectorFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + static const char *vec_func_id[FUNC_HSV2RGB + 1] = { + "normalize($)", + "max(min($,vec3(1.0)),vec3(0.0))", + "-($)", + "1.0/($)", + "", + "", + }; + + String code; + + if (func == FUNC_RGB2HSV) { + code += "\t{\n"; + code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; + code += "\t\tvec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n"; + code += "\t\tvec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n"; + code += "\t\tvec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n"; + code += "\t\tfloat d = q.x - min(q.w, q.y);\n"; + code += "\t\tfloat e = 1.0e-10;\n"; + code += "\t\t" + p_output_vars[0] + "=vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n"; + code += "\t}\n"; + } else if (func == FUNC_HSV2RGB) { + code += "\t{\n"; + code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; + code += "\t\tvec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n"; + code += "\t\tvec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n"; + code += "\t\t" + p_output_vars[0] + "=c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n"; + code += "\t}\n"; + + } else { + code += "\t" + p_output_vars[0] + "=" + String(vec_func_id[func]).replace("$", p_input_vars[0]) + ";\n"; + } + + return code; +} + +void VisualShaderNodeVectorFunc::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeVectorFunc::Function VisualShaderNodeVectorFunc::get_function() const { + + return func; +} + +Vector<StringName> VisualShaderNodeVectorFunc::get_editable_properties() const { + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeVectorFunc::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeVectorFunc::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeVectorFunc::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Normalize,Saturate,Negate,Reciprocal,RGB2HSV,HSV2RGB"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_NORMALIZE); + BIND_ENUM_CONSTANT(FUNC_SATURATE); + BIND_ENUM_CONSTANT(FUNC_NEGATE); + BIND_ENUM_CONSTANT(FUNC_RECIPROCAL); + BIND_ENUM_CONSTANT(FUNC_RGB2HSV); + BIND_ENUM_CONSTANT(FUNC_HSV2RGB); +} + +VisualShaderNodeVectorFunc::VisualShaderNodeVectorFunc() { + func = FUNC_NORMALIZE; + set_input_port_default_value(0, Vector3()); +} + +////////////// Dot Product + +String VisualShaderNodeDotProduct::get_caption() const { + return "DotProduct"; +} + +int VisualShaderNodeDotProduct::get_input_port_count() const { + return 2; +} +VisualShaderNodeDotProduct::PortType VisualShaderNodeDotProduct::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeDotProduct::get_input_port_name(int p_port) const { + return p_port == 0 ? "a" : "b"; +} + +int VisualShaderNodeDotProduct::get_output_port_count() const { + return 1; +} +VisualShaderNodeDotProduct::PortType VisualShaderNodeDotProduct::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeDotProduct::get_output_port_name(int p_port) const { + return "dot"; +} + +String VisualShaderNodeDotProduct::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = dot( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; +} + +VisualShaderNodeDotProduct::VisualShaderNodeDotProduct() { + set_input_port_default_value(0, Vector3()); + set_input_port_default_value(1, Vector3()); +} + +////////////// Vector Len + +String VisualShaderNodeVectorLen::get_caption() const { + return "VectorLen"; +} + +int VisualShaderNodeVectorLen::get_input_port_count() const { + return 1; +} +VisualShaderNodeVectorLen::PortType VisualShaderNodeVectorLen::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorLen::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeVectorLen::get_output_port_count() const { + return 1; +} +VisualShaderNodeVectorLen::PortType VisualShaderNodeVectorLen::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeVectorLen::get_output_port_name(int p_port) const { + return "length"; +} + +String VisualShaderNodeVectorLen::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = length( " + p_input_vars[0] + " );\n"; +} + +VisualShaderNodeVectorLen::VisualShaderNodeVectorLen() { + set_input_port_default_value(0, Vector3()); +} + +////////////// Scalar Interp + +String VisualShaderNodeScalarInterp::get_caption() const { + return "ScalarInterp"; +} + +int VisualShaderNodeScalarInterp::get_input_port_count() const { + return 3; +} +VisualShaderNodeScalarInterp::PortType VisualShaderNodeScalarInterp::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarInterp::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "a"; + } else if (p_port == 1) { + return "b"; + } else { + return "c"; + } +} + +int VisualShaderNodeScalarInterp::get_output_port_count() const { + return 1; +} +VisualShaderNodeScalarInterp::PortType VisualShaderNodeScalarInterp::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarInterp::get_output_port_name(int p_port) const { + return "mix"; +} + +String VisualShaderNodeScalarInterp::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = mix( " + p_input_vars[0] + " , " + p_input_vars[1] + " , " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeScalarInterp::VisualShaderNodeScalarInterp() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, 0.0); +} + +////////////// Vector Interp + +String VisualShaderNodeVectorInterp::get_caption() const { + return "VectorInterp"; +} + +int VisualShaderNodeVectorInterp::get_input_port_count() const { + return 3; +} +VisualShaderNodeVectorInterp::PortType VisualShaderNodeVectorInterp::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorInterp::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "a"; + } else if (p_port == 1) { + return "b"; + } else { + return "c"; + } +} + +int VisualShaderNodeVectorInterp::get_output_port_count() const { + return 1; +} +VisualShaderNodeVectorInterp::PortType VisualShaderNodeVectorInterp::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorInterp::get_output_port_name(int p_port) const { + return "mix"; +} + +String VisualShaderNodeVectorInterp::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = mix( " + p_input_vars[0] + " , " + p_input_vars[1] + " , " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeVectorInterp::VisualShaderNodeVectorInterp() { + set_input_port_default_value(0, Vector3()); + set_input_port_default_value(1, Vector3()); + set_input_port_default_value(2, Vector3()); +} + +////////////// Vector Compose +String VisualShaderNodeVectorCompose::get_caption() const { + return "VectorCompose"; +} + +int VisualShaderNodeVectorCompose::get_input_port_count() const { + return 3; +} +VisualShaderNodeVectorCompose::PortType VisualShaderNodeVectorCompose::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeVectorCompose::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "x"; + } else if (p_port == 1) { + return "y"; + } else { + return "z"; + } +} + +int VisualShaderNodeVectorCompose::get_output_port_count() const { + return 1; +} +VisualShaderNodeVectorCompose::PortType VisualShaderNodeVectorCompose::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorCompose::get_output_port_name(int p_port) const { + return "vec"; +} + +String VisualShaderNodeVectorCompose::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = vec3( " + p_input_vars[0] + " , " + p_input_vars[1] + " , " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeVectorCompose::VisualShaderNodeVectorCompose() { + + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, 0.0); +} + +////////////// Transform Compose + +String VisualShaderNodeTransformCompose::get_caption() const { + return "TransformCompose"; +} + +int VisualShaderNodeTransformCompose::get_input_port_count() const { + return 4; +} +VisualShaderNodeTransformCompose::PortType VisualShaderNodeTransformCompose::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeTransformCompose::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "x"; + } else if (p_port == 1) { + return "y"; + } else if (p_port == 2) { + return "z"; + } else { + return "origin"; + } +} + +int VisualShaderNodeTransformCompose::get_output_port_count() const { + return 1; +} +VisualShaderNodeTransformCompose::PortType VisualShaderNodeTransformCompose::get_output_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} +String VisualShaderNodeTransformCompose::get_output_port_name(int p_port) const { + return "xform"; +} + +String VisualShaderNodeTransformCompose::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = mat4( vec4(" + p_input_vars[0] + ", 0.0) , vec4(" + p_input_vars[1] + ", 0.0) , vec4(" + p_input_vars[2] + ",0.0), vec4(" + p_input_vars[3] + ",1.0) );\n"; +} + +VisualShaderNodeTransformCompose::VisualShaderNodeTransformCompose() { + + set_input_port_default_value(0, Vector3()); + set_input_port_default_value(1, Vector3()); + set_input_port_default_value(2, Vector3()); + set_input_port_default_value(3, Vector3()); +} + +////////////// Vector Decompose +String VisualShaderNodeVectorDecompose::get_caption() const { + return "VectorDecompose"; +} + +int VisualShaderNodeVectorDecompose::get_input_port_count() const { + return 1; +} +VisualShaderNodeVectorDecompose::PortType VisualShaderNodeVectorDecompose::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVectorDecompose::get_input_port_name(int p_port) const { + return "vec"; +} + +int VisualShaderNodeVectorDecompose::get_output_port_count() const { + return 3; +} +VisualShaderNodeVectorDecompose::PortType VisualShaderNodeVectorDecompose::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeVectorDecompose::get_output_port_name(int p_port) const { + if (p_port == 0) { + return "x"; + } else if (p_port == 1) { + return "y"; + } else { + return "z"; + } +} + +String VisualShaderNodeVectorDecompose::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + String code; + code += "\t" + p_output_vars[0] + " = " + p_input_vars[0] + ".x;\n"; + code += "\t" + p_output_vars[1] + " = " + p_input_vars[0] + ".y;\n"; + code += "\t" + p_output_vars[2] + " = " + p_input_vars[0] + ".z;\n"; + return code; +} + +VisualShaderNodeVectorDecompose::VisualShaderNodeVectorDecompose() { + set_input_port_default_value(0, Vector3()); +} + +////////////// Transform Decompose + +String VisualShaderNodeTransformDecompose::get_caption() const { + return "TransformDecompose"; +} + +int VisualShaderNodeTransformDecompose::get_input_port_count() const { + return 1; +} +VisualShaderNodeTransformDecompose::PortType VisualShaderNodeTransformDecompose::get_input_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} +String VisualShaderNodeTransformDecompose::get_input_port_name(int p_port) const { + return "xform"; +} + +int VisualShaderNodeTransformDecompose::get_output_port_count() const { + return 4; +} +VisualShaderNodeTransformDecompose::PortType VisualShaderNodeTransformDecompose::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeTransformDecompose::get_output_port_name(int p_port) const { + if (p_port == 0) { + return "x"; + } else if (p_port == 1) { + return "y"; + } else if (p_port == 2) { + return "z"; + } else { + return "origin"; + } +} + +String VisualShaderNodeTransformDecompose::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + String code; + code += "\t" + p_output_vars[0] + " = " + p_input_vars[0] + "[0].xyz;\n"; + code += "\t" + p_output_vars[1] + " = " + p_input_vars[0] + "[1].xyz;\n"; + code += "\t" + p_output_vars[2] + " = " + p_input_vars[0] + "[2].xyz;\n"; + code += "\t" + p_output_vars[3] + " = " + p_input_vars[0] + "[3].xyz;\n"; + return code; +} + +VisualShaderNodeTransformDecompose::VisualShaderNodeTransformDecompose() { + set_input_port_default_value(0, Transform()); +} + +////////////// Scalar Uniform + +String VisualShaderNodeScalarUniform::get_caption() const { + return "ScalarUniform"; +} + +int VisualShaderNodeScalarUniform::get_input_port_count() const { + return 0; +} +VisualShaderNodeScalarUniform::PortType VisualShaderNodeScalarUniform::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarUniform::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeScalarUniform::get_output_port_count() const { + return 1; +} +VisualShaderNodeScalarUniform::PortType VisualShaderNodeScalarUniform::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} +String VisualShaderNodeScalarUniform::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeScalarUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform float " + get_uniform_name() + ";\n"; +} +String VisualShaderNodeScalarUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; +} + +VisualShaderNodeScalarUniform::VisualShaderNodeScalarUniform() { +} + +////////////// Color Uniform + +String VisualShaderNodeColorUniform::get_caption() const { + return "ColorUniform"; +} + +int VisualShaderNodeColorUniform::get_input_port_count() const { + return 0; +} +VisualShaderNodeColorUniform::PortType VisualShaderNodeColorUniform::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeColorUniform::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeColorUniform::get_output_port_count() const { + return 2; +} +VisualShaderNodeColorUniform::PortType VisualShaderNodeColorUniform::get_output_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeColorUniform::get_output_port_name(int p_port) const { + return p_port == 0 ? "color" : "alpha"; //no output port means the editor will be used as port +} + +String VisualShaderNodeColorUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + + return "uniform vec4 " + get_uniform_name() + " : hint_color;\n"; +} + +String VisualShaderNodeColorUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + String code = "\t" + p_output_vars[0] + " = " + get_uniform_name() + ".rgb;\n"; + code += "\t" + p_output_vars[1] + " = " + get_uniform_name() + ".a;\n"; + return code; +} + +VisualShaderNodeColorUniform::VisualShaderNodeColorUniform() { +} + +////////////// Vector Uniform + +String VisualShaderNodeVec3Uniform::get_caption() const { + return "VectorUniform"; +} + +int VisualShaderNodeVec3Uniform::get_input_port_count() const { + return 0; +} +VisualShaderNodeVec3Uniform::PortType VisualShaderNodeVec3Uniform::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVec3Uniform::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeVec3Uniform::get_output_port_count() const { + return 1; +} +VisualShaderNodeVec3Uniform::PortType VisualShaderNodeVec3Uniform::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeVec3Uniform::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} +String VisualShaderNodeVec3Uniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform vec3 " + get_uniform_name() + ";\n"; +} + +String VisualShaderNodeVec3Uniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; +} + +VisualShaderNodeVec3Uniform::VisualShaderNodeVec3Uniform() { +} + +////////////// Transform Uniform + +String VisualShaderNodeTransformUniform::get_caption() const { + return "TransformUniform"; +} + +int VisualShaderNodeTransformUniform::get_input_port_count() const { + return 0; +} +VisualShaderNodeTransformUniform::PortType VisualShaderNodeTransformUniform::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} +String VisualShaderNodeTransformUniform::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeTransformUniform::get_output_port_count() const { + return 1; +} +VisualShaderNodeTransformUniform::PortType VisualShaderNodeTransformUniform::get_output_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} +String VisualShaderNodeTransformUniform::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} +String VisualShaderNodeTransformUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform mat4 " + get_uniform_name() + ";\n"; +} + +String VisualShaderNodeTransformUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; +} + +VisualShaderNodeTransformUniform::VisualShaderNodeTransformUniform() { +} + +////////////// Texture Uniform + +String VisualShaderNodeTextureUniform::get_caption() const { + return "TextureUniform"; +} + +int VisualShaderNodeTextureUniform::get_input_port_count() const { + return 2; +} +VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniform::get_input_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeTextureUniform::get_input_port_name(int p_port) const { + return p_port == 0 ? "uv" : "lod"; +} + +int VisualShaderNodeTextureUniform::get_output_port_count() const { + return 2; +} +VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniform::get_output_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeTextureUniform::get_output_port_name(int p_port) const { + return p_port == 0 ? "rgb" : "alpha"; +} + +String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code = "uniform sampler2D " + get_uniform_name(); + + switch (texture_type) { + case TYPE_DATA: + if (color_default == COLOR_DEFAULT_BLACK) + code += " : hint_black;\n"; + else + code += ";\n"; + break; + case TYPE_COLOR: + if (color_default == COLOR_DEFAULT_BLACK) + code += " : hint_black_albedo;\n"; + else + code += " : hint_albedo;\n"; + break; + case TYPE_NORMALMAP: code += " : hint_normal;\n"; break; + case TYPE_ANISO: code += " : hint_aniso;\n"; break; + } + + return code; +} + +String VisualShaderNodeTextureUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + + String id = get_uniform_name(); + String code = "\t{\n"; + if (p_input_vars[0] == String()) { //none bound, do nothing + + code += "\t\tvec4 n_tex_read = vec4(0.0);\n"; + } else if (p_input_vars[1] == String()) { + //no lod + code += "\t\tvec4 n_tex_read = texture( " + id + " , " + p_input_vars[0] + ".xy );\n"; + } else { + code += "\t\tvec4 n_tex_read = textureLod( " + id + " , " + p_input_vars[0] + ".xy , " + p_input_vars[1] + " );\n"; + } + + code += "\t\t" + p_output_vars[0] + " = n_tex_read.rgb;\n"; + code += "\t\t" + p_output_vars[1] + " = n_tex_read.a;\n"; + code += "\t}\n"; + return code; +} + +void VisualShaderNodeTextureUniform::set_texture_type(TextureType p_type) { + + texture_type = p_type; + emit_changed(); +} + +VisualShaderNodeTextureUniform::TextureType VisualShaderNodeTextureUniform::get_texture_type() const { + return texture_type; +} + +void VisualShaderNodeTextureUniform::set_color_default(ColorDefault p_default) { + color_default = p_default; + emit_changed(); +} +VisualShaderNodeTextureUniform::ColorDefault VisualShaderNodeTextureUniform::get_color_default() const { + return color_default; +} + +Vector<StringName> VisualShaderNodeTextureUniform::get_editable_properties() const { + Vector<StringName> props; + props.push_back("texture_type"); + props.push_back("color_default"); + return props; +} + +void VisualShaderNodeTextureUniform::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture_type", "type"), &VisualShaderNodeTextureUniform::set_texture_type); + ClassDB::bind_method(D_METHOD("get_texture_type"), &VisualShaderNodeTextureUniform::get_texture_type); + + ClassDB::bind_method(D_METHOD("set_color_default", "type"), &VisualShaderNodeTextureUniform::set_color_default); + ClassDB::bind_method(D_METHOD("get_color_default"), &VisualShaderNodeTextureUniform::get_color_default); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normalmap,Aniso"), "set_texture_type", "get_texture_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "color_default", PROPERTY_HINT_ENUM, "White Default,Black Default"), "set_color_default", "get_color_default"); + + BIND_ENUM_CONSTANT(TYPE_DATA); + BIND_ENUM_CONSTANT(TYPE_COLOR); + BIND_ENUM_CONSTANT(TYPE_NORMALMAP); + BIND_ENUM_CONSTANT(TYPE_ANISO); + + BIND_ENUM_CONSTANT(COLOR_DEFAULT_WHITE); + BIND_ENUM_CONSTANT(COLOR_DEFAULT_BLACK); +} + +VisualShaderNodeTextureUniform::VisualShaderNodeTextureUniform() { + texture_type = TYPE_DATA; + color_default = COLOR_DEFAULT_WHITE; +} + +////////////// CubeMap Uniform + +String VisualShaderNodeCubeMapUniform::get_caption() const { + return "CubeMapUniform"; +} + +int VisualShaderNodeCubeMapUniform::get_input_port_count() const { + return 2; +} +VisualShaderNodeCubeMapUniform::PortType VisualShaderNodeCubeMapUniform::get_input_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeCubeMapUniform::get_input_port_name(int p_port) const { + return p_port == 0 ? "normal" : "lod"; +} + +int VisualShaderNodeCubeMapUniform::get_output_port_count() const { + return 2; +} +VisualShaderNodeCubeMapUniform::PortType VisualShaderNodeCubeMapUniform::get_output_port_type(int p_port) const { + return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; +} +String VisualShaderNodeCubeMapUniform::get_output_port_name(int p_port) const { + return p_port == 0 ? "rgb" : "alpha"; +} + +String VisualShaderNodeCubeMapUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const { + return String(); +} + +VisualShaderNodeCubeMapUniform::VisualShaderNodeCubeMapUniform() { +} diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h new file mode 100644 index 0000000000..2ede36fbc8 --- /dev/null +++ b/scene/resources/visual_shader_nodes.h @@ -0,0 +1,861 @@ +#ifndef VISUAL_SHADER_NODES_H +#define VISUAL_SHADER_NODES_H + +#include "scene/resources/visual_shader.h" + +/// CONSTANTS /// + +class VisualShaderNodeScalarConstant : public VisualShaderNode { + GDCLASS(VisualShaderNodeScalarConstant, VisualShaderNode) + float constant; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_constant(float p_value); + float get_constant() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeScalarConstant(); +}; + +class VisualShaderNodeColorConstant : public VisualShaderNode { + GDCLASS(VisualShaderNodeColorConstant, VisualShaderNode) + Color constant; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_constant(Color p_value); + Color get_constant() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeColorConstant(); +}; + +class VisualShaderNodeVec3Constant : public VisualShaderNode { + GDCLASS(VisualShaderNodeVec3Constant, VisualShaderNode) + Vector3 constant; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_constant(Vector3 p_value); + Vector3 get_constant() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeVec3Constant(); +}; + +class VisualShaderNodeTransformConstant : public VisualShaderNode { + GDCLASS(VisualShaderNodeTransformConstant, VisualShaderNode) + Transform constant; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_constant(Transform p_value); + Transform get_constant() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeTransformConstant(); +}; + +////////////////////////////////// + +class VisualShaderNodeTexture : public VisualShaderNode { + GDCLASS(VisualShaderNodeTexture, VisualShaderNode) + Ref<Texture> texture; + +public: + enum Source { + SOURCE_TEXTURE, + SOURCE_SCREEN, + SOURCE_2D_TEXTURE, + SOURCE_2D_NORMAL + }; + + enum TextureType { + TYPE_DATA, + TYPE_COLOR, + TYPE_NORMALMAP + }; + +private: + Source source; + TextureType texture_type; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_source(Source p_source); + Source get_source() const; + + void set_texture(Ref<Texture> p_value); + Ref<Texture> get_texture() const; + + void set_texture_type(TextureType p_type); + TextureType get_texture_type() const; + + virtual Vector<StringName> get_editable_properties() const; + + virtual String get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const; + + VisualShaderNodeTexture(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeTexture::TextureType) +VARIANT_ENUM_CAST(VisualShaderNodeTexture::Source) + +////////////////////////////////// + +class VisualShaderNodeCubeMap : public VisualShaderNode { + GDCLASS(VisualShaderNodeCubeMap, VisualShaderNode) + Ref<CubeMap> cube_map; + +public: + enum TextureType { + TYPE_DATA, + TYPE_COLOR, + TYPE_NORMALMAP + }; + +private: + TextureType texture_type; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_cube_map(Ref<CubeMap> p_value); + Ref<CubeMap> get_cube_map() const; + + void set_texture_type(TextureType p_type); + TextureType get_texture_type() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeCubeMap(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeCubeMap::TextureType) +/////////////////////////////////////// + +class VisualShaderNodeScalarOp : public VisualShaderNode { + GDCLASS(VisualShaderNodeScalarOp, VisualShaderNode) + +public: + enum Operator { + OP_ADD, + OP_SUB, + OP_MUL, + OP_DIV, + OP_MOD, + OP_POW, + OP_MAX, + OP_MIN, + OP_ATAN2 + }; + +protected: + Operator op; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_operator(Operator p_op); + Operator get_operator() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeScalarOp(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeScalarOp::Operator) + +class VisualShaderNodeVectorOp : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorOp, VisualShaderNode) + +public: + enum Operator { + OP_ADD, + OP_SUB, + OP_MUL, + OP_DIV, + OP_MOD, + OP_POW, + OP_MAX, + OP_MIN, + OP_CROSS + + }; + +protected: + Operator op; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_operator(Operator p_op); + Operator get_operator() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeVectorOp(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeVectorOp::Operator) + +class VisualShaderNodeColorOp : public VisualShaderNode { + GDCLASS(VisualShaderNodeColorOp, VisualShaderNode) + +public: + enum Operator { + OP_SCREEN, + OP_DIFFERENCE, + OP_DARKEN, + OP_LIGHTEN, + OP_OVERLAY, + OP_DODGE, + OP_BURN, + OP_SOFT_LIGHT, + OP_HARD_LIGHT, + }; + +protected: + Operator op; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_operator(Operator p_op); + Operator get_operator() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeColorOp(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeColorOp::Operator) + +class VisualShaderNodeTransformMult : public VisualShaderNode { + GDCLASS(VisualShaderNodeTransformMult, VisualShaderNode) + +public: + enum Operator { + OP_AxB, + OP_BxA, + }; + +protected: + Operator op; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_operator(Operator p_op); + Operator get_operator() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeTransformMult(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeTransformMult::Operator) + +class VisualShaderNodeTransformVecMult : public VisualShaderNode { + GDCLASS(VisualShaderNodeTransformVecMult, VisualShaderNode) + +public: + enum Operator { + OP_AxB, + OP_BxA, + OP_3x3_AxB, + OP_3x3_BxA, + }; + +protected: + Operator op; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_operator(Operator p_op); + Operator get_operator() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeTransformVecMult(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeTransformVecMult::Operator) + +/////////////////////////////////////// + +class VisualShaderNodeScalarFunc : public VisualShaderNode { + GDCLASS(VisualShaderNodeScalarFunc, VisualShaderNode) + +public: + enum Function { + FUNC_SIN, + FUNC_COS, + FUNC_TAN, + FUNC_ASIN, + FUNC_ACOS, + FUNC_ATAN, + FUNC_SINH, + FUNC_COSH, + FUNC_TANH, + FUNC_LOG, + FUNC_EXP, + FUNC_SQRT, + FUNC_ABS, + FUNC_SIGN, + FUNC_FLOOR, + FUNC_ROUND, + FUNC_CEIL, + FUNC_FRAC, + FUNC_SATURATE, + FUNC_NEGATE, + }; + +protected: + Function func; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_function(Function p_func); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeScalarFunc(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeScalarFunc::Function) + +/////////////////////////////////////// + +class VisualShaderNodeVectorFunc : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorFunc, VisualShaderNode) + +public: + enum Function { + FUNC_NORMALIZE, + FUNC_SATURATE, + FUNC_NEGATE, + FUNC_RECIPROCAL, + FUNC_RGB2HSV, + FUNC_HSV2RGB, + }; + +protected: + Function func; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_function(Function p_func); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeVectorFunc(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeVectorFunc::Function) + +/////////////////////////////////////// + +class VisualShaderNodeDotProduct : public VisualShaderNode { + GDCLASS(VisualShaderNodeDotProduct, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeDotProduct(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeVectorLen : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorLen, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorLen(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeScalarInterp : public VisualShaderNode { + GDCLASS(VisualShaderNodeScalarInterp, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeScalarInterp(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeVectorInterp : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorInterp, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorInterp(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeVectorCompose : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorCompose, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorCompose(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeTransformCompose : public VisualShaderNode { + GDCLASS(VisualShaderNodeTransformCompose, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeTransformCompose(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeVectorDecompose : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorDecompose, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorDecompose(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeTransformDecompose : public VisualShaderNode { + GDCLASS(VisualShaderNodeTransformDecompose, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeTransformDecompose(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeScalarUniform : public VisualShaderNodeUniform { + GDCLASS(VisualShaderNodeScalarUniform, VisualShaderNodeUniform) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeScalarUniform(); +}; + +class VisualShaderNodeColorUniform : public VisualShaderNodeUniform { + GDCLASS(VisualShaderNodeColorUniform, VisualShaderNodeUniform) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeColorUniform(); +}; + +class VisualShaderNodeVec3Uniform : public VisualShaderNodeUniform { + GDCLASS(VisualShaderNodeVec3Uniform, VisualShaderNodeUniform) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVec3Uniform(); +}; + +class VisualShaderNodeTransformUniform : public VisualShaderNodeUniform { + GDCLASS(VisualShaderNodeTransformUniform, VisualShaderNodeUniform) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeTransformUniform(); +}; + +////////////////////////////////// + +class VisualShaderNodeTextureUniform : public VisualShaderNodeUniform { + GDCLASS(VisualShaderNodeTextureUniform, VisualShaderNodeUniform) +public: + enum TextureType { + TYPE_DATA, + TYPE_COLOR, + TYPE_NORMALMAP, + TYPE_ANISO, + }; + + enum ColorDefault { + COLOR_DEFAULT_WHITE, + COLOR_DEFAULT_BLACK + }; + +private: + TextureType texture_type; + ColorDefault color_default; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + Vector<StringName> get_editable_properties() const; + + void set_texture_type(TextureType p_type); + TextureType get_texture_type() const; + + void set_color_default(ColorDefault p_default); + ColorDefault get_color_default() const; + + VisualShaderNodeTextureUniform(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeTextureUniform::TextureType) +VARIANT_ENUM_CAST(VisualShaderNodeTextureUniform::ColorDefault) + +////////////////////////////////// + +class VisualShaderNodeCubeMapUniform : public VisualShaderNode { + GDCLASS(VisualShaderNodeCubeMapUniform, VisualShaderNode) + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeCubeMapUniform(); +}; + +#endif // VISUAL_SHADER_NODES_H diff --git a/servers/audio/audio_driver_dummy.cpp b/servers/audio/audio_driver_dummy.cpp index 1ca2334392..be36c3b748 100644 --- a/servers/audio/audio_driver_dummy.cpp +++ b/servers/audio/audio_driver_dummy.cpp @@ -44,7 +44,7 @@ Error AudioDriverDummy::init() { speaker_mode = SPEAKER_MODE_STEREO; channels = 2; - int latency = GLOBAL_DEF("audio/output_latency", DEFAULT_OUTPUT_LATENCY); + int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); buffer_frames = closest_power_of_2(latency * mix_rate / 1000); samples_in = memnew_arr(int32_t, buffer_frames * channels); diff --git a/servers/audio/audio_rb_resampler.cpp b/servers/audio/audio_rb_resampler.cpp index 9faa4056c3..3414351681 100644 --- a/servers/audio/audio_rb_resampler.cpp +++ b/servers/audio/audio_rb_resampler.cpp @@ -100,6 +100,8 @@ uint32_t AudioRBResampler::_resample(AudioFrame *p_dest, int p_todo, int32_t p_i if (C == 6) { + // FIXME: Lot of unused assignments here, but it seems like intermediate calculations + // should be done as for C == 2 (C == 4 also has some unused assignments). float v0 = rb[(pos * 6) + 0]; float v1 = rb[(pos * 6) + 1]; float v2 = rb[(pos * 6) + 2]; diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index f2df7119e7..ceb843c031 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -117,6 +117,10 @@ AudioDriver::AudioDriver() { _last_mix_time = 0; _mix_amount = 0; + +#ifdef DEBUG_ENABLED + prof_time = 0; +#endif } AudioDriver *AudioDriverManager::drivers[MAX_DRIVERS]; @@ -184,6 +188,10 @@ void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) { int todo = p_frames; +#ifdef DEBUG_ENABLED + uint64_t prof_ticks = OS::get_singleton()->get_ticks_usec(); +#endif + if (channel_count != get_channel_count()) { // Amount of channels changed due to a device change // reinitialize the buses channels and buffers @@ -234,6 +242,17 @@ void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) { todo -= to_copy; to_mix -= to_copy; } + + // Calculate latency for Performance.AUDIO_OUTPUT_LATENCY + if (OS::get_singleton()) { + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + output_latency = (ticks - output_latency_ticks) / 1000000.f; + output_latency_ticks = ticks; + } + +#ifdef DEBUG_ENABLED + prof_time += OS::get_singleton()->get_ticks_usec() - prof_ticks; +#endif } void AudioServer::_mix_step() { @@ -307,6 +326,10 @@ void AudioServer::_mix_step() { if (!bus->effects[j].enabled) continue; +#ifdef DEBUG_ENABLED + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); +#endif + for (int k = 0; k < bus->channels.size(); k++) { if (!bus->channels[k].active) @@ -321,6 +344,10 @@ void AudioServer::_mix_step() { continue; SWAP(bus->channels[k].buffer, temp_buffer[k]); } + +#ifdef DEBUG_ENABLED + bus->effects[j].prof_time += OS::get_singleton()->get_ticks_usec() - ticks; +#endif } } @@ -761,6 +788,9 @@ void AudioServer::add_bus_effect(int p_bus, const Ref<AudioEffect> &p_effect, in fx.effect = p_effect; //fx.instance=p_effect->instance(); fx.enabled = true; +#ifdef DEBUG_ENABLED + fx.prof_time = 0; +#endif if (p_at_pos >= buses[p_bus]->effects.size() || p_at_pos < 0) { buses[p_bus]->effects.push_back(fx); @@ -873,8 +903,8 @@ void AudioServer::init_channels_and_buffers() { void AudioServer::init() { - channel_disable_threshold_db = GLOBAL_DEF("audio/channel_disable_threshold_db", -60.0); - channel_disable_frames = float(GLOBAL_DEF("audio/channel_disable_time", 2.0)) * get_mix_rate(); + channel_disable_threshold_db = GLOBAL_DEF_RST("audio/channel_disable_threshold_db", -60.0); + channel_disable_frames = float(GLOBAL_DEF_RST("audio/channel_disable_time", 2.0)) * get_mix_rate(); buffer_size = 1024; //hardcoded for now init_channels_and_buffers(); @@ -890,7 +920,69 @@ void AudioServer::init() { set_edited(false); //avoid editors from thinking this was edited #endif - GLOBAL_DEF("audio/video_delay_compensation_ms", 0); + GLOBAL_DEF_RST("audio/video_delay_compensation_ms", 0); +} + +void AudioServer::update() { +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton() && ScriptDebugger::get_singleton()->is_profiling()) { + + // Driver time includes server time + effects times + // Server time includes effects times + uint64_t driver_time = AudioDriver::get_singleton()->get_profiling_time(); + uint64_t server_time = prof_time; + + // Substract the server time from the driver time + if (driver_time > server_time) + driver_time -= server_time; + + Array values; + + for (int i = buses.size() - 1; i >= 0; i--) { + Bus *bus = buses[i]; + if (bus->bypass) + continue; + + for (int j = 0; j < bus->effects.size(); j++) { + if (!bus->effects[j].enabled) + continue; + + values.push_back(String(bus->name) + bus->effects[j].effect->get_name()); + values.push_back(USEC_TO_SEC(bus->effects[j].prof_time)); + + // Substract the effect time from the driver and server times + if (driver_time > bus->effects[j].prof_time) + driver_time -= bus->effects[j].prof_time; + if (server_time > bus->effects[j].prof_time) + server_time -= bus->effects[j].prof_time; + } + } + + values.push_back("audio_server"); + values.push_back(USEC_TO_SEC(server_time)); + values.push_back("audio_driver"); + values.push_back(USEC_TO_SEC(driver_time)); + + ScriptDebugger::get_singleton()->add_profiling_frame_data("audio_thread", values); + } + + // Reset profiling times + for (int i = buses.size() - 1; i >= 0; i--) { + Bus *bus = buses[i]; + if (bus->bypass) + continue; + + for (int j = 0; j < bus->effects.size(); j++) { + if (!bus->effects[j].enabled) + continue; + + bus->effects[j].prof_time = 0; + } + } + + AudioDriver::get_singleton()->reset_profiling_time(); + prof_time = 0; +#endif } void AudioServer::load_default_bus_layout() { @@ -1178,6 +1270,11 @@ AudioServer::AudioServer() { mix_frames = 0; channel_count = 0; to_mix = 0; + output_latency = 0; + output_latency_ticks = 0; +#ifdef DEBUG_ENABLED + prof_time = 0; +#endif } AudioServer::~AudioServer() { diff --git a/servers/audio_server.h b/servers/audio_server.h index b7fcd9c093..258fd1d9b0 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -33,6 +33,7 @@ #include "audio_frame.h" #include "object.h" +#include "os/os.h" #include "servers/audio/audio_effect.h" #include "variant.h" @@ -44,10 +45,23 @@ class AudioDriver { uint64_t _last_mix_time; uint64_t _mix_amount; +#ifdef DEBUG_ENABLED + uint64_t prof_ticks; + uint64_t prof_time; +#endif + protected: void audio_server_process(int p_frames, int32_t *p_buffer, bool p_update_mix_time = true); void update_mix_time(int p_frames); +#ifdef DEBUG_ENABLED + _FORCE_INLINE_ void start_counting_ticks() { prof_ticks = OS::get_singleton()->get_ticks_usec(); } + _FORCE_INLINE_ void stop_counting_ticks() { prof_time += OS::get_singleton()->get_ticks_usec() - prof_ticks; } +#else + _FORCE_INLINE_ void start_counting_ticks() {} + _FORCE_INLINE_ void stop_counting_ticks() {} +#endif + public: double get_mix_time() const; //useful for video -> audio sync @@ -82,6 +96,11 @@ public: SpeakerMode get_speaker_mode_by_total_channels(int p_channels) const; int get_total_channels_by_speaker_mode(SpeakerMode) const; +#ifdef DEBUG_ENABLED + uint64_t get_profiling_time() const { return prof_time; } + void reset_profiling_time() { prof_time = 0; } +#endif + AudioDriver(); virtual ~AudioDriver() {} }; @@ -129,6 +148,9 @@ private: uint32_t buffer_size; uint64_t mix_count; uint64_t mix_frames; +#ifdef DEBUG_ENABLED + uint64_t prof_time; +#endif float channel_disable_threshold_db; uint32_t channel_disable_frames; @@ -166,6 +188,9 @@ private: struct Effect { Ref<AudioEffect> effect; bool enabled; +#ifdef DEBUG_ENABLED + uint64_t prof_time; +#endif }; Vector<Effect> effects; @@ -190,6 +215,9 @@ private: Mutex *audio_data_lock; + float output_latency; + uint64_t output_latency_ticks; + void init_channels_and_buffers(); void _mix_step(); @@ -273,6 +301,7 @@ public: virtual void init(); virtual void finish(); + virtual void update(); virtual void load_default_bus_layout(); /* MISC config */ @@ -306,6 +335,8 @@ public: String get_device(); void set_device(String device); + float get_output_latency() { return output_latency; } + AudioServer(); virtual ~AudioServer(); }; diff --git a/servers/physics/body_pair_sw.cpp b/servers/physics/body_pair_sw.cpp index 2a6a9e08ae..5a41b621eb 100644 --- a/servers/physics/body_pair_sw.cpp +++ b/servers/physics/body_pair_sw.cpp @@ -211,6 +211,44 @@ bool BodyPairSW::_test_ccd(real_t p_step, BodySW *p_A, int p_shape_A, const Tran return true; } +real_t combine_bounce(BodySW *A, BodySW *B) { + const PhysicsServer::CombineMode cm = A->get_bounce_combine_mode(); + + switch (cm) { + case PhysicsServer::COMBINE_MODE_INHERIT: + if (B->get_bounce_combine_mode() != PhysicsServer::COMBINE_MODE_INHERIT) + return combine_bounce(B, A); + // else use MAX [This is used when the two bodies doesn't use physical material] + case PhysicsServer::COMBINE_MODE_MAX: + return MAX(A->get_bounce(), B->get_bounce()); + case PhysicsServer::COMBINE_MODE_MIN: + return MIN(A->get_bounce(), B->get_bounce()); + case PhysicsServer::COMBINE_MODE_MULTIPLY: + return A->get_bounce() * B->get_bounce(); + default: // Is always PhysicsServer::COMBINE_MODE_AVERAGE: + return (A->get_bounce() + B->get_bounce()) / 2; + } +} + +real_t combine_friction(BodySW *A, BodySW *B) { + const PhysicsServer::CombineMode cm = A->get_friction_combine_mode(); + + switch (cm) { + case PhysicsServer::COMBINE_MODE_INHERIT: + if (B->get_friction_combine_mode() != PhysicsServer::COMBINE_MODE_INHERIT) + return combine_friction(B, A); + // else use Multiply [This is used when the two bodies doesn't use physical material] + case PhysicsServer::COMBINE_MODE_MULTIPLY: + return A->get_friction() * B->get_friction(); + case PhysicsServer::COMBINE_MODE_MAX: + return MAX(A->get_friction(), B->get_friction()); + case PhysicsServer::COMBINE_MODE_MIN: + return MIN(A->get_friction(), B->get_friction()); + default: // Is always PhysicsServer::COMBINE_MODE_AVERAGE: + return (A->get_friction() + B->get_friction()) / 2; + } +} + bool BodyPairSW::setup(real_t p_step) { //cannot collide @@ -331,7 +369,7 @@ bool BodyPairSW::setup(real_t p_step) { c.acc_bias_impulse = 0; c.acc_bias_impulse_center_of_mass = 0; - c.bounce = MAX(A->get_bounce(), B->get_bounce()); + c.bounce = combine_bounce(A, B); if (c.bounce) { Vector3 crA = A->get_angular_velocity().cross(c.rA); @@ -421,7 +459,7 @@ void BodyPairSW::solve(real_t p_step) { //friction impulse - real_t friction = A->get_friction() * B->get_friction(); + 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); diff --git a/servers/physics/body_sw.cpp b/servers/physics/body_sw.cpp index cc9681193c..59f987fc17 100644 --- a/servers/physics/body_sw.cpp +++ b/servers/physics/body_sw.cpp @@ -423,6 +423,22 @@ void BodySW::_compute_area_gravity_and_dampenings(const AreaSW *p_area) { area_angular_damp += p_area->get_angular_damp(); } +void BodySW::set_combine_mode(PhysicsServer::BodyParameter p_param, PhysicsServer::CombineMode p_mode) { + if (p_param == PhysicsServer::BODY_PARAM_BOUNCE) { + bounce_combine_mode = p_mode; + } else { + friction_combine_mode = p_mode; + } +} + +PhysicsServer::CombineMode BodySW::get_combine_mode(PhysicsServer::BodyParameter p_param) const { + if (p_param == PhysicsServer::BODY_PARAM_BOUNCE) { + return bounce_combine_mode; + } else { + return friction_combine_mode; + } +} + void BodySW::set_axis_lock(PhysicsServer::BodyAxis p_axis, bool lock) { if (lock) { locked_axis |= p_axis; diff --git a/servers/physics/body_sw.h b/servers/physics/body_sw.h index fd2ab16b84..25eb20f5d8 100644 --- a/servers/physics/body_sw.h +++ b/servers/physics/body_sw.h @@ -49,6 +49,8 @@ class BodySW : public CollisionObjectSW { real_t mass; real_t bounce; real_t friction; + PhysicsServer::CombineMode bounce_combine_mode; + PhysicsServer::CombineMode friction_combine_mode; real_t linear_damp; real_t angular_damp; @@ -298,6 +300,12 @@ public: _FORCE_INLINE_ Vector3 get_gravity() const { return gravity; } _FORCE_INLINE_ real_t get_bounce() const { return bounce; } + void set_combine_mode(PhysicsServer::BodyParameter p_param, PhysicsServer::CombineMode p_mode); + PhysicsServer::CombineMode get_combine_mode(PhysicsServer::BodyParameter p_param) const; + + _FORCE_INLINE_ PhysicsServer::CombineMode get_bounce_combine_mode() const { return bounce_combine_mode; } + _FORCE_INLINE_ PhysicsServer::CombineMode get_friction_combine_mode() const { return friction_combine_mode; } + void set_axis_lock(PhysicsServer::BodyAxis p_axis, bool lock); bool is_axis_locked(PhysicsServer::BodyAxis p_axis) const; diff --git a/servers/physics/collision_solver_sat.cpp b/servers/physics/collision_solver_sat.cpp index e587485fcb..44b7c9ac34 100644 --- a/servers/physics/collision_solver_sat.cpp +++ b/servers/physics/collision_solver_sat.cpp @@ -217,8 +217,6 @@ static void _generate_contacts_face_face(const Vector3 *p_points_A, int p_point_ // generate contacts //Plane plane_A(p_points_A[0],p_points_A[1],p_points_A[2]); - int added = 0; - for (int i = 0; i < clipbuf_len; i++) { real_t d = plane_B.distance_to(clipbuf_src[i]); @@ -233,7 +231,6 @@ static void _generate_contacts_face_face(const Vector3 *p_points_A, int p_point_ continue; p_callback->call(clipbuf_src[i], closest_B); - added++; } } diff --git a/servers/physics/physics_server_sw.cpp b/servers/physics/physics_server_sw.cpp index 6c25ad43f9..f1e0cbef29 100644 --- a/servers/physics/physics_server_sw.cpp +++ b/servers/physics/physics_server_sw.cpp @@ -701,6 +701,20 @@ real_t PhysicsServerSW::body_get_param(RID p_body, BodyParameter p_param) const return body->get_param(p_param); }; +void PhysicsServerSW::body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode) { + BodySW *body = body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->set_combine_mode(p_param, p_mode); +} + +PhysicsServer::CombineMode PhysicsServerSW::body_get_combine_mode(RID p_body, BodyParameter p_param) const { + BodySW *body = body_owner.get(p_body); + ERR_FAIL_COND_V(!body, COMBINE_MODE_INHERIT); + + return body->get_combine_mode(p_param); +} + void PhysicsServerSW::body_set_kinematic_safe_margin(RID p_body, real_t p_margin) { BodySW *body = body_owner.get(p_body); ERR_FAIL_COND(!body); @@ -1367,6 +1381,8 @@ void PhysicsServerSW::init() { void PhysicsServerSW::step(real_t p_step) { +#ifndef _3D_DISABLED + if (!active) return; @@ -1387,6 +1403,7 @@ void PhysicsServerSW::step(real_t p_step) { active_objects += E->get()->get_active_objects(); collision_pairs += E->get()->get_collision_pairs(); } +#endif } void PhysicsServerSW::sync(){ @@ -1395,6 +1412,8 @@ void PhysicsServerSW::sync(){ void PhysicsServerSW::flush_queries() { +#ifndef _3D_DISABLED + if (!active) return; @@ -1441,6 +1460,7 @@ void PhysicsServerSW::flush_queries() { ScriptDebugger::get_singleton()->add_profiling_frame_data("physics", values); } +#endif }; void PhysicsServerSW::finish() { diff --git a/servers/physics/physics_server_sw.h b/servers/physics/physics_server_sw.h index 3f56ba26d0..7e9700d026 100644 --- a/servers/physics/physics_server_sw.h +++ b/servers/physics/physics_server_sw.h @@ -188,6 +188,10 @@ public: virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value); virtual real_t body_get_param(RID p_body, BodyParameter p_param) const; + /// p_param accept only Bounce and Friction + virtual void body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode); + virtual CombineMode body_get_combine_mode(RID p_body, BodyParameter p_param) const; + virtual void body_set_kinematic_safe_margin(RID p_body, real_t p_margin); virtual real_t body_get_kinematic_safe_margin(RID p_body) const; @@ -230,6 +234,72 @@ public: // this function only works on physics process, errors and returns null otherwise virtual PhysicsDirectBodyState *body_get_direct_state(RID p_body); + /* SOFT BODY */ + + virtual RID soft_body_create(bool p_init_sleeping = false) { return RID(); } + + virtual void soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler) {} + + virtual void soft_body_set_space(RID p_body, RID p_space) {} + virtual RID soft_body_get_space(RID p_body) const { return RID(); } + + virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer) {} + virtual uint32_t soft_body_get_collision_layer(RID p_body) const { return 0; } + + virtual void soft_body_set_collision_mask(RID p_body, uint32_t p_mask) {} + virtual uint32_t soft_body_get_collision_mask(RID p_body) const { return 0; } + + virtual void soft_body_add_collision_exception(RID p_body, RID p_body_b) {} + virtual void soft_body_remove_collision_exception(RID p_body, RID p_body_b) {} + virtual void soft_body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) {} + + virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) {} + virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const { return Variant(); } + + virtual void soft_body_set_transform(RID p_body, const Transform &p_transform) {} + virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const { return Vector3(); } + + virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable) {} + virtual bool soft_body_is_ray_pickable(RID p_body) const { return false; } + + virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) {} + virtual int soft_body_get_simulation_precision(RID p_body) { return 0; } + + virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass) {} + virtual real_t soft_body_get_total_mass(RID p_body) { return 0.; } + + virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) {} + virtual real_t soft_body_get_linear_stiffness(RID p_body) { return 0.; } + + virtual void soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness) {} + virtual real_t soft_body_get_areaAngular_stiffness(RID p_body) { return 0.; } + + virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) {} + virtual real_t soft_body_get_volume_stiffness(RID p_body) { return 0.; } + + virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) {} + virtual real_t soft_body_get_pressure_coefficient(RID p_body) { return 0.; } + + virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) {} + virtual real_t soft_body_get_pose_matching_coefficient(RID p_body) { return 0.; } + + virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) {} + virtual real_t soft_body_get_damping_coefficient(RID p_body) { return 0.; } + + virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) {} + virtual real_t soft_body_get_drag_coefficient(RID p_body) { return 0.; } + + virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh) {} + + virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) {} + virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) { return Vector3(); } + + virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const { return Vector3(); } + + virtual void soft_body_remove_all_pinned_points(RID p_body) {} + virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) {} + virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) { return 0; } + /* JOINT API */ virtual RID joint_create_pin(RID p_body_A, const Vector3 &p_local_A, RID p_body_B, const Vector3 &p_local_B); diff --git a/servers/physics_2d/body_2d_sw.cpp b/servers/physics_2d/body_2d_sw.cpp index aa063d6c1e..fcd2a65ee7 100644 --- a/servers/physics_2d/body_2d_sw.cpp +++ b/servers/physics_2d/body_2d_sw.cpp @@ -405,6 +405,22 @@ void Body2DSW::_compute_area_gravity_and_dampenings(const Area2DSW *p_area) { area_angular_damp += p_area->get_angular_damp(); } +void Body2DSW::set_combine_mode(Physics2DServer::BodyParameter p_param, Physics2DServer::CombineMode p_mode) { + if (p_param == Physics2DServer::BODY_PARAM_BOUNCE) { + bounce_combine_mode = p_mode; + } else { + friction_combine_mode = p_mode; + } +} + +Physics2DServer::CombineMode Body2DSW::get_combine_mode(Physics2DServer::BodyParameter p_param) const { + if (p_param == Physics2DServer::BODY_PARAM_BOUNCE) { + return bounce_combine_mode; + } else { + return friction_combine_mode; + } +} + void Body2DSW::integrate_forces(real_t p_step) { if (mode == Physics2DServer::BODY_MODE_STATIC) diff --git a/servers/physics_2d/body_2d_sw.h b/servers/physics_2d/body_2d_sw.h index 782adf3416..301bd6b299 100644 --- a/servers/physics_2d/body_2d_sw.h +++ b/servers/physics_2d/body_2d_sw.h @@ -54,6 +54,8 @@ class Body2DSW : public CollisionObject2DSW { real_t mass; real_t bounce; real_t friction; + Physics2DServer::CombineMode bounce_combine_mode; + Physics2DServer::CombineMode friction_combine_mode; real_t _inv_mass; real_t _inv_inertia; @@ -256,6 +258,12 @@ public: _FORCE_INLINE_ real_t get_linear_damp() const { return linear_damp; } _FORCE_INLINE_ real_t get_angular_damp() const { return angular_damp; } + void set_combine_mode(Physics2DServer::BodyParameter p_param, Physics2DServer::CombineMode p_mode); + Physics2DServer::CombineMode get_combine_mode(Physics2DServer::BodyParameter p_param) const; + + _FORCE_INLINE_ Physics2DServer::CombineMode get_bounce_combine_mode() const { return bounce_combine_mode; } + _FORCE_INLINE_ Physics2DServer::CombineMode get_friction_combine_mode() const { return friction_combine_mode; } + void integrate_forces(real_t p_step); void integrate_velocities(real_t p_step); diff --git a/servers/physics_2d/body_pair_2d_sw.cpp b/servers/physics_2d/body_pair_2d_sw.cpp index 61c0e0063f..be8dcf6fa8 100644 --- a/servers/physics_2d/body_pair_2d_sw.cpp +++ b/servers/physics_2d/body_pair_2d_sw.cpp @@ -219,6 +219,44 @@ bool BodyPair2DSW::_test_ccd(real_t p_step, Body2DSW *p_A, int p_shape_A, const return true; } +real_t combine_bounce(Body2DSW *A, Body2DSW *B) { + const Physics2DServer::CombineMode cm = A->get_bounce_combine_mode(); + + switch (cm) { + case Physics2DServer::COMBINE_MODE_INHERIT: + if (B->get_bounce_combine_mode() != Physics2DServer::COMBINE_MODE_INHERIT) + return combine_bounce(B, A); + // else use MAX [This is used when the two bodies doesn't use physical material] + case Physics2DServer::COMBINE_MODE_MAX: + return MAX(A->get_bounce(), B->get_bounce()); + case Physics2DServer::COMBINE_MODE_MIN: + return MIN(A->get_bounce(), B->get_bounce()); + case Physics2DServer::COMBINE_MODE_MULTIPLY: + return A->get_bounce() * B->get_bounce(); + default: // Is always Physics2DServer::COMBINE_MODE_AVERAGE: + return (A->get_bounce() + B->get_bounce()) / 2; + } +} + +real_t combine_friction(Body2DSW *A, Body2DSW *B) { + const Physics2DServer::CombineMode cm = A->get_friction_combine_mode(); + + switch (cm) { + case Physics2DServer::COMBINE_MODE_INHERIT: + if (B->get_friction_combine_mode() != Physics2DServer::COMBINE_MODE_INHERIT) + return combine_friction(B, A); + // else use Multiply [This is used when the two bodies doesn't use physical material] + case Physics2DServer::COMBINE_MODE_MULTIPLY: + return A->get_friction() * B->get_friction(); + case Physics2DServer::COMBINE_MODE_MAX: + return MAX(A->get_friction(), B->get_friction()); + case Physics2DServer::COMBINE_MODE_MIN: + return MIN(A->get_friction(), B->get_friction()); + default: // Is always Physics2DServer::COMBINE_MODE_AVERAGE: + return (A->get_friction() + B->get_friction()) / 2; + } +} + bool BodyPair2DSW::setup(real_t p_step) { //cannot collide @@ -432,7 +470,7 @@ bool BodyPair2DSW::setup(real_t p_step) { #endif - c.bounce = MAX(A->get_bounce(), B->get_bounce()); + c.bounce = combine_bounce(A, B); if (c.bounce) { Vector2 crA(-A->get_angular_velocity() * c.rA.y, A->get_angular_velocity() * c.rA.x); @@ -488,7 +526,7 @@ void BodyPair2DSW::solve(real_t p_step) { real_t jnOld = c.acc_normal_impulse; c.acc_normal_impulse = MAX(jnOld + jn, 0.0f); - real_t friction = A->get_friction() * B->get_friction(); + real_t friction = combine_friction(A, B); real_t jtMax = friction * c.acc_normal_impulse; real_t jt = -vt * c.mass_tangent; diff --git a/servers/physics_2d/collision_solver_2d_sw.cpp b/servers/physics_2d/collision_solver_2d_sw.cpp index efee98a35a..6ce019f36e 100644 --- a/servers/physics_2d/collision_solver_2d_sw.cpp +++ b/servers/physics_2d/collision_solver_2d_sw.cpp @@ -72,7 +72,7 @@ bool CollisionSolver2DSW::solve_static_line(const Shape2DSW *p_shape_A, const Tr return found; } -bool CollisionSolver2DSW::solve_raycast(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis) { +bool CollisionSolver2DSW::solve_raycast(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis) { const RayShape2DSW *ray = static_cast<const RayShape2DSW *>(p_shape_A); if (p_shape_B->get_type() == Physics2DServer::SHAPE_RAY) @@ -80,6 +80,11 @@ bool CollisionSolver2DSW::solve_raycast(const Shape2DSW *p_shape_A, const Transf Vector2 from = p_transform_A.get_origin(); Vector2 to = from + p_transform_A[1] * ray->get_length(); + if (p_motion_A != Vector2()) { + //not the best but should be enough + Vector2 normal = (to - from).normalized(); + to += normal * MAX(0.0, normal.dot(p_motion_A)); + } Vector2 support_A = to; Transform2D invb = p_transform_B.affine_inverse(); @@ -270,9 +275,9 @@ bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p } if (swap) { - return solve_raycast(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, sep_axis); + return solve_raycast(p_shape_B, p_motion_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, sep_axis); } else { - return solve_raycast(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, sep_axis); + return solve_raycast(p_shape_A, p_motion_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, sep_axis); } } else if (concave_B) { diff --git a/servers/physics_2d/collision_solver_2d_sw.h b/servers/physics_2d/collision_solver_2d_sw.h index e39c41fb75..6faa166115 100644 --- a/servers/physics_2d/collision_solver_2d_sw.h +++ b/servers/physics_2d/collision_solver_2d_sw.h @@ -41,7 +41,7 @@ private: static bool solve_static_line(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); static void concave_callback(void *p_userdata, Shape2DSW *p_convex); static bool solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis = NULL, real_t p_margin_A = 0, real_t p_margin_B = 0); - static bool solve_raycast(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis = NULL); + static bool solve_raycast(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis = NULL); public: static bool solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *sep_axis = NULL, real_t p_margin_A = 0, real_t p_margin_B = 0); diff --git a/servers/physics_2d/physics_2d_server_sw.cpp b/servers/physics_2d/physics_2d_server_sw.cpp index a14fed8184..cfcef8cb04 100644 --- a/servers/physics_2d/physics_2d_server_sw.cpp +++ b/servers/physics_2d/physics_2d_server_sw.cpp @@ -789,6 +789,22 @@ real_t Physics2DServerSW::body_get_param(RID p_body, BodyParameter p_param) cons return body->get_param(p_param); }; +void Physics2DServerSW::body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode) { + + Body2DSW *body = body_owner.get(p_body); + ERR_FAIL_COND(!body); + + body->set_combine_mode(p_param, p_mode); +} + +Physics2DServer::CombineMode Physics2DServerSW::body_get_combine_mode(RID p_body, BodyParameter p_param) const { + + Body2DSW *body = body_owner.get(p_body); + ERR_FAIL_COND_V(!body, COMBINE_MODE_INHERIT); + + return body->get_combine_mode(p_param); +} + void Physics2DServerSW::body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { Body2DSW *body = body_owner.get(p_body); @@ -962,22 +978,40 @@ void Physics2DServerSW::body_set_pickable(RID p_body, bool p_pickable) { body->set_pickable(p_pickable); } -bool Physics2DServerSW::body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin, MotionResult *r_result) { +bool Physics2DServerSW::body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin, MotionResult *r_result, bool p_exclude_raycast_shapes) { + + Body2DSW *body = body_owner.get(p_body); + ERR_FAIL_COND_V(!body, false); + ERR_FAIL_COND_V(!body->get_space(), false); + ERR_FAIL_COND_V(body->get_space()->is_locked(), false); + + return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, p_margin, r_result, p_exclude_raycast_shapes); +} + +int Physics2DServerSW::body_test_ray_separation(RID p_body, const Transform2D &p_transform, bool p_infinite_inertia, Vector2 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin) { Body2DSW *body = body_owner.get(p_body); ERR_FAIL_COND_V(!body, false); ERR_FAIL_COND_V(!body->get_space(), false); ERR_FAIL_COND_V(body->get_space()->is_locked(), false); - return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, p_margin, r_result); + return body->get_space()->test_body_ray_separation(body, p_transform, p_infinite_inertia, r_recover_motion, r_results, p_result_max, p_margin); } Physics2DDirectBodyState *Physics2DServerSW::body_get_direct_state(RID p_body) { + if ((using_threads && !doing_sync)) { + ERR_EXPLAIN("Body state is inaccessible right now, wait for iteration or physics process notification."); + ERR_FAIL_V(NULL); + } + + if (!body_owner.owns(p_body)) + return NULL; + Body2DSW *body = body_owner.get(p_body); ERR_FAIL_COND_V(!body, NULL); - if ((using_threads && !doing_sync) || body->get_space()->is_locked()) { + if (body->get_space()->is_locked()) { ERR_EXPLAIN("Body state is inaccessible right now, wait for iteration or physics process notification."); ERR_FAIL_V(NULL); diff --git a/servers/physics_2d/physics_2d_server_sw.h b/servers/physics_2d/physics_2d_server_sw.h index 036eb934e1..bf00746063 100644 --- a/servers/physics_2d/physics_2d_server_sw.h +++ b/servers/physics_2d/physics_2d_server_sw.h @@ -200,6 +200,10 @@ public: virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value); virtual real_t body_get_param(RID p_body, BodyParameter p_param) const; + /// p_param accept only Bounce and Friction + virtual void body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode); + virtual CombineMode body_get_combine_mode(RID p_body, BodyParameter p_param) const; + virtual void body_set_state(RID p_body, BodyState p_state, const Variant &p_variant); virtual Variant body_get_state(RID p_body, BodyState p_state) const; @@ -232,7 +236,8 @@ public: virtual void body_set_pickable(RID p_body, bool p_pickable); - virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = NULL); + virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = NULL, bool p_exclude_raycast_shapes = true); + virtual int body_test_ray_separation(RID p_body, const Transform2D &p_transform, bool p_infinite_inertia, Vector2 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin = 0.001); // this function only works on physics process, errors and returns null otherwise virtual Physics2DDirectBodyState *body_get_direct_state(RID p_body); diff --git a/servers/physics_2d/physics_2d_server_wrap_mt.h b/servers/physics_2d/physics_2d_server_wrap_mt.h index a15e8bde8b..a85cd5ef8d 100644 --- a/servers/physics_2d/physics_2d_server_wrap_mt.h +++ b/servers/physics_2d/physics_2d_server_wrap_mt.h @@ -211,6 +211,9 @@ public: FUNC3(body_set_param, RID, BodyParameter, real_t); FUNC2RC(real_t, body_get_param, RID, BodyParameter); + FUNC3(body_set_combine_mode, RID, BodyParameter, CombineMode); + FUNC2RC(CombineMode, body_get_combine_mode, RID, BodyParameter); + FUNC3(body_set_state, RID, BodyState, const Variant &); FUNC2RC(Variant, body_get_state, RID, BodyState); @@ -245,10 +248,16 @@ public: FUNC2(body_set_pickable, RID, bool); - bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = NULL) { + bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = NULL, bool p_exclude_raycast_shapes = true) { + + ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false); + return physics_2d_server->body_test_motion(p_body, p_from, p_motion, p_infinite_inertia, p_margin, r_result, p_exclude_raycast_shapes); + } + + int body_test_ray_separation(RID p_body, const Transform2D &p_transform, bool p_infinite_inertia, Vector2 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin = 0.001) { ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false); - return physics_2d_server->body_test_motion(p_body, p_from, p_motion, p_infinite_inertia, p_margin, r_result); + return physics_2d_server->body_test_ray_separation(p_body, p_transform, p_infinite_inertia, r_recover_motion, r_results, p_result_max, p_margin); } // this function only works on physics process, errors and returns null otherwise diff --git a/servers/physics_2d/space_2d_sw.cpp b/servers/physics_2d/space_2d_sw.cpp index 0e1f74d8d0..6e45951f42 100644 --- a/servers/physics_2d/space_2d_sw.cpp +++ b/servers/physics_2d/space_2d_sw.cpp @@ -487,7 +487,156 @@ int Space2DSW::_cull_aabb_for_body(Body2DSW *p_body, const Rect2 &p_aabb) { return amount; } -bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin, Physics2DServer::MotionResult *r_result) { +int Space2DSW::test_body_ray_separation(Body2DSW *p_body, const Transform2D &p_transform, bool p_infinite_inertia, Vector2 &r_recover_motion, Physics2DServer::SeparationResult *r_results, int p_result_max, real_t p_margin) { + + Rect2 body_aabb; + + for (int i = 0; i < p_body->get_shape_count(); i++) { + + if (i == 0) + body_aabb = p_body->get_shape_aabb(i); + else + body_aabb = body_aabb.merge(p_body->get_shape_aabb(i)); + } + + // Undo the currently transform the physics server is aware of and apply the provided one + body_aabb = p_transform.xform(p_body->get_inv_transform().xform(body_aabb)); + body_aabb = body_aabb.grow(p_margin); + + Transform2D body_transform = p_transform; + + for (int i = 0; i < p_result_max; i++) { + //reset results + r_results[i].collision_depth = 0; + } + + int rays_found = 0; + + { + // raycast AND separate + + const int max_results = 32; + int recover_attempts = 4; + Vector2 sr[max_results * 2]; + Physics2DServerSW::CollCbkData cbk; + cbk.max = max_results; + Physics2DServerSW::CollCbkData *cbkptr = &cbk; + CollisionSolver2DSW::CallbackResult cbkres = Physics2DServerSW::_shape_col_cbk; + + do { + + Vector2 recover_motion; + + bool collided = false; + + int amount = _cull_aabb_for_body(p_body, body_aabb); + int ray_index = 0; + + for (int j = 0; j < p_body->get_shape_count(); j++) { + if (p_body->is_shape_set_as_disabled(j)) + continue; + + Shape2DSW *body_shape = p_body->get_shape(j); + + if (body_shape->get_type() != Physics2DServer::SHAPE_RAY) + continue; + + Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(j); + + for (int i = 0; i < amount; i++) { + + const CollisionObject2DSW *col_obj = intersection_query_results[i]; + int shape_idx = intersection_query_subindex_results[i]; + + cbk.amount = 0; + cbk.ptr = sr; + cbk.invalid_by_dir = 0; + + if (CollisionObject2DSW::TYPE_BODY == col_obj->get_type()) { + const Body2DSW *b = static_cast<const Body2DSW *>(col_obj); + if (p_infinite_inertia && Physics2DServer::BODY_MODE_STATIC != b->get_mode() && Physics2DServer::BODY_MODE_KINEMATIC != b->get_mode()) { + continue; + } + } + + if (col_obj->is_shape_set_as_one_way_collision(shape_idx)) { + + cbk.valid_dir = body_shape_xform.get_axis(1).normalized(); + cbk.valid_depth = p_margin; //only valid depth is the collision margin + cbk.invalid_by_dir = 0; + + } else { + cbk.valid_dir = Vector2(); + cbk.valid_depth = 0; + cbk.invalid_by_dir = 0; + } + + Shape2DSW *against_shape = col_obj->get_shape(shape_idx); + if (CollisionSolver2DSW::solve(body_shape, body_shape_xform, Vector2(), against_shape, col_obj->get_transform() * col_obj->get_shape_transform(shape_idx), Vector2(), cbkres, cbkptr, NULL, p_margin)) { + if (cbk.amount > 0) { + collided = true; + } + + if (ray_index < p_result_max) { + Physics2DServer::SeparationResult &result = r_results[ray_index]; + + for (int k = 0; k < cbk.amount; k++) { + Vector2 a = sr[k * 2 + 0]; + Vector2 b = sr[k * 2 + 1]; + + recover_motion += (b - a) * 0.4; + + float depth = a.distance_to(b); + if (depth > result.collision_depth) { + + result.collision_depth = depth; + result.collision_point = b; + result.collision_normal = (b - a).normalized(); + result.collision_local_shape = shape_idx; + result.collider = col_obj->get_self(); + result.collider_id = col_obj->get_instance_id(); + result.collider_metadata = col_obj->get_shape_metadata(shape_idx); + if (col_obj->get_type() == CollisionObject2DSW::TYPE_BODY) { + Body2DSW *body = (Body2DSW *)col_obj; + + Vector2 rel_vec = b - body->get_transform().get_origin(); + result.collider_velocity = Vector2(-body->get_angular_velocity() * rel_vec.y, body->get_angular_velocity() * rel_vec.x) + body->get_linear_velocity(); + } + } + } + } + } + } + + ray_index++; + } + + rays_found = MAX(ray_index, rays_found); + + if (!collided || recover_motion == Vector2()) { + break; + } + + body_transform.elements[2] += recover_motion; + body_aabb.position += recover_motion; + + recover_attempts--; + } while (recover_attempts); + } + + //optimize results (remove non colliding) + for (int i = 0; i < rays_found; i++) { + if (r_results[i].collision_depth == 0) { + rays_found--; + SWAP(r_results[i], r_results[rays_found]); + } + } + + r_recover_motion = body_transform.elements[2] - p_transform.elements[2]; + return rays_found; +} + +bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin, Physics2DServer::MotionResult *r_result, bool p_exclude_raycast_shapes) { //give me back regular physics engine logic //this is madness @@ -547,8 +696,12 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co if (p_body->is_shape_set_as_disabled(j)) continue; - Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(j); Shape2DSW *body_shape = p_body->get_shape(j); + if (p_exclude_raycast_shapes && body_shape->get_type() == Physics2DServer::SHAPE_RAY) { + continue; + } + + Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(j); for (int i = 0; i < amount; i++) { const CollisionObject2DSW *col_obj = intersection_query_results[i]; @@ -635,8 +788,12 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co if (p_body->is_shape_set_as_disabled(body_shape_idx)) continue; - Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(body_shape_idx); Shape2DSW *body_shape = p_body->get_shape(body_shape_idx); + if (p_exclude_raycast_shapes && body_shape->get_type() == Physics2DServer::SHAPE_RAY) { + continue; + } + + Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(body_shape_idx); bool stuck = false; diff --git a/servers/physics_2d/space_2d_sw.h b/servers/physics_2d/space_2d_sw.h index 79349c46f3..959e15e12d 100644 --- a/servers/physics_2d/space_2d_sw.h +++ b/servers/physics_2d/space_2d_sw.h @@ -182,7 +182,8 @@ public: int get_collision_pairs() const { return collision_pairs; } - bool test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin, Physics2DServer::MotionResult *r_result); + bool test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin, Physics2DServer::MotionResult *r_result, bool p_exclude_raycast_shapes = true); + int test_body_ray_separation(Body2DSW *p_body, const Transform2D &p_transform, bool p_infinite_inertia, Vector2 &r_recover_motion, Physics2DServer::SeparationResult *r_results, int p_result_max, real_t p_margin); void set_debug_contacts(int p_amount) { contact_debug.resize(p_amount); } _FORCE_INLINE_ bool is_debugging_contacts() const { return !contact_debug.empty(); } diff --git a/servers/physics_2d_server.h b/servers/physics_2d_server.h index ba5232f7fe..89c649af49 100644 --- a/servers/physics_2d_server.h +++ b/servers/physics_2d_server.h @@ -416,6 +416,19 @@ public: virtual void body_set_param(RID p_body, BodyParameter p_param, float p_value) = 0; virtual float body_get_param(RID p_body, BodyParameter p_param) const = 0; + enum CombineMode { + COMBINE_MODE_MAX, + COMBINE_MODE_MIN, + COMBINE_MODE_MULTIPLY, + COMBINE_MODE_AVERAGE, + + COMBINE_MODE_INHERIT /// Inherit from other body or use COMBINE_MODE_MAX (Restitution) COMBINE_MODE_MULTIPLY (Friction) + }; + + /// p_param accept only Bounce and Friction + virtual void body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode) = 0; + virtual CombineMode body_get_combine_mode(RID p_body, BodyParameter p_param) const = 0; + //state enum BodyState { BODY_STATE_TRANSFORM, @@ -479,7 +492,22 @@ public: Variant collider_metadata; }; - virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, float p_margin = 0.001, MotionResult *r_result = NULL) = 0; + virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, float p_margin = 0.001, MotionResult *r_result = NULL, bool p_exclude_raycast_shapes = true) = 0; + + struct SeparationResult { + + float collision_depth; + Vector2 collision_point; + Vector2 collision_normal; + Vector2 collider_velocity; + int collision_local_shape; + ObjectID collider_id; + RID collider; + int collider_shape; + Variant collider_metadata; + }; + + virtual int body_test_ray_separation(RID p_body, const Transform2D &p_transform, bool p_infinite_inertia, Vector2 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin = 0.001) = 0; /* JOINT API */ diff --git a/servers/physics_server.cpp b/servers/physics_server.cpp index de173491b2..b4bd4cb35f 100644 --- a/servers/physics_server.cpp +++ b/servers/physics_server.cpp @@ -400,6 +400,8 @@ void PhysicsShapeQueryResult::_bind_methods() { void PhysicsServer::_bind_methods() { +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("shape_create", "type"), &PhysicsServer::shape_create); ClassDB::bind_method(D_METHOD("shape_set_data", "shape", "data"), &PhysicsServer::shape_set_data); @@ -737,6 +739,8 @@ void PhysicsServer::_bind_methods() { BIND_ENUM_CONSTANT(BODY_AXIS_ANGULAR_X); BIND_ENUM_CONSTANT(BODY_AXIS_ANGULAR_Y); BIND_ENUM_CONSTANT(BODY_AXIS_ANGULAR_Z); + +#endif } PhysicsServer::PhysicsServer() { diff --git a/servers/physics_server.h b/servers/physics_server.h index 8ecf17c0e6..497d23c555 100644 --- a/servers/physics_server.h +++ b/servers/physics_server.h @@ -399,6 +399,19 @@ public: virtual void body_set_param(RID p_body, BodyParameter p_param, float p_value) = 0; virtual float body_get_param(RID p_body, BodyParameter p_param) const = 0; + enum CombineMode { + COMBINE_MODE_MAX, + COMBINE_MODE_MIN, + COMBINE_MODE_MULTIPLY, + COMBINE_MODE_AVERAGE, + + COMBINE_MODE_INHERIT /// Inherit from other body or use COMBINE_MODE_MAX (Restitution) COMBINE_MODE_MULTIPLY (Friction) + }; + + /// p_param accept only Bounce and Friction + virtual void body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode) = 0; + virtual CombineMode body_get_combine_mode(RID p_body, BodyParameter p_param) const = 0; + virtual void body_set_kinematic_safe_margin(RID p_body, real_t p_margin) = 0; virtual real_t body_get_kinematic_safe_margin(RID p_body) const = 0; @@ -477,6 +490,72 @@ public: virtual bool body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = NULL) = 0; + /* SOFT BODY */ + + virtual RID soft_body_create(bool p_init_sleeping = false) = 0; + + virtual void soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler) = 0; + + virtual void soft_body_set_space(RID p_body, RID p_space) = 0; + virtual RID soft_body_get_space(RID p_body) const = 0; + + virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh) = 0; + + virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer) = 0; + virtual uint32_t soft_body_get_collision_layer(RID p_body) const = 0; + + virtual void soft_body_set_collision_mask(RID p_body, uint32_t p_mask) = 0; + virtual uint32_t soft_body_get_collision_mask(RID p_body) const = 0; + + virtual void soft_body_add_collision_exception(RID p_body, RID p_body_b) = 0; + virtual void soft_body_remove_collision_exception(RID p_body, RID p_body_b) = 0; + virtual void soft_body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) = 0; + + virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) = 0; + virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const = 0; + + virtual void soft_body_set_transform(RID p_body, const Transform &p_transform) = 0; + virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const = 0; + + virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable) = 0; + virtual bool soft_body_is_ray_pickable(RID p_body) const = 0; + + virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) = 0; + virtual int soft_body_get_simulation_precision(RID p_body) = 0; + + virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass) = 0; + virtual real_t soft_body_get_total_mass(RID p_body) = 0; + + virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) = 0; + virtual real_t soft_body_get_linear_stiffness(RID p_body) = 0; + + virtual void soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness) = 0; + virtual real_t soft_body_get_areaAngular_stiffness(RID p_body) = 0; + + virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) = 0; + virtual real_t soft_body_get_volume_stiffness(RID p_body) = 0; + + virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) = 0; + virtual real_t soft_body_get_pressure_coefficient(RID p_body) = 0; + + virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) = 0; + virtual real_t soft_body_get_pose_matching_coefficient(RID p_body) = 0; + + virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) = 0; + virtual real_t soft_body_get_damping_coefficient(RID p_body) = 0; + + virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) = 0; + virtual real_t soft_body_get_drag_coefficient(RID p_body) = 0; + + virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) = 0; + virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) = 0; + + virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const = 0; + + virtual void soft_body_remove_all_pinned_points(RID p_body) = 0; + virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) = 0; + virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) = 0; + /* JOINT API */ enum JointType { diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index 3245e7be70..a8f4377ce7 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -104,10 +104,12 @@ public: VS::ShadowCastingSetting cast_shadows; + //fit in 32 bits bool mirror : 8; bool receive_shadows : 8; bool visible : 8; - bool baked_light : 8; //this flag is only to know if it actually did use baked light + bool baked_light : 4; //this flag is only to know if it actually did use baked light + bool redraw_if_visible : 4; float depth; //used for sorting @@ -131,6 +133,7 @@ public: depth_layer = 0; layer_mask = 1; baked_light = false; + redraw_if_visible = false; lightmap_capture = NULL; } }; @@ -201,6 +204,7 @@ public: virtual void textures_keep_original(bool p_enable) = 0; virtual void texture_set_proxy(RID p_proxy, RID p_base) = 0; + virtual void texture_set_force_redraw_if_visible(RID p_texture, bool p_enable) = 0; /* SKY API */ @@ -276,6 +280,7 @@ public: virtual AABB mesh_get_custom_aabb(RID p_mesh) const = 0; virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton) const = 0; + virtual void mesh_clear(RID p_mesh) = 0; /* MULTIMESH API */ diff --git a/servers/visual/shader_language.cpp b/servers/visual/shader_language.cpp index 2069e64c43..fd1eb77143 100644 --- a/servers/visual/shader_language.cpp +++ b/servers/visual/shader_language.cpp @@ -2545,7 +2545,9 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons TkPos pos = _get_tkpos(); tk = _get_token(); - if (tk.type == TK_PERIOD) { + if (tk.type == TK_CURSOR) { + //do nothing + } else if (tk.type == TK_PERIOD) { StringName identifier; if (_get_completable_identifier(p_block, COMPLETION_INDEX, identifier)) { @@ -3583,7 +3585,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui return OK; } -Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_functions, const Set<String> &p_render_modes, const Set<String> &p_shader_types) { +Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types) { Token tk = _get_token(); @@ -3642,7 +3644,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct return ERR_PARSE_ERROR; } - if (!p_render_modes.has(mode)) { + if (p_render_modes.find(mode) == -1) { _set_error("Invalid render mode: '" + String(mode) + "'"); return ERR_PARSE_ERROR; } @@ -4097,7 +4099,7 @@ String ShaderLanguage::get_shader_type(const String &p_code) { return String(); } -Error ShaderLanguage::compile(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Set<String> &p_render_modes, const Set<String> &p_shader_types) { +Error ShaderLanguage::compile(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types) { clear(); @@ -4114,7 +4116,7 @@ Error ShaderLanguage::compile(const String &p_code, const Map<StringName, Functi return OK; } -Error ShaderLanguage::complete(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Set<String> &p_render_modes, const Set<String> &p_shader_types, List<String> *r_options, String &r_call_hint) { +Error ShaderLanguage::complete(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types, List<String> *r_options, String &r_call_hint) { clear(); @@ -4130,13 +4132,13 @@ Error ShaderLanguage::complete(const String &p_code, const Map<StringName, Funct switch (completion_type) { case COMPLETION_NONE: { - //do none - return ERR_PARSE_ERROR; + //do nothing + return OK; } break; case COMPLETION_RENDER_MODE: { - for (const Set<String>::Element *E = p_render_modes.front(); E; E = E->next()) { + for (int i = 0; i < p_render_modes.size(); i++) { - r_options->push_back(E->get()); + r_options->push_back(p_render_modes[i]); } return OK; diff --git a/servers/visual/shader_language.h b/servers/visual/shader_language.h index 720511e18d..9b84c5f195 100644 --- a/servers/visual/shader_language.h +++ b/servers/visual/shader_language.h @@ -650,7 +650,7 @@ private: Error _parse_block(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, bool p_just_one = false, bool p_can_break = false, bool p_can_continue = false); - Error _parse_shader(const Map<StringName, FunctionInfo> &p_functions, const Set<String> &p_render_modes, const Set<String> &p_shader_types); + Error _parse_shader(const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types); public: //static void get_keyword_list(ShaderType p_type,List<String> *p_keywords); @@ -658,8 +658,8 @@ public: void clear(); static String get_shader_type(const String &p_code); - Error compile(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Set<String> &p_render_modes, const Set<String> &p_shader_types); - Error complete(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Set<String> &p_render_modes, const Set<String> &p_shader_types, List<String> *r_options, String &r_call_hint); + Error compile(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types); + Error complete(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types, List<String> *r_options, String &r_call_hint); String get_error_text(); int get_error_line(); diff --git a/servers/visual/shader_types.cpp b/servers/visual/shader_types.cpp index a92e1b06d2..0de8676f32 100644 --- a/servers/visual/shader_types.cpp +++ b/servers/visual/shader_types.cpp @@ -35,7 +35,7 @@ const Map<StringName, ShaderLanguage::FunctionInfo> &ShaderTypes::get_functions( return shader_modes[p_mode].functions; } -const Set<String> &ShaderTypes::get_modes(VS::ShaderMode p_mode) { +const Vector<StringName> &ShaderTypes::get_modes(VS::ShaderMode p_mode) { return shader_modes[p_mode].modes; } @@ -140,43 +140,45 @@ ShaderTypes::ShaderTypes() { shader_modes[VS::SHADER_SPATIAL].functions["light"].can_discard = true; - shader_modes[VS::SHADER_SPATIAL].modes.insert("blend_mix"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("blend_add"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("blend_sub"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("blend_mul"); + //order used puts first enum mode (default) first + shader_modes[VS::SHADER_SPATIAL].modes.push_back("blend_mix"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("blend_add"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("blend_sub"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("blend_mul"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("depth_draw_opaque"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("depth_draw_always"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("depth_draw_never"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("depth_draw_alpha_prepass"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("depth_draw_opaque"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("depth_draw_always"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("depth_draw_never"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("depth_draw_alpha_prepass"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("depth_test_disable"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("depth_test_disable"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("cull_front"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("cull_back"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("cull_disabled"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("cull_back"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("cull_front"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("cull_disabled"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("unshaded"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("unshaded"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("diffuse_lambert"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("diffuse_lambert_wrap"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("diffuse_oren_nayar"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("diffuse_burley"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("diffuse_toon"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("diffuse_lambert"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("diffuse_lambert_wrap"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("diffuse_oren_nayar"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("diffuse_burley"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("diffuse_toon"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("specular_schlick_ggx"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("specular_blinn"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("specular_phong"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("specular_toon"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("specular_disabled"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("specular_schlick_ggx"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("specular_blinn"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("specular_phong"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("specular_toon"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("specular_disabled"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("skip_vertex_transform"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("world_vertex_coords"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("ensure_correct_normals"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("skip_vertex_transform"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("world_vertex_coords"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("ensure_correct_normals"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("shadows_disabled"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("shadows_disabled"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("ambient_light_disabled"); - shader_modes[VS::SHADER_SPATIAL].modes.insert("vertex_lighting"); + shader_modes[VS::SHADER_SPATIAL].modes.push_back("vertex_lighting"); /************ CANVAS ITEM **************************/ @@ -227,17 +229,17 @@ ShaderTypes::ShaderTypes() { shader_modes[VS::SHADER_CANVAS_ITEM].functions["light"].built_ins["TIME"] = constt(ShaderLanguage::TYPE_FLOAT); shader_modes[VS::SHADER_CANVAS_ITEM].functions["light"].can_discard = true; - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("skip_vertex_transform"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("skip_vertex_transform"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("blend_mix"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("blend_add"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("blend_sub"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("blend_mul"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("blend_premul_alpha"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("blend_disabled"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("blend_mix"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("blend_add"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("blend_sub"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("blend_mul"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("blend_premul_alpha"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("blend_disabled"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("unshaded"); - shader_modes[VS::SHADER_CANVAS_ITEM].modes.insert("light_only"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("unshaded"); + shader_modes[VS::SHADER_CANVAS_ITEM].modes.push_back("light_only"); /************ PARTICLES **************************/ @@ -257,9 +259,9 @@ ShaderTypes::ShaderTypes() { shader_modes[VS::SHADER_PARTICLES].functions["vertex"].built_ins["RANDOM_SEED"] = constt(ShaderLanguage::TYPE_UINT); shader_modes[VS::SHADER_PARTICLES].functions["vertex"].can_discard = false; - shader_modes[VS::SHADER_PARTICLES].modes.insert("disable_force"); - shader_modes[VS::SHADER_PARTICLES].modes.insert("disable_velocity"); - shader_modes[VS::SHADER_PARTICLES].modes.insert("keep_data"); + shader_modes[VS::SHADER_PARTICLES].modes.push_back("disable_force"); + shader_modes[VS::SHADER_PARTICLES].modes.push_back("disable_velocity"); + shader_modes[VS::SHADER_PARTICLES].modes.push_back("keep_data"); shader_types.insert("spatial"); shader_types.insert("canvas_item"); diff --git a/servers/visual/shader_types.h b/servers/visual/shader_types.h index 1f43ff9c92..0680ec8242 100644 --- a/servers/visual/shader_types.h +++ b/servers/visual/shader_types.h @@ -31,14 +31,16 @@ #ifndef SHADERTYPES_H #define SHADERTYPES_H +#include "ordered_hash_map.h" #include "servers/visual_server.h" #include "shader_language.h" + class ShaderTypes { struct Type { Map<StringName, ShaderLanguage::FunctionInfo> functions; - Set<String> modes; + Vector<StringName> modes; }; Map<VS::ShaderMode, Type> shader_modes; @@ -51,7 +53,7 @@ public: static ShaderTypes *get_singleton() { return singleton; } const Map<StringName, ShaderLanguage::FunctionInfo> &get_functions(VS::ShaderMode p_mode); - const Set<String> &get_modes(VS::ShaderMode p_mode); + const Vector<StringName> &get_modes(VS::ShaderMode p_mode); const Set<String> &get_types(); ShaderTypes(); diff --git a/servers/visual/visual_server_raster.cpp b/servers/visual/visual_server_raster.cpp index 163aa9bb07..5f207b1d3f 100644 --- a/servers/visual/visual_server_raster.cpp +++ b/servers/visual/visual_server_raster.cpp @@ -95,10 +95,11 @@ void VisualServerRaster::request_frame_drawn_callback(Object *p_where, const Str void VisualServerRaster::draw(bool p_swap_buffers) { - changes = 0; - + //needs to be done before changes is reset to 0, to not force the editor to redraw VS::get_singleton()->emit_signal("frame_pre_draw"); + changes = 0; + VSG::rasterizer->begin_frame(); VSG::scene->update_dirty_instances(); //update scene stuff diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index c03005592f..ec0d02ed2a 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -173,6 +173,8 @@ public: BIND2(texture_set_proxy, RID, RID) + BIND2(texture_set_force_redraw_if_visible, RID, bool) + /* SKY API */ BIND0R(RID, sky_create) diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp index 697c890c9a..887cd7429a 100644 --- a/servers/visual/visual_server_scene.cpp +++ b/servers/visual/visual_server_scene.cpp @@ -820,6 +820,11 @@ void VisualServerScene::instance_geometry_set_flag(RID p_instance, VS::InstanceF instance->baked_light = p_enabled; } break; + case VS::INSTANCE_FLAG_REDRAW_FRAME_IF_VISIBLE: { + + instance->redraw_if_visible = p_enabled; + + } break; } } void VisualServerScene::instance_geometry_set_cast_shadows_setting(RID p_instance, VS::ShadowCastingSetting p_shadow_casting_setting) { @@ -1644,7 +1649,8 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons } void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas) { - // render to mono camera +// render to mono camera +#ifndef _3D_DISABLED Camera *camera = camera_owner.getornull(p_camera); ERR_FAIL_COND(!camera); @@ -1679,6 +1685,7 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view _prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID()); _render_scene(camera->transform, camera_matrix, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); +#endif } void VisualServerScene::render_camera(Ref<ARVRInterface> &p_interface, ARVRInterface::Eyes p_eye, RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas) { @@ -1871,6 +1878,10 @@ void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const Ca InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(ins->base_data); + if (ins->redraw_if_visible) { + VisualServerRaster::redraw_request(); + } + if (ins->base_type == VS::INSTANCE_PARTICLES) { //particles visible? process them VSG::storage->particles_request_process(ins->base); @@ -2102,6 +2113,8 @@ void VisualServerScene::_render_scene(const Transform p_cam_transform, const Cam void VisualServerScene::render_empty_scene(RID p_scenario, RID p_shadow_atlas) { +#ifndef _3D_DISABLED + Scenario *scenario = scenario_owner.getornull(p_scenario); RID environment; @@ -2110,6 +2123,7 @@ void VisualServerScene::render_empty_scene(RID p_scenario, RID p_shadow_atlas) { else environment = scenario->fallback_environment; VSG::scene_render->render_scene(Transform(), CameraMatrix(), true, NULL, 0, NULL, 0, NULL, 0, environment, p_shadow_atlas, scenario->reflection_atlas, RID(), 0); +#endif } bool VisualServerScene::_render_reflection_probe_step(Instance *p_instance, int p_step) { diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 54fcd166c9..48f0ec46f3 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -107,6 +107,8 @@ public: FUNC2(texture_set_proxy, RID, RID) + FUNC2(texture_set_force_redraw_if_visible, RID, bool) + /* SKY API */ FUNCRID(sky) diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index dffaccc0d4..1f3319dc04 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -187,16 +187,14 @@ RID VisualServer::_make_test_cube() { PoolVector<float> tangents; PoolVector<Vector3> uvs; - int vtx_idx = 0; -#define ADD_VTX(m_idx) \ - vertices.push_back(face_points[m_idx]); \ - normals.push_back(normal_points[m_idx]); \ - tangents.push_back(normal_points[m_idx][1]); \ - tangents.push_back(normal_points[m_idx][2]); \ - tangents.push_back(normal_points[m_idx][0]); \ - tangents.push_back(1.0); \ - uvs.push_back(Vector3(uv_points[m_idx * 2 + 0], uv_points[m_idx * 2 + 1], 0)); \ - vtx_idx++; +#define ADD_VTX(m_idx) \ + vertices.push_back(face_points[m_idx]); \ + normals.push_back(normal_points[m_idx]); \ + tangents.push_back(normal_points[m_idx][1]); \ + tangents.push_back(normal_points[m_idx][2]); \ + tangents.push_back(normal_points[m_idx][0]); \ + tangents.push_back(1.0); \ + uvs.push_back(Vector3(uv_points[m_idx * 2 + 0], uv_points[m_idx * 2 + 1], 0)); for (int i = 0; i < 6; i++) { @@ -795,6 +793,140 @@ Error VisualServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint32_ return OK; } +uint32_t VisualServer::mesh_surface_get_format_offset(uint32_t p_format, int p_vertex_len, int p_index_len, int p_array_index) const { + uint32_t offsets[ARRAY_MAX]; + mesh_surface_make_offsets_from_format(p_format, p_vertex_len, p_index_len, offsets); + return offsets[p_array_index]; +} + +uint32_t VisualServer::mesh_surface_get_format_stride(uint32_t p_format, int p_vertex_len, int p_index_len) const { + uint32_t offsets[ARRAY_MAX]; + return mesh_surface_make_offsets_from_format(p_format, p_vertex_len, p_index_len, offsets); +} + +uint32_t VisualServer::mesh_surface_make_offsets_from_format(uint32_t p_format, int p_vertex_len, int p_index_len, uint32_t *r_offsets) const { + + int total_elem_size = 0; + + for (int i = 0; i < VS::ARRAY_MAX; i++) { + + r_offsets[i] = 0; //reset + + if (!(p_format & (1 << i))) // no array + continue; + + int elem_size = 0; + + switch (i) { + + case VS::ARRAY_VERTEX: { + + if (p_format & ARRAY_FLAG_USE_2D_VERTICES) { + elem_size = 2; + } else { + elem_size = 3; + } + + if (p_format & ARRAY_COMPRESS_VERTEX) { + elem_size *= sizeof(int16_t); + } else { + elem_size *= sizeof(float); + } + + if (elem_size == 6) { + elem_size = 8; + } + + } break; + case VS::ARRAY_NORMAL: { + + if (p_format & ARRAY_COMPRESS_NORMAL) { + elem_size = sizeof(uint32_t); + } else { + elem_size = sizeof(float) * 3; + } + + } break; + + case VS::ARRAY_TANGENT: { + if (p_format & ARRAY_COMPRESS_TANGENT) { + elem_size = sizeof(uint32_t); + } else { + elem_size = sizeof(float) * 4; + } + + } break; + case VS::ARRAY_COLOR: { + + if (p_format & ARRAY_COMPRESS_COLOR) { + elem_size = sizeof(uint32_t); + } else { + elem_size = sizeof(float) * 4; + } + } break; + case VS::ARRAY_TEX_UV: { + if (p_format & ARRAY_COMPRESS_TEX_UV) { + elem_size = sizeof(uint32_t); + } else { + elem_size = sizeof(float) * 2; + } + + } break; + + case VS::ARRAY_TEX_UV2: { + if (p_format & ARRAY_COMPRESS_TEX_UV2) { + elem_size = sizeof(uint32_t); + } else { + elem_size = sizeof(float) * 2; + } + + } break; + case VS::ARRAY_WEIGHTS: { + + if (p_format & ARRAY_COMPRESS_WEIGHTS) { + elem_size = sizeof(uint16_t) * 4; + } else { + elem_size = sizeof(float) * 4; + } + + } break; + case VS::ARRAY_BONES: { + + if (p_format & ARRAY_FLAG_USE_16_BIT_BONES) { + elem_size = sizeof(uint16_t) * 4; + } else { + elem_size = sizeof(uint32_t); + } + + } break; + case VS::ARRAY_INDEX: { + + if (p_index_len <= 0) { + ERR_PRINT("index_array_len==NO_INDEX_ARRAY"); + break; + } + /* determine whether using 16 or 32 bits indices */ + if (p_vertex_len >= (1 << 16)) { + + elem_size = 4; + + } else { + elem_size = 2; + } + r_offsets[i] = elem_size; + continue; + } break; + default: { + ERR_FAIL_V(0); + } + } + + r_offsets[i] = total_elem_size; + total_elem_size += elem_size; + } + return total_elem_size; +} + void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_compress_format) { ERR_FAIL_INDEX(p_primitive, VS::PRIMITIVE_MAX); @@ -1543,10 +1675,10 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("texture_debug_usage"), &VisualServer::_texture_debug_usage_bind); ClassDB::bind_method(D_METHOD("textures_keep_original", "enable"), &VisualServer::textures_keep_original); - +#ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("sky_create"), &VisualServer::sky_create); ClassDB::bind_method(D_METHOD("sky_set_texture", "sky", "cube_map", "radiance_size"), &VisualServer::sky_set_texture); - +#endif ClassDB::bind_method(D_METHOD("shader_create"), &VisualServer::shader_create); ClassDB::bind_method(D_METHOD("shader_set_code", "shader", "code"), &VisualServer::shader_set_code); ClassDB::bind_method(D_METHOD("shader_get_code", "shader"), &VisualServer::shader_get_code); @@ -1564,11 +1696,14 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("material_set_next_pass", "material", "next_material"), &VisualServer::material_set_next_pass); ClassDB::bind_method(D_METHOD("mesh_create"), &VisualServer::mesh_create); + ClassDB::bind_method(D_METHOD("mesh_surface_get_format_offset", "format", "vertex_len", "index_len", "array_index"), &VisualServer::mesh_surface_get_format_offset); + ClassDB::bind_method(D_METHOD("mesh_surface_get_format_stride", "format", "vertex_len", "index_len"), &VisualServer::mesh_surface_get_format_stride); ClassDB::bind_method(D_METHOD("mesh_add_surface_from_arrays", "mesh", "primtive", "arrays", "blend_shapes", "compress_format"), &VisualServer::mesh_add_surface_from_arrays, DEFVAL(Array()), DEFVAL(ARRAY_COMPRESS_DEFAULT)); ClassDB::bind_method(D_METHOD("mesh_set_blend_shape_count", "mesh", "amount"), &VisualServer::mesh_set_blend_shape_count); ClassDB::bind_method(D_METHOD("mesh_get_blend_shape_count", "mesh"), &VisualServer::mesh_get_blend_shape_count); ClassDB::bind_method(D_METHOD("mesh_set_blend_shape_mode", "mesh", "mode"), &VisualServer::mesh_set_blend_shape_mode); ClassDB::bind_method(D_METHOD("mesh_get_blend_shape_mode", "mesh"), &VisualServer::mesh_get_blend_shape_mode); + ClassDB::bind_method(D_METHOD("mesh_surface_update_region", "mesh", "surface", "offset", "data"), &VisualServer::mesh_surface_update_region); ClassDB::bind_method(D_METHOD("mesh_surface_set_material", "mesh", "surface", "material"), &VisualServer::mesh_surface_set_material); ClassDB::bind_method(D_METHOD("mesh_surface_get_material", "mesh", "surface"), &VisualServer::mesh_surface_get_material); ClassDB::bind_method(D_METHOD("mesh_surface_get_array_len", "mesh", "surface"), &VisualServer::mesh_surface_get_array_len); @@ -1603,7 +1738,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("multimesh_set_visible_instances", "multimesh", "visible"), &VisualServer::multimesh_set_visible_instances); ClassDB::bind_method(D_METHOD("multimesh_get_visible_instances", "multimesh"), &VisualServer::multimesh_get_visible_instances); ClassDB::bind_method(D_METHOD("multimesh_set_as_bulk_array", "multimesh", "array"), &VisualServer::multimesh_set_as_bulk_array); - +#ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("immediate_create"), &VisualServer::immediate_create); ClassDB::bind_method(D_METHOD("immediate_begin", "immediate", "primitive", "texture"), &VisualServer::immediate_begin, DEFVAL(RID())); ClassDB::bind_method(D_METHOD("immediate_vertex", "immediate", "vertex"), &VisualServer::immediate_vertex); @@ -1617,6 +1752,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("immediate_clear", "immediate"), &VisualServer::immediate_clear); ClassDB::bind_method(D_METHOD("immediate_set_material", "immediate", "material"), &VisualServer::immediate_set_material); ClassDB::bind_method(D_METHOD("immediate_get_material", "immediate"), &VisualServer::immediate_get_material); +#endif ClassDB::bind_method(D_METHOD("skeleton_create"), &VisualServer::skeleton_create); ClassDB::bind_method(D_METHOD("skeleton_allocate", "skeleton", "bones", "is_2d_skeleton"), &VisualServer::skeleton_allocate, DEFVAL(false)); @@ -1626,6 +1762,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("skeleton_bone_set_transform_2d", "skeleton", "bone", "transform"), &VisualServer::skeleton_bone_set_transform_2d); ClassDB::bind_method(D_METHOD("skeleton_bone_get_transform_2d", "skeleton", "bone"), &VisualServer::skeleton_bone_get_transform_2d); +#ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("directional_light_create"), &VisualServer::directional_light_create); ClassDB::bind_method(D_METHOD("omni_light_create"), &VisualServer::omni_light_create); ClassDB::bind_method(D_METHOD("spot_light_create"), &VisualServer::spot_light_create); @@ -1695,7 +1832,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("lightmap_capture_get_octree", "capture"), &VisualServer::lightmap_capture_get_octree); ClassDB::bind_method(D_METHOD("lightmap_capture_set_energy", "capture", "energy"), &VisualServer::lightmap_capture_set_energy); ClassDB::bind_method(D_METHOD("lightmap_capture_get_energy", "capture"), &VisualServer::lightmap_capture_get_energy); - +#endif ClassDB::bind_method(D_METHOD("particles_create"), &VisualServer::particles_create); ClassDB::bind_method(D_METHOD("particles_set_emitting", "particles", "emitting"), &VisualServer::particles_set_emitting); ClassDB::bind_method(D_METHOD("particles_get_emitting", "particles"), &VisualServer::particles_get_emitting); @@ -1782,6 +1919,8 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("scenario_set_reflection_atlas_size", "scenario", "p_size", "subdiv"), &VisualServer::scenario_set_reflection_atlas_size); ClassDB::bind_method(D_METHOD("scenario_set_fallback_environment", "scenario", "environment"), &VisualServer::scenario_set_fallback_environment); +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("instance_create2", "base", "scenario"), &VisualServer::instance_create2); ClassDB::bind_method(D_METHOD("instance_create"), &VisualServer::instance_create); ClassDB::bind_method(D_METHOD("instance_set_base", "instance", "base"), &VisualServer::instance_set_base); @@ -1806,7 +1945,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("instances_cull_aabb", "aabb", "scenario"), &VisualServer::_instances_cull_aabb_bind, DEFVAL(RID())); ClassDB::bind_method(D_METHOD("instances_cull_ray", "from", "to", "scenario"), &VisualServer::_instances_cull_ray_bind, DEFVAL(RID())); ClassDB::bind_method(D_METHOD("instances_cull_convex", "convex", "scenario"), &VisualServer::_instances_cull_convex_bind, DEFVAL(RID())); - +#endif ClassDB::bind_method(D_METHOD("canvas_create"), &VisualServer::canvas_create); ClassDB::bind_method(D_METHOD("canvas_set_item_mirroring", "canvas", "item", "mirroring"), &VisualServer::canvas_set_item_mirroring); ClassDB::bind_method(D_METHOD("canvas_set_modulate", "canvas", "color"), &VisualServer::canvas_set_modulate); @@ -1889,13 +2028,14 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("init"), &VisualServer::init); ClassDB::bind_method(D_METHOD("finish"), &VisualServer::finish); ClassDB::bind_method(D_METHOD("get_render_info", "info"), &VisualServer::get_render_info); +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("make_sphere_mesh", "latitudes", "longitudes", "radius"), &VisualServer::make_sphere_mesh); ClassDB::bind_method(D_METHOD("get_test_cube"), &VisualServer::get_test_cube); +#endif ClassDB::bind_method(D_METHOD("get_test_texture"), &VisualServer::get_test_texture); ClassDB::bind_method(D_METHOD("get_white_texture"), &VisualServer::get_white_texture); - ClassDB::bind_method(D_METHOD("make_sphere_mesh", "latitudes", "longitudes", "radius"), &VisualServer::make_sphere_mesh); - ClassDB::bind_method(D_METHOD("set_boot_image", "image", "color", "scale"), &VisualServer::set_boot_image); ClassDB::bind_method(D_METHOD("set_default_clear_color", "color"), &VisualServer::set_default_clear_color); @@ -2061,6 +2201,7 @@ void VisualServer::_bind_methods() { BIND_ENUM_CONSTANT(INSTANCE_GEOMETRY_MASK); BIND_ENUM_CONSTANT(INSTANCE_FLAG_USE_BAKED_LIGHT); + BIND_ENUM_CONSTANT(INSTANCE_FLAG_REDRAW_FRAME_IF_VISIBLE); BIND_ENUM_CONSTANT(INSTANCE_FLAG_MAX); BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_OFF); diff --git a/servers/visual_server.h b/servers/visual_server.h index 5027009634..367642b7d4 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -144,6 +144,7 @@ public: virtual void textures_keep_original(bool p_enable) = 0; virtual void texture_set_proxy(RID p_proxy, RID p_base) = 0; + virtual void texture_set_force_redraw_if_visible(RID p_texture, bool p_enable) = 0; /* SKY API */ @@ -250,6 +251,10 @@ public: virtual RID mesh_create() = 0; + virtual uint32_t mesh_surface_get_format_offset(uint32_t p_format, int p_vertex_len, int p_index_len, int p_array_index) const; + virtual uint32_t mesh_surface_get_format_stride(uint32_t p_format, int p_vertex_len, int p_index_len) const; + /// Returns stride + virtual uint32_t mesh_surface_make_offsets_from_format(uint32_t p_format, int p_vertex_len, int p_index_len, uint32_t *r_offsets) const; virtual void mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), uint32_t p_compress_format = ARRAY_COMPRESS_DEFAULT); virtual void mesh_add_surface(RID p_mesh, uint32_t p_format, PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<PoolVector<uint8_t> > &p_blend_shapes = Vector<PoolVector<uint8_t> >(), const Vector<AABB> &p_bone_aabbs = Vector<AABB>()) = 0; @@ -804,6 +809,7 @@ public: enum InstanceFlags { INSTANCE_FLAG_USE_BAKED_LIGHT, + INSTANCE_FLAG_REDRAW_FRAME_IF_VISIBLE, INSTANCE_FLAG_MAX }; |