Merge pull request #97082 from akien-mga/4.3-cherrypicks
Some checks are pending
🔗 GHA / 📊 Static checks (push) Waiting to run
🔗 GHA / 🤖 Android (push) Blocked by required conditions
🔗 GHA / 🍏 iOS (push) Blocked by required conditions
🔗 GHA / 🐧 Linux (push) Blocked by required conditions
🔗 GHA / 🍎 macOS (push) Blocked by required conditions
🔗 GHA / 🏁 Windows (push) Blocked by required conditions
🔗 GHA / 🌐 Web (push) Blocked by required conditions
🔗 GHA / 🪲 Godot CPP (push) Blocked by required conditions

[4.3] Cherry-picks for the 4.3 branch (future 4.3.1) - 1st batch
This commit is contained in:
Rémi Verschelde 2024-09-17 12:39:29 +02:00 committed by GitHub
commit 6225d39826
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
285 changed files with 3635 additions and 2118 deletions

View File

@ -1070,7 +1070,6 @@ if "check_c_headers" in env:
env.AppendUnique(CPPDEFINES=[headers[header]])
# FIXME: This method mixes both cosmetic progress stuff and cache handling...
methods.show_progress(env)
# TODO: replace this with `env.Dump(format="json")`
# once we start requiring SCons 4.0 as min version.
@ -1102,3 +1101,5 @@ def purge_flaky_files():
atexit.register(purge_flaky_files)
methods.clean_cache(env)

View File

@ -140,7 +140,7 @@ if env["builtin_zstd"]:
"decompress/zstd_decompress_block.c",
"decompress/zstd_decompress.c",
]
if env["platform"] in ["android", "ios", "linuxbsd", "macos"]:
if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64":
# Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h
thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S")
thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources]

View File

@ -1489,15 +1489,6 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/ios/session_category", PROPERTY_HINT_ENUM, "Ambient,Multi Route,Play and Record,Playback,Record,Solo Ambient"), 0);
GLOBAL_DEF("audio/general/ios/mix_with_others", false);
PackedStringArray extensions;
extensions.push_back("gd");
if (ClassDB::class_exists("CSharpScript")) {
extensions.push_back("cs");
}
extensions.push_back("gdshader");
GLOBAL_DEF(PropertyInfo(Variant::PACKED_STRING_ARRAY, "editor/script/search_in_file_extensions"), extensions);
_add_builtin_input_map();
// Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum.

View File

@ -781,23 +781,14 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb
}
}
String actual_lib_path;
OS::GDExtensionData data = {
true, // also_set_library_path
&actual_lib_path, // r_resolved_path
&library_path, // r_resolved_path
Engine::get_singleton()->is_editor_hint(), // generate_temp_files
&abs_dependencies_paths, // library_dependencies
};
Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data);
if (actual_lib_path.get_file() != abs_path.get_file()) {
// If temporary files are generated, let's change the library path to point at the original,
// because that's what we want to check to see if it's changed.
library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file());
} else {
library_path = actual_lib_path;
}
ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path);

View File

@ -8,7 +8,7 @@ __XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back
Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,platform:Android,
# Web
standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web,
standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:+a4,righttrigger:+a5,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web,
Linux24c6581a,PowerA Xbox One Cabled,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,
Linux0e6f0301,Logic 3 Controller (xbox compatible),a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,
Linux045e028e,Microsoft X-Box 360 pad,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,

View File

@ -30,12 +30,7 @@
#include "expression.h"
#include "core/io/marshalls.h"
#include "core/math/math_funcs.h"
#include "core/object/class_db.h"
#include "core/object/ref_counted.h"
#include "core/os/os.h"
#include "core/variant/variant_parser.h"
Error Expression::_get_token(Token &r_token) {
while (true) {
@ -392,7 +387,6 @@ Error Expression::_get_token(Token &r_token) {
if (is_digit(c)) {
} else if (c == 'e') {
reading = READING_EXP;
} else {
reading = READING_DONE;
}
@ -419,7 +413,9 @@ Error Expression::_get_token(Token &r_token) {
is_first_char = false;
}
str_ofs--;
if (c != 0) {
str_ofs--;
}
r_token.type = TK_CONSTANT;

View File

@ -105,6 +105,9 @@ public:
static _ALWAYS_INLINE_ double fmod(double p_x, double p_y) { return ::fmod(p_x, p_y); }
static _ALWAYS_INLINE_ float fmod(float p_x, float p_y) { return ::fmodf(p_x, p_y); }
static _ALWAYS_INLINE_ double modf(double p_x, double *r_y) { return ::modf(p_x, r_y); }
static _ALWAYS_INLINE_ float modf(float p_x, float *r_y) { return ::modff(p_x, r_y); }
static _ALWAYS_INLINE_ double floor(double p_x) { return ::floor(p_x); }
static _ALWAYS_INLINE_ float floor(float p_x) { return ::floorf(p_x); }

View File

@ -60,6 +60,11 @@ int64_t RandomPCG::rand_weighted(const Vector<float> &p_weights) {
}
}
for (int64_t i = weights_size - 1; i >= 0; --i) {
if (weights[i] > 0) {
return i;
}
}
return -1;
}

View File

@ -45,14 +45,17 @@
#ifdef DEBUG_ENABLED
struct _ObjectDebugLock {
Object *obj;
ObjectID obj_id;
_ObjectDebugLock(Object *p_obj) {
obj = p_obj;
obj->_lock_index.ref();
obj_id = p_obj->get_instance_id();
p_obj->_lock_index.ref();
}
~_ObjectDebugLock() {
obj->_lock_index.unref();
Object *obj_ptr = ObjectDB::get_instance(obj_id);
if (likely(obj_ptr)) {
obj_ptr->_lock_index.unref();
}
}
};
@ -2097,7 +2100,11 @@ Object::~Object() {
// Disconnect signals that connect to this object.
while (connections.size()) {
Connection c = connections.front()->get();
bool disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
Object *obj = c.callable.get_object();
bool disconnected = false;
if (likely(obj)) {
disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
}
if (unlikely(!disconnected)) {
// If the disconnect has failed, abandon the connection to avoid getting trapped in an infinite loop here.
connections.pop_front();

View File

@ -142,6 +142,7 @@ void ScriptLanguageExtension::_bind_methods() {
GDVIRTUAL_BIND(_debug_get_current_stack_info);
GDVIRTUAL_BIND(_reload_all_scripts);
GDVIRTUAL_BIND(_reload_scripts, "scripts", "soft_reload");
GDVIRTUAL_BIND(_reload_tool_script, "script", "soft_reload");
GDVIRTUAL_BIND(_get_recognized_extensions);

View File

@ -468,7 +468,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T
p_caller_pool_thread->signaled = false;
if (IS_WAIT_OVER) {
p_caller_pool_thread->yield_is_over = false;
if (unlikely(p_task == ThreadData::YIELDING)) {
p_caller_pool_thread->yield_is_over = false;
}
if (!exit_threads && was_signaled) {
// This thread was awaken for some additional reason, but it's about to exit.
// Let's find out what may be pending and forward the requests.

View File

@ -39,19 +39,10 @@ StaticCString StaticCString::create(const char *p_ptr) {
return scs;
}
StringName::_Data *StringName::_table[STRING_TABLE_LEN];
StringName _scs_create(const char *p_chr, bool p_static) {
return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
}
bool StringName::configured = false;
Mutex StringName::mutex;
#ifdef DEBUG_ENABLED
bool StringName::debug_stringname = false;
#endif
void StringName::setup() {
ERR_FAIL_COND(configured);
for (int i = 0; i < STRING_TABLE_LEN; i++) {

View File

@ -67,7 +67,7 @@ class StringName {
_Data() {}
};
static _Data *_table[STRING_TABLE_LEN];
static inline _Data *_table[STRING_TABLE_LEN];
_Data *_data = nullptr;
@ -75,10 +75,10 @@ class StringName {
friend void register_core_types();
friend void unregister_core_types();
friend class Main;
static Mutex mutex;
static inline Mutex mutex;
static void setup();
static void cleanup();
static bool configured;
static inline bool configured = false;
#ifdef DEBUG_ENABLED
struct DebugSortReferences {
bool operator()(const _Data *p_left, const _Data *p_right) const {
@ -86,7 +86,7 @@ class StringName {
}
};
static bool debug_stringname;
static inline bool debug_stringname = false;
#endif
StringName(_Data *p_data) { _data = p_data; }

View File

@ -1537,13 +1537,16 @@ Vector<double> String::split_floats(const String &p_splitter, bool p_allow_empty
int from = 0;
int len = length();
String buffer = *this;
while (true) {
int end = find(p_splitter, from);
if (end < 0) {
end = len;
}
if (p_allow_empty || (end > from)) {
ret.push_back(String::to_float(&get_data()[from]));
buffer[end] = 0;
ret.push_back(String::to_float(&buffer.get_data()[from]));
buffer[end] = _cowdata.get(end);
}
if (end == len) {
@ -1561,6 +1564,7 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
int from = 0;
int len = length();
String buffer = *this;
while (true) {
int idx;
int end = findmk(p_splitters, from, &idx);
@ -1572,7 +1576,9 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
}
if (p_allow_empty || (end > from)) {
ret.push_back(String::to_float(&get_data()[from]));
buffer[end] = 0;
ret.push_back(String::to_float(&buffer.get_data()[from]));
buffer[end] = _cowdata.get(end);
}
if (end == len) {

View File

@ -169,7 +169,7 @@
astar.ConnectPoints(2, 3, false);
astar.ConnectPoints(4, 3, false);
astar.ConnectPoints(1, 4, false);
int[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3]
long[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3]
[/csharp]
[/codeblocks]
If you change the 2nd point's weight to 3, then the result will be [code][1, 4, 3][/code] instead, because now even though the distance is longer, it's "easier" to get through point 4 than through point 2.
@ -209,7 +209,7 @@
astar.ConnectPoints(1, 2, true);
astar.ConnectPoints(1, 3, true);
int[] neighbors = astar.GetPointConnections(1); // Returns [2, 3]
long[] neighbors = astar.GetPointConnections(1); // Returns [2, 3]
[/csharp]
[/codeblocks]
</description>

View File

@ -197,7 +197,7 @@
astar.ConnectPoints(2, 3, false);
astar.ConnectPoints(4, 3, false);
astar.ConnectPoints(1, 4, false);
int[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3]
long[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3]
[/csharp]
[/codeblocks]
If you change the 2nd point's weight to 3, then the result will be [code][1, 4, 3][/code] instead, because now even though the distance is longer, it's "easier" to get through point 4 than through point 2.
@ -236,7 +236,7 @@
astar.ConnectPoints(1, 2, true);
astar.ConnectPoints(1, 3, true);
int[] neighbors = astar.GetPointConnections(1); // Returns [2, 3]
long[] neighbors = astar.GetPointConnections(1); // Returns [2, 3]
[/csharp]
[/codeblocks]
</description>

View File

@ -5,7 +5,7 @@
</brief_description>
<description>
A resource used by [AnimationNodeBlendTree].
[AnimationNodeBlendSpace1D] represents a virtual 2D space on which [AnimationRootNode]s are placed. Outputs the linear blend of the three adjacent animations using a [Vector2] weight. Adjacent in this context means the three [AnimationRootNode]s making up the triangle that contains the current value.
[AnimationNodeBlendSpace2D] represents a virtual 2D space on which [AnimationRootNode]s are placed. Outputs the linear blend of the three adjacent animations using a [Vector2] weight. Adjacent in this context means the three [AnimationRootNode]s making up the triangle that contains the current value.
You can add vertices to the blend space with [method add_blend_point] and automatically triangulate it by setting [member auto_triangles] to [code]true[/code]. Otherwise, use [method add_triangle] and [method remove_triangle] to triangulate the blend space by hand.
</description>
<tutorials>
@ -93,7 +93,7 @@
<param index="0" name="point" type="int" />
<param index="1" name="pos" type="Vector2" />
<description>
Updates the position of the point at index [param point] on the blend axis.
Updates the position of the point at index [param point] in the blend space.
</description>
</method>
</methods>

View File

@ -243,7 +243,7 @@
var numbers = [1, 2, 3]
var extra = [4, 5, 6]
numbers.append_array(extra)
print(nums) # Prints [1, 2, 3, 4, 5, 6]
print(numbers) # Prints [1, 2, 3, 4, 5, 6]
[/codeblock]
</description>
</method>
@ -715,7 +715,7 @@
<param index="0" name="func" type="Callable" />
<description>
Sorts the array using a custom [Callable].
[param func] is called as many times as necessary, receiving two array elements as arguments. The function should return [code]true[/code] if the first element should be moved [i]behind[/i] the second one, otherwise it should return [code]false[/code].
[param func] is called as many times as necessary, receiving two array elements as arguments. The function should return [code]true[/code] if the first element should be moved [i]before[/i] the second one, otherwise it should return [code]false[/code].
[codeblock]
func sort_ascending(a, b):
if a[1] &lt; b[1]:
@ -728,7 +728,7 @@
print(my_items) # Prints [["Rice", 4], ["Tomato", 5], ["Apple", 9]]
# Sort descending, using a lambda function.
my_items.sort_custom(func(a, b): return a[0] &gt; b[0])
my_items.sort_custom(func(a, b): return a[1] &gt; b[1])
print(my_items) # Prints [["Apple", 9], ["Tomato", 5], ["Rice", 4]]
[/codeblock]
It may also be necessary to use this method to sort strings by natural order, with [method String.naturalnocasecmp_to], as in the following example:

View File

@ -88,7 +88,7 @@
<param index="0" name="idx" type="int" />
<param index="1" name="t" type="float" />
<description>
Returns the position between the vertex [param idx] and the vertex [code]idx + 1[/code], where [param t] controls if the point is the first vertex ([code]t = 0.0[/code]), the last vertex ([code]t = 1.0[/code]), or in between. Values of [param t] outside the range ([code]0.0 &gt;= t &lt;=1[/code]) give strange, but predictable results.
Returns the position between the vertex [param idx] and the vertex [code]idx + 1[/code], where [param t] controls if the point is the first vertex ([code]t = 0.0[/code]), the last vertex ([code]t = 1.0[/code]), or in between. Values of [param t] outside the range ([code]0.0 &lt;= t &lt;= 1.0[/code]) give strange, but predictable results.
If [param idx] is out of bounds it is truncated to the first or last vertex, and [param t] is ignored. If the curve has no points, the function sends an error to the console, and returns [code](0, 0)[/code].
</description>
</method>

View File

@ -29,6 +29,12 @@
Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_navigation_map" qualifiers="const">
<return type="RID" />
<description>
Returns the current navigation map [RID] used by this link.
</description>
</method>
<method name="get_rid" qualifiers="const">
<return type="RID" />
<description>
@ -57,6 +63,13 @@
Based on [param value], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_navigation_map">
<return type="void" />
<param index="0" name="navigation_map" type="RID" />
<description>
Sets the [RID] of the navigation map this link should use. By default the link will automatically join the [World2D] default navigation map so this function is only required to override the default map.
</description>
</method>
</methods>
<members>
<member name="bidirectional" type="bool" setter="set_bidirectional" getter="is_bidirectional" default="true">

View File

@ -29,6 +29,12 @@
Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_navigation_map" qualifiers="const">
<return type="RID" />
<description>
Returns the current navigation map [RID] used by this link.
</description>
</method>
<method name="get_rid" qualifiers="const">
<return type="RID" />
<description>
@ -57,6 +63,13 @@
Based on [param value], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_navigation_map">
<return type="void" />
<param index="0" name="navigation_map" type="RID" />
<description>
Sets the [RID] of the navigation map this link should use. By default the link will automatically join the [World3D] default navigation map so this function is only required to override the default map.
</description>
</method>
</methods>
<members>
<member name="bidirectional" type="bool" setter="set_bidirectional" getter="is_bidirectional" default="true">

View File

@ -136,7 +136,7 @@
}
}
private List&lt;int&gt; _numbers = new();
private Godot.Collections.Array&lt;int&gt; _numbers = new();
public override Godot.Collections.Array&lt;Godot.Collections.Dictionary&gt; _GetPropertyList()
{
@ -173,7 +173,7 @@
if (propertyName.StartsWith("number_"))
{
int index = int.Parse(propertyName.Substring("number_".Length));
numbers[index] = value.As&lt;int&gt;();
_numbers[index] = value.As&lt;int&gt;();
return true;
}
return false;

View File

@ -1000,7 +1000,7 @@
prime-run %command%
[/codeblock]
</member>
<member name="editor/script/search_in_file_extensions" type="PackedStringArray" setter="" getter="" default="PackedStringArray(&quot;gd&quot;, &quot;gdshader&quot;)">
<member name="editor/script/search_in_file_extensions" type="PackedStringArray" setter="" getter="">
Text-based file extensions to include in the script editor's "Find in Files" feature. You can add e.g. [code]tscn[/code] if you wish to also parse your scene files, especially if you use built-in scripts which are serialized in the scene files.
</member>
<member name="editor/script/templates_search_path" type="String" setter="" getter="" default="&quot;res://script_templates&quot;">

View File

@ -944,7 +944,8 @@
<param index="1" name="item" type="RID" />
<param index="2" name="mirroring" type="Vector2" />
<description>
A copy of the canvas item will be drawn with a local offset of the mirroring [Vector2].
A copy of the canvas item will be drawn with a local offset of the [param mirroring].
[b]Note:[/b] This is equivalent to calling [method canvas_set_item_repeat] like [code]canvas_set_item_repeat(item, mirroring, 1)[/code], with an additional check ensuring [param canvas] is a parent of [param item].
</description>
</method>
<method name="canvas_set_item_repeat">

View File

@ -40,7 +40,7 @@
</member>
<member name="kerning_pairs" type="PackedStringArray" setter="" getter="" default="PackedStringArray()">
Kerning pairs for the font. Kerning pair adjust the spacing between two characters.
Each string consist of three space separated values: "from" string, "to" string and integer offset. Each combination form the two string for a kerning pair, e.g, [code]ab cd -3[/code] will create kerning pairs [code]ac[/code], [code]ad[/code], [code]bc[/code], and [code]bd[/code] with offset [code]-3[/code].
Each string consist of three space separated values: "from" string, "to" string and integer offset. Each combination form the two string for a kerning pair, e.g, [code]ab cd -3[/code] will create kerning pairs [code]ac[/code], [code]ad[/code], [code]bc[/code], and [code]bd[/code] with offset [code]-3[/code]. [code]\uXXXX[/code] escape sequences can be used to add Unicode characters.
</member>
<member name="rows" type="int" setter="" getter="" default="1">
Number of rows in the font image. See also [member columns].

View File

@ -314,6 +314,13 @@
<description>
</description>
</method>
<method name="_reload_scripts" qualifiers="virtual">
<return type="void" />
<param index="0" name="scripts" type="Array" />
<param index="1" name="soft_reload" type="bool" />
<description>
</description>
</method>
<method name="_reload_tool_script" qualifiers="virtual">
<return type="void" />
<param index="0" name="script" type="Script" />

View File

@ -551,7 +551,7 @@
</member>
<member name="default_font_size" type="int" setter="set_default_font_size" getter="get_default_font_size" default="-1">
The default font size of this theme resource. Used as the default value when trying to fetch a font size value that doesn't exist in this theme or is in invalid state. If the default font size is also missing or invalid, the engine fallback value is used (see [member ThemeDB.fallback_font_size]).
Values below [code]0[/code] are invalid and can be used to unset the property. Use [method has_default_font_size] to check if this value is valid.
Values below [code]1[/code] are invalid and can be used to unset the property. Use [method has_default_font_size] to check if this value is valid.
</member>
</members>
<constants>

View File

@ -2143,17 +2143,28 @@ void RenderingDeviceDriverD3D12::command_pipeline_barrier(CommandBufferID p_cmd_
for (uint32_t i = 0; i < p_texture_barriers.size(); i++) {
const TextureBarrier &texture_barrier_rd = p_texture_barriers[i];
const TextureInfo *texture_info = (const TextureInfo *)(texture_barrier_rd.texture.id);
if (texture_info->main_texture) {
texture_info = texture_info->main_texture;
}
_rd_stages_and_access_to_d3d12(p_src_stages, texture_barrier_rd.prev_layout, texture_barrier_rd.src_access, texture_barrier_d3d12.SyncBefore, texture_barrier_d3d12.AccessBefore);
_rd_stages_and_access_to_d3d12(p_dst_stages, texture_barrier_rd.next_layout, texture_barrier_rd.dst_access, texture_barrier_d3d12.SyncAfter, texture_barrier_d3d12.AccessAfter);
texture_barrier_d3d12.LayoutBefore = _rd_texture_layout_to_d3d12_barrier_layout(texture_barrier_rd.prev_layout);
texture_barrier_d3d12.LayoutAfter = _rd_texture_layout_to_d3d12_barrier_layout(texture_barrier_rd.next_layout);
texture_barrier_d3d12.pResource = texture_info->resource;
texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = texture_barrier_rd.subresources.base_mipmap;
texture_barrier_d3d12.Subresources.NumMipLevels = texture_barrier_rd.subresources.mipmap_count;
texture_barrier_d3d12.Subresources.FirstArraySlice = texture_barrier_rd.subresources.base_layer;
texture_barrier_d3d12.Subresources.NumArraySlices = texture_barrier_rd.subresources.layer_count;
texture_barrier_d3d12.Subresources.FirstPlane = _compute_plane_slice(texture_info->format, texture_barrier_rd.subresources.aspect);
texture_barrier_d3d12.Subresources.NumPlanes = format_get_plane_count(texture_info->format);
if (texture_barrier_rd.subresources.mipmap_count == texture_info->mipmaps && texture_barrier_rd.subresources.layer_count == texture_info->layers) {
// So, all resources. Then, let's be explicit about it so D3D12 doesn't think
// we are dealing with a subset of subresources.
texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = 0xffffffff;
texture_barrier_d3d12.Subresources.NumMipLevels = 0;
// Because NumMipLevels == 0, all the other fields are ignored by D3D12.
} else {
texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = texture_barrier_rd.subresources.base_mipmap;
texture_barrier_d3d12.Subresources.NumMipLevels = texture_barrier_rd.subresources.mipmap_count;
texture_barrier_d3d12.Subresources.FirstArraySlice = texture_barrier_rd.subresources.base_layer;
texture_barrier_d3d12.Subresources.NumArraySlices = texture_barrier_rd.subresources.layer_count;
texture_barrier_d3d12.Subresources.FirstPlane = _compute_plane_slice(texture_info->format, texture_barrier_rd.subresources.aspect);
texture_barrier_d3d12.Subresources.NumPlanes = format_get_plane_count(texture_info->format);
}
texture_barrier_d3d12.Flags = (texture_barrier_rd.prev_layout == RDD::TEXTURE_LAYOUT_UNDEFINED) ? D3D12_TEXTURE_BARRIER_FLAG_DISCARD : D3D12_TEXTURE_BARRIER_FLAG_NONE;
texture_barriers.push_back(texture_barrier_d3d12);
}

View File

@ -468,7 +468,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
update_skeletons = false;
}
// Canvas group begins here, render until before this item
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached);
item_count = 0;
if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) {
@ -499,7 +499,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info);
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info, material_screen_texture_mipmaps_cached);
item_count = 0;
if (ci->canvas_group->blur_mipmaps) {
@ -523,7 +523,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
//render anything pending, including clearing if no items
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached);
item_count = 0;
texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps);
@ -553,7 +553,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info);
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info, material_screen_texture_mipmaps_cached);
//then reset
item_count = 0;
}
@ -573,10 +573,10 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
state.current_instance_buffer_index = 0;
}
void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) {
void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info, bool p_backbuffer_has_mipmaps) {
GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
canvas_begin(p_to_render_target, p_to_backbuffer);
canvas_begin(p_to_render_target, p_to_backbuffer, p_backbuffer_has_mipmaps);
if (p_item_count <= 0) {
// Nothing to draw, just call canvas_begin() to clear the render target and return.
@ -647,18 +647,17 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
_record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, Point2());
} else {
Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2);
Point2 end_pos = ci->repeat_size * ci->repeat_times + ci->repeat_size + start_pos;
Point2 pos = start_pos;
Point2 offset;
do {
do {
_record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, pos);
pos.y += ci->repeat_size.y;
} while (pos.y < end_pos.y);
pos.x += ci->repeat_size.x;
pos.y = start_pos.y;
} while (pos.x < end_pos.x);
int repeat_times_x = ci->repeat_size.x ? ci->repeat_times : 0;
int repeat_times_y = ci->repeat_size.y ? ci->repeat_times : 0;
for (int ry = 0; ry <= repeat_times_y; ry++) {
offset.y = start_pos.y + ry * ci->repeat_size.y;
for (int rx = 0; rx <= repeat_times_x; rx++) {
offset.x = start_pos.x + rx * ci->repeat_size.x;
_record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, offset);
}
}
}
}
@ -809,7 +808,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
state.last_item_index += index;
}
void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, const Point2 &p_offset) {
void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, const Point2 &p_repeat_offset) {
RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? state.default_filter : p_item->texture_filter;
if (texture_filter != state.canvas_instance_batches[state.current_batch_index].filter) {
@ -826,11 +825,11 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
state.canvas_instance_batches[state.current_batch_index].repeat = texture_repeat;
}
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
if (p_offset.x || p_offset.y) {
base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
Transform2D base_transform = p_item->final_transform;
if (p_item->repeat_source_item && (p_repeat_offset.x || p_repeat_offset.y)) {
base_transform.columns[2] += p_item->repeat_source_item->final_transform.basis_xform(p_repeat_offset);
}
base_transform = p_canvas_transform_inverse * base_transform;
Transform2D draw_transform; // Used by transform command
@ -2170,7 +2169,7 @@ bool RasterizerCanvasGLES3::free(RID p_rid) {
void RasterizerCanvasGLES3::update() {
}
void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer) {
void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps) {
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
GLES3::Config *config = GLES3::Config::get_singleton();
@ -2185,6 +2184,7 @@ void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backb
glBindFramebuffer(GL_FRAMEBUFFER, render_target->fbo);
glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 4);
glBindTexture(GL_TEXTURE_2D, render_target->backbuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_backbuffer_has_mipmaps ? render_target->mipmap_count - 1 : 0);
}
if (render_target->is_transparent || p_to_backbuffer) {

View File

@ -335,7 +335,7 @@ public:
typedef void Texture;
void canvas_begin(RID p_to_render_target, bool p_to_backbuffer);
void canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps);
//virtual void draw_window_margins(int *black_margin, RID *black_image) override;
void draw_lens_distortion_rect(const Rect2 &p_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample);
@ -361,8 +361,8 @@ public:
void _prepare_canvas_texture(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, uint32_t &r_index, Size2 &r_texpixel_size);
void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) override;
void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_offset);
void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr, bool p_backbuffer_has_mipmaps = false);
void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_repeat_offset);
void _render_batch(Light *p_lights, uint32_t p_index, RenderingMethod::RenderInfo *r_render_info = nullptr);
bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization);
void _new_batch(bool &r_batch_broken);

View File

@ -62,6 +62,10 @@
#define _EXT_DEBUG_SEVERITY_LOW_ARB 0x9148
#define _EXT_DEBUG_OUTPUT 0x92E0
#ifndef GL_FRAMEBUFFER_SRGB
#define GL_FRAMEBUFFER_SRGB 0x8DB9
#endif
#ifndef GLAPIENTRY
#if defined(WINDOWS_ENABLED)
#define GLAPIENTRY APIENTRY
@ -345,6 +349,9 @@ RasterizerGLES3::RasterizerGLES3() {
}
}
// Disable OpenGL linear to sRGB conversion, because Godot will always do this conversion itself.
glDisable(GL_FRAMEBUFFER_SRGB);
// OpenGL needs to be initialized before initializing the Rasterizers
config = memnew(GLES3::Config);
utilities = memnew(GLES3::Utilities);

View File

@ -777,7 +777,6 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection,
ERR_FAIL_COND(p_env.is_null());
Sky *sky = sky_owner.get_or_null(environment_get_sky(p_env));
ERR_FAIL_NULL(sky);
GLES3::SkyMaterialData *material_data = nullptr;
RID sky_material;
@ -851,6 +850,15 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection,
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::SKY_ENERGY_MULTIPLIER, p_sky_energy_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
Color fog_color = environment_get_fog_light_color(p_env).srgb_to_linear() * environment_get_fog_light_energy(p_env);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_ENABLED, environment_get_fog_enabled(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_AERIAL_PERSPECTIVE, environment_get_fog_aerial_perspective(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_LIGHT_COLOR, fog_color, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_SUN_SCATTER, environment_get_fog_sun_scatter(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_DENSITY, environment_get_fog_density(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_SKY_AFFECT, environment_get_fog_sky_affect(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::DIRECTIONAL_LIGHT_COUNT, sky_globals.directional_light_count, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
if (p_use_multiview) {
glBindBufferBase(GL_UNIFORM_BUFFER, SKY_MULTIVIEW_UNIFORM_LOCATION, scene_state.multiview_buffer);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
@ -904,7 +912,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p
RS::SkyMode sky_mode = sky->mode;
if (sky_mode == RS::SKY_MODE_AUTOMATIC) {
if (shader_data->uses_time || shader_data->uses_position) {
if ((shader_data->uses_time || shader_data->uses_position) && sky->radiance_size == 256) {
update_single_frame = true;
sky_mode = RS::SKY_MODE_REALTIME;
} else if (shader_data->uses_light || shader_data->ubo_size > 0) {
@ -925,7 +933,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p
int max_processing_layer = sky->mipmap_count;
// Update radiance cubemap
if (sky->reflection_dirty && (sky->processing_layer > max_processing_layer || update_single_frame)) {
if (sky->reflection_dirty && (sky->processing_layer >= max_processing_layer || update_single_frame)) {
static const Vector3 view_normals[6] = {
Vector3(+1, 0, 0),
Vector3(-1, 0, 0),
@ -2587,7 +2595,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
scene_state.enable_gl_depth_draw(false);
if (draw_sky) {
if (draw_sky || draw_sky_fog_only) {
RENDER_TIMESTAMP("Render Sky");
scene_state.enable_gl_depth_test(true);

View File

@ -698,7 +698,8 @@ void ShaderGLES3::_clear_version(Version *p_version) {
void ShaderGLES3::_initialize_version(Version *p_version) {
ERR_FAIL_COND(p_version->variants.size() > 0);
if (shader_cache_dir_valid && _load_from_cache(p_version)) {
bool use_cache = shader_cache_dir_valid && !(feedback_count > 0 && GLES3::Config::get_singleton()->disable_transform_feedback_shader_cache);
if (use_cache && _load_from_cache(p_version)) {
return;
}
p_version->variants.reserve(variant_count);
@ -709,7 +710,7 @@ void ShaderGLES3::_initialize_version(Version *p_version) {
_compile_specialization(spec, i, p_version, specialization_default_mask);
p_version->variants[i].insert(specialization_default_mask, spec);
}
if (shader_cache_dir_valid) {
if (use_cache) {
_save_to_cache(p_version);
}
}

View File

@ -583,6 +583,8 @@ void main() {
#define SHADER_IS_SRGB true
#define FLAGS_NON_UNIFORM_SCALE (1 << 4)
/* Varyings */
#if defined(COLOR_USED)
@ -955,6 +957,7 @@ ivec2 multiview_uv(ivec2 uv) {
uniform highp mat4 world_transform;
uniform mediump float opaque_prepass_threshold;
uniform highp uint model_flags;
#if defined(RENDER_MATERIAL)
layout(location = 0) out vec4 albedo_output_buffer;
@ -1521,6 +1524,13 @@ void main() {
vec3 light_vertex = vertex;
#endif //LIGHT_VERTEX_USED
highp mat3 model_normal_matrix;
if (bool(model_flags & uint(FLAGS_NON_UNIFORM_SCALE))) {
model_normal_matrix = transpose(inverse(mat3(model_matrix)));
} else {
model_normal_matrix = mat3(model_matrix);
}
{
#CODE : FRAGMENT
}
@ -1728,16 +1738,10 @@ void main() {
vec3 n = normalize(lightmap_normal_xform * normal);
ambient_light += lm_light_l0 * 0.282095f;
ambient_light += lm_light_l1n1 * 0.32573 * n.y * lightmap_exposure_normalization;
ambient_light += lm_light_l1_0 * 0.32573 * n.z * lightmap_exposure_normalization;
ambient_light += lm_light_l1p1 * 0.32573 * n.x * lightmap_exposure_normalization;
if (metallic > 0.01) { // Since the more direct bounced light is lost, we can kind of fake it with this trick.
vec3 r = reflect(normalize(-vertex), normal);
specular_light += lm_light_l1n1 * 0.32573 * r.y * lightmap_exposure_normalization;
specular_light += lm_light_l1_0 * 0.32573 * r.z * lightmap_exposure_normalization;
specular_light += lm_light_l1p1 * 0.32573 * r.x * lightmap_exposure_normalization;
}
ambient_light += lm_light_l0 * lightmap_exposure_normalization;
ambient_light += lm_light_l1n1 * n.y * lightmap_exposure_normalization;
ambient_light += lm_light_l1_0 * n.z * lightmap_exposure_normalization;
ambient_light += lm_light_l1p1 * n.x * lightmap_exposure_normalization;
#else
ambient_light += textureLod(lightmap_textures, uvw, 0.0).rgb * lightmap_exposure_normalization;
#endif
@ -1871,7 +1875,7 @@ void main() {
alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0));
#if defined(ALPHA_SCISSOR_USED)
if (alpha < alpha_scissor) {
if (alpha < alpha_scissor_threshold) {
discard;
}
#endif // !ALPHA_SCISSOR_USED

View File

@ -59,7 +59,7 @@ layout(location = 10) in highp uvec4 in_bone_attrib;
layout(location = 11) in mediump vec4 in_weight_attrib;
#endif
uniform mediump sampler2D skeleton_texture; // texunit:0
uniform highp sampler2D skeleton_texture; // texunit:0
#endif
/* clang-format on */

View File

@ -108,11 +108,11 @@ uniform float sky_energy_multiplier;
uniform float luminance_multiplier;
uniform float fog_aerial_perspective;
uniform vec3 fog_light_color;
uniform vec4 fog_light_color;
uniform float fog_sun_scatter;
uniform bool fog_enabled;
uniform float fog_density;
uniform float z_far;
uniform float fog_sky_affect;
uniform uint directional_light_count;
#ifdef USE_MULTIVIEW
@ -135,6 +135,24 @@ vec3 interleaved_gradient_noise(vec2 pos) {
}
#endif
#if !defined(DISABLE_FOG)
vec4 fog_process(vec3 view, vec3 sky_color) {
vec3 fog_color = mix(fog_light_color.rgb, sky_color, fog_aerial_perspective);
if (fog_sun_scatter > 0.001) {
vec4 sun_scatter = vec4(0.0);
float sun_total = 0.0;
for (uint i = 0u; i < directional_light_count; i++) {
vec3 light_color = directional_lights.data[i].color_size.xyz * directional_lights.data[i].direction_energy.w;
float light_amount = pow(max(dot(view, directional_lights.data[i].direction_energy.xyz), 0.0), 8.0);
fog_color += light_color * light_amount * fog_sun_scatter;
}
}
return vec4(fog_color, 1.0);
}
#endif // !DISABLE_FOG
void main() {
vec3 cube_normal;
#ifdef USE_MULTIVIEW
@ -203,6 +221,21 @@ void main() {
// Convert to Linear for tonemapping so color matches scene shader better
color = srgb_to_linear(color);
#if !defined(DISABLE_FOG) && !defined(USE_CUBEMAP_PASS)
// Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
if (fog_enabled) {
vec4 fog = fog_process(cube_normal, color.rgb);
color.rgb = mix(color.rgb, fog.rgb, fog.a * fog_sky_affect);
}
if (custom_fog.a > 0.0) {
color.rgb = mix(color.rgb, custom_fog.rgb, custom_fog.a);
}
#endif // DISABLE_FOG
color *= exposure;
#ifdef APPLY_TONEMAPPING
color = apply_tonemapping(color, white);

View File

@ -121,7 +121,7 @@ Config::Config() {
#ifdef WEB_ENABLED
msaa_supported = (msaa_max_samples > 0);
#else
msaa_supported = extensions.has("GL_EXT_framebuffer_multisample");
msaa_supported = true;
#endif
#ifndef IOS_ENABLED
#ifdef WEB_ENABLED
@ -218,6 +218,8 @@ Config::Config() {
//https://github.com/godotengine/godot/issues/92662#issuecomment-2161199477
//disable_particles_workaround = false;
}
} else if (rendering_device_name == "PowerVR Rogue GE8320") {
disable_transform_feedback_shader_cache = true;
}
}

View File

@ -96,6 +96,9 @@ public:
bool disable_particles_workaround = false; // set to 'true' to disable 'GPUParticles'
bool flip_xy_workaround = false;
// PowerVR GE 8320 workaround
bool disable_transform_feedback_shader_cache = false;
#ifdef ANDROID_ENABLED
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC eglFramebufferTextureMultiviewOVR = nullptr;
PFNGLTEXSTORAGE3DMULTISAMPLEPROC eglTexStorage3DMultisample = nullptr;

View File

@ -1518,6 +1518,11 @@ bool LightStorage::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas, int *p_i
uint64_t min_pass = 0; // Pass of the existing one, try to use the least recently used one (LRU fashion).
for (int j = 0; j < sc; j++) {
if (sarr[j].owner_is_omni != is_omni) {
// Existing light instance type doesn't match new light instance type skip.
continue;
}
LightInstance *sli = light_instance_owner.get_or_null(sarr[j].owner);
if (!sli) {
// Found a released light instance.

View File

@ -301,7 +301,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface)
Vector<uint8_t> ir = new_surface.index_data;
wr = wf_indices.ptrw();
if (new_surface.vertex_count < (1 << 16)) {
if (new_surface.vertex_count <= 65536) {
// Read 16 bit indices.
const uint16_t *src_idx = (const uint16_t *)ir.ptr();
for (uint32_t i = 0; i + 5 < wf_index_count; i += 6) {
@ -743,6 +743,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const {
}
void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) {
ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh.");
Mesh *mesh = mesh_owner.get_or_null(p_mesh);
ERR_FAIL_NULL(mesh);

View File

@ -1039,7 +1039,7 @@ Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
data.resize(data_size);
ERR_FAIL_COND_V(data.is_empty(), Ref<Image>());
image = Image::create_from_data(texture->width, texture->height, texture->mipmaps > 1, texture->real_format, data);
image = Image::create_from_data(texture->alloc_width, texture->alloc_height, texture->mipmaps > 1, texture->real_format, data);
ERR_FAIL_COND_V(image->is_empty(), Ref<Image>());
if (texture->format != texture->real_format) {
image->convert(texture->format);
@ -1095,7 +1095,7 @@ Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
data.resize(data_size);
ERR_FAIL_COND_V(data.is_empty(), Ref<Image>());
image = Image::create_from_data(texture->width, texture->height, false, Image::FORMAT_RGBA8, data);
image = Image::create_from_data(texture->alloc_width, texture->alloc_height, false, Image::FORMAT_RGBA8, data);
ERR_FAIL_COND_V(image->is_empty(), Ref<Image>());
if (texture->format != Image::FORMAT_RGBA8) {
@ -1497,11 +1497,9 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref<Image> &p_image,
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
if (texture->target == GL_TEXTURE_2D_ARRAY) {
if (p_initialize) {
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, i, internal_format, w, h, texture->layers, 0,
size * texture->layers, &read[ofs]);
} else {
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, i, 0, 0, p_layer, w, h, 1, internal_format, size, &read[ofs]);
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, i, internal_format, w, h, texture->layers, 0, size * texture->layers, nullptr);
}
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, i, 0, 0, p_layer, w, h, 1, internal_format, size, &read[ofs]);
} else {
glCompressedTexImage2D(blit_target, i, internal_format, w, h, 0, size, &read[ofs]);
}

View File

@ -383,7 +383,7 @@ uint64_t FileAccessUnix::_get_modified_time(const String &p_file) {
if (!err) {
return status.st_mtime;
} else {
print_verbose("Failed to get modified time for: " + p_file + "");
WARN_PRINT("Failed to get modified time for: " + p_file);
return 0;
}
}

View File

@ -553,15 +553,40 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
file = file.substr(0, file.length() - 1);
}
struct _stat st;
int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st);
HANDLE handle = CreateFileW((LPCWSTR)(file.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (rv == 0) {
return st.st_mtime;
} else {
print_verbose("Failed to get modified time for: " + p_file + "");
return 0;
if (handle != INVALID_HANDLE_VALUE) {
FILETIME ft_create, ft_write;
bool status = GetFileTime(handle, &ft_create, nullptr, &ft_write);
CloseHandle(handle);
if (status) {
uint64_t ret = 0;
// If write time is invalid, fallback to creation time.
if (ft_write.dwHighDateTime == 0 && ft_write.dwLowDateTime == 0) {
ret = ft_create.dwHighDateTime;
ret <<= 32;
ret |= ft_create.dwLowDateTime;
} else {
ret = ft_write.dwHighDateTime;
ret <<= 32;
ret |= ft_write.dwLowDateTime;
}
const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;
const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;
if (ret >= TICKS_TO_UNIX_EPOCH) {
return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;
}
}
}
print_verbose("Failed to get modified time for: " + p_file);
return 0;
}
BitField<FileAccess::UnixPermissionFlags> FileAccessWindows::_get_unix_permissions(const String &p_file) {

View File

@ -35,6 +35,7 @@
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_spin_slider.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/view_panner.h"
#include "scene/resources/text_line.h"
@ -868,6 +869,11 @@ void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::Hand
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_out_handle(track_key_pair.first, track_key_pair.second));
undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track_key_pair.first, track_key_pair.second, p_mode, p_auto ? Animation::HANDLE_SET_MODE_AUTO : Animation::HANDLE_SET_MODE_RESET);
}
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
}
@ -1083,7 +1089,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (I.key == REMOVE_ICON) {
if (!read_only) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action("Remove Bezier Track");
undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr());
undo_redo->add_do_method(this, "_update_locked_tracks_after", track);
undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);
@ -1559,6 +1565,11 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio);
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio);
}
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
moving_handle = 0;
queue_redraw();
@ -1672,6 +1683,11 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
queue_redraw();
}
@ -1772,6 +1788,11 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_
i++;
}
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->add_do_method(this, "queue_redraw");
undo_redo->add_undo_method(this, "queue_redraw");
undo_redo->commit_action();
@ -1821,6 +1842,15 @@ void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) {
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos, i == 0);
i++;
}
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->add_do_method(this, "queue_redraw");
undo_redo->add_undo_method(this, "queue_redraw");
undo_redo->commit_action();
}
}
@ -1899,9 +1929,15 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
i++;
}
undo_redo->commit_action();
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->add_do_method(this, "queue_redraw");
undo_redo->add_undo_method(this, "queue_redraw");
queue_redraw();
undo_redo->commit_action();
}
}
@ -1916,6 +1952,11 @@ void AnimationBezierTrackEdit::delete_selection() {
}
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
//selection.clear();
@ -1925,6 +1966,15 @@ void AnimationBezierTrackEdit::delete_selection() {
void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) {
int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);
p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Animation Bezier Curve Change Call"));
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
}
void AnimationBezierTrackEdit::_bind_methods() {

View File

@ -266,6 +266,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
setting = false;
@ -282,6 +287,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
setting = false;
@ -298,6 +308,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
setting = false;
@ -318,6 +333,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev_in_handle);
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev_out_handle);
undo_redo->add_undo_method(this, "_update_obj", animation);
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
undo_redo->add_do_method(ape, "_animation_update_key_frame");
undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
setting = false;
@ -7032,7 +7052,10 @@ void AnimationTrackEditor::_update_snap_unit() {
if (timeline->is_using_fps()) {
snap_unit = 1.0 / step->get_value();
} else {
snap_unit = 1.0 / Math::round(1.0 / step->get_value()); // Follow the snap behavior of the timeline editor.
double integer;
double fraction = Math::modf(step->get_value(), &integer);
fraction = 1.0 / Math::round(1.0 / fraction);
snap_unit = integer + fraction;
}
}

View File

@ -994,6 +994,9 @@ Ref<Texture2D> CodeTextEditor::_get_completion_icon(const ScriptLanguage::CodeCo
tex = get_editor_theme_icon(p_option.display);
} else {
tex = EditorNode::get_singleton()->get_class_icon(p_option.display);
if (!tex.is_valid()) {
tex = get_editor_theme_icon(SNAME("Object"));
}
}
} break;
case ScriptLanguage::CODE_COMPLETION_KIND_ENUM:

View File

@ -578,32 +578,34 @@ void DependencyRemoveDialog::ok_pressed() {
}
}
bool project_settings_modified = false;
for (int i = 0; i < files_to_delete.size(); ++i) {
// If the file we are deleting for e.g. the main scene, default environment,
// or audio bus layout, we must clear its definition in Project Settings.
if (files_to_delete[i] == String(GLOBAL_GET("application/config/icon"))) {
ProjectSettings::get_singleton()->set("application/config/icon", "");
}
if (files_to_delete[i] == String(GLOBAL_GET("application/run/main_scene"))) {
project_settings_modified = true;
} else if (files_to_delete[i] == String(GLOBAL_GET("application/run/main_scene"))) {
ProjectSettings::get_singleton()->set("application/run/main_scene", "");
}
if (files_to_delete[i] == String(GLOBAL_GET("application/boot_splash/image"))) {
project_settings_modified = true;
} else if (files_to_delete[i] == String(GLOBAL_GET("application/boot_splash/image"))) {
ProjectSettings::get_singleton()->set("application/boot_splash/image", "");
}
if (files_to_delete[i] == String(GLOBAL_GET("rendering/environment/defaults/default_environment"))) {
project_settings_modified = true;
} else if (files_to_delete[i] == String(GLOBAL_GET("rendering/environment/defaults/default_environment"))) {
ProjectSettings::get_singleton()->set("rendering/environment/defaults/default_environment", "");
}
if (files_to_delete[i] == String(GLOBAL_GET("display/mouse_cursor/custom_image"))) {
project_settings_modified = true;
} else if (files_to_delete[i] == String(GLOBAL_GET("display/mouse_cursor/custom_image"))) {
ProjectSettings::get_singleton()->set("display/mouse_cursor/custom_image", "");
}
if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom"))) {
project_settings_modified = true;
} else if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom"))) {
ProjectSettings::get_singleton()->set("gui/theme/custom", "");
}
if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom_font"))) {
project_settings_modified = true;
} else if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom_font"))) {
ProjectSettings::get_singleton()->set("gui/theme/custom_font", "");
}
if (files_to_delete[i] == String(GLOBAL_GET("audio/buses/default_bus_layout"))) {
project_settings_modified = true;
} else if (files_to_delete[i] == String(GLOBAL_GET("audio/buses/default_bus_layout"))) {
ProjectSettings::get_singleton()->set("audio/buses/default_bus_layout", "");
project_settings_modified = true;
}
String path = OS::get_singleton()->get_resource_dir() + files_to_delete[i].replace_first("res://", "/");
@ -615,6 +617,9 @@ void DependencyRemoveDialog::ok_pressed() {
emit_signal(SNAME("file_removed"), files_to_delete[i]);
}
}
if (project_settings_modified) {
ProjectSettings::get_singleton()->save();
}
if (dirs_to_delete.size() == 0) {
// If we only deleted files we should only need to tell the file system about the files we touched.

View File

@ -457,7 +457,9 @@ void EditorAutoloadSettings::init_autoloads() {
for (const AutoloadInfo &info : autoload_cache) {
if (info.node && info.in_editor) {
callable_mp((Node *)get_tree()->get_root(), &Node::add_child).call_deferred(info.node, false, Node::INTERNAL_MODE_DISABLED);
// It's important to add the node without deferring because code in plugins or tool scripts
// could use the autoload node when they are enabled.
get_tree()->get_root()->add_child(info.node);
}
}
}

View File

@ -386,6 +386,8 @@ void EditorFileSystem::_scan_filesystem() {
// On the first scan, the first_scan_root_dir is created in _first_scan_filesystem.
if (first_scan) {
sd = first_scan_root_dir;
// Will be updated on scan.
ResourceUID::get_singleton()->clear();
} else {
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
sd = memnew(ScannedDirectory);
@ -1779,7 +1781,9 @@ String EditorFileSystem::_get_global_script_class(const String &p_type, const St
void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info) {
String icon_path;
if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) {
if (file_info->resource_script_class != StringName()) {
icon_path = EditorNode::get_editor_data().script_class_get_icon_path(file_info->resource_script_class);
} else if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) {
const String &script_dep = file_info->deps[0]; // Assuming the first dependency is a script.
const String &script_path = script_dep.contains("::") ? script_dep.get_slice("::", 2) : script_dep;
if (!script_path.is_empty()) {
@ -3059,7 +3063,6 @@ EditorFileSystem::EditorFileSystem() {
using_fat32_or_exfat = (da->get_filesystem_type() == "FAT32" || da->get_filesystem_type() == "exFAT");
scan_total = 0;
callable_mp(ResourceUID::get_singleton(), &ResourceUID::clear).call_deferred(); // Will be updated on scan.
ResourceSaver::set_get_resource_id_for_path(_resource_saver_get_resource_id_for_path);
}

View File

@ -174,6 +174,32 @@ static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode";
static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"%s\" directory manually before attempting this operation again.";
static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset.";
bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refresh) {
if (Thread::is_main_thread()) {
return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh);
} else {
EditorNode::progress_task_step_bg(task, p_step);
return false;
}
}
EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel) {
if (Thread::is_main_thread()) {
EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel);
} else {
EditorNode::progress_add_task_bg(p_task, p_label, p_amount);
}
task = p_task;
}
EditorProgress::~EditorProgress() {
if (Thread::is_main_thread()) {
EditorNode::progress_end_task(task);
} else {
EditorNode::progress_end_task_bg(task);
}
}
void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vector<String> &r_filenames) {
ERR_FAIL_COND_MSG(p_full_paths.size() != r_filenames.size(), vformat("disambiguate_filenames requires two string vectors of same length (%d != %d).", p_full_paths.size(), r_filenames.size()));
@ -670,7 +696,10 @@ void EditorNode::_notification(int p_what) {
callable_mp(this, &EditorNode::_begin_first_scan).call_deferred();
DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_update_theme).bind(false));
last_dark_mode_state = DisplayServer::get_singleton()->is_dark_mode();
last_system_accent_color = DisplayServer::get_singleton()->get_accent_color();
last_system_base_color = DisplayServer::get_singleton()->get_base_color();
DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_check_system_theme_changed));
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
} break;
@ -1658,17 +1687,17 @@ void EditorNode::_find_node_types(Node *p_node, int &count_2d, int &count_3d) {
}
void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
EditorProgress save("save", TTR("Saving Scene"), 4);
save_scene_progress = memnew(EditorProgress("save", TTR("Saving Scene"), 4));
if (editor_data.get_edited_scene_root() != nullptr) {
save.step(TTR("Analyzing"), 0);
save_scene_progress->step(TTR("Analyzing"), 0);
int c2d = 0;
int c3d = 0;
_find_node_types(editor_data.get_edited_scene_root(), c2d, c3d);
save.step(TTR("Creating Thumbnail"), 1);
save_scene_progress->step(TTR("Creating Thumbnail"), 1);
// Current view?
Ref<Image> img;
@ -1696,8 +1725,7 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
if (img.is_valid() && img->get_width() > 0 && img->get_height() > 0) {
img = img->duplicate();
save.step(TTR("Creating Thumbnail"), 2);
save.step(TTR("Creating Thumbnail"), 3);
save_scene_progress->step(TTR("Creating Thumbnail"), 3);
int preview_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
preview_size *= EDSCALE;
@ -1733,12 +1761,19 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
}
}
save.step(TTR("Saving Scene"), 4);
save_scene_progress->step(TTR("Saving Scene"), 4);
_save_scene(p_file, p_idx);
if (!singleton->cmdline_export_mode) {
EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
}
_close_save_scene_progress();
}
void EditorNode::_close_save_scene_progress() {
memdelete_notnull(save_scene_progress);
save_scene_progress = nullptr;
}
bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_node) {
@ -5132,6 +5167,7 @@ bool EditorNode::is_project_exporting() const {
void EditorNode::show_accept(const String &p_text, const String &p_title) {
current_menu_option = -1;
if (accept) {
_close_save_scene_progress();
accept->set_ok_button_text(p_title);
accept->set_text(p_text);
EditorInterface::get_singleton()->popup_dialog_centered(accept);
@ -5141,6 +5177,7 @@ void EditorNode::show_accept(const String &p_text, const String &p_title) {
void EditorNode::show_save_accept(const String &p_text, const String &p_title) {
current_menu_option = -1;
if (save_accept) {
_close_save_scene_progress();
save_accept->set_ok_button_text(p_title);
save_accept->set_text(p_text);
EditorInterface::get_singleton()->popup_dialog_centered(save_accept);
@ -5149,6 +5186,7 @@ void EditorNode::show_save_accept(const String &p_text, const String &p_title) {
void EditorNode::show_warning(const String &p_text, const String &p_title) {
if (warning) {
_close_save_scene_progress();
warning->set_text(p_text);
warning->set_title(p_title);
EditorInterface::get_singleton()->popup_dialog_centered(warning);
@ -5296,14 +5334,18 @@ void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) {
PackedStringArray scenes = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes");
for (int i = 0; i < scenes.size(); i++) {
load_scene(scenes[i]);
if (FileAccess::exists(scenes[i])) {
load_scene(scenes[i]);
}
}
if (p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "current_scene")) {
String current_scene = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "current_scene");
int current_scene_idx = scenes.find(current_scene);
if (current_scene_idx >= 0) {
_set_current_scene(current_scene_idx);
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
if (editor_data.get_scene_path(i) == current_scene) {
_set_current_scene(i);
break;
}
}
}
@ -6033,7 +6075,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
base_packed_scene = current_packed_scene;
}
if (!local_scene_cache.find(path)) {
current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err);
current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
local_scene_cache[path] = current_packed_scene;
} else {
current_packed_scene = local_scene_cache[path];

View File

@ -122,6 +122,14 @@ class SurfaceUpgradeTool;
class SurfaceUpgradeDialog;
class WindowWrapper;
struct EditorProgress {
String task;
bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true);
EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false);
~EditorProgress();
};
class EditorNode : public Node {
GDCLASS(EditorNode, Node);
@ -470,6 +478,7 @@ private:
String external_file;
String open_navigate;
String saving_scene;
EditorProgress *save_scene_progress = nullptr;
DynamicFontImportSettingsDialog *fontdata_import_settings = nullptr;
SceneImportSettingsDialog *scene_import_settings = nullptr;
@ -624,6 +633,7 @@ private:
void _find_node_types(Node *p_node, int &count_2d, int &count_3d);
void _save_scene_with_preview(String p_file, int p_idx = -1);
void _close_save_scene_progress();
bool _find_scene_in_use(Node *p_node, const String &p_path) const;
@ -952,33 +962,6 @@ public:
bool ensure_main_scene(bool p_from_native);
};
struct EditorProgress {
String task;
bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true) {
if (Thread::is_main_thread()) {
return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh);
} else {
EditorNode::progress_task_step_bg(task, p_step);
return false;
}
}
EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false) {
if (Thread::is_main_thread()) {
EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel);
} else {
EditorNode::progress_add_task_bg(p_task, p_label, p_amount);
}
task = p_task;
}
~EditorProgress() {
if (Thread::is_main_thread()) {
EditorNode::progress_end_task(task);
} else {
EditorNode::progress_end_task_bg(task);
}
}
};
class EditorPluginList : public Object {
private:
Vector<EditorPlugin *> plugins_list;

View File

@ -2778,7 +2778,11 @@ void EditorPropertyNodePath::_update_menu() {
void EditorPropertyNodePath::_menu_option(int p_idx) {
switch (p_idx) {
case ACTION_CLEAR: {
emit_changed(get_edited_property(), NodePath());
if (editing_node) {
emit_changed(get_edited_property(), Variant());
} else {
emit_changed(get_edited_property(), NodePath());
}
update_property();
} break;

View File

@ -175,6 +175,13 @@ void EditorResourcePicker::_file_quick_selected() {
_file_selected(quick_open->get_selected());
}
void EditorResourcePicker::_resource_saved(Object *p_resource) {
if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {
emit_signal(SNAME("resource_changed"), edited_resource);
_update_resource();
}
}
void EditorResourcePicker::_update_menu() {
_update_menu_items();
@ -408,6 +415,10 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
if (edited_resource.is_null()) {
return;
}
Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
EditorNode::get_singleton()->connect("resource_saved", resource_saved);
}
EditorNode::get_singleton()->save_resource_as(edited_resource);
} break;
@ -833,6 +844,13 @@ void EditorResourcePicker::_notification(int p_what) {
assign_button->queue_redraw();
}
} break;
case NOTIFICATION_EXIT_TREE: {
Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
EditorNode::get_singleton()->disconnect("resource_saved", resource_saved);
}
} break;
}
}

View File

@ -91,6 +91,8 @@ class EditorResourcePicker : public HBoxContainer {
void _file_quick_selected();
void _file_selected(const String &p_path);
void _resource_saved(Object *p_resource);
void _update_menu();
void _update_menu_items();
void _edit_menu_cbk(int p_which);

View File

@ -685,7 +685,15 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s
}
Vector<String> FileSystemDock::get_selected_paths() const {
return _tree_get_selected(false);
if (display_mode == DISPLAY_MODE_TREE_ONLY) {
return _tree_get_selected(false);
} else {
Vector<String> selected = _file_list_get_selected();
if (selected.is_empty()) {
selected.push_back(get_current_directory());
}
return selected;
}
}
String FileSystemDock::get_current_path() const {
@ -953,7 +961,8 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
files->set_max_columns(1);
files->set_max_text_lines(1);
files->set_fixed_column_width(0);
files->set_fixed_icon_size(Size2());
const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
files->set_fixed_icon_size(Size2(icon_size, icon_size));
}
Ref<Texture2D> folder_icon = (use_thumbnails) ? folder_thumbnail : get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
@ -2049,6 +2058,15 @@ Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion, bo
return selected_strings;
}
Vector<String> FileSystemDock::_file_list_get_selected() const {
Vector<String> selected;
for (int idx : files->get_selected_items()) {
selected.push_back(files->get_item_metadata(idx));
}
return selected;
}
Vector<String> FileSystemDock::_remove_self_included_paths(Vector<String> selected_strings) {
// Remove paths or files that are included into another.
if (selected_strings.size() > 1) {
@ -3392,6 +3410,11 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton
current_path = current_path_line_edit->get_text();
// Favorites isn't a directory so don't show menu.
if (current_path == "Favorites") {
return;
}
file_list_popup->clear();
file_list_popup->reset_size();

View File

@ -359,6 +359,7 @@ private:
void _update_display_mode(bool p_force = false);
Vector<String> _tree_get_selected(bool remove_self_inclusion = true, bool p_include_unselected_cursor = false) const;
Vector<String> _file_list_get_selected() const;
bool _is_file_type_disabled_by_feature_profile(const StringName &p_class);

View File

@ -115,6 +115,8 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file
file_name = ProjectSettings::get_singleton()->localize_path(file_name);
}
}
selected_options = p_selected_options;
String f = files[0];
if (mode == FILE_MODE_OPEN_FILES) {
emit_signal(SNAME("files_selected"), files);
@ -146,7 +148,6 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file
}
file->set_text(f);
dir->set_text(f.get_base_dir());
selected_options = p_selected_options;
filter->select(p_filter);
}

View File

@ -614,13 +614,13 @@ void EditorSpinSlider::_value_focus_exited() {
// -> TAB was pressed
// -> modal_close was not called
// -> need to close/hide manually
if (value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) {
if (!is_visible_in_tree() || value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) {
// Hidden or something else took focus.
if (value_input_popup) {
value_input_popup->hide();
}
//tab was pressed
} else {
//enter, click, esc
// Enter or Esc was pressed.
grab_focus();
}
@ -664,6 +664,10 @@ bool EditorSpinSlider::is_grabbing() const {
}
void EditorSpinSlider::_focus_entered() {
if (is_read_only()) {
return;
}
_ensure_input_popup();
value_input->set_text(get_text_value());
value_input_popup->set_size(get_size());

View File

@ -490,10 +490,14 @@ void SceneTreeEditor::_update_node_tooltip(Node *p_node, TreeItem *p_item) {
String tooltip = p_node->get_name();
if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) {
p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) {
p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
}
tooltip += String("\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path());
} else if (p_node != get_scene_node() && !p_node->get_scene_file_path().is_empty() && can_open_instance) {
p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) {
p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
}
tooltip += String("\n" + TTR("Instance:") + " " + p_node->get_scene_file_path());
}
@ -1000,6 +1004,7 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) {
TreeItem *item = p_node ? _find(tree->get_root(), p_node->get_path()) : nullptr;
if (item) {
selected = p_node;
if (auto_expand_selected) {
// Make visible when it's collapsed.
TreeItem *node = item->get_parent();
@ -1009,8 +1014,24 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) {
}
item->select(0);
item->set_as_cursor(0);
selected = p_node;
tree->ensure_cursor_is_visible();
} else {
// Ensure the node is selected and visible for the user if the node
// is not collapsed.
bool collapsed = false;
TreeItem *node = item;
while (node && node != tree->get_root()) {
if (node->is_collapsed()) {
collapsed = true;
break;
}
node = node->get_parent();
}
if (!collapsed) {
item->select(0);
item->set_as_cursor(0);
tree->ensure_cursor_is_visible();
}
}
} else {
if (!p_node) {

View File

@ -293,18 +293,20 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin
}
String from_tokens;
for (int i = 0; i < kp_tokens[0].length(); i++) {
if (i <= kp_tokens[0].length() - 6 && kp_tokens[0][i] == '\\' && kp_tokens[0][i + 1] == 'u') {
if (i <= kp_tokens[0].length() - 6 && kp_tokens[0][i] == '\\' && kp_tokens[0][i + 1] == 'u' && is_hex_digit(kp_tokens[0][i + 2]) && is_hex_digit(kp_tokens[0][i + 3]) && is_hex_digit(kp_tokens[0][i + 4]) && is_hex_digit(kp_tokens[0][i + 5])) {
char32_t charcode = kp_tokens[0].substr(i + 2, 4).hex_to_int();
from_tokens += charcode;
i += 5;
} else {
from_tokens += kp_tokens[0][i];
}
}
String to_tokens;
for (int i = 0; i < kp_tokens[1].length(); i++) {
if (i <= kp_tokens[1].length() - 6 && kp_tokens[1][i] == '\\' && kp_tokens[1][i + 1] == 'u') {
if (i <= kp_tokens[1].length() - 6 && kp_tokens[1][i] == '\\' && kp_tokens[1][i + 1] == 'u' && is_hex_digit(kp_tokens[1][i + 2]) && is_hex_digit(kp_tokens[1][i + 3]) && is_hex_digit(kp_tokens[1][i + 4]) && is_hex_digit(kp_tokens[1][i + 5])) {
char32_t charcode = kp_tokens[1].substr(i + 2, 4).hex_to_int();
to_tokens += charcode;
i += 5;
} else {
to_tokens += kp_tokens[1][i];
}

View File

@ -428,10 +428,10 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
loop_end = p_options["edit/loop_end"];
// Wrap around to max frames, so `-1` can be used to select the end, etc.
if (loop_begin < 0) {
loop_begin = CLAMP(loop_begin + frames + 1, 0, frames);
loop_begin = CLAMP(loop_begin + frames, 0, frames - 1);
}
if (loop_end < 0) {
loop_end = CLAMP(loop_end + frames + 1, 0, frames);
loop_end = CLAMP(loop_end + frames, 0, frames - 1);
}
}
@ -517,16 +517,19 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
Vector<uint8_t> dst_data;
if (compression == 2) {
dst_format = AudioStreamWAV::FORMAT_QOA;
qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
qoa_desc desc = {};
uint32_t qoa_len = 0;
desc.samplerate = rate;
desc.samples = frames;
desc.channels = format_channels;
void *encoded = qoa_encode((short *)pcm_data.ptrw(), &desc, &qoa_len);
dst_data.resize(qoa_len);
memcpy(dst_data.ptrw(), encoded, qoa_len);
void *encoded = qoa_encode((short *)pcm_data.ptr(), &desc, &qoa_len);
if (encoded) {
dst_data.resize(qoa_len);
memcpy(dst_data.ptrw(), encoded, qoa_len);
QOA_FREE(encoded);
}
} else {
dst_data = pcm_data;
}

View File

@ -751,7 +751,9 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
}
void AbstractPolygon2DEditorPlugin::edit(Object *p_object) {
polygon_editor->edit(Object::cast_to<Node>(p_object));
Node *polygon_node = Object::cast_to<Node>(p_object);
polygon_editor->edit(polygon_node);
make_visible(polygon_node != nullptr);
}
bool AbstractPolygon2DEditorPlugin::handles(Object *p_object) const {

View File

@ -561,7 +561,9 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
return;
}
anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
if (!anim->get_path().is_resource_file()) {
anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
}
String base_name;
if (anim->get_name() != "") {

View File

@ -339,7 +339,17 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
track_editor->set_animation(anim, animation_is_readonly);
Node *root = player->get_node_or_null(player->get_root_node());
if (root) {
// Player shouldn't access parent if it's the scene root.
if (!root || (player == get_tree()->get_edited_scene_root() && player->get_root_node() == SceneStringName(path_pp))) {
NodePath cached_root_path = player->get_path_to(get_cached_root_node());
if (player->get_node_or_null(cached_root_path) != nullptr) {
player->set_root_node(cached_root_path);
} else {
player->set_root_node(SceneStringName(path_pp)); // No other choice, preventing crash.
}
} else {
cached_root_node_id = root->get_instance_id(); // Caching as `track_editor` can lose track of player's root node.
track_editor->set_root(root);
}
}
@ -1824,6 +1834,10 @@ AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const {
return original_node;
}
Node *AnimationPlayerEditor::get_cached_root_node() const {
return Object::cast_to<Node>(ObjectDB::get_instance(cached_root_node_id));
}
bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
bool is_valid = true;
if (!p_anim.is_valid()) {

View File

@ -52,6 +52,7 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationPlayerEditorPlugin *plugin = nullptr;
AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree.
AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy.
ObjectID cached_root_node_id;
bool is_dummy = false;
enum {
@ -251,6 +252,7 @@ public:
AnimationMixer *get_editing_node() const;
AnimationPlayer *get_player() const;
AnimationMixer *fetch_mixer_for_library() const;
Node *get_cached_root_node() const;
static AnimationPlayerEditor *get_singleton() { return singleton; }

View File

@ -3999,6 +3999,8 @@ void CanvasItemEditor::_project_settings_changed() {
void CanvasItemEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
_update_lock_and_group_button();
EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true));
EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false));
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed));
@ -4108,7 +4110,8 @@ void CanvasItemEditor::_notification(int p_what) {
}
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
case NOTIFICATION_APPLICATION_FOCUS_OUT:
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
if (drag_type != DRAG_NONE) {
_reset_drag();
viewport->queue_redraw();

View File

@ -81,6 +81,8 @@ void EditorNode3DGizmo::redraw() {
gizmo_plugin->redraw(this);
}
_update_bvh();
if (Node3DEditor::get_singleton()->is_current_selected_gizmo(this)) {
Node3DEditor::get_singleton()->update_transform_gizmo();
}
@ -244,6 +246,32 @@ void EditorNode3DGizmo::add_mesh(const Ref<Mesh> &p_mesh, const Ref<Material> &p
instances.push_back(ins);
}
void EditorNode3DGizmo::_update_bvh() {
ERR_FAIL_NULL(spatial_node);
Transform3D transform = spatial_node->get_global_transform();
float effective_icon_size = selectable_icon_size > 0.0f ? selectable_icon_size : 0.0f;
Vector3 icon_size_vector3 = Vector3(effective_icon_size, effective_icon_size, effective_icon_size);
AABB aabb(spatial_node->get_position() - icon_size_vector3 * 100.0f, icon_size_vector3 * 200.0f);
for (const Vector3 &segment_end : collision_segments) {
aabb.expand_to(transform.xform(segment_end));
}
if (collision_mesh.is_valid()) {
for (const Face3 &face : collision_mesh->get_faces()) {
aabb.expand_to(transform.xform(face.vertex[0]));
aabb.expand_to(transform.xform(face.vertex[1]));
aabb.expand_to(transform.xform(face.vertex[2]));
}
}
Node3DEditor::get_singleton()->update_gizmo_bvh_node(
bvh_node_id,
aabb);
}
void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard, const Color &p_modulate) {
add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate);
}
@ -403,12 +431,13 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref<
colors.resize(p_handles.size());
Color *w = colors.ptrw();
for (int i = 0; i < p_handles.size(); i++) {
int id = p_ids.is_empty() ? i : p_ids[i];
Color col(1, 1, 1, 1);
if (is_handle_highlighted(i, p_secondary)) {
if (is_handle_highlighted(id, p_secondary)) {
col = Color(0, 0, 1, 0.9);
}
int id = p_ids.is_empty() ? i : p_ids[i];
if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) {
col.a = 0.8;
}
@ -765,6 +794,10 @@ void EditorNode3DGizmo::create() {
instances.write[i].create_instance(spatial_node, hidden);
}
bvh_node_id = Node3DEditor::get_singleton()->insert_gizmo_bvh_node(
spatial_node,
AABB(spatial_node->get_position(), Vector3(0, 0, 0)));
transform();
}
@ -774,6 +807,8 @@ void EditorNode3DGizmo::transform() {
for (int i = 0; i < instances.size(); i++) {
RS::get_singleton()->instance_set_transform(instances[i].instance, spatial_node->get_global_transform() * instances[i].xform);
}
_update_bvh();
}
void EditorNode3DGizmo::free() {
@ -790,6 +825,9 @@ void EditorNode3DGizmo::free() {
clear();
Node3DEditor::get_singleton()->remove_gizmo_bvh_node(bvh_node_id);
bvh_node_id = DynamicBVH::ID();
valid = false;
}

View File

@ -31,6 +31,7 @@
#ifndef NODE_3D_EDITOR_GIZMOS_H
#define NODE_3D_EDITOR_GIZMOS_H
#include "core/math/dynamic_bvh.h"
#include "core/templates/hash_map.h"
#include "core/templates/local_vector.h"
#include "scene/3d/camera_3d.h"
@ -72,8 +73,12 @@ class EditorNode3DGizmo : public Node3DGizmo {
Vector<Instance> instances;
Node3D *spatial_node = nullptr;
DynamicBVH::ID bvh_node_id;
void _set_node_3d(Node *p_node) { set_node_3d(Object::cast_to<Node3D>(p_node)); }
void _update_bvh();
protected:
static void _bind_methods();

View File

@ -770,7 +770,7 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {
}
}
if (p_allow_locked || !_is_node_locked(selected)) {
if (p_allow_locked || (selected != nullptr && !_is_node_locked(selected))) {
if (clicked_wants_append) {
if (editor_selection->is_selected(selected)) {
editor_selection->remove_node(selected);
@ -800,7 +800,6 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {
RS::get_singleton()->sdfgi_set_debug_probe_select(pos, ray);
}
Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario());
HashSet<Ref<EditorNode3DGizmo>> found_gizmos;
Node *edited_scene = get_tree()->get_edited_scene_root();
@ -808,9 +807,9 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {
Node *item = nullptr;
float closest_dist = 1e20;
for (int i = 0; i < instances.size(); i++) {
Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());
for (Node3D *spat : nodes_with_gizmos) {
if (!spat) {
continue;
}
@ -863,12 +862,11 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayRe
Vector3 ray = get_ray(p_pos);
Vector3 pos = get_ray_pos(p_pos);
Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario());
Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());
HashSet<Node3D *> found_nodes;
for (int i = 0; i < instances.size(); i++) {
Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
for (Node3D *spat : nodes_with_gizmos) {
if (!spat) {
continue;
}
@ -1046,7 +1044,7 @@ void Node3DEditorViewport::_select_region() {
_clear_selected();
}
Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world_3d()->get_scenario());
Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_frustum_query(frustum);
HashSet<Node3D *> found_nodes;
Vector<Node *> selected;
@ -1055,8 +1053,7 @@ void Node3DEditorViewport::_select_region() {
return;
}
for (int i = 0; i < instances.size(); i++) {
Node3D *sp = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
for (Node3D *sp : nodes_with_gizmos) {
if (!sp || _is_node_locked(sp)) {
continue;
}
@ -1064,21 +1061,23 @@ void Node3DEditorViewport::_select_region() {
if (found_nodes.has(sp)) {
continue;
}
found_nodes.insert(sp);
Node *node = Object::cast_to<Node>(sp);
if (node != edited_scene) {
node = edited_scene->get_deepest_editable_node(node);
}
// Prevent selection of nodes not owned by the edited scene.
while (node && node != edited_scene->get_parent()) {
Node *node_owner = node->get_owner();
if (node_owner == edited_scene || node == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {
break;
// Selection requires that the node is the edited scene or its descendant, and has an owner.
if (node != edited_scene) {
if (!node->get_owner() || !edited_scene->is_ancestor_of(node)) {
continue;
}
node = edited_scene->get_deepest_editable_node(node);
while (node != edited_scene) {
Node *node_owner = node->get_owner();
if (node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {
break;
}
node = node->get_parent();
}
node = node->get_parent();
}
// Replace the node by the group if grouped
@ -1952,7 +1951,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
if (_edit.mode != TRANSFORM_NONE) {
if (!_edit.instant && _edit.mode != TRANSFORM_NONE) {
Node3D *selected = spatial_editor->get_single_selected_node();
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
@ -2252,6 +2251,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (_edit.mode == TRANSFORM_NONE) {
if (_edit.gizmo.is_null() && is_freelook_active() && k->get_keycode() == Key::ESCAPE) {
set_freelook_active(false);
return;
}
if (_edit.gizmo.is_valid() && (k->get_keycode() == Key::ESCAPE || k->get_keycode() == Key::BACKSPACE)) {
// Restore.
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);
@ -2390,15 +2394,30 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) {
cancel_transform();
}
if (!is_freelook_active()) {
if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event)) {
begin_transform(TRANSFORM_TRANSLATE, true);
if (!is_freelook_active() && !k->is_echo()) {
if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_TRANSLATE, true);
} else if (_edit.instant) {
commit_transform();
begin_transform(TRANSFORM_TRANSLATE, true);
}
}
if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event)) {
begin_transform(TRANSFORM_ROTATE, true);
if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_ROTATE, true);
} else if (_edit.instant) {
commit_transform();
begin_transform(TRANSFORM_ROTATE, true);
}
}
if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event)) {
begin_transform(TRANSFORM_SCALE, true);
if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_SCALE, true);
} else if (_edit.instant) {
commit_transform();
begin_transform(TRANSFORM_SCALE, true);
}
}
}
@ -3030,8 +3049,11 @@ void Node3DEditorViewport::_notification(int p_what) {
update_preview_node = false;
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT:
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
set_freelook_active(false);
cursor.region_select = false;
surface->queue_redraw();
} break;
case NOTIFICATION_ENTER_TREE: {
@ -3107,6 +3129,7 @@ void Node3DEditorViewport::_notification(int p_what) {
case NOTIFICATION_DRAG_END: {
// Clear preview material when dropped outside applicable object.
if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) {
_reset_preview_material();
_remove_preview_material();
} else {
_remove_preview_node();
@ -4001,6 +4024,14 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
_menu_option(VIEW_GIZMOS);
}
}
if (p_state.has("grid")) {
bool grid = p_state["grid"];
int idx = view_menu->get_popup()->get_item_index(VIEW_GRID);
if (view_menu->get_popup()->is_item_checked(idx) != grid) {
_menu_option(VIEW_GRID);
}
}
if (p_state.has("information")) {
bool information = p_state["information"];
@ -4079,6 +4110,7 @@ Dictionary Node3DEditorViewport::get_state() const {
d["listener"] = viewport->is_audio_listener_3d();
d["doppler"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER));
d["gizmos"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS));
d["grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GRID));
d["information"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION));
d["frame_time"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));
d["half_res"] = subviewport_container->get_stretch_shrink() > 1;
@ -5001,14 +5033,24 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
} break;
case TRANSFORM_ROTATE: {
Plane plane = Plane(_get_camera_normal(), _edit.center);
Plane plane;
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
Vector3 cam_to_obj = _edit.center - _get_camera_position();
if (!cam_to_obj.is_zero_approx()) {
plane = Plane(cam_to_obj.normalized(), _edit.center);
} else {
plane = Plane(_get_camera_normal(), _edit.center);
}
} else {
plane = Plane(_get_camera_normal(), _edit.center);
}
Vector3 local_axis;
Vector3 global_axis;
switch (_edit.plane) {
case TRANSFORM_VIEW:
// local_axis unused
global_axis = _get_camera_normal();
global_axis = plane.normal;
break;
case TRANSFORM_X_AXIS:
local_axis = Vector3(1, 0, 0);
@ -5039,7 +5081,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
break;
}
static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(87.0f));
static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(85.0f));
bool axis_is_orthogonal = ABS(plane.normal.dot(global_axis)) < orthogonal_threshold;
double angle = 0.0f;
@ -7746,6 +7788,9 @@ void Node3DEditor::_sun_environ_settings_pressed() {
sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0));
sun_environ_popup->reset_size();
sun_environ_popup->popup();
// Grabbing the focus is required for Shift modifier checking to be functional
// (when the Add sun/environment buttons are pressed).
sun_environ_popup->grab_focus();
}
void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) {
@ -9208,6 +9253,49 @@ void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
_update_gizmos_menu();
}
DynamicBVH::ID Node3DEditor::insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb) {
return gizmo_bvh.insert(p_aabb, p_node);
}
void Node3DEditor::update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb) {
gizmo_bvh.update(p_id, p_aabb);
gizmo_bvh.optimize_incremental(1);
}
void Node3DEditor::remove_gizmo_bvh_node(DynamicBVH::ID p_id) {
gizmo_bvh.remove(p_id);
}
Vector<Node3D *> Node3DEditor::gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end) {
struct Result {
Vector<Node3D *> nodes;
bool operator()(void *p_data) {
nodes.append((Node3D *)p_data);
return false;
}
} result;
gizmo_bvh.ray_query(p_ray_start, p_ray_end, result);
return result.nodes;
}
Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum) {
Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&p_frustum[0], p_frustum.size());
struct Result {
Vector<Node3D *> nodes;
bool operator()(void *p_data) {
nodes.append((Node3D *)p_data);
return false;
}
} result;
gizmo_bvh.convex_query(p_frustum.ptr(), p_frustum.size(), points.ptr(), points.size(), result);
return result.nodes;
}
Node3DEditorPlugin::Node3DEditorPlugin() {
spatial_editor = memnew(Node3DEditor);
spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);

View File

@ -31,6 +31,7 @@
#ifndef NODE_3D_EDITOR_PLUGIN_H
#define NODE_3D_EDITOR_PLUGIN_H
#include "core/math/dynamic_bvh.h"
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/node_3d_editor_gizmos.h"
#include "editor/themes/editor_scale.h"
@ -629,6 +630,8 @@ private:
int current_hover_gizmo_handle;
bool current_hover_gizmo_handle_secondary;
DynamicBVH gizmo_bvh;
real_t snap_translate_value;
real_t snap_rotate_value;
real_t snap_scale_value;
@ -933,6 +936,12 @@ public:
void add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
void remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
DynamicBVH::ID insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb);
void update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb);
void remove_gizmo_bvh_node(DynamicBVH::ID p_id);
Vector<Node3D *> gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end);
Vector<Node3D *> gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum);
void edit(Node3D *p_spatial);
void clear();

View File

@ -83,9 +83,8 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path,
int idx_last = atr_owners.size() - 1;
if (idx_last > 0 && !parent_path.begins_with(atr_owners[idx_last].first)) {
// Switch to the previous auto translation owner this was nested in, if that was the case.
// Exit from the current owner nesting into the previous one.
atr_owners.remove_at(idx_last);
idx_last -= 1;
}
if (property == "auto_translate_mode") {
@ -106,7 +105,7 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path,
// If `auto_translate_mode` wasn't found, that means it is set to its default value (`AUTO_TRANSLATE_MODE_INHERIT`).
if (!auto_translate_mode_found) {
int idx_last = atr_owners.size() - 1;
if (idx_last > 0 && atr_owners[idx_last].first == parent_path) {
if (idx_last > 0 && parent_path.begins_with(atr_owners[idx_last].first)) {
auto_translating = atr_owners[idx_last].second;
} else {
atr_owners.push_back(Pair(state->get_node_path(i), true));

View File

@ -197,12 +197,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
const Vector2 new_point = xform.affine_inverse().xform(gpoint2);
curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Split Curve"));
undo_redo->add_do_method(curve.ptr(), "add_point", new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1);
action = ACTION_MOVING_NEW_POINT;
action = ACTION_MOVING_NEW_POINT_FROM_SPLIT;
action_point = insertion_point + 1;
moving_from = curve->get_point_position(action_point);
moving_screen_from = gpoint2;
@ -245,6 +240,16 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
undo_redo->commit_action(false);
} break;
case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
undo_redo->create_action(TTR("Split Curve"));
undo_redo->add_do_method(curve.ptr(), "add_point", Vector2(), Vector2(), Vector2(), action_point);
undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
undo_redo->add_undo_method(curve.ptr(), "remove_point", action_point);
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action(false);
} break;
case ACTION_MOVING_IN: {
if (original_mouse_pos != gpoint) {
undo_redo->create_action(TTR("Move In-Control in Curve"));
@ -350,7 +355,8 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
break;
case ACTION_MOVING_POINT:
case ACTION_MOVING_NEW_POINT: {
case ACTION_MOVING_NEW_POINT:
case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
curve->set_point_position(action_point, cpoint);
} break;
@ -573,6 +579,10 @@ void Path2DEditor::_cancel_current_action() {
curve->remove_point(curve->get_point_count() - 1);
} break;
case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
curve->remove_point(action_point);
} break;
case ACTION_MOVING_IN: {
curve->set_point_in(action_point, moving_from);
curve->set_point_out(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));

View File

@ -81,6 +81,7 @@ class Path2DEditor : public HBoxContainer {
ACTION_NONE,
ACTION_MOVING_POINT,
ACTION_MOVING_NEW_POINT,
ACTION_MOVING_NEW_POINT_FROM_SPLIT,
ACTION_MOVING_IN,
ACTION_MOVING_OUT,
};

View File

@ -181,7 +181,7 @@ void Polygon2DEditor::_sync_bones() {
}
if (weights.size() == 0) { //create them
weights.resize(node->get_polygon().size());
weights.resize(wc);
float *w = weights.ptrw();
for (int j = 0; j < wc; j++) {
w[j] = 0.0;
@ -850,8 +850,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
if (mm.is_valid()) {
if (uv_drag) {
Vector2 uv_drag_to = mm->get_position();
uv_drag_to = snap_point(uv_drag_to); // FIXME: Only works correctly with 'UV_MODE_EDIT_POINT', it's imprecise with the rest.
Vector2 drag = mtx.affine_inverse().xform(uv_drag_to) - mtx.affine_inverse().xform(uv_drag_from);
uv_drag_to = snap_point(uv_drag_to);
Vector2 drag = mtx.affine_inverse().basis_xform(uv_drag_to - uv_drag_from);
switch (uv_move_current) {
case UV_MODE_CREATE: {
@ -1166,12 +1166,8 @@ void Polygon2DEditor::_uv_draw() {
poly_line_color.a *= 0.25;
}
Color polygon_line_color = Color(0.5, 0.5, 0.9);
Vector<Color> polygon_fill_color;
{
Color pf = polygon_line_color;
pf.a *= 0.5;
polygon_fill_color.push_back(pf);
}
Color polygon_fill_color = polygon_line_color;
polygon_fill_color.a *= 0.5;
Color prev_color = Color(0.5, 0.5, 0.5);
int uv_draw_max = uvs.size();
@ -1216,7 +1212,7 @@ void Polygon2DEditor::_uv_draw() {
uv_edit_draw->draw_line(mtx.xform(uvs[idx]), mtx.xform(uvs[idx_next]), polygon_line_color, Math::round(EDSCALE));
}
if (points.size() >= 3) {
uv_edit_draw->draw_polygon(polypoints, polygon_fill_color);
uv_edit_draw->draw_colored_polygon(polypoints, polygon_fill_color);
}
}
@ -1309,8 +1305,8 @@ void Polygon2DEditor::_bind_methods() {
Vector2 Polygon2DEditor::snap_point(Vector2 p_target) const {
if (use_snap) {
p_target.x = Math::snap_scalar(snap_offset.x * uv_draw_zoom - uv_draw_ofs.x, snap_step.x * uv_draw_zoom, p_target.x);
p_target.y = Math::snap_scalar(snap_offset.y * uv_draw_zoom - uv_draw_ofs.y, snap_step.y * uv_draw_zoom, p_target.y);
p_target.x = Math::snap_scalar((snap_offset.x - uv_draw_ofs.x) * uv_draw_zoom, snap_step.x * uv_draw_zoom, p_target.x);
p_target.y = Math::snap_scalar((snap_offset.y - uv_draw_ofs.y) * uv_draw_zoom, snap_step.y * uv_draw_zoom, p_target.y);
}
return p_target;
@ -1486,7 +1482,7 @@ Polygon2DEditor::Polygon2DEditor() {
grid_settings = memnew(AcceptDialog);
grid_settings->set_title(TTR("Configure Grid:"));
add_child(grid_settings);
uv_edit->add_child(grid_settings);
VBoxContainer *grid_settings_vb = memnew(VBoxContainer);
grid_settings->add_child(grid_settings_vb);

View File

@ -2711,9 +2711,11 @@ void ScriptEditor::apply_scripts() const {
}
void ScriptEditor::reload_scripts(bool p_refresh_only) {
if (external_editor_active) {
return;
}
// Call deferred to make sure it runs on the main thread.
callable_mp(this, &ScriptEditor::_reload_scripts).call_deferred(p_refresh_only);
}
void ScriptEditor::_reload_scripts(bool p_refresh_only) {
for (int i = 0; i < tab_container->get_tab_count(); i++) {
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
if (!se) {

View File

@ -436,6 +436,7 @@ class ScriptEditor : public PanelContainer {
void _file_removed(const String &p_file);
void _autosave_scripts();
void _update_autosave_timer();
void _reload_scripts(bool p_refresh_only = false);
void _update_members_overview_visibility();
void _update_members_overview();

View File

@ -372,7 +372,7 @@ void TileAtlasView::_draw_base_tiles_shape_grid() {
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_data(tile_id, 0)->get_texture_origin();
if (tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, -tile_shape_size / 2) && tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, tile_shape_size / 2 - Vector2(1, 1))) {
if (tile_set_atlas_source->is_rect_in_tile_texture_region(tile_id, 0, Rect2(Vector2(-tile_shape_size) / 2, tile_shape_size))) {
for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
Color color = grid_color;
if (frame > 0) {

View File

@ -165,10 +165,14 @@ void GenericTilePolygonEditor::_base_control_draw() {
base_control->draw_set_transform_matrix(xform);
// Draw fill rect under texture region.
Rect2 texture_rect(-background_region.size / 2, background_region.size);
Rect2 texture_rect(Vector2(), background_region.size);
if (tile_data) {
texture_rect.position -= tile_data->get_texture_origin();
if (tile_data->get_transpose()) {
texture_rect.size = Size2(texture_rect.size.y, texture_rect.size.x);
}
}
texture_rect.position -= texture_rect.size / 2; // Half-size offset must be applied after transposing.
base_control->draw_rect(texture_rect, Color(1, 1, 1, 0.3));
// Draw the background.
@ -180,18 +184,14 @@ void GenericTilePolygonEditor::_base_control_draw() {
if (tile_data->get_flip_v()) {
region_size.y = -region_size.y;
}
base_control->draw_texture_rect_region(background_atlas_source->get_texture(), Rect2(-background_region.size / 2 - tile_data->get_texture_origin(), region_size), background_region, tile_data->get_modulate(), tile_data->get_transpose());
// Destination rect position must account for transposing, size must not.
base_control->draw_texture_rect_region(background_atlas_source->get_texture(), Rect2(texture_rect.position, region_size), background_region, tile_data->get_modulate(), tile_data->get_transpose());
}
// Compute and draw the grid area.
Rect2 grid_area = Rect2(-base_tile_size / 2, base_tile_size);
if (tile_data) {
grid_area.expand_to(-background_region.get_size() / 2 - tile_data->get_texture_origin());
grid_area.expand_to(background_region.get_size() / 2 - tile_data->get_texture_origin());
} else {
grid_area.expand_to(-background_region.get_size() / 2);
grid_area.expand_to(background_region.get_size() / 2);
}
grid_area.expand_to(texture_rect.position);
grid_area.expand_to(texture_rect.get_end());
base_control->draw_rect(grid_area, Color(1, 1, 1, 0.3), false);
// Draw grid.
@ -1385,10 +1385,8 @@ void TileDataTextureOriginEditor::draw_over_tile(CanvasItem *p_canvas_item, Tran
TileSetSource *source = *(tile_set->get_source(p_cell.source_id));
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, -tile_set_tile_size / 2) && atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, tile_set_tile_size / 2 - Vector2(1, 1))) {
Transform2D tile_xform;
tile_xform.set_scale(tile_set_tile_size);
tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, color);
if (atlas_source->is_rect_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, Rect2(Vector2(-tile_set_tile_size) / 2, tile_set_tile_size))) {
tile_set->draw_tile_shape(p_canvas_item, p_transform.scaled_local(tile_set_tile_size), color);
}
if (atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, Vector2())) {

View File

@ -1366,11 +1366,13 @@ void TileMapLayerEditorTilesPlugin::_stop_dragging() {
Vector2i coords;
HashMap<Vector2i, TileMapCell> cells_undo;
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
coords = tile_set->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(edited_layer->get_cell_source_id(coords), edited_layer->get_cell_atlas_coords(coords), edited_layer->get_cell_alternative_tile(coords));
}
for (int i = 0; i < selection_used_cells.size(); i++) {
coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
}
// Build the list of cells to do.
HashMap<Vector2i, TileMapCell> cells_do;

View File

@ -115,8 +115,8 @@ void VSGraphNode::_draw_port(int p_slot_index, Point2i p_pos, bool p_left, const
icon_offset = -port_icon->get_size() * 0.5;
// Draw "shadow"/outline in the connection rim color.
draw_texture_rect(port_icon, Rect2(p_pos + icon_offset - Size2(2, 2), port_icon->get_size() + Size2(4, 4)), false, p_rim_color);
draw_texture(port_icon, p_pos + icon_offset, p_color);
draw_texture_rect(port_icon, Rect2(p_pos + (icon_offset - Size2(2, 2)) * EDSCALE, (port_icon->get_size() + Size2(4, 4)) * EDSCALE), false, p_rim_color);
draw_texture_rect(port_icon, Rect2(p_pos + icon_offset * EDSCALE, port_icon->get_size() * EDSCALE), false, p_color);
}
void VSGraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) {
@ -154,7 +154,6 @@ VSRerouteNode::VSRerouteNode() {
title_lbl->hide();
const Size2 size = Size2(32, 32) * EDSCALE;
print_line("VSRerouteNode size: " + size);
Control *slot_area = memnew(Control);
slot_area->set_custom_minimum_size(size);
@ -2888,8 +2887,8 @@ void VisualShaderEditor::_frame_title_popup_show(const Point2 &p_position, int p
}
frame_title_change_edit->set_text(node->get_title());
frame_title_change_popup->set_meta("id", p_node_id);
frame_title_change_popup->popup();
frame_title_change_popup->set_position(p_position);
frame_title_change_popup->popup();
// Select current text.
frame_title_change_edit->grab_focus();

View File

@ -162,7 +162,7 @@ void ProjectDialog::_validate_path() {
}
}
if (target_path.is_empty() || target_path.is_relative_path()) {
if (target_path.is_relative_path()) {
_set_message(TTR("The path specified is invalid."), MESSAGE_ERROR, target_path_input_type);
return;
}
@ -352,7 +352,7 @@ void ProjectDialog::_install_path_changed() {
void ProjectDialog::_browse_project_path() {
String path = project_path->get_text();
if (path.is_empty()) {
if (path.is_relative_path()) {
path = EDITOR_GET("filesystem/directories/default_project_path");
}
if (mode == MODE_IMPORT && install_path->is_visible_in_tree()) {
@ -382,12 +382,16 @@ void ProjectDialog::_browse_project_path() {
void ProjectDialog::_browse_install_path() {
ERR_FAIL_COND_MSG(mode != MODE_IMPORT, "Install path is only used for MODE_IMPORT.");
String path = install_path->get_text();
if (path.is_relative_path() || !DirAccess::dir_exists_absolute(path)) {
path = EDITOR_GET("filesystem/directories/default_project_path");
}
if (create_dir->is_pressed()) {
// Select parent directory of install path.
fdialog_install->set_current_dir(install_path->get_text().get_base_dir());
fdialog_install->set_current_dir(path.get_base_dir());
} else {
// Select install path.
fdialog_install->set_current_dir(install_path->get_text());
fdialog_install->set_current_dir(path);
}
fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);

View File

@ -760,7 +760,7 @@ void ProjectList::_create_project_item_control(int p_index) {
hb->set_tags(item.tags, this);
hb->set_unsupported_features(item.unsupported_features.duplicate());
hb->set_project_version(item.project_version);
hb->set_last_edited_info(Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true));
hb->set_last_edited_info(!item.missing ? Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true) : TTR("Missing Date"));
hb->set_is_favorite(item.favorite);
hb->set_is_missing(item.missing);

View File

@ -1181,7 +1181,16 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
case TOOL_OPEN_DOCUMENTATION: {
List<Node *> selection = editor_selection->get_selected_node_list();
for (const Node *node : selection) {
ScriptEditor::get_singleton()->goto_help("class_name:" + node->get_class());
String class_name;
Ref<Script> script_base = node->get_script();
if (script_base.is_valid()) {
class_name = script_base->get_global_name();
}
if (class_name.is_empty()) {
class_name = node->get_class();
}
ScriptEditor::get_singleton()->goto_help("class_name:" + class_name);
}
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
} break;
@ -2226,8 +2235,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) {
}
void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, Vector<Node *> p_nodes, bool p_keep_global_xform) {
Node *new_parent = p_new_parent;
ERR_FAIL_NULL(new_parent);
ERR_FAIL_NULL(p_new_parent);
if (p_nodes.size() == 0) {
return; // Nothing to reparent.
@ -2267,7 +2275,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
// Prevent selecting the hovered node and keep the reparented node(s) selected instead.
hovered_but_reparenting = true;
Node *validate = new_parent;
Node *validate = p_new_parent;
while (validate) {
ERR_FAIL_COND_MSG(p_nodes.has(validate), "Selection changed at some point. Can't reparent.");
validate = validate->get_parent();
@ -2289,7 +2297,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
// No undo implemented for this yet.
Node *node = p_nodes[ni];
fill_path_renames(node, new_parent, &path_renames);
fill_path_renames(node, p_new_parent, &path_renames);
former_names.push_back(node->get_name());
List<Node *> owned;
@ -2299,7 +2307,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
owners.push_back(E);
}
bool same_parent = new_parent == node->get_parent();
bool same_parent = p_new_parent == node->get_parent();
if (same_parent && node->get_index(false) < p_position_in_parent + ni) {
inc--; // If the child will generate a gap when moved, adjust.
}
@ -2310,17 +2318,17 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
need_edit = select_node_hovered_at_end_of_drag;
} else {
undo_redo->add_do_method(node->get_parent(), "remove_child", node);
undo_redo->add_do_method(new_parent, "add_child", node, true);
undo_redo->add_do_method(p_new_parent, "add_child", node, true);
}
int new_position_in_parent = p_position_in_parent == -1 ? -1 : p_position_in_parent + inc;
if (new_position_in_parent >= 0 || same_parent) {
undo_redo->add_do_method(new_parent, "move_child", node, new_position_in_parent);
undo_redo->add_do_method(p_new_parent, "move_child", node, new_position_in_parent);
}
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
String old_name = former_names[ni];
String new_name = new_parent->validate_child_name(node);
String new_name = p_new_parent->validate_child_name(node);
// Name was modified, fix the path renames.
if (old_name.casecmp_to(new_name) != 0) {
@ -2345,8 +2353,12 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
}
}
undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, new_position_in_parent);
undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false));
// FIXME: Live editing for "Reparent to New Node" option is broken.
// We must get the path to `p_new_parent` *after* it was added to the scene.
if (p_new_parent->is_inside_tree()) {
undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(p_new_parent), new_name, new_position_in_parent);
undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(p_new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false));
}
if (p_keep_global_xform) {
if (Object::cast_to<Node2D>(node)) {
@ -2366,7 +2378,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
undo_redo->add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node);
}
undo_redo->add_undo_method(new_parent, "remove_child", node);
undo_redo->add_undo_method(p_new_parent, "remove_child", node);
undo_redo->add_undo_method(node, "set_name", former_names[ni]);
inc++;
@ -2384,7 +2396,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
}
int child_pos = node->get_index(false);
bool reparented_to_container = Object::cast_to<Container>(new_parent) && Object::cast_to<Control>(node);
bool reparented_to_container = Object::cast_to<Container>(p_new_parent) && Object::cast_to<Control>(node);
undo_redo->add_undo_method(node->get_parent(), "add_child", node, true);
undo_redo->add_undo_method(node->get_parent(), "move_child", node, child_pos);
@ -2641,6 +2653,13 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
}
}
if (!entire_scene) {
for (const Node *E : remove_list) {
// `move_child` + `get_index` doesn't really work for internal nodes.
ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
}
}
EditorNode::get_singleton()->hide_unused_editors(this);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
@ -2653,10 +2672,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
undo_redo->add_undo_method(scene_tree, "update_tree");
undo_redo->add_undo_reference(edited_scene);
} else {
for (const Node *E : remove_list) {
// `move_child` + `get_index` doesn't really work for internal nodes.
ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
}
if (delete_tracks_checkbox->is_pressed() || p_cut) {
remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions.
HashMap<Node *, NodePath> path_renames;
@ -2761,10 +2776,10 @@ void SceneTreeDock::_selection_changed() {
_update_script_button();
}
void SceneTreeDock::_do_create(Node *p_parent) {
Node *SceneTreeDock::_do_create(Node *p_parent) {
Variant c = create_dialog->instantiate_selected();
Node *child = Object::cast_to<Node>(c);
ERR_FAIL_NULL(child);
ERR_FAIL_NULL_V(child, nullptr);
String new_name = p_parent->validate_child_name(child);
if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) {
@ -2778,8 +2793,6 @@ void SceneTreeDock::_do_create(Node *p_parent) {
if (edited_scene) {
undo_redo->add_do_method(p_parent, "add_child", child, true);
undo_redo->add_do_method(child, "set_owner", edited_scene);
undo_redo->add_do_method(editor_selection, "clear");
undo_redo->add_do_method(editor_selection, "add_node", child);
undo_redo->add_do_reference(child);
undo_redo->add_undo_method(p_parent, "remove_child", child);
@ -2794,28 +2807,34 @@ void SceneTreeDock::_do_create(Node *p_parent) {
undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
}
undo_redo->add_do_method(this, "_post_do_create", child);
undo_redo->commit_action();
_push_item(c);
editor_selection->clear();
editor_selection->add_node(child);
if (Object::cast_to<Control>(c)) {
//make editor more comfortable, so some controls don't appear super shrunk
Control *ct = Object::cast_to<Control>(c);
Size2 ms = ct->get_minimum_size();
return child;
}
void SceneTreeDock::_post_do_create(Node *p_child) {
editor_selection->clear();
editor_selection->add_node(p_child);
_push_item(p_child);
// Make editor more comfortable, so some controls don't appear super shrunk.
Control *control = Object::cast_to<Control>(p_child);
if (control) {
Size2 ms = control->get_minimum_size();
if (ms.width < 4) {
ms.width = 40;
}
if (ms.height < 4) {
ms.height = 40;
}
if (ct->is_layout_rtl()) {
ct->set_position(ct->get_position() - Vector2(ms.x, 0));
if (control->is_layout_rtl()) {
control->set_position(control->get_position() - Vector2(ms.x, 0));
}
ct->set_size(ms);
control->set_size(ms);
}
emit_signal(SNAME("node_created"), c);
emit_signal(SNAME("node_created"), p_child);
}
void SceneTreeDock::_create() {
@ -2898,22 +2917,24 @@ void SceneTreeDock::_create() {
}
Node *parent = nullptr;
int original_position = -1;
if (only_one_top_node) {
parent = top_node->get_parent();
original_position = top_node->get_index(false);
} else {
parent = top_node->get_parent()->get_parent();
}
_do_create(parent);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action_for_history(TTR("Reparent to New Node"), editor_data->get_current_edited_scene_history_id());
Node *last_created = _do_create(parent);
Vector<Node *> nodes;
for (Node *E : selection) {
nodes.push_back(E);
}
// This works because editor_selection was cleared and populated with last created node in _do_create()
Node *last_created = editor_selection->get_selected_node_list().front()->get();
if (center_parent) {
// Find parent type and only average positions of relevant nodes.
Node3D *parent_node_3d = Object::cast_to<Node3D>(last_created);
@ -2952,6 +2973,11 @@ void SceneTreeDock::_create() {
}
_do_reparent(last_created, -1, nodes, true);
if (only_one_top_node) {
undo_redo->add_do_method(parent, "move_child", last_created, original_position);
}
undo_redo->commit_action();
}
scene_tree->get_scene_tree()->grab_focus();
@ -4354,6 +4380,7 @@ void SceneTreeDock::_edit_subresource(int p_idx, const PopupMenu *p_from_menu) {
}
void SceneTreeDock::_bind_methods() {
ClassDB::bind_method(D_METHOD("_post_do_create"), &SceneTreeDock::_post_do_create);
ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners);
ClassDB::bind_method(D_METHOD("_reparent_nodes_to_root"), &SceneTreeDock::_reparent_nodes_to_root);
ClassDB::bind_method(D_METHOD("_reparent_nodes_to_paths_with_transform_and_name"), &SceneTreeDock::_reparent_nodes_to_paths_with_transform_and_name);

View File

@ -179,7 +179,8 @@ class SceneTreeDock : public VBoxContainer {
bool first_enter = true;
void _create();
void _do_create(Node *p_parent);
Node *_do_create(Node *p_parent);
void _post_do_create(Node *p_child);
Node *scene_root = nullptr;
Node *edited_scene = nullptr;
Node *pending_click_select = nullptr;

View File

@ -1419,21 +1419,24 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_icon("decrement_highlight", "VScrollBar", empty_icon);
p_theme->set_icon("decrement_pressed", "VScrollBar", empty_icon);
// Slider
const int background_margin = MAX(2, p_config.base_margin / 2);
// HSlider.
p_theme->set_icon("grabber_highlight", "HSlider", p_theme->get_icon(SNAME("GuiSliderGrabberHl"), EditorStringName(EditorIcons)));
p_theme->set_icon("grabber", "HSlider", p_theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons)));
p_theme->set_stylebox("slider", "HSlider", make_flat_stylebox(p_config.dark_color_3, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2, p_config.corner_radius));
p_theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2, p_config.corner_radius));
p_theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2));
p_theme->set_stylebox("slider", "HSlider", make_flat_stylebox(p_config.dark_color_3, 0, background_margin, 0, background_margin, p_config.corner_radius));
p_theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, background_margin, 0, background_margin, p_config.corner_radius));
p_theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, background_margin, 0, background_margin));
p_theme->set_constant("center_grabber", "HSlider", 0);
p_theme->set_constant("grabber_offset", "HSlider", 0);
// VSlider.
p_theme->set_icon("grabber", "VSlider", p_theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons)));
p_theme->set_icon("grabber_highlight", "VSlider", p_theme->get_icon(SNAME("GuiSliderGrabberHl"), EditorStringName(EditorIcons)));
p_theme->set_stylebox("slider", "VSlider", make_flat_stylebox(p_config.dark_color_3, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0, p_config.corner_radius));
p_theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(p_config.contrast_color_1, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0, p_config.corner_radius));
p_theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(p_config.contrast_color_1, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0));
p_theme->set_stylebox("slider", "VSlider", make_flat_stylebox(p_config.dark_color_3, background_margin, 0, background_margin, 0, p_config.corner_radius));
p_theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(p_config.contrast_color_1, background_margin, 0, background_margin, 0, p_config.corner_radius));
p_theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(p_config.contrast_color_1, background_margin, 0, background_margin, 0));
p_theme->set_constant("center_grabber", "VSlider", 0);
p_theme->set_constant("grabber_offset", "VSlider", 0);
}
@ -1768,7 +1771,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_color("background", EditorStringName(Editor), background_color_opaque);
p_theme->set_stylebox("Background", EditorStringName(EditorStyles), make_flat_stylebox(background_color_opaque, p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin));
p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), p_config.base_style);
Ref<StyleBoxFlat> editor_panel_foreground = p_config.base_style->duplicate();
editor_panel_foreground->set_corner_radius_all(0);
p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), editor_panel_foreground);
// Editor focus.
p_theme->set_stylebox("Focus", EditorStringName(EditorStyles), p_config.button_style_focus);

View File

@ -1511,6 +1511,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
main_args.push_back(arg);
main_args.push_back(N->get());
N = N->next();
// GDScript docgen requires Autoloads, but loading those also creates a main loop.
// This forces main loop to quit without adding more GDScript-specific exceptions to setup.
quit_after = 1;
} else {
OS::get_singleton()->print("Missing relative or absolute path to project for --gdscript-docs, aborting.\n");
goto error;
@ -2465,7 +2468,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup");
if (p_second_phase) {
return setup2();
exit_err = setup2();
if (exit_err != OK) {
goto error;
}
}
return OK;
@ -2763,6 +2769,30 @@ Error Main::setup2(bool p_show_boot_logo) {
if (err != OK || display_server == nullptr) {
ERR_PRINT("Unable to create DisplayServer, all display drivers failed.\nUse \"--headless\" command line argument to run the engine in headless mode if this is desired (e.g. for continuous integration).");
if (display_server) {
memdelete(display_server);
}
GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_SERVERS);
uninitialize_modules(MODULE_INITIALIZATION_LEVEL_SERVERS);
unregister_server_types();
if (input) {
memdelete(input);
}
if (tsman) {
memdelete(tsman);
}
#ifndef _3D_DISABLED
if (physics_server_3d_manager) {
memdelete(physics_server_3d_manager);
}
#endif // _3D_DISABLED
if (physics_server_2d_manager) {
memdelete(physics_server_2d_manager);
}
return err;
}
@ -3062,6 +3092,14 @@ Error Main::setup2(bool p_show_boot_logo) {
OS::get_singleton()->benchmark_end_measure("Scene", "Modules and Extensions");
}
PackedStringArray extensions;
extensions.push_back("gd");
if (ClassDB::class_exists("CSharpScript")) {
extensions.push_back("cs");
}
extensions.push_back("gdshader");
GLOBAL_DEF_NOVAL(PropertyInfo(Variant::PACKED_STRING_ARRAY, "editor/script/search_in_file_extensions"), extensions); // Note: should be defined after Scene level modules init to see .NET.
OS::get_singleton()->benchmark_end_measure("Startup", "Scene");
#ifdef TOOLS_ENABLED
@ -3333,13 +3371,16 @@ int Main::start() {
gdscript_docs_path = E->next()->get();
#endif
} else if (E->get() == "--export-release") {
ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting.");
editor = true; //needs editor
_export_preset = E->next()->get();
} else if (E->get() == "--export-debug") {
ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting.");
editor = true; //needs editor
_export_preset = E->next()->get();
export_debug = true;
} else if (E->get() == "--export-pack") {
ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting.");
editor = true;
_export_preset = E->next()->get();
export_pack_only = true;
@ -3351,6 +3392,8 @@ int Main::start() {
if (parsed_pair) {
E = E->next();
}
} else if (E->get().begins_with("--export-")) {
ERR_FAIL_V_MSG(EXIT_FAILURE, "Missing export preset name, aborting.");
}
#ifdef TOOLS_ENABLED
// Handle case where no path is given to --doctool.
@ -4189,7 +4232,7 @@ bool Main::iteration() {
}
#ifdef TOOLS_ENABLED
if (wait_for_import && EditorFileSystem::get_singleton()->doing_first_scan()) {
if (wait_for_import && EditorFileSystem::get_singleton() && EditorFileSystem::get_singleton()->doing_first_scan()) {
exit = false;
}
#endif

View File

@ -905,21 +905,18 @@ def show_progress(env):
node_count_fname = str(env.Dir("#")) + "/.scons_node_count"
import math
import time
class cache_progress:
# The default is 1 GB cache and 12 hours half life
def __init__(self, path=None, limit=1073741824, half_life=43200):
# The default is 1 GB cache
def __init__(self, path=None, limit=pow(1024, 3)):
self.path = path
self.limit = limit
self.exponent_scale = math.log(2) / half_life
if env["verbose"] and path is not None:
screen.write(
"Current cache limit is {} (used: {})\n".format(
self.convert_size(limit), self.convert_size(self.get_size(path))
)
)
self.delete(self.file_list())
def __call__(self, node, *args, **kw):
nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress
@ -936,12 +933,66 @@ def show_progress(env):
screen.write("\r[Initial build] ")
screen.flush()
def convert_size(self, size_bytes):
if size_bytes == 0:
return "0 bytes"
size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return "%s %s" % (int(s) if i == 0 else s, size_name[i])
def get_size(self, start_path="."):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
return total_size
def progress_finish(target, source, env):
nonlocal node_count, progressor
try:
with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f:
f.write("%d\n" % node_count)
except Exception:
pass
try:
with open(node_count_fname, "r", encoding="utf-8") as f:
node_count_max = int(f.readline())
except Exception:
pass
cache_directory = os.environ.get("SCONS_CACHE")
# Simple cache pruning, attached to SCons' progress callback. Trim the
# cache directory to a size not larger than cache_limit.
cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
progressor = cache_progress(cache_directory, cache_limit)
Progress(progressor, interval=node_count_interval)
progress_finish_command = Command("progress_finish", [], progress_finish)
AlwaysBuild(progress_finish_command)
def clean_cache(env):
import atexit
import time
class cache_clean:
def __init__(self, path=None, limit=pow(1024, 3)):
self.path = path
self.limit = limit
def clean(self):
self.delete(self.file_list())
def delete(self, files):
if len(files) == 0:
return
if env["verbose"]:
# Utter something
screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file"))
print("Purging %d %s from cache..." % (len(files), "files" if len(files) > 1 else "file"))
[os.remove(f) for f in files]
def file_list(self):
@ -975,47 +1026,20 @@ def show_progress(env):
else:
return [x[0] for x in file_stat[mark:]]
def convert_size(self, size_bytes):
if size_bytes == 0:
return "0 bytes"
size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return "%s %s" % (int(s) if i == 0 else s, size_name[i])
def get_size(self, start_path="."):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
return total_size
def progress_finish(target, source, env):
nonlocal node_count, progressor
def cache_finally():
nonlocal cleaner
try:
with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f:
f.write("%d\n" % node_count)
progressor.delete(progressor.file_list())
cleaner.clean()
except Exception:
pass
try:
with open(node_count_fname, "r", encoding="utf-8") as f:
node_count_max = int(f.readline())
except Exception:
pass
cache_directory = os.environ.get("SCONS_CACHE")
# Simple cache pruning, attached to SCons' progress callback. Trim the
# cache directory to a size not larger than cache_limit.
cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
progressor = cache_progress(cache_directory, cache_limit)
Progress(progressor, interval=node_count_interval)
cleaner = cache_clean(cache_directory, cache_limit)
progress_finish_command = Command("progress_finish", [], progress_finish)
AlwaysBuild(progress_finish_command)
atexit.register(cache_finally)
def dump(env):

View File

@ -3225,6 +3225,26 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
}
}
#ifdef DEBUG_ENABLED
// Consider `Signal(self, "my_signal")` as an implicit use of the signal.
if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) {
const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0];
if (object_arg && object_arg->type == GDScriptParser::Node::SELF) {
const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1];
if (signal_arg && signal_arg->is_constant) {
const StringName &signal_name = signal_arg->reduced_value;
if (parser->current_class->has_member(signal_name)) {
const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
member.signal->usages++;
}
}
}
}
}
#endif
p_call->set_datatype(call_type);
return;
} else if (GDScriptUtilityFunctions::function_exists(function_name)) {
@ -3459,6 +3479,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type);
}
// Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal.
if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) {
const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0];
if (signal_arg && signal_arg->is_constant) {
const StringName &signal_name = signal_arg->reduced_value;
if (parser->current_class->has_member(signal_name)) {
const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
member.signal->usages++;
}
}
}
}
#endif // DEBUG_ENABLED
call_type = return_type;

View File

@ -296,6 +296,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code,
encode_uint32(identifier_map.size(), &contents.write[0]);
encode_uint32(constant_map.size(), &contents.write[4]);
encode_uint32(token_lines.size(), &contents.write[8]);
encode_uint32(0, &contents.write[12]); // Unused, kept for compatibility. Please remove at next `TOKENIZER_VERSION` increment.
encode_uint32(token_counter, &contents.write[16]);
int buf_pos = 20;

View File

@ -229,19 +229,6 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) {
arr[i] = item.to_json();
i++;
}
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
arr = native_member_completions.duplicate();
for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) {
ExtendGDScriptParser *scr = E.value;
const Array &items = scr->get_member_completions();
const int start_size = arr.size();
arr.resize(start_size + items.size());
for (int i = start_size; i < arr.size(); i++) {
arr[i] = items[i - start_size];
}
}
}
return arr;
}

View File

@ -958,28 +958,30 @@ struct CompletionItem {
/**
* A string that should be used when comparing this item
* with other items. When `falsy` the label is used.
* with other items. When omitted the label is used
* as the filter text for this item.
*/
String sortText;
/**
* A string that should be used when filtering a set of
* completion items. When `falsy` the label is used.
* completion items. When omitted the label is used as the
* filter text for this item.
*/
String filterText;
/**
* A string that should be inserted into a document when selecting
* this completion. When `falsy` the label is used.
* this completion. When omitted the label is used as the insert text
* for this item.
*
* The `insertText` is subject to interpretation by the client side.
* Some tools might not take the string literally. For example
* VS Code when code complete is requested in this example `con<cursor position>`
* and a completion item with an `insertText` of `console` is provided it
* will only insert `sole`. Therefore it is recommended to use `textEdit` instead
* since it avoids additional client side interpretation.
*
* @deprecated Use textEdit instead.
* VS Code when code complete is requested in this example
* `con<cursor position>` and a completion item with an `insertText` of
* `console` is provided it will only insert `sole`. Therefore it is
* recommended to use `textEdit` instead since it avoids additional client
* side interpretation.
*/
String insertText;
@ -1034,14 +1036,20 @@ struct CompletionItem {
dict["label"] = label;
dict["kind"] = kind;
dict["data"] = data;
dict["insertText"] = insertText;
if (!insertText.is_empty()) {
dict["insertText"] = insertText;
}
if (resolved) {
dict["detail"] = detail;
dict["documentation"] = documentation.to_json();
dict["deprecated"] = deprecated;
dict["preselect"] = preselect;
dict["sortText"] = sortText;
dict["filterText"] = filterText;
if (!sortText.is_empty()) {
dict["sortText"] = sortText;
}
if (!filterText.is_empty()) {
dict["filterText"] = filterText;
}
if (commitCharacters.size()) {
dict["commitCharacters"] = commitCharacters;
}

View File

@ -1,12 +1,29 @@
signal s1()
signal s2()
signal s3()
# Doesn't produce the warning:
signal used_as_first_class_signal()
signal used_with_signal_constructor()
signal used_with_signal_emit()
signal used_with_object_emit_signal()
signal used_with_object_connect()
signal used_with_object_disconnect()
signal used_with_self_prefix()
# Produce the warning:
signal used_with_dynamic_name()
signal just_unused()
@warning_ignore("unused_signal")
signal s4()
signal unused_but_ignored()
func no_exec():
s1.emit()
print(s2)
print(used_as_first_class_signal)
print(Signal(self, "used_with_signal_constructor"))
used_with_signal_emit.emit()
print(emit_signal("used_with_object_emit_signal"))
print(connect("used_with_object_connect", Callable()))
disconnect("used_with_object_disconnect", Callable())
print(self.emit_signal("used_with_self_prefix"))
var dynamic_name := "used_with_dynamic_name"
print(emit_signal(dynamic_name))
func test():
pass

View File

@ -1,5 +1,9 @@
GDTEST_OK
>> WARNING
>> Line: 3
>> Line: 11
>> UNUSED_SIGNAL
>> The signal "s3" is declared but never explicitly used in the class.
>> The signal "used_with_dynamic_name" is declared but never explicitly used in the class.
>> WARNING
>> Line: 12
>> UNUSED_SIGNAL
>> The signal "just_unused" is declared but never explicitly used in the class.

View File

@ -0,0 +1,50 @@
# https://github.com/godotengine/godot/issues/75658
class MyObj:
var callable: Callable
func run():
callable.call()
var prop:
set(value):
callable.call()
get:
callable.call()
return 0
func _on_some_signal():
callable.call()
func _init(p_callable: Callable):
self.callable = p_callable
signal some_signal
var obj: MyObj
func test():
# Call.
obj = MyObj.new(nullify_obj)
obj.run()
print(obj)
# Get.
obj = MyObj.new(nullify_obj)
var _aux = obj.prop
print(obj)
# Set.
obj = MyObj.new(nullify_obj)
obj.prop = 1
print(obj)
# Signal handling.
obj = MyObj.new(nullify_obj)
@warning_ignore("return_value_discarded")
some_signal.connect(obj._on_some_signal)
some_signal.emit()
print(obj)
func nullify_obj():
obj = null

View File

@ -0,0 +1,5 @@
GDTEST_OK
<null>
<null>
<null>
<null>

View File

@ -115,8 +115,15 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
List<String> *r_missing_deps, Error *r_err) {
String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
if (blender_major_version == -1 || blender_minor_version == -1) {
_get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr);
ERR_FAIL_COND_V_MSG(blender_path.is_empty(), nullptr, "Blender path is empty, check your Editor Settings.");
ERR_FAIL_COND_V_MSG(!FileAccess::exists(blender_path), nullptr, vformat("Invalid Blender path: %s, check your Editor Settings.", blender_path));
if (blender_major_version == -1 || blender_minor_version == -1 || last_tested_blender_path != blender_path) {
String error;
if (!_get_blender_version(blender_path, blender_major_version, blender_minor_version, &error)) {
ERR_FAIL_V_MSG(nullptr, error);
}
last_tested_blender_path = blender_path;
}
// Get global paths for source and sink.

View File

@ -45,6 +45,7 @@ class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter {
int blender_major_version = -1;
int blender_minor_version = -1;
String last_tested_blender_path;
public:
enum {

View File

@ -5658,6 +5658,15 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
if (p_scene_root == nullptr) {
// If the root node argument is null, this is the root node.
p_scene_root = current_node;
// If multiple nodes were generated under the root node, ensure they have the owner set.
if (unlikely(current_node->get_child_count() > 0)) {
Array args;
args.append(p_scene_root);
for (int i = 0; i < current_node->get_child_count(); i++) {
Node *child = current_node->get_child(i);
child->propagate_call(StringName("set_owner"), args);
}
}
} else {
// Add the node we generated and set the owner to the scene root.
p_scene_parent->add_child(current_node, true);

View File

@ -643,6 +643,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
_do_paste();
input_action = INPUT_NONE;
_update_paste_indicator();
return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else if (mb->is_shift_pressed() && can_edit) {
input_action = INPUT_SELECT;
last_selection = selection;

View File

@ -777,7 +777,7 @@ void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_
if (stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED) {
int next_clip = stream->clips[p_to_clip_index].auto_advance_next_clip;
if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && next_clip != playback_current && (!transition.use_filler_clip || next_clip != transition.filler_clip)) {
if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && (!transition.use_filler_clip || next_clip != transition.filler_clip)) {
auto_advance_to = next_clip;
}
}
@ -905,7 +905,9 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_
// time to start!
from_frame = state.fade_wait * mix_rate;
state.fade_wait = 0;
queue_next = state.auto_advance;
if (state.fade_speed == 0.0) {
queue_next = state.auto_advance;
}
playback_current = p_state_idx;
state.first_mix = false;
} else {
@ -919,7 +921,6 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_
state.playback->mix(temp_buffer + from_frame, 1.0, p_frames - from_frame);
double frame_fade_inc = state.fade_speed * frame_inc;
for (int i = from_frame; i < p_frames; i++) {
if (state.fade_wait) {
// This is for fade out of existing stream;
@ -933,6 +934,7 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_
state.fade_speed = 0.0;
frame_fade_inc = 0.0;
state.fade_volume = 1.0;
queue_next = state.auto_advance;
}
} else if (frame_fade_inc < 0.0) {
state.fade_volume += frame_fade_inc;

Some files were not shown because too many files have changed in this diff Show More