diff options
333 files changed, 4679 insertions, 5529 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9dea66914f..7ffae10a2a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -45,7 +45,7 @@ body: label: Issue description description: | Describe your issue briefly. What doesn't work, and how do you expect it to work instead? - You can include images or videos with drag and drop, and format code blocks or logs with <code>```</code> tags, on separate lines before and after the text. (Use <code>```gdscript</code> to add GDScript syntax highlighting.) + You can include images or videos with drag and drop, and format code blocks or logs with <code>\`\`\`</code> tags, on separate lines before and after the text. (Use <code>\`\`\`gdscript</code> to add GDScript syntax highlighting.) Please do not add code examples or error messages as screenshots, but as text, this helps searching for issues and testing the code. If you are reporting a bug in the editor interface, like the script editor, please provide both a screenshot *and* the text of the code to help with testing. validations: required: true diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 914f0394e4..e3cd03d279 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -169,6 +169,11 @@ jobs: ${{ matrix.bin }} --help ${{ matrix.bin }} --headless --test --force-colors + - name: .NET source generators tests + if: ${{ matrix.build-mono }} + run: | + dotnet test modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests + # Check class reference - name: Check for class reference updates if: ${{ matrix.doc-test }} diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 9b3282ecba..60487c5a52 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -910,6 +910,39 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, return ERR_INVALID_DATA; } + // Optionally check maximum compatibility. + if (config->has_section_key("configuration", "compatibility_maximum")) { + uint32_t compatibility_maximum[3] = { 0, 0, 0 }; + String compat_string = config->get_value("configuration", "compatibility_maximum"); + Vector<int> parts = compat_string.split_ints("."); + for (int i = 0; i < 3; i++) { + if (i < parts.size() && parts[i] >= 0) { + compatibility_maximum[i] = parts[i]; + } else { + // If a version part is missing, set the maximum to an arbitrary high value. + compatibility_maximum[i] = 9999; + } + } + + compatible = true; + if (VERSION_MAJOR != compatibility_maximum[0]) { + compatible = VERSION_MAJOR < compatibility_maximum[0]; + } else if (VERSION_MINOR != compatibility_maximum[1]) { + compatible = VERSION_MINOR < compatibility_maximum[1]; + } +#if VERSION_PATCH + // #if check to avoid -Wtype-limits warning when 0. + else { + compatible = VERSION_PATCH <= compatibility_maximum[2]; + } +#endif + + if (!compatible) { + ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s", compat_string, p_path)); + return ERR_INVALID_DATA; + } + } + String library_path = GDExtension::find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); if (library_path.is_empty()) { diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp index 037851661b..6870c84b49 100644 --- a/core/os/midi_driver.cpp +++ b/core/os/midi_driver.cpp @@ -42,9 +42,10 @@ void MIDIDriver::set_singleton() { singleton = this; } -void MIDIDriver::receive_input_packet(uint64_t timestamp, uint8_t *data, uint32_t length) { +void MIDIDriver::receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length) { Ref<InputEventMIDI> event; event.instantiate(); + event->set_device(device_index); uint32_t param_position = 1; if (length >= 1) { diff --git a/core/os/midi_driver.h b/core/os/midi_driver.h index 6ad21c319e..cad3d8189e 100644 --- a/core/os/midi_driver.h +++ b/core/os/midi_driver.h @@ -51,7 +51,7 @@ public: virtual PackedStringArray get_connected_inputs(); - static void receive_input_packet(uint64_t timestamp, uint8_t *data, uint32_t length); + static void receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length); MIDIDriver(); virtual ~MIDIDriver() {} diff --git a/core/templates/search_array.h b/core/templates/search_array.h index f537ef67fd..9c3f527323 100644 --- a/core/templates/search_array.h +++ b/core/templates/search_array.h @@ -38,12 +38,12 @@ class SearchArray { public: Comparator compare; - inline int bisect(const T *p_array, int p_len, const T &p_value, bool p_before) const { - int lo = 0; - int hi = p_len; + inline int64_t bisect(const T *p_array, int64_t p_len, const T &p_value, bool p_before) const { + int64_t lo = 0; + int64_t hi = p_len; if (p_before) { while (lo < hi) { - const int mid = (lo + hi) / 2; + const int64_t mid = (lo + hi) / 2; if (compare(p_array[mid], p_value)) { lo = mid + 1; } else { @@ -52,7 +52,7 @@ public: } } else { while (lo < hi) { - const int mid = (lo + hi) / 2; + const int64_t mid = (lo + hi) / 2; if (compare(p_value, p_array[mid])) { hi = mid; } else { diff --git a/core/templates/sort_array.h b/core/templates/sort_array.h index fbe2a89a84..45aeaf1579 100644 --- a/core/templates/sort_array.h +++ b/core/templates/sort_array.h @@ -78,8 +78,8 @@ public: } } - inline int bitlog(int n) const { - int k; + inline int64_t bitlog(int64_t n) const { + int64_t k; for (k = 0; n != 1; n >>= 1) { ++k; } @@ -88,8 +88,8 @@ public: /* Heap / Heapsort functions */ - inline void push_heap(int p_first, int p_hole_idx, int p_top_index, T p_value, T *p_array) const { - int parent = (p_hole_idx - 1) / 2; + inline void push_heap(int64_t p_first, int64_t p_hole_idx, int64_t p_top_index, T p_value, T *p_array) const { + int64_t parent = (p_hole_idx - 1) / 2; while (p_hole_idx > p_top_index && compare(p_array[p_first + parent], p_value)) { p_array[p_first + p_hole_idx] = p_array[p_first + parent]; p_hole_idx = parent; @@ -98,17 +98,17 @@ public: p_array[p_first + p_hole_idx] = p_value; } - inline void pop_heap(int p_first, int p_last, int p_result, T p_value, T *p_array) const { + inline void pop_heap(int64_t p_first, int64_t p_last, int64_t p_result, T p_value, T *p_array) const { p_array[p_result] = p_array[p_first]; adjust_heap(p_first, 0, p_last - p_first, p_value, p_array); } - inline void pop_heap(int p_first, int p_last, T *p_array) const { + inline void pop_heap(int64_t p_first, int64_t p_last, T *p_array) const { pop_heap(p_first, p_last - 1, p_last - 1, p_array[p_last - 1], p_array); } - inline void adjust_heap(int p_first, int p_hole_idx, int p_len, T p_value, T *p_array) const { - int top_index = p_hole_idx; - int second_child = 2 * p_hole_idx + 2; + inline void adjust_heap(int64_t p_first, int64_t p_hole_idx, int64_t p_len, T p_value, T *p_array) const { + int64_t top_index = p_hole_idx; + int64_t second_child = 2 * p_hole_idx + 2; while (second_child < p_len) { if (compare(p_array[p_first + second_child], p_array[p_first + (second_child - 1)])) { @@ -127,18 +127,18 @@ public: push_heap(p_first, p_hole_idx, top_index, p_value, p_array); } - inline void sort_heap(int p_first, int p_last, T *p_array) const { + inline void sort_heap(int64_t p_first, int64_t p_last, T *p_array) const { while (p_last - p_first > 1) { pop_heap(p_first, p_last--, p_array); } } - inline void make_heap(int p_first, int p_last, T *p_array) const { + inline void make_heap(int64_t p_first, int64_t p_last, T *p_array) const { if (p_last - p_first < 2) { return; } - int len = p_last - p_first; - int parent = (len - 2) / 2; + int64_t len = p_last - p_first; + int64_t parent = (len - 2) / 2; while (true) { adjust_heap(p_first, parent, len, p_array[p_first + parent], p_array); @@ -149,9 +149,9 @@ public: } } - inline void partial_sort(int p_first, int p_last, int p_middle, T *p_array) const { + inline void partial_sort(int64_t p_first, int64_t p_last, int64_t p_middle, T *p_array) const { make_heap(p_first, p_middle, p_array); - for (int i = p_middle; i < p_last; i++) { + for (int64_t i = p_middle; i < p_last; i++) { if (compare(p_array[i], p_array[p_first])) { pop_heap(p_first, p_middle, i, p_array[i], p_array); } @@ -159,18 +159,18 @@ public: sort_heap(p_first, p_middle, p_array); } - inline void partial_select(int p_first, int p_last, int p_middle, T *p_array) const { + inline void partial_select(int64_t p_first, int64_t p_last, int64_t p_middle, T *p_array) const { make_heap(p_first, p_middle, p_array); - for (int i = p_middle; i < p_last; i++) { + for (int64_t i = p_middle; i < p_last; i++) { if (compare(p_array[i], p_array[p_first])) { pop_heap(p_first, p_middle, i, p_array[i], p_array); } } } - inline int partitioner(int p_first, int p_last, T p_pivot, T *p_array) const { - const int unmodified_first = p_first; - const int unmodified_last = p_last; + inline int64_t partitioner(int64_t p_first, int64_t p_last, T p_pivot, T *p_array) const { + const int64_t unmodified_first = p_first; + const int64_t unmodified_last = p_last; while (true) { while (compare(p_array[p_first], p_pivot)) { @@ -196,7 +196,7 @@ public: } } - inline void introsort(int p_first, int p_last, T *p_array, int p_max_depth) const { + inline void introsort(int64_t p_first, int64_t p_last, T *p_array, int64_t p_max_depth) const { while (p_last - p_first > INTROSORT_THRESHOLD) { if (p_max_depth == 0) { partial_sort(p_first, p_last, p_last, p_array); @@ -205,7 +205,7 @@ public: p_max_depth--; - int cut = partitioner( + int64_t cut = partitioner( p_first, p_last, median_of_3( @@ -219,7 +219,7 @@ public: } } - inline void introselect(int p_first, int p_nth, int p_last, T *p_array, int p_max_depth) const { + inline void introselect(int64_t p_first, int64_t p_nth, int64_t p_last, T *p_array, int64_t p_max_depth) const { while (p_last - p_first > 3) { if (p_max_depth == 0) { partial_select(p_first, p_nth + 1, p_last, p_array); @@ -229,7 +229,7 @@ public: p_max_depth--; - int cut = partitioner( + int64_t cut = partitioner( p_first, p_last, median_of_3( @@ -248,8 +248,8 @@ public: insertion_sort(p_first, p_last, p_array); } - inline void unguarded_linear_insert(int p_last, T p_value, T *p_array) const { - int next = p_last - 1; + inline void unguarded_linear_insert(int64_t p_last, T p_value, T *p_array) const { + int64_t next = p_last - 1; while (compare(p_value, p_array[next])) { if (Validate) { ERR_BAD_COMPARE(next == 0); @@ -261,10 +261,10 @@ public: p_array[p_last] = p_value; } - inline void linear_insert(int p_first, int p_last, T *p_array) const { + inline void linear_insert(int64_t p_first, int64_t p_last, T *p_array) const { T val = p_array[p_last]; if (compare(val, p_array[p_first])) { - for (int i = p_last; i > p_first; i--) { + for (int64_t i = p_last; i > p_first; i--) { p_array[i] = p_array[i - 1]; } @@ -274,22 +274,22 @@ public: } } - inline void insertion_sort(int p_first, int p_last, T *p_array) const { + inline void insertion_sort(int64_t p_first, int64_t p_last, T *p_array) const { if (p_first == p_last) { return; } - for (int i = p_first + 1; i != p_last; i++) { + for (int64_t i = p_first + 1; i != p_last; i++) { linear_insert(p_first, i, p_array); } } - inline void unguarded_insertion_sort(int p_first, int p_last, T *p_array) const { - for (int i = p_first; i != p_last; i++) { + inline void unguarded_insertion_sort(int64_t p_first, int64_t p_last, T *p_array) const { + for (int64_t i = p_first; i != p_last; i++) { unguarded_linear_insert(i, p_array[i], p_array); } } - inline void final_insertion_sort(int p_first, int p_last, T *p_array) const { + inline void final_insertion_sort(int64_t p_first, int64_t p_last, T *p_array) const { if (p_last - p_first > INTROSORT_THRESHOLD) { insertion_sort(p_first, p_first + INTROSORT_THRESHOLD, p_array); unguarded_insertion_sort(p_first + INTROSORT_THRESHOLD, p_last, p_array); @@ -298,18 +298,18 @@ public: } } - inline void sort_range(int p_first, int p_last, T *p_array) const { + inline void sort_range(int64_t p_first, int64_t p_last, T *p_array) const { if (p_first != p_last) { introsort(p_first, p_last, p_array, bitlog(p_last - p_first) * 2); final_insertion_sort(p_first, p_last, p_array); } } - inline void sort(T *p_array, int p_len) const { + inline void sort(T *p_array, int64_t p_len) const { sort_range(0, p_len, p_array); } - inline void nth_element(int p_first, int p_last, int p_nth, T *p_array) const { + inline void nth_element(int64_t p_first, int64_t p_last, int64_t p_nth, T *p_array) const { if (p_first == p_last || p_nth == p_last) { return; } diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index ebdca643ce..b00889f483 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -359,8 +359,10 @@ <param index="0" name="track_idx" type="int" /> <param index="1" name="time" type="float" /> <param index="2" name="find_mode" type="int" enum="Animation.FindMode" default="0" /> + <param index="3" name="limit" type="bool" default="false" /> <description> Finds the key index by time in a given track. Optionally, only find it if the approx/exact time is given. + If [param limit] is [code]true[/code], it does not return keys outside the animation range. </description> </method> <method name="track_get_interpolation_loop_wrap" qualifiers="const"> diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml index e7ec7af8e7..26d28f9c50 100644 --- a/doc/classes/AnimationMixer.xml +++ b/doc/classes/AnimationMixer.xml @@ -273,7 +273,7 @@ The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers. For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each. </member> - <member name="callback_mode_discrete" type="int" setter="set_callback_mode_discrete" getter="get_callback_mode_discrete" enum="AnimationMixer.AnimationCallbackModeDiscrete" default="1"> + <member name="callback_mode_discrete" type="int" setter="set_callback_mode_discrete" getter="get_callback_mode_discrete" enum="AnimationMixer.AnimationCallbackModeDiscrete" default="0"> Ordinarily, tracks can be set to [constant Animation.UPDATE_DISCRETE] to update infrequently, usually when using nearest interpolation. However, when blending with [constant Animation.UPDATE_CONTINUOUS] several results are considered. The [member callback_mode_discrete] specify it explicitly. See also [enum AnimationCallbackModeDiscrete]. To make the blended results look good, it is recommended to set this to [constant ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS] to update every frame during blending. Other values exist for compatibility and they are fine if there is no blending, but not so, may produce artifacts. @@ -356,10 +356,10 @@ Make method calls immediately when reached in the animation. </constant> <constant name="ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT" value="0" enum="AnimationCallbackModeDiscrete"> - An [constant Animation.UPDATE_DISCRETE] track value takes precedence when blending [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and [constant Animation.UPDATE_DISCRETE] track values. + An [constant Animation.UPDATE_DISCRETE] track value takes precedence when blending [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and [constant Animation.UPDATE_DISCRETE] track values. This is the default behavior for [AnimationPlayer]. </constant> <constant name="ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE" value="1" enum="AnimationCallbackModeDiscrete"> - An [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track value takes precedence when blending the [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and the [constant Animation.UPDATE_DISCRETE] track values. This is the default behavior for [AnimationPlayer]. + An [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track value takes precedence when blending the [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and the [constant Animation.UPDATE_DISCRETE] track values. </constant> <constant name="ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS" value="2" enum="AnimationCallbackModeDiscrete"> Always treat the [constant Animation.UPDATE_DISCRETE] track value as [constant Animation.UPDATE_CONTINUOUS] with [constant Animation.INTERPOLATION_NEAREST]. This is the default behavior for [AnimationTree]. diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index 535515165b..4fd288f16d 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -72,9 +72,6 @@ <member name="checked" type="bool" setter="set_checked" getter="is_checked" default="false"> Used by the inspector, set to [code]true[/code] when the property is checked. </member> - <member name="configuration_warning" type="String" setter="set_configuration_warning" getter="get_configuration_warning" default=""""> - Used by the inspector, set to show a configuration warning on the property. - </member> <member name="deletable" type="bool" setter="set_deletable" getter="is_deletable" default="false"> Used by the inspector, set to [code]true[/code] when the property can be deleted by the user. </member> diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml index acd959d7f7..6913d1f75c 100644 --- a/doc/classes/Environment.xml +++ b/doc/classes/Environment.xml @@ -87,7 +87,18 @@ This is useful to simulate [url=https://en.wikipedia.org/wiki/Aerial_perspective]aerial perspective[/url] in large scenes with low density fog. However, it is not very useful for high-density fog, as the sky will shine through. When set to [code]1.0[/code], the fog color comes completely from the [Sky]. If set to [code]0.0[/code], aerial perspective is disabled. </member> <member name="fog_density" type="float" setter="set_fog_density" getter="get_fog_density" default="0.01"> - The [i]exponential[/i] fog density to use. Higher values result in a more dense fog. Fog rendering is exponential as in real life. + The fog density to be used. This is demonstrated in different ways depending on the [member fog_mode] mode chosen: + [b]Exponential Fog Mode:[/b] Higher values result in denser fog. The fog rendering is exponential like in real life. + [b]Depth Fog mode:[/b] The maximum intensity of the deep fog, effect will appear in the distance (relative to the camera). At [code]1.0[/code] the fog will fully obscure the scene, at [code]0.0[/code] the fog will not be visible. + </member> + <member name="fog_depth_begin" type="float" setter="set_fog_depth_begin" getter="get_fog_depth_begin" default="10.0"> + The fog's depth starting distance from the camera. Only available when [member fog_mode] is set to [constant FOG_MODE_DEPTH]. + </member> + <member name="fog_depth_curve" type="float" setter="set_fog_depth_curve" getter="get_fog_depth_curve" default="1.0"> + The fog depth's intensity curve. A number of presets are available in the Inspector by right-clicking the curve. Only available when [member fog_mode] is set to [constant FOG_MODE_DEPTH]. + </member> + <member name="fog_depth_end" type="float" setter="set_fog_depth_end" getter="get_fog_depth_end" default="100.0"> + The fog's depth end distance from the camera. If this value is set to [code]0[/code], it will be equal to the current camera's [member Camera3D.far] value. Only available when [member fog_mode] is set to [constant FOG_MODE_DEPTH]. </member> <member name="fog_enabled" type="bool" setter="set_fog_enabled" getter="is_fog_enabled" default="false"> If [code]true[/code], fog effects are enabled. @@ -104,6 +115,9 @@ <member name="fog_light_energy" type="float" setter="set_fog_light_energy" getter="get_fog_light_energy" default="1.0"> The fog's brightness. Higher values result in brighter fog. </member> + <member name="fog_mode" type="int" setter="set_fog_mode" getter="get_fog_mode" enum="Environment.FogMode" default="0"> + The fog mode. See [enum FogMode] for possible values. + </member> <member name="fog_sky_affect" type="float" setter="set_fog_sky_affect" getter="get_fog_sky_affect" default="1.0"> The factor to use when affecting the sky with non-volumetric fog. [code]1.0[/code] means that fog can fully obscure the sky. Lower values reduce the impact of fog on sky rendering, with [code]0.0[/code] not affecting sky rendering at all. [b]Note:[/b] [member fog_sky_affect] has no visual effect if [member fog_aerial_perspective] is [code]1.0[/code]. @@ -412,6 +426,12 @@ <constant name="GLOW_BLEND_MODE_MIX" value="4" enum="GlowBlendMode"> Mixes the glow with the underlying color to avoid increasing brightness as much while still maintaining a glow effect. </constant> + <constant name="FOG_MODE_EXPONENTIAL" value="0" enum="FogMode"> + Use a physically-based fog model defined primarily by fog density. + </constant> + <constant name="FOG_MODE_DEPTH" value="1" enum="FogMode"> + Use a simple fog model defined by start and end positions and a custom curve. While not physically accurate, this model can be useful when you need more artistic control. + </constant> <constant name="SDFGI_Y_SCALE_50_PERCENT" value="0" enum="SDFGIYScale"> Use 50% scale for SDFGI on the Y (vertical) axis. SDFGI cells will be twice as short as they are wide. This allows providing increased GI detail and reduced light leaking with thin floors and ceilings. This is usually the best choice for scenes that don't feature much verticality. </constant> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 92bbacd62a..29e144e4b9 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -37,13 +37,9 @@ </description> </method> <method name="_get_configuration_warnings" qualifiers="virtual const"> - <return type="Array" /> + <return type="PackedStringArray" /> <description> The elements in the array returned from this method are displayed as warnings in the Scene dock if the script that overrides it is a [code]tool[/code] script. - Each array element must either be a [String] or a [Dictionary]. - A dictionary element must contain a key [code]message[/code] of type [String] which is shown in the user interface. - The dictionary may optionally contain a key [code]property[/code] of type [NodePath], which also shows this warning in the inspector on the corresponding property. - If a string is found in the returned array, it is converted to an equivalent dictionary with the [code]message[/code] field set. Returning an empty array produces no warnings. Call [method update_configuration_warnings] when the warnings need to be updated for this node. [codeblock] diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml index 3e2d939e10..d0c3e64b7d 100644 --- a/doc/classes/ParticleProcessMaterial.xml +++ b/doc/classes/ParticleProcessMaterial.xml @@ -310,7 +310,7 @@ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead. </member> <member name="scale_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> - Each particle's scale will vary along this [CurveTexture]. If a [CurveXYZTexture] is supplied instead, the scale will be separated per-axis. + Each particle's scale will vary along this [CurveTexture] over its lifetime. If a [CurveXYZTexture] is supplied instead, the scale will be separated per-axis. </member> <member name="scale_max" type="float" setter="set_param_max" getter="get_param_max" default="1.0"> Maximum initial scale applied to each particle. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 1658ad86b7..12b88d021a 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1145,6 +1145,7 @@ <param index="7" name="height_density" type="float" /> <param index="8" name="aerial_perspective" type="float" /> <param index="9" name="sky_affect" type="float" /> + <param index="10" name="fog_mode" type="int" enum="RenderingServer.EnvironmentFogMode" default="0" /> <description> Configures fog for the specified environment RID. See [code]fog_*[/code] properties in [Environment] for more information. </description> @@ -4888,6 +4889,12 @@ <constant name="ENV_GLOW_BLEND_MODE_MIX" value="4" enum="EnvironmentGlowBlendMode"> Mixes the glow with the underlying color to avoid increasing brightness as much while still maintaining a glow effect. </constant> + <constant name="ENV_FOG_MODE_EXPONENTIAL" value="0" enum="EnvironmentFogMode"> + Use a physically-based fog model defined primarily by fog density. + </constant> + <constant name="ENV_FOG_MODE_DEPTH" value="1" enum="EnvironmentFogMode"> + Use a simple fog model defined by start and end positions and a custom curve. While not physically accurate, this model can be useful when you need more artistic control. + </constant> <constant name="ENV_TONE_MAPPER_LINEAR" value="0" enum="EnvironmentToneMapper"> Output color as they came in. This can cause bright lighting to look blown out, with noticeable clipping in the output colors. </constant> diff --git a/doc/classes/Texture3D.xml b/doc/classes/Texture3D.xml index 6f054a8e0d..2b1bc026c2 100644 --- a/doc/classes/Texture3D.xml +++ b/doc/classes/Texture3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Texture3D" inherits="Texture" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Base class for 3-dimensionnal textures. + Base class for 3-dimensional textures. </brief_description> <description> Base class for [ImageTexture3D] and [CompressedTexture3D]. Cannot be used directly, but contains all the functions necessary for accessing the derived resource types. [Texture3D] is the base class for all 3-dimensional texture types. See also [TextureLayered]. diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index b265f8ba11..ac16bebecb 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -237,6 +237,12 @@ <param index="0" name="parallel" type="bool" default="true" /> <description> If [param parallel] is [code]true[/code], the [Tweener]s appended after this method will by default run simultaneously, as opposed to sequentially. + [b]Note:[/b] Just like with [method parallel], the tweener added right before this method will also be part of the parallel step. + [codeblock] + tween.tween_property(self, "position", Vector2(300, 0), 0.5) + tween.set_parallel() + tween.tween_property(self, "modulate", Color.GREEN, 0.5) # Runs together with the position tweener. + [/codeblock] </description> </method> <method name="set_pause_mode"> diff --git a/doc/classes/VisualShaderNodeCubemap.xml b/doc/classes/VisualShaderNodeCubemap.xml index 8fc98e24cc..4e6cf2120a 100644 --- a/doc/classes/VisualShaderNodeCubemap.xml +++ b/doc/classes/VisualShaderNodeCubemap.xml @@ -33,7 +33,7 @@ No hints are added to the uniform declaration. </constant> <constant name="TYPE_COLOR" value="1" enum="TextureType"> - Adds [code]hint_albedo[/code] as hint to the uniform declaration for proper sRGB to linear conversion. + Adds [code]source_color[/code] as hint to the uniform declaration for proper sRGB to linear conversion. </constant> <constant name="TYPE_NORMAL_MAP" value="2" enum="TextureType"> Adds [code]hint_normal[/code] as hint to the uniform declaration, which internally converts the texture for proper usage as normal map. diff --git a/doc/classes/VisualShaderNodeTexture.xml b/doc/classes/VisualShaderNodeTexture.xml index 5b38683eba..eda09573ad 100644 --- a/doc/classes/VisualShaderNodeTexture.xml +++ b/doc/classes/VisualShaderNodeTexture.xml @@ -51,7 +51,7 @@ No hints are added to the uniform declaration. </constant> <constant name="TYPE_COLOR" value="1" enum="TextureType"> - Adds [code]hint_albedo[/code] as hint to the uniform declaration for proper sRGB to linear conversion. + Adds [code]source_color[/code] as hint to the uniform declaration for proper sRGB to linear conversion. </constant> <constant name="TYPE_NORMAL_MAP" value="2" enum="TextureType"> Adds [code]hint_normal[/code] as hint to the uniform declaration, which internally converts the texture for proper usage as normal map. diff --git a/doc/classes/XRFaceModifier3D.xml b/doc/classes/XRFaceModifier3D.xml new file mode 100644 index 0000000000..7a60e6db34 --- /dev/null +++ b/doc/classes/XRFaceModifier3D.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="XRFaceModifier3D" inherits="Node3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A node for driving standard face meshes from [XRFaceTracker] weights. + </brief_description> + <description> + This node applies weights from a [XRFaceTracker] to a mesh with supporting face blend shapes. + The [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/unified-blendshapes]Unified Expressions[/url] blend shapes are supported, as well as ARKit and SRanipal blend shapes. + The node attempts to identify blend shapes based on name matching. Blend shapes should match the names listed in the [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/compatibility/overview]Unified Expressions Compatibility[/url] chart. + </description> + <tutorials> + <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> + </tutorials> + <members> + <member name="face_tracker" type="StringName" setter="set_face_tracker" getter="get_face_tracker" default="&"/user/head""> + The [XRFaceTracker] path. + </member> + <member name="target" type="NodePath" setter="set_target" getter="get_target" default="NodePath("")"> + The [NodePath] of the face [MeshInstance3D]. + </member> + </members> +</class> diff --git a/doc/classes/XRFaceTracker.xml b/doc/classes/XRFaceTracker.xml new file mode 100644 index 0000000000..0212cc8fff --- /dev/null +++ b/doc/classes/XRFaceTracker.xml @@ -0,0 +1,469 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="XRFaceTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A tracked face. + </brief_description> + <description> + An instance of this object represents a tracked face and its corresponding blend shapes. The blend shapes come from the [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/unified-blendshapes]Unified Expressions[/url] standard, and contain extended details and visuals for each blend shape. Additionally the [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/compatibility/overview]Tracking Standard Comparison[/url] page documents the relationship between Unified Expressions and other standards. + As face trackers are turned on they are registered with the [XRServer]. + </description> + <tutorials> + <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> + </tutorials> + <methods> + <method name="get_blend_shape" qualifiers="const"> + <return type="float" /> + <param index="0" name="blend_shape" type="int" enum="XRFaceTracker.BlendShapeEntry" /> + <description> + Returns the requested face blend shape weight. + </description> + </method> + <method name="set_blend_shape"> + <return type="void" /> + <param index="0" name="blend_shape" type="int" enum="XRFaceTracker.BlendShapeEntry" /> + <param index="1" name="weight" type="float" /> + <description> + Sets a face blend shape weight. + </description> + </method> + </methods> + <members> + <member name="blend_shapes" type="PackedFloat32Array" setter="set_blend_shapes" getter="get_blend_shapes" default="PackedFloat32Array()"> + The array of face blend shape weights with indices corresponding to the [enum BlendShapeEntry] enum. + </member> + </members> + <constants> + <constant name="FT_EYE_LOOK_OUT_RIGHT" value="0" enum="BlendShapeEntry"> + Right eye looks outwards. + </constant> + <constant name="FT_EYE_LOOK_IN_RIGHT" value="1" enum="BlendShapeEntry"> + Right eye looks inwards. + </constant> + <constant name="FT_EYE_LOOK_UP_RIGHT" value="2" enum="BlendShapeEntry"> + Right eye looks upwards. + </constant> + <constant name="FT_EYE_LOOK_DOWN_RIGHT" value="3" enum="BlendShapeEntry"> + Right eye looks downwards. + </constant> + <constant name="FT_EYE_LOOK_OUT_LEFT" value="4" enum="BlendShapeEntry"> + Left eye looks outwards. + </constant> + <constant name="FT_EYE_LOOK_IN_LEFT" value="5" enum="BlendShapeEntry"> + Left eye looks inwards. + </constant> + <constant name="FT_EYE_LOOK_UP_LEFT" value="6" enum="BlendShapeEntry"> + Left eye looks upwards. + </constant> + <constant name="FT_EYE_LOOK_DOWN_LEFT" value="7" enum="BlendShapeEntry"> + Left eye looks downwards. + </constant> + <constant name="FT_EYE_CLOSED_RIGHT" value="8" enum="BlendShapeEntry"> + Closes the right eyelid. + </constant> + <constant name="FT_EYE_CLOSED_LEFT" value="9" enum="BlendShapeEntry"> + Closes the left eyelid. + </constant> + <constant name="FT_EYE_SQUINT_RIGHT" value="10" enum="BlendShapeEntry"> + Squeezes the right eye socket muscles. + </constant> + <constant name="FT_EYE_SQUINT_LEFT" value="11" enum="BlendShapeEntry"> + Squeezes the left eye socket muscles. + </constant> + <constant name="FT_EYE_WIDE_RIGHT" value="12" enum="BlendShapeEntry"> + Right eyelid widens beyond relaxed. + </constant> + <constant name="FT_EYE_WIDE_LEFT" value="13" enum="BlendShapeEntry"> + Left eyelid widens beyond relaxed. + </constant> + <constant name="FT_EYE_DILATION_RIGHT" value="14" enum="BlendShapeEntry"> + Dilates the right eye pupil. + </constant> + <constant name="FT_EYE_DILATION_LEFT" value="15" enum="BlendShapeEntry"> + Dilates the left eye pupil. + </constant> + <constant name="FT_EYE_CONSTRICT_RIGHT" value="16" enum="BlendShapeEntry"> + Constricts the right eye pupil. + </constant> + <constant name="FT_EYE_CONSTRICT_LEFT" value="17" enum="BlendShapeEntry"> + Constricts the left eye pupil. + </constant> + <constant name="FT_BROW_PINCH_RIGHT" value="18" enum="BlendShapeEntry"> + Right eyebrow pinches in. + </constant> + <constant name="FT_BROW_PINCH_LEFT" value="19" enum="BlendShapeEntry"> + Left eyebrow pinches in. + </constant> + <constant name="FT_BROW_LOWERER_RIGHT" value="20" enum="BlendShapeEntry"> + Outer right eyebrow pulls down. + </constant> + <constant name="FT_BROW_LOWERER_LEFT" value="21" enum="BlendShapeEntry"> + Outer left eyebrow pulls down. + </constant> + <constant name="FT_BROW_INNER_UP_RIGHT" value="22" enum="BlendShapeEntry"> + Inner right eyebrow pulls up. + </constant> + <constant name="FT_BROW_INNER_UP_LEFT" value="23" enum="BlendShapeEntry"> + Inner left eyebrow pulls up. + </constant> + <constant name="FT_BROW_OUTER_UP_RIGHT" value="24" enum="BlendShapeEntry"> + Outer right eyebrow pulls up. + </constant> + <constant name="FT_BROW_OUTER_UP_LEFT" value="25" enum="BlendShapeEntry"> + Outer left eyebrow pulls up. + </constant> + <constant name="FT_NOSE_SNEER_RIGHT" value="26" enum="BlendShapeEntry"> + Right side face sneers. + </constant> + <constant name="FT_NOSE_SNEER_LEFT" value="27" enum="BlendShapeEntry"> + Left side face sneers. + </constant> + <constant name="FT_NASAL_DILATION_RIGHT" value="28" enum="BlendShapeEntry"> + Right side nose canal dilates. + </constant> + <constant name="FT_NASAL_DILATION_LEFT" value="29" enum="BlendShapeEntry"> + Left side nose canal dilates. + </constant> + <constant name="FT_NASAL_CONSTRICT_RIGHT" value="30" enum="BlendShapeEntry"> + Right side nose canal constricts. + </constant> + <constant name="FT_NASAL_CONSTRICT_LEFT" value="31" enum="BlendShapeEntry"> + Left side nose canal constricts. + </constant> + <constant name="FT_CHEEK_SQUINT_RIGHT" value="32" enum="BlendShapeEntry"> + Raises the right side cheek. + </constant> + <constant name="FT_CHEEK_SQUINT_LEFT" value="33" enum="BlendShapeEntry"> + Raises the left side cheek. + </constant> + <constant name="FT_CHEEK_PUFF_RIGHT" value="34" enum="BlendShapeEntry"> + Puffs the right side cheek. + </constant> + <constant name="FT_CHEEK_PUFF_LEFT" value="35" enum="BlendShapeEntry"> + Puffs the left side cheek. + </constant> + <constant name="FT_CHEEK_SUCK_RIGHT" value="36" enum="BlendShapeEntry"> + Sucks in the right side cheek. + </constant> + <constant name="FT_CHEEK_SUCK_LEFT" value="37" enum="BlendShapeEntry"> + Sucks in the left side cheek. + </constant> + <constant name="FT_JAW_OPEN" value="38" enum="BlendShapeEntry"> + Opens jawbone. + </constant> + <constant name="FT_MOUTH_CLOSED" value="39" enum="BlendShapeEntry"> + Closes the mouth. + </constant> + <constant name="FT_JAW_RIGHT" value="40" enum="BlendShapeEntry"> + Pushes jawbone right. + </constant> + <constant name="FT_JAW_LEFT" value="41" enum="BlendShapeEntry"> + Pushes jawbone left. + </constant> + <constant name="FT_JAW_FORWARD" value="42" enum="BlendShapeEntry"> + Pushes jawbone forward. + </constant> + <constant name="FT_JAW_BACKWARD" value="43" enum="BlendShapeEntry"> + Pushes jawbone backward. + </constant> + <constant name="FT_JAW_CLENCH" value="44" enum="BlendShapeEntry"> + Flexes jaw muscles. + </constant> + <constant name="FT_JAW_MANDIBLE_RAISE" value="45" enum="BlendShapeEntry"> + Raises the jawbone. + </constant> + <constant name="FT_LIP_SUCK_UPPER_RIGHT" value="46" enum="BlendShapeEntry"> + Upper right lip part tucks in the mouth. + </constant> + <constant name="FT_LIP_SUCK_UPPER_LEFT" value="47" enum="BlendShapeEntry"> + Upper left lip part tucks in the mouth. + </constant> + <constant name="FT_LIP_SUCK_LOWER_RIGHT" value="48" enum="BlendShapeEntry"> + Lower right lip part tucks in the mouth. + </constant> + <constant name="FT_LIP_SUCK_LOWER_LEFT" value="49" enum="BlendShapeEntry"> + Lower left lip part tucks in the mouth. + </constant> + <constant name="FT_LIP_SUCK_CORNER_RIGHT" value="50" enum="BlendShapeEntry"> + Right lip corner folds into the mouth. + </constant> + <constant name="FT_LIP_SUCK_CORNER_LEFT" value="51" enum="BlendShapeEntry"> + Left lip corner folds into the mouth. + </constant> + <constant name="FT_LIP_FUNNEL_UPPER_RIGHT" value="52" enum="BlendShapeEntry"> + Upper right lip part pushes into a funnel. + </constant> + <constant name="FT_LIP_FUNNEL_UPPER_LEFT" value="53" enum="BlendShapeEntry"> + Upper left lip part pushes into a funnel. + </constant> + <constant name="FT_LIP_FUNNEL_LOWER_RIGHT" value="54" enum="BlendShapeEntry"> + Lower right lip part pushes into a funnel. + </constant> + <constant name="FT_LIP_FUNNEL_LOWER_LEFT" value="55" enum="BlendShapeEntry"> + Lower left lip part pushes into a funnel. + </constant> + <constant name="FT_LIP_PUCKER_UPPER_RIGHT" value="56" enum="BlendShapeEntry"> + Upper right lip part pushes outwards. + </constant> + <constant name="FT_LIP_PUCKER_UPPER_LEFT" value="57" enum="BlendShapeEntry"> + Upper left lip part pushes outwards. + </constant> + <constant name="FT_LIP_PUCKER_LOWER_RIGHT" value="58" enum="BlendShapeEntry"> + Lower right lip part pushes outwards. + </constant> + <constant name="FT_LIP_PUCKER_LOWER_LEFT" value="59" enum="BlendShapeEntry"> + Lower left lip part pushes outwards. + </constant> + <constant name="FT_MOUTH_UPPER_UP_RIGHT" value="60" enum="BlendShapeEntry"> + Upper right part of the lip pulls up. + </constant> + <constant name="FT_MOUTH_UPPER_UP_LEFT" value="61" enum="BlendShapeEntry"> + Upper left part of the lip pulls up. + </constant> + <constant name="FT_MOUTH_LOWER_DOWN_RIGHT" value="62" enum="BlendShapeEntry"> + Lower right part of the lip pulls up. + </constant> + <constant name="FT_MOUTH_LOWER_DOWN_LEFT" value="63" enum="BlendShapeEntry"> + Lower left part of the lip pulls up. + </constant> + <constant name="FT_MOUTH_UPPER_DEEPEN_RIGHT" value="64" enum="BlendShapeEntry"> + Upper right lip part pushes in the cheek. + </constant> + <constant name="FT_MOUTH_UPPER_DEEPEN_LEFT" value="65" enum="BlendShapeEntry"> + Upper left lip part pushes in the cheek. + </constant> + <constant name="FT_MOUTH_UPPER_RIGHT" value="66" enum="BlendShapeEntry"> + Moves upper lip right. + </constant> + <constant name="FT_MOUTH_UPPER_LEFT" value="67" enum="BlendShapeEntry"> + Moves upper lip left. + </constant> + <constant name="FT_MOUTH_LOWER_RIGHT" value="68" enum="BlendShapeEntry"> + Moves lower lip right. + </constant> + <constant name="FT_MOUTH_LOWER_LEFT" value="69" enum="BlendShapeEntry"> + Moves lower lip left. + </constant> + <constant name="FT_MOUTH_CORNER_PULL_RIGHT" value="70" enum="BlendShapeEntry"> + Right lip corner pulls diagonally up and out. + </constant> + <constant name="FT_MOUTH_CORNER_PULL_LEFT" value="71" enum="BlendShapeEntry"> + Left lip corner pulls diagonally up and out. + </constant> + <constant name="FT_MOUTH_CORNER_SLANT_RIGHT" value="72" enum="BlendShapeEntry"> + Right corner lip slants up. + </constant> + <constant name="FT_MOUTH_CORNER_SLANT_LEFT" value="73" enum="BlendShapeEntry"> + Left corner lip slants up. + </constant> + <constant name="FT_MOUTH_FROWN_RIGHT" value="74" enum="BlendShapeEntry"> + Right corner lip pulls down. + </constant> + <constant name="FT_MOUTH_FROWN_LEFT" value="75" enum="BlendShapeEntry"> + Left corner lip pulls down. + </constant> + <constant name="FT_MOUTH_STRETCH_RIGHT" value="76" enum="BlendShapeEntry"> + Mouth corner lip pulls out and down. + </constant> + <constant name="FT_MOUTH_STRETCH_LEFT" value="77" enum="BlendShapeEntry"> + Mouth corner lip pulls out and down. + </constant> + <constant name="FT_MOUTH_DIMPLE_RIGHT" value="78" enum="BlendShapeEntry"> + Right lip corner is pushed backwards. + </constant> + <constant name="FT_MOUTH_DIMPLE_LEFT" value="79" enum="BlendShapeEntry"> + Left lip corner is pushed backwards. + </constant> + <constant name="FT_MOUTH_RAISER_UPPER" value="80" enum="BlendShapeEntry"> + Raises and slightly pushes out the upper mouth. + </constant> + <constant name="FT_MOUTH_RAISER_LOWER" value="81" enum="BlendShapeEntry"> + Raises and slightly pushes out the lower mouth. + </constant> + <constant name="FT_MOUTH_PRESS_RIGHT" value="82" enum="BlendShapeEntry"> + Right side lips press and flatten together vertically. + </constant> + <constant name="FT_MOUTH_PRESS_LEFT" value="83" enum="BlendShapeEntry"> + Left side lips press and flatten together vertically. + </constant> + <constant name="FT_MOUTH_TIGHTENER_RIGHT" value="84" enum="BlendShapeEntry"> + Right side lips squeeze together horizontally. + </constant> + <constant name="FT_MOUTH_TIGHTENER_LEFT" value="85" enum="BlendShapeEntry"> + Left side lips squeeze together horizontally. + </constant> + <constant name="FT_TONGUE_OUT" value="86" enum="BlendShapeEntry"> + Tongue visibly sticks out of the mouth. + </constant> + <constant name="FT_TONGUE_UP" value="87" enum="BlendShapeEntry"> + Tongue points upwards. + </constant> + <constant name="FT_TONGUE_DOWN" value="88" enum="BlendShapeEntry"> + Tongue points downwards. + </constant> + <constant name="FT_TONGUE_RIGHT" value="89" enum="BlendShapeEntry"> + Tongue points right. + </constant> + <constant name="FT_TONGUE_LEFT" value="90" enum="BlendShapeEntry"> + Tongue points left. + </constant> + <constant name="FT_TONGUE_ROLL" value="91" enum="BlendShapeEntry"> + Sides of the tongue funnel, creating a roll. + </constant> + <constant name="FT_TONGUE_BLEND_DOWN" value="92" enum="BlendShapeEntry"> + Tongue arches up then down inside the mouth. + </constant> + <constant name="FT_TONGUE_CURL_UP" value="93" enum="BlendShapeEntry"> + Tongue arches down then up inside the mouth. + </constant> + <constant name="FT_TONGUE_SQUISH" value="94" enum="BlendShapeEntry"> + Tongue squishes together and thickens. + </constant> + <constant name="FT_TONGUE_FLAT" value="95" enum="BlendShapeEntry"> + Tongue flattens and thins out. + </constant> + <constant name="FT_TONGUE_TWIST_RIGHT" value="96" enum="BlendShapeEntry"> + Tongue tip rotates clockwise, with the rest following gradually. + </constant> + <constant name="FT_TONGUE_TWIST_LEFT" value="97" enum="BlendShapeEntry"> + Tongue tip rotates counter-clockwise, with the rest following gradually. + </constant> + <constant name="FT_SOFT_PALATE_CLOSE" value="98" enum="BlendShapeEntry"> + Inner mouth throat closes. + </constant> + <constant name="FT_THROAT_SWALLOW" value="99" enum="BlendShapeEntry"> + The Adam's apple visibly swallows. + </constant> + <constant name="FT_NECK_FLEX_RIGHT" value="100" enum="BlendShapeEntry"> + Right side neck visibly flexes. + </constant> + <constant name="FT_NECK_FLEX_LEFT" value="101" enum="BlendShapeEntry"> + Left side neck visibly flexes. + </constant> + <constant name="FT_EYE_CLOSED" value="102" enum="BlendShapeEntry"> + Closes both eye lids. + </constant> + <constant name="FT_EYE_WIDE" value="103" enum="BlendShapeEntry"> + Widens both eye lids. + </constant> + <constant name="FT_EYE_SQUINT" value="104" enum="BlendShapeEntry"> + Squints both eye lids. + </constant> + <constant name="FT_EYE_DILATION" value="105" enum="BlendShapeEntry"> + Dilates both pupils. + </constant> + <constant name="FT_EYE_CONSTRICT" value="106" enum="BlendShapeEntry"> + Constricts both pupils. + </constant> + <constant name="FT_BROW_DOWN_RIGHT" value="107" enum="BlendShapeEntry"> + Pulls the right eyebrow down and in. + </constant> + <constant name="FT_BROW_DOWN_LEFT" value="108" enum="BlendShapeEntry"> + Pulls the left eyebrow down and in. + </constant> + <constant name="FT_BROW_DOWN" value="109" enum="BlendShapeEntry"> + Pulls both eyebrows down and in. + </constant> + <constant name="FT_BROW_UP_RIGHT" value="110" enum="BlendShapeEntry"> + Right brow appears worried. + </constant> + <constant name="FT_BROW_UP_LEFT" value="111" enum="BlendShapeEntry"> + Left brow appears worried. + </constant> + <constant name="FT_BROW_UP" value="112" enum="BlendShapeEntry"> + Both brows appear worried. + </constant> + <constant name="FT_NOSE_SNEER" value="113" enum="BlendShapeEntry"> + Entire face sneers. + </constant> + <constant name="FT_NASAL_DILATION" value="114" enum="BlendShapeEntry"> + Both nose canals dilate. + </constant> + <constant name="FT_NASAL_CONSTRICT" value="115" enum="BlendShapeEntry"> + Both nose canals constrict. + </constant> + <constant name="FT_CHEEK_PUFF" value="116" enum="BlendShapeEntry"> + Puffs both cheeks. + </constant> + <constant name="FT_CHEEK_SUCK" value="117" enum="BlendShapeEntry"> + Sucks in both cheeks. + </constant> + <constant name="FT_CHEEK_SQUINT" value="118" enum="BlendShapeEntry"> + Raises both cheeks. + </constant> + <constant name="FT_LIP_SUCK_UPPER" value="119" enum="BlendShapeEntry"> + Tucks in the upper lips. + </constant> + <constant name="FT_LIP_SUCK_LOWER" value="120" enum="BlendShapeEntry"> + Tucks in the lower lips. + </constant> + <constant name="FT_LIP_SUCK" value="121" enum="BlendShapeEntry"> + Tucks in both lips. + </constant> + <constant name="FT_LIP_FUNNEL_UPPER" value="122" enum="BlendShapeEntry"> + Funnels in the upper lips. + </constant> + <constant name="FT_LIP_FUNNEL_LOWER" value="123" enum="BlendShapeEntry"> + Funnels in the lower lips. + </constant> + <constant name="FT_LIP_FUNNEL" value="124" enum="BlendShapeEntry"> + Funnels in both lips. + </constant> + <constant name="FT_LIP_PUCKER_UPPER" value="125" enum="BlendShapeEntry"> + Upper lip part pushes outwards. + </constant> + <constant name="FT_LIP_PUCKER_LOWER" value="126" enum="BlendShapeEntry"> + Lower lip part pushes outwards. + </constant> + <constant name="FT_LIP_PUCKER" value="127" enum="BlendShapeEntry"> + Lips push outwards. + </constant> + <constant name="FT_MOUTH_UPPER_UP" value="128" enum="BlendShapeEntry"> + Raises the upper lips. + </constant> + <constant name="FT_MOUTH_LOWER_DOWN" value="129" enum="BlendShapeEntry"> + Lowers the lower lips. + </constant> + <constant name="FT_MOUTH_OPEN" value="130" enum="BlendShapeEntry"> + Mouth opens, revealing teeth. + </constant> + <constant name="FT_MOUTH_RIGHT" value="131" enum="BlendShapeEntry"> + Moves mouth right. + </constant> + <constant name="FT_MOUTH_LEFT" value="132" enum="BlendShapeEntry"> + Moves mouth left. + </constant> + <constant name="FT_MOUTH_SMILE_RIGHT" value="133" enum="BlendShapeEntry"> + Right side of the mouth smiles. + </constant> + <constant name="FT_MOUTH_SMILE_LEFT" value="134" enum="BlendShapeEntry"> + Left side of the mouth smiles. + </constant> + <constant name="FT_MOUTH_SMILE" value="135" enum="BlendShapeEntry"> + Mouth expresses a smile. + </constant> + <constant name="FT_MOUTH_SAD_RIGHT" value="136" enum="BlendShapeEntry"> + Right side of the mouth expresses sadness. + </constant> + <constant name="FT_MOUTH_SAD_LEFT" value="137" enum="BlendShapeEntry"> + Left side of the mouth expresses sadness. + </constant> + <constant name="FT_MOUTH_SAD" value="138" enum="BlendShapeEntry"> + Mouth expresses sadness. + </constant> + <constant name="FT_MOUTH_STRETCH" value="139" enum="BlendShapeEntry"> + Mouth stretches. + </constant> + <constant name="FT_MOUTH_DIMPLE" value="140" enum="BlendShapeEntry"> + Lip corners dimple. + </constant> + <constant name="FT_MOUTH_TIGHTENER" value="141" enum="BlendShapeEntry"> + Mouth tightens. + </constant> + <constant name="FT_MOUTH_PRESS" value="142" enum="BlendShapeEntry"> + Mouth presses together. + </constant> + <constant name="FT_MAX" value="143" enum="BlendShapeEntry"> + Represents the size of the [enum BlendShapeEntry] enum. + </constant> + </constants> +</class> diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index 2f106af340..f98c1d66a4 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -10,6 +10,14 @@ <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> </tutorials> <methods> + <method name="add_face_tracker"> + <return type="void" /> + <param index="0" name="tracker_name" type="StringName" /> + <param index="1" name="face_tracker" type="XRFaceTracker" /> + <description> + Registers a new [XRFaceTracker] that tracks the blend shapes of a face. + </description> + </method> <method name="add_interface"> <return type="void" /> <param index="0" name="interface" type="XRInterface" /> @@ -50,6 +58,19 @@ Finds an interface by its [param name]. For example, if your project uses capabilities of an AR/VR platform, you can find the interface for that platform by name and initialize it. </description> </method> + <method name="get_face_tracker" qualifiers="const"> + <return type="XRFaceTracker" /> + <param index="0" name="tracker_name" type="StringName" /> + <description> + Returns the [XRFaceTracker] with the given tracker name. + </description> + </method> + <method name="get_face_trackers" qualifiers="const"> + <return type="Dictionary" /> + <description> + Returns a dictionary of the registered face trackers. Each element of the dictionary is a tracker name mapping to the [XRFaceTracker] instance. + </description> + </method> <method name="get_hmd_transform"> <return type="Transform3D" /> <description> @@ -95,6 +116,13 @@ Returns a dictionary of trackers for [param tracker_types]. </description> </method> + <method name="remove_face_tracker"> + <return type="void" /> + <param index="0" name="tracker_name" type="StringName" /> + <description> + Removes a registered [XRFaceTracker]. + </description> + </method> <method name="remove_interface"> <return type="void" /> <param index="0" name="interface" type="XRInterface" /> @@ -123,6 +151,26 @@ </member> </members> <signals> + <signal name="face_tracker_added"> + <param index="0" name="tracker_name" type="StringName" /> + <param index="1" name="face_tracker" type="XRFaceTracker" /> + <description> + Emitted when a new face tracker is added. + </description> + </signal> + <signal name="face_tracker_removed"> + <param index="0" name="tracker_name" type="StringName" /> + <description> + Emitted when a face tracker is removed. + </description> + </signal> + <signal name="face_tracker_updated"> + <param index="0" name="tracker_name" type="StringName" /> + <param index="1" name="face_tracker" type="XRFaceTracker" /> + <description> + Emitted when an existing face tracker is updated. + </description> + </signal> <signal name="interface_added"> <param index="0" name="interface_name" type="StringName" /> <description> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index b827f420f5..4e735039f7 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -66,6 +66,7 @@ BASE_STRINGS = [ "This method doesn't need an instance to be called, so it can be called directly using the class name.", "This method describes a valid operator to use with this type as left-hand operand.", "This value is an integer composed as a bitmask of the following flags.", + "No return value.", "There is currently no description for this class. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", "There is currently no description for this signal. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", "There is currently no description for this enum. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", @@ -454,7 +455,7 @@ class TypeName: if self.enum is not None: return make_enum(self.enum, self.is_bitfield, state) elif self.type_name == "void": - return "void" + return "|void|" else: return make_type(self.type_name, state) @@ -1454,14 +1455,26 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: def make_type(klass: str, state: State) -> str: if klass.find("*") != -1: # Pointer, ignore - return klass + return f"``{klass}``" + link_type = klass + is_array = False + if link_type.endswith("[]"): # Typed array, strip [] to link to contained type. link_type = link_type[:-2] + is_array = True + if link_type in state.classes: - return f":ref:`{klass}<class_{link_type}>`" - print_error(f'{state.current_class}.xml: Unresolved type "{klass}".', state) - return klass + type_rst = f":ref:`{link_type}<class_{link_type}>`" + if is_array: + type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]" + return type_rst + + print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state) + type_rst = f"``{link_type}``" + if is_array: + type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]" + return type_rst def make_enum(t: str, is_bitfield: bool, state: State) -> str: @@ -1483,7 +1496,7 @@ def make_enum(t: str, is_bitfield: bool, state: State) -> str: if is_bitfield: if not state.classes[c].enums[e].is_bitfield: print_error(f'{state.current_class}.xml: Enum "{t}" is not bitfield.', state) - return f"|bitfield|\\<:ref:`{e}<enum_{c}_{e}>`\\>" + return f"|bitfield|\\[:ref:`{e}<enum_{c}_{e}>`\\]" else: return f":ref:`{e}<enum_{c}_{e}>`" @@ -1513,36 +1526,36 @@ def make_method_signature( out += f":ref:`{op_name}<class_{class_def.name}_{ref_type}_{sanitize_operator_name(definition.name, state)}" for parameter in definition.parameters: out += f"_{parameter.type_name.type_name}" - out += f">` " + out += f">`" elif ref_type == "method": ref_type_qualifier = "" if definition.name.startswith("_"): ref_type_qualifier = "private_" - out += f":ref:`{definition.name}<class_{class_def.name}_{ref_type_qualifier}{ref_type}_{definition.name}>` " + out += f":ref:`{definition.name}<class_{class_def.name}_{ref_type_qualifier}{ref_type}_{definition.name}>`" else: - out += f":ref:`{definition.name}<class_{class_def.name}_{ref_type}_{definition.name}>` " + out += f":ref:`{definition.name}<class_{class_def.name}_{ref_type}_{definition.name}>`" else: - out += f"**{definition.name}** " + out += f"**{definition.name}**" - out += "**(**" + out += "\\ (" for i, arg in enumerate(definition.parameters): if i > 0: out += ", " else: - out += " " + out += "\\ " - out += f"{arg.type_name.to_rst(state)} {arg.name}" + out += f"{arg.name}\\: {arg.type_name.to_rst(state)}" if arg.default_value is not None: - out += f"={arg.default_value}" + out += f" = {arg.default_value}" if qualifiers is not None and "vararg" in qualifiers: if len(definition.parameters) > 0: out += ", ..." else: - out += " ..." + out += "\\ ..." - out += " **)**" + out += "\\ )" if qualifiers is not None: # Use substitutions for abbreviations. This is used to display tooltips on hover. @@ -1629,6 +1642,7 @@ def make_footer() -> str: ) operator_msg = translate("This method describes a valid operator to use with this type as left-hand operand.") bitfield_msg = translate("This value is an integer composed as a bitmask of the following flags.") + void_msg = translate("No return value.") return ( f".. |virtual| replace:: :abbr:`virtual ({virtual_msg})`\n" @@ -1638,6 +1652,7 @@ def make_footer() -> str: f".. |static| replace:: :abbr:`static ({static_msg})`\n" f".. |operator| replace:: :abbr:`operator ({operator_msg})`\n" f".. |bitfield| replace:: :abbr:`BitField ({bitfield_msg})`\n" + f".. |void| replace:: :abbr:`void ({void_msg})`\n" ) diff --git a/drivers/alsamidi/midi_driver_alsamidi.cpp b/drivers/alsamidi/midi_driver_alsamidi.cpp index 6b35987f70..b87be69cc5 100644 --- a/drivers/alsamidi/midi_driver_alsamidi.cpp +++ b/drivers/alsamidi/midi_driver_alsamidi.cpp @@ -82,13 +82,13 @@ size_t MIDIDriverALSAMidi::msg_expected_data(uint8_t status_byte) { } void MIDIDriverALSAMidi::InputConnection::parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver, - uint64_t timestamp) { + uint64_t timestamp, int device_index) { switch (msg_category(byte)) { case MessageCategory::RealTime: // Real-Time messages are single byte messages that can // occur at any point. // We pass them straight through. - driver.receive_input_packet(timestamp, &byte, 1); + driver.receive_input_packet(device_index, timestamp, &byte, 1); break; case MessageCategory::Data: @@ -100,7 +100,7 @@ void MIDIDriverALSAMidi::InputConnection::parse_byte(uint8_t byte, MIDIDriverALS // Forward a complete message and reset relevant state. if (received_data == expected_data) { - driver.receive_input_packet(timestamp, buffer, received_data + 1); + driver.receive_input_packet(device_index, timestamp, buffer, received_data + 1); received_data = 0; if (msg_category(buffer[0]) != MessageCategory::Voice) { @@ -130,13 +130,13 @@ void MIDIDriverALSAMidi::InputConnection::parse_byte(uint8_t byte, MIDIDriverALS expected_data = msg_expected_data(byte); skipping_sys_ex = false; if (expected_data == 0) { - driver.receive_input_packet(timestamp, &byte, 1); + driver.receive_input_packet(device_index, timestamp, &byte, 1); } break; } } -int MIDIDriverALSAMidi::InputConnection::read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp) { +int MIDIDriverALSAMidi::InputConnection::read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index) { int ret; do { uint8_t byte = 0; @@ -147,7 +147,7 @@ int MIDIDriverALSAMidi::InputConnection::read_in(MIDIDriverALSAMidi &driver, uin ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(ret))); } } else { - parse_byte(byte, driver, timestamp); + parse_byte(byte, driver, timestamp, device_index); } } while (ret > 0); @@ -165,7 +165,7 @@ void MIDIDriverALSAMidi::thread_func(void *p_udata) { size_t connection_count = md->connected_inputs.size(); for (size_t i = 0; i < connection_count; i++) { - connections[i].read_in(*md, timestamp); + connections[i].read_in(*md, timestamp, (int)i); } md->unlock(); diff --git a/drivers/alsamidi/midi_driver_alsamidi.h b/drivers/alsamidi/midi_driver_alsamidi.h index 80cc96310f..95ded3b1c9 100644 --- a/drivers/alsamidi/midi_driver_alsamidi.h +++ b/drivers/alsamidi/midi_driver_alsamidi.h @@ -58,7 +58,7 @@ class MIDIDriverALSAMidi : public MIDIDriver { rawmidi_ptr{ midi_in } {} // Read in and parse available data, forwarding any complete messages through the driver. - int read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp); + int read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index); snd_rawmidi_t *rawmidi_ptr = nullptr; @@ -68,7 +68,7 @@ class MIDIDriverALSAMidi : public MIDIDriver { size_t expected_data = 0; size_t received_data = 0; bool skipping_sys_ex = false; - void parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver, uint64_t timestamp); + void parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index); }; Vector<InputConnection> connected_inputs; diff --git a/drivers/coremidi/midi_driver_coremidi.cpp b/drivers/coremidi/midi_driver_coremidi.cpp index ed991b3d9d..87fc7612f7 100644 --- a/drivers/coremidi/midi_driver_coremidi.cpp +++ b/drivers/coremidi/midi_driver_coremidi.cpp @@ -39,8 +39,9 @@ void MIDIDriverCoreMidi::read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con) { MIDIPacket *packet = const_cast<MIDIPacket *>(packet_list->packet); + int *device_index = static_cast<int *>(src_conn_ref_con); for (UInt32 i = 0; i < packet_list->numPackets; i++) { - receive_input_packet(packet->timeStamp, packet->data, packet->length); + receive_input_packet(*device_index, packet->timeStamp, packet->data, packet->length); packet = MIDIPacketNext(packet); } } @@ -64,7 +65,7 @@ Error MIDIDriverCoreMidi::open() { for (int i = 0; i < sources; i++) { MIDIEndpointRef source = MIDIGetSource(i); if (source) { - MIDIPortConnectSource(port_in, source, (void *)this); + MIDIPortConnectSource(port_in, source, static_cast<void *>(&i)); connected_sources.insert(i, source); } } diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 6cca1fef7e..efd554eac9 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1642,8 +1642,12 @@ void RasterizerSceneGLES3::_setup_environment(const RenderDataGLES3 *p_render_da } scene_state.ubo.fog_enabled = environment_get_fog_enabled(p_render_data->environment); + scene_state.ubo.fog_mode = environment_get_fog_mode(p_render_data->environment); scene_state.ubo.fog_density = environment_get_fog_density(p_render_data->environment); scene_state.ubo.fog_height = environment_get_fog_height(p_render_data->environment); + scene_state.ubo.fog_depth_curve = environment_get_fog_depth_curve(p_render_data->environment); + scene_state.ubo.fog_depth_end = environment_get_fog_depth_end(p_render_data->environment) > 0.0 ? environment_get_fog_depth_end(p_render_data->environment) : scene_state.ubo.z_far; + scene_state.ubo.fog_depth_begin = MIN(environment_get_fog_depth_begin(p_render_data->environment), scene_state.ubo.fog_depth_end - 0.001); scene_state.ubo.fog_height_density = environment_get_fog_height_density(p_render_data->environment); scene_state.ubo.fog_aerial_perspective = environment_get_fog_aerial_perspective(p_render_data->environment); @@ -2598,6 +2602,10 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ if (render_data.environment.is_null() || (render_data.environment.is_valid() && !environment_get_fog_enabled(render_data.environment))) { spec_constant_base_flags |= SceneShaderGLES3::DISABLE_FOG; } + + if (render_data.environment.is_valid() && environment_get_fog_mode(render_data.environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH) { + spec_constant_base_flags |= SceneShaderGLES3::USE_DEPTH_FOG; + } } // Render Opaque Objects. RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, spec_constant_base_flags, use_wireframe); diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 7880591dcb..ed59aba266 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -398,15 +398,21 @@ private: float IBL_exposure_normalization; uint32_t fog_enabled; + uint32_t fog_mode; float fog_density; float fog_height; + float fog_height_density; + float fog_depth_curve; + float pad; + float fog_depth_begin; float fog_light_color[3]; + float fog_depth_end; + float fog_sun_scatter; float shadow_bias; - float pad; uint32_t camera_visible_layers; bool pancake_shadows; }; diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index ffdac85c1e..667cbb3d90 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -13,6 +13,7 @@ DISABLE_LIGHT_DIRECTIONAL = false DISABLE_LIGHT_OMNI = false DISABLE_LIGHT_SPOT = false DISABLE_FOG = false +USE_DEPTH_FOG = false USE_RADIANCE_MAP = true USE_LIGHTMAP = false USE_SH_LIGHTMAP = false @@ -181,15 +182,21 @@ layout(std140) uniform SceneData { // ubo:2 float IBL_exposure_normalization; bool fog_enabled; + uint fog_mode; float fog_density; float fog_height; float fog_height_density; + float fog_depth_curve; + float pad; + float fog_depth_begin; + vec3 fog_light_color; + float fog_depth_end; + float fog_sun_scatter; float shadow_bias; - float pad; uint camera_visible_layers; bool pancake_shadows; } @@ -666,15 +673,21 @@ layout(std140) uniform SceneData { // ubo:2 float IBL_exposure_normalization; bool fog_enabled; + uint fog_mode; float fog_density; float fog_height; float fog_height_density; + float fog_depth_curve; + float pad; + float fog_depth_begin; + vec3 fog_light_color; + float fog_depth_end; + float fog_sun_scatter; float shadow_bias; - float pad; uint camera_visible_layers; bool pancake_shadows; } @@ -1250,7 +1263,14 @@ vec4 fog_process(vec3 vertex) { } #endif // !DISABLE_LIGHT_DIRECTIONAL - float fog_amount = 1.0 - exp(min(0.0, -length(vertex) * scene_data.fog_density)); + float fog_amount = 0.0; + +#ifdef USE_DEPTH_FOG + float fog_z = smoothstep(scene_data.fog_depth_begin, scene_data.fog_depth_end, length(vertex)); + fog_amount = pow(fog_z, scene_data.fog_depth_curve) * scene_data.fog_density; +#else + fog_amount = 1 - exp(min(0.0, -length(vertex) * scene_data.fog_density)); +#endif // USE_DEPTH_FOG if (abs(scene_data.fog_height_density) >= 0.0001) { float y = (scene_data.inv_view_matrix * vec4(vertex, 1.0)).y; diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 6c4bef10d5..8ab66e2bc6 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -1962,7 +1962,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b //if we have a mesh set, we need to re-generate the AABB from the new data const float *data = p_buffer.ptr(); - if (multimesh->custom_aabb != AABB()) { + if (multimesh->custom_aabb == AABB()) { _multimesh_re_create_aabb(multimesh, data, multimesh->instances); multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); } @@ -2113,7 +2113,7 @@ void MeshStorage::_update_dirty_multimeshes() { if (multimesh->aabb_dirty && multimesh->mesh.is_valid()) { multimesh->aabb_dirty = false; - if (multimesh->custom_aabb != AABB()) { + if (multimesh->custom_aabb == AABB()) { _multimesh_re_create_aabb(multimesh, data, visible_instances); multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); } diff --git a/drivers/winmidi/midi_driver_winmidi.cpp b/drivers/winmidi/midi_driver_winmidi.cpp index cdbab489c4..07f0226c5d 100644 --- a/drivers/winmidi/midi_driver_winmidi.cpp +++ b/drivers/winmidi/midi_driver_winmidi.cpp @@ -36,7 +36,7 @@ void MIDIDriverWinMidi::read(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { if (wMsg == MIM_DATA) { - receive_input_packet((uint64_t)dwParam2, (uint8_t *)&dwParam1, 3); + receive_input_packet((int)dwInstance, (uint64_t)dwParam2, (uint8_t *)&dwParam1, 3); } } @@ -44,7 +44,7 @@ Error MIDIDriverWinMidi::open() { for (UINT i = 0; i < midiInGetNumDevs(); i++) { HMIDIIN midi_in; - MMRESULT res = midiInOpen(&midi_in, i, (DWORD_PTR)read, (DWORD_PTR)this, CALLBACK_FUNCTION); + MMRESULT res = midiInOpen(&midi_in, i, (DWORD_PTR)read, (DWORD_PTR)i, CALLBACK_FUNCTION); if (res == MMSYSERR_NOERROR) { midiInStart(midi_in); connected_sources.insert(i, midi_in); diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index e1ce08b83e..aff56bf792 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -834,20 +834,20 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } if (p_event->is_pressed()) { - if (ED_GET_SHORTCUT("animation_editor/duplicate_selected_keys")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) { if (!read_only) { duplicate_selected_keys(-1.0); } accept_event(); } - if (ED_GET_SHORTCUT("animation_editor/copy_selected_keys")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_editor/copy_selected_keys", p_event)) { if (!read_only) { - copy_selected_keys(); + copy_selected_keys(false); } accept_event(); } - if (ED_GET_SHORTCUT("animation_editor/paste_keys")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) { if (!read_only) { paste_keys(-1.0); } @@ -858,7 +858,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> key_press = p_event; if (key_press.is_valid() && key_press->is_pressed()) { - if (ED_GET_SHORTCUT("animation_bezier_editor/focus")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_bezier_editor/focus", p_event)) { SelectionSet focused_keys; if (selection.is_empty()) { for (int i = 0; i < edit_points.size(); ++i) { @@ -927,7 +927,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { queue_redraw(); accept_event(); return; - } else if (ED_GET_SHORTCUT("animation_bezier_editor/select_all_keys")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("animation_bezier_editor/select_all_keys", p_event)) { for (int i = 0; i < edit_points.size(); ++i) { selection.insert(IntPair(edit_points[i].track, edit_points[i].key)); } @@ -935,7 +935,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { queue_redraw(); accept_event(); return; - } else if (ED_GET_SHORTCUT("animation_bezier_editor/deselect_all_keys")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("animation_bezier_editor/deselect_all_keys", p_event)) { selection.clear(); queue_redraw(); @@ -959,6 +959,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (selected || selection.size()) { menu->add_separator(); menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE); + menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCut")), TTR("Cut Selected Key(s)"), MENU_KEY_CUT); menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCopy")), TTR("Copy Selected Key(s)"), MENU_KEY_COPY); } @@ -1592,8 +1593,11 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) { case MENU_KEY_DELETE: { delete_selection(); } break; + case MENU_KEY_CUT: { + copy_selected_keys(true); + } break; case MENU_KEY_COPY: { - copy_selected_keys(); + copy_selected_keys(false); } break; case MENU_KEY_PASTE: { paste_keys(time); @@ -1673,7 +1677,7 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs) { undo_redo->commit_action(); } -void AnimationBezierTrackEdit::copy_selected_keys() { +void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) { if (selection.is_empty()) { return; } @@ -1696,6 +1700,25 @@ void AnimationBezierTrackEdit::copy_selected_keys() { keys.insert(sk, ki); } editor->_set_key_clipboard(selected_track, top_time, keys); + + if (p_cut) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Animation Cut Keys"), UndoRedo::MERGE_DISABLE, animation.ptr()); + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) { + int track_idx = E->key().track; + int key_idx = E->key().key; + float time = E->value().pos; + undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track_idx, time); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track_idx, time, animation->track_get_key_value(track_idx, key_idx), animation->track_get_key_transition(track_idx, key_idx)); + undo_redo->add_undo_method(this, "_select_at_anim", animation, track_idx, time); + } + for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) { + undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos); + } + undo_redo->commit_action(); + } } void AnimationBezierTrackEdit::paste_keys(real_t p_ofs) { diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index 109ba0d780..ec2b52221e 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -42,6 +42,7 @@ class AnimationBezierTrackEdit : public Control { enum { MENU_KEY_INSERT, MENU_KEY_DUPLICATE, + MENU_KEY_CUT, MENU_KEY_COPY, MENU_KEY_PASTE, MENU_KEY_DELETE, @@ -212,7 +213,7 @@ public: void update_play_position(); void duplicate_selected_keys(real_t p_ofs); - void copy_selected_keys(); + void copy_selected_keys(bool p_cut); void paste_keys(real_t p_ofs); void delete_selection(); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index caeae69ede..f893234acf 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1401,15 +1401,15 @@ void AnimationTimelineEdit::_notification(int p_what) { time_icon->set_texture(get_editor_theme_icon(SNAME("Time"))); add_track->get_popup()->clear(); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyValue")), TTR("Property Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXPosition")), TTR("3D Position Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXRotation")), TTR("3D Rotation Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXScale")), TTR("3D Scale Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBlendShape")), TTR("Blend Shape Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyCall")), TTR("Call Method Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBezier")), TTR("Bezier Curve Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAudio")), TTR("Audio Playback Track")); - add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAnimation")), TTR("Animation Playback Track")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyValue")), TTR("Property Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXPosition")), TTR("3D Position Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXRotation")), TTR("3D Rotation Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyXScale")), TTR("3D Scale Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBlendShape")), TTR("Blend Shape Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyCall")), TTR("Call Method Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyBezier")), TTR("Bezier Curve Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAudio")), TTR("Audio Playback Track...")); + add_track->get_popup()->add_icon_item(get_editor_theme_icon(SNAME("KeyAnimation")), TTR("Animation Playback Track...")); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { @@ -2685,27 +2685,33 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (p_event->is_pressed()) { - if (ED_GET_SHORTCUT("animation_editor/duplicate_selected_keys")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) { if (!read_only) { emit_signal(SNAME("duplicate_request"), -1.0); } accept_event(); } - if (ED_GET_SHORTCUT("animation_editor/copy_selected_keys")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_editor/cut_selected_keys", p_event)) { + if (!read_only) { + emit_signal(SNAME("cut_request")); + } + accept_event(); + } + if (ED_IS_SHORTCUT("animation_editor/copy_selected_keys", p_event)) { if (!read_only) { emit_signal(SNAME("copy_request")); } accept_event(); } - if (ED_GET_SHORTCUT("animation_editor/paste_keys")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) { if (!read_only) { emit_signal(SNAME("paste_request"), -1.0); } accept_event(); } - if (ED_GET_SHORTCUT("animation_editor/delete_selection")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("animation_editor/delete_selection", p_event)) { if (!read_only) { emit_signal(SNAME("delete_request")); } @@ -2847,10 +2853,11 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { bool selected = _try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), false); menu->clear(); - menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Key"), MENU_KEY_INSERT); + menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Key..."), MENU_KEY_INSERT); if (selected || editor->is_selection_active()) { menu->add_separator(); menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE); + menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCut")), TTR("Cut Key(s)"), MENU_KEY_CUT); menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCopy")), TTR("Copy Key(s)"), MENU_KEY_COPY); } if (editor->is_key_clipboard_active()) { @@ -3176,10 +3183,12 @@ void AnimationTrackEdit::_menu_selected(int p_index) { case MENU_KEY_DUPLICATE: { emit_signal(SNAME("duplicate_request"), insert_at_pos); } break; + case MENU_KEY_CUT: { + emit_signal(SNAME("cut_request")); + } break; case MENU_KEY_COPY: { emit_signal(SNAME("copy_request")); } break; - case MENU_KEY_PASTE: { emit_signal(SNAME("paste_request"), insert_at_pos); } break; @@ -3258,6 +3267,7 @@ void AnimationTrackEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset"))); ADD_SIGNAL(MethodInfo("create_reset_request")); ADD_SIGNAL(MethodInfo("copy_request")); + ADD_SIGNAL(MethodInfo("cut_request")); ADD_SIGNAL(MethodInfo("paste_request", PropertyInfo(Variant::FLOAT, "offset"))); ADD_SIGNAL(MethodInfo("delete_request")); } @@ -4610,6 +4620,7 @@ void AnimationTrackEditor::_update_tracks() { track_edit->connect("move_selection_cancel", callable_mp(this, &AnimationTrackEditor::_move_selection_cancel)); track_edit->connect("duplicate_request", callable_mp(this, &AnimationTrackEditor::_anim_duplicate_keys).bind(i), CONNECT_DEFERRED); + track_edit->connect("cut_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_CUT_KEYS), CONNECT_DEFERRED); track_edit->connect("copy_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_COPY_KEYS), CONNECT_DEFERRED); track_edit->connect("paste_request", callable_mp(this, &AnimationTrackEditor::_anim_paste_keys).bind(i), CONNECT_DEFERRED); track_edit->connect("create_reset_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_ADD_RESET_KEY), CONNECT_DEFERRED); @@ -5724,10 +5735,11 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) { } } -void AnimationTrackEditor::_anim_copy_keys() { +void AnimationTrackEditor::_anim_copy_keys(bool p_cut) { if (is_selection_active() && animation.is_valid()) { int top_track = 0x7FFFFFFF; float top_time = 1e10; + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { const SelectedKey &sk = E->key(); @@ -5743,6 +5755,24 @@ void AnimationTrackEditor::_anim_copy_keys() { ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10); _set_key_clipboard(top_track, top_time, selection); + + if (p_cut) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Animation Cut Keys"), UndoRedo::MERGE_DISABLE, animation.ptr()); + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + int track_idx = E->key().track; + int key_idx = E->key().key; + float time = E->value().pos; + undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track_idx, time); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track_idx, time, animation->track_get_key_value(track_idx, key_idx), animation->track_get_key_transition(track_idx, key_idx)); + } + for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos); + } + undo_redo->commit_action(); + } } } @@ -5904,6 +5934,16 @@ bool AnimationTrackEditor::_is_track_compatible(int p_target_track_idx, Variant: void AnimationTrackEditor::_edit_menu_about_to_popup() { AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset()); + + bool has_length = false; + for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { + if (animation->track_get_type(E.key.track) == Animation::TYPE_AUDIO && animation->audio_track_get_key_stream(E.key.track, E.key.key).is_valid()) { + has_length = true; + break; + } + } + edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_SET_START_OFFSET), !has_length); + edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_SET_END_OFFSET), !has_length); } void AnimationTrackEditor::goto_prev_step(bool p_from_mouse_event) { @@ -6229,6 +6269,62 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { undo_redo->commit_action(); } break; + case EDIT_SET_START_OFFSET: { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Animation Set Start Offset"), UndoRedo::MERGE_ENDS); + for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { + if (animation->track_get_type(E.key.track) != Animation::TYPE_AUDIO) { + continue; + } + Ref<AudioStream> stream = animation->audio_track_get_key_stream(E.key.track, E.key.key); + if (stream.is_null()) { + continue; + } + double len = stream->get_length() - animation->audio_track_get_key_end_offset(E.key.track, E.key.key); + real_t prev_offset = animation->audio_track_get_key_start_offset(E.key.track, E.key.key); + double prev_time = animation->track_get_key_time(E.key.track, E.key.key); + float cur_time = timeline->get_play_position(); + float diff = prev_offset + cur_time - prev_time; + float destination = cur_time - MIN(0, diff); + if (diff >= len || animation->track_find_key(E.key.track, destination, Animation::FIND_MODE_EXACT) >= 0) { + continue; + } + undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", E.key.track, E.key.key, diff); + undo_redo->add_do_method(animation.ptr(), "track_set_key_time", E.key.track, E.key.key, destination); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_time", E.key.track, E.key.key, prev_time); + undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", E.key.track, E.key.key, prev_offset); + } + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + undo_redo->commit_action(); + } break; + case EDIT_SET_END_OFFSET: { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Animation Set End Offset"), UndoRedo::MERGE_ENDS); + for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { + if (animation->track_get_type(E.key.track) != Animation::TYPE_AUDIO) { + continue; + } + Ref<AudioStream> stream = animation->audio_track_get_key_stream(E.key.track, E.key.key); + if (stream.is_null()) { + continue; + } + double len = stream->get_length() - animation->audio_track_get_key_start_offset(E.key.track, E.key.key); + real_t prev_offset = animation->audio_track_get_key_end_offset(E.key.track, E.key.key); + double prev_time = animation->track_get_key_time(E.key.track, E.key.key); + float cur_time = timeline->get_play_position(); + float diff = prev_time + len - cur_time; + if (diff >= len) { + continue; + } + undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", E.key.track, E.key.key, diff); + undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", E.key.track, E.key.key, prev_offset); + } + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + undo_redo->commit_action(); + } break; + case EDIT_EASE_SELECTION: { ease_dialog->popup_centered(Size2(200, 100) * EDSCALE); } break; @@ -6332,16 +6428,53 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } _anim_duplicate_keys(-1.0, -1.0); } break; + case EDIT_CUT_KEYS: { + if (bezier_edit->is_visible()) { + bezier_edit->copy_selected_keys(true); + break; + } + _anim_copy_keys(true); + } break; case EDIT_COPY_KEYS: { if (bezier_edit->is_visible()) { - bezier_edit->copy_selected_keys(); + bezier_edit->copy_selected_keys(false); break; } - _anim_copy_keys(); + _anim_copy_keys(false); } break; case EDIT_PASTE_KEYS: { _anim_paste_keys(-1.0, -1.0); } break; + case EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR: { + if (moving_selection || selection.is_empty()) { + break; + } + real_t from_t = 1e20; + for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { + real_t t = animation->track_get_key_time(E.key.track, E.key.key); + if (t < from_t) { + from_t = t; + } + } + _move_selection_begin(); + _move_selection(timeline->get_play_position() - from_t); + _move_selection_commit(); + } break; + case EDIT_MOVE_LAST_SELECTED_KEY_TO_CURSOR: { + if (moving_selection || selection.is_empty()) { + break; + } + real_t to_t = -1e20; + for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { + real_t t = animation->track_get_key_time(E.key.track, E.key.key); + if (t > to_t) { + to_t = t; + } + } + _move_selection_begin(); + _move_selection(timeline->get_play_position() - to_t); + _move_selection_commit(); + } break; case EDIT_ADD_RESET_KEY: { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Animation Add RESET Keys")); @@ -6590,6 +6723,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { + _clear_selection(); for (int i = 0; i < p_animation->get_track_count(); i++) { if (!root->has_node_and_resource(p_animation->track_get_path(i))) { continue; @@ -6618,6 +6752,76 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { continue; } + if (cleanup_keys_with_trimming_head->is_pressed()) { + // Check is necessary because if there is already a key in position 0, it should not be replaced. + if (p_animation->track_get_type(i) == Animation::TYPE_AUDIO && p_animation->track_find_key(i, 0, Animation::FIND_MODE_EXACT) < 0) { + for (int j = 0; j < p_animation->track_get_key_count(i); j++) { + double t = p_animation->track_get_key_time(i, j); + if (t < 0) { + if (j == p_animation->track_get_key_count(i) - 1 || (j + 1 < p_animation->track_get_key_count(i) && p_animation->track_get_key_time(i, j + 1) > 0)) { + Ref<AudioStream> stream = p_animation->audio_track_get_key_stream(i, j); + double len = stream->get_length() - p_animation->audio_track_get_key_end_offset(i, j); + double prev_offset = p_animation->audio_track_get_key_start_offset(i, j); + double prev_time = p_animation->track_get_key_time(i, j); + double diff = prev_offset - prev_time; + if (diff >= len) { + p_animation->track_remove_key(i, j); + j--; + continue; + } + p_animation->audio_track_set_key_start_offset(i, j, diff); + p_animation->track_set_key_time(i, j, 0); + } else { + p_animation->track_remove_key(i, j); + j--; + } + } + } + } else { + for (int j = 0; j < p_animation->track_get_key_count(i); j++) { + double t = p_animation->track_get_key_time(i, j); + if (t < 0) { + p_animation->track_remove_key(i, j); + j--; + } + } + } + } + + if (cleanup_keys_with_trimming_end->is_pressed()) { + if (p_animation->track_get_type(i) == Animation::TYPE_AUDIO) { + for (int j = 0; j < p_animation->track_get_key_count(i); j++) { + double t = p_animation->track_get_key_time(i, j); + if (t <= p_animation->get_length() && (j == p_animation->track_get_key_count(i) - 1 || (j + 1 < p_animation->track_get_key_count(i) && p_animation->track_get_key_time(i, j + 1) > p_animation->get_length()))) { + Ref<AudioStream> stream = animation->audio_track_get_key_stream(i, j); + double len = stream->get_length() - animation->audio_track_get_key_start_offset(i, j); + if (t + len < p_animation->get_length()) { + continue; + } + double prev_time = animation->track_get_key_time(i, j); + double diff = prev_time + len - p_animation->get_length(); + if (diff >= len) { + p_animation->track_remove_key(i, j); + j--; + continue; + } + p_animation->audio_track_set_key_end_offset(i, j, diff); + } else if (t > p_animation->get_length()) { + p_animation->track_remove_key(i, j); + j--; + } + } + } else { + for (int j = 0; j < p_animation->track_get_key_count(i); j++) { + double t = p_animation->track_get_key_time(i, j); + if (t > p_animation->get_length()) { + p_animation->track_remove_key(i, j); + j--; + } + } + } + } + if (!prop_exists || p_animation->track_get_type(i) != Animation::TYPE_VALUE || !cleanup_keys->is_pressed()) { continue; } @@ -6977,19 +7181,26 @@ AnimationTrackEditor::AnimationTrackEditor() { edit->set_flat(false); edit->set_disabled(true); edit->set_tooltip_text(TTR("Animation properties.")); - edit->get_popup()->add_item(TTR("Copy Tracks"), EDIT_COPY_TRACKS); + edit->get_popup()->add_item(TTR("Copy Tracks..."), EDIT_COPY_TRACKS); edit->get_popup()->add_item(TTR("Paste Tracks"), EDIT_PASTE_TRACKS); edit->get_popup()->add_separator(); - edit->get_popup()->add_item(TTR("Scale Selection"), EDIT_SCALE_SELECTION); - edit->get_popup()->add_item(TTR("Scale From Cursor"), EDIT_SCALE_FROM_CURSOR); + edit->get_popup()->add_item(TTR("Scale Selection..."), EDIT_SCALE_SELECTION); + edit->get_popup()->add_item(TTR("Scale From Cursor..."), EDIT_SCALE_FROM_CURSOR); + edit->get_popup()->add_separator(); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/set_start_offset", TTR("Set Start Offset (Audio)"), KeyModifierMask::CMD_OR_CTRL | Key::BRACKETLEFT), EDIT_SET_START_OFFSET); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/set_end_offset", TTR("Set End Offset (Audio)"), KeyModifierMask::CMD_OR_CTRL | Key::BRACKETRIGHT), EDIT_SET_END_OFFSET); edit->get_popup()->add_separator(); - edit->get_popup()->add_item(TTR("Make Easing Selection"), EDIT_EASE_SELECTION); + edit->get_popup()->add_item(TTR("Make Easing Selection..."), EDIT_EASE_SELECTION); edit->get_popup()->add_separator(); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selected_keys", TTR("Duplicate Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::D), EDIT_DUPLICATE_SELECTED_KEYS); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/cut_selected_keys", TTR("Cut Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::X), EDIT_CUT_KEYS); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/copy_selected_keys", TTR("Copy Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::C), EDIT_COPY_KEYS); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/paste_keys", TTR("Paste Keys"), KeyModifierMask::CMD_OR_CTRL | Key::V), EDIT_PASTE_KEYS); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/add_reset_value", TTR("Add RESET Value(s)"))); edit->get_popup()->add_separator(); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/move_first_selected_key_to_cursor", TTR("Move First Selected Key to Cursor"), Key::BRACKETLEFT), EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/move_last_selected_key_to_cursor", TTR("Move Last Selected Key to Cursor"), Key::BRACKETRIGHT), EDIT_MOVE_LAST_SELECTED_KEY_TO_CURSOR); + edit->get_popup()->add_separator(); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/delete_selection", TTR("Delete Selection"), Key::KEY_DELETE), EDIT_DELETE_SELECTION); edit->get_popup()->add_separator(); @@ -6998,9 +7209,9 @@ AnimationTrackEditor::AnimationTrackEditor() { edit->get_popup()->add_separator(); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/apply_reset", TTR("Apply Reset")), EDIT_APPLY_RESET); edit->get_popup()->add_separator(); - edit->get_popup()->add_item(TTR("Bake Animation"), EDIT_BAKE_ANIMATION); - edit->get_popup()->add_item(TTR("Optimize Animation (no undo)"), EDIT_OPTIMIZE_ANIMATION); - edit->get_popup()->add_item(TTR("Clean-Up Animation (no undo)"), EDIT_CLEAN_UP_ANIMATION); + edit->get_popup()->add_item(TTR("Bake Animation..."), EDIT_BAKE_ANIMATION); + edit->get_popup()->add_item(TTR("Optimize Animation (no undo)..."), EDIT_OPTIMIZE_ANIMATION); + edit->get_popup()->add_item(TTR("Clean-Up Animation (no undo)..."), EDIT_CLEAN_UP_ANIMATION); edit->get_popup()->connect("id_pressed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed)); edit->get_popup()->connect("about_to_popup", callable_mp(this, &AnimationTrackEditor::_edit_menu_about_to_popup)); @@ -7083,12 +7294,21 @@ AnimationTrackEditor::AnimationTrackEditor() { optimize_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_OPTIMIZE_ANIMATION_CONFIRM)); // - cleanup_dialog = memnew(ConfirmationDialog); add_child(cleanup_dialog); VBoxContainer *cleanup_vb = memnew(VBoxContainer); cleanup_dialog->add_child(cleanup_vb); + cleanup_keys_with_trimming_head = memnew(CheckBox); + cleanup_keys_with_trimming_head->set_text(TTR("Trim keys placed in negative time")); + cleanup_keys_with_trimming_head->set_pressed(true); + cleanup_vb->add_child(cleanup_keys_with_trimming_head); + + cleanup_keys_with_trimming_end = memnew(CheckBox); + cleanup_keys_with_trimming_end->set_text(TTR("Trim keys placed exceed the animation length")); + cleanup_keys_with_trimming_end->set_pressed(true); + cleanup_vb->add_child(cleanup_keys_with_trimming_end); + cleanup_keys = memnew(CheckBox); cleanup_keys->set_text(TTR("Remove invalid keys")); cleanup_keys->set_pressed(true); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index bb84577ba3..0d6f93fefc 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -231,6 +231,7 @@ class AnimationTrackEdit : public Control { MENU_LOOP_CLAMP, MENU_KEY_INSERT, MENU_KEY_DUPLICATE, + MENU_KEY_CUT, MENU_KEY_COPY, MENU_KEY_PASTE, MENU_KEY_ADD_RESET, @@ -556,6 +557,8 @@ class AnimationTrackEditor : public VBoxContainer { SpinBox *optimize_precision_error = nullptr; ConfirmationDialog *cleanup_dialog = nullptr; + CheckBox *cleanup_keys_with_trimming_head = nullptr; + CheckBox *cleanup_keys_with_trimming_end = nullptr; CheckBox *cleanup_keys = nullptr; CheckBox *cleanup_tracks = nullptr; CheckBox *cleanup_all = nullptr; @@ -578,7 +581,7 @@ class AnimationTrackEditor : public VBoxContainer { void _anim_duplicate_keys(float p_ofs, int p_track); - void _anim_copy_keys(); + void _anim_copy_keys(bool p_cut); bool _is_track_compatible(int p_target_track_idx, Variant::Type p_source_value_type, Animation::TrackType p_source_track_type); @@ -649,14 +652,21 @@ public: EDIT_COPY_TRACKS, EDIT_COPY_TRACKS_CONFIRM, EDIT_PASTE_TRACKS, + EDIT_CUT_KEYS, EDIT_COPY_KEYS, EDIT_PASTE_KEYS, EDIT_SCALE_SELECTION, EDIT_SCALE_FROM_CURSOR, EDIT_SCALE_CONFIRM, + EDIT_SET_START_OFFSET, + EDIT_SET_END_OFFSET, EDIT_EASE_SELECTION, EDIT_EASE_CONFIRM, EDIT_DUPLICATE_SELECTED_KEYS, + EDIT_DUPLICATE_SELECTION, + EDIT_DUPLICATE_TRANSPOSED, + EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR, + EDIT_MOVE_LAST_SELECTED_KEY_TO_CURSOR, EDIT_ADD_RESET_KEY, EDIT_DELETE_SELECTION, EDIT_GOTO_NEXT_STEP, diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 2809b873b1..1f978bf797 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -350,7 +350,7 @@ void EditorProfiler::_update_frame() { category->set_custom_color(0, _get_color_from_signature(m.categories[i].signature)); } - for (int j = m.categories[i].items.size() - 1; j >= 0; j--) { + for (int j = 0; j < m.categories[i].items.size(); j++) { const Metric::Category::Item &it = m.categories[i].items[j]; if (it.internal == it.total && !display_internal_profiles->is_pressed() && m.categories[i].name == "Script Functions") { diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index 9c0d474f49..b4ef0f8c4a 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -36,6 +36,8 @@ #include "core/version.h" #include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" +#include "scene/gui/item_list.h" +#include "scene/resources/style_box.h" // The metadata key used to store and retrieve the version text to copy to the clipboard. const String EditorAbout::META_TEXT_TO_COPY = "text_to_copy"; @@ -52,13 +54,23 @@ void EditorAbout::_notification(int p_what) { _tpl_text->add_theme_constant_override("line_separation", 4 * EDSCALE); _tpl_text->end_bulk_theme_override(); - _license_text->begin_bulk_theme_override(); - _license_text->add_theme_font_override("normal_font", font); - _license_text->add_theme_font_size_override("normal_font_size", font_size); - _license_text->add_theme_constant_override("line_separation", 4 * EDSCALE); - _license_text->end_bulk_theme_override(); + license_text_label->begin_bulk_theme_override(); + license_text_label->add_theme_font_override("normal_font", font); + license_text_label->add_theme_font_size_override("normal_font_size", font_size); + license_text_label->add_theme_constant_override("line_separation", 4 * EDSCALE); + license_text_label->end_bulk_theme_override(); _logo->set_texture(get_editor_theme_icon(SNAME("Logo"))); + + Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty); + for (ItemList *il : name_lists) { + for (int i = 0; i < il->get_item_count(); i++) { + if (il->get_item_metadata(i)) { + il->set_item_icon(i, get_theme_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons))); + il->set_item_icon_modulate(i, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))); + } + } + } } break; } } @@ -73,15 +85,18 @@ void EditorAbout::_version_button_pressed() { DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY)); } -void EditorAbout::_bind_methods() { - ClassDB::bind_method(D_METHOD("_version_button_pressed"), &EditorAbout::_version_button_pressed); +void EditorAbout::_item_with_website_selected(int p_id, ItemList *p_il) { + const String website = p_il->get_item_metadata(p_id); + if (!website.is_empty()) { + OS::get_singleton()->shell_open(website); + } } -TextureRect *EditorAbout::get_logo() const { - return _logo; +void EditorAbout::_item_list_resized(ItemList *p_il) { + p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?. } -ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_flag_single_column) { +ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website) { ScrollContainer *sc = memnew(ScrollContainer); sc->set_name(p_name); sc->set_v_size_flags(Control::SIZE_EXPAND); @@ -90,8 +105,10 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<St vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); sc->add_child(vbc); + Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty); + for (int i = 0; i < p_sections.size(); i++) { - bool single_column = p_flag_single_column & 1 << i; + bool single_column = p_single_column_flags & (1 << i); const char *const *names_ptr = p_src[i]; if (*names_ptr) { Label *lbl = memnew(Label); @@ -105,11 +122,47 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<St il->set_same_column_width(true); il->set_auto_height(true); il->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + il->set_focus_mode(Control::FOCUS_NONE); il->add_theme_constant_override("h_separation", 16 * EDSCALE); - while (*names_ptr) { - il->add_item(String::utf8(*names_ptr++), nullptr, false); + if (p_allow_website) { + il->set_focus_mode(Control::FOCUS_CLICK); + il->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + il->connect("item_activated", callable_mp(this, &EditorAbout::_item_with_website_selected).bind(il)); + il->connect("resized", callable_mp(this, &EditorAbout::_item_list_resized).bind(il)); + il->connect("focus_exited", callable_mp(il, &ItemList::deselect_all)); + + il->add_theme_style_override("focus", empty_stylebox); + il->add_theme_style_override("selected", empty_stylebox); + + while (*names_ptr) { + const String name = String::utf8(*names_ptr++); + const String identifier = name.get_slice("<", 0); + const String website = name.get_slice_count("<") == 1 ? "" : name.get_slice("<", 1).trim_suffix(">"); + + const int name_item_id = il->add_item(identifier, nullptr, false); + il->set_item_tooltip_enabled(name_item_id, false); + + if (!website.is_empty()) { + il->set_item_selectable(name_item_id, true); + il->set_item_metadata(name_item_id, website); + il->set_item_tooltip(name_item_id, website + "\n\n" + TTR("Double-click to open in browser.")); + il->set_item_tooltip_enabled(name_item_id, true); + } + + if (!*names_ptr && name.contains(" anonymous ")) { + il->set_item_disabled(name_item_id, true); + } + } + } else { + while (*names_ptr) { + il->add_item(String::utf8(*names_ptr++), nullptr, false); + } } - il->set_max_columns(il->get_item_count() < 4 || single_column ? 1 : 16); + il->set_max_columns(single_column ? 1 : 16); + + name_lists.append(il); + vbc->add_child(il); HSeparator *hs = memnew(HSeparator); @@ -173,7 +226,7 @@ EditorAbout::EditorAbout() { tc->set_theme_type_variation("TabContainerOdd"); vbc->add_child(tc); - // Authors + // Authors. List<String> dev_sections; dev_sections.push_back(TTR("Project Founders")); @@ -187,9 +240,9 @@ EditorAbout::EditorAbout() { AUTHORS_PROJECT_MANAGERS, AUTHORS_DEVELOPERS, }; - tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 1)); + tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1)); // First section (Project Founders) is always one column. - // Donors + // Donors. List<String> donor_sections; donor_sections.push_back(TTR("Patrons")); @@ -210,19 +263,19 @@ EditorAbout::EditorAbout() { DONORS_MEMBERS_PLATINUM, DONORS_MEMBERS_GOLD, }; - tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 3)); + tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 0b1, true)); // First section (Patron) is one column. - // License + // License. - _license_text = memnew(RichTextLabel); - _license_text->set_threaded(true); - _license_text->set_name(TTR("License")); - _license_text->set_h_size_flags(Control::SIZE_EXPAND_FILL); - _license_text->set_v_size_flags(Control::SIZE_EXPAND_FILL); - _license_text->set_text(String::utf8(GODOT_LICENSE_TEXT)); - tc->add_child(_license_text); + license_text_label = memnew(RichTextLabel); + license_text_label->set_threaded(true); + license_text_label->set_name(TTR("License")); + license_text_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + license_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + license_text_label->set_text(String::utf8(GODOT_LICENSE_TEXT)); + tc->add_child(license_text_label); - // Thirdparty License + // Thirdparty License. VBoxContainer *license_thirdparty = memnew(VBoxContainer); license_thirdparty->set_name(TTR("Third-party Licenses")); diff --git a/editor/editor_about.h b/editor/editor_about.h index 639dc6cc3f..fc3d6cedce 100644 --- a/editor/editor_about.h +++ b/editor/editor_about.h @@ -54,21 +54,21 @@ class EditorAbout : public AcceptDialog { private: void _license_tree_selected(); void _version_button_pressed(); - ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_flag_single_column = 0); + void _item_with_website_selected(int p_id, ItemList *p_il); + void _item_list_resized(ItemList *p_il); + ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false); LinkButton *version_btn = nullptr; Tree *_tpl_tree = nullptr; - RichTextLabel *_license_text = nullptr; + RichTextLabel *license_text_label = nullptr; RichTextLabel *_tpl_text = nullptr; TextureRect *_logo = nullptr; + Vector<ItemList *> name_lists; protected: void _notification(int p_what); - static void _bind_methods(); public: - TextureRect *get_logo() const; - EditorAbout(); ~EditorAbout(); }; diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 7ece509731..99459df96c 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -476,7 +476,8 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { // If the search term is empty, add any classes which are not script docs or which don't start with // a double-quotation. This will ensure that only C++ classes and explicitly named classes will // be added. - match.name = (term.is_empty() && (!class_doc->is_script_doc || class_doc->name[0] != '\"')) || _match_string(term, class_doc->name) || _match_keywords(term, class_doc->keywords); + match.name = (term.is_empty() && (!class_doc->is_script_doc || class_doc->name[0] != '\"')) || _match_string(term, class_doc->name); + match.keyword = _match_keywords(term, class_doc->keywords); } // Match members only if the term is long enough, to avoid slow performance from building a large tree. @@ -493,36 +494,56 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { } if (search_flags & SEARCH_SIGNALS) { for (int i = 0; i < class_doc->signals.size(); i++) { - if (_all_terms_in_name(class_doc->signals[i].name) || _all_terms_in_keywords(class_doc->signals[i].keywords)) { - match.signals.push_back(const_cast<DocData::MethodDoc *>(&class_doc->signals[i])); + MemberMatch<DocData::MethodDoc> signal; + signal.name = _all_terms_in_name(class_doc->signals[i].name); + signal.keyword = _match_keywords_in_all_terms(class_doc->signals[i].keywords); + if (signal.name || !signal.keyword.is_empty()) { + signal.doc = const_cast<DocData::MethodDoc *>(&class_doc->signals[i]); + match.signals.push_back(signal); } } } if (search_flags & SEARCH_CONSTANTS) { for (int i = 0; i < class_doc->constants.size(); i++) { - if (_all_terms_in_name(class_doc->constants[i].name) || _all_terms_in_keywords(class_doc->constants[i].keywords)) { - match.constants.push_back(const_cast<DocData::ConstantDoc *>(&class_doc->constants[i])); + MemberMatch<DocData::ConstantDoc> constant; + constant.name = _all_terms_in_name(class_doc->constants[i].name); + constant.keyword = _match_keywords_in_all_terms(class_doc->constants[i].keywords); + if (constant.name || !constant.keyword.is_empty()) { + constant.doc = const_cast<DocData::ConstantDoc *>(&class_doc->constants[i]); + match.constants.push_back(constant); } } } if (search_flags & SEARCH_PROPERTIES) { for (int i = 0; i < class_doc->properties.size(); i++) { - if (_all_terms_in_name(class_doc->properties[i].name) || _all_terms_in_keywords(class_doc->properties[i].keywords)) { - match.properties.push_back(const_cast<DocData::PropertyDoc *>(&class_doc->properties[i])); + MemberMatch<DocData::PropertyDoc> property; + property.name = _all_terms_in_name(class_doc->properties[i].name); + property.keyword = _match_keywords_in_all_terms(class_doc->properties[i].keywords); + if (property.name || !property.keyword.is_empty()) { + property.doc = const_cast<DocData::PropertyDoc *>(&class_doc->properties[i]); + match.properties.push_back(property); } } } if (search_flags & SEARCH_THEME_ITEMS) { for (int i = 0; i < class_doc->theme_properties.size(); i++) { - if (_all_terms_in_name(class_doc->theme_properties[i].name) || _all_terms_in_keywords(class_doc->theme_properties[i].keywords)) { - match.theme_properties.push_back(const_cast<DocData::ThemeItemDoc *>(&class_doc->theme_properties[i])); + MemberMatch<DocData::ThemeItemDoc> theme_property; + theme_property.name = _all_terms_in_name(class_doc->theme_properties[i].name); + theme_property.keyword = _match_keywords_in_all_terms(class_doc->theme_properties[i].keywords); + if (theme_property.name || !theme_property.keyword.is_empty()) { + theme_property.doc = const_cast<DocData::ThemeItemDoc *>(&class_doc->theme_properties[i]); + match.theme_properties.push_back(theme_property); } } } if (search_flags & SEARCH_ANNOTATIONS) { for (int i = 0; i < class_doc->annotations.size(); i++) { - if (_match_string(term, class_doc->annotations[i].name) || _all_terms_in_keywords(class_doc->annotations[i].keywords)) { - match.annotations.push_back(const_cast<DocData::MethodDoc *>(&class_doc->annotations[i])); + MemberMatch<DocData::MethodDoc> annotation; + annotation.name = _all_terms_in_name(class_doc->annotations[i].name); + annotation.keyword = _match_keywords_in_all_terms(class_doc->annotations[i].keywords); + if (annotation.name || !annotation.keyword.is_empty()) { + annotation.doc = const_cast<DocData::MethodDoc *>(&class_doc->annotations[i]); + match.annotations.push_back(annotation); } } } @@ -606,8 +627,8 @@ bool EditorHelpSearch::Runner::_phase_class_items() { _create_class_hierarchy(match); } } else { - if (match.name) { - _create_class_item(root_item, match.doc, false); + if (match.name || !match.keyword.is_empty()) { + _create_class_item(root_item, match.doc, false, match.name ? String() : match.keyword); } } @@ -636,14 +657,14 @@ bool EditorHelpSearch::Runner::_phase_member_items() { TreeItem *parent_item = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item; bool constructor_created = false; for (int i = 0; i < match.methods.size(); i++) { - String text = match.methods[i]->name; + String text = match.methods[i].doc->name; if (!constructor_created) { - if (match.doc->name == match.methods[i]->name) { + if (match.doc->name == match.methods[i].doc->name) { text += " " + TTR("(constructors)"); constructor_created = true; } } else { - if (match.doc->name == match.methods[i]->name) { + if (match.doc->name == match.methods[i].doc->name) { continue; } } @@ -662,8 +683,7 @@ bool EditorHelpSearch::Runner::_phase_member_items() { _create_theme_property_item(parent_item, match.doc, match.theme_properties[i]); } for (int i = 0; i < match.annotations.size(); i++) { - // Hide the redundant leading @ symbol. - _create_annotation_item(parent_item, match.doc, match.annotations[i]->name.substr(1), match.annotations[i]); + _create_annotation_item(parent_item, match.doc, match.annotations[i]); } ++iterator_match; @@ -677,16 +697,20 @@ bool EditorHelpSearch::Runner::_phase_select_match() { return true; } -void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<DocData::MethodDoc *> *r_match_methods) { +void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods) { // Constructors, Methods, Operators... for (int i = 0; i < p_methods.size(); i++) { String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? p_methods[i].name : p_methods[i].name.to_lower(); String keywords = (search_flags & SEARCH_CASE_SENSITIVE) ? p_methods[i].keywords : p_methods[i].keywords.to_lower(); - if (_all_terms_in_name(method_name) || _all_terms_in_keywords(keywords) || + MemberMatch<DocData::MethodDoc> method; + method.name = _all_terms_in_name(method_name); + method.keyword = _match_keywords_in_all_terms(keywords); + if (method.name || !method.keyword.is_empty() || (term.begins_with(".") && method_name.begins_with(term.substr(1))) || (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { - r_match_methods->push_back(const_cast<DocData::MethodDoc *>(&p_methods[i])); + method.doc = const_cast<DocData::MethodDoc *>(&p_methods[i]); + r_match_methods->push_back(method); } } } @@ -700,13 +724,15 @@ bool EditorHelpSearch::Runner::_all_terms_in_name(const String &p_name) const { return true; } -bool EditorHelpSearch::Runner::_all_terms_in_keywords(const String &p_keywords) const { - for (const String &keyword : p_keywords.split(",")) { - if (_all_terms_in_name(keyword.strip_edges())) { - return true; +String EditorHelpSearch::Runner::_match_keywords_in_all_terms(const String &p_keywords) const { + String matching_keyword; + for (int i = 0; i < terms.size(); i++) { + matching_keyword = _match_keywords(terms[i], p_keywords); + if (matching_keyword.is_empty()) { + return String(); } } - return false; + return matching_keyword; } bool EditorHelpSearch::Runner::_match_string(const String &p_term, const String &p_string) const { @@ -717,13 +743,14 @@ bool EditorHelpSearch::Runner::_match_string(const String &p_term, const String } } -bool EditorHelpSearch::Runner::_match_keywords(const String &p_term, const String &p_keywords) const { - for (const String &keyword : p_keywords.split(",")) { - if (_match_string(p_term, keyword.strip_edges())) { - return true; +String EditorHelpSearch::Runner::_match_keywords(const String &p_term, const String &p_keywords) const { + for (const String &k : p_keywords.split(",")) { + const String keyword = k.strip_edges(); + if (_match_string(p_term, keyword)) { + return keyword; } } - return false; + return String(); } void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_text, bool p_is_keywords) { @@ -766,9 +793,26 @@ String EditorHelpSearch::Runner::_build_method_tooltip(const DocData::ClassDoc * } } tooltip += ")"; + tooltip += _build_keywords_tooltip(p_doc->keywords); return tooltip; } +String EditorHelpSearch::Runner::_build_keywords_tooltip(const String &p_keywords) const { + String tooltip; + if (p_keywords.is_empty()) { + return tooltip; + } + + tooltip = "\n\n" + TTR("Keywords") + ": "; + + for (const String &keyword : p_keywords.split(",")) { + tooltip += keyword.strip_edges().quote() + ", "; + } + + // Remove trailing comma and space. + return tooltip.left(-2); +} + TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const ClassMatch &p_match) { if (p_match.doc->name.is_empty()) { return nullptr; @@ -790,7 +834,7 @@ TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const ClassMatch &p_ } } - TreeItem *class_item = _create_class_item(parent_item, p_match.doc, !p_match.name); + TreeItem *class_item = _create_class_item(parent_item, p_match.doc, !p_match.name && p_match.keyword.is_empty(), p_match.name ? String() : p_match.keyword); class_items[p_match.doc->name] = class_item; return class_item; } @@ -815,15 +859,15 @@ bool EditorHelpSearch::Runner::_find_or_create_item(TreeItem *p_parent, const St } } -TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray) { +TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray, const String &p_matching_keyword) { String tooltip = DTR(p_doc->brief_description.strip_edges()); + tooltip += _build_keywords_tooltip(p_doc->keywords); const String item_meta = "class_name:" + p_doc->name; TreeItem *item = nullptr; if (_find_or_create_item(p_parent, item_meta, item)) { item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_doc->name)); - item->set_text(0, p_doc->name); item->set_text(1, TTR("Class")); item->set_tooltip_text(0, tooltip); item->set_tooltip_text(1, tooltip); @@ -845,6 +889,12 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const item->clear_custom_color(1); } + if (p_matching_keyword.is_empty()) { + item->set_text(0, p_doc->name); + } else { + item->set_text(0, p_doc->name + " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword))); + } + _match_item(item, p_doc->name); for (const String &keyword : p_doc->keywords.split(",")) { _match_item(item, keyword.strip_edges(), true); @@ -853,39 +903,44 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const return item; } -TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc) { - String tooltip = _build_method_tooltip(p_class_doc, p_doc); - return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_doc->name, p_text, TTRC("Method"), "method", tooltip, p_doc->keywords, p_doc->is_deprecated, p_doc->is_experimental); +TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match) { + String tooltip = _build_method_tooltip(p_class_doc, p_match.doc); + return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_match.doc->name, p_text, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } -TreeItem *EditorHelpSearch::Runner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) { - String tooltip = _build_method_tooltip(p_class_doc, p_doc); - return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_doc->name, p_doc->name, TTRC("Signal"), "signal", tooltip, p_doc->keywords, p_doc->is_deprecated, p_doc->is_experimental); +TreeItem *EditorHelpSearch::Runner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) { + String tooltip = _build_method_tooltip(p_class_doc, p_match.doc); + return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } -TreeItem *EditorHelpSearch::Runner::_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc) { - String tooltip = _build_method_tooltip(p_class_doc, p_doc); - return _create_member_item(p_parent, p_class_doc->name, "MemberAnnotation", p_doc->name, p_text, TTRC("Annotation"), "annotation", tooltip, p_doc->keywords, p_doc->is_deprecated, p_doc->is_experimental); +TreeItem *EditorHelpSearch::Runner::_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) { + String tooltip = _build_method_tooltip(p_class_doc, p_match.doc); + // Hide the redundant leading @ symbol. + String text = p_match.doc->name.substr(1); + return _create_member_item(p_parent, p_class_doc->name, "MemberAnnotation", p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } -TreeItem *EditorHelpSearch::Runner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc) { - String tooltip = p_class_doc->name + "." + p_doc->name; - return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_doc->name, p_doc->name, TTRC("Constant"), "constant", tooltip, p_doc->keywords, p_doc->is_deprecated, p_doc->is_experimental); +TreeItem *EditorHelpSearch::Runner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match) { + String tooltip = p_class_doc->name + "." + p_match.doc->name; + tooltip += _build_keywords_tooltip(p_match.doc->keywords); + return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } -TreeItem *EditorHelpSearch::Runner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc) { - String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; - tooltip += "\n " + p_class_doc->name + "." + p_doc->setter + "(value) setter"; - tooltip += "\n " + p_class_doc->name + "." + p_doc->getter + "() getter"; - return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_doc->name, p_doc->name, TTRC("Property"), "property", tooltip, p_doc->keywords, p_doc->is_deprecated, p_doc->is_experimental); +TreeItem *EditorHelpSearch::Runner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match) { + String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name; + tooltip += "\n " + p_class_doc->name + "." + p_match.doc->setter + "(value) setter"; + tooltip += "\n " + p_class_doc->name + "." + p_match.doc->getter + "() getter"; + tooltip += _build_keywords_tooltip(p_match.doc->keywords); + return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } -TreeItem *EditorHelpSearch::Runner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc) { - String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; - return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_doc->name, p_doc->name, TTRC("Theme Property"), "theme_item", p_doc->keywords, tooltip, false, false); +TreeItem *EditorHelpSearch::Runner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match) { + String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name; + tooltip += _build_keywords_tooltip(p_match.doc->keywords); + return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, false, false, p_match.name ? String() : p_match.keyword); } -TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool is_deprecated, bool is_experimental) { +TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) { const String item_meta = "class_" + p_metatype + ":" + p_class_name + ":" + p_name; TreeItem *item = nullptr; @@ -896,20 +951,25 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons item->set_tooltip_text(1, p_tooltip); item->set_metadata(0, item_meta); - if (is_deprecated) { + if (p_is_deprecated) { Ref<Texture2D> error_icon = ui_service->get_editor_theme_icon(SNAME("StatusError")); item->add_button(0, error_icon, 0, false, TTR("This member is marked as deprecated.")); - } else if (is_experimental) { + } else if (p_is_experimental) { Ref<Texture2D> warning_icon = ui_service->get_editor_theme_icon(SNAME("NodeWarning")); item->add_button(0, warning_icon, 0, false, TTR("This member is marked as experimental.")); } } + String text; if (search_flags & SEARCH_SHOW_HIERARCHY) { - item->set_text(0, p_text); + text = p_text; } else { - item->set_text(0, p_class_name + "." + p_text); + text = p_class_name + "." + p_text; + } + if (!p_matching_keyword.is_empty()) { + text += " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword)); } + item->set_text(0, text); _match_item(item, p_name); for (const String &keyword : p_keywords.split(",")) { diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h index 1982da04e8..003b98adf0 100644 --- a/editor/editor_help_search.h +++ b/editor/editor_help_search.h @@ -117,20 +117,28 @@ class EditorHelpSearch::Runner : public RefCounted { }; int phase = 0; + template <class T> + struct MemberMatch { + T *doc = nullptr; + bool name = false; + String keyword; + }; + struct ClassMatch { DocData::ClassDoc *doc = nullptr; bool name = false; - Vector<DocData::MethodDoc *> constructors; - Vector<DocData::MethodDoc *> methods; - Vector<DocData::MethodDoc *> operators; - Vector<DocData::MethodDoc *> signals; - Vector<DocData::ConstantDoc *> constants; - Vector<DocData::PropertyDoc *> properties; - Vector<DocData::ThemeItemDoc *> theme_properties; - Vector<DocData::MethodDoc *> annotations; + String keyword; + Vector<MemberMatch<DocData::MethodDoc>> constructors; + Vector<MemberMatch<DocData::MethodDoc>> methods; + Vector<MemberMatch<DocData::MethodDoc>> operators; + Vector<MemberMatch<DocData::MethodDoc>> signals; + Vector<MemberMatch<DocData::ConstantDoc>> constants; + Vector<MemberMatch<DocData::PropertyDoc>> properties; + Vector<MemberMatch<DocData::ThemeItemDoc>> theme_properties; + Vector<MemberMatch<DocData::MethodDoc>> annotations; bool required() { - return name || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size() || annotations.size(); + return name || !keyword.is_empty() || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size() || annotations.size(); } }; @@ -167,22 +175,23 @@ class EditorHelpSearch::Runner : public RefCounted { bool _phase_select_match(); String _build_method_tooltip(const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) const; + String _build_keywords_tooltip(const String &p_keywords) const; - void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<DocData::MethodDoc *> *r_match_methods); + void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods); bool _all_terms_in_name(const String &p_name) const; - bool _all_terms_in_keywords(const String &p_name) const; + String _match_keywords_in_all_terms(const String &p_keywords) const; bool _match_string(const String &p_term, const String &p_string) const; - bool _match_keywords(const String &p_term, const String &p_keywords) const; + String _match_keywords(const String &p_term, const String &p_keywords) const; void _match_item(TreeItem *p_item, const String &p_text, bool p_is_keywords = false); TreeItem *_create_class_hierarchy(const ClassMatch &p_match); - TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray); - TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc); - TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc); - TreeItem *_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc); - TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc); - TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc); - TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc); - TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool is_deprecated, bool is_experimental); + TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray, const String &p_matching_keyword); + TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match); + TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match); + TreeItem *_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match); + TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match); + TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match); + TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match); + TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword); public: bool work(uint64_t slot = 100000); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 4e76d40b1d..583a52ed52 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -214,22 +214,6 @@ void EditorProperty::_notification(int p_what) { text_size -= close->get_width() + 4 * EDSCALE; } } - - if (!configuration_warning.is_empty() && !read_only) { - Ref<Texture2D> warning; - - warning = get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons")); - - rect.size.x -= warning->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); - - if (is_layout_rtl()) { - rect.position.x += warning->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); - } - - if (no_children) { - text_size -= warning->get_width() + 4 * EDSCALE; - } - } } //set children @@ -415,38 +399,6 @@ void EditorProperty::_notification(int p_what) { } else { delete_rect = Rect2(); } - - if (!configuration_warning.is_empty() && !read_only) { - Ref<Texture2D> warning; - - StringName warning_icon; - Node *node = Object::cast_to<Node>(object); - if (node) { - const int warning_num = node->get_configuration_warnings_of_property(property_path).size(); - warning_icon = Node::get_configuration_warning_icon(warning_num); - } else { - // This shouldn't happen, but let's not crash over an icon. - warning_icon = "NodeWarning"; - } - warning = get_theme_icon(warning_icon, SNAME("EditorIcons")); - - ofs -= warning->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); - - Color color2(1, 1, 1); - if (configuration_warning_hover) { - color2.r *= 1.2; - color2.g *= 1.2; - color2.b *= 1.2; - } - configuration_warning_rect = Rect2(ofs, ((size.height - warning->get_height()) / 2), warning->get_width(), warning->get_height()); - if (rtl) { - draw_texture(warning, Vector2(size.width - configuration_warning_rect.position.x - warning->get_width(), configuration_warning_rect.position.y), color2); - } else { - draw_texture(warning, configuration_warning_rect.position, color2); - } - } else { - configuration_warning_rect = Rect2(); - } } break; } } @@ -722,12 +674,6 @@ void EditorProperty::gui_input(const Ref<InputEvent> &p_event) { check_hover = new_check_hover; queue_redraw(); } - - bool new_configuration_warning_hover = configuration_warning_rect.has_point(mpos) && !button_left; - if (new_configuration_warning_hover != configuration_warning_hover) { - configuration_warning_hover = new_configuration_warning_hover; - queue_redraw(); - } } Ref<InputEventMouseButton> mb = p_event; @@ -784,16 +730,6 @@ void EditorProperty::gui_input(const Ref<InputEvent> &p_event) { queue_redraw(); emit_signal(SNAME("property_checked"), property, checked); } - - if (configuration_warning_rect.has_point(mpos)) { - if (warning_dialog == nullptr) { - warning_dialog = memnew(AcceptDialog); - add_child(warning_dialog); - warning_dialog->set_title(TTR("Node Configuration Warning!")); - } - warning_dialog->set_text(configuration_warning); - warning_dialog->popup_centered(); - } } else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { accept_event(); _update_popup(); @@ -919,16 +855,6 @@ float EditorProperty::get_name_split_ratio() const { return split_ratio; } -void EditorProperty::set_configuration_warning(const String &p_configuration_warning) { - configuration_warning = p_configuration_warning; - queue_redraw(); - queue_sort(); -} - -String EditorProperty::get_configuration_warning() const { - return configuration_warning; -} - void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) { object = p_object; property = p_property; @@ -985,15 +911,6 @@ void EditorProperty::_update_pin_flags() { } } -void EditorProperty::_update_configuration_warnings() { - Node *node = Object::cast_to<Node>(object); - if (node) { - const PackedStringArray warnings = node->get_configuration_warnings_as_strings(true, property_path); - const String warning_lines = String("\n").join(warnings); - set_configuration_warning(warning_lines); - } -} - Control *EditorProperty::make_custom_tooltip(const String &p_text) const { EditorHelpBit *tooltip = nullptr; @@ -1069,9 +986,6 @@ void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("set_deletable", "deletable"), &EditorProperty::set_deletable); ClassDB::bind_method(D_METHOD("is_deletable"), &EditorProperty::is_deletable); - ClassDB::bind_method(D_METHOD("set_configuration_warning", "configuration_warning"), &EditorProperty::set_configuration_warning); - ClassDB::bind_method(D_METHOD("get_configuration_warning"), &EditorProperty::get_configuration_warning); - ClassDB::bind_method(D_METHOD("get_edited_property"), &EditorProperty::get_edited_property); ClassDB::bind_method(D_METHOD("get_edited_object"), &EditorProperty::get_edited_object); @@ -1089,7 +1003,6 @@ void EditorProperty::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_warning"), "set_draw_warning", "is_draw_warning"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying"), "set_keying", "is_keying"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deletable"), "set_deletable", "is_deletable"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "configuration_warning"), "set_configuration_warning", "get_configuration_warning"); ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::STRING_NAME, "field"), PropertyInfo(Variant::BOOL, "changing"))); ADD_SIGNAL(MethodInfo("multiple_properties_changed", PropertyInfo(Variant::PACKED_STRING_ARRAY, "properties"), PropertyInfo(Variant::ARRAY, "value"))); @@ -3409,7 +3322,6 @@ void EditorInspector::update_tree() { ep->set_keying(keying); ep->set_read_only(property_read_only || all_read_only); ep->set_deletable(deletable_properties || p.name.begins_with("metadata/")); - ep->_update_configuration_warnings(); } current_vbox->add_child(editors[i].property_editor); @@ -3535,9 +3447,6 @@ void EditorInspector::edit(Object *p_object) { object = p_object; - property_configuration_warnings.clear(); - _update_configuration_warnings(); - if (object) { update_scroll_request = 0; //reset if (scroll_cache.has(object->get_instance_id())) { //if exists, set something else @@ -4059,52 +3968,6 @@ void EditorInspector::_node_removed(Node *p_node) { } } -void EditorInspector::_warning_changed(Node *p_node) { - if (p_node == object) { - // Only update the tree if the list of configuration warnings has changed. - if (_update_configuration_warnings()) { - update_tree_pending = true; - } - } -} - -bool EditorInspector::_update_configuration_warnings() { - Node *node = Object::cast_to<Node>(object); - if (!node) { - return false; - } - - bool changed = false; - LocalVector<int> found_warning_indices; - - // New and changed warnings. - Vector<Dictionary> warnings = node->get_configuration_warnings_as_dicts(); - for (const Dictionary &warning : warnings) { - if (!warning.has("property")) { - continue; - } - - int found_warning_index = property_configuration_warnings.find(warning); - if (found_warning_index < 0) { - found_warning_index = property_configuration_warnings.size(); - property_configuration_warnings.push_back(warning); - changed = true; - } - found_warning_indices.push_back(found_warning_index); - } - - // Removed warnings. - for (uint32_t i = 0; i < property_configuration_warnings.size(); i++) { - if (found_warning_indices.find(i) < 0) { - property_configuration_warnings.remove_at(i); - i--; - changed = true; - } - } - - return changed; -} - void EditorInspector::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { @@ -4116,7 +3979,6 @@ void EditorInspector::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { if (!sub_inspector) { get_tree()->connect("node_removed", callable_mp(this, &EditorInspector::_node_removed)); - get_tree()->connect("node_configuration_warning_changed", callable_mp(this, &EditorInspector::_warning_changed)); } } break; @@ -4127,7 +3989,6 @@ void EditorInspector::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { if (!sub_inspector) { get_tree()->disconnect("node_removed", callable_mp(this, &EditorInspector::_node_removed)); - get_tree()->disconnect("node_configuration_warning_changed", callable_mp(this, &EditorInspector::_warning_changed)); } edit(nullptr); } break; diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 4e937ae4ad..0e908b7a14 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -76,7 +76,6 @@ private: String doc_path; bool internal = false; bool has_doc_tooltip = false; - AcceptDialog *warning_dialog = nullptr; int property_usage; @@ -99,8 +98,6 @@ private: bool check_hover = false; Rect2 delete_rect; bool delete_hover = false; - Rect2 configuration_warning_rect; - bool configuration_warning_hover = false; bool can_revert = false; bool can_pin = false; @@ -124,15 +121,12 @@ private: Control *bottom_editor = nullptr; PopupMenu *menu = nullptr; - String configuration_warning; - HashMap<StringName, Variant> cache; GDVIRTUAL0(_update_property) GDVIRTUAL1(_set_read_only, bool) void _update_pin_flags(); - void _update_configuration_warnings(); protected: void _notification(int p_what); @@ -209,9 +203,6 @@ public: void set_name_split_ratio(float p_ratio); float get_name_split_ratio() const; - void set_configuration_warning(const String &p_configuration_warning); - String get_configuration_warning() const; - void set_object_and_property(Object *p_object, const StringName &p_property); virtual Control *make_custom_tooltip(const String &p_text) const override; @@ -514,7 +505,6 @@ class EditorInspector : public ScrollContainer { int property_focusable; int update_scroll_request; - LocalVector<Dictionary> property_configuration_warnings; HashMap<StringName, HashMap<StringName, String>> doc_path_cache; HashSet<StringName> restart_request_props; HashMap<String, String> custom_property_descriptions; @@ -543,8 +533,6 @@ class EditorInspector : public ScrollContainer { void _object_id_selected(const String &p_path, ObjectID p_id); void _node_removed(Node *p_node); - void _warning_changed(Node *p_node); - bool _update_configuration_warnings(); HashMap<StringName, int> per_array_page; void _page_change_request(int p_new_page, const StringName &p_array_prefix); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index dddd7345c8..c0d48609da 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -47,6 +47,7 @@ #include "editor/editor_string_names.h" #include "main/main.h" #include "scene/3d/bone_attachment_3d.h" +#include "scene/animation/animation_tree.h" #include "scene/gui/color_picker.h" #include "scene/gui/dialogs.h" #include "scene/gui/file_dialog.h" @@ -1739,7 +1740,14 @@ int EditorNode::_save_external_resources() { static void _reset_animation_mixers(Node *p_node, List<Pair<AnimationMixer *, Ref<AnimatedValuesBackup>>> *r_anim_backups) { for (int i = 0; i < p_node->get_child_count(); i++) { AnimationMixer *mixer = Object::cast_to<AnimationMixer>(p_node->get_child(i)); - if (mixer && mixer->is_reset_on_save_enabled() && mixer->can_apply_reset()) { + if (mixer && mixer->is_active() && mixer->is_reset_on_save_enabled() && mixer->can_apply_reset()) { + AnimationTree *tree = Object::cast_to<AnimationTree>(p_node->get_child(i)); + if (tree) { + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(tree->get_node_or_null(tree->get_animation_player())); + if (player && player->is_active() && player->is_reset_on_save_enabled() && player->can_apply_reset()) { + continue; // Avoid to process reset/restore many times. + } + } Ref<AnimatedValuesBackup> backup = mixer->apply_reset(); if (backup.is_valid()) { Pair<AnimationMixer *, Ref<AnimatedValuesBackup>> pair; @@ -5077,8 +5085,8 @@ void EditorNode::_update_layouts_menu() { overridden_default_layout = -1; editor_layouts->reset_size(); - editor_layouts->add_shortcut(ED_SHORTCUT("layout/save", TTR("Save Layout")), SETTINGS_LAYOUT_SAVE); - editor_layouts->add_shortcut(ED_SHORTCUT("layout/delete", TTR("Delete Layout")), SETTINGS_LAYOUT_DELETE); + editor_layouts->add_shortcut(ED_SHORTCUT("layout/save", TTR("Save Layout...")), SETTINGS_LAYOUT_SAVE); + editor_layouts->add_shortcut(ED_SHORTCUT("layout/delete", TTR("Delete Layout...")), SETTINGS_LAYOUT_DELETE); editor_layouts->add_separator(); editor_layouts->add_shortcut(ED_SHORTCUT("layout/default", TTR("Default")), SETTINGS_LAYOUT_DEFAULT); @@ -6967,7 +6975,7 @@ EditorNode::EditorNode() { help_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); - ED_SHORTCUT_AND_COMMAND("editor/editor_help", TTR("Search Help"), Key::F1); + ED_SHORTCUT_AND_COMMAND("editor/editor_help", TTR("Search Help..."), Key::F1); ED_SHORTCUT_OVERRIDE("editor/editor_help", "macos", KeyModifierMask::ALT | Key::SPACE); help_menu->add_icon_shortcut(theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH); help_menu->add_separator(); @@ -6983,7 +6991,7 @@ EditorNode::EditorNode() { help_menu->add_separator(); if (!global_menu || !OS::get_singleton()->has_feature("macos")) { // On macOS "Quit" and "About" options are in the "app" menu. - help_menu->add_icon_shortcut(theme->get_icon(SNAME("Godot"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT); + help_menu->add_icon_shortcut(theme->get_icon(SNAME("Godot"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot...")), HELP_ABOUT); } help_menu->add_icon_shortcut(theme->get_icon(SNAME("Heart"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT); diff --git a/editor/editor_plugin_settings.cpp b/editor/editor_plugin_settings.cpp index d22e60d7b4..7d0024b1da 100644 --- a/editor/editor_plugin_settings.cpp +++ b/editor/editor_plugin_settings.cpp @@ -36,6 +36,7 @@ #include "core/io/file_access.h" #include "core/os/main_loop.h" #include "editor/editor_node.h" +#include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" #include "scene/gui/margin_container.h" #include "scene/gui/tree.h" @@ -62,44 +63,33 @@ void EditorPluginSettings::update_plugins() { plugins.sort(); for (int i = 0; i < plugins.size(); i++) { - Ref<ConfigFile> cf; - cf.instantiate(); + Ref<ConfigFile> cfg; + cfg.instantiate(); const String &path = plugins[i]; - Error err2 = cf->load(path); + Error err = cfg->load(path); - if (err2 != OK) { - WARN_PRINT("Can't load plugin config: " + path); + if (err != OK) { + WARN_PRINT("Can't load plugin config at: " + path); } else { - bool key_missing = false; - - if (!cf->has_section_key("plugin", "name")) { - WARN_PRINT("Plugin config misses \"plugin/name\" key: " + path); - key_missing = true; - } - if (!cf->has_section_key("plugin", "author")) { - WARN_PRINT("Plugin config misses \"plugin/author\" key: " + path); - key_missing = true; - } - if (!cf->has_section_key("plugin", "version")) { - WARN_PRINT("Plugin config misses \"plugin/version\" key: " + path); - key_missing = true; - } - if (!cf->has_section_key("plugin", "description")) { - WARN_PRINT("Plugin config misses \"plugin/description\" key: " + path); - key_missing = true; - } - if (!cf->has_section_key("plugin", "script")) { - WARN_PRINT("Plugin config misses \"plugin/script\" key: " + path); - key_missing = true; + Vector<String> missing_keys; + for (const String required_key : { "name", "author", "version", "description", "script" }) { + if (!cfg->has_section_key("plugin", required_key)) { + missing_keys.append("\"plugin/" + required_key + "\""); + } } - if (!key_missing) { - String name = cf->get_value("plugin", "name"); - String author = cf->get_value("plugin", "author"); - String version = cf->get_value("plugin", "version"); - String description = cf->get_value("plugin", "description"); - String scr = cf->get_value("plugin", "script"); + if (!missing_keys.is_empty()) { + WARN_PRINT(vformat("Plugin config at \"%s\" is missing the following keys: %s", path, String(",").join(missing_keys))); + } else { + String name = cfg->get_value("plugin", "name"); + String author = cfg->get_value("plugin", "author"); + String version = cfg->get_value("plugin", "version"); + String description = cfg->get_value("plugin", "description"); + String scr = cfg->get_value("plugin", "script"); + + bool is_enabled = EditorNode::get_singleton()->is_addon_plugin_enabled(path); + Color disabled_color = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)); const PackedInt32Array boundaries = TS->string_get_word_breaks(description, "", 80); String wrapped_description; @@ -111,19 +101,22 @@ void EditorPluginSettings::update_plugins() { } TreeItem *item = plugin_list->create_item(root); - item->set_text(0, name); - item->set_tooltip_text(0, TTR("Name:") + " " + name + "\n" + TTR("Path:") + " " + path + "\n" + TTR("Main Script:") + " " + scr + "\n" + TTR("Description:") + " " + wrapped_description); - item->set_metadata(0, path); - item->set_text(1, version); - item->set_metadata(1, scr); - item->set_text(2, author); - item->set_metadata(2, description); - item->set_cell_mode(3, TreeItem::CELL_MODE_CHECK); - item->set_text(3, TTR("Enable")); - bool is_active = EditorNode::get_singleton()->is_addon_plugin_enabled(path); - item->set_checked(3, is_active); - item->set_editable(3, true); - item->add_button(4, get_editor_theme_icon(SNAME("Edit")), BUTTON_PLUGIN_EDIT, false, TTR("Edit Plugin")); + item->set_text(COLUMN_NAME, name); + if (!is_enabled) { + item->set_custom_color(COLUMN_NAME, disabled_color); + } + item->set_tooltip_text(COLUMN_NAME, vformat(TTR("Name: %s\nPath: %s\nMain Script: %s\n\n%s"), name, path, scr, wrapped_description)); + item->set_metadata(COLUMN_NAME, path); + item->set_text(COLUMN_VERSION, version); + item->set_custom_font(COLUMN_VERSION, get_theme_font("source", EditorStringName(EditorFonts))); + item->set_metadata(COLUMN_VERSION, scr); + item->set_text(COLUMN_AUTHOR, author); + item->set_metadata(COLUMN_AUTHOR, description); + item->set_cell_mode(COLUMN_STATUS, TreeItem::CELL_MODE_CHECK); + item->set_text(COLUMN_STATUS, TTR("On")); + item->set_checked(COLUMN_STATUS, is_enabled); + item->set_editable(COLUMN_STATUS, true); + item->add_button(COLUMN_EDIT, get_editor_theme_icon(SNAME("Edit")), BUTTON_PLUGIN_EDIT, false, TTR("Edit Plugin")); } } } @@ -138,18 +131,19 @@ void EditorPluginSettings::_plugin_activity_changed() { TreeItem *ti = plugin_list->get_edited(); ERR_FAIL_NULL(ti); - bool active = ti->is_checked(3); - String name = ti->get_metadata(0); + bool checked = ti->is_checked(COLUMN_STATUS); + String name = ti->get_metadata(COLUMN_NAME); - EditorNode::get_singleton()->set_addon_plugin_enabled(name, active, true); + EditorNode::get_singleton()->set_addon_plugin_enabled(name, checked, true); - bool is_active = EditorNode::get_singleton()->is_addon_plugin_enabled(name); + bool is_enabled = EditorNode::get_singleton()->is_addon_plugin_enabled(name); - if (is_active != active) { + if (is_enabled != checked) { updating = true; - ti->set_checked(3, is_active); + ti->set_checked(COLUMN_STATUS, is_enabled); updating = false; } + ti->set_custom_color(COLUMN_NAME, is_enabled ? Color() : get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))); } void EditorPluginSettings::_create_clicked() { @@ -166,8 +160,8 @@ void EditorPluginSettings::_cell_button_pressed(Object *p_item, int p_column, in return; } if (p_id == BUTTON_PLUGIN_EDIT) { - if (p_column == 4) { - String dir = item->get_metadata(0); + if (p_column == COLUMN_EDIT) { + String dir = item->get_metadata(COLUMN_NAME); plugin_config_dialog->config(dir); plugin_config_dialog->popup_centered(); } @@ -212,38 +206,47 @@ EditorPluginSettings::EditorPluginSettings() { add_child(plugin_config_dialog); HBoxContainer *title_hb = memnew(HBoxContainer); - Label *l = memnew(Label(TTR("Installed Plugins:"))); - l->set_theme_type_variation("HeaderSmall"); - title_hb->add_child(l); + Label *label = memnew(Label(TTR("Installed Plugins:"))); + label->set_theme_type_variation("HeaderSmall"); + title_hb->add_child(label); title_hb->add_spacer(); - Button *create_plugin = memnew(Button(TTR("Create New Plugin"))); - create_plugin->connect("pressed", callable_mp(this, &EditorPluginSettings::_create_clicked)); - title_hb->add_child(create_plugin); + Button *create_plugin_button = memnew(Button(TTR("Create New Plugin"))); + create_plugin_button->connect("pressed", callable_mp(this, &EditorPluginSettings::_create_clicked)); + title_hb->add_child(create_plugin_button); add_child(title_hb); plugin_list = memnew(Tree); plugin_list->set_v_size_flags(SIZE_EXPAND_FILL); - plugin_list->set_columns(5); + plugin_list->set_columns(COLUMN_MAX); plugin_list->set_column_titles_visible(true); - plugin_list->set_column_title(0, TTR("Name")); - plugin_list->set_column_title(1, TTR("Version")); - plugin_list->set_column_title(2, TTR("Author")); - plugin_list->set_column_title(3, TTR("Status")); - plugin_list->set_column_title(4, TTR("Edit")); - plugin_list->set_column_expand(0, true); - plugin_list->set_column_clip_content(0, true); - plugin_list->set_column_expand(1, false); - plugin_list->set_column_clip_content(1, true); - plugin_list->set_column_expand(2, false); - plugin_list->set_column_clip_content(2, true); - plugin_list->set_column_expand(3, false); - plugin_list->set_column_clip_content(3, true); - plugin_list->set_column_expand(4, false); - plugin_list->set_column_clip_content(4, true); - plugin_list->set_column_custom_minimum_width(1, 100 * EDSCALE); - plugin_list->set_column_custom_minimum_width(2, 250 * EDSCALE); - plugin_list->set_column_custom_minimum_width(3, 80 * EDSCALE); - plugin_list->set_column_custom_minimum_width(4, 40 * EDSCALE); + plugin_list->set_column_title(COLUMN_STATUS, TTR("Enabled")); + plugin_list->set_column_title(COLUMN_NAME, TTR("Name")); + plugin_list->set_column_title(COLUMN_VERSION, TTR("Version")); + plugin_list->set_column_title(COLUMN_AUTHOR, TTR("Author")); + plugin_list->set_column_title(COLUMN_EDIT, TTR("Edit")); + plugin_list->set_column_title_alignment(COLUMN_STATUS, HORIZONTAL_ALIGNMENT_LEFT); + plugin_list->set_column_title_alignment(COLUMN_NAME, HORIZONTAL_ALIGNMENT_LEFT); + plugin_list->set_column_title_alignment(COLUMN_VERSION, HORIZONTAL_ALIGNMENT_LEFT); + plugin_list->set_column_title_alignment(COLUMN_AUTHOR, HORIZONTAL_ALIGNMENT_LEFT); + plugin_list->set_column_title_alignment(COLUMN_EDIT, HORIZONTAL_ALIGNMENT_LEFT); + plugin_list->set_column_expand(COLUMN_PADDING_LEFT, false); + plugin_list->set_column_expand(COLUMN_STATUS, false); + plugin_list->set_column_expand(COLUMN_NAME, true); + plugin_list->set_column_expand(COLUMN_VERSION, false); + plugin_list->set_column_expand(COLUMN_AUTHOR, false); + plugin_list->set_column_expand(COLUMN_EDIT, false); + plugin_list->set_column_expand(COLUMN_PADDING_RIGHT, false); + plugin_list->set_column_clip_content(COLUMN_STATUS, true); + plugin_list->set_column_clip_content(COLUMN_NAME, true); + plugin_list->set_column_clip_content(COLUMN_VERSION, true); + plugin_list->set_column_clip_content(COLUMN_AUTHOR, true); + plugin_list->set_column_clip_content(COLUMN_EDIT, true); + plugin_list->set_column_custom_minimum_width(COLUMN_PADDING_LEFT, 10 * EDSCALE); + plugin_list->set_column_custom_minimum_width(COLUMN_STATUS, 80 * EDSCALE); + plugin_list->set_column_custom_minimum_width(COLUMN_VERSION, 100 * EDSCALE); + plugin_list->set_column_custom_minimum_width(COLUMN_AUTHOR, 250 * EDSCALE); + plugin_list->set_column_custom_minimum_width(COLUMN_EDIT, 40 * EDSCALE); + plugin_list->set_column_custom_minimum_width(COLUMN_PADDING_RIGHT, 10 * EDSCALE); plugin_list->set_hide_root(true); plugin_list->connect("item_edited", callable_mp(this, &EditorPluginSettings::_plugin_activity_changed), CONNECT_DEFERRED); diff --git a/editor/editor_plugin_settings.h b/editor/editor_plugin_settings.h index 96ddad2a43..8db8ffd78c 100644 --- a/editor/editor_plugin_settings.h +++ b/editor/editor_plugin_settings.h @@ -43,6 +43,17 @@ class EditorPluginSettings : public VBoxContainer { BUTTON_PLUGIN_EDIT }; + enum { + COLUMN_PADDING_LEFT, + COLUMN_STATUS, + COLUMN_NAME, + COLUMN_VERSION, + COLUMN_AUTHOR, + COLUMN_EDIT, + COLUMN_PADDING_RIGHT, + COLUMN_MAX, + }; + PluginConfigDialog *plugin_config_dialog = nullptr; Tree *plugin_list = nullptr; bool updating = false; diff --git a/editor/editor_properties_vector.cpp b/editor/editor_properties_vector.cpp index 82cd703508..453675bebe 100644 --- a/editor/editor_properties_vector.cpp +++ b/editor/editor_properties_vector.cpp @@ -105,8 +105,6 @@ void EditorPropertyVectorN::_update_ratio() { if (spin_sliders[base_slider_idx]->get_value() != 0) { ratio_write[i] = spin_sliders[secondary_slider_idx]->get_value() / spin_sliders[base_slider_idx]->get_value(); - } else { - ratio_write[i] = 0; } } } diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index 74db32df19..95436427ad 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -194,10 +194,11 @@ void EditorResourcePicker::_update_menu_items() { set_create_options(edit_menu); // Add an option to load a resource from a file using the QuickOpen dialog. - edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Quick Load"), OBJ_MENU_QUICKLOAD); + edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Quick Load..."), OBJ_MENU_QUICKLOAD); + edit_menu->set_item_tooltip(-1, TTR("Opens a quick menu to select from a list of allowed Resource files.")); // Add an option to load a resource from a file using the regular file dialog. - edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Load"), OBJ_MENU_LOAD); + edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Load..."), OBJ_MENU_LOAD); } // Add options for changing existing value of the resource. @@ -1054,11 +1055,11 @@ void EditorScriptPicker::set_create_options(Object *p_menu_node) { return; } - menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script"), OBJ_MENU_NEW_SCRIPT); + menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT); if (script_owner) { Ref<Script> scr = script_owner->get_script(); if (scr.is_valid()) { - menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptExtend")), TTR("Extend Script"), OBJ_MENU_EXTEND_SCRIPT); + menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptExtend")), TTR("Extend Script..."), OBJ_MENU_EXTEND_SCRIPT); } } menu_node->add_separator(); @@ -1110,7 +1111,7 @@ void EditorShaderPicker::set_create_options(Object *p_menu_node) { return; } - menu_node->add_icon_item(get_editor_theme_icon(SNAME("Shader")), TTR("New Shader"), OBJ_MENU_NEW_SHADER); + menu_node->add_icon_item(get_editor_theme_icon(SNAME("Shader")), TTR("New Shader..."), OBJ_MENU_NEW_SHADER); menu_node->add_separator(); } diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 2e34685d75..0c1cce9f3e 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -47,6 +47,7 @@ void EditorExport::_save() { config->set_value(section, "name", preset->get_name()); config->set_value(section, "platform", preset->get_platform()->get_name()); config->set_value(section, "runnable", preset->is_runnable()); + config->set_value(section, "advanced_options", preset->are_advanced_options_enabled()); config->set_value(section, "dedicated_server", preset->is_dedicated_server()); config->set_value(section, "custom_features", preset->get_custom_features()); @@ -234,6 +235,7 @@ void EditorExport::load_config() { } preset->set_name(config->get_value(section, "name")); + preset->set_advanced_options_enabled(config->get_value(section, "advanced_options", false)); preset->set_runnable(config->get_value(section, "runnable")); preset->set_dedicated_server(config->get_value(section, "dedicated_server", false)); diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index 6fc2228bee..e2e3e9d154 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -229,6 +229,19 @@ bool EditorExportPreset::is_runnable() const { return runnable; } +void EditorExportPreset::set_advanced_options_enabled(bool p_enabled) { + if (advanced_options_enabled == p_enabled) { + return; + } + advanced_options_enabled = p_enabled; + EditorExport::singleton->save_presets(); + notify_property_list_changed(); +} + +bool EditorExportPreset::are_advanced_options_enabled() const { + return advanced_options_enabled; +} + void EditorExportPreset::set_dedicated_server(bool p_enable) { dedicated_server = p_enable; EditorExport::singleton->save_presets(); diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h index 03ff75b467..c6a8808af1 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -71,6 +71,7 @@ private: HashSet<String> selected_files; HashMap<String, FileExportMode> customized_files; bool runnable = false; + bool advanced_options_enabled = false; bool dedicated_server = false; friend class EditorExport; @@ -128,6 +129,9 @@ public: void set_runnable(bool p_enable); bool is_runnable() const; + void set_advanced_options_enabled(bool p_enabled); + bool are_advanced_options_enabled() const; + void set_dedicated_server(bool p_enable); bool is_dedicated_server() const; diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 5de56b9d90..7088d4e2ab 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -240,6 +240,7 @@ void ProjectExportDialog::_edit_preset(int p_index) { name->set_text(""); name->set_editable(false); export_path->hide(); + advanced_options->set_disabled(true); runnable->set_disabled(true); parameters->edit(nullptr); presets->deselect_all(); @@ -274,6 +275,8 @@ void ProjectExportDialog::_edit_preset(int p_index) { export_path->setup(extension_vector, false, true); export_path->update_property(); + advanced_options->set_disabled(false); + advanced_options->set_pressed(current->are_advanced_options_enabled()); runnable->set_disabled(false); runnable->set_pressed(current->is_runnable()); if (parameters->get_edited_object() != current.ptr()) { @@ -449,6 +452,18 @@ void ProjectExportDialog::_update_parameters(const String &p_edited_property) { _update_current_preset(); } +void ProjectExportDialog::_advanced_options_pressed() { + if (updating) { + return; + } + + Ref<EditorExportPreset> current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_advanced_options_enabled(advanced_options->is_pressed()); + _update_presets(); +} + void ProjectExportDialog::_runnable_pressed() { if (updating) { return; @@ -637,6 +652,7 @@ void ProjectExportDialog::_duplicate_preset() { if (make_runnable) { preset->set_runnable(make_runnable); } + preset->set_advanced_options_enabled(current->are_advanced_options_enabled()); preset->set_dedicated_server(current->is_dedicated_server()); preset->set_export_filter(current->get_export_filter()); preset->set_include_filter(current->get_include_filter()); @@ -1236,11 +1252,22 @@ ProjectExportDialog::ProjectExportDialog() { name = memnew(LineEdit); settings_vb->add_margin_child(TTR("Name:"), name); name->connect("text_changed", callable_mp(this, &ProjectExportDialog::_name_changed)); + runnable = memnew(CheckButton); runnable->set_text(TTR("Runnable")); runnable->set_tooltip_text(TTR("If checked, the preset will be available for use in one-click deploy.\nOnly one preset per platform may be marked as runnable.")); runnable->connect("pressed", callable_mp(this, &ProjectExportDialog::_runnable_pressed)); - settings_vb->add_child(runnable); + + advanced_options = memnew(CheckButton); + advanced_options->set_text(TTR("Advanced Options")); + advanced_options->set_tooltip_text(TTR("If checked, the advanced options will be shown.")); + advanced_options->connect("pressed", callable_mp(this, &ProjectExportDialog::_advanced_options_pressed)); + + HBoxContainer *preset_configs_container = memnew(HBoxContainer); + preset_configs_container->add_spacer(true); + preset_configs_container->add_child(advanced_options); + preset_configs_container->add_child(runnable); + settings_vb->add_child(preset_configs_container); export_path = memnew(EditorPropertyPath); settings_vb->add_child(export_path); @@ -1413,6 +1440,7 @@ ProjectExportDialog::ProjectExportDialog() { // Disable by default. name->set_editable(false); export_path->hide(); + advanced_options->set_disabled(true); runnable->set_disabled(true); duplicate_preset->set_disabled(true); delete_preset->set_disabled(true); diff --git a/editor/export/project_export.h b/editor/export/project_export.h index 0fe7ecc2a8..bcab05cebb 100644 --- a/editor/export/project_export.h +++ b/editor/export/project_export.h @@ -81,6 +81,7 @@ class ProjectExportDialog : public ConfirmationDialog { EditorPropertyPath *export_path = nullptr; EditorInspector *parameters = nullptr; CheckButton *runnable = nullptr; + CheckButton *advanced_options = nullptr; Button *button_export = nullptr; bool updating = false; @@ -119,6 +120,7 @@ class ProjectExportDialog : public ConfirmationDialog { bool exporting = false; + void _advanced_options_pressed(); void _runnable_pressed(); void _update_parameters(const String &p_edited_property); void _name_changed(const String &p_string); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 7fb52251ce..14af49aabf 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -132,13 +132,32 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } undo_redo->commit_action(); } else if (p_id == BUTTON_WARNING) { - const PackedStringArray warnings = n->get_configuration_warnings_as_strings(true); + const PackedStringArray warnings = n->get_configuration_warnings(); + if (warnings.is_empty()) { return; } - const String warning_lines = String("\n").join(warnings); - warning->set_text(warning_lines); + // Improve looks on tooltip, extra spacing on non-bullet point newlines. + const String bullet_point = U"• "; + String all_warnings; + for (const String &w : warnings) { + all_warnings += "\n" + bullet_point + w; + } + + // Limit the line width while keeping some padding. + // It is not efficient, but it does not have to be. + const PackedInt32Array boundaries = TS->string_get_word_breaks(all_warnings, "", 80); + PackedStringArray lines; + for (int i = 0; i < boundaries.size(); i += 2) { + const int start = boundaries[i]; + const int end = boundaries[i + 1]; + const String line = all_warnings.substr(start, end - start); + lines.append(line); + } + all_warnings = String("\n").join(lines).indent(" ").replace(U" •", U"\n•").substr(2); // We don't want the first two newlines. + + warning->set_text(all_warnings); warning->popup_centered(); } else if (p_id == BUTTON_SIGNALS) { @@ -275,12 +294,29 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { if (can_rename) { //should be can edit.. - const PackedStringArray warnings = p_node->get_configuration_warnings_as_strings(false); + const PackedStringArray warnings = p_node->get_configuration_warnings(); const int num_warnings = warnings.size(); if (num_warnings > 0) { - const StringName warning_icon = Node::get_configuration_warning_icon(num_warnings); - const String warning_lines = String("\n\n").join(warnings); - item->add_button(0, get_editor_theme_icon(warning_icon), BUTTON_WARNING, false, TTR("Node configuration warning:") + "\n\n" + warning_lines); + String warning_icon; + if (num_warnings == 1) { + warning_icon = SNAME("NodeWarning"); + } else if (num_warnings <= 3) { + warning_icon = vformat("NodeWarnings%d", num_warnings); + } else { + warning_icon = SNAME("NodeWarnings4Plus"); + } + + // Improve looks on tooltip, extra spacing on non-bullet point newlines. + const String bullet_point = U"• "; + String all_warnings; + for (const String &w : warnings) { + all_warnings += "\n\n" + bullet_point + w.replace("\n", "\n "); + } + if (num_warnings == 1) { + all_warnings.remove_at(0); // With only one warning, two newlines do not look great. + } + + item->add_button(0, get_editor_theme_icon(warning_icon), BUTTON_WARNING, false, TTR("Node configuration warning:") + all_warnings); } if (p_node->is_unique_name_in_owner()) { diff --git a/editor/import/resource_importer_csv_translation.cpp b/editor/import/resource_importer_csv_translation.cpp index 5d45292222..d56b426c86 100644 --- a/editor/import/resource_importer_csv_translation.cpp +++ b/editor/import/resource_importer_csv_translation.cpp @@ -101,9 +101,12 @@ Error ResourceImporterCSVTranslation::import(const String &p_source_file, const for (int i = 1; i < line.size(); i++) { String locale = TranslationServer::get_singleton()->standardize_locale(line[i]); - if (locale.is_empty()) { + if (line[i].left(1) == "_") { skipped_locales.insert(i); - ERR_CONTINUE_MSG(true, vformat("Error importing CSV translation: Invalid locale format '%s', should be 'language_Script_COUNTRY_VARIANT@extra'.", line[i])); + continue; + } else if (locale.is_empty()) { + skipped_locales.insert(i); + ERR_CONTINUE_MSG(true, vformat("Error importing CSV translation: Invalid locale format '%s', should be 'language_Script_COUNTRY_VARIANT@extra'. This column will be ignored.", line[i])); } locales.push_back(locale); @@ -117,7 +120,7 @@ Error ResourceImporterCSVTranslation::import(const String &p_source_file, const line = f->get_csv_line(delimiter); String key = line[0]; if (!key.is_empty()) { - ERR_CONTINUE_MSG(line.size() != locales.size() + 1, vformat("Error importing CSV translation: expected %d locale(s), but the '%s' key has %d locale(s).", locales.size(), key, line.size() - 1)); + ERR_CONTINUE_MSG(line.size() != locales.size() + (int)skipped_locales.size() + 1, vformat("Error importing CSV translation: expected %d locale(s), but the '%s' key has %d locale(s).", locales.size(), key, line.size() - 1)); for (int i = 1; i < line.size(); i++) { if (skipped_locales.has(i)) { diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp index 5c2c059c98..8391eddc59 100644 --- a/editor/plugin_config_dialog.cpp +++ b/editor/plugin_config_dialog.cpp @@ -100,7 +100,8 @@ void PluginConfigDialog::_on_canceled() { void PluginConfigDialog::_on_required_text_changed() { int lang_idx = script_option_edit->get_selected(); - String ext = ScriptServer::get_language(lang_idx)->get_extension(); + ScriptLanguage *language = ScriptServer::get_language(lang_idx); + String ext = language->get_extension(); if (name_edit->get_text().is_empty()) { validation_panel->set_message(MSG_ID_PLUGIN, TTR("Plugin name cannot be blank."), EditorValidationPanel::MSG_ERROR); @@ -120,6 +121,15 @@ void PluginConfigDialog::_on_required_text_changed() { } else { validation_panel->set_message(MSG_ID_SUBFOLDER, "", EditorValidationPanel::MSG_OK); } + if (active_edit->is_visible()) { + if (language->get_name() == "C#") { + active_edit->set_pressed(false); + active_edit->set_disabled(true); + validation_panel->set_message(MSG_ID_ACTIVE, TTR("C# doesn't support activating the plugin on creation because the project must be built first."), EditorValidationPanel::MSG_WARNING); + } else { + active_edit->set_disabled(false); + } + } } String PluginConfigDialog::_get_subfolder() { @@ -290,7 +300,6 @@ PluginConfigDialog::PluginConfigDialog() { grid->add_child(script_edit); // Activate now checkbox - // TODO Make this option work better with languages like C#. Right now, it does not work because the C# project must be compiled first. Label *active_lb = memnew(Label); active_lb->set_text(TTR("Activate now?")); active_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); @@ -311,6 +320,7 @@ PluginConfigDialog::PluginConfigDialog() { validation_panel->add_line(MSG_ID_PLUGIN, TTR("Plugin name is valid.")); validation_panel->add_line(MSG_ID_SCRIPT, TTR("Script extension is valid.")); validation_panel->add_line(MSG_ID_SUBFOLDER, TTR("Subfolder name is valid.")); + validation_panel->add_line(MSG_ID_ACTIVE, ""); validation_panel->set_update_callback(callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); validation_panel->set_accept_button(get_ok_button()); diff --git a/editor/plugin_config_dialog.h b/editor/plugin_config_dialog.h index c6befbab85..2fdf6368a0 100644 --- a/editor/plugin_config_dialog.h +++ b/editor/plugin_config_dialog.h @@ -48,6 +48,7 @@ class PluginConfigDialog : public ConfirmationDialog { MSG_ID_PLUGIN, MSG_ID_SUBFOLDER, MSG_ID_SCRIPT, + MSG_ID_ACTIVE, }; LineEdit *name_edit = nullptr; diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 3f9f609dba..4b2fa1876d 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -1867,7 +1867,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug tool_anim->set_flat(false); tool_anim->set_tooltip_text(TTR("Animation Tools")); tool_anim->set_text(TTR("Animation")); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New...")), TOOL_NEW_ANIM); tool_anim->get_popup()->add_separator(); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTR("Manage Animations...")), TOOL_ANIM_LIBRARY); tool_anim->get_popup()->add_separator(); @@ -2157,6 +2157,7 @@ void AnimationPlayerEditorPlugin::_update_dummy_player(AnimationMixer *p_mixer) Node *parent = p_mixer->get_parent(); ERR_FAIL_NULL(parent); dummy_player = memnew(AnimationPlayer); + dummy_player->set_active(false); // Inactive as default, it will be activated if the AnimationPlayerEditor visibility is changed. parent->add_child(dummy_player); } player = dummy_player; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 132b85b090..bb076163b0 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1285,25 +1285,25 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo Ref<InputEventKey> k = p_event; if (k.is_valid()) { if (k->is_pressed()) { - if (ED_GET_SHORTCUT("canvas_item_editor/zoom_3.125_percent")->matches_event(p_event)) { + if (ED_IS_SHORTCUT("canvas_item_editor/zoom_3.125_percent", p_event)) { _shortcut_zoom_set(1.0 / 32.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_6.25_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_6.25_percent", p_event)) { _shortcut_zoom_set(1.0 / 16.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_12.5_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_12.5_percent", p_event)) { _shortcut_zoom_set(1.0 / 8.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_25_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_25_percent", p_event)) { _shortcut_zoom_set(1.0 / 4.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_50_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_50_percent", p_event)) { _shortcut_zoom_set(1.0 / 2.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_100_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_100_percent", p_event)) { _shortcut_zoom_set(1.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_200_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_200_percent", p_event)) { _shortcut_zoom_set(2.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_400_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_400_percent", p_event)) { _shortcut_zoom_set(4.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_800_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_800_percent", p_event)) { _shortcut_zoom_set(8.0); - } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_1600_percent")->matches_event(p_event)) { + } else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_1600_percent", p_event)) { _shortcut_zoom_set(16.0); } } diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 8625e468fa..f8e2bd0915 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -625,13 +625,13 @@ ShaderEditorPlugin::ShaderEditorPlugin() { file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); file_menu->set_shortcut_context(main_split); - file_menu->get_popup()->add_item(TTR("New Shader"), FILE_NEW); - file_menu->get_popup()->add_item(TTR("New Shader Include"), FILE_NEW_INCLUDE); + file_menu->get_popup()->add_item(TTR("New Shader..."), FILE_NEW); + file_menu->get_popup()->add_item(TTR("New Shader Include..."), FILE_NEW_INCLUDE); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_item(TTR("Load Shader File"), FILE_OPEN); - file_menu->get_popup()->add_item(TTR("Load Shader Include File"), FILE_OPEN_INCLUDE); + file_menu->get_popup()->add_item(TTR("Load Shader File..."), FILE_OPEN); + file_menu->get_popup()->add_item(TTR("Load Shader Include File..."), FILE_OPEN_INCLUDE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/save", TTR("Save File"), KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::S), FILE_SAVE); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/save_as", TTR("Save File As")), FILE_SAVE_AS); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/save_as", TTR("Save File As...")), FILE_SAVE_AS); file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_item(TTR("Open File in Inspector"), FILE_INSPECT); file_menu->get_popup()->add_separator(); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 7efa978a78..176e8b7fee 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2673,11 +2673,9 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_atlas_view->connect("transform_changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed).unbind(2)); right_panel->add_child(tile_atlas_view); - tile_create_help = memnew(HBoxContainer); + tile_create_help = memnew(VBoxContainer); tile_atlas_view->add_child(tile_create_help); tile_create_help->set_mouse_filter(MOUSE_FILTER_IGNORE); - tile_create_help->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT, Control::PRESET_MODE_MINSIZE, 30 * EDSCALE); - tile_create_help->add_theme_constant_override("separation", 30 * EDSCALE); Label *help_label = memnew(Label(TTR("Hold Ctrl to create multiple tiles."))); tile_create_help->add_child(help_label); @@ -2685,6 +2683,11 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { help_label = memnew(Label(TTR("Hold Shift to create big tiles."))); tile_create_help->add_child(help_label); + tile_create_help->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT, Control::PRESET_MODE_MINSIZE); + Vector2 pos = tile_create_help->get_position(); + pos.y -= 8 * EDSCALE; + tile_create_help->set_position(pos); + base_tile_popup_menu = memnew(PopupMenu); base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE), TILE_DELETE); base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index e1e6a3113c..b98705baf1 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -160,7 +160,7 @@ private: // -- Atlas view -- TileAtlasView *tile_atlas_view = nullptr; - HBoxContainer *tile_create_help = nullptr; + VBoxContainer *tile_create_help = nullptr; // Dragging enum DragType { diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index ea3d00a437..8a580b565f 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -4162,7 +4162,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec ED_SHORTCUT("scene_tree/rename", TTR("Rename"), Key::F2); ED_SHORTCUT_OVERRIDE("scene_tree/rename", "macos", Key::ENTER); - ED_SHORTCUT("scene_tree/batch_rename", TTR("Batch Rename"), KeyModifierMask::SHIFT | Key::F2); + ED_SHORTCUT("scene_tree/batch_rename", TTR("Batch Rename..."), KeyModifierMask::SHIFT | Key::F2); ED_SHORTCUT_OVERRIDE("scene_tree/batch_rename", "macos", KeyModifierMask::SHIFT | Key::ENTER); ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node..."), KeyModifierMask::CMD_OR_CTRL | Key::A); @@ -4179,10 +4179,10 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec ED_SHORTCUT("scene_tree/move_up", TTR("Move Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP); ED_SHORTCUT("scene_tree/move_down", TTR("Move Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN); ED_SHORTCUT("scene_tree/duplicate", TTR("Duplicate"), KeyModifierMask::CMD_OR_CTRL | Key::D); - ED_SHORTCUT("scene_tree/reparent", TTR("Reparent")); - ED_SHORTCUT("scene_tree/reparent_to_new_node", TTR("Reparent to New Node")); + ED_SHORTCUT("scene_tree/reparent", TTR("Reparent...")); + ED_SHORTCUT("scene_tree/reparent_to_new_node", TTR("Reparent to New Node...")); ED_SHORTCUT("scene_tree/make_root", TTR("Make Scene Root")); - ED_SHORTCUT("scene_tree/save_branch_as_scene", TTR("Save Branch as Scene")); + ED_SHORTCUT("scene_tree/save_branch_as_scene", TTR("Save Branch as Scene...")); ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); ED_SHORTCUT("scene_tree/show_in_file_system", TTR("Show in FileSystem")); ED_SHORTCUT("scene_tree/toggle_unique_name", TTR("Toggle Access as Unique Name")); diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 32795330c4..b801066204 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -84,13 +84,6 @@ Validate extension JSON: Error: Field 'classes/Sky/properties/sky_material': typ Property hints reordered to improve editor usability. The types allowed are still the same as before. No adjustments should be necessary. -GH-68420 --------- -Validate extension JSON: Error: Field 'classes/Node/methods/_get_configuration_warnings/return_value': type changed value in new API, from "PackedStringArray" to "Array". - -Allow configuration warnings to refer to a property. Compatibility method registered. - - GH-86907 -------- @@ -140,3 +133,17 @@ Validate extension JSON: Error: Field 'classes/Animation/methods/blend_shape_tra Validate extension JSON: Error: Field 'classes/Animation/methods/value_track_interpolate/arguments': size changed value in new API, from 2 to 3. Added optional argument to track_interpolate to treat playing backward correctly. Compatibility method registered. + + +GH-86661 +-------- +Validate extension JSON: Error: Field 'classes/Animation/methods/track_find_key/arguments': size changed value in new API, from 3 to 4. + +Added optional argument to track_find_key to avoid finding keys out of the animation range. Compatibility method registered. + + +GH-84792 +-------- +Validate extension JSON: Error: Field 'classes/RenderingServer/methods/environment_set_fog/arguments': size changed value in new API, from 10 to 11. + +Added fog mode argument. Compatibility method registered. diff --git a/modules/basis_universal/SCsub b/modules/basis_universal/SCsub index 14669847bc..80bfd7e858 100644 --- a/modules/basis_universal/SCsub +++ b/modules/basis_universal/SCsub @@ -28,7 +28,6 @@ encoder_sources = [ "basisu_resample_filters.cpp", "basisu_ssim.cpp", "basisu_uastc_enc.cpp", - "jpgd.cpp", "pvpngreader.cpp", ] encoder_sources = [thirdparty_dir + "encoder/" + file for file in encoder_sources] @@ -36,9 +35,11 @@ transcoder_sources = [thirdparty_dir + "transcoder/basisu_transcoder.cpp"] # Treat Basis headers as system headers to avoid raising warnings. Not supported on MSVC. if not env.msvc: - env_basisu.Append(CPPFLAGS=["-isystem", Dir(thirdparty_dir).path]) + env_basisu.Append( + CPPFLAGS=["-isystem", Dir(thirdparty_dir).path, "-isystem", Dir("#thirdparty/jpeg-compressor").path] + ) else: - env_basisu.Prepend(CPPPATH=[thirdparty_dir]) + env_basisu.Prepend(CPPPATH=[thirdparty_dir, "#thirdparty/jpeg-compressor"]) if env["builtin_zstd"]: env_basisu.Prepend(CPPPATH=["#thirdparty/zstd"]) diff --git a/modules/basis_universal/config.py b/modules/basis_universal/config.py index d22f9454ed..6a67f2c77c 100644 --- a/modules/basis_universal/config.py +++ b/modules/basis_universal/config.py @@ -1,4 +1,5 @@ def can_build(env, platform): + env.module_add_dependencies("basis_universal", ["jpg"]) return True diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp new file mode 100644 index 0000000000..41de62b20b --- /dev/null +++ b/modules/basis_universal/image_compress_basisu.cpp @@ -0,0 +1,251 @@ +/**************************************************************************/ +/* image_compress_basisu.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "image_compress_basisu.h" + +#include "servers/rendering_server.h" + +#include <transcoder/basisu_transcoder.h> +#ifdef TOOLS_ENABLED +#include <encoder/basisu_comp.h> +#endif + +void basis_universal_init() { +#ifdef TOOLS_ENABLED + basisu::basisu_encoder_init(); +#endif + + basist::basisu_transcoder_init(); +} + +#ifdef TOOLS_ENABLED +Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) { + Ref<Image> image = p_image->duplicate(); + image->convert(Image::FORMAT_RGBA8); + + basisu::basis_compressor_params params; + + params.m_uastc = true; + params.m_quality_level = basisu::BASISU_QUALITY_MIN; + params.m_pack_uastc_flags &= ~basisu::cPackUASTCLevelMask; + params.m_pack_uastc_flags |= basisu::cPackUASTCLevelFastest; + + params.m_rdo_uastc = 0.0f; + params.m_rdo_uastc_quality_scalar = 0.0f; + params.m_rdo_uastc_dict_size = 1024; + + params.m_mip_fast = true; + params.m_multithreading = true; + params.m_check_for_alpha = false; + + basisu::job_pool job_pool(OS::get_singleton()->get_processor_count()); + params.m_pJob_pool = &job_pool; + + BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_RG; + switch (p_channels) { + case Image::USED_CHANNELS_L: { + decompress_format = BASIS_DECOMPRESS_RGB; + } break; + case Image::USED_CHANNELS_LA: { + params.m_force_alpha = true; + decompress_format = BASIS_DECOMPRESS_RGBA; + } break; + case Image::USED_CHANNELS_R: { + decompress_format = BASIS_DECOMPRESS_RGB; + } break; + case Image::USED_CHANNELS_RG: { + // Currently RG textures are compressed as DXT5/ETC2_RGBA8 with a RA -> RG swizzle, + // as BasisUniversal didn't use to support ETC2_RG11 transcoding. + params.m_force_alpha = true; + image->convert_rg_to_ra_rgba8(); + decompress_format = BASIS_DECOMPRESS_RG_AS_RA; + } break; + case Image::USED_CHANNELS_RGB: { + decompress_format = BASIS_DECOMPRESS_RGB; + } break; + case Image::USED_CHANNELS_RGBA: { + params.m_force_alpha = true; + decompress_format = BASIS_DECOMPRESS_RGBA; + } break; + } + + { + // Encode the image with mipmaps. + Vector<uint8_t> image_data = image->get_data(); + basisu::vector<basisu::image> basisu_mipmaps; + + for (int32_t i = 0; i <= image->get_mipmap_count(); i++) { + int ofs, size, width, height; + image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); + + basisu::image basisu_image(width, height); + memcpy(basisu_image.get_ptr(), image_data.ptr() + ofs, size); + + if (i == 0) { + params.m_source_images.push_back(basisu_image); + } else { + basisu_mipmaps.push_back(basisu_image); + } + } + + params.m_source_mipmap_images.push_back(basisu_mipmaps); + } + + // Encode the image data. + Vector<uint8_t> basisu_data; + + basisu::basis_compressor compressor; + compressor.init(params); + + int basisu_err = compressor.process(); + ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, basisu_data); + + const basisu::uint8_vec &basisu_out = compressor.get_output_basis_file(); + basisu_data.resize(basisu_out.size() + 4); + + // Copy the encoded data to the buffer. + { + uint8_t *w = basisu_data.ptrw(); + *(uint32_t *)w = decompress_format; + + memcpy(w + 4, basisu_out.get_ptr(), basisu_out.size()); + } + + return basisu_data; +} +#endif // TOOLS_ENABLED + +Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { + Ref<Image> image; + ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid BasisUniversal data."); + + const uint8_t *src_ptr = p_data; + int src_size = p_size; + + basist::transcoder_texture_format basisu_format = basist::transcoder_texture_format::cTFTotalTextureFormats; + Image::Format image_format = Image::FORMAT_MAX; + + // Get supported compression formats. + bool bptc_supported = RS::get_singleton()->has_os_feature("bptc"); + bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc"); + bool etc2_supported = RS::get_singleton()->has_os_feature("etc2"); + + switch (*(uint32_t *)(src_ptr)) { + case BASIS_DECOMPRESS_RG: { + // RGTC transcoding is currently performed with RG_AS_RA, fail. + ERR_FAIL_V(image); + } break; + case BASIS_DECOMPRESS_RGB: { + if (bptc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC7_M6_OPAQUE_ONLY; + image_format = Image::FORMAT_BPTC_RGBA; + } else if (s3tc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC1; + image_format = Image::FORMAT_DXT1; + } else if (etc2_supported) { + basisu_format = basist::transcoder_texture_format::cTFETC1; + image_format = Image::FORMAT_ETC2_RGB8; + } else { + // No supported VRAM compression formats, decompress. + basisu_format = basist::transcoder_texture_format::cTFRGBA32; + image_format = Image::FORMAT_RGBA8; + } + + } break; + case BASIS_DECOMPRESS_RGBA: { + if (bptc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC7_M5; + image_format = Image::FORMAT_BPTC_RGBA; + } else if (s3tc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC3; + image_format = Image::FORMAT_DXT5; + } else if (etc2_supported) { + basisu_format = basist::transcoder_texture_format::cTFETC2; + image_format = Image::FORMAT_ETC2_RGBA8; + } else { + // No supported VRAM compression formats, decompress. + basisu_format = basist::transcoder_texture_format::cTFRGBA32; + image_format = Image::FORMAT_RGBA8; + } + } break; + case BASIS_DECOMPRESS_RG_AS_RA: { + if (s3tc_supported) { + basisu_format = basist::transcoder_texture_format::cTFBC3; + image_format = Image::FORMAT_DXT5_RA_AS_RG; + } else if (etc2_supported) { + basisu_format = basist::transcoder_texture_format::cTFETC2; + image_format = Image::FORMAT_ETC2_RA_AS_RG; + } else { + // No supported VRAM compression formats, decompress. + basisu_format = basist::transcoder_texture_format::cTFRGBA32; + image_format = Image::FORMAT_RGBA8; + } + } break; + } + + src_ptr += 4; + src_size -= 4; + + basist::basisu_transcoder transcoder; + ERR_FAIL_COND_V(!transcoder.validate_header(src_ptr, src_size), image); + + transcoder.start_transcoding(src_ptr, src_size); + + basist::basisu_image_info basisu_info; + transcoder.get_image_info(src_ptr, src_size, basisu_info, 0); + + Vector<uint8_t> out_data; + out_data.resize(Image::get_image_data_size(basisu_info.m_width, basisu_info.m_height, image_format, basisu_info.m_total_levels > 1)); + + uint8_t *dst = out_data.ptrw(); + memset(dst, 0, out_data.size()); + + uint32_t mip_count = Image::get_image_required_mipmaps(basisu_info.m_orig_width, basisu_info.m_orig_height, image_format); + for (uint32_t i = 0; i <= mip_count; i++) { + basist::basisu_image_level_info basisu_level; + transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i); + + int ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i); + bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, basisu_level.m_total_blocks, basisu_format); + + if (!result) { + print_line(vformat("BasisUniversal cannot unpack level %d.", i)); + break; + } + } + + image = Image::create_from_data(basisu_info.m_width, basisu_info.m_height, basisu_info.m_total_levels > 1, image_format, out_data); + + return image; +} + +Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) { + return basis_universal_unpacker_ptr(p_buffer.ptr(), p_buffer.size()); +} diff --git a/scene/main/node.compat.inc b/modules/basis_universal/image_compress_basisu.h index 7e957e5a14..ac5d62ae73 100644 --- a/scene/main/node.compat.inc +++ b/modules/basis_universal/image_compress_basisu.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* node.compat.inc */ +/* image_compress_basisu.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,25 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef DISABLE_DEPRECATED +#ifndef IMAGE_COMPRESS_BASISU_H +#define IMAGE_COMPRESS_BASISU_H -PackedStringArray Node::get_configuration_warnings_bind_compat_68420() const { - return PackedStringArray(get_configuration_warnings()); -} +#include "core/io/image.h" -void Node::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("get_configuration_warnings"), &Node::get_configuration_warnings_bind_compat_68420); -} +enum BasisDecompressFormat { + BASIS_DECOMPRESS_RG, + BASIS_DECOMPRESS_RGB, + BASIS_DECOMPRESS_RGBA, + BASIS_DECOMPRESS_RG_AS_RA, +}; -#endif // DISABLE_DEPRECATED +void basis_universal_init(); + +#ifdef TOOLS_ENABLED +Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels); +#endif + +Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size); +Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer); + +#endif // IMAGE_COMPRESS_BASISU_H diff --git a/modules/basis_universal/patches/external-jpgd.patch b/modules/basis_universal/patches/external-jpgd.patch new file mode 100644 index 0000000000..7a805d00cb --- /dev/null +++ b/modules/basis_universal/patches/external-jpgd.patch @@ -0,0 +1,13 @@ +diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp +index c431ceaf12..e87dd636a2 100644 +--- a/thirdparty/basis_universal/encoder/basisu_enc.cpp ++++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp +@@ -409,7 +409,7 @@ namespace basisu + bool load_jpg(const char *pFilename, image& img) + { + int width = 0, height = 0, actual_comps = 0; +- uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagLinearChromaFiltering); ++ uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagBoxChromaFiltering); + if (!pImage_data) + return false; + diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp index c9ea67cb09..06a3fb76cb 100644 --- a/modules/basis_universal/register_types.cpp +++ b/modules/basis_universal/register_types.cpp @@ -30,262 +30,19 @@ #include "register_types.h" -#include "core/os/os.h" -#include "servers/rendering_server.h" - -#include <transcoder/basisu_transcoder.h> - -#ifdef TOOLS_ENABLED -#include <encoder/basisu_comp.h> -#endif - -enum BasisDecompressFormat { - BASIS_DECOMPRESS_RG, - BASIS_DECOMPRESS_RGB, - BASIS_DECOMPRESS_RGBA, - BASIS_DECOMPRESS_RG_AS_RA -}; - -//workaround for lack of ETC2 RG -#define USE_RG_AS_RGBA - -#ifdef TOOLS_ENABLED -static Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) { - Vector<uint8_t> budata; - { - basisu::basis_compressor_params params; - Ref<Image> image = p_image->duplicate(); - if (image->get_format() != Image::FORMAT_RGBA8) { - image->convert(Image::FORMAT_RGBA8); - } - - params.m_uastc = true; - params.m_quality_level = basisu::BASISU_QUALITY_MIN; - - params.m_pack_uastc_flags &= ~basisu::cPackUASTCLevelMask; - - static const uint32_t s_level_flags[basisu::TOTAL_PACK_UASTC_LEVELS] = { basisu::cPackUASTCLevelFastest, basisu::cPackUASTCLevelFaster, basisu::cPackUASTCLevelDefault, basisu::cPackUASTCLevelSlower, basisu::cPackUASTCLevelVerySlow }; - params.m_pack_uastc_flags |= s_level_flags[0]; - params.m_rdo_uastc = 0.0f; - params.m_rdo_uastc_quality_scalar = 0.0f; - params.m_rdo_uastc_dict_size = 1024; - - params.m_mip_fast = true; - params.m_multithreading = true; - - basisu::job_pool jpool(OS::get_singleton()->get_processor_count()); - params.m_pJob_pool = &jpool; - - BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_RG; - params.m_check_for_alpha = false; - - switch (p_channels) { - case Image::USED_CHANNELS_L: { - decompress_format = BASIS_DECOMPRESS_RGB; - } break; - case Image::USED_CHANNELS_LA: { - params.m_force_alpha = true; - decompress_format = BASIS_DECOMPRESS_RGBA; - } break; - case Image::USED_CHANNELS_R: { - decompress_format = BASIS_DECOMPRESS_RGB; - } break; - case Image::USED_CHANNELS_RG: { -#ifdef USE_RG_AS_RGBA - params.m_force_alpha = true; - image->convert_rg_to_ra_rgba8(); - decompress_format = BASIS_DECOMPRESS_RG_AS_RA; -#else - params.m_seperate_rg_to_color_alpha = true; - decompress_format = BASIS_DECOMPRESS_RG; -#endif - } break; - case Image::USED_CHANNELS_RGB: { - decompress_format = BASIS_DECOMPRESS_RGB; - } break; - case Image::USED_CHANNELS_RGBA: { - params.m_force_alpha = true; - decompress_format = BASIS_DECOMPRESS_RGBA; - } break; - } - - if (!image->has_mipmaps()) { - basisu::image buimg(image->get_width(), image->get_height()); - Vector<uint8_t> vec = image->get_data(); - const uint8_t *r = vec.ptr(); - memcpy(buimg.get_ptr(), r, vec.size()); - params.m_source_images.push_back(buimg); - } else { - { - Ref<Image> base_image = image->get_image_from_mipmap(0); - Vector<uint8_t> image_vec = base_image->get_data(); - basisu::image buimg_image(base_image->get_width(), base_image->get_height()); - const uint8_t *r = image_vec.ptr(); - memcpy(buimg_image.get_ptr(), r, image_vec.size()); - params.m_source_images.push_back(buimg_image); - } - basisu::vector<basisu::image> images; - for (int32_t mip_map_i = 1; mip_map_i <= image->get_mipmap_count(); mip_map_i++) { - Ref<Image> mip_map = image->get_image_from_mipmap(mip_map_i); - Vector<uint8_t> mip_map_vec = mip_map->get_data(); - basisu::image buimg_mipmap(mip_map->get_width(), mip_map->get_height()); - const uint8_t *r = mip_map_vec.ptr(); - memcpy(buimg_mipmap.get_ptr(), r, mip_map_vec.size()); - images.push_back(buimg_mipmap); - } - params.m_source_mipmap_images.push_back(images); - } - - basisu::basis_compressor c; - c.init(params); - - int buerr = c.process(); - ERR_FAIL_COND_V(buerr != basisu::basis_compressor::cECSuccess, budata); - - const basisu::uint8_vec &buvec = c.get_output_basis_file(); - budata.resize(buvec.size() + 4); - - { - uint8_t *w = budata.ptrw(); - uint32_t *decf = (uint32_t *)w; - *decf = decompress_format; - memcpy(w + 4, &buvec[0], buvec.size()); - } - } - - return budata; -} -#endif // TOOLS_ENABLED - -static Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { - Ref<Image> image; - - const uint8_t *ptr = p_data; - int size = p_size; - ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid basis universal data."); - - basist::transcoder_texture_format format = basist::transcoder_texture_format::cTFTotalTextureFormats; - Image::Format imgfmt = Image::FORMAT_MAX; - - switch (*(uint32_t *)(ptr)) { - case BASIS_DECOMPRESS_RG: { - if (RS::get_singleton()->has_os_feature("rgtc")) { - format = basist::transcoder_texture_format::cTFBC5; // get this from renderer - imgfmt = Image::FORMAT_RGTC_RG; - } else if (RS::get_singleton()->has_os_feature("etc2")) { - //unfortunately, basis universal does not support - // - ERR_FAIL_V(image); //unimplemented here - //format = basist::transcoder_texture_format::cTFETC1; // get this from renderer - //imgfmt = Image::FORMAT_RGTC_RG; - } else { - // FIXME: There wasn't anything here, but then imgformat is used uninitialized. - ERR_FAIL_V(image); - } - } break; - case BASIS_DECOMPRESS_RGB: { - if (RS::get_singleton()->has_os_feature("bptc")) { - format = basist::transcoder_texture_format::cTFBC7_M6_OPAQUE_ONLY; // get this from renderer - imgfmt = Image::FORMAT_BPTC_RGBA; - } else if (RS::get_singleton()->has_os_feature("s3tc")) { - format = basist::transcoder_texture_format::cTFBC1; // get this from renderer - imgfmt = Image::FORMAT_DXT1; - } else if (RS::get_singleton()->has_os_feature("etc2")) { - format = basist::transcoder_texture_format::cTFETC1; // get this from renderer - imgfmt = Image::FORMAT_ETC2_RGB8; - } else { - format = basist::transcoder_texture_format::cTFBGR565; // get this from renderer - imgfmt = Image::FORMAT_RGB565; - } - - } break; - case BASIS_DECOMPRESS_RGBA: { - if (RS::get_singleton()->has_os_feature("bptc")) { - format = basist::transcoder_texture_format::cTFBC7_M5; // get this from renderer - imgfmt = Image::FORMAT_BPTC_RGBA; - } else if (RS::get_singleton()->has_os_feature("s3tc")) { - format = basist::transcoder_texture_format::cTFBC3; // get this from renderer - imgfmt = Image::FORMAT_DXT5; - } else if (RS::get_singleton()->has_os_feature("etc2")) { - format = basist::transcoder_texture_format::cTFETC2; // get this from renderer - imgfmt = Image::FORMAT_ETC2_RGBA8; - } else { - //opengl most likely - format = basist::transcoder_texture_format::cTFRGBA4444; // get this from renderer - imgfmt = Image::FORMAT_RGBA4444; - } - } break; - case BASIS_DECOMPRESS_RG_AS_RA: { - if (RS::get_singleton()->has_os_feature("s3tc")) { - format = basist::transcoder_texture_format::cTFBC3; // get this from renderer - imgfmt = Image::FORMAT_DXT5_RA_AS_RG; - } else if (RS::get_singleton()->has_os_feature("etc2")) { - format = basist::transcoder_texture_format::cTFETC2; // get this from renderer - imgfmt = Image::FORMAT_ETC2_RA_AS_RG; - } else { - //opengl most likely, bad for normal maps, nothing to do about this. - format = basist::transcoder_texture_format::cTFRGBA32; - imgfmt = Image::FORMAT_RGBA8; - } - } break; - } - - ptr += 4; - size -= 4; - - basist::basisu_transcoder tr; - - ERR_FAIL_COND_V(!tr.validate_header(ptr, size), image); - - tr.start_transcoding(ptr, size); - - basist::basisu_image_info info; - tr.get_image_info(ptr, size, info, 0); - Vector<uint8_t> gpudata; - gpudata.resize(Image::get_image_data_size(info.m_width, info.m_height, imgfmt, info.m_total_levels > 1)); - - uint8_t *w = gpudata.ptrw(); - uint8_t *dst = w; - for (int i = 0; i < gpudata.size(); i++) { - dst[i] = 0x00; - } - uint32_t mip_count = Image::get_image_required_mipmaps(info.m_orig_width, info.m_orig_height, imgfmt); - for (uint32_t level_i = 0; level_i <= mip_count; level_i++) { - basist::basisu_image_level_info level; - tr.get_image_level_info(ptr, size, level, 0, level_i); - int ofs = Image::get_image_mipmap_offset(info.m_width, info.m_height, imgfmt, level_i); - bool ret = tr.transcode_image_level(ptr, size, 0, level_i, dst + ofs, level.m_total_blocks, format); - if (!ret) { - print_line(vformat("Basis universal cannot unpack level %d.", level_i)); - break; - }; - } - - image = Image::create_from_data(info.m_width, info.m_height, info.m_total_levels > 1, imgfmt, gpudata); - - return image; -} - -static Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) { - Ref<Image> image; - - const uint8_t *r = p_buffer.ptr(); - int size = p_buffer.size(); - return basis_universal_unpacker_ptr(r, size); -} +#include "image_compress_basisu.h" void initialize_basis_universal_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } + basis_universal_init(); + #ifdef TOOLS_ENABLED - using namespace basisu; - using namespace basist; - basisu_encoder_init(); Image::basis_universal_packer = basis_universal_packer; #endif - basist::basisu_transcoder_init(); + Image::basis_universal_unpacker = basis_universal_unpacker; Image::basis_universal_unpacker_ptr = basis_universal_unpacker_ptr; } @@ -298,6 +55,7 @@ void uninitialize_basis_universal_module(ModuleInitializationLevel p_level) { #ifdef TOOLS_ENABLED Image::basis_universal_packer = nullptr; #endif + Image::basis_universal_unpacker = nullptr; Image::basis_universal_unpacker_ptr = nullptr; } diff --git a/modules/mono/config.py b/modules/mono/config.py index 859d77b262..3d087c9e27 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,8 +1,3 @@ -# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "web", "ios"] -# Eventually support for each them should be added back. -supported_platforms = ["windows", "macos", "linuxbsd", "android", "ios"] - - def can_build(env, platform): if env["arch"].startswith("rv"): return False @@ -14,9 +9,10 @@ def can_build(env, platform): def configure(env): - platform = env["platform"] + # Check if the platform has marked mono as supported. + supported = env.get("supported", []) - if platform not in supported_platforms: + if not "mono" in supported: raise RuntimeError("This module does not currently support building for this platform") env.add_module_version_string("mono") diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs index 2cd1a08c0e..457d8daa8e 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs @@ -2,17 +2,6 @@ namespace Godot.SourceGenerators.Sample { - partial class Generic<T> : GodotObject - { - private int _field; - } - - // Generic again but different generic parameters - partial class Generic<T, R> : GodotObject - { - private int _field; - } - // Generic again but without generic parameters partial class Generic : GodotObject { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic1T.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic1T.cs new file mode 100644 index 0000000000..9c4f8ee1e1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic1T.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS0169 + +namespace Godot.SourceGenerators.Sample +{ + partial class Generic1T<T> : GodotObject + { + private int _field; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic2T.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic2T.cs new file mode 100644 index 0000000000..80551a0b42 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic2T.cs @@ -0,0 +1,10 @@ +#pragma warning disable CS0169 + +namespace Godot.SourceGenerators.Sample +{ + // Generic again but different generic parameters + partial class Generic2T<T, R> : GodotObject + { + private int _field; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs index d75922481c..84e319352d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs @@ -61,15 +61,15 @@ where TSourceGenerator : ISourceGenerator, new() build_property.GodotProjectDir = {Constants.ExecutingAssemblyPath} """)); - verifier.TestState.Sources.AddRange(sources.Select(source => - { - return (source, SourceText.From(File.ReadAllText(Path.Combine(Constants.SourceFolderPath, source)))); - })); + verifier.TestState.Sources.AddRange(sources.Select(source => ( + source, + SourceText.From(File.ReadAllText(Path.Combine(Constants.SourceFolderPath, source))) + ))); - verifier.TestState.GeneratedSources.AddRange(generatedSources.Select(generatedSource => - { - return (FullGeneratedSourceName(generatedSource), SourceText.From(File.ReadAllText(Path.Combine(Constants.GeneratedSourceFolderPath, generatedSource)), Encoding.UTF8)); - })); + verifier.TestState.GeneratedSources.AddRange(generatedSources.Select(generatedSource => ( + FullGeneratedSourceName(generatedSource), + SourceText.From(File.ReadAllText(Path.Combine(Constants.GeneratedSourceFolderPath, generatedSource)), Encoding.UTF8) + ))); return verifier; } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs index b7ad4217aa..4f6b50cf02 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -47,9 +48,33 @@ public class ScriptPathAttributeGeneratorTests { var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier( new string[] { "Generic.cs" }, - new string[] { "Generic_ScriptPath.generated.cs" } + new string[] { "Generic(Of T)_ScriptPath.generated.cs" } ); - verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic" })); + verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic<>" })); + await verifier.RunAsync(); + } + + [Fact] + public async void GenericMultipleClassesSameName() + { + var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier( + Array.Empty<string>(), + new string[] { "Generic(Of T)_ScriptPath.generated.cs" } + ); + verifier.TestState.Sources.Add(("Generic.cs", File.ReadAllText(Path.Combine(Constants.SourceFolderPath, "Generic.GD0003.cs")))); + verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic<>", "global::Generic<,>", "global::Generic" })); + await verifier.RunAsync(); + } + + [Fact] + public async void NamespaceMultipleClassesSameName() + { + var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier( + Array.Empty<string>(), + new string[] { "NamespaceA.SameName_ScriptPath.generated.cs" } + ); + verifier.TestState.Sources.Add(("SameName.cs", File.ReadAllText(Path.Combine(Constants.SourceFolderPath, "SameName.GD0003.cs")))); + verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::NamespaceA.SameName", "global::NamespaceB.SameName" })); await verifier.RunAsync(); } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic(Of T)_ScriptPath.generated.cs index 72c48595a2..355b01f753 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic(Of T)_ScriptPath.generated.cs @@ -1,5 +1,5 @@ using Godot; [ScriptPathAttribute("res://Generic.cs")] -partial class Generic +partial class Generic<T> { } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/NamespaceA.SameName_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/NamespaceA.SameName_ScriptPath.generated.cs new file mode 100644 index 0000000000..cad9f2a46b --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/NamespaceA.SameName_ScriptPath.generated.cs @@ -0,0 +1,9 @@ +using Godot; +namespace NamespaceA { + +[ScriptPathAttribute("res://SameName.cs")] +partial class SameName +{ +} + +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs index 160c5d193d..51dc359157 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/EventSignals.cs @@ -4,4 +4,15 @@ public partial class EventSignals : GodotObject { [Signal] public delegate void MySignalEventHandler(string str, int num); + + private struct MyStruct { } + + [Signal] + private delegate void {|GD0201:MyInvalidSignal|}(); + + [Signal] + private delegate void MyInvalidParameterTypeSignalEventHandler(MyStruct {|GD0202:myStruct|}); + + [Signal] + private delegate MyStruct {|GD0203:MyInvalidReturnTypeSignalEventHandler|}(); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.GD0003.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.GD0003.cs new file mode 100644 index 0000000000..15c1e03801 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.GD0003.cs @@ -0,0 +1,18 @@ +using Godot; + +partial class Generic<T> : GodotObject +{ + private int _field; +} + +// Generic again but different generic parameters +partial class {|GD0003:Generic|}<T, R> : GodotObject +{ + private int _field; +} + +// Generic again but without generic parameters +partial class {|GD0003:Generic|} : GodotObject +{ + private int _field; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs index 84d1ede065..5a83e21e96 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs @@ -4,15 +4,3 @@ partial class Generic<T> : GodotObject { private int _field; } - -// Generic again but different generic parameters -partial class Generic<T, R> : GodotObject -{ - private int _field; -} - -// Generic again but without generic parameters -partial class Generic : GodotObject -{ - private int _field; -} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs index 6e6d3a6f39..1908703a71 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs @@ -15,8 +15,8 @@ public partial class CustomGlobalClass2 : Node } // This raises a GD0401 diagnostic error: global classes must inherit from GodotObject -{|GD0401:[GlobalClass] -public partial class CustomGlobalClass3 +[GlobalClass] +public partial class {|GD0401:CustomGlobalClass3|} { -}|} +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs index 1c0a169841..4f7885cf37 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs @@ -8,8 +8,8 @@ public partial class CustomGlobalClass : GodotObject } // This raises a GD0402 diagnostic error: global classes can't have any generic type parameter -{|GD0402:[GlobalClass] -public partial class CustomGlobalClass<T> : GodotObject +[GlobalClass] +public partial class {|GD0402:CustomGlobalClass|}<T> : GodotObject { -}|} +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/SameName.GD0003.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/SameName.GD0003.cs new file mode 100644 index 0000000000..3f4f79fc49 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/SameName.GD0003.cs @@ -0,0 +1,18 @@ +using Godot; + +namespace NamespaceA +{ + partial class SameName : GodotObject + { + private int _field; + } +} + +// SameName again but different namespace +namespace NamespaceB +{ + partial class {|GD0003:SameName|} : GodotObject + { + private int _field; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 1113629fef..6cd5ddb42f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -1,7 +1,5 @@ -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; namespace Godot.SourceGenerators { @@ -67,430 +65,164 @@ namespace Godot.SourceGenerators outerTypeDeclSyntax.SyntaxTree.FilePath)); } - public static void ReportExportedMemberIsStatic( - GeneratorExecutionContext context, - ISymbol exportedMemberSymbol - ) - { - var locations = exportedMemberSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - bool isField = exportedMemberSymbol is IFieldSymbol; - - string message = $"Attempted to export static {(isField ? "field" : "property")}: " + - $"'{exportedMemberSymbol.ToDisplayString()}'"; - - string description = $"{message}. Only instance fields and properties can be exported." + - " Remove the 'static' modifier or the '[Export]' attribute."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0101", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0101")), - location, - location?.SourceTree?.FilePath)); - } - - public static void ReportExportedMemberTypeNotSupported( - GeneratorExecutionContext context, - ISymbol exportedMemberSymbol - ) - { - var locations = exportedMemberSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - bool isField = exportedMemberSymbol is IFieldSymbol; - - string message = $"The type of the exported {(isField ? "field" : "property")} " + - $"is not supported: '{exportedMemberSymbol.ToDisplayString()}'"; - - string description = $"{message}. Use a supported type or remove the '[Export]' attribute."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0102", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0102")), - location, - location?.SourceTree?.FilePath)); - } - - public static void ReportExportedMemberIsReadOnly( - GeneratorExecutionContext context, - ISymbol exportedMemberSymbol - ) - { - var locations = exportedMemberSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - bool isField = exportedMemberSymbol is IFieldSymbol; - - string message = $"The exported {(isField ? "field" : "property")} " + - $"is read-only: '{exportedMemberSymbol.ToDisplayString()}'"; - - string description = isField ? - $"{message}. Exported fields cannot be read-only." : - $"{message}. Exported properties must be writable."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0103", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0103")), - location, - location?.SourceTree?.FilePath)); - } - - public static void ReportExportedMemberIsWriteOnly( - GeneratorExecutionContext context, - ISymbol exportedMemberSymbol - ) - { - var locations = exportedMemberSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - - string message = $"The exported property is write-only: '{exportedMemberSymbol.ToDisplayString()}'"; - - string description = $"{message}. Exported properties must be readable."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0104", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0104")), - location, - location?.SourceTree?.FilePath)); - } - - public static void ReportExportedMemberIsIndexer( - GeneratorExecutionContext context, - ISymbol exportedMemberSymbol - ) - { - var locations = exportedMemberSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - - string message = $"Attempted to export indexer property: " + - $"'{exportedMemberSymbol.ToDisplayString()}'"; - - string description = $"{message}. Indexer properties can't be exported." + - " Remove the '[Export]' attribute."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0105", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0105")), - location, - location?.SourceTree?.FilePath)); - } - - public static void ReportExportedMemberIsExplicitInterfaceImplementation( - GeneratorExecutionContext context, - ISymbol exportedMemberSymbol - ) - { - var locations = exportedMemberSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - - string message = $"Attempted to export explicit interface property implementation: " + - $"'{exportedMemberSymbol.ToDisplayString()}'"; - - string description = $"{message}. Explicit interface implementations can't be exported." + - " Remove the '[Export]' attribute."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0106", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0106")), - location, - location?.SourceTree?.FilePath)); - } - - public static void ReportOnlyNodesShouldExportNodes( - GeneratorExecutionContext context, - ISymbol exportedMemberSymbol - ) - { - var locations = exportedMemberSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - bool isField = exportedMemberSymbol is IFieldSymbol; - - string message = $"Types not derived from Node should not export Node {(isField ? "fields" : "properties")}"; - - string description = $"{message}. Node export is only supported in Node-derived classes."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0107", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description), - location, - location?.SourceTree?.FilePath)); - } - - public static void ReportSignalDelegateMissingSuffix( - GeneratorExecutionContext context, - INamedTypeSymbol delegateSymbol) - { - var locations = delegateSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); - - string message = "The name of the delegate must end with 'EventHandler': " + - delegateSymbol.ToDisplayString() + - $". Did you mean '{delegateSymbol.Name}EventHandler'?"; + public static readonly DiagnosticDescriptor MultipleClassesInGodotScriptRule = + new DiagnosticDescriptor(id: "GD0003", + title: "Found multiple classes with the same name in the same script file", + messageFormat: "Found multiple classes with the name '{0}' in the same script file", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "Found multiple classes with the same name in the same script file. A script file must only contain one class with a name that matches the file name.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0003")); - string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute."; + public static readonly DiagnosticDescriptor ExportedMemberIsStaticRule = + new DiagnosticDescriptor(id: "GD0101", + title: "The exported member is static", + messageFormat: "The exported member '{0}' is static", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported member is static. Only instance fields and properties can be exported. Remove the 'static' modifier, or the '[Export]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0101")); - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0201", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0201")), - location, - location?.SourceTree?.FilePath)); - } + public static readonly DiagnosticDescriptor ExportedMemberTypeIsNotSupportedRule = + new DiagnosticDescriptor(id: "GD0102", + title: "The type of the exported member is not supported", + messageFormat: "The type of the exported member '{0}' is not supported", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The type of the exported member is not supported. Use a supported type, or remove the '[Export]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0102")); - public static void ReportSignalParameterTypeNotSupported( - GeneratorExecutionContext context, - IParameterSymbol parameterSymbol) - { - var locations = parameterSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + public static readonly DiagnosticDescriptor ExportedMemberIsReadOnlyRule = + new DiagnosticDescriptor(id: "GD0103", + title: "The exported member is read-only", + messageFormat: "The exported member '{0}' is read-only", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported member is read-only. Exported member must be writable.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0103")); - string message = "The parameter of the delegate signature of the signal " + - $"is not supported: '{parameterSymbol.ToDisplayString()}'"; + public static readonly DiagnosticDescriptor ExportedPropertyIsWriteOnlyRule = + new DiagnosticDescriptor(id: "GD0104", + title: "The exported property is write-only", + messageFormat: "The exported property '{0}' is write-only", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported property is write-only. Exported properties must be readable.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0104")); - string description = $"{message}. Use supported types only or remove the '[Signal]' attribute."; + public static readonly DiagnosticDescriptor ExportedMemberIsIndexerRule = + new DiagnosticDescriptor(id: "GD0105", + title: "The exported property is an indexer", + messageFormat: "The exported property '{0}' is an indexer", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported property is an indexer. Remove the '[Export]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0105")); - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0202", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0202")), - location, - location?.SourceTree?.FilePath)); - } + public static readonly DiagnosticDescriptor ExportedMemberIsExplicitInterfaceImplementationRule = + new DiagnosticDescriptor(id: "GD0106", + title: "The exported property is an explicit interface implementation", + messageFormat: "The exported property '{0}' is an explicit interface implementation", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported property is an explicit interface implementation. Remove the '[Export]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0106")); - public static void ReportSignalDelegateSignatureMustReturnVoid( - GeneratorExecutionContext context, - INamedTypeSymbol delegateSymbol) - { - var locations = delegateSymbol.Locations; - var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + public static readonly DiagnosticDescriptor OnlyNodesShouldExportNodesRule = + new DiagnosticDescriptor(id: "GD0107", + title: "Types not derived from Node should not export Node members", + messageFormat: "Types not derived from Node should not export Node members", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "Types not derived from Node should not export Node members. Node export is only supported in Node-derived classes.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0107")); - string message = "The delegate signature of the signal " + - $"must return void: '{delegateSymbol.ToDisplayString()}'"; + public static readonly DiagnosticDescriptor SignalDelegateMissingSuffixRule = + new DiagnosticDescriptor(id: "GD0201", + title: "The name of the delegate must end with 'EventHandler'", + messageFormat: "The name of the delegate '{0}' must end with 'EventHandler'", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The name of the delegate must end with 'EventHandler'. Rename the delegate accordingly, or remove the '[Signal]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0201")); - string description = $"{message}. Return void or remove the '[Signal]' attribute."; + public static readonly DiagnosticDescriptor SignalParameterTypeNotSupportedRule = + new DiagnosticDescriptor(id: "GD0202", + title: "The parameter of the delegate signature of the signal is not supported", + messageFormat: "The parameter of the delegate signature of the signal '{0}' is not supported", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The parameter of the delegate signature of the signal is not supported. Use supported types only, or remove the '[Signal]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0202")); - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0203", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0203")), - location, - location?.SourceTree?.FilePath)); - } + public static readonly DiagnosticDescriptor SignalDelegateSignatureMustReturnVoidRule = + new DiagnosticDescriptor(id: "GD0203", + title: "The delegate signature of the signal must return void", + messageFormat: "The delegate signature of the signal '{0}' must return void", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The delegate signature of the signal must return void. Return void, or remove the '[Signal]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0203")); public static readonly DiagnosticDescriptor GenericTypeArgumentMustBeVariantRule = new DiagnosticDescriptor(id: "GD0301", title: "The generic type argument must be a Variant compatible type", - messageFormat: "The generic type argument must be a Variant compatible type: {0}", + messageFormat: "The generic type argument '{0}' must be a Variant compatible type", category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument.", helpLinkUri: string.Format(_helpLinkFormat, "GD0301")); - public static void ReportGenericTypeArgumentMustBeVariant( - SyntaxNodeAnalysisContext context, - SyntaxNode typeArgumentSyntax, - ISymbol typeArgumentSymbol) - { - string message = "The generic type argument " + - $"must be a Variant compatible type: '{typeArgumentSymbol.ToDisplayString()}'"; - - string description = $"{message}. Use a Variant compatible type as the generic type argument."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0301", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0301")), - typeArgumentSyntax.GetLocation(), - typeArgumentSyntax.SyntaxTree.FilePath)); - } - public static readonly DiagnosticDescriptor GenericTypeParameterMustBeVariantAnnotatedRule = new DiagnosticDescriptor(id: "GD0302", - title: "The generic type parameter must be annotated with the MustBeVariant attribute", - messageFormat: "The generic type argument must be a Variant type: {0}", + title: "The generic type parameter must be annotated with the '[MustBeVariant]' attribute", + messageFormat: "The generic type parameter '{0}' must be annotated with the '[MustBeVariant]' attribute", category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.", + "The generic type parameter must be annotated with the '[MustBeVariant]' attribute. Add the '[MustBeVariant]' attribute to the generic type parameter.", helpLinkUri: string.Format(_helpLinkFormat, "GD0302")); - public static void ReportGenericTypeParameterMustBeVariantAnnotated( - SyntaxNodeAnalysisContext context, - SyntaxNode typeArgumentSyntax, - ISymbol typeArgumentSymbol) - { - string message = "The generic type parameter must be annotated with the MustBeVariant attribute"; - - string description = $"{message}. Add the MustBeVariant attribute to the generic type parameter."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0302", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0302")), - typeArgumentSyntax.GetLocation(), - typeArgumentSyntax.SyntaxTree.FilePath)); - } - public static readonly DiagnosticDescriptor TypeArgumentParentSymbolUnhandledRule = new DiagnosticDescriptor(id: "GD0303", - title: "The generic type parameter must be annotated with the MustBeVariant attribute", - messageFormat: "The generic type argument must be a Variant type: {0}", + title: "The parent symbol of a type argument that must be Variant compatible was not handled", + messageFormat: "The parent symbol '{0}' of a type argument that must be Variant compatible was not handled", category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.", + "The parent symbol of a type argument that must be Variant compatible was not handled. This is an issue in the engine, and should be reported.", helpLinkUri: string.Format(_helpLinkFormat, "GD0303")); - public static void ReportTypeArgumentParentSymbolUnhandled( - SyntaxNodeAnalysisContext context, - SyntaxNode typeArgumentSyntax, - ISymbol parentSymbol) - { - string message = $"Symbol '{parentSymbol.ToDisplayString()}' parent of a type argument " + - "that must be Variant compatible was not handled."; - - string description = $"{message}. Handle type arguments that are children of the unhandled symbol type."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0303", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0303")), - typeArgumentSyntax.GetLocation(), - typeArgumentSyntax.SyntaxTree.FilePath)); - } - public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule = new DiagnosticDescriptor(id: "GD0401", - title: "The class must derive from GodotObject or a derived class", - messageFormat: "The class '{0}' must derive from GodotObject or a derived class", + title: $"The class must derive from {GodotClasses.GodotObject} or a derived class", + messageFormat: $"The class '{{0}}' must derive from {GodotClasses.GodotObject} or a derived class", category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute.", + $"The class must derive from {GodotClasses.GodotObject} or a derived class. Change the base type, or remove the '[GlobalClass]' attribute.", helpLinkUri: string.Format(_helpLinkFormat, "GD0401")); - public static void ReportGlobalClassMustDeriveFromGodotObject( - SyntaxNodeAnalysisContext context, - SyntaxNode classSyntax, - ISymbol typeSymbol) - { - string message = $"The class '{typeSymbol.ToDisplayString()}' must derive from GodotObject or a derived class"; - - string description = $"{message}. Change the base class or remove the '[GlobalClass]' attribute."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0401", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0401")), - classSyntax.GetLocation(), - classSyntax.SyntaxTree.FilePath)); - } - public static readonly DiagnosticDescriptor GlobalClassMustNotBeGenericRule = new DiagnosticDescriptor(id: "GD0402", - title: "The class must not contain generic arguments", - messageFormat: "The class '{0}' must not contain generic arguments", + title: "The class must not be generic", + messageFormat: "The class '{0}' must not be generic", category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute.", - helpLinkUri: string.Format(_helpLinkFormat, "GD0401")); - - public static void ReportGlobalClassMustNotBeGeneric( - SyntaxNodeAnalysisContext context, - SyntaxNode classSyntax, - ISymbol typeSymbol) - { - string message = $"The class '{typeSymbol.ToDisplayString()}' must not contain generic arguments"; - - string description = $"{message}. Remove the generic arguments or the '[GlobalClass]' attribute."; - - context.ReportDiagnostic(Diagnostic.Create( - new DiagnosticDescriptor(id: "GD0402", - title: message, - messageFormat: message, - category: "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description, - helpLinkUri: string.Format(_helpLinkFormat, "GD0402")), - classSyntax.GetLocation(), - classSyntax.SyntaxTree.FilePath)); - } + "The class must not be generic. Make the class non-generic, or remove the '[GlobalClass]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0402")); } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index c7fd45238d..35db0d6f10 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -329,6 +329,11 @@ namespace Godot.SourceGenerators } } + public static Location? FirstLocationWithSourceTreeOrDefault(this IEnumerable<Location> locations) + { + return locations.FirstOrDefault(location => location.SourceTree != null) ?? locations.FirstOrDefault(); + } + public static string Path(this Location location) => location.SourceTree?.GetLineSpan(location.SourceSpan).Path ?? location.GetLineSpan().Path; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs index bcb35dae8a..77530ea049 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs @@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.Diagnostics; namespace Godot.SourceGenerators { [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class GlobalClassAnalyzer : DiagnosticAnalyzer + public sealed class GlobalClassAnalyzer : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create( @@ -33,10 +33,22 @@ namespace Godot.SourceGenerators return; if (typeSymbol.IsGenericType) - Common.ReportGlobalClassMustNotBeGeneric(context, typeClassDecl, typeSymbol); + { + context.ReportDiagnostic(Diagnostic.Create( + Common.GlobalClassMustNotBeGenericRule, + typeSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + typeSymbol.ToDisplayString() + )); + } if (!typeSymbol.InheritsFrom("GodotSharp", GodotClasses.GodotObject)) - Common.ReportGlobalClassMustDeriveFromGodotObject(context, typeClassDecl, typeSymbol); + { + context.ReportDiagnostic(Diagnostic.Create( + Common.GlobalClassMustDeriveFromGodotObjectRule, + typeSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + typeSymbol.ToDisplayString() + )); + } } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs index b4f78fd218..95eaca4d3d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis.Diagnostics; namespace Godot.SourceGenerators { [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class MustBeVariantAnalyzer : DiagnosticAnalyzer + public sealed class MustBeVariantAnalyzer : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create( @@ -62,7 +62,11 @@ namespace Godot.SourceGenerators { if (!typeParamSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false)) { - Common.ReportGenericTypeParameterMustBeVariantAnnotated(context, typeSyntax, typeSymbol); + context.ReportDiagnostic(Diagnostic.Create( + Common.GenericTypeParameterMustBeVariantAnnotatedRule, + typeSyntax.GetLocation(), + typeSymbol.ToDisplayString() + )); } continue; } @@ -71,8 +75,11 @@ namespace Godot.SourceGenerators if (marshalType is null) { - Common.ReportGenericTypeArgumentMustBeVariant(context, typeSyntax, typeSymbol); - continue; + context.ReportDiagnostic(Diagnostic.Create( + Common.GenericTypeArgumentMustBeVariantRule, + typeSyntax.GetLocation(), + typeSymbol.ToDisplayString() + )); } } } @@ -106,8 +113,15 @@ namespace Godot.SourceGenerators /// <param name="parentSymbol">The symbol retrieved for the parent node syntax.</param> /// <param name="typeArgumentSyntax">The type node syntax of the argument type to check.</param> /// <param name="typeArgumentSymbol">The symbol retrieved for the type node syntax.</param> + /// <param name="typeArgumentIndex"></param> /// <returns><see langword="true"/> if the type must be variant and must be analyzed.</returns> - private bool ShouldCheckTypeArgument(SyntaxNodeAnalysisContext context, SyntaxNode parentSyntax, ISymbol parentSymbol, TypeSyntax typeArgumentSyntax, ITypeSymbol typeArgumentSymbol, int typeArgumentIndex) + private bool ShouldCheckTypeArgument( + SyntaxNodeAnalysisContext context, + SyntaxNode parentSyntax, + ISymbol parentSymbol, + TypeSyntax typeArgumentSyntax, + ITypeSymbol typeArgumentSymbol, + int typeArgumentIndex) { ITypeParameterSymbol? typeParamSymbol = parentSymbol switch { @@ -120,18 +134,24 @@ namespace Godot.SourceGenerators INamedTypeSymbol { TypeParameters.Length: > 0 } typeSymbol => typeSymbol.TypeParameters[typeArgumentIndex], + _ => null }; - if (typeParamSymbol == null) + if (typeParamSymbol != null) { - Common.ReportTypeArgumentParentSymbolUnhandled(context, typeArgumentSyntax, parentSymbol); - return false; + return typeParamSymbol.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false); } - return typeParamSymbol.GetAttributes() - .Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false); + context.ReportDiagnostic(Diagnostic.Create( + Common.TypeArgumentParentSymbolUnhandledRule, + typeArgumentSyntax.GetLocation(), + parentSymbol.ToDisplayString() + )); + + return false; } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index 625a6f9921..6dc541cc59 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -58,9 +58,10 @@ namespace Godot.SourceGenerators .GroupBy<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol), INamedTypeSymbol>(x => x.symbol, SymbolEqualityComparer.Default) .ToDictionary<IGrouping<INamedTypeSymbol, (ClassDeclarationSyntax cds, INamedTypeSymbol symbol)>, INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>>(g => g.Key, g => g.Select(x => x.cds), SymbolEqualityComparer.Default); + var usedPaths = new HashSet<string>(); foreach (var godotClass in godotClasses) { - VisitGodotScriptClass(context, godotProjectDir, + VisitGodotScriptClass(context, godotProjectDir, usedPaths, symbol: godotClass.Key, classDeclarations: godotClass.Value); } @@ -74,6 +75,7 @@ namespace Godot.SourceGenerators private static void VisitGodotScriptClass( GeneratorExecutionContext context, string godotProjectDir, + HashSet<string> usedPaths, INamedTypeSymbol symbol, IEnumerable<ClassDeclarationSyntax> classDeclarations ) @@ -93,8 +95,19 @@ namespace Godot.SourceGenerators if (attributes.Length != 0) attributes.Append("\n"); + string scriptPath = RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir); + if (!usedPaths.Add(scriptPath)) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.MultipleClassesInGodotScriptRule, + cds.Identifier.GetLocation(), + symbol.Name + )); + return; + } + attributes.Append(@"[ScriptPathAttribute(""res://"); - attributes.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir)); + attributes.Append(scriptPath); attributes.Append(@""")]"); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 6e034c6e72..9c883ffbf0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -443,14 +443,22 @@ namespace Godot.SourceGenerators if (propertySymbol.GetMethod == null) { // This should never happen, as we filtered WriteOnly properties, but just in case. - Common.ReportExportedMemberIsWriteOnly(context, propertySymbol); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedPropertyIsWriteOnlyRule, + propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + propertySymbol.ToDisplayString() + )); return null; } if (propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly) { // This should never happen, as we filtered ReadOnly properties, but just in case. - Common.ReportExportedMemberIsReadOnly(context, propertySymbol); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberIsReadOnlyRule, + propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + propertySymbol.ToDisplayString() + )); return null; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index 253e24f092..27e7632ff7 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -131,33 +131,55 @@ namespace Godot.SourceGenerators { if (property.IsStatic) { - Common.ReportExportedMemberIsStatic(context, property); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberIsStaticRule, + property.Locations.FirstLocationWithSourceTreeOrDefault(), + property.ToDisplayString() + )); continue; } if (property.IsIndexer) { - Common.ReportExportedMemberIsIndexer(context, property); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberIsIndexerRule, + property.Locations.FirstLocationWithSourceTreeOrDefault(), + property.ToDisplayString() + )); continue; } - // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. - // Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable. + // TODO: We should still restore read-only properties after reloading assembly. + // Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter, without a setter or with an init-only setter. + // Godot properties must be both readable and writable. if (property.IsWriteOnly) { - Common.ReportExportedMemberIsWriteOnly(context, property); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedPropertyIsWriteOnlyRule, + property.Locations.FirstLocationWithSourceTreeOrDefault(), + property.ToDisplayString() + )); continue; } if (property.IsReadOnly || property.SetMethod!.IsInitOnly) { - Common.ReportExportedMemberIsReadOnly(context, property); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberIsReadOnlyRule, + property.Locations.FirstLocationWithSourceTreeOrDefault(), + property.ToDisplayString() + )); continue; } if (property.ExplicitInterfaceImplementations.Length > 0) { - Common.ReportExportedMemberIsExplicitInterfaceImplementation(context, property); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberIsExplicitInterfaceImplementationRule, + property.Locations.FirstLocationWithSourceTreeOrDefault(), + property.ToDisplayString() + )); continue; } @@ -166,7 +188,11 @@ namespace Godot.SourceGenerators if (marshalType == null) { - Common.ReportExportedMemberTypeNotSupported(context, property); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberTypeIsNotSupportedRule, + property.Locations.FirstLocationWithSourceTreeOrDefault(), + property.ToDisplayString() + )); continue; } @@ -175,7 +201,10 @@ namespace Godot.SourceGenerators if (!symbol.InheritsFrom("GodotSharp", "Godot.Node") && propertyType.InheritsFrom("GodotSharp", "Godot.Node")) { - Common.ReportOnlyNodesShouldExportNodes(context, property); + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyNodesShouldExportNodesRule, + property.Locations.FirstLocationWithSourceTreeOrDefault() + )); } } @@ -194,7 +223,7 @@ namespace Godot.SourceGenerators else { var propertyGet = propertyDeclarationSyntax.AccessorList?.Accessors - .Where(a => a.Keyword.IsKind(SyntaxKind.GetKeyword)).FirstOrDefault(); + .FirstOrDefault(a => a.Keyword.IsKind(SyntaxKind.GetKeyword)); if (propertyGet != null) { if (propertyGet.ExpressionBody != null) @@ -253,7 +282,11 @@ namespace Godot.SourceGenerators { if (field.IsStatic) { - Common.ReportExportedMemberIsStatic(context, field); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberIsStaticRule, + field.Locations.FirstLocationWithSourceTreeOrDefault(), + field.ToDisplayString() + )); continue; } @@ -261,7 +294,11 @@ namespace Godot.SourceGenerators // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. if (field.IsReadOnly) { - Common.ReportExportedMemberIsReadOnly(context, field); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberIsReadOnlyRule, + field.Locations.FirstLocationWithSourceTreeOrDefault(), + field.ToDisplayString() + )); continue; } @@ -270,7 +307,11 @@ namespace Godot.SourceGenerators if (marshalType == null) { - Common.ReportExportedMemberTypeNotSupported(context, field); + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedMemberTypeIsNotSupportedRule, + field.Locations.FirstLocationWithSourceTreeOrDefault(), + field.ToDisplayString() + )); continue; } @@ -279,7 +320,10 @@ namespace Godot.SourceGenerators if (!symbol.InheritsFrom("GodotSharp", "Godot.Node") && fieldType.InheritsFrom("GodotSharp", "Godot.Node")) { - Common.ReportOnlyNodesShouldExportNodes(context, field); + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyNodesShouldExportNodesRule, + field.Locations.FirstLocationWithSourceTreeOrDefault() + )); } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 5246cc5780..323af85d3b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -136,7 +136,11 @@ namespace Godot.SourceGenerators { if (!signalDelegateSymbol.Name.EndsWith(SignalDelegateSuffix)) { - Common.ReportSignalDelegateMissingSuffix(context, signalDelegateSymbol); + context.ReportDiagnostic(Diagnostic.Create( + Common.SignalDelegateMissingSuffixRule, + signalDelegateSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + signalDelegateSymbol.ToDisplayString() + )); continue; } @@ -154,21 +158,32 @@ namespace Godot.SourceGenerators { if (parameter.RefKind != RefKind.None) { - Common.ReportSignalParameterTypeNotSupported(context, parameter); + context.ReportDiagnostic(Diagnostic.Create( + Common.SignalParameterTypeNotSupportedRule, + parameter.Locations.FirstLocationWithSourceTreeOrDefault(), + parameter.ToDisplayString() + )); continue; } var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(parameter.Type, typeCache); - if (marshalType == null) { - Common.ReportSignalParameterTypeNotSupported(context, parameter); + context.ReportDiagnostic(Diagnostic.Create( + Common.SignalParameterTypeNotSupportedRule, + parameter.Locations.FirstLocationWithSourceTreeOrDefault(), + parameter.ToDisplayString() + )); } } if (!methodSymbol.ReturnsVoid) { - Common.ReportSignalDelegateSignatureMustReturnVoid(context, signalDelegateSymbol); + context.ReportDiagnostic(Diagnostic.Create( + Common.SignalDelegateSignatureMustReturnVoidRule, + signalDelegateSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + signalDelegateSymbol.ToDisplayString() + )); } } diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 01aa65bfc3..3c46079414 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -7,11 +7,11 @@ namespace GodotTools.BuildLogger { public class GodotBuildLogger : ILogger { - public string Parameters { get; set; } + public string? Parameters { get; set; } public LoggerVerbosity Verbosity { get; set; } - private StreamWriter _logStreamWriter; - private StreamWriter _issuesStreamWriter; + private StreamWriter _logStreamWriter = StreamWriter.Null; + private StreamWriter _issuesStreamWriter = StreamWriter.Null; private int _indent; public void Initialize(IEventSource eventSource) diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index d0972f1eae..fd836f9ad2 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -3,6 +3,7 @@ <ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid> <TargetFramework>net6.0</TargetFramework> <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" ExcludeAssets="runtime" /> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index cfd5c88a58..0755484465 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -3,5 +3,6 @@ <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid> <TargetFramework>net6.0</TargetFramework> <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> </PropertyGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs index a4d7dedbd5..4dd18b4c03 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs @@ -11,7 +11,7 @@ namespace GodotTools.Core { var tcs = new TaskCompletionSource<bool>(); - void ProcessExited(object sender, EventArgs e) + void ProcessExited(object? sender, EventArgs e) { tcs.TrySetResult(true); } diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index d86a77d222..d5c3a92351 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -7,7 +7,7 @@ namespace GodotTools.Core { public static class StringExtensions { - private static readonly string _driveRoot = Path.GetPathRoot(Environment.CurrentDirectory); + private static readonly string _driveRoot = Path.GetPathRoot(Environment.CurrentDirectory)!; public static string RelativeToPath(this string path, string dir) { @@ -26,9 +26,6 @@ namespace GodotTools.Core public static string NormalizePath(this string path) { - if (string.IsNullOrEmpty(path)) - return path; - bool rooted = path.IsAbsolutePath(); path = path.Replace('\\', '/'); diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj index eb6ac8bafc..3bf678e9f9 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj @@ -4,6 +4,7 @@ <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs index 450c4bf0cb..e344aa4a37 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs @@ -64,14 +64,14 @@ namespace GodotTools.IdeMessaging.CLI while (!fwdClient.IsDisposed) { - string firstLine = await inputReader.ReadLineAsync(); + string? firstLine = await inputReader.ReadLineAsync(); if (firstLine == null || firstLine == "QUIT") goto ExitMainLoop; string messageId = firstLine; - string messageArgcLine = await inputReader.ReadLineAsync(); + string? messageArgcLine = await inputReader.ReadLineAsync(); if (messageArgcLine == null) { @@ -89,7 +89,7 @@ namespace GodotTools.IdeMessaging.CLI for (int i = 0; i < messageArgc; i++) { - string bodyLine = await inputReader.ReadLineAsync(); + string? bodyLine = await inputReader.ReadLineAsync(); if (bodyLine == null) { @@ -126,29 +126,29 @@ namespace GodotTools.IdeMessaging.CLI } } - private static async Task<Response> SendRequest(Client client, string id, MessageContent content) + private static async Task<Response?> SendRequest(Client client, string id, MessageContent content) { - var handlers = new Dictionary<string, Func<Task<Response>>> + var handlers = new Dictionary<string, Func<Task<Response?>>> { [PlayRequest.Id] = async () => { var request = JsonConvert.DeserializeObject<PlayRequest>(content.Body); - return await client.SendRequest<PlayResponse>(request); + return await client.SendRequest<PlayResponse>(request!); }, [DebugPlayRequest.Id] = async () => { var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body); - return await client.SendRequest<DebugPlayResponse>(request); + return await client.SendRequest<DebugPlayResponse>(request!); }, [ReloadScriptsRequest.Id] = async () => { var request = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body); - return await client.SendRequest<ReloadScriptsResponse>(request); + return await client.SendRequest<ReloadScriptsResponse>(request!); }, [CodeCompletionRequest.Id] = async () => { var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body); - return await client.SendRequest<CodeCompletionResponse>(request); + return await client.SendRequest<CodeCompletionResponse>(request!); } }; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs index 72e2a1fc0d..7bfa07be0b 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Collections.Generic; using System.IO; using System.Net; @@ -27,7 +28,7 @@ namespace GodotTools.IdeMessaging private readonly IMessageHandler messageHandler; - private Peer peer; + private Peer? peer; private readonly SemaphoreSlim connectionSem = new SemaphoreSlim(1); private readonly Queue<NotifyAwaiter<bool>> clientConnectedAwaiters = new Queue<NotifyAwaiter<bool>>(); @@ -53,6 +54,7 @@ namespace GodotTools.IdeMessaging public bool IsDisposed { get; private set; } // ReSharper disable once MemberCanBePrivate.Global + [MemberNotNullWhen(true, "peer")] public bool IsConnected => peer != null && !peer.IsDisposed && peer.IsTcpClientConnected; // ReSharper disable once EventNeverSubscribedTo.Global @@ -111,7 +113,7 @@ namespace GodotTools.IdeMessaging if (disposing) { peer?.Dispose(); - fsWatcher?.Dispose(); + fsWatcher.Dispose(); } } @@ -215,12 +217,12 @@ namespace GodotTools.IdeMessaging using (var fileStream = new FileStream(MetaFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var reader = new StreamReader(fileStream)) { - string portStr = reader.ReadLine(); + string? portStr = reader.ReadLine(); if (portStr == null) return null; - string editorExecutablePath = reader.ReadLine(); + string? editorExecutablePath = reader.ReadLine(); if (editorExecutablePath == null) return null; @@ -333,7 +335,7 @@ namespace GodotTools.IdeMessaging } } - public async Task<TResponse> SendRequest<TResponse>(Request request) + public async Task<TResponse?> SendRequest<TResponse>(Request request) where TResponse : Response, new() { if (!IsConnected) @@ -346,7 +348,7 @@ namespace GodotTools.IdeMessaging return await peer.SendRequest<TResponse>(request.Id, body); } - public async Task<TResponse> SendRequest<TResponse>(string id, string body) + public async Task<TResponse?> SendRequest<TResponse>(string id, string body) where TResponse : Response, new() { if (!IsConnected) diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs index 43041be7be..7fef628c79 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace GodotTools.IdeMessaging @@ -9,7 +10,7 @@ namespace GodotTools.IdeMessaging public string GetHandshakeLine(string identity) => $"{ClientHandshakeBase},{identity}"; - public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger) + public bool IsValidPeerHandshake(string handshake, [NotNullWhen(true)] out string? identity, ILogger logger) { identity = null; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs index 64bcfd824c..0321aa1b57 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs @@ -42,7 +42,7 @@ namespace GodotTools.IdeMessaging [OpenFileRequest.Id] = async (peer, content) => { var request = JsonConvert.DeserializeObject<OpenFileRequest>(content.Body); - return await HandleOpenFile(request); + return await HandleOpenFile(request!); } }; } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs new file mode 100644 index 0000000000..a4a7ee82df --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs @@ -0,0 +1,142 @@ +// ReSharper disable once CheckNamespace +namespace System.Diagnostics.CodeAnalysis +{ + /// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute + { } + + /// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class DisallowNullAttribute : Attribute + { } + + /// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute + { } + + /// <summary>Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.</summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute + { } + + /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary> + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + /// <summary>Initializes the attribute with the specified return value condition.</summary> + /// <param name="returnValue"> + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// </param> + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// <summary>Gets the return value condition.</summary> + public bool ReturnValue { get; } + } + + /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary> + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// <summary>Initializes the attribute with the specified return value condition.</summary> + /// <param name="returnValue"> + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// </param> + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// <summary>Gets the return value condition.</summary> + public bool ReturnValue { get; } + } + + /// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary> + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + internal sealed class NotNullIfNotNullAttribute : Attribute + { + /// <summary>Initializes the attribute with the associated parameter name.</summary> + /// <param name="parameterName"> + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// </param> + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// <summary>Gets the associated parameter name.</summary> + public string ParameterName { get; } + } + + /// <summary>Applied to a method that will never return under any circumstance.</summary> + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + internal sealed class DoesNotReturnAttribute : Attribute + { } + + /// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary> + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class DoesNotReturnIfAttribute : Attribute + { + /// <summary>Initializes the attribute with the specified parameter value.</summary> + /// <param name="parameterValue"> + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// </param> + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// <summary>Gets the condition parameter value.</summary> + public bool ParameterValue { get; } + } + + /// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary> + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullAttribute : Attribute + { + /// <summary>Initializes the attribute with a field or property member.</summary> + /// <param name="member"> + /// The field or property member that is promised to be not-null. + /// </param> + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// <summary>Initializes the attribute with the list of field and property members.</summary> + /// <param name="members"> + /// The list of field and property members that are promised to be not-null. + /// </param> + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// <summary>Gets field or property member names.</summary> + public string[] Members { get; } + } + + /// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary> + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + /// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary> + /// <param name="returnValue"> + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// </param> + /// <param name="member"> + /// The field or property member that is promised to be not-null. + /// </param> + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary> + /// <param name="returnValue"> + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// </param> + /// <param name="members"> + /// The list of field and property members that are promised to be not-null. + /// </param> + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// <summary>Gets the return value condition.</summary> + public bool ReturnValue { get; } + + /// <summary>Gets field or property member names.</summary> + public string[] Members { get; } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs index 2448a2953b..f11a7cc149 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace GodotTools.IdeMessaging { public readonly struct GodotIdeMetadata @@ -23,7 +25,7 @@ namespace GodotTools.IdeMessaging return !(a == b); } - public override bool Equals(object obj) + public override bool Equals([NotNullWhen(true)] object? obj) { return obj is GodotIdeMetadata metadata && metadata == this; } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj index 02f1764f32..be6af398e2 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj @@ -2,9 +2,10 @@ <PropertyGroup> <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid> <TargetFramework>netstandard2.0</TargetFramework> - <LangVersion>7.2</LangVersion> + <LangVersion>9</LangVersion> + <Nullable>enable</Nullable> <PackageId>GodotTools.IdeMessaging</PackageId> - <Version>1.1.1</Version> + <Version>1.1.2</Version> <AssemblyVersion>$(Version)</AssemblyVersion> <Authors>Godot Engine contributors</Authors> <Company /> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs index 6387145a28..8ec60876cc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs @@ -1,8 +1,10 @@ +using System.Diagnostics.CodeAnalysis; + namespace GodotTools.IdeMessaging { public interface IHandshake { string GetHandshakeLine(string identity); - bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger); + bool IsValidPeerHandshake(string handshake, [NotNullWhen(true)] out string? identity, ILogger logger); } } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs index a00575a2a1..caf1ae2705 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs @@ -8,7 +8,7 @@ namespace GodotTools.IdeMessaging private class DecodedMessage { public MessageKind? Kind; - public string Id; + public string? Id; public MessageStatus? Status; public readonly StringBuilder Body = new StringBuilder(); public uint? PendingBodyLines; @@ -41,7 +41,7 @@ namespace GodotTools.IdeMessaging private readonly DecodedMessage decodingMessage = new DecodedMessage(); - public State Decode(string messageLine, out Message decodedMessage) + public State Decode(string messageLine, out Message? decodedMessage) { decodedMessage = null; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs index dd3913b4f3..5e453dcfab 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs @@ -46,11 +46,11 @@ namespace GodotTools.IdeMessaging private readonly SemaphoreSlim writeSem = new SemaphoreSlim(1); - private string remoteIdentity = string.Empty; - public string RemoteIdentity => remoteIdentity; + private string? remoteIdentity; + public string RemoteIdentity => remoteIdentity ??= string.Empty; - public event Action Connected; - public event Action Disconnected; + public event Action? Connected; + public event Action? Disconnected; private ILogger Logger { get; } @@ -87,7 +87,7 @@ namespace GodotTools.IdeMessaging { var decoder = new MessageDecoder(); - string messageLine; + string? messageLine; while ((messageLine = await ReadLine()) != null) { var state = decoder.Decode(messageLine, out var msg); @@ -105,7 +105,7 @@ namespace GodotTools.IdeMessaging try { - if (msg.Kind == MessageKind.Request) + if (msg!.Kind == MessageKind.Request) { var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger); await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent)); @@ -163,9 +163,9 @@ namespace GodotTools.IdeMessaging return false; } - string peerHandshake = await readHandshakeTask; + string? peerHandshake = await readHandshakeTask; - if (handshake == null || !handshake.IsValidPeerHandshake(peerHandshake, out remoteIdentity, Logger)) + if (peerHandshake == null || !handshake.IsValidPeerHandshake(peerHandshake, out remoteIdentity, Logger)) { Logger.LogError("Received invalid handshake: " + peerHandshake); return false; @@ -179,7 +179,7 @@ namespace GodotTools.IdeMessaging return true; } - private async Task<string> ReadLine() + private async Task<string?> ReadLine() { try { @@ -216,7 +216,7 @@ namespace GodotTools.IdeMessaging return WriteLine(builder.ToString()); } - public async Task<TResponse> SendRequest<TResponse>(string id, string body) + public async Task<TResponse?> SendRequest<TResponse>(string id, string body) where TResponse : Response, new() { ResponseAwaiter responseAwaiter; @@ -243,7 +243,7 @@ namespace GodotTools.IdeMessaging private async Task<bool> WriteLine(string text) { - if (clientWriter == null || IsDisposed || !IsTcpClientConnected) + if (IsDisposed || !IsTcpClientConnected) return false; using (await writeSem.UseAsync()) @@ -290,9 +290,9 @@ namespace GodotTools.IdeMessaging Disconnected?.Invoke(); } - clientReader?.Dispose(); - clientWriter?.Dispose(); - ((IDisposable)tcpClient)?.Dispose(); + clientReader.Dispose(); + clientWriter.Dispose(); + ((IDisposable)tcpClient).Dispose(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs index e93db9377b..452593b673 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs @@ -2,6 +2,7 @@ // ReSharper disable UnusedMember.Global // ReSharper disable UnusedAutoPropertyAccessor.Global +using System; using Newtonsoft.Json; namespace GodotTools.IdeMessaging.Requests @@ -38,7 +39,7 @@ namespace GodotTools.IdeMessaging.Requests } public CompletionKind Kind { get; set; } - public string ScriptFile { get; set; } + public string ScriptFile { get; set; } = string.Empty; public new const string Id = "CodeCompletion"; @@ -50,8 +51,8 @@ namespace GodotTools.IdeMessaging.Requests public sealed class CodeCompletionResponse : Response { public CodeCompletionRequest.CompletionKind Kind; - public string ScriptFile { get; set; } - public string[] Suggestions { get; set; } + public string ScriptFile { get; set; } = string.Empty; + public string[] Suggestions { get; set; } = Array.Empty<string>(); } public sealed class PlayRequest : Request @@ -82,7 +83,7 @@ namespace GodotTools.IdeMessaging.Requests public sealed class DebugPlayRequest : Request { - public string DebuggerHost { get; set; } + public string DebuggerHost { get; set; } = string.Empty; public int DebuggerPort { get; set; } public bool? BuildBeforePlaying { get; set; } @@ -99,7 +100,7 @@ namespace GodotTools.IdeMessaging.Requests public sealed class OpenFileRequest : Request { - public string File { get; set; } + public string File { get; set; } = string.Empty; public int? Line { get; set; } public int? Column { get; set; } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs index a57c82b608..b09575d5d2 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs @@ -15,7 +15,7 @@ namespace GodotTools.IdeMessaging public override void SetResult(MessageContent content) { if (content.Status == MessageStatus.Ok) - SetResult(JsonConvert.DeserializeObject<T>(content.Body)); + SetResult(JsonConvert.DeserializeObject<T>(content.Body)!); else SetResult(new T { Status = content.Status }); } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs index a285d5fa97..6dde1828a9 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs @@ -1,3 +1,6 @@ +// ReSharper disable ParameterHidesMember +// ReSharper disable UnusedMember.Global + using System; using System.Runtime.CompilerServices; @@ -5,9 +8,9 @@ namespace GodotTools.IdeMessaging.Utils { public class NotifyAwaiter<T> : INotifyCompletion { - private Action continuation; - private Exception exception; - private T result; + private Action? continuation; + private Exception? exception; + private T? result; public bool IsCompleted { get; private set; } @@ -15,7 +18,7 @@ namespace GodotTools.IdeMessaging.Utils { if (exception != null) throw exception; - return result; + return result!; } public void OnCompleted(Action continuation) diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj index 22778f21cb..09908c85a1 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -4,10 +4,14 @@ <OutputType>Exe</OutputType> <TargetFramework>net6.0-windows</TargetFramework> <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> + <RuntimeIdentifier>win-x86</RuntimeIdentifier> + <SelfContained>False</SelfContained> </PropertyGroup> <PropertyGroup Condition="Exists('$(SolutionDir)/../../../../bin/GodotSharp/Api/Debug/GodotSharp.dll') And ('$(GodotPlatform)' == 'windows' Or ('$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT'))"> <OutputPath>$(SolutionDir)/../../../../bin/GodotSharp/Tools</OutputPath> <AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath> + <AppendRuntimeIdentifierToOutputPath>False</AppendRuntimeIdentifierToOutputPath> </PropertyGroup> <ItemGroup> <PackageReference Include="EnvDTE" Version="17.8.37221" /> diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs index 8e8eaa79b8..3072ca2857 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -53,6 +53,12 @@ namespace GodotTools.OpenVisualStudio { // Launch of VS 2022 failed, fallback to 2019 dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0"); + + if (dte == null) + { + Console.Error.WriteLine("Visual Studio not found"); + return 1; + } } dte.UserControl = true; @@ -137,12 +143,12 @@ namespace GodotTools.OpenVisualStudio return 0; } - private static DTE TryVisualStudioLaunch(string version) + private static DTE? TryVisualStudioLaunch(string version) { try { var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true); - var dte = (DTE)Activator.CreateInstance(visualStudioDteType); + var dte = (DTE?)Activator.CreateInstance(visualStudioDteType!); return dte; } @@ -152,7 +158,7 @@ namespace GodotTools.OpenVisualStudio } } - private static DTE FindInstanceEditingSolution(string solutionPath) + private static DTE? FindInstanceEditingSolution(string solutionPath) { if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0) return null; @@ -218,7 +224,7 @@ namespace GodotTools.OpenVisualStudio // Class containing the IOleMessageFilter // thread error-handling functions - private static IOleMessageFilter _oldFilter; + private static IOleMessageFilter? _oldFilter; // Start the filter public static void Register() @@ -268,7 +274,7 @@ namespace GodotTools.OpenVisualStudio // Implement the IOleMessageFilter interface [DllImport("ole32.dll")] - private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter); + private static extern int CoRegisterMessageFilter(IOleMessageFilter? newFilter, out IOleMessageFilter? oldFilter); } [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs index 355b21d63a..ff15b96c1c 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -34,22 +34,23 @@ EndProject"; @" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; - private string _directoryPath; private readonly Dictionary<string, ProjectInfo> _projects = new Dictionary<string, ProjectInfo>(); public string Name { get; } - - public string DirectoryPath - { - get => _directoryPath; - set => _directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); - } + public string DirectoryPath { get; } public class ProjectInfo { - public string Guid; - public string PathRelativeToSolution; - public List<string> Configs = new List<string>(); + public string Guid { get; } + public string PathRelativeToSolution { get; } + public List<string> Configs { get; } + + public ProjectInfo(string guid, string pathRelativeToSolution, List<string> configs) + { + Guid = guid; + PathRelativeToSolution = pathRelativeToSolution; + Configs = configs; + } } public void AddNewProject(string name, ProjectInfo projectInfo) @@ -117,9 +118,10 @@ EndProject"; File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM } - public DotNetSolution(string name) + public DotNetSolution(string name, string directoryPath) { Name = name; + DirectoryPath = directoryPath.IsAbsolutePath() ? directoryPath : Path.GetFullPath(directoryPath); } public static void MigrateFromOldConfigNames(string slnPath) diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index bde14b2b40..623475e11a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -3,6 +3,7 @@ <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> <TargetFramework>net6.0</TargetFramework> <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" /> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 1d17f3d4f5..794443a69c 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -34,7 +34,7 @@ namespace GodotTools.ProjectEditor public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath) => MSBuildLocator.RegisterMSBuildPath(msbuildPath); - public static MSBuildProject Open(string path) + public static MSBuildProject? Open(string path) { var root = ProjectRootElement.Open(path); return root != null ? new MSBuildProject(root) : null; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs index 6e0c63dd43..79eb9e2ce2 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace GodotTools.Build { public class BuildDiagnostic diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index 7c02f29606..be0b7d3222 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs @@ -1,11 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; using Godot; using Godot.Collections; using GodotTools.Internals; using Path = System.IO.Path; -#nullable enable - namespace GodotTools.Build { [Serializable] @@ -25,7 +24,7 @@ namespace GodotTools.Build public string LogsDirPath => GodotSharpDirs.LogsDirPathFor(Solution, Configuration); - public override bool Equals(object? obj) + public override bool Equals([NotNullWhen(true)] object? obj) { return obj is BuildInfo other && other.Solution == Solution && diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 6e99483a1c..ebb2677361 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -11,18 +11,18 @@ namespace GodotTools.Build { public static class BuildManager { - private static BuildInfo _buildInProgress; + private static BuildInfo? _buildInProgress; public const string MsBuildIssuesFileName = "msbuild_issues.csv"; private const string MsBuildLogFileName = "msbuild_log.txt"; public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason); - public static event BuildLaunchFailedEventHandler BuildLaunchFailed; - public static event Action<BuildInfo> BuildStarted; - public static event Action<BuildResult> BuildFinished; - public static event Action<string> StdOutputReceived; - public static event Action<string> StdErrorReceived; + public static event BuildLaunchFailedEventHandler? BuildLaunchFailed; + public static event Action<BuildInfo>? BuildStarted; + public static event Action<BuildResult>? BuildFinished; + public static event Action<string?>? StdOutputReceived; + public static event Action<string?>? StdErrorReceived; public static DateTime LastValidBuildDateTime { get; private set; } @@ -274,8 +274,8 @@ namespace GodotTools.Build } private static BuildInfo CreateBuildInfo( - [DisallowNull] string configuration, - [AllowNull] string platform = null, + string configuration, + string? platform = null, bool rebuild = false, bool onlyClean = false ) @@ -294,10 +294,10 @@ namespace GodotTools.Build } private static BuildInfo CreatePublishBuildInfo( - [DisallowNull] string configuration, - [DisallowNull] string platform, - [DisallowNull] string runtimeIdentifier, - [DisallowNull] string publishOutputDir, + string configuration, + string platform, + string runtimeIdentifier, + string publishOutputDir, bool includeDebugSymbols = true ) { @@ -319,20 +319,20 @@ namespace GodotTools.Build } public static bool BuildProjectBlocking( - [DisallowNull] string configuration, - [AllowNull] string platform = null, + string configuration, + string? platform = null, bool rebuild = false ) => BuildProjectBlocking(CreateBuildInfo(configuration, platform, rebuild)); public static bool CleanProjectBlocking( - [DisallowNull] string configuration, - [AllowNull] string platform = null + string configuration, + string? platform = null ) => CleanProjectBlocking(CreateBuildInfo(configuration, platform, rebuild: false, onlyClean: true)); public static bool PublishProjectBlocking( - [DisallowNull] string configuration, - [DisallowNull] string platform, - [DisallowNull] string runtimeIdentifier, + string configuration, + string platform, + string runtimeIdentifier, string publishOutputDir, bool includeDebugSymbols = true ) => PublishProjectBlocking(CreatePublishBuildInfo(configuration, diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index f9e85c36e5..5cf6581fc4 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -1,8 +1,6 @@ using Godot; using static GodotTools.Internals.Globals; -#nullable enable - namespace GodotTools.Build { public partial class BuildOutputView : HBoxContainer diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs index 9c165e5767..2071f687b3 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs @@ -1,7 +1,5 @@ using Godot; -#nullable enable - namespace GodotTools.Build { public class BuildProblemsFilter diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs index d62eb3ce9e..fef169aa07 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs @@ -8,8 +8,6 @@ using GodotTools.Internals; using static GodotTools.Internals.Globals; using FileAccess = Godot.FileAccess; -#nullable enable - namespace GodotTools.Build { public partial class BuildProblemsView : HBoxContainer @@ -244,7 +242,9 @@ namespace GodotTools.Build if (string.IsNullOrEmpty(projectDir)) return; - string file = Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath()); + string? file = !string.IsNullOrEmpty(diagnostic.File) ? + Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath()) : + null; if (!File.Exists(file)) return; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 57b5598a78..5ae4dfa076 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -17,10 +17,10 @@ namespace GodotTools.Build { public static class BuildSystem { - private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, - Action<string> stdErrHandler) + private static Process LaunchBuild(BuildInfo buildInfo, Action<string?>? stdOutHandler, + Action<string?>? stdErrHandler) { - string dotnetPath = DotNetFinder.FindDotNetExe(); + string? dotnetPath = DotNetFinder.FindDotNetExe(); if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); @@ -67,7 +67,7 @@ namespace GodotTools.Build return process; } - public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + public static int Build(BuildInfo buildInfo, Action<string?>? stdOutHandler, Action<string?>? stdErrHandler) { using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { @@ -77,8 +77,8 @@ namespace GodotTools.Build } } - public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, - Action<string> stdErrHandler) + public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string?>? stdOutHandler, + Action<string?>? stdErrHandler) { using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { @@ -88,10 +88,10 @@ namespace GodotTools.Build } } - private static Process LaunchPublish(BuildInfo buildInfo, Action<string> stdOutHandler, - Action<string> stdErrHandler) + private static Process LaunchPublish(BuildInfo buildInfo, Action<string?>? stdOutHandler, + Action<string?>? stdErrHandler) { - string dotnetPath = DotNetFinder.FindDotNetExe(); + string? dotnetPath = DotNetFinder.FindDotNetExe(); if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); @@ -137,7 +137,7 @@ namespace GodotTools.Build return process; } - public static int Publish(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + public static int Publish(BuildInfo buildInfo, Action<string?>? stdOutHandler, Action<string?>? stdErrHandler) { using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler)) { @@ -297,7 +297,7 @@ namespace GodotTools.Build } private static Process DoGenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, - Action<string> stdOutHandler, Action<string> stdErrHandler) + Action<string?>? stdOutHandler, Action<string?>? stdErrHandler) { if (Directory.Exists(xcFrameworkPath)) { @@ -341,7 +341,7 @@ namespace GodotTools.Build return process; } - public static int GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, Action<string> stdOutHandler, Action<string> stdErrHandler) + public static int GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, Action<string?>? stdOutHandler, Action<string?>? stdErrHandler) { using (var process = DoGenerateXCFramework(outputPaths, xcFrameworkPath, stdOutHandler, stdErrHandler)) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs index cfe79cf3e1..4d1456b70c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs @@ -5,15 +5,13 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; using System.Text; -using JetBrains.Annotations; using OS = GodotTools.Utils.OS; namespace GodotTools.Build { public static class DotNetFinder { - [CanBeNull] - public static string FindDotNetExe() + public static string? FindDotNetExe() { // In the future, this method may do more than just search in PATH. We could look in // known locations or use Godot's linked nethost to search from the hostfxr location. @@ -40,14 +38,14 @@ namespace GodotTools.Build public static bool TryFindDotNetSdk( Version expectedVersion, - [NotNullWhen(true)] out Version version, - [NotNullWhen(true)] out string path + [NotNullWhen(true)] out Version? version, + [NotNullWhen(true)] out string? path ) { version = null; path = null; - string dotNetExe = FindDotNetExe(); + string? dotNetExe = FindDotNetExe(); if (string.IsNullOrEmpty(dotNetExe)) return false; @@ -86,8 +84,8 @@ namespace GodotTools.Build process.BeginOutputReadLine(); process.WaitForExit(); - Version latestVersionMatch = null; - string matchPath = null; + Version? latestVersionMatch = null; + string? matchPath = null; foreach (var line in lines) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 9a02c9ca88..986da47860 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -5,8 +5,6 @@ using GodotTools.Internals; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; -#nullable enable - namespace GodotTools.Build { public partial class MSBuildPanel : MarginContainer, ISerializationListener @@ -183,7 +181,7 @@ namespace GodotTools.Build } } - private void StdOutputReceived(string text) + private void StdOutputReceived(string? text) { lock (_pendingBuildLogTextLock) { @@ -193,7 +191,7 @@ namespace GodotTools.Build } } - private void StdErrorReceived(string text) + private void StdErrorReceived(string? text) { lock (_pendingBuildLogTextLock) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index c4f0b77ecf..372c4b114b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -1,6 +1,7 @@ using Godot; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -73,7 +74,7 @@ namespace GodotTools.Export }; } - private string _maybeLastExportError; + private string? _maybeLastExportError; // With this method we can override how a file is exported in the PCK public override void _ExportFile(string path, string type, string[] features) @@ -135,7 +136,7 @@ namespace GodotTools.Export if (!ProjectContainsDotNet()) return; - if (!DeterminePlatformFromFeatures(features, out string platform)) + if (!DeterminePlatformFromFeatures(features, out string? platform)) throw new NotSupportedException("Target platform not supported."); if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS } @@ -327,7 +328,7 @@ namespace GodotTools.Export AddSharedObject(path, tags: null, Path.Join(projectDataDirName, Path.GetRelativePath(publishOutputDir, - Path.GetDirectoryName(path)))); + Path.GetDirectoryName(path)!))); } } } @@ -450,7 +451,7 @@ namespace GodotTools.Export } } - private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform) + private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform) { foreach (var feature in features) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs index 4f5bebfb42..023f46b685 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs @@ -5,7 +5,7 @@ namespace GodotTools.Export { public static class XcodeHelper { - private static string _XcodePath = null; + private static string? _XcodePath = null; public static string XcodePath { @@ -23,7 +23,7 @@ namespace GodotTools.Export } } - private static string FindSelectedXcode() + private static string? FindSelectedXcode() { var outputWrapper = new Godot.Collections.Array(); @@ -40,9 +40,9 @@ namespace GodotTools.Export return null; } - public static string FindXcode() + public static string? FindXcode() { - string selectedXcode = FindSelectedXcode(); + string? selectedXcode = FindSelectedXcode(); if (selectedXcode != null) { if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer"))) @@ -50,10 +50,10 @@ namespace GodotTools.Export // The path already pointed to Contents/Developer var dirInfo = new DirectoryInfo(selectedXcode); - if (dirInfo.Name != "Developer" || dirInfo.Parent.Name != "Contents") + if (dirInfo is not { Parent.Name: "Contents", Name: "Developer" }) { Console.WriteLine(Path.GetDirectoryName(selectedXcode)); - Console.WriteLine(System.IO.Directory.GetParent(selectedXcode).Name); + Console.WriteLine(System.IO.Directory.GetParent(selectedXcode)?.Name); Console.Error.WriteLine("Unrecognized path for selected Xcode"); } else diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 44422eb862..dcbcc4352b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -34,6 +34,7 @@ namespace GodotTools public const string ProblemsLayout = "dotnet/build/problems_layout"; } +#nullable disable private EditorSettings _editorSettings; private PopupMenu _menuPopup; @@ -51,6 +52,7 @@ namespace GodotTools public GodotIdeManager GodotIdeManager { get; private set; } public MSBuildPanel MSBuildPanel { get; private set; } +#nullable enable public bool SkipBuildBeforePlaying { get; set; } = false; @@ -67,29 +69,23 @@ namespace GodotTools private bool CreateProjectSolution() { - string errorMessage = null; + string? errorMessage = null; using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2)) { pr.Step("Generating C# project...".TTR()); - string csprojDir = Path.GetDirectoryName(GodotSharpDirs.ProjectCsProjPath); - string slnDir = Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath); + string csprojDir = Path.GetDirectoryName(GodotSharpDirs.ProjectCsProjPath)!; + string slnDir = Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!; string name = GodotSharpDirs.ProjectAssemblyName; string guid = CsProjOperations.GenerateGameProject(csprojDir, name); if (guid.Length > 0) { - var solution = new DotNetSolution(name) - { - DirectoryPath = slnDir - }; + var solution = new DotNetSolution(name, slnDir); - var projectInfo = new DotNetSolution.ProjectInfo - { - Guid = guid, - PathRelativeToSolution = Path.GetRelativePath(slnDir, GodotSharpDirs.ProjectCsProjPath), - Configs = new List<string> { "Debug", "ExportDebug", "ExportRelease" } - }; + var projectInfo = new DotNetSolution.ProjectInfo(guid, + Path.GetRelativePath(slnDir, GodotSharpDirs.ProjectCsProjPath), + new List<string> { "Debug", "ExportDebug", "ExportRelease" }); solution.AddNewProject(name, projectInfo); @@ -342,7 +338,7 @@ namespace GodotTools } } - args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)); + args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!); string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); @@ -459,7 +455,7 @@ namespace GodotTools // First we try to find the .NET Sdk ourselves to make sure we get the // correct version first, otherwise pick the latest. - if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string sdkPath)) + if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string? sdkPath)) { if (Godot.OS.IsStdOutVerbose()) Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}"); @@ -694,8 +690,9 @@ namespace GodotTools } // Singleton - +#nullable disable public static GodotSharpEditor Instance { get; private set; } +#nullable enable [UsedImplicitly] private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index b5567ea3c7..35b3f5a710 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -4,6 +4,7 @@ <TargetFramework>net6.0</TargetFramework> <EnableDynamicLoading>true</EnableDynamicLoading> <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> <!-- The Godot editor uses the Debug Godot API assemblies --> <GodotApiConfiguration>Debug</GodotApiConfiguration> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> @@ -28,7 +29,7 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" /> - <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.8" /> + <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.9" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <Reference Include="GodotSharp"> diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index 66717d8636..638b7d6420 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -7,7 +7,9 @@ namespace GodotTools { public partial class HotReloadAssemblyWatcher : Node { +#nullable disable private Timer _watchTimer; +#nullable enable public override void _Notification(int what) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 65b77112aa..6563bfbb06 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -10,10 +10,10 @@ namespace GodotTools.Ides { public sealed partial class GodotIdeManager : Node, ISerializationListener { - private MessagingServer _messagingServer; + private MessagingServer? _messagingServer; - private MonoDevelop.Instance _monoDevelInstance; - private MonoDevelop.Instance _vsForMacInstance; + private MonoDevelop.Instance? _monoDevelInstance; + private MonoDevelop.Instance? _vsForMacInstance; private MessagingServer GetRunningOrNewServer() { @@ -59,7 +59,7 @@ namespace GodotTools.Ides switch (editorId) { case ExternalEditorId.None: - return null; + return string.Empty; case ExternalEditorId.VisualStudio: return "VisualStudio"; case ExternalEditorId.VsCode: diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index 51c7a8aa22..c5acc5e2db 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; @@ -97,9 +98,9 @@ namespace GodotTools.Ides foreach (var connection in Peers) connection.Dispose(); Peers.Clear(); - _listener?.Stop(); + _listener.Stop(); - _metaFile?.Dispose(); + _metaFile.Dispose(); File.Delete(_metaFilePath); } @@ -122,7 +123,7 @@ namespace GodotTools.Ides _listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); _listener.Start(); - int port = ((IPEndPoint)_listener.Server.LocalEndPoint).Port; + int port = ((IPEndPoint?)_listener.Server.LocalEndPoint)?.Port ?? 0; using (var metaFileWriter = new StreamWriter(_metaFile, Encoding.UTF8)) { metaFileWriter.WriteLine(port); @@ -235,7 +236,7 @@ namespace GodotTools.Ides public string GetHandshakeLine(string identity) => $"{_serverHandshakeBase},{identity}"; - public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger) + public bool IsValidPeerHandshake(string handshake, [NotNullWhen(true)] out string? identity, ILogger logger) { identity = null; @@ -311,12 +312,12 @@ namespace GodotTools.Ides [DebugPlayRequest.Id] = async (peer, content) => { var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body); - return await HandleDebugPlay(request); + return await HandleDebugPlay(request!); }, [StopPlayRequest.Id] = async (peer, content) => { var request = JsonConvert.DeserializeObject<StopPlayRequest>(content.Body); - return await HandleStopPlay(request); + return await HandleStopPlay(request!); }, [ReloadScriptsRequest.Id] = async (peer, content) => { @@ -326,7 +327,7 @@ namespace GodotTools.Ides [CodeCompletionRequest.Id] = async (peer, content) => { var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body); - return await HandleCodeCompletionRequest(request); + return await HandleCodeCompletionRequest(request!); } }; } @@ -383,7 +384,7 @@ namespace GodotTools.Ides { // This is needed if the "resource path" part of the path is case insensitive. // However, it doesn't fix resource loading if the rest of the path is also case insensitive. - string scriptFileLocalized = FsPathUtils.LocalizePathWithCaseChecked(request.ScriptFile); + string? scriptFileLocalized = FsPathUtils.LocalizePathWithCaseChecked(request.ScriptFile); // The node API can only be called from the main thread. await Godot.Engine.GetMainLoop().ToSignal(Godot.Engine.GetMainLoop(), "process_frame"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 7a0983a8cb..66983156d0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -13,7 +13,7 @@ namespace GodotTools.Ides.MonoDevelop private readonly string _solutionFile; private readonly EditorId _editorId; - private Process _process; + private Process? _process; public bool IsRunning => _process != null && !_process.HasExited; public bool IsDisposed { get; private set; } @@ -24,7 +24,7 @@ namespace GodotTools.Ides.MonoDevelop var args = new List<string>(); - string command; + string? command; if (OS.IsMacOS) { @@ -136,6 +136,8 @@ namespace GodotTools.Ides.MonoDevelop {EditorId.MonoDevelop, "monodevelop"} }; } + ExecutableNames ??= new Dictionary<EditorId, string>(); + BundleIds ??= new Dictionary<EditorId, string>(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs index 61c1581281..77e89dfb7f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs @@ -20,12 +20,12 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment } } - public T FromJson<T>(string json) + public T? FromJson<T>(string json) { return JsonConvert.DeserializeObject<T>(json); } - public void Info(string message, Exception e = null) + public void Info(string message, Exception? e = null) { if (e == null) GD.Print(message); @@ -33,7 +33,7 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment GD.Print(message, e); } - public void Warn(string message, Exception e = null) + public void Warn(string message, Exception? e = null) { if (e == null) GD.PushWarning(message); @@ -41,7 +41,7 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment GD.PushWarning(message, e); } - public void Error(string message, Exception e = null) + public void Error(string message, Exception? e = null) { if (e == null) GD.PushError(message); @@ -49,7 +49,7 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment GD.PushError(message, e); } - public void Verbose(string message, Exception e = null) + public void Verbose(string message, Exception? e = null) { // do nothing, since IDK how to write only to the log, without spamming the output } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index a0ab381b9b..5c54d2d9ba 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -5,8 +5,6 @@ using Godot; using GodotTools.Internals; using JetBrains.Rider.PathLocator; -#nullable enable - namespace GodotTools.Ides.Rider { public static class RiderPathManager diff --git a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs index b86a2b8b24..8aeb19e08b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs @@ -28,6 +28,15 @@ namespace GodotTools.Inspector continue; string scriptPath = script.ResourcePath; + + if (string.IsNullOrEmpty(scriptPath)) + { + // Generic types used empty paths in older versions of Godot + // so we assume your project is out of sync. + AddCustomControl(new InspectorOutOfSyncWarning()); + break; + } + if (scriptPath.StartsWith("csharp://")) { // This is a virtual path used by generic types, extract the real path. diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 67891a0594..94499c4f38 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.IO; using Godot; using Godot.NativeInterop; @@ -59,16 +60,17 @@ namespace GodotTools.Internals } } + [MemberNotNull("_projectAssemblyName", "_projectSlnPath", "_projectCsProjPath")] public static void DetermineProjectLocation() { - _projectAssemblyName = (string)ProjectSettings.GetSetting("dotnet/project/assembly_name"); + _projectAssemblyName = (string?)ProjectSettings.GetSetting("dotnet/project/assembly_name"); if (string.IsNullOrEmpty(_projectAssemblyName)) { _projectAssemblyName = CSharpProjectName; ProjectSettings.SetSetting("dotnet/project/assembly_name", _projectAssemblyName); } - string slnParentDir = (string)ProjectSettings.GetSetting("dotnet/project/solution_directory"); + string? slnParentDir = (string?)ProjectSettings.GetSetting("dotnet/project/solution_directory"); if (string.IsNullOrEmpty(slnParentDir)) slnParentDir = "res://"; else if (!slnParentDir.StartsWith("res://")) @@ -84,9 +86,9 @@ namespace GodotTools.Internals string.Concat(_projectAssemblyName, ".csproj")); } - private static string _projectAssemblyName; - private static string _projectSlnPath; - private static string _projectCsProjPath; + private static string? _projectAssemblyName; + private static string? _projectSlnPath; + private static string? _projectCsProjPath; public static string ProjectAssemblyName { diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs index e3c2c822a5..2abca985da 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -1,17 +1,19 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; namespace GodotTools.Utils { public static class CollectionExtensions { - public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null) + [return: NotNullIfNotNull("orElse")] + public static T? SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T?> predicate, T? orElse = null) where T : class { foreach (T elem in enumerable) { - T result = predicate(elem); + T? result = predicate(elem); if (result != null) return result; } @@ -21,7 +23,7 @@ namespace GodotTools.Utils public static IEnumerable<string> EnumerateLines(this TextReader textReader) { - string line; + string? line; while ((line = textReader.ReadLine()) != null) yield return line; } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs index 89bda704bb..fee9646931 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs @@ -30,8 +30,7 @@ namespace GodotTools.Utils return childPathNorm.PathStartsWithAlreadyNorm(parentPathNorm); } - [return: MaybeNull] - public static string LocalizePathWithCaseChecked(string path) + public static string? LocalizePathWithCaseChecked(string path) { string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar; string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar; diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index c24b730c89..d5d8de93d2 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -150,8 +150,7 @@ namespace GodotTools.Utils public static char PathSep => IsWindows ? ';' : ':'; - [return: MaybeNull] - public static string PathWhich([NotNull] string name) + public static string? PathWhich(string name) { if (IsWindows) return PathWhichWindows(name); @@ -159,12 +158,11 @@ namespace GodotTools.Utils return PathWhichUnix(name); } - [return: MaybeNull] - private static string PathWhichWindows([NotNull] string name) + private static string? PathWhichWindows(string name) { string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>(); - string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); + string[]? pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); char[] invalidPathChars = Path.GetInvalidPathChars(); var searchDirs = new List<string>(); @@ -196,10 +194,9 @@ namespace GodotTools.Utils select path + ext).FirstOrDefault(File.Exists); } - [return: MaybeNull] - private static string PathWhichUnix([NotNull] string name) + private static string? PathWhichUnix(string name) { - string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); + string[]? pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); char[] invalidPathChars = Path.GetInvalidPathChars(); var searchDirs = new List<string>(); @@ -238,7 +235,7 @@ namespace GodotTools.Utils foreach (string arg in arguments) startInfo.ArgumentList.Add(arg); - using Process process = Process.Start(startInfo); + using Process? process = Process.Start(startInfo); if (process == null) throw new InvalidOperationException("No process was started."); @@ -315,7 +312,7 @@ namespace GodotTools.Utils public static StringBuilder GetCommandLineDisplay( this ProcessStartInfo startInfo, - StringBuilder optionalBuilder = null + StringBuilder? optionalBuilder = null ) { var builder = optionalBuilder ?? new StringBuilder(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index e89bbbd370..9de6aafc8a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -106,7 +106,7 @@ namespace Godot instance = instance.Substring(1); } - if (instance.StartsWith("0b")) + if (instance.StartsWith("0b", StringComparison.OrdinalIgnoreCase)) { instance = instance.Substring(2); } @@ -816,7 +816,7 @@ namespace Godot instance = instance.Substring(1); } - if (instance.StartsWith("0x")) + if (instance.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { instance = instance.Substring(2); } diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index fecefc7e71..264a2e9c8e 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -87,8 +87,8 @@ void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const { } #endif -Array MultiplayerSpawner::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray MultiplayerSpawner::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (spawn_path.is_empty() || !has_node(spawn_path)) { warnings.push_back(RTR("A valid NodePath must be set in the \"Spawn Path\" property in order for MultiplayerSpawner to be able to spawn Nodes.")); diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h index 6cd2946df7..0e94b781ea 100644 --- a/modules/multiplayer/multiplayer_spawner.h +++ b/modules/multiplayer/multiplayer_spawner.h @@ -91,7 +91,7 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; #endif public: - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr; diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index 16df9da78e..02e3a11964 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -143,8 +143,8 @@ bool MultiplayerSynchronizer::update_inbound_sync_time(uint16_t p_network_time) return true; } -Array MultiplayerSynchronizer::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (root_path.is_empty() || !has_node(root_path)) { warnings.push_back(RTR("A valid NodePath must be set in the \"Root Path\" property in order for MultiplayerSynchronizer to be able to synchronize properties.")); diff --git a/modules/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h index f5f3993f0d..192d7a5920 100644 --- a/modules/multiplayer/multiplayer_synchronizer.h +++ b/modules/multiplayer/multiplayer_synchronizer.h @@ -91,7 +91,7 @@ public: bool update_outbound_sync_time(uint64_t p_usec); bool update_inbound_sync_time(uint16_t p_network_time); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_replication_interval(double p_interval); double get_replication_interval() const; diff --git a/platform/android/detect.py b/platform/android/detect.py index 98b3ecdeff..fea8ec3287 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -69,6 +69,7 @@ def get_flags(): return [ ("arch", "arm64"), # Default for convenience. ("target", "template_debug"), + ("supported", ["mono"]), ] diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index d0db7b2e6c..28ab8e3335 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -830,8 +830,9 @@ bool EditorExportPlatformAndroid::_uses_vulkan() { void EditorExportPlatformAndroid::_notification(int p_what) { #ifndef ANDROID_ENABLED if (p_what == NOTIFICATION_POSTINITIALIZE) { - ERR_FAIL_NULL(EditorExport::get_singleton()); - EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status)); + if (EditorExport::get_singleton()) { + EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status)); + } } #endif } @@ -1911,13 +1912,22 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio } bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_preset == nullptr) { + return true; + } + + bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); + if (p_option == "graphics/opengl_debug" || + p_option == "command_line/extra_args" || + p_option == "permissions/custom_permissions") { + return advanced_options_enabled; + } if (p_option == "gradle_build/gradle_build_directory" || p_option == "gradle_build/android_source_template") { - // @todo These are experimental options - keep them hidden for now. - //return (bool)p_preset->get("gradle_build/use_gradle_build"); - return false; - } else if (p_option == "custom_template/debug" || p_option == "custom_template/release") { + return advanced_options_enabled && bool(p_preset->get("gradle_build/use_gradle_build")); + } + if (p_option == "custom_template/debug" || p_option == "custom_template/release") { // The APK templates are ignored if Gradle build is enabled. - return !p_preset->get("gradle_build/use_gradle_build"); + return advanced_options_enabled && !bool(p_preset->get("gradle_build/use_gradle_build")); } return true; } diff --git a/platform/ios/detect.py b/platform/ios/detect.py index 9f929ddf0a..4d6e3ae9ba 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -51,6 +51,7 @@ def get_flags(): ("arch", "arm64"), # Default for convenience. ("target", "template_debug"), ("use_volk", False), + ("supported", ["mono"]), ] diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index ea2b23cfb9..91d75b0629 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -127,7 +127,9 @@ String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPres void EditorExportPlatformIOS::_notification(int p_what) { #ifdef MACOS_ENABLED if (p_what == NOTIFICATION_POSTINITIALIZE) { - EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformIOS::_update_preset_status)); + if (EditorExport::get_singleton()) { + EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformIOS::_update_preset_status)); + } } #endif } diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 8fbd8b10b1..4856076436 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -67,6 +67,7 @@ def get_doc_path(): def get_flags(): return [ ("arch", detect_arch()), + ("supported", ["mono"]), ] diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 85846335f7..f29275c910 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -34,6 +34,7 @@ #include "core/io/dir_access.h" #include "main/main.h" #include "servers/display_server.h" +#include "servers/rendering_server.h" #ifdef X11_ENABLED #include "x11/display_server_x11.h" diff --git a/platform/macos/detect.py b/platform/macos/detect.py index cfbe9a8ee7..9150a527a5 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -56,6 +56,7 @@ def get_flags(): return [ ("arch", detect_arch()), ("use_volk", False), + ("supported", ["mono"]), ] diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index ce4c7b2e05..d8e546f571 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2559,7 +2559,7 @@ bool DisplayServerMacOS::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, N } // Confine mouse position to the window, and update delta. - NSRect frame = [p_wd.window_object frame]; + NSRect frame = [p_wd.window_view frame]; NSPoint conf_pos = r_mpos; conf_pos.x = CLAMP(conf_pos.x + r_delta.x, 0.f, frame.size.width); conf_pos.y = CLAMP(conf_pos.y - r_delta.y, 0.f, frame.size.height); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index f536c1ac27..4585884859 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -248,6 +248,7 @@ def get_flags(): return [ ("arch", arch), + ("supported", ["mono"]), ] diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index d319c8ae4e..699d09c0d3 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -569,8 +569,8 @@ StringName AnimatedSprite2D::get_animation() const { return animation; } -Array AnimatedSprite2D::get_configuration_warnings() const { - Array warnings = Node2D::get_configuration_warnings(); +PackedStringArray AnimatedSprite2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (frames.is_null()) { warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite2D to display frames.")); } diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index 1f70f1af81..ac53bd26ee 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -125,7 +125,7 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; AnimatedSprite2D(); diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index f87880afd7..2c5c6a1a16 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -113,8 +113,8 @@ Color CanvasModulate::get_color() const { return color; } -Array CanvasModulate::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray CanvasModulate::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_in_canvas && is_visible_in_tree()) { List<Node *> nodes; diff --git a/scene/2d/canvas_modulate.h b/scene/2d/canvas_modulate.h index 05b126e889..08ded52e23 100644 --- a/scene/2d/canvas_modulate.h +++ b/scene/2d/canvas_modulate.h @@ -54,7 +54,7 @@ public: void set_color(const Color &p_color); Color get_color() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CanvasModulate(); ~CanvasModulate(); diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index e36eda573c..4e5852984b 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -582,8 +582,8 @@ void CollisionObject2D::_update_pickable() { } } -Array CollisionObject2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionObject2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape.")); diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 25ae9af8d5..780793f289 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -166,7 +166,7 @@ public: void set_pickable(bool p_enabled); bool is_pickable() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; _FORCE_INLINE_ RID get_rid() const { return rid; } diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index cfbdf65776..d2f71eca9d 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -232,8 +232,8 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl } #endif -Array CollisionPolygon2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionPolygon2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); diff --git a/scene/2d/collision_polygon_2d.h b/scene/2d/collision_polygon_2d.h index cbedc718d8..f1ee30babe 100644 --- a/scene/2d/collision_polygon_2d.h +++ b/scene/2d/collision_polygon_2d.h @@ -77,7 +77,7 @@ public: void set_polygon(const Vector<Point2> &p_polygon); Vector<Point2> get_polygon() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_disabled(bool p_disabled); bool is_disabled() const; diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index 2c6e675527..ee413c7bc2 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -173,8 +173,8 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double return shape->_edit_is_selected_on_click(p_point, p_tolerance); } -Array CollisionShape2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionShape2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/2d/collision_shape_2d.h b/scene/2d/collision_shape_2d.h index 18f16269c0..3e13dd698c 100644 --- a/scene/2d/collision_shape_2d.h +++ b/scene/2d/collision_shape_2d.h @@ -80,7 +80,7 @@ public: void set_debug_color(const Color &p_color); Color get_debug_color() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CollisionShape2D(); }; diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index c06b172b38..e04e6d7dce 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -246,8 +246,8 @@ bool CPUParticles2D::get_fractional_delta() const { return fractional_delta; } -Array CPUParticles2D::get_configuration_warnings() const { - Array warnings = Node2D::get_configuration_warnings(); +PackedStringArray CPUParticles2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 7108764bbe..3f858c3277 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -282,7 +282,7 @@ public: void set_gravity(const Vector2 &p_gravity); Vector2 get_gravity() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void restart(); diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index d34e339448..bc39513c03 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -327,8 +327,8 @@ float GPUParticles2D::get_interp_to_end() const { return interp_to_end_factor; } -Array GPUParticles2D::get_configuration_warnings() const { - Array warnings = Node2D::get_configuration_warnings(); +PackedStringArray GPUParticles2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (process_material.is_null()) { warnings.push_back(RTR("A material to process the particles is not assigned, so no behavior is imprinted.")); diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index ba10e54251..58996b0327 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -161,7 +161,7 @@ public: void set_amount_ratio(float p_ratio); float get_amount_ratio() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_sub_emitter(const NodePath &p_path); NodePath get_sub_emitter() const; diff --git a/scene/2d/joint_2d.cpp b/scene/2d/joint_2d.cpp index dd9a721240..41b121a482 100644 --- a/scene/2d/joint_2d.cpp +++ b/scene/2d/joint_2d.cpp @@ -212,8 +212,8 @@ bool Joint2D::get_exclude_nodes_from_collision() const { return exclude_from_collision; } -Array Joint2D::get_configuration_warnings() const { - Array warnings = Node2D::get_configuration_warnings(); +PackedStringArray Joint2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!warning.is_empty()) { warnings.push_back(warning); diff --git a/scene/2d/joint_2d.h b/scene/2d/joint_2d.h index 6a3777d3f1..5ff75a77a1 100644 --- a/scene/2d/joint_2d.h +++ b/scene/2d/joint_2d.h @@ -62,7 +62,7 @@ protected: _FORCE_INLINE_ bool is_configured() const { return configured; } public: - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_node_a(const NodePath &p_node_a); NodePath get_node_a() const; diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index a8aecd8257..c03786caef 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -401,14 +401,11 @@ Vector2 PointLight2D::get_texture_offset() const { return texture_offset; } -Array PointLight2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray PointLight2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!texture.is_valid()) { - Dictionary texture_warning; - texture_warning["message"] = RTR("A texture with the shape of the light must be supplied."); - texture_warning["property"] = "texture"; - warnings.push_back(texture_warning); + warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property.")); } return warnings; diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 79b49b4c69..3c1171deae 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -174,7 +174,7 @@ public: void set_texture_scale(real_t p_scale); real_t get_texture_scale() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; PointLight2D(); }; diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index f5da96d4f9..61f5d5cd46 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -247,8 +247,8 @@ int LightOccluder2D::get_occluder_light_mask() const { return mask; } -Array LightOccluder2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray LightOccluder2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!occluder_polygon.is_valid()) { warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect.")); diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index b8add2e498..dd3130394e 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -105,7 +105,7 @@ public: void set_as_sdf_collision(bool p_enable); bool is_set_as_sdf_collision() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; LightOccluder2D(); ~LightOccluder2D(); diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index 3b222e27c4..fee774fe2e 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -624,8 +624,8 @@ void NavigationAgent2D::_avoidance_done(Vector3 p_new_velocity) { emit_signal(SNAME("velocity_computed"), safe_velocity); } -Array NavigationAgent2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationAgent2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node2D>(get_parent())) { warnings.push_back(RTR("The NavigationAgent2D can be used only under a Node2D inheriting parent node.")); diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index 7a8d008e90..0e46086a81 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -200,7 +200,7 @@ public: void _avoidance_done(Vector3 p_new_velocity); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_avoidance_layers(uint32_t p_layers); uint32_t get_avoidance_layers() const; diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp index be224eb08b..04ba550888 100644 --- a/scene/2d/navigation_link_2d.cpp +++ b/scene/2d/navigation_link_2d.cpp @@ -337,8 +337,8 @@ void NavigationLink2D::set_travel_cost(real_t p_travel_cost) { NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost); } -Array NavigationLink2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationLink2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful.")); diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h index 7d8b375b6c..2929691c04 100644 --- a/scene/2d/navigation_link_2d.h +++ b/scene/2d/navigation_link_2d.h @@ -93,7 +93,7 @@ public: void set_travel_cost(real_t p_travel_cost); real_t get_travel_cost() const { return travel_cost; } - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; NavigationLink2D(); ~NavigationLink2D(); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 134ca065ce..4a434fa3f8 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -283,8 +283,8 @@ void NavigationRegion2D::_navigation_debug_changed() { } #endif // DEBUG_ENABLED -Array NavigationRegion2D::get_configuration_warnings() const { - Array warnings = Node2D::get_configuration_warnings(); +PackedStringArray NavigationRegion2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!navigation_polygon.is_valid()) { diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 9e9a21d9f3..e9387376cb 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -114,7 +114,7 @@ public: void set_avoidance_layer_value(int p_layer_number, bool p_value); bool get_avoidance_layer_value(int p_layer_number) const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void bake_navigation_polygon(bool p_on_thread); void _bake_finished(Ref<NavigationPolygon> p_navigation_polygon); diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 44cfc1deb2..3dd0d7b61c 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -137,8 +137,8 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s _update_mirroring(); } -Array ParallaxLayer::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray ParallaxLayer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<ParallaxBackground>(get_parent())) { warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node.")); diff --git a/scene/2d/parallax_layer.h b/scene/2d/parallax_layer.h index dbf9386198..22fa0dd51c 100644 --- a/scene/2d/parallax_layer.h +++ b/scene/2d/parallax_layer.h @@ -59,7 +59,7 @@ public: void set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; ParallaxLayer(); }; diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 4fce49dc57..282d14da5d 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -287,8 +287,8 @@ void PathFollow2D::_validate_property(PropertyInfo &p_property) const { } } -Array PathFollow2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray PathFollow2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path2D>(get_parent())) { diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index 616903c788..bfd5cde5e9 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -106,7 +106,7 @@ public: void set_cubic_interpolation_enabled(bool p_enabled); bool is_cubic_interpolation_enabled() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; PathFollow2D() {} }; diff --git a/scene/2d/physical_bone_2d.cpp b/scene/2d/physical_bone_2d.cpp index 12a3e6da21..64cf56fa85 100644 --- a/scene/2d/physical_bone_2d.cpp +++ b/scene/2d/physical_bone_2d.cpp @@ -106,8 +106,8 @@ void PhysicalBone2D::_find_joint_child() { } } -Array PhysicalBone2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray PhysicalBone2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!parent_skeleton) { warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); diff --git a/scene/2d/physical_bone_2d.h b/scene/2d/physical_bone_2d.h index 1930ce1aba..e585f2c0ed 100644 --- a/scene/2d/physical_bone_2d.h +++ b/scene/2d/physical_bone_2d.h @@ -79,7 +79,7 @@ public: void set_follow_bone_when_simulating(bool p_follow); bool get_follow_bone_when_simulating() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; PhysicalBone2D(); ~PhysicalBone2D(); diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 2e0b020232..92af3fa3f0 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -928,10 +928,10 @@ void RigidBody2D::_notification(int p_what) { #endif } -Array RigidBody2D::get_configuration_warnings() const { +PackedStringArray RigidBody2D::get_configuration_warnings() const { Transform2D t = get_transform(); - Array warnings = CollisionObject2D::get_configuration_warnings(); + PackedStringArray warnings = CollisionObject2D::get_configuration_warnings(); if (ABS(t.columns[0].length() - 1.0) > 0.05 || ABS(t.columns[1].length() - 1.0) > 0.05) { warnings.push_back(RTR("Size changes to RigidBody2D will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 44ff1ba822..88161e284a 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -313,7 +313,7 @@ public: TypedArray<Node2D> get_colliding_bodies() const; //function for script - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; RigidBody2D(); ~RigidBody2D(); diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index b3926a4638..5ea5098475 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -200,8 +200,8 @@ void RemoteTransform2D::force_update_cache() { _update_cache(); } -Array RemoteTransform2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray RemoteTransform2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { warnings.push_back(RTR("Path property must point to a valid Node2D node to work.")); diff --git a/scene/2d/remote_transform_2d.h b/scene/2d/remote_transform_2d.h index fe7289380a..997fd8fc69 100644 --- a/scene/2d/remote_transform_2d.h +++ b/scene/2d/remote_transform_2d.h @@ -70,7 +70,7 @@ public: void force_update_cache(); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; RemoteTransform2D(); }; diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp index 332e5eeb3e..90d80d7549 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -402,8 +402,8 @@ Array ShapeCast2D::_get_collision_result() const { return ret; } -Array ShapeCast2D::get_configuration_warnings() const { - Array warnings = Node2D::get_configuration_warnings(); +PackedStringArray ShapeCast2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (shape.is_null()) { warnings.push_back(RTR("This node cannot interact with other objects unless a Shape2D is assigned.")); diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h index da7c58bacc..a577c351fd 100644 --- a/scene/2d/shape_cast_2d.h +++ b/scene/2d/shape_cast_2d.h @@ -118,7 +118,7 @@ public: void remove_exception(const CollisionObject2D *p_node); void clear_exceptions(); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; ShapeCast2D(); }; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 432b8e914e..69e0414855 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -415,8 +415,8 @@ int Bone2D::get_index_in_skeleton() const { return skeleton_index; } -Array Bone2D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray Bone2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!skeleton) { if (parent_bone) { warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node.")); diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 1128ebb7c6..6a36a31552 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -78,7 +78,7 @@ public: void apply_rest(); Transform2D get_skeleton_rest() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_autocalculate_length_and_angle(bool p_autocalculate); bool get_autocalculate_length_and_angle() const; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index b8ca480e3b..48fe2afccc 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -846,8 +846,8 @@ TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) { return tile_set->get_surrounding_cells(p_coords); } -Array TileMap::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray TileMap::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); // Retrieve the set of Z index values with a Y-sorted layer. RBSet<int> y_sorted_z_index; diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 6edf348caf..16750625e5 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -216,7 +216,7 @@ public: GDVIRTUAL3(_tile_data_runtime_update, int, Vector2i, TileData *); // Configuration warnings. - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; TileMap(); ~TileMap(); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 3cab128be5..5683fb7306 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -87,8 +87,8 @@ void BoneAttachment3D::_get_property_list(List<PropertyInfo> *p_list) const { } } -Array BoneAttachment3D::get_configuration_warnings() const { - Array warnings = Node3D::get_configuration_warnings(); +PackedStringArray BoneAttachment3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (use_external_skeleton) { if (external_skeleton_node_cache.is_null()) { diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index 13fdccab3c..1bf44c2756 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -71,8 +71,7 @@ public: virtual void notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map); #endif // TOOLS_ENABLED -public: - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_bone_name(const String &p_name); String get_bone_name() const; diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 32799cacfe..0cfe0f8cb7 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -728,8 +728,8 @@ bool CollisionObject3D::get_capture_input_on_drag() const { return capture_input_on_drag; } -Array CollisionObject3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionObject3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape.")); diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h index dc3c9269e4..b51423f021 100644 --- a/scene/3d/collision_object_3d.h +++ b/scene/3d/collision_object_3d.h @@ -173,7 +173,7 @@ public: _FORCE_INLINE_ RID get_rid() const { return rid; } - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CollisionObject3D(); ~CollisionObject3D(); diff --git a/scene/3d/collision_polygon_3d.cpp b/scene/3d/collision_polygon_3d.cpp index 29cec43011..9c1a7181aa 100644 --- a/scene/3d/collision_polygon_3d.cpp +++ b/scene/3d/collision_polygon_3d.cpp @@ -168,8 +168,8 @@ void CollisionPolygon3D::set_margin(real_t p_margin) { } } -Array CollisionPolygon3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionPolygon3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject3D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape.")); diff --git a/scene/3d/collision_polygon_3d.h b/scene/3d/collision_polygon_3d.h index 715ea721cf..61290a7947 100644 --- a/scene/3d/collision_polygon_3d.h +++ b/scene/3d/collision_polygon_3d.h @@ -74,7 +74,7 @@ public: real_t get_margin() const; void set_margin(real_t p_margin); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CollisionPolygon3D(); }; diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index 30692660f6..61941a0e53 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -118,8 +118,8 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) { } #endif -Array CollisionShape3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionShape3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/3d/collision_shape_3d.h b/scene/3d/collision_shape_3d.h index f66f88648e..bc0e70f8ac 100644 --- a/scene/3d/collision_shape_3d.h +++ b/scene/3d/collision_shape_3d.h @@ -64,7 +64,7 @@ public: void set_disabled(bool p_disabled); bool is_disabled() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CollisionShape3D(); ~CollisionShape3D(); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 32d68f7f78..d47e63d790 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -203,8 +203,8 @@ bool CPUParticles3D::get_fractional_delta() const { return fractional_delta; } -Array CPUParticles3D::get_configuration_warnings() const { - Array warnings = GeometryInstance3D::get_configuration_warnings(); +PackedStringArray CPUParticles3D::get_configuration_warnings() const { + PackedStringArray warnings = GeometryInstance3D::get_configuration_warnings(); bool mesh_found = false; bool anim_material_found = false; diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index e9b75d9140..82ea4bbef3 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -305,7 +305,7 @@ public: void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void restart(); diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index acd52da8b0..6878df21d8 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -162,8 +162,8 @@ void Decal::_validate_property(PropertyInfo &p_property) const { } } -Array Decal::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray Decal::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends.")); diff --git a/scene/3d/decal.h b/scene/3d/decal.h index 3bc6664afd..171b52815a 100644 --- a/scene/3d/decal.h +++ b/scene/3d/decal.h @@ -69,7 +69,7 @@ protected: #endif // DISABLE_DEPRECATED public: - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_size(const Vector3 &p_size); Vector3 get_size() const; diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index 1c17a733e9..12ca1888c4 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -117,8 +117,8 @@ AABB FogVolume::get_aabb() const { return AABB(); } -Array FogVolume::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray FogVolume::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment(); diff --git a/scene/3d/fog_volume.h b/scene/3d/fog_volume.h index a185cdbd94..f7e861e3d0 100644 --- a/scene/3d/fog_volume.h +++ b/scene/3d/fog_volume.h @@ -66,7 +66,7 @@ public: Ref<Material> get_material() const; virtual AABB get_aabb() const override; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; FogVolume(); ~FogVolume(); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 6670c2c5f6..16813b9017 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -296,8 +296,8 @@ bool GPUParticles3D::get_interpolate() const { return interpolate; } -Array GPUParticles3D::get_configuration_warnings() const { - Array warnings = GeometryInstance3D::get_configuration_warnings(); +PackedStringArray GPUParticles3D::get_configuration_warnings() const { + PackedStringArray warnings = GeometryInstance3D::get_configuration_warnings(); bool meshes_found = false; bool anim_material_found = false; diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 1697749d9b..0c9f2c1378 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -164,7 +164,7 @@ public: void set_draw_pass_mesh(int p_pass, const Ref<Mesh> &p_mesh); Ref<Mesh> get_draw_pass_mesh(int p_pass) const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_sub_emitter(const NodePath &p_path); NodePath get_sub_emitter() const; diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index f5d5795a27..cbc75801b0 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -527,8 +527,8 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { return ret; } -Array GPUParticlesCollisionSDF3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (bake_mask == 0) { warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property.")); diff --git a/scene/3d/gpu_particles_collision_3d.h b/scene/3d/gpu_particles_collision_3d.h index a4d8b15f9f..1649320069 100644 --- a/scene/3d/gpu_particles_collision_3d.h +++ b/scene/3d/gpu_particles_collision_3d.h @@ -170,7 +170,7 @@ protected: #endif // DISABLE_DEPRECATED public: - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_thickness(float p_thickness); float get_thickness() const; diff --git a/scene/3d/joint_3d.cpp b/scene/3d/joint_3d.cpp index d76838d434..1e4e50182c 100644 --- a/scene/3d/joint_3d.cpp +++ b/scene/3d/joint_3d.cpp @@ -198,8 +198,8 @@ bool Joint3D::get_exclude_nodes_from_collision() const { return exclude_from_collision; } -Array Joint3D::get_configuration_warnings() const { - Array warnings = Node3D::get_configuration_warnings(); +PackedStringArray Joint3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!warning.is_empty()) { warnings.push_back(warning); diff --git a/scene/3d/joint_3d.h b/scene/3d/joint_3d.h index bf2519db12..527aed4079 100644 --- a/scene/3d/joint_3d.h +++ b/scene/3d/joint_3d.h @@ -63,7 +63,7 @@ protected: _FORCE_INLINE_ bool is_configured() const { return configured; } public: - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_node_a(const NodePath &p_node_a); NodePath get_node_a() const; diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index bc74ff2c0f..7b70986adc 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -169,8 +169,8 @@ AABB Light3D::get_aabb() const { return AABB(); } -Array Light3D::get_configuration_warnings() const { - Array warnings = VisualInstance3D::get_configuration_warnings(); +PackedStringArray Light3D::get_configuration_warnings() const { + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (!get_scale().is_equal_approx(Vector3(1, 1, 1))) { warnings.push_back(RTR("A light's scale does not affect the visual size of the light.")); @@ -596,8 +596,8 @@ OmniLight3D::ShadowMode OmniLight3D::get_shadow_mode() const { return shadow_mode; } -Array OmniLight3D::get_configuration_warnings() const { - Array warnings = Light3D::get_configuration_warnings(); +PackedStringArray OmniLight3D::get_configuration_warnings() const { + PackedStringArray warnings = Light3D::get_configuration_warnings(); if (!has_shadow() && get_projector().is_valid()) { warnings.push_back(RTR("Projector texture only works with shadows active.")); @@ -628,8 +628,8 @@ OmniLight3D::OmniLight3D() : set_shadow_mode(SHADOW_CUBE); } -Array SpotLight3D::get_configuration_warnings() const { - Array warnings = Light3D::get_configuration_warnings(); +PackedStringArray SpotLight3D::get_configuration_warnings() const { + PackedStringArray warnings = Light3D::get_configuration_warnings(); if (has_shadow() && get_param(PARAM_SPOT_ANGLE) >= 90.0) { warnings.push_back(RTR("A SpotLight3D with an angle wider than 90 degrees cannot cast shadows.")); diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index b242493c28..d6eca8d8b6 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -147,7 +147,7 @@ public: Color get_correlated_color() const; virtual AABB get_aabb() const override; - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; Light3D(); ~Light3D(); @@ -217,7 +217,7 @@ public: void set_shadow_mode(ShadowMode p_mode); ShadowMode get_shadow_mode() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; OmniLight3D(); }; @@ -231,7 +231,7 @@ protected: static void _bind_methods(); public: - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; SpotLight3D(); }; diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 008a09f141..86ff6d15dd 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1528,8 +1528,8 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const { return camera_attributes; } -Array LightmapGI::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray LightmapGI::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail.")); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 8d96c1e909..765e4a731d 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -302,7 +302,7 @@ public: BakeError bake(Node *p_from_node, String p_image_data_path = "", Lightmapper::BakeStepFunc p_bake_step = nullptr, void *p_bake_userdata = nullptr); - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; LightmapGI(); }; diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index ff0869b4ea..eb52d4540e 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -690,8 +690,8 @@ void NavigationAgent3D::_avoidance_done(Vector3 p_new_velocity) { emit_signal(SNAME("velocity_computed"), safe_velocity); } -Array NavigationAgent3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationAgent3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node3D>(get_parent())) { warnings.push_back(RTR("The NavigationAgent3D can be used only under a Node3D inheriting parent node.")); diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h index 5f7b249b7f..4eaed83149 100644 --- a/scene/3d/navigation_agent_3d.h +++ b/scene/3d/navigation_agent_3d.h @@ -221,7 +221,7 @@ public: void _avoidance_done(Vector3 p_new_velocity); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_avoidance_layers(uint32_t p_layers); uint32_t get_avoidance_layers() const; diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp index c535393273..dc776ebea2 100644 --- a/scene/3d/navigation_link_3d.cpp +++ b/scene/3d/navigation_link_3d.cpp @@ -458,8 +458,8 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) { NavigationServer3D::get_singleton()->link_set_travel_cost(link, travel_cost); } -Array NavigationLink3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationLink3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful.")); diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h index 771d470711..1867082811 100644 --- a/scene/3d/navigation_link_3d.h +++ b/scene/3d/navigation_link_3d.h @@ -99,7 +99,7 @@ public: void set_travel_cost(real_t p_travel_cost); real_t get_travel_cost() const { return travel_cost; } - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; }; #endif // NAVIGATION_LINK_3D_H diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 5d258d394a..d8a63c60a2 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -270,8 +270,8 @@ bool NavigationRegion3D::is_baking() const { return NavigationServer3D::get_singleton()->is_baking_navigation_mesh(navigation_mesh); } -Array NavigationRegion3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationRegion3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!navigation_mesh.is_valid()) { diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h index b6dde0bec8..82468627de 100644 --- a/scene/3d/navigation_region_3d.h +++ b/scene/3d/navigation_region_3d.h @@ -108,7 +108,7 @@ public: void _bake_finished(Ref<NavigationMesh> p_navigation_mesh); bool is_baking() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; NavigationRegion3D(); ~NavigationRegion3D(); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 0f56bfad52..a76488e479 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -693,8 +693,8 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node, return BAKE_ERROR_OK; } -Array OccluderInstance3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray OccluderInstance3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) { warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling.")); diff --git a/scene/3d/occluder_instance_3d.h b/scene/3d/occluder_instance_3d.h index cf20655c2c..f607877e8f 100644 --- a/scene/3d/occluder_instance_3d.h +++ b/scene/3d/occluder_instance_3d.h @@ -181,7 +181,7 @@ protected: static void _bind_methods(); public: - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; enum BakeError { BAKE_ERROR_OK, diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index 12e0123242..1f8f7cd54c 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -317,8 +317,8 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const { } } -Array PathFollow3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray PathFollow3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path3D>(get_parent())) { diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h index 82fbf60bb6..0c9111bb8e 100644 --- a/scene/3d/path_3d.h +++ b/scene/3d/path_3d.h @@ -122,7 +122,7 @@ public: void set_cubic_interpolation_enabled(bool p_enabled); bool is_cubic_interpolation_enabled() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void update_transform(bool p_immediate = false); diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 2cbd484870..67a7f76d7d 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -995,8 +995,8 @@ TypedArray<Node3D> RigidBody3D::get_colliding_bodies() const { return ret; } -Array RigidBody3D::get_configuration_warnings() const { - Array warnings = CollisionObject3D::get_configuration_warnings(); +PackedStringArray RigidBody3D::get_configuration_warnings() const { + PackedStringArray warnings = CollisionObject3D::get_configuration_warnings(); Vector3 scale = get_transform().get_basis().get_scale(); if (ABS(scale.x - 1.0) > 0.05 || ABS(scale.y - 1.0) > 0.05 || ABS(scale.z - 1.0) > 0.05) { diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index b360954263..b84b7c4f02 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -329,7 +329,7 @@ public: void set_constant_torque(const Vector3 &p_torque); Vector3 get_constant_torque() const; - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; RigidBody3D(); ~RigidBody3D(); diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index 89368a7309..b4dd6d09be 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -190,8 +190,8 @@ AABB ReflectionProbe::get_aabb() const { return aabb; } -Array ReflectionProbe::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray ReflectionProbe::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("ReflectionProbes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); diff --git a/scene/3d/reflection_probe.h b/scene/3d/reflection_probe.h index 2561b2a922..425fbb5bc2 100644 --- a/scene/3d/reflection_probe.h +++ b/scene/3d/reflection_probe.h @@ -122,7 +122,7 @@ public: virtual AABB get_aabb() const override; - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; ReflectionProbe(); ~ReflectionProbe(); diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index bb41015219..8d6e717132 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -200,8 +200,8 @@ void RemoteTransform3D::force_update_cache() { _update_cache(); } -Array RemoteTransform3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray RemoteTransform3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) { warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work.")); diff --git a/scene/3d/remote_transform_3d.h b/scene/3d/remote_transform_3d.h index 40b59a5a5e..3841821dae 100644 --- a/scene/3d/remote_transform_3d.h +++ b/scene/3d/remote_transform_3d.h @@ -70,7 +70,7 @@ public: void force_update_cache(); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; RemoteTransform3D(); }; diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp index ba4ce834da..4d9eeada06 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -170,8 +170,8 @@ void ShapeCast3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_shape_custom_color"), "set_debug_shape_custom_color", "get_debug_shape_custom_color"); } -Array ShapeCast3D::get_configuration_warnings() const { - Array warnings = Node3D::get_configuration_warnings(); +PackedStringArray ShapeCast3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (shape.is_null()) { warnings.push_back(RTR("This node cannot interact with other objects unless a Shape3D is assigned.")); diff --git a/scene/3d/shape_cast_3d.h b/scene/3d/shape_cast_3d.h index d690a17a78..043e35090f 100644 --- a/scene/3d/shape_cast_3d.h +++ b/scene/3d/shape_cast_3d.h @@ -140,7 +140,7 @@ public: void remove_exception(const CollisionObject3D *p_node); void clear_exceptions(); - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; }; #endif // SHAPE_CAST_3D_H diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index ee30f60c72..cae1b2ce9c 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -95,17 +95,32 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { } else if (what == "scale") { set_bone_pose_scale(which, p_value); #ifndef DISABLE_DEPRECATED - } else if (what == "pose") { + } else if (what == "pose" || what == "bound_children") { // Kept for compatibility from 3.x to 4.x. WARN_DEPRECATED_MSG("Skeleton uses old pose format, which is deprecated (and loads slower). Consider re-importing or re-saving the scene." + (is_inside_tree() ? vformat(" Path: \"%s\"", get_path()) : String())); - // Old Skeleton poses were relative to rest, new ones are absolute, so we need to recompute the pose. - // Skeleton3D nodes were always written with rest before pose, so this *SHOULD* work... - Transform3D rest = get_bone_rest(which); - Transform3D pose = rest * (Transform3D)p_value; - set_bone_pose_position(which, pose.origin); - set_bone_pose_rotation(which, pose.basis.get_rotation_quaternion()); - set_bone_pose_scale(which, pose.basis.get_scale()); + if (what == "pose") { + // Old Skeleton poses were relative to rest, new ones are absolute, so we need to recompute the pose. + // Skeleton3D nodes were always written with rest before pose, so this *SHOULD* work... + Transform3D rest = get_bone_rest(which); + Transform3D pose = rest * (Transform3D)p_value; + set_bone_pose_position(which, pose.origin); + set_bone_pose_rotation(which, pose.basis.get_rotation_quaternion()); + set_bone_pose_scale(which, pose.basis.get_scale()); + } else { // bound_children + // This handles the case where the pose was set to the rest position; the pose property would == Transform() and would not be saved to the scene by default. + // However, the bound_children property was always saved regardless of value, and it was always saved after both pose and rest. + // We don't do anything else with bound_children, as it's not present on Skeleton3D. + Vector3 pos = get_bone_pose_position(which); + Quaternion rot = get_bone_pose_rotation(which); + Vector3 scale = get_bone_pose_scale(which); + Transform3D rest = get_bone_rest(which); + if (rest != Transform3D() && pos == Vector3() && rot == Quaternion() && scale == Vector3(1, 1, 1)) { + set_bone_pose_position(which, rest.origin); + set_bone_pose_rotation(which, rest.basis.get_rotation_quaternion()); + set_bone_pose_scale(which, rest.basis.get_scale()); + } + } #endif } else { return false; diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 540e70866a..bd03a97a36 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -376,8 +376,8 @@ void SoftBody3D::_bind_methods() { BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE); } -Array SoftBody3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray SoftBody3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (mesh.is_null()) { warnings.push_back(RTR("This body will be ignored until you set a mesh.")); diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h index b5d31d8440..ab30f7e654 100644 --- a/scene/3d/soft_body_3d.h +++ b/scene/3d/soft_body_3d.h @@ -126,7 +126,7 @@ protected: void _notification(int p_what); static void _bind_methods(); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; public: RID get_physics_rid() const { return physics_rid; } diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 234baac61a..8e76e75d0e 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -1430,8 +1430,8 @@ StringName AnimatedSprite3D::get_animation() const { return animation; } -Array AnimatedSprite3D::get_configuration_warnings() const { - Array warnings = SpriteBase3D::get_configuration_warnings(); +PackedStringArray AnimatedSprite3D::get_configuration_warnings() const { + PackedStringArray warnings = SpriteBase3D::get_configuration_warnings(); if (frames.is_null()) { warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames.")); } diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index c83ed88b2d..cbc7192ddf 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -285,7 +285,7 @@ public: virtual Rect2 get_item_rect() const override; - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; AnimatedSprite3D(); diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index b309205697..c23032d3b9 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -105,8 +105,8 @@ void VehicleWheel3D::_notification(int p_what) { } } -Array VehicleWheel3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray VehicleWheel3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<VehicleBody3D>(get_parent())) { warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D.")); diff --git a/scene/3d/vehicle_body_3d.h b/scene/3d/vehicle_body_3d.h index 8127340acc..ce913f152d 100644 --- a/scene/3d/vehicle_body_3d.h +++ b/scene/3d/vehicle_body_3d.h @@ -147,7 +147,7 @@ public: void set_steering(real_t p_steering); real_t get_steering() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; VehicleWheel3D(); }; diff --git a/scene/3d/visible_on_screen_notifier_3d.cpp b/scene/3d/visible_on_screen_notifier_3d.cpp index c10ddec17e..be86872a59 100644 --- a/scene/3d/visible_on_screen_notifier_3d.cpp +++ b/scene/3d/visible_on_screen_notifier_3d.cpp @@ -79,8 +79,8 @@ void VisibleOnScreenNotifier3D::_notification(int p_what) { } } -Array VisibleOnScreenNotifier3D::get_configuration_warnings() const { - Array warnings = VisualInstance3D::get_configuration_warnings(); +PackedStringArray VisibleOnScreenNotifier3D::get_configuration_warnings() const { + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("VisibleOnScreenNotifier3D nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); diff --git a/scene/3d/visible_on_screen_notifier_3d.h b/scene/3d/visible_on_screen_notifier_3d.h index 10e41ceec6..85156c256e 100644 --- a/scene/3d/visible_on_screen_notifier_3d.h +++ b/scene/3d/visible_on_screen_notifier_3d.h @@ -57,7 +57,7 @@ public: virtual AABB get_aabb() const override; bool is_on_screen() const; - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; VisibleOnScreenNotifier3D(); ~VisibleOnScreenNotifier3D(); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 47a9a581b8..503c39ae3e 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -425,8 +425,8 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() { return ignore_occlusion_culling; } -Array GeometryInstance3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray GeometryInstance3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) { warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance.")); diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index c088e189ce..59ede26ac1 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -194,7 +194,7 @@ public: void set_ignore_occlusion_culling(bool p_enabled); bool is_ignoring_occlusion_culling(); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; GeometryInstance3D(); virtual ~GeometryInstance3D(); }; diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index 011aecc724..eb8569fa30 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -518,8 +518,8 @@ AABB VoxelGI::get_aabb() const { return AABB(-size / 2, size); } -Array VoxelGI::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray VoxelGI::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("VoxelGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); diff --git a/scene/3d/voxel_gi.h b/scene/3d/voxel_gi.h index 316cb26b77..7d7787f721 100644 --- a/scene/3d/voxel_gi.h +++ b/scene/3d/voxel_gi.h @@ -163,7 +163,7 @@ public: virtual AABB get_aabb() const override; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; VoxelGI(); ~VoxelGI(); diff --git a/scene/3d/world_environment.cpp b/scene/3d/world_environment.cpp index 8db310ce30..4687c84734 100644 --- a/scene/3d/world_environment.cpp +++ b/scene/3d/world_environment.cpp @@ -135,8 +135,8 @@ Ref<CameraAttributes> WorldEnvironment::get_camera_attributes() const { return camera_attributes; } -Array WorldEnvironment::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray WorldEnvironment::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!environment.is_valid() && !camera_attributes.is_valid()) { warnings.push_back(RTR("To have any visible effect, WorldEnvironment requires its \"Environment\" property to contain an Environment, its \"Camera Attributes\" property to contain a CameraAttributes resource, or both.")); diff --git a/scene/3d/world_environment.h b/scene/3d/world_environment.h index 57d95f1f0e..2809d2550a 100644 --- a/scene/3d/world_environment.h +++ b/scene/3d/world_environment.h @@ -55,7 +55,7 @@ public: void set_camera_attributes(const Ref<CameraAttributes> &p_camera_attributes); Ref<CameraAttributes> get_camera_attributes() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; WorldEnvironment(); }; diff --git a/scene/3d/xr_face_modifier_3d.cpp b/scene/3d/xr_face_modifier_3d.cpp new file mode 100644 index 0000000000..be92a587b0 --- /dev/null +++ b/scene/3d/xr_face_modifier_3d.cpp @@ -0,0 +1,615 @@ +/**************************************************************************/ +/* xr_face_modifier_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "xr_face_modifier_3d.h" + +#include "servers/xr/xr_face_tracker.h" +#include "servers/xr_server.h" + +// This method takes the name of a mesh blend shape and returns the +// corresponding XRFaceTracker blend shape. If no match is +// found then the function returns -1. +static int find_face_blend_shape(const StringName &p_name) { + // Entry for blend shape name table. + struct blend_map_entry { + int blend; + const char *name[4]; + }; + + // Table of blend shape names. + // + // This table consists of the XRFaceTracker blend shape and + // the corresponding names (lowercase and no underscore) of: + // - The Unified Expression blend shape name. + // - The ARKit blend shape name (if present and different). + // - The SRanipal blend shape name (if present and different). + // - The Meta blend shape name (if present and different). + static constexpr blend_map_entry blend_map[] = { + { XRFaceTracker::FT_EYE_LOOK_OUT_RIGHT, + { "eyelookoutright", "eyerightright", "eyeslookoutr" } }, + { XRFaceTracker::FT_EYE_LOOK_IN_RIGHT, + { "eyelookinright", "eyerightleft", "eyeslookinr" } }, + { XRFaceTracker::FT_EYE_LOOK_UP_RIGHT, + { "eyelookupright", "eyerightlookup", "eyeslookupr" } }, + { XRFaceTracker::FT_EYE_LOOK_DOWN_RIGHT, + { "eyelookdownright", "eyerightlookdown", "eyeslookdownr" } }, + { XRFaceTracker::FT_EYE_LOOK_OUT_LEFT, + { "eyelookoutleft", "eyeleftleft", "eyeslookoutl" } }, + { XRFaceTracker::FT_EYE_LOOK_IN_LEFT, + { "eyelookinleft", "eyeleftright", "eyeslookinl" } }, + { XRFaceTracker::FT_EYE_LOOK_UP_LEFT, + { "eyelookupleft", "eyeleftlookup", "eyeslookupl" } }, + { XRFaceTracker::FT_EYE_LOOK_DOWN_LEFT, + { "eyelookdownleft", "eyeleftlookdown", "eyeslookdownl" } }, + { XRFaceTracker::FT_EYE_CLOSED_RIGHT, + { "eyeclosedright", "eyeblinkright", "eyerightblink", "eyesclosedr" } }, + { XRFaceTracker::FT_EYE_CLOSED_LEFT, + { "eyeclosedleft", "eyeblinkleft", "eyeleftblink", "eyesclosedl" } }, + { XRFaceTracker::FT_EYE_SQUINT_RIGHT, + { "eyesquintright", "eyessquintr" } }, + { XRFaceTracker::FT_EYE_SQUINT_LEFT, + { "eyesquintleft", "eyessquintl" } }, + { XRFaceTracker::FT_EYE_WIDE_RIGHT, + { "eyewideright", "eyerightwide", "eyeswidenr" } }, + { XRFaceTracker::FT_EYE_WIDE_LEFT, + { "eyewideleft", "eyeleftwide", "eyeswidenl" } }, + { XRFaceTracker::FT_EYE_DILATION_RIGHT, + { "eyedilationright", "eyerightdilation" } }, + { XRFaceTracker::FT_EYE_DILATION_LEFT, + { "eyedilationleft", "eyeleftdilation" } }, + { XRFaceTracker::FT_EYE_CONSTRICT_RIGHT, + { "eyeconstrictright", "eyerightconstrict" } }, + { XRFaceTracker::FT_EYE_CONSTRICT_LEFT, + { "eyeconstrictleft", "eyeleftconstrict" } }, + { XRFaceTracker::FT_BROW_PINCH_RIGHT, + { "browpinchright" } }, + { XRFaceTracker::FT_BROW_PINCH_LEFT, + { "browpinchleft" } }, + { XRFaceTracker::FT_BROW_LOWERER_RIGHT, + { "browlowererright" } }, + { XRFaceTracker::FT_BROW_LOWERER_LEFT, + { "browlowererleft" } }, + { XRFaceTracker::FT_BROW_INNER_UP_RIGHT, + { "browinnerupright", "innerbrowraiserr" } }, + { XRFaceTracker::FT_BROW_INNER_UP_LEFT, + { "browinnerupleft", "innerbrowraiserl" } }, + { XRFaceTracker::FT_BROW_OUTER_UP_RIGHT, + { "browouterupright", "outerbrowraiserr" } }, + { XRFaceTracker::FT_BROW_OUTER_UP_LEFT, + { "browouterupleft", "outerbrowraiserl" } }, + { XRFaceTracker::FT_NOSE_SNEER_RIGHT, + { "nosesneerright", "nosewrinklerr" } }, + { XRFaceTracker::FT_NOSE_SNEER_LEFT, + { "nosesneerleft", "nosewrinklerl" } }, + { XRFaceTracker::FT_NASAL_DILATION_RIGHT, + { "nasaldilationright" } }, + { XRFaceTracker::FT_NASAL_DILATION_LEFT, + { "nasaldilationleft" } }, + { XRFaceTracker::FT_NASAL_CONSTRICT_RIGHT, + { "nasalconstrictright" } }, + { XRFaceTracker::FT_NASAL_CONSTRICT_LEFT, + { "nasalconstrictleft" } }, + { XRFaceTracker::FT_CHEEK_SQUINT_RIGHT, + { "cheeksquintright", "cheekraiserr" } }, + { XRFaceTracker::FT_CHEEK_SQUINT_LEFT, + { "cheeksquintleft", "cheekraiserl" } }, + { XRFaceTracker::FT_CHEEK_PUFF_RIGHT, + { "cheekpuffright", "cheekpuffr" } }, + { XRFaceTracker::FT_CHEEK_PUFF_LEFT, + { "cheekpuffleft", "cheekpuffl" } }, + { XRFaceTracker::FT_CHEEK_SUCK_RIGHT, + { "cheeksuckright", "cheeksuckr" } }, + { XRFaceTracker::FT_CHEEK_SUCK_LEFT, + { "cheeksuckleft", "cheeksuckl" } }, + { XRFaceTracker::FT_JAW_OPEN, + { "jawopen", "jawdrop" } }, + { XRFaceTracker::FT_MOUTH_CLOSED, + { "mouthclosed", "mouthclose", "mouthapeshape", "lipstoward" } }, + { XRFaceTracker::FT_JAW_RIGHT, + { "jawright", "jawsidewaysright" } }, + { XRFaceTracker::FT_JAW_LEFT, + { "jawleft", "jawsidewaysleft" } }, + { XRFaceTracker::FT_JAW_FORWARD, + { "jawforward", "jawthrust" } }, + { XRFaceTracker::FT_JAW_BACKWARD, + { "jawbackward" } }, + { XRFaceTracker::FT_JAW_CLENCH, + { "jawclench" } }, + { XRFaceTracker::FT_JAW_MANDIBLE_RAISE, + { "jawmandibleraise" } }, + { XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT, + { "lipsuckupperright", "lipsuckrt" } }, + { XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT, + { "lipsuckupperleft", "lipsucklt" } }, + { XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT, + { "lipsucklowerright", "lipsuckrb" } }, + { XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT, + { "lipsucklowerleft", "lipsucklb" } }, + { XRFaceTracker::FT_LIP_SUCK_CORNER_RIGHT, + { "lipsuckcornerright" } }, + { XRFaceTracker::FT_LIP_SUCK_CORNER_LEFT, + { "lipsuckcornerleft" } }, + { XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT, + { "lipfunnelupperright", "lipfunnelerrt" } }, + { XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT, + { "lipfunnelupperleft", "lipfunnelerlt" } }, + { XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT, + { "lipfunnellowerright", "lipsuckrb" } }, + { XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT, + { "lipfunnellowerleft", "lipsucklb" } }, + { XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT, + { "lippuckerupperright" } }, + { XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT, + { "lippuckerupperleft" } }, + { XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT, + { "lippuckerlowerright" } }, + { XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT, + { "lippuckerlowerleft" } }, + { XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT, + { "mouthupperupright", "upperlipraiserr" } }, + { XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT, + { "mouthupperupleft", "upperlipraiserl" } }, + { XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT, + { "mouthlowerdownright", "mouthlowerupright", "lowerlipdepressorr" } }, + { XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT, + { "mouthlowerdownleft", "mouthlowerupleft", "lowerlipdepressorl" } }, + { XRFaceTracker::FT_MOUTH_UPPER_DEEPEN_RIGHT, + { "mouthupperdeepenright" } }, + { XRFaceTracker::FT_MOUTH_UPPER_DEEPEN_LEFT, + { "mouthupperdeepenleft" } }, + { XRFaceTracker::FT_MOUTH_UPPER_RIGHT, + { "mouthupperright" } }, + { XRFaceTracker::FT_MOUTH_UPPER_LEFT, + { "mouthupperleft" } }, + { XRFaceTracker::FT_MOUTH_LOWER_RIGHT, + { "mouthlowerright" } }, + { XRFaceTracker::FT_MOUTH_LOWER_LEFT, + { "mouthlowerleft" } }, + { XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT, + { "mouthcornerpullright" } }, + { XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT, + { "mouthcornerpullleft" } }, + { XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT, + { "mouthcornerslantright" } }, + { XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT, + { "mouthcornerslantleft" } }, + { XRFaceTracker::FT_MOUTH_FROWN_RIGHT, + { "mouthfrownright", "lipcornerdepressorr" } }, + { XRFaceTracker::FT_MOUTH_FROWN_LEFT, + { "mouthfrownleft", "lipcornerdepressorl" } }, + { XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, + { "mouthstretchright", "lipstretcherr" } }, + { XRFaceTracker::FT_MOUTH_STRETCH_LEFT, + { "mouthstretchleft", "lipstretcherl" } }, + { XRFaceTracker::FT_MOUTH_DIMPLE_RIGHT, + { "mouthdimplerright", "mouthdimpleright", "dimplerr" } }, + { XRFaceTracker::FT_MOUTH_DIMPLE_LEFT, + { "mouthdimplerleft", "mouthdimpleleft", "dimplerl" } }, + { XRFaceTracker::FT_MOUTH_RAISER_UPPER, + { "mouthraiserupper", "mouthshrugupper", "chinraisert" } }, + { XRFaceTracker::FT_MOUTH_RAISER_LOWER, + { "mouthraiserlower", "mouthshruglower", "mouthloweroverlay", "chinraiserb" } }, + { XRFaceTracker::FT_MOUTH_PRESS_RIGHT, + { "mouthpressright", "lippressorr" } }, + { XRFaceTracker::FT_MOUTH_PRESS_LEFT, + { "mouthpressleft", "lippressorl" } }, + { XRFaceTracker::FT_MOUTH_TIGHTENER_RIGHT, + { "mouthtightenerright", "liptightenerr" } }, + { XRFaceTracker::FT_MOUTH_TIGHTENER_LEFT, + { "mouthtightenerleft", "liptightenerl" } }, + { XRFaceTracker::FT_TONGUE_OUT, + { "tongueout", "tonguelongstep2" } }, + { XRFaceTracker::FT_TONGUE_UP, + { "tongueup" } }, + { XRFaceTracker::FT_TONGUE_DOWN, + { "tonguedown" } }, + { XRFaceTracker::FT_TONGUE_RIGHT, + { "tongueright" } }, + { XRFaceTracker::FT_TONGUE_LEFT, + { "tongueleft" } }, + { XRFaceTracker::FT_TONGUE_ROLL, + { "tongueroll" } }, + { XRFaceTracker::FT_TONGUE_BLEND_DOWN, + { "tongueblenddown" } }, + { XRFaceTracker::FT_TONGUE_CURL_UP, + { "tonguecurlup" } }, + { XRFaceTracker::FT_TONGUE_SQUISH, + { "tonguesquish" } }, + { XRFaceTracker::FT_TONGUE_FLAT, + { "tongueflat" } }, + { XRFaceTracker::FT_TONGUE_TWIST_RIGHT, + { "tonguetwistright" } }, + { XRFaceTracker::FT_TONGUE_TWIST_LEFT, + { "tonguetwistleft" } }, + { XRFaceTracker::FT_SOFT_PALATE_CLOSE, + { "softpalateclose" } }, + { XRFaceTracker::FT_THROAT_SWALLOW, + { "throatswallow" } }, + { XRFaceTracker::FT_NECK_FLEX_RIGHT, + { "neckflexright" } }, + { XRFaceTracker::FT_NECK_FLEX_LEFT, + { "neckflexleft" } }, + { XRFaceTracker::FT_EYE_CLOSED, + { "eyeclosed" } }, + { XRFaceTracker::FT_EYE_WIDE, + { "eyewide" } }, + { XRFaceTracker::FT_EYE_SQUINT, + { "eyesquint" } }, + { XRFaceTracker::FT_EYE_DILATION, + { "eyedilation" } }, + { XRFaceTracker::FT_EYE_CONSTRICT, + { "eyeconstrict" } }, + { XRFaceTracker::FT_BROW_DOWN_RIGHT, + { "browdownright", "browlowererr" } }, + { XRFaceTracker::FT_BROW_DOWN_LEFT, + { "browdownleft", "browlowererl" } }, + { XRFaceTracker::FT_BROW_DOWN, + { "browdown" } }, + { XRFaceTracker::FT_BROW_UP_RIGHT, + { "browupright" } }, + { XRFaceTracker::FT_BROW_UP_LEFT, + { "browupleft" } }, + { XRFaceTracker::FT_BROW_UP, + { "browup" } }, + { XRFaceTracker::FT_NOSE_SNEER, + { "nosesneer" } }, + { XRFaceTracker::FT_NASAL_DILATION, + { "nasaldilation" } }, + { XRFaceTracker::FT_NASAL_CONSTRICT, + { "nasalconstrict" } }, + { XRFaceTracker::FT_CHEEK_PUFF, + { "cheekpuff" } }, + { XRFaceTracker::FT_CHEEK_SUCK, + { "cheeksuck" } }, + { XRFaceTracker::FT_CHEEK_SQUINT, + { "cheeksquint" } }, + { XRFaceTracker::FT_LIP_SUCK_UPPER, + { "lipsuckupper", "mouthrollupper", "mouthupperinside" } }, + { XRFaceTracker::FT_LIP_SUCK_LOWER, + { "lipsucklower", "mouthrolllower", "mouthlowerinside" } }, + { XRFaceTracker::FT_LIP_SUCK, + { "lipsuck" } }, + { XRFaceTracker::FT_LIP_FUNNEL_UPPER, + { "lipfunnelupper", "mouthupperoverturn" } }, + { XRFaceTracker::FT_LIP_FUNNEL_LOWER, + { "lipfunnellower", "mouthloweroverturn" } }, + { XRFaceTracker::FT_LIP_FUNNEL, + { "lipfunnel", "mouthfunnel" } }, + { XRFaceTracker::FT_LIP_PUCKER_UPPER, + { "lippuckerupper" } }, + { XRFaceTracker::FT_LIP_PUCKER_LOWER, + { "lippuckerlower" } }, + { XRFaceTracker::FT_LIP_PUCKER, + { "lippucker", "mouthpucker", "mouthpout" } }, + { XRFaceTracker::FT_MOUTH_UPPER_UP, + { "mouthupperup" } }, + { XRFaceTracker::FT_MOUTH_LOWER_DOWN, + { "mouthlowerdown" } }, + { XRFaceTracker::FT_MOUTH_OPEN, + { "mouthopen" } }, + { XRFaceTracker::FT_MOUTH_RIGHT, + { "mouthright" } }, + { XRFaceTracker::FT_MOUTH_LEFT, + { "mouthleft" } }, + { XRFaceTracker::FT_MOUTH_SMILE_RIGHT, + { "mouthsmileright", "lipcornerpullerr" } }, + { XRFaceTracker::FT_MOUTH_SMILE_LEFT, + { "mouthsmileleft", "lipcornerpullerl" } }, + { XRFaceTracker::FT_MOUTH_SMILE, + { "mouthsmile" } }, + { XRFaceTracker::FT_MOUTH_SAD_RIGHT, + { "mouthsadright" } }, + { XRFaceTracker::FT_MOUTH_SAD_LEFT, + { "mouthsadleft" } }, + { XRFaceTracker::FT_MOUTH_SAD, + { "mouthsad" } }, + { XRFaceTracker::FT_MOUTH_STRETCH, + { "mouthstretch" } }, + { XRFaceTracker::FT_MOUTH_DIMPLE, + { "mouthdimple" } }, + { XRFaceTracker::FT_MOUTH_TIGHTENER, + { "mouthtightener" } }, + { XRFaceTracker::FT_MOUTH_PRESS, + { "mouthpress" } } + }; + + // Convert the name to lower-case and strip non-alphanumeric characters. + const String name = String(p_name).to_lower().replace("_", ""); + + // Iterate through the blend map. + for (const blend_map_entry &entry : blend_map) { + for (const char *n : entry.name) { + if (n == nullptr) { + break; + } + + if (name == n) { + return entry.blend; + } + } + } + + // Blend shape not found. + return -1; +} + +// This method adds all the identified XRFaceTracker blend shapes of +// the mesh to the p_blend_mapping map. The map is indexed by the +// XRFaceTracker blend shape, and the value is the index of the mesh +// blend shape. +static void identify_face_blend_shapes(RBMap<int, int> &p_blend_mapping, const Ref<Mesh> &mesh) { + // Find all blend shapes. + const int count = mesh->get_blend_shape_count(); + for (int i = 0; i < count; i++) { + const int blend = find_face_blend_shape(mesh->get_blend_shape_name(i)); + if (blend >= 0) { + p_blend_mapping[blend] = i; + } + } +} + +// This method removes any unified blend shapes from the p_blend_mapping map +// if all the individual blend shapes are found and going to be driven. +static void remove_driven_unified_blend_shapes(RBMap<int, int> &p_blend_mapping) { + // Entry for unified blend table. + struct unified_blend_entry { + int unified; + int individual[4]; + }; + + // Table of unified blend shapes. + // + // This table consists of: + // - The XRFaceTracker unified blend shape. + // - The individual blend shapes that make up the unified blend shape. + static constexpr unified_blend_entry unified_blends[] = { + { XRFaceTracker::FT_EYE_CLOSED, + { XRFaceTracker::FT_EYE_CLOSED_RIGHT, XRFaceTracker::FT_EYE_CLOSED_LEFT, -1, -1 } }, + { XRFaceTracker::FT_EYE_WIDE, + { XRFaceTracker::FT_EYE_WIDE_RIGHT, XRFaceTracker::FT_EYE_WIDE_LEFT, -1, -1 } }, + { XRFaceTracker::FT_EYE_SQUINT, + { XRFaceTracker::FT_EYE_SQUINT_RIGHT, XRFaceTracker::FT_EYE_SQUINT_LEFT, -1, -1 } }, + { XRFaceTracker::FT_EYE_DILATION, + { XRFaceTracker::FT_EYE_DILATION_RIGHT, XRFaceTracker::FT_EYE_DILATION_LEFT, -1, -1 } }, + { XRFaceTracker::FT_EYE_CONSTRICT, + { XRFaceTracker::FT_EYE_CONSTRICT_RIGHT, XRFaceTracker::FT_EYE_CONSTRICT_LEFT, -1, -1 } }, + { XRFaceTracker::FT_BROW_DOWN_RIGHT, + { XRFaceTracker::FT_BROW_LOWERER_RIGHT, XRFaceTracker::FT_BROW_PINCH_RIGHT, -1, -1 } }, + { XRFaceTracker::FT_BROW_DOWN_LEFT, + { XRFaceTracker::FT_BROW_LOWERER_LEFT, XRFaceTracker::FT_BROW_PINCH_LEFT, -1, -1 } }, + { XRFaceTracker::FT_BROW_DOWN, + { XRFaceTracker::FT_BROW_LOWERER_RIGHT, XRFaceTracker::FT_BROW_PINCH_RIGHT, XRFaceTracker::FT_BROW_LOWERER_LEFT, XRFaceTracker::FT_BROW_PINCH_LEFT } }, + { XRFaceTracker::FT_BROW_UP_RIGHT, + { XRFaceTracker::FT_BROW_INNER_UP_RIGHT, XRFaceTracker::FT_BROW_OUTER_UP_RIGHT, -1, -1 } }, + { XRFaceTracker::FT_BROW_UP_LEFT, + { XRFaceTracker::FT_BROW_INNER_UP_LEFT, XRFaceTracker::FT_BROW_OUTER_UP_LEFT, -1, -1 } }, + { XRFaceTracker::FT_BROW_UP, + { XRFaceTracker::FT_BROW_INNER_UP_RIGHT, XRFaceTracker::FT_BROW_OUTER_UP_RIGHT, XRFaceTracker::FT_BROW_INNER_UP_LEFT, XRFaceTracker::FT_BROW_OUTER_UP_LEFT } }, + { XRFaceTracker::FT_NOSE_SNEER, + { XRFaceTracker::FT_NOSE_SNEER_RIGHT, XRFaceTracker::FT_NOSE_SNEER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_NASAL_DILATION, + { XRFaceTracker::FT_NASAL_DILATION_RIGHT, XRFaceTracker::FT_NASAL_DILATION_LEFT, -1, -1 } }, + { XRFaceTracker::FT_NASAL_CONSTRICT, + { XRFaceTracker::FT_NASAL_CONSTRICT_RIGHT, XRFaceTracker::FT_NASAL_CONSTRICT_LEFT, -1, -1 } }, + { XRFaceTracker::FT_CHEEK_PUFF, + { XRFaceTracker::FT_CHEEK_PUFF_RIGHT, XRFaceTracker::FT_CHEEK_PUFF_LEFT, -1, -1 } }, + { XRFaceTracker::FT_CHEEK_SUCK, + { XRFaceTracker::FT_CHEEK_SUCK_RIGHT, XRFaceTracker::FT_CHEEK_SUCK_LEFT, -1, -1 } }, + { XRFaceTracker::FT_CHEEK_SQUINT, + { XRFaceTracker::FT_CHEEK_SQUINT_RIGHT, XRFaceTracker::FT_CHEEK_SQUINT_LEFT, -1, -1 } }, + { XRFaceTracker::FT_LIP_SUCK_UPPER, + { XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT, XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_LIP_SUCK_LOWER, + { XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT, XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_LIP_SUCK, + { XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT, XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT, XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT, XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT } }, + { XRFaceTracker::FT_LIP_FUNNEL_UPPER, + { XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_LIP_FUNNEL_LOWER, + { XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_LIP_FUNNEL, + { XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT } }, + { XRFaceTracker::FT_LIP_PUCKER_UPPER, + { XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_LIP_PUCKER_LOWER, + { XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_LIP_PUCKER, + { XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT, XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT } }, + { XRFaceTracker::FT_MOUTH_UPPER_UP, + { XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT, XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_LOWER_DOWN, + { XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_OPEN, + { XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT, XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT } }, + { XRFaceTracker::FT_MOUTH_RIGHT, + { XRFaceTracker::FT_MOUTH_UPPER_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_RIGHT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_LEFT, + { XRFaceTracker::FT_MOUTH_UPPER_LEFT, XRFaceTracker::FT_MOUTH_LOWER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_SMILE_RIGHT, + { XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_SMILE_LEFT, + { XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_SMILE, + { XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT } }, + { XRFaceTracker::FT_MOUTH_SAD_RIGHT, + { XRFaceTracker::FT_MOUTH_FROWN_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_SAD_LEFT, + { XRFaceTracker::FT_MOUTH_FROWN_LEFT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_SAD, + { XRFaceTracker::FT_MOUTH_FROWN_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, XRFaceTracker::FT_MOUTH_FROWN_LEFT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT } }, + { XRFaceTracker::FT_MOUTH_STRETCH, + { XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_DIMPLE, + { XRFaceTracker::FT_MOUTH_DIMPLE_RIGHT, XRFaceTracker::FT_MOUTH_DIMPLE_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_TIGHTENER, + { XRFaceTracker::FT_MOUTH_TIGHTENER_RIGHT, XRFaceTracker::FT_MOUTH_TIGHTENER_LEFT, -1, -1 } }, + { XRFaceTracker::FT_MOUTH_PRESS, + { XRFaceTracker::FT_MOUTH_PRESS_RIGHT, XRFaceTracker::FT_MOUTH_PRESS_LEFT, -1, -1 } } + }; + + // Remove unified blend shapes if individual blend shapes are found. + for (const unified_blend_entry &entry : unified_blends) { + // Check if all individual blend shapes are found. + bool found = true; + for (const int i : entry.individual) { + if (i >= 0 && !p_blend_mapping.find(i)) { + found = false; + break; + } + } + + // If all individual blend shapes are found then remove the unified blend shape. + if (found) { + p_blend_mapping.erase(entry.unified); + } + } +} + +void XRFaceModifier3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_face_tracker", "tracker_name"), &XRFaceModifier3D::set_face_tracker); + ClassDB::bind_method(D_METHOD("get_face_tracker"), &XRFaceModifier3D::get_face_tracker); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/head"), "set_face_tracker", "get_face_tracker"); + + ClassDB::bind_method(D_METHOD("set_target", "target"), &XRFaceModifier3D::set_target); + ClassDB::bind_method(D_METHOD("get_target"), &XRFaceModifier3D::get_target); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "MeshInstance3D"), "set_target", "get_target"); +} + +void XRFaceModifier3D::set_face_tracker(const StringName &p_tracker_name) { + tracker_name = p_tracker_name; +} + +StringName XRFaceModifier3D::get_face_tracker() const { + return tracker_name; +} + +void XRFaceModifier3D::set_target(const NodePath &p_target) { + target = p_target; + + if (is_inside_tree()) { + _get_blend_data(); + } +} + +NodePath XRFaceModifier3D::get_target() const { + return target; +} + +MeshInstance3D *XRFaceModifier3D::get_mesh_instance() const { + if (!has_node(target)) { + return nullptr; + } + + Node *node = get_node(target); + if (!node) { + return nullptr; + } + + return Object::cast_to<MeshInstance3D>(node); +} + +void XRFaceModifier3D::_get_blend_data() { + // This method constructs the blend mapping from the XRFaceTracker + // blend shapes to the available blend shapes of the target mesh. It does this + // by: + // + // 1. Identifying the blend shapes of the target mesh and identifying what + // XRFaceTracker blend shape they correspond to. The results are + // placed in the blend_mapping map. + // 2. Prevent over-driving facial blend-shapes by removing any unified blend + // shapes from the map if all the individual blend shapes are already + // found and going to be driven. + + blend_mapping.clear(); + + // Get the target MeshInstance3D. + const MeshInstance3D *mesh_instance = get_mesh_instance(); + if (!mesh_instance) { + return; + } + + // Get the mesh. + const Ref<Mesh> mesh = mesh_instance->get_mesh(); + if (mesh.is_null()) { + return; + } + + // Identify all face blend shapes and populate the map. + identify_face_blend_shapes(blend_mapping, mesh); + + // Remove the unified blend shapes if all the individual blend shapes are found. + remove_driven_unified_blend_shapes(blend_mapping); +} + +void XRFaceModifier3D::_update_face_blends() const { + // Get the XR Server. + const XRServer *xr_server = XRServer::get_singleton(); + if (!xr_server) { + return; + } + + // Get the face tracker. + const Ref<XRFaceTracker> p = xr_server->get_face_tracker(tracker_name); + if (!p.is_valid()) { + return; + } + + // Get the face mesh. + MeshInstance3D *mesh_instance = get_mesh_instance(); + if (!mesh_instance) { + return; + } + + // Get the blend weights. + const PackedFloat32Array weights = p->get_blend_shapes(); + + // Apply all the face blend weights to the mesh. + for (const KeyValue<int, int> &it : blend_mapping) { + mesh_instance->set_blend_shape_value(it.value, weights[it.key]); + } +} + +void XRFaceModifier3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _get_blend_data(); + set_process_internal(true); + } break; + case NOTIFICATION_EXIT_TREE: { + set_process_internal(false); + blend_mapping.clear(); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + _update_face_blends(); + } break; + default: { + } break; + } +} diff --git a/scene/3d/xr_face_modifier_3d.h b/scene/3d/xr_face_modifier_3d.h new file mode 100644 index 0000000000..147c374e95 --- /dev/null +++ b/scene/3d/xr_face_modifier_3d.h @@ -0,0 +1,73 @@ +/**************************************************************************/ +/* xr_face_modifier_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef XR_FACE_MODIFIER_3D_H +#define XR_FACE_MODIFIER_3D_H + +#include "mesh_instance_3d.h" +#include "scene/3d/node_3d.h" + +/** + The XRFaceModifier3D node drives the blend shapes of a MeshInstance3D + with facial expressions from an XRFaceTracking instance. + + The blend shapes provided by the mesh are interrogated, and used to + deduce an optimal mapping from the Unified Expressions blend shapes + provided by the XRFaceTracking instance to drive the face. + */ + +class XRFaceModifier3D : public Node3D { + GDCLASS(XRFaceModifier3D, Node3D); + +private: + StringName tracker_name = "/user/head"; + NodePath target; + + // Map from XRFaceTracker blend shape index to mesh blend shape index. + RBMap<int, int> blend_mapping; + + MeshInstance3D *get_mesh_instance() const; + void _get_blend_data(); + void _update_face_blends() const; + +protected: + static void _bind_methods(); + +public: + void set_face_tracker(const StringName &p_tracker_name); + StringName get_face_tracker() const; + + void set_target(const NodePath &p_target); + NodePath get_target() const; + + void _notification(int p_what); +}; + +#endif // XR_FACE_MODIFIER_3D_H diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index 79db9d5560..70e32c1a31 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -76,8 +76,8 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) { } } -Array XRCamera3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray XRCamera3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // must be child node of XROrigin3D! @@ -424,8 +424,8 @@ XRNode3D::~XRNode3D() { xr_server->disconnect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker)); } -Array XRNode3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray XRNode3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // must be child node of XROrigin! @@ -603,8 +603,8 @@ Plane XRAnchor3D::get_plane() const { Vector<XROrigin3D *> XROrigin3D::origin_nodes; -Array XROrigin3D::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray XROrigin3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { bool has_camera = false; diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index 7f75e69fbc..ad52cf113d 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -55,7 +55,7 @@ protected: void _pose_changed(const Ref<XRPose> &p_pose); public: - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const override; virtual Point2 unproject_position(const Vector3 &p_pos) const override; @@ -109,7 +109,7 @@ public: Ref<XRPose> get_pose(); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; XRNode3D(); ~XRNode3D(); @@ -193,7 +193,7 @@ protected: static void _bind_methods(); public: - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; real_t get_world_scale() const; void set_world_scale(real_t p_world_scale); diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 078d27aa50..87ac0bf5c8 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -661,13 +661,6 @@ bool AnimationMixer::_update_caches() { Ref<Resource> resource; Vector<StringName> leftover_path; - if (!parent->has_node_and_resource(path)) { - if (check_path) { - WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', couldn't resolve track: '" + String(path) + "'. This warning can be disabled in Project Settings."); - } - continue; - } - Node *child = parent->get_node_and_resource(path, resource, leftover_path); if (!child) { if (check_path) { @@ -872,13 +865,12 @@ bool AnimationMixer::_update_caches() { } } else if (track_cache_type == Animation::TYPE_VALUE) { TrackCacheValue *track_value = static_cast<TrackCacheValue *>(track); - if (track_value->init_value.is_string() && anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE) { - WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm."); - } - // If it has at least one angle interpolation, it also uses angle interpolation for blending. bool was_using_angle = track_value->is_using_angle; if (track_src_type == Animation::TYPE_VALUE) { + if (track_value->init_value.is_string() && anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE) { + WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm."); + } track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; } if (check_angle_interpolation && (was_using_angle != track_value->is_using_angle)) { @@ -1419,10 +1411,11 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; // Nothing to blend. } TrackCacheValue *t = static_cast<TrackCacheValue *>(track); - bool is_discrete = a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; + bool is_value = ttype == Animation::TYPE_VALUE; + bool is_discrete = is_value && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; bool force_continuous = callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS; if (!is_discrete || force_continuous) { - Variant value = ttype == Animation::TYPE_VALUE ? a->value_track_interpolate(i, time, is_discrete && force_continuous ? backward : false) : Variant(a->bezier_track_interpolate(i, time)); + Variant value = is_value ? a->value_track_interpolate(i, time, is_discrete && force_continuous ? backward : false) : Variant(a->bezier_track_interpolate(i, time)); value = post_process_key_value(a, i, value, t->object_id); if (value == Variant()) { continue; @@ -1458,7 +1451,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } else { if (seeked) { - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); if (idx < 0) { continue; } @@ -1494,7 +1487,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track); if (seeked) { - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); if (idx < 0) { continue; } @@ -1541,7 +1534,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { // Find stream. int idx = -1; if (seeked) { - idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + // Audio key may be playbacked from the middle, should use FIND_MODE_NEAREST. + // Then, check the current playing stream to prevent to playback doubly. + idx = a->track_find_key(i, time, Animation::FIND_MODE_NEAREST, true); // Discard previous stream when seeking. if (map.has(idx)) { t->audio_stream_playback->stop_stream(map[idx].index); @@ -1609,7 +1604,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } if (seeked) { // Seek. - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); if (idx < 0) { continue; } @@ -1972,7 +1967,13 @@ void AnimationMixer::_build_backup_track_cache() { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); Object *t_obj = ObjectDB::get_instance(t->object_id); if (t_obj) { - t->value = t_obj->get_indexed(t->subpath); + t->value = Animation::cast_to_blendwise(t_obj->get_indexed(t->subpath)); + } + t->use_discrete = false; + if (t->init_value.is_array()) { + t->element_size = MAX(t->element_size.operator int(), (t->value.operator Array()).size()); + } else if (t->init_value.is_string()) { + t->element_size = (real_t)(t->value.operator Array()).size(); } } break; case Animation::TYPE_AUDIO: { diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 5447a00ee3..7808ec788c 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -126,7 +126,7 @@ protected: /* ---- General settings for animation ---- */ AnimationCallbackModeProcess callback_mode_process = ANIMATION_CALLBACK_MODE_PROCESS_IDLE; AnimationCallbackModeMethod callback_mode_method = ANIMATION_CALLBACK_MODE_METHOD_DEFERRED; - AnimationCallbackModeDiscrete callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE; + AnimationCallbackModeDiscrete callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT; int audio_max_polyphony = 32; NodePath root_node; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 9694e855b5..2b7c47c869 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -568,10 +568,11 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const pi.seeked = true; root_animation_node->_pre_process(&process_state, pi, false); started = false; + } else { + pi.seeked = false; + pi.time = p_delta; + root_animation_node->_pre_process(&process_state, pi, false); } - pi.seeked = false; - pi.time = p_delta; - root_animation_node->_pre_process(&process_state, pi, false); } if (!process_state.valid) { @@ -606,8 +607,8 @@ uint64_t AnimationTree::get_last_process_pass() const { return process_pass; } -Array AnimationTree::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray AnimationTree::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!root_animation_node.is_valid()) { warnings.push_back(RTR("No root AnimationNode for the graph is set.")); } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 7d153b2736..87928e4d20 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -261,7 +261,7 @@ public: void set_advance_expression_base_node(const NodePath &p_path); NodePath get_advance_expression_base_node() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; bool is_state_invalid() const; String get_invalid_state_reason() const; diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp index d5f6f91c88..19b3ec481b 100644 --- a/scene/audio/audio_stream_player_internal.cpp +++ b/scene/audio/audio_stream_player_internal.cpp @@ -38,7 +38,7 @@ void AudioStreamPlayerInternal::_set_process(bool p_enabled) { if (physical) { node->set_physics_process_internal(p_enabled); } else { - node->set_process(p_enabled); + node->set_process_internal(p_enabled); } } diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 49616f87cc..66b14dc967 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -430,8 +430,8 @@ bool BaseButton::_was_pressed_by_mouse() const { return was_mouse_pressed; } -Array BaseButton::get_configuration_warnings() const { - Array warnings = Control::get_configuration_warnings(); +PackedStringArray BaseButton::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); if (get_button_group().is_valid() && !is_toggle_mode()) { warnings.push_back(RTR("ButtonGroup is intended to be used only with buttons that have toggle_mode set to true.")); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index b5fbf11c8d..a8d5cee44c 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -139,7 +139,7 @@ public: void set_button_group(const Ref<ButtonGroup> &p_group); Ref<ButtonGroup> get_button_group() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; BaseButton(); ~BaseButton(); diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 594ae3eee8..c6e66c95c6 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -189,8 +189,8 @@ void Container::_notification(int p_what) { } } -Array Container::get_configuration_warnings() const { - Array warnings = Control::get_configuration_warnings(); +PackedStringArray Container::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); if (get_class() == "Container" && get_script().is_null()) { warnings.push_back(RTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead.")); diff --git a/scene/gui/container.h b/scene/gui/container.h index d04a71fe23..94c3c540d7 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -63,7 +63,7 @@ public: virtual Vector<int> get_allowed_size_flags_horizontal() const; virtual Vector<int> get_allowed_size_flags_vertical() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; Container(); }; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 2124ffb806..b351d1ee4f 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -228,9 +228,9 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List } } -Array Control::get_configuration_warnings() const { - ERR_READ_THREAD_GUARD_V(Array()); - Array warnings = Node::get_configuration_warnings(); +PackedStringArray Control::get_configuration_warnings() const { + ERR_READ_THREAD_GUARD_V(PackedStringArray()); + PackedStringArray warnings = Node::get_configuration_warnings(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); diff --git a/scene/gui/control.h b/scene/gui/control.h index 8bcd955457..a900a593dd 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -407,7 +407,7 @@ public: static void set_root_layout_direction(int p_root_dir); virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; virtual bool is_text_field() const; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 7ea3aa344a..c23d21775f 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -251,8 +251,8 @@ Control::CursorShape GraphEdit::get_cursor_shape(const Point2 &p_pos) const { return Control::get_cursor_shape(p_pos); } -Array GraphEdit::get_configuration_warnings() const { - Array warnings = Control::get_configuration_warnings(); +PackedStringArray GraphEdit::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); warnings.push_back(RTR("Please be aware that GraphEdit and GraphNode will undergo extensive refactoring in a future 4.x version involving compatibility-breaking API changes.")); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index dd99651d60..e24f039e84 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -375,7 +375,7 @@ public: virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index f847971015..56da1332e7 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -330,8 +330,8 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col } } -Array Label::get_configuration_warnings() const { - Array warnings = Control::get_configuration_warnings(); +PackedStringArray Label::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); // FIXME: This is not ideal and the sizing model should be fixed, // but for now we have to warn about this impossible to resolve combination. diff --git a/scene/gui/label.h b/scene/gui/label.h index fe90bd06bb..4bd0e53605 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -97,7 +97,7 @@ protected: public: virtual Size2 get_minimum_size() const override; - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_horizontal_alignment(HorizontalAlignment p_alignment); HorizontalAlignment get_horizontal_alignment() const; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 72a84e4884..7aa4a136d9 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -138,8 +138,16 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) { return; } + if (selection.enabled) { + selection_delete(); + return; + } + + if (caret_column == 0) { + return; // Nothing to do. + } + if (p_all_to_left) { - deselect(); text = text.substr(caret_column); _shape(); set_caret_column(0); @@ -147,11 +155,6 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) { return; } - if (selection.enabled) { - selection_delete(); - return; - } - if (p_word) { int cc = caret_column; @@ -176,25 +179,22 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { return; } - if (p_all_to_right) { - deselect(); - text = text.substr(0, caret_column); - _shape(); - _text_changed(); - return; - } - if (selection.enabled) { selection_delete(); return; } - int text_len = text.length(); - - if (caret_column == text_len) { + if (caret_column == text.length()) { return; // Nothing to do. } + if (p_all_to_right) { + text = text.substr(0, caret_column); + _shape(); + _text_changed(); + return; + } + if (p_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); @@ -2279,8 +2279,8 @@ void LineEdit::_emit_text_change() { emit_signal(SNAME("text_changed"), text); text_changed_dirty = false; } -Array LineEdit::get_configuration_warnings() const { - Array warnings = Control::get_configuration_warnings(); +PackedStringArray LineEdit::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); if (secret_character.length() > 1) { warnings.push_back("Secret Character property supports only one character. Extra characters will be ignored."); } diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index b2582e0eb5..993bc727e4 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -385,7 +385,7 @@ public: virtual bool is_text_field() const override; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void show_virtual_keyboard(); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 8fc21c077b..6613f25b4c 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -336,6 +336,13 @@ void OptionButton::set_item_count(int p_count) { } } + if (!initialized) { + if (queued_current != current) { + current = queued_current; + } + initialized = true; + } + _refresh_size_cache(); notify_property_list_changed(); } @@ -432,7 +439,13 @@ void OptionButton::_select(int p_which, bool p_emit) { } void OptionButton::_select_int(int p_which) { - if (p_which < NONE_SELECTED || p_which >= popup->get_item_count()) { + if (p_which < NONE_SELECTED) { + return; + } + if (p_which >= popup->get_item_count()) { + if (!initialized) { + queued_current = p_which; + } return; } _select(p_which, false); @@ -571,11 +584,10 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_allow_reselect"), &OptionButton::get_allow_reselect); ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &OptionButton::set_disable_shortcuts); - // "selected" property must come after "item_count", otherwise GH-10213 occurs. - ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_to_longest_item"), "set_fit_to_longest_item", "is_fit_to_longest_item"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect"); + ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index"))); ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "index"))); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index eef498333b..9c15b295a9 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -44,6 +44,8 @@ class OptionButton : public Button { Vector2 _cached_size; bool cache_refresh_pending = false; bool allow_reselect = false; + bool initialized = false; + int queued_current = -1; struct ThemeCache { Ref<StyleBox> normal; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 90d962ccfd..0da5093ab8 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -456,9 +456,6 @@ void PopupMenu::_input_from_window(const Ref<InputEvent> &p_event) { } void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { - Ref<InputEventMouseButton> b = p_event; - Ref<InputEventMouseMotion> m = p_event; - if (!items.is_empty()) { Input *input = Input::get_singleton(); Ref<InputEventJoypadMotion> joypadmotion_event = p_event; @@ -587,58 +584,53 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { } } - if (m.is_valid() && drag_to_press) { - BitField<MouseButtonMask> initial_button_mask = m->get_button_mask(); - if (!initial_button_mask.has_flag(mouse_button_to_mask(MouseButton::LEFT)) && !initial_button_mask.has_flag(mouse_button_to_mask(MouseButton::RIGHT))) { - mouse_is_pressed = false; - } - - if (!item_clickable_area.has_point(m->get_position()) && !mouse_is_pressed) { - drag_to_press = false; - } - } + Ref<InputEventMouseButton> b = p_event; - if ((b.is_valid() && b->is_pressed()) || (!mouse_is_pressed && drag_to_press)) { - if (b.is_valid()) { - MouseButton button_idx = b->get_button_index(); - if (button_idx != MouseButton::LEFT && button_idx != MouseButton::RIGHT) { - return; - } - } else { - uint64_t now = OS::get_singleton()->get_ticks_msec(); - uint64_t diff = now - popup_time_msec; - if (diff < 250) { - drag_to_press = false; - return; - } + if (b.is_valid()) { + if (!item_clickable_area.has_point(b->get_position())) { + return; } - drag_to_press = false; - - int over = -1; - - if (m.is_valid()) { - over = _get_mouse_over(m->get_position()); - } else if (b.is_valid()) { - over = _get_mouse_over(b->get_position()); - } + MouseButton button_idx = b->get_button_index(); + if (!b->is_pressed()) { + // Activate the item on release of either the left mouse button or + // any mouse button held down when the popup was opened. + // This allows for opening the popup and triggering an action in a single mouse click. + if (button_idx == MouseButton::LEFT || initial_button_mask.has_flag(mouse_button_to_mask(button_idx))) { + bool was_during_grabbed_click = during_grabbed_click; + during_grabbed_click = false; + initial_button_mask.clear(); + + // Disable clicks under a time threshold to avoid selection right when opening the popup. + uint64_t now = OS::get_singleton()->get_ticks_msec(); + uint64_t diff = now - popup_time_msec; + if (diff < 150) { + return; + } - if (over < 0) { - hide(); - return; - } + int over = _get_mouse_over(b->get_position()); + if (over < 0) { + if (!was_during_grabbed_click) { + hide(); + } + return; + } - if (items[over].separator || items[over].disabled) { - return; - } + if (items[over].separator || items[over].disabled) { + return; + } - if (!items[over].submenu.is_empty()) { - _activate_submenu(over); - return; + if (!items[over].submenu.is_empty()) { + _activate_submenu(over); + return; + } + activate_item(over); + } } - activate_item(over); } + Ref<InputEventMouseMotion> m = p_event; + if (m.is_valid()) { if (m->get_velocity().is_zero_approx()) { return; @@ -1070,6 +1062,11 @@ void PopupMenu::_notification(int p_what) { } } break; + case NOTIFICATION_POST_POPUP: { + initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); + during_grabbed_click = (bool)initial_button_mask; + } break; + case NOTIFICATION_INTERNAL_PROCESS: { Input *input = Input::get_singleton(); @@ -2805,8 +2802,6 @@ void PopupMenu::popup(const Rect2i &p_bounds) { moved = Vector2(); popup_time_msec = OS::get_singleton()->get_ticks_msec(); Popup::popup(p_bounds); - drag_to_press = true; - mouse_is_pressed = true; } PopupMenu::PopupMenu() { diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index abe852a993..d068418059 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -107,8 +107,8 @@ class PopupMenu : public Popup { Timer *submenu_timer = nullptr; List<Rect2> autohide_areas; Vector<Item> items; - bool mouse_is_pressed = true; - bool drag_to_press = true; + BitField<MouseButtonMask> initial_button_mask; + bool during_grabbed_click = false; int mouse_over = -1; int submenu_over = -1; String _get_accel_text(const Item &p_item) const; diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index da40c7f3e2..236dfcc864 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -30,8 +30,8 @@ #include "range.h" -Array Range::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray Range::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (shared->exp_ratio && shared->min <= 0) { warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.")); diff --git a/scene/gui/range.h b/scene/gui/range.h index e08a77f0f7..b1c2446ded 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -103,7 +103,7 @@ public: void share(Range *p_range); void unshare(); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; Range(); ~Range(); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index fd3a1f4a45..89d308de3f 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -537,8 +537,8 @@ void ScrollContainer::set_follow_focus(bool p_follow) { follow_focus = p_follow; } -Array ScrollContainer::get_configuration_warnings() const { - Array warnings = Container::get_configuration_warnings(); +PackedStringArray ScrollContainer::get_configuration_warnings() const { + PackedStringArray warnings = Container::get_configuration_warnings(); int found = 0; diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 69ff7ccc14..02146618cd 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -119,7 +119,7 @@ public: VScrollBar *get_v_scroll_bar(); void ensure_control_visible(Control *p_control); - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; ScrollContainer(); }; diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 4ed136005a..0d33774e20 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -259,8 +259,8 @@ void SubViewportContainer::remove_child_notify(Node *p_child) { } } -Array SubViewportContainer::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray SubViewportContainer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); bool has_viewport = false; for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index e172f2f040..06420de730 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -68,7 +68,7 @@ public: virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; SubViewportContainer(); }; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index bb787a742a..f87bccdfe7 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -635,6 +635,13 @@ void TabBar::set_tab_count(int p_count) { } } + if (!initialized) { + if (queued_current != current) { + current = queued_current; + } + initialized = true; + } + queue_redraw(); update_minimum_size(); notify_property_list_changed(); @@ -649,6 +656,10 @@ void TabBar::set_current_tab(int p_current) { // An index of -1 is only valid if deselecting is enabled or there are no valid tabs. ERR_FAIL_COND_MSG(!_can_deselect(), "Cannot deselect tabs, deselection is not enabled."); } else { + if (!initialized && p_current >= get_tab_count()) { + queued_current = p_current; + return; + } ERR_FAIL_INDEX(p_current, get_tab_count()); } @@ -1825,9 +1836,6 @@ void TabBar::_bind_methods() { ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to"))); - // "current_tab" property must come after "tab_count", otherwise the property isn't loaded correctly. - ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); @@ -1840,6 +1848,8 @@ void TabBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_enabled"), "set_deselect_enabled", "get_deselect_enabled"); + ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_"); + BIND_ENUM_CONSTANT(ALIGNMENT_LEFT); BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); BIND_ENUM_CONSTANT(ALIGNMENT_RIGHT); diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 444c737220..65a1d5bd4f 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -108,6 +108,9 @@ private: bool scroll_to_selected = true; int tabs_rearrange_group = -1; + bool initialized = false; + int queued_current = -1; + const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5; const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20; float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS; diff --git a/scene/main/missing_node.cpp b/scene/main/missing_node.cpp index 02e079892c..83672ae5e0 100644 --- a/scene/main/missing_node.cpp +++ b/scene/main/missing_node.cpp @@ -82,9 +82,9 @@ bool MissingNode::is_recording_properties() const { return recording_properties; } -Array MissingNode::get_configuration_warnings() const { +PackedStringArray MissingNode::get_configuration_warnings() const { // The mere existence of this node is warning. - Array ret; + PackedStringArray ret; if (!original_scene.is_empty()) { ret.push_back(vformat(RTR("This node was an instance of scene '%s', which was no longer available when this scene was loaded."), original_scene)); ret.push_back(vformat(RTR("Saving current scene will discard instance and all its properties, including editable children edits (if existing)."))); diff --git a/scene/main/missing_node.h b/scene/main/missing_node.h index ccaf1e471e..fb1c957988 100644 --- a/scene/main/missing_node.h +++ b/scene/main/missing_node.h @@ -59,7 +59,7 @@ public: void set_recording_properties(bool p_enable); bool is_recording_properties() const; - virtual Array get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; MissingNode(); }; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 77e00e4ab4..f827f68def 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -110,14 +110,12 @@ void Node::_notification(int p_notification) { // Don't translate UI elements when they're being edited. if (is_part_of_edited_scene()) { set_message_translation(false); - } else if (data.auto_translate_mode != AUTO_TRANSLATE_MODE_DISABLED) { - notification(NOTIFICATION_TRANSLATION_CHANGED); } -#else +#endif + if (data.auto_translate_mode != AUTO_TRANSLATE_MODE_DISABLED) { notification(NOTIFICATION_TRANSLATION_CHANGED); } -#endif if (data.input) { add_to_group("_vp_input" + itos(get_viewport()->get_instance_id())); @@ -2506,7 +2504,7 @@ StringName Node::get_property_store_alias(const StringName &p_property) const { bool Node::is_part_of_edited_scene() const { return Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->get_edited_scene_root() && - (get_tree()->get_edited_scene_root() == this || get_tree()->get_edited_scene_root()->is_ancestor_of(this)); + get_tree()->get_edited_scene_root()->get_parent()->is_ancestor_of(this); } #endif @@ -3027,9 +3025,9 @@ Array Node::_get_node_and_resource(const NodePath &p_path) { Node *Node::get_node_and_resource(const NodePath &p_path, Ref<Resource> &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const { ERR_THREAD_GUARD_V(nullptr); - Node *node = get_node(p_path); r_res = Ref<Resource>(); r_leftover_subpath = Vector<StringName>(); + Node *node = get_node_or_null(p_path); if (!node) { return nullptr; } @@ -3204,91 +3202,16 @@ void Node::clear_internal_tree_resource_paths() { } } -Array Node::get_configuration_warnings() const { - ERR_THREAD_GUARD_V(Array()); - Array warnings; - GDVIRTUAL_CALL(_get_configuration_warnings, warnings); - return warnings; -} +PackedStringArray Node::get_configuration_warnings() const { + ERR_THREAD_GUARD_V(PackedStringArray()); + PackedStringArray ret; -Dictionary Node::configuration_warning_to_dict(const Variant &p_warning) const { - switch (p_warning.get_type()) { - case Variant::Type::DICTIONARY: - return p_warning; - case Variant::Type::STRING: { - // Convert string to dictionary. - Dictionary warning; - warning["message"] = p_warning; - return warning; - } - default: { - ERR_FAIL_V_MSG(Dictionary(), "Node::get_configuration_warnings returned a value which is neither a string nor a dictionary, but a " + Variant::get_type_name(p_warning.get_type())); - } - } -} - -Vector<Dictionary> Node::get_configuration_warnings_as_dicts() const { - Vector<Dictionary> ret; - Array mixed = get_configuration_warnings(); - for (int i = 0; i < mixed.size(); i++) { - ret.append(configuration_warning_to_dict(mixed[i])); - } - return ret; -} - -Vector<Dictionary> Node::get_configuration_warnings_of_property(const String &p_property) const { - Vector<Dictionary> ret; - Vector<Dictionary> warnings = get_configuration_warnings_as_dicts(); - if (p_property.is_empty()) { + Vector<String> warnings; + if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) { ret.append_array(warnings); - } else { - // Filter by property path. - for (int i = 0; i < warnings.size(); i++) { - Dictionary warning = warnings[i]; - String warning_property = warning.get("property", String()); - if (p_property == warning_property) { - ret.append(warning); - } - } } - return ret; -} - -PackedStringArray Node::get_configuration_warnings_as_strings(bool p_wrap_lines, const String &p_property) const { - Vector<Dictionary> warnings = get_configuration_warnings_of_property(p_property); - const String bullet_point = U"• "; - PackedStringArray all_warnings; - for (const Dictionary &warning : warnings) { - if (!warning.has("message")) { - continue; - } - - // Prefix with property name if we are showing all warnings. - String text; - if (warning.has("property") && p_property.is_empty()) { - text = bullet_point + vformat("[%s] %s", warning["property"], warning["message"]); - } else { - text = bullet_point + static_cast<String>(warning["message"]); - } - - if (p_wrap_lines) { - // Limit the line width while keeping some padding. - // It is not efficient, but it does not have to be. - const PackedInt32Array boundaries = TS->string_get_word_breaks(text, "", 80); - PackedStringArray lines; - for (int i = 0; i < boundaries.size(); i += 2) { - const int start = boundaries[i]; - const int end = boundaries[i + 1]; - String line = text.substr(start, end - start); - lines.append(line); - } - text = String("\n").join(lines); - } - text = text.replace("\n", "\n "); - all_warnings.append(text); - } - return all_warnings; + return ret; } void Node::update_configuration_warnings() { @@ -3774,16 +3697,6 @@ String Node::_get_name_num_separator() { return " "; } -StringName Node::get_configuration_warning_icon(int p_count) { - if (p_count == 1) { - return SNAME("NodeWarning"); - } else if (p_count <= 3) { - return vformat("NodeWarnings%d", p_count); - } else { - return SNAME("NodeWarnings4Plus"); - } -} - Node::Node() { orphan_node_count++; } diff --git a/scene/main/node.h b/scene/main/node.h index bbbdb87a32..b936cdd375 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -304,7 +304,6 @@ protected: static void _bind_methods(); static String _get_name_num_separator(); - static StringName get_configuration_warning_icon(int p_count); friend class SceneState; @@ -331,7 +330,7 @@ protected: GDVIRTUAL0(_enter_tree) GDVIRTUAL0(_exit_tree) GDVIRTUAL0(_ready) - GDVIRTUAL0RC(Array, _get_configuration_warnings) + GDVIRTUAL0RC(Vector<String>, _get_configuration_warnings) GDVIRTUAL1(_input, Ref<InputEvent>) GDVIRTUAL1(_shortcut_input, Ref<InputEvent>) @@ -648,11 +647,7 @@ public: _FORCE_INLINE_ Viewport *get_viewport() const { return data.viewport; } - virtual Array get_configuration_warnings() const; - Dictionary configuration_warning_to_dict(const Variant &p_warning) const; - Vector<Dictionary> get_configuration_warnings_as_dicts() const; - Vector<Dictionary> get_configuration_warnings_of_property(const String &p_property = String()) const; - PackedStringArray get_configuration_warnings_as_strings(bool p_wrap_lines, const String &p_property = String()) const; + virtual PackedStringArray get_configuration_warnings() const; void update_configuration_warnings(); diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp index b3d3659c5b..e3c26c30e2 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -271,8 +271,8 @@ void ShaderGlobalsOverride::_notification(int p_what) { } } -Array ShaderGlobalsOverride::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray ShaderGlobalsOverride::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!active) { warnings.push_back(RTR("ShaderGlobalsOverride is not active because another node of the same type is in the scene.")); diff --git a/scene/main/shader_globals_override.h b/scene/main/shader_globals_override.h index 72226c0cb3..d8557ecf6a 100644 --- a/scene/main/shader_globals_override.h +++ b/scene/main/shader_globals_override.h @@ -58,7 +58,7 @@ protected: static void _bind_methods(); public: - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; ShaderGlobalsOverride(); }; diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index acb788f76f..0f4f18b495 100644 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -180,8 +180,8 @@ void Timer::_set_process(bool p_process, bool p_force) { processing = p_process; } -Array Timer::get_configuration_warnings() const { - Array warnings = Node::get_configuration_warnings(); +PackedStringArray Timer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (wait_time < 0.05 - CMP_EPSILON) { warnings.push_back(RTR("Very low timer wait times (< 0.05 seconds) may behave in significantly different ways depending on the rendered or physics frame rate.\nConsider using a script's process loop instead of relying on a Timer for very low wait times.")); diff --git a/scene/main/timer.h b/scene/main/timer.h index add61ef5f4..d16e49793d 100644 --- a/scene/main/timer.h +++ b/scene/main/timer.h @@ -73,7 +73,7 @@ public: double get_time_left() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_timer_process_callback(TimerProcessCallback p_callback); TimerProcessCallback get_timer_process_callback() const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 5952af60ee..a86a685fc3 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1383,6 +1383,13 @@ Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) { } Transform2D ai = get_final_transform().affine_inverse(); + Ref<InputEventMouse> me = ev; + if (me.is_valid()) { + me = me->xformed_by(ai); + // For InputEventMouse, the global position is not adjusted by ev->xformed_by() and needs to be set separately. + me->set_global_position(me->get_position()); + return me; + } return ev->xformed_by(ai); } @@ -3510,9 +3517,9 @@ Variant Viewport::gui_get_drag_data() const { return gui.drag_data; } -Array Viewport::get_configuration_warnings() const { - ERR_MAIN_THREAD_GUARD_V(Array()); - Array warnings = Node::get_configuration_warnings(); +PackedStringArray Viewport::get_configuration_warnings() const { + ERR_MAIN_THREAD_GUARD_V(PackedStringArray()); + PackedStringArray warnings = Node::get_configuration_warnings(); if (size.x <= 1 || size.y <= 1) { warnings.push_back(RTR("The Viewport size must be greater than or equal to 2 pixels on both dimensions to render anything.")); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 68f0e8b655..03db0d4023 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -614,7 +614,7 @@ public: Control *gui_get_focus_owner() const; Control *gui_get_hovered_control() const; - Array get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_debug_draw(DebugDraw p_debug_draw); DebugDraw get_debug_draw() const; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 3ec36f731c..c7cd52124a 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1770,7 +1770,10 @@ void Window::popup(const Rect2i &p_screen_rect) { if (p_screen_rect != Rect2i()) { set_position(p_screen_rect.position); - set_size(p_screen_rect.size); + int screen_id = DisplayServer::get_singleton()->get_screen_from_rect(p_screen_rect); + Size2i screen_size = DisplayServer::get_singleton()->screen_get_usable_rect(screen_id).size; + Size2i new_size = p_screen_rect.size.min(screen_size); + set_size(new_size); } Rect2i adjust = _popup_adjust_rect(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index d7e5eb4698..837237ac50 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -272,6 +272,7 @@ #include "scene/3d/visible_on_screen_notifier_3d.h" #include "scene/3d/voxel_gi.h" #include "scene/3d/world_environment.h" +#include "scene/3d/xr_face_modifier_3d.h" #include "scene/3d/xr_nodes.h" #include "scene/animation/root_motion_view.h" #include "scene/resources/environment.h" @@ -519,6 +520,7 @@ void register_scene_types() { GDREGISTER_CLASS(XRController3D); GDREGISTER_CLASS(XRAnchor3D); GDREGISTER_CLASS(XROrigin3D); + GDREGISTER_CLASS(XRFaceModifier3D); GDREGISTER_CLASS(MeshInstance3D); GDREGISTER_CLASS(OccluderInstance3D); GDREGISTER_ABSTRACT_CLASS(Occluder3D); diff --git a/scene/resources/animation.compat.inc b/scene/resources/animation.compat.inc index bbf016b34d..d7d8867748 100644 --- a/scene/resources/animation.compat.inc +++ b/scene/resources/animation.compat.inc @@ -50,12 +50,17 @@ Variant Animation::_value_track_interpolate_bind_compat_86629(int p_track, doubl return value_track_interpolate(p_track, p_time, false); } +int Animation::_track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode) const { + return track_find_key(p_track, p_time, p_find_mode, false); +} + void Animation::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("position_track_interpolate", "track_idx", "time_sec"), &Animation::_position_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("rotation_track_interpolate", "track_idx", "time_sec"), &Animation::_rotation_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("scale_track_interpolate", "track_idx", "time_sec"), &Animation::_scale_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("blend_shape_track_interpolate", "track_idx", "time_sec"), &Animation::_blend_shape_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("value_track_interpolate", "track_idx", "time_sec"), &Animation::_value_track_interpolate_bind_compat_86629); + ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_86661, DEFVAL(FIND_MODE_NEAREST)); } #endif // DISABLE_DEPRECATED diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 796a03bd8b..079101f679 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -1474,7 +1474,7 @@ void Animation::track_remove_key(int p_track, int p_idx) { emit_changed(); } -int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) const { +int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, bool p_limit) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -1496,7 +1496,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) return key_index; } - int k = _find(tt->positions, p_time); + int k = _find(tt->positions, p_time, false, p_limit); if (k < 0 || k >= tt->positions.size()) { return -1; } @@ -1523,7 +1523,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) return key_index; } - int k = _find(rt->rotations, p_time); + int k = _find(rt->rotations, p_time, false, p_limit); if (k < 0 || k >= rt->rotations.size()) { return -1; } @@ -1550,7 +1550,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) return key_index; } - int k = _find(st->scales, p_time); + int k = _find(st->scales, p_time, false, p_limit); if (k < 0 || k >= st->scales.size()) { return -1; } @@ -1577,7 +1577,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) return key_index; } - int k = _find(bst->blend_shapes, p_time); + int k = _find(bst->blend_shapes, p_time, false, p_limit); if (k < 0 || k >= bst->blend_shapes.size()) { return -1; } @@ -1589,7 +1589,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) } break; case TYPE_VALUE: { ValueTrack *vt = static_cast<ValueTrack *>(t); - int k = _find(vt->values, p_time); + int k = _find(vt->values, p_time, false, p_limit); if (k < 0 || k >= vt->values.size()) { return -1; } @@ -1601,7 +1601,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) } break; case TYPE_METHOD: { MethodTrack *mt = static_cast<MethodTrack *>(t); - int k = _find(mt->methods, p_time); + int k = _find(mt->methods, p_time, false, p_limit); if (k < 0 || k >= mt->methods.size()) { return -1; } @@ -1613,7 +1613,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) } break; case TYPE_BEZIER: { BezierTrack *bt = static_cast<BezierTrack *>(t); - int k = _find(bt->values, p_time); + int k = _find(bt->values, p_time, false, p_limit); if (k < 0 || k >= bt->values.size()) { return -1; } @@ -1625,7 +1625,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) } break; case TYPE_AUDIO: { AudioTrack *at = static_cast<AudioTrack *>(t); - int k = _find(at->values, p_time); + int k = _find(at->values, p_time, false, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -1637,7 +1637,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) } break; case TYPE_ANIMATION: { AnimationTrack *at = static_cast<AnimationTrack *>(t); - int k = _find(at->values, p_time); + int k = _find(at->values, p_time, false, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -2332,7 +2332,7 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr } template <class K> -int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward) const { +int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward, bool p_limit) const { int len = p_keys.size(); if (len == 0) { return -2; @@ -2344,7 +2344,7 @@ int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward) co #ifdef DEBUG_ENABLED if (low > high) { - ERR_PRINT("low > high, this may be a bug"); + ERR_PRINT("low > high, this may be a bug."); } #endif @@ -2372,6 +2372,14 @@ int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward) co } } + if (p_limit) { + double diff = length - keys[middle].time; + if ((signbit(keys[middle].time) && !Math::is_zero_approx(keys[middle].time)) || (signbit(diff) && !Math::is_zero_approx(diff))) { + ERR_PRINT_ONCE_ED("Found the key outside the animation range. Consider using the clean-up option in AnimationTrackEditor to fix it."); + return -1; + } + } + return middle; } @@ -3804,7 +3812,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("track_get_key_count", "track_idx"), &Animation::track_get_key_count); ClassDB::bind_method(D_METHOD("track_get_key_value", "track_idx", "key_idx"), &Animation::track_get_key_value); ClassDB::bind_method(D_METHOD("track_get_key_time", "track_idx", "key_idx"), &Animation::track_get_key_time); - ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST)); + ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode", "limit"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST), DEFVAL(false)); ClassDB::bind_method(D_METHOD("track_set_interpolation_type", "track_idx", "interpolation"), &Animation::track_set_interpolation_type); ClassDB::bind_method(D_METHOD("track_get_interpolation_type", "track_idx"), &Animation::track_get_interpolation_type); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 115a1a5050..5b27958005 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -44,13 +44,13 @@ public: typedef uint32_t TypeHash; enum TrackType { - TYPE_VALUE, ///< Set a value in a property, can be interpolated. - TYPE_POSITION_3D, ///< Position 3D track - TYPE_ROTATION_3D, ///< Rotation 3D track - TYPE_SCALE_3D, ///< Scale 3D track - TYPE_BLEND_SHAPE, ///< Blend Shape track - TYPE_METHOD, ///< Call any method on a specific node. - TYPE_BEZIER, ///< Bezier curve + TYPE_VALUE, // Set a value in a property, can be interpolated. + TYPE_POSITION_3D, // Position 3D track, can be compressed. + TYPE_ROTATION_3D, // Rotation 3D track, can be compressed. + TYPE_SCALE_3D, // Scale 3D track, can be compressed. + TYPE_BLEND_SHAPE, // Blend Shape track, can be compressed. + TYPE_METHOD, // Call any method on a specific node. + TYPE_BEZIER, // Bezier curve. TYPE_AUDIO, TYPE_ANIMATION, }; @@ -116,10 +116,10 @@ private: struct Key { real_t transition = 1.0; - double time = 0.0; // time in secs + double time = 0.0; // Time in secs. }; - // transform key holds either Vector3 or Quaternion + // Transform key holds either Vector3 or Quaternion. template <class T> struct TKey : public Key { T value; @@ -188,8 +188,8 @@ private: /* BEZIER TRACK */ struct BezierKey { - Vector2 in_handle; //relative (x always <0) - Vector2 out_handle; //relative (x always >0) + Vector2 in_handle; // Relative (x always <0) + Vector2 out_handle; // Relative (x always >0) real_t value = 0.0; #ifdef TOOLS_ENABLED HandleMode handle_mode = HANDLE_MODE_FREE; @@ -208,8 +208,8 @@ private: struct AudioKey { Ref<Resource> stream; - real_t start_offset = 0.0; //offset from start - real_t end_offset = 0.0; //offset from end, if 0 then full length or infinite + real_t start_offset = 0.0; // Offset from start. + real_t end_offset = 0.0; // Offset from end, if 0 then full length or infinite. AudioKey() { } }; @@ -235,10 +235,6 @@ private: Vector<Track *> tracks; - /* - template<class T> - int _insert_pos(double p_time, T& p_keys);*/ - template <class T> void _clear(T &p_keys); @@ -247,7 +243,7 @@ private: template <class K> - inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false) const; + inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false, bool p_limit = false) const; _FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const; _FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const; @@ -327,7 +323,7 @@ private: struct Compression { enum { MAX_DATA_TRACK_SIZE = 16384, - BLEND_SHAPE_RANGE = 8, // - 8.0 to 8.0 + BLEND_SHAPE_RANGE = 8, // -8.0 to 8.0. FORMAT_VERSION = 1 }; struct Page { @@ -337,7 +333,7 @@ private: uint32_t fps = 120; LocalVector<Page> pages; - LocalVector<AABB> bounds; //used by position and scale tracks (which contain index to track and index to bounds). + LocalVector<AABB> bounds; // Used by position and scale tracks (which contain index to track and index to bounds). bool enabled = false; } compression; @@ -386,6 +382,7 @@ protected: Vector3 _scale_track_interpolate_bind_compat_86629(int p_track, double p_time) const; float _blend_shape_track_interpolate_bind_compat_86629(int p_track, double p_time) const; Variant _value_track_interpolate_bind_compat_86629(int p_track, double p_time) const; + int _track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; static void _bind_compatibility_methods(); #endif // DISABLE_DEPRECATED @@ -417,7 +414,7 @@ public: void track_set_key_transition(int p_track, int p_key_idx, real_t p_transition); void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value); void track_set_key_time(int p_track, int p_key_idx, double p_time); - int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; + int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false) const; void track_remove_key(int p_track, int p_idx); void track_remove_key_at_time(int p_track, double p_time); int track_get_key_count(int p_track) const; @@ -503,7 +500,7 @@ public: void clear(); void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3); - void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests + void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests. // Helper functions for Variant. static bool is_variant_interpolatable(const Variant p_value); diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index b936c2decf..910a6e303d 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -766,6 +766,21 @@ bool Environment::is_fog_enabled() const { return fog_enabled; } +void Environment::set_fog_mode(FogMode p_mode) { + if (fog_mode != p_mode && p_mode == FogMode::FOG_MODE_EXPONENTIAL) { + set_fog_density(0.01); + } else { + set_fog_density(1.0); + } + fog_mode = p_mode; + _update_fog(); + notify_property_list_changed(); +} + +Environment::FogMode Environment::get_fog_mode() const { + return fog_mode; +} + void Environment::set_fog_light_color(const Color &p_light_color) { fog_light_color = p_light_color; _update_fog(); @@ -837,7 +852,51 @@ void Environment::_update_fog() { fog_height, fog_height_density, fog_aerial_perspective, - fog_sky_affect); + fog_sky_affect, + RS::EnvironmentFogMode(fog_mode)); +} + +// Depth Fog + +void Environment::set_fog_depth_curve(float p_curve) { + fog_depth_curve = p_curve; + _update_fog_depth(); +} + +float Environment::get_fog_depth_curve() const { + return fog_depth_curve; +} + +void Environment::set_fog_depth_begin(float p_begin) { + fog_depth_begin = p_begin; + if (fog_depth_begin > fog_depth_end) { + set_fog_depth_end(fog_depth_begin); + } + _update_fog_depth(); +} + +float Environment::get_fog_depth_begin() const { + return fog_depth_begin; +} + +void Environment::set_fog_depth_end(float p_end) { + fog_depth_end = p_end; + if (fog_depth_end < fog_depth_begin) { + set_fog_depth_begin(fog_depth_end); + } + _update_fog_depth(); +} + +float Environment::get_fog_depth_end() const { + return fog_depth_end; +} + +void Environment::_update_fog_depth() { + RS::get_singleton()->environment_set_fog_depth( + environment, + fog_depth_curve, + fog_depth_begin, + fog_depth_end); } // Volumetric Fog @@ -1040,6 +1099,12 @@ void Environment::_validate_property(PropertyInfo &p_property) const { } } + if (p_property.name == "fog_depth_curve" || p_property.name == "fog_depth_begin" || p_property.name == "fog_depth_end") { + if (fog_mode == FOG_MODE_EXPONENTIAL) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } + } + if (p_property.name == "ambient_light_color" || p_property.name == "ambient_light_energy") { if (ambient_source == AMBIENT_SOURCE_DISABLED) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; @@ -1377,6 +1442,8 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fog_enabled", "enabled"), &Environment::set_fog_enabled); ClassDB::bind_method(D_METHOD("is_fog_enabled"), &Environment::is_fog_enabled); + ClassDB::bind_method(D_METHOD("set_fog_mode", "mode"), &Environment::set_fog_mode); + ClassDB::bind_method(D_METHOD("get_fog_mode"), &Environment::get_fog_mode); ClassDB::bind_method(D_METHOD("set_fog_light_color", "light_color"), &Environment::set_fog_light_color); ClassDB::bind_method(D_METHOD("get_fog_light_color"), &Environment::get_fog_light_color); ClassDB::bind_method(D_METHOD("set_fog_light_energy", "light_energy"), &Environment::set_fog_light_energy); @@ -1399,8 +1466,16 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fog_sky_affect", "sky_affect"), &Environment::set_fog_sky_affect); ClassDB::bind_method(D_METHOD("get_fog_sky_affect"), &Environment::get_fog_sky_affect); + ClassDB::bind_method(D_METHOD("set_fog_depth_curve", "curve"), &Environment::set_fog_depth_curve); + ClassDB::bind_method(D_METHOD("get_fog_depth_curve"), &Environment::get_fog_depth_curve); + ClassDB::bind_method(D_METHOD("set_fog_depth_begin", "begin"), &Environment::set_fog_depth_begin); + ClassDB::bind_method(D_METHOD("get_fog_depth_begin"), &Environment::get_fog_depth_begin); + ClassDB::bind_method(D_METHOD("set_fog_depth_end", "end"), &Environment::set_fog_depth_end); + ClassDB::bind_method(D_METHOD("get_fog_depth_end"), &Environment::get_fog_depth_end); + ADD_GROUP("Fog", "fog_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fog_enabled"), "set_fog_enabled", "is_fog_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fog_mode", PROPERTY_HINT_ENUM, "Exponential,Depth"), "set_fog_mode", "get_fog_mode"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "fog_light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_fog_light_color", "get_fog_light_color"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_light_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_fog_light_energy", "get_fog_light_energy"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_sun_scatter", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_fog_sun_scatter", "get_fog_sun_scatter"); @@ -1411,6 +1486,10 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_less,or_greater,suffix:m"), "set_fog_height", "get_fog_height"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_density", PROPERTY_HINT_RANGE, "-16,16,0.0001,or_less,or_greater"), "set_fog_height_density", "get_fog_height_density"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_depth_curve", PROPERTY_HINT_EXP_EASING), "set_fog_depth_curve", "get_fog_depth_curve"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_depth_begin", PROPERTY_HINT_RANGE, "0,4000,0.1,or_greater,or_less,suffix:m"), "set_fog_depth_begin", "get_fog_depth_begin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_depth_end", PROPERTY_HINT_RANGE, "0,4000,0.1,or_greater,or_less,suffix:m"), "set_fog_depth_end", "get_fog_depth_end"); + ClassDB::bind_method(D_METHOD("set_volumetric_fog_enabled", "enabled"), &Environment::set_volumetric_fog_enabled); ClassDB::bind_method(D_METHOD("is_volumetric_fog_enabled"), &Environment::is_volumetric_fog_enabled); ClassDB::bind_method(D_METHOD("set_volumetric_fog_emission", "color"), &Environment::set_volumetric_fog_emission); @@ -1504,6 +1583,9 @@ void Environment::_bind_methods() { BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_REPLACE); BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_MIX); + BIND_ENUM_CONSTANT(FOG_MODE_EXPONENTIAL); + BIND_ENUM_CONSTANT(FOG_MODE_DEPTH); + BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_50_PERCENT); BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_75_PERCENT); BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_100_PERCENT); diff --git a/scene/resources/environment.h b/scene/resources/environment.h index b01c328b50..68b49f38d7 100644 --- a/scene/resources/environment.h +++ b/scene/resources/environment.h @@ -75,6 +75,11 @@ public: SDFGI_Y_SCALE_100_PERCENT, }; + enum FogMode { + FOG_MODE_EXPONENTIAL, + FOG_MODE_DEPTH, + }; + enum GlowBlendMode { GLOW_BLEND_MODE_ADDITIVE, GLOW_BLEND_MODE_SCREEN, @@ -172,6 +177,7 @@ private: // Fog bool fog_enabled = false; + FogMode fog_mode = FOG_MODE_EXPONENTIAL; Color fog_light_color = Color(0.518, 0.553, 0.608); float fog_light_energy = 1.0; float fog_sun_scatter = 0.0; @@ -183,6 +189,13 @@ private: void _update_fog(); + // Depth Fog + float fog_depth_curve = 1.0; + float fog_depth_begin = 10.0; + float fog_depth_end = 100.0; + + void _update_fog_depth(); + // Volumetric Fog bool volumetric_fog_enabled = false; float volumetric_fog_density = 0.05; @@ -361,6 +374,8 @@ public: void set_fog_enabled(bool p_enabled); bool is_fog_enabled() const; + void set_fog_mode(FogMode p_mode); + FogMode get_fog_mode() const; void set_fog_light_color(const Color &p_light_color); Color get_fog_light_color() const; void set_fog_light_energy(float p_amount); @@ -379,6 +394,14 @@ public: void set_fog_sky_affect(float p_sky_affect); float get_fog_sky_affect() const; + // Depth Fog + void set_fog_depth_curve(float p_curve); + float get_fog_depth_curve() const; + void set_fog_depth_begin(float p_begin); + float get_fog_depth_begin() const; + void set_fog_depth_end(float p_end); + float get_fog_depth_end() const; + // Volumetric Fog void set_volumetric_fog_enabled(bool p_enable); bool is_volumetric_fog_enabled() const; @@ -429,5 +452,6 @@ VARIANT_ENUM_CAST(Environment::ReflectionSource) VARIANT_ENUM_CAST(Environment::ToneMapper) VARIANT_ENUM_CAST(Environment::SDFGIYScale) VARIANT_ENUM_CAST(Environment::GlowBlendMode) +VARIANT_ENUM_CAST(Environment::FogMode) #endif // ENVIRONMENT_H diff --git a/servers/debugger/servers_debugger.cpp b/servers/debugger/servers_debugger.cpp index 8da3a10ce9..06be73acc5 100644 --- a/servers/debugger/servers_debugger.cpp +++ b/servers/debugger/servers_debugger.cpp @@ -198,7 +198,7 @@ class ServersDebugger::ScriptsProfiler : public EngineProfiler { typedef ServersDebugger::ScriptFunctionInfo FunctionInfo; struct ProfileInfoSort { bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const { - return A->total_time < B->total_time; + return A->total_time > B->total_time; } }; Vector<ScriptLanguage::ProfilingInfo> info; diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 0ee2984a8c..b42e7bf9bb 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -80,6 +80,7 @@ #include "text/text_server_dummy.h" #include "text/text_server_extension.h" #include "text_server.h" +#include "xr/xr_face_tracker.h" #include "xr/xr_interface.h" #include "xr/xr_interface_extension.h" #include "xr/xr_positional_tracker.h" @@ -189,6 +190,7 @@ void register_server_types() { GDREGISTER_CLASS(XRInterfaceExtension); // can't register this as virtual because we need a creation function for our extensions. GDREGISTER_CLASS(XRPose); GDREGISTER_CLASS(XRPositionalTracker); + GDREGISTER_CLASS(XRFaceTracker); GDREGISTER_CLASS(AudioStream); GDREGISTER_CLASS(AudioStreamPlayback); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index e972d91072..c454d35d28 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -374,7 +374,7 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p SceneShaderForwardClustered::PipelineVersion pipeline_version = SceneShaderForwardClustered::PIPELINE_VERSION_MAX; // Assigned to silence wrong -Wmaybe-initialized. uint32_t pipeline_color_pass_flags = 0; - uint32_t pipeline_specialization = 0; + uint32_t pipeline_specialization = p_params->spec_constant_base_flags; if constexpr (p_pass_mode == PASS_MODE_COLOR) { if (element_info.uses_softshadow) { @@ -1884,6 +1884,13 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co bool debug_sdfgi_probes = get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_SDFGI_PROBES; bool depth_pre_pass = bool(GLOBAL_GET("rendering/driver/depth_prepass/enable")) && depth_framebuffer.is_valid(); + uint32_t spec_constant_base_flags = 0; + { + if (p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH) { + spec_constant_base_flags |= 1 << SPEC_CONSTANT_USE_DEPTH_FOG; + } + } + bool using_ssao = depth_pre_pass && !is_reflection_probe && p_render_data->environment.is_valid() && environment_get_ssao_enabled(p_render_data->environment); if (depth_pre_pass) { //depth pre pass @@ -1906,7 +1913,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, nullptr, RID(), samplers); bool finish_depth = using_ssao || using_ssil || using_sdfgi || using_voxelgi; - RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, depth_pass_mode, 0, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count); + RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, depth_pass_mode, 0, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags); _render_list_with_draw_list(&render_list_params, depth_framebuffer, needs_pre_resolve ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, needs_pre_resolve ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, needs_pre_resolve ? Vector<Color>() : depth_pass_clear); RD::get_singleton()->draw_command_end_label(); @@ -1972,7 +1979,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co uint32_t opaque_color_pass_flags = using_motion_pass ? (color_pass_flags & ~COLOR_PASS_FLAG_MOTION_VECTORS) : color_pass_flags; RID opaque_framebuffer = using_motion_pass ? rb_data->get_color_pass_fb(opaque_color_pass_flags) : color_framebuffer; - RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count); + RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags); _render_list_with_draw_list(&render_list_params, opaque_framebuffer, load_color ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pre_pass ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, c, 1.0, 0); } @@ -1992,7 +1999,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_MOTION, p_render_data, radiance_texture, samplers, true); - RenderListParameters render_list_params(render_list[RENDER_LIST_MOTION].elements.ptr(), render_list[RENDER_LIST_MOTION].element_info.ptr(), render_list[RENDER_LIST_MOTION].elements.size(), reverse_cull, PASS_MODE_COLOR, color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count); + RenderListParameters render_list_params(render_list[RENDER_LIST_MOTION].elements.ptr(), render_list[RENDER_LIST_MOTION].element_info.ptr(), render_list[RENDER_LIST_MOTION].elements.size(), reverse_cull, PASS_MODE_COLOR, color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags); _render_list_with_draw_list(&render_list_params, color_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE); RD::get_singleton()->draw_command_end_label(); @@ -2125,7 +2132,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co } RID alpha_framebuffer = rb_data.is_valid() ? rb_data->get_color_pass_fb(transparent_color_pass_flags) : color_only_framebuffer; - RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count); + RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags); _render_list_with_draw_list(&render_list_params, alpha_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE); } diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index be55341102..51fd0aaffb 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -72,6 +72,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { SPEC_CONSTANT_DIRECTIONAL_PENUMBRA_SHADOW_SAMPLES = 9, SPEC_CONSTANT_DECAL_FILTER = 10, SPEC_CONSTANT_PROJECTOR_FILTER = 11, + SPEC_CONSTANT_USE_DEPTH_FOG = 12, }; enum { @@ -210,8 +211,9 @@ class RenderForwardClustered : public RendererSceneRenderRD { RD::FramebufferFormatID framebuffer_format = 0; uint32_t element_offset = 0; bool use_directional_soft_shadow = false; + uint32_t spec_constant_base_flags = 0; - RenderListParameters(GeometryInstanceSurfaceDataCache **p_elements, RenderElementInfo *p_element_info, int p_element_count, bool p_reverse_cull, PassMode p_pass_mode, uint32_t p_color_pass_flags, bool p_no_gi, bool p_use_directional_soft_shadows, RID p_render_pass_uniform_set, bool p_force_wireframe = false, const Vector2 &p_uv_offset = Vector2(), float p_lod_distance_multiplier = 0.0, float p_screen_mesh_lod_threshold = 0.0, uint32_t p_view_count = 1, uint32_t p_element_offset = 0) { + RenderListParameters(GeometryInstanceSurfaceDataCache **p_elements, RenderElementInfo *p_element_info, int p_element_count, bool p_reverse_cull, PassMode p_pass_mode, uint32_t p_color_pass_flags, bool p_no_gi, bool p_use_directional_soft_shadows, RID p_render_pass_uniform_set, bool p_force_wireframe = false, const Vector2 &p_uv_offset = Vector2(), float p_lod_distance_multiplier = 0.0, float p_screen_mesh_lod_threshold = 0.0, uint32_t p_view_count = 1, uint32_t p_element_offset = 0, uint32_t p_spec_constant_base_flags = 0) { elements = p_elements; element_info = p_element_info; element_count = p_element_count; @@ -227,6 +229,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { screen_mesh_lod_threshold = p_screen_mesh_lod_threshold; element_offset = p_element_offset; use_directional_soft_shadow = p_use_directional_soft_shadows; + spec_constant_base_flags = p_spec_constant_base_flags; } }; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 29c5deb6c2..fd81430983 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -899,6 +899,10 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color if (!is_environment(p_render_data->environment) || !environment_get_fog_enabled(p_render_data->environment)) { spec_constant_base_flags |= 1 << SPEC_CONSTANT_DISABLE_FOG; } + + if (p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH) { + spec_constant_base_flags |= 1 << SPEC_CONSTANT_USE_DEPTH_FOG; + } } { diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index ba5fefc83f..5c02204627 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -80,6 +80,7 @@ private: SPEC_CONSTANT_DISABLE_DECALS = 13, SPEC_CONSTANT_DISABLE_FOG = 14, + SPEC_CONSTANT_USE_DEPTH_FOG = 16, }; diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index cb95621219..6eae64c04e 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -656,6 +656,7 @@ layout(constant_id = 9) const uint sc_directional_penumbra_shadow_samples = 4; layout(constant_id = 10) const bool sc_decal_use_mipmaps = true; layout(constant_id = 11) const bool sc_projector_use_mipmaps = true; +layout(constant_id = 12) const bool sc_use_depth_fog = false; // not used in clustered renderer but we share some code with the mobile renderer that requires this. const float sc_luminance_multiplier = 1.0; @@ -848,7 +849,15 @@ vec4 fog_process(vec3 vertex) { } } - float fog_amount = 1.0 - exp(min(0.0, -length(vertex) * scene_data_block.data.fog_density)); + float fog_amount = 0.0; + + if (sc_use_depth_fog) { + float fog_z = smoothstep(scene_data_block.data.fog_depth_begin, scene_data_block.data.fog_depth_end, length(vertex)); + float fog_quad_amount = pow(fog_z, scene_data_block.data.fog_depth_curve) * scene_data_block.data.fog_density; + fog_amount = fog_quad_amount; + } else { + fog_amount = 1 - exp(min(0.0, -length(vertex) * scene_data_block.data.fog_density)); + } if (abs(scene_data_block.data.fog_height_density) >= 0.0001) { float y = (scene_data_block.data.inv_view_matrix * vec4(vertex, 1.0)).y; diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index e9c69058f2..259edc63a0 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -525,6 +525,7 @@ layout(constant_id = 12) const bool sc_disable_directional_lights = false; layout(constant_id = 7) const bool sc_decal_use_mipmaps = true; layout(constant_id = 13) const bool sc_disable_decals = false; layout(constant_id = 14) const bool sc_disable_fog = false; +layout(constant_id = 16) const bool sc_use_depth_fog = false; #endif //!MODE_RENDER_DEPTH @@ -690,7 +691,15 @@ vec4 fog_process(vec3 vertex) { } } - float fog_amount = 1.0 - exp(min(0.0, -length(vertex) * scene_data_block.data.fog_density)); + float fog_amount = 0.0; + + if (sc_use_depth_fog) { + float fog_z = smoothstep(scene_data_block.data.fog_depth_begin, scene_data_block.data.fog_depth_end, length(vertex)); + float fog_quad_amount = pow(fog_z, scene_data_block.data.fog_depth_curve) * scene_data_block.data.fog_density; + fog_amount = fog_quad_amount; + } else { + fog_amount = 1 - exp(min(0.0, -length(vertex) * scene_data_block.data.fog_density)); + } if (abs(scene_data_block.data.fog_height_density) >= 0.0001) { float y = (scene_data_block.data.inv_view_matrix * vec4(vertex, 1.0)).y; diff --git a/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl index 70e670c3f7..67542d61fd 100644 --- a/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl @@ -49,24 +49,29 @@ struct SceneData { mediump float opaque_prepass_threshold; bool fog_enabled; + uint fog_mode; highp float fog_density; highp float fog_height; highp float fog_height_density; + highp float fog_depth_curve; + highp float pad; + highp float fog_depth_begin; + mediump vec3 fog_light_color; - mediump float fog_sun_scatter; + highp float fog_depth_end; + mediump float fog_sun_scatter; mediump float fog_aerial_perspective; highp float time; mediump float reflection_multiplier; // one normally, zero when rendering reflections - bool material_uv2_mode; vec2 taa_jitter; + bool material_uv2_mode; float emissive_exposure_normalization; - float IBL_exposure_normalization; + float IBL_exposure_normalization; bool pancake_shadows; uint camera_visible_layers; float pass_alpha_multiplier; - uint pad3; }; diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 21787b3fcf..1f362ffd21 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1963,7 +1963,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b //if we have a mesh set, we need to re-generate the AABB from the new data const float *data = p_buffer.ptr(); - if (multimesh->custom_aabb != AABB()) { + if (multimesh->custom_aabb == AABB()) { _multimesh_re_create_aabb(multimesh, data, multimesh->instances); multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); } @@ -2087,7 +2087,7 @@ void MeshStorage::_update_dirty_multimeshes() { if (multimesh->aabb_dirty) { //aabb is dirty.. multimesh->aabb_dirty = false; - if (multimesh->custom_aabb != AABB()) { + if (multimesh->custom_aabb == AABB()) { _multimesh_re_create_aabb(multimesh, data, visible_instances); multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); } diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp index 506e78bae9..86f0f5acf2 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp @@ -168,11 +168,16 @@ void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p } ubo.fog_enabled = render_scene_render->environment_get_fog_enabled(p_env); + ubo.fog_mode = render_scene_render->environment_get_fog_mode(p_env); ubo.fog_density = render_scene_render->environment_get_fog_density(p_env); ubo.fog_height = render_scene_render->environment_get_fog_height(p_env); ubo.fog_height_density = render_scene_render->environment_get_fog_height_density(p_env); ubo.fog_aerial_perspective = render_scene_render->environment_get_fog_aerial_perspective(p_env); + ubo.fog_depth_curve = render_scene_render->environment_get_fog_depth_curve(p_env); + ubo.fog_depth_end = render_scene_render->environment_get_fog_depth_end(p_env) > 0.0 ? render_scene_render->environment_get_fog_depth_end(p_env) : ubo.z_far; + ubo.fog_depth_begin = MIN(render_scene_render->environment_get_fog_depth_begin(p_env), ubo.fog_depth_end - 0.001); + Color fog_color = render_scene_render->environment_get_fog_light_color(p_env).srgb_to_linear(); float fog_energy = render_scene_render->environment_get_fog_light_energy(p_env); diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h index f93f22816b..9453966a86 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h @@ -131,26 +131,31 @@ private: // Fog uint32_t fog_enabled; + uint32_t fog_mode; float fog_density; float fog_height; + float fog_height_density; + float fog_depth_curve; + float pad; + float fog_depth_begin; float fog_light_color[3]; - float fog_sun_scatter; + float fog_depth_end; + float fog_sun_scatter; float fog_aerial_perspective; float time; float reflection_multiplier; - uint32_t material_uv2_mode; float taa_jitter[2]; + uint32_t material_uv2_mode; float emissive_exposure_normalization; // Needed to normalize emissive when using physical units. - float IBL_exposure_normalization; // Adjusts for baked exposure. + float IBL_exposure_normalization; // Adjusts for baked exposure. uint32_t pancake_shadows; uint32_t camera_visible_layers; float pass_alpha_multiplier; - uint32_t pad3; }; struct UBODATA { diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h index a09823b008..f48902a4ef 100644 --- a/servers/rendering/renderer_scene_cull.h +++ b/servers/rendering/renderer_scene_cull.h @@ -1203,7 +1203,7 @@ public: PASS1RC(float, environment_get_white, RID) // Fog - PASS10(environment_set_fog, RID, bool, const Color &, float, float, float, float, float, float, float) + PASS11(environment_set_fog, RID, bool, const Color &, float, float, float, float, float, float, float, RS::EnvironmentFogMode) PASS1RC(bool, environment_get_fog_enabled, RID) PASS1RC(Color, environment_get_fog_light_color, RID) @@ -1214,10 +1214,17 @@ public: PASS1RC(float, environment_get_fog_height, RID) PASS1RC(float, environment_get_fog_height_density, RID) PASS1RC(float, environment_get_fog_aerial_perspective, RID) + PASS1RC(RS::EnvironmentFogMode, environment_get_fog_mode, RID) PASS2(environment_set_volumetric_fog_volume_size, int, int) PASS1(environment_set_volumetric_fog_filter_active, bool) + // Depth Fog + PASS4(environment_set_fog_depth, RID, float, float, float) + PASS1RC(float, environment_get_fog_depth_curve, RID) + PASS1RC(float, environment_get_fog_depth_begin, RID) + PASS1RC(float, environment_get_fog_depth_end, RID) + // Volumentric Fog PASS14(environment_set_volumetric_fog, RID, bool, float, const Color &, const Color &, float, float, float, float, float, bool, float, float, float) diff --git a/servers/rendering/renderer_scene_render.cpp b/servers/rendering/renderer_scene_render.cpp index dce301d87b..95eb29a891 100644 --- a/servers/rendering/renderer_scene_render.cpp +++ b/servers/rendering/renderer_scene_render.cpp @@ -310,14 +310,18 @@ float RendererSceneRender::environment_get_white(RID p_env) const { // Fog -void RendererSceneRender::environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect) { - environment_storage.environment_set_fog(p_env, p_enable, p_light_color, p_light_energy, p_sun_scatter, p_density, p_height, p_height_density, p_aerial_perspective, p_sky_affect); +void RendererSceneRender::environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode) { + environment_storage.environment_set_fog(p_env, p_enable, p_light_color, p_light_energy, p_sun_scatter, p_density, p_height, p_height_density, p_aerial_perspective, p_sky_affect, p_mode); } bool RendererSceneRender::environment_get_fog_enabled(RID p_env) const { return environment_storage.environment_get_fog_enabled(p_env); } +RS::EnvironmentFogMode RendererSceneRender::environment_get_fog_mode(RID p_env) const { + return environment_storage.environment_get_fog_mode(p_env); +} + Color RendererSceneRender::environment_get_fog_light_color(RID p_env) const { return environment_storage.environment_get_fog_light_color(p_env); } @@ -350,6 +354,24 @@ float RendererSceneRender::environment_get_fog_aerial_perspective(RID p_env) con return environment_storage.environment_get_fog_aerial_perspective(p_env); } +// Depth Fog + +void RendererSceneRender::environment_set_fog_depth(RID p_env, float p_curve, float p_begin, float p_end) { + environment_storage.environment_set_fog_depth(p_env, p_curve, p_begin, p_end); +} + +float RendererSceneRender::environment_get_fog_depth_curve(RID p_env) const { + return environment_storage.environment_get_fog_depth_curve(p_env); +} + +float RendererSceneRender::environment_get_fog_depth_begin(RID p_env) const { + return environment_storage.environment_get_fog_depth_begin(p_env); +} + +float RendererSceneRender::environment_get_fog_depth_end(RID p_env) const { + return environment_storage.environment_get_fog_depth_end(p_env); +} + // Volumetric Fog void RendererSceneRender::environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_albedo, const Color &p_emission, float p_emission_energy, float p_anisotropy, float p_length, float p_detail_spread, float p_gi_inject, bool p_temporal_reprojection, float p_temporal_reprojection_amount, float p_ambient_inject, float p_sky_affect) { diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h index 9007383641..c6e2c23e91 100644 --- a/servers/rendering/renderer_scene_render.h +++ b/servers/rendering/renderer_scene_render.h @@ -116,8 +116,9 @@ public: float environment_get_white(RID p_env) const; // Fog - void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect); + void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode); bool environment_get_fog_enabled(RID p_env) const; + RS::EnvironmentFogMode environment_get_fog_mode(RID p_env) const; Color environment_get_fog_light_color(RID p_env) const; float environment_get_fog_light_energy(RID p_env) const; float environment_get_fog_sun_scatter(RID p_env) const; @@ -127,6 +128,12 @@ public: float environment_get_fog_height_density(RID p_env) const; float environment_get_fog_aerial_perspective(RID p_env) const; + // Depth Fog + void environment_set_fog_depth(RID p_env, float p_curve, float p_begin, float p_end); + float environment_get_fog_depth_curve(RID p_env) const; + float environment_get_fog_depth_begin(RID p_env) const; + float environment_get_fog_depth_end(RID p_env) const; + // Volumetric Fog void environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_albedo, const Color &p_emission, float p_emission_energy, float p_anisotropy, float p_length, float p_detail_spread, float p_gi_inject, bool p_temporal_reprojection, float p_temporal_reprojection_amount, float p_ambient_inject, float p_sky_affect); bool environment_get_volumetric_fog_enabled(RID p_env) const; diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h index d1c6c1cbf9..be14a50eec 100644 --- a/servers/rendering/rendering_method.h +++ b/servers/rendering/rendering_method.h @@ -154,9 +154,10 @@ public: virtual float environment_get_white(RID p_env) const = 0; // Fog - virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect) = 0; + virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode = RS::EnvironmentFogMode::ENV_FOG_MODE_EXPONENTIAL) = 0; virtual bool environment_get_fog_enabled(RID p_env) const = 0; + virtual RS::EnvironmentFogMode environment_get_fog_mode(RID p_env) const = 0; virtual Color environment_get_fog_light_color(RID p_env) const = 0; virtual float environment_get_fog_light_energy(RID p_env) const = 0; virtual float environment_get_fog_sun_scatter(RID p_env) const = 0; @@ -166,6 +167,13 @@ public: virtual float environment_get_fog_aerial_perspective(RID p_env) const = 0; virtual float environment_get_fog_sky_affect(RID p_env) const = 0; + // Depth Fog + virtual void environment_set_fog_depth(RID p_env, float p_curve, float p_begin, float p_end) = 0; + + virtual float environment_get_fog_depth_curve(RID p_env) const = 0; + virtual float environment_get_fog_depth_begin(RID p_env) const = 0; + virtual float environment_get_fog_depth_end(RID p_env) const = 0; + // Volumetric Fog virtual void environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_albedo, const Color &p_emission, float p_emission_energy, float p_anisotropy, float p_length, float p_detail_spread, float p_gi_inject, bool p_temporal_reprojection, float p_temporal_reprojection_amount, float p_ambient_inject, float p_sky_affect) = 0; diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 577e9accc0..c218007a78 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -725,7 +725,9 @@ public: FUNC7(environment_set_adjustment, RID, bool, float, float, float, bool, RID) - FUNC10(environment_set_fog, RID, bool, const Color &, float, float, float, float, float, float, float) + FUNC11(environment_set_fog, RID, bool, const Color &, float, float, float, float, float, float, float, EnvironmentFogMode) + + FUNC4(environment_set_fog_depth, RID, float, float, float) FUNC14(environment_set_volumetric_fog, RID, bool, float, const Color &, const Color &, float, float, float, float, float, bool, float, float, float) FUNC2(environment_set_volumetric_fog_volume_size, int, int) diff --git a/servers/rendering/storage/environment_storage.cpp b/servers/rendering/storage/environment_storage.cpp index 0234f52ca1..ec26e36509 100644 --- a/servers/rendering/storage/environment_storage.cpp +++ b/servers/rendering/storage/environment_storage.cpp @@ -205,10 +205,11 @@ float RendererEnvironmentStorage::environment_get_white(RID p_env) const { // Fog -void RendererEnvironmentStorage::environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_fog_aerial_perspective, float p_sky_affect) { +void RendererEnvironmentStorage::environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_fog_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode) { Environment *env = environment_owner.get_or_null(p_env); ERR_FAIL_NULL(env); env->fog_enabled = p_enable; + env->fog_mode = p_mode; env->fog_light_color = p_light_color; env->fog_light_energy = p_light_energy; env->fog_sun_scatter = p_sun_scatter; @@ -225,6 +226,12 @@ bool RendererEnvironmentStorage::environment_get_fog_enabled(RID p_env) const { return env->fog_enabled; } +RS::EnvironmentFogMode RendererEnvironmentStorage::environment_get_fog_mode(RID p_env) const { + Environment *env = environment_owner.get_or_null(p_env); + ERR_FAIL_NULL_V(env, RS::ENV_FOG_MODE_EXPONENTIAL); + return env->fog_mode; +} + Color RendererEnvironmentStorage::environment_get_fog_light_color(RID p_env) const { Environment *env = environment_owner.get_or_null(p_env); ERR_FAIL_NULL_V(env, Color(0.5, 0.6, 0.7)); @@ -273,6 +280,34 @@ float RendererEnvironmentStorage::environment_get_fog_sky_affect(RID p_env) cons return env->fog_sky_affect; } +// Depth Fog + +void RendererEnvironmentStorage::environment_set_fog_depth(RID p_env, float p_curve, float p_begin, float p_end) { + Environment *env = environment_owner.get_or_null(p_env); + ERR_FAIL_NULL(env); + env->fog_depth_curve = p_curve; + env->fog_depth_begin = p_begin; + env->fog_depth_end = p_end; +} + +float RendererEnvironmentStorage::environment_get_fog_depth_curve(RID p_env) const { + Environment *env = environment_owner.get_or_null(p_env); + ERR_FAIL_NULL_V(env, 0.0); + return env->fog_depth_curve; +} + +float RendererEnvironmentStorage::environment_get_fog_depth_begin(RID p_env) const { + Environment *env = environment_owner.get_or_null(p_env); + ERR_FAIL_NULL_V(env, 0.0); + return env->fog_depth_begin; +} + +float RendererEnvironmentStorage::environment_get_fog_depth_end(RID p_env) const { + Environment *env = environment_owner.get_or_null(p_env); + ERR_FAIL_NULL_V(env, 0.0); + return env->fog_depth_end; +} + // Volumetric Fog void RendererEnvironmentStorage::environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_albedo, const Color &p_emission, float p_emission_energy, float p_anisotropy, float p_length, float p_detail_spread, float p_gi_inject, bool p_temporal_reprojection, float p_temporal_reprojection_amount, float p_ambient_inject, float p_sky_affect) { diff --git a/servers/rendering/storage/environment_storage.h b/servers/rendering/storage/environment_storage.h index d677dfc57b..c077e093da 100644 --- a/servers/rendering/storage/environment_storage.h +++ b/servers/rendering/storage/environment_storage.h @@ -62,6 +62,7 @@ private: // Fog bool fog_enabled = false; + RS::EnvironmentFogMode fog_mode = RS::EnvironmentFogMode::ENV_FOG_MODE_EXPONENTIAL; Color fog_light_color = Color(0.518, 0.553, 0.608); float fog_light_energy = 1.0; float fog_sun_scatter = 0.0; @@ -71,6 +72,11 @@ private: float fog_height_density = 0.0; //can be negative to invert effect float fog_aerial_perspective = 0.0; + // Depth Fog + float fog_depth_curve = 1.0; + float fog_depth_begin = 10.0; + float fog_depth_end = 100.0; + // Volumetric Fog bool volumetric_fog_enabled = false; float volumetric_fog_density = 0.01; @@ -192,8 +198,9 @@ public: float environment_get_white(RID p_env) const; // Fog - void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect); + void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode); bool environment_get_fog_enabled(RID p_env) const; + RS::EnvironmentFogMode environment_get_fog_mode(RID p_env) const; Color environment_get_fog_light_color(RID p_env) const; float environment_get_fog_light_energy(RID p_env) const; float environment_get_fog_sun_scatter(RID p_env) const; @@ -203,6 +210,12 @@ public: float environment_get_fog_height_density(RID p_env) const; float environment_get_fog_aerial_perspective(RID p_env) const; + // Depth Fog + void environment_set_fog_depth(RID p_env, float p_curve, float p_begin, float p_end); + float environment_get_fog_depth_curve(RID p_env) const; + float environment_get_fog_depth_begin(RID p_env) const; + float environment_get_fog_depth_end(RID p_env) const; + // Volumetric Fog void environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_albedo, const Color &p_emission, float p_emission_energy, float p_anisotropy, float p_length, float p_detail_spread, float p_gi_inject, bool p_temporal_reprojection, float p_temporal_reprojection_amount, float p_ambient_inject, float p_sky_affect); bool environment_get_volumetric_fog_enabled(RID p_env) const; diff --git a/servers/rendering_server.compat.inc b/servers/rendering_server.compat.inc new file mode 100644 index 0000000000..0cef3c906c --- /dev/null +++ b/servers/rendering_server.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* rendering_server.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void RenderingServer::_environment_set_fog_bind_compat_84792(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect) { + environment_set_fog(p_env, p_enable, p_light_color, p_light_energy, p_sun_scatter, p_density, p_height, p_height_density, p_aerial_perspective, p_sky_affect, RS::EnvironmentFogMode::ENV_FOG_MODE_EXPONENTIAL); +} + +void RenderingServer::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density", "aerial_perspective", "sky_affect"), &RenderingServer::_environment_set_fog_bind_compat_84792); +} + +#endif diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index d03f8113f8..655b748d3f 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "rendering_server.h" +#include "rendering_server.compat.inc" #include "core/config/project_settings.h" #include "core/object/worker_thread_pool.h" @@ -2943,7 +2944,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("environment_set_adjustment", "env", "enable", "brightness", "contrast", "saturation", "use_1d_color_correction", "color_correction"), &RenderingServer::environment_set_adjustment); ClassDB::bind_method(D_METHOD("environment_set_ssr", "env", "enable", "max_steps", "fade_in", "fade_out", "depth_tolerance"), &RenderingServer::environment_set_ssr); ClassDB::bind_method(D_METHOD("environment_set_ssao", "env", "enable", "radius", "intensity", "power", "detail", "horizon", "sharpness", "light_affect", "ao_channel_affect"), &RenderingServer::environment_set_ssao); - ClassDB::bind_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density", "aerial_perspective", "sky_affect"), &RenderingServer::environment_set_fog); + ClassDB::bind_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density", "aerial_perspective", "sky_affect", "fog_mode"), &RenderingServer::environment_set_fog, DEFVAL(RS::ENV_FOG_MODE_EXPONENTIAL)); ClassDB::bind_method(D_METHOD("environment_set_sdfgi", "env", "enable", "cascades", "min_cell_size", "y_scale", "use_occlusion", "bounce_feedback", "read_sky", "energy", "normal_bias", "probe_bias"), &RenderingServer::environment_set_sdfgi); ClassDB::bind_method(D_METHOD("environment_set_volumetric_fog", "env", "enable", "density", "albedo", "emission", "emission_energy", "anisotropy", "length", "p_detail_spread", "gi_inject", "temporal_reprojection", "temporal_reprojection_amount", "ambient_inject", "sky_affect"), &RenderingServer::environment_set_volumetric_fog); @@ -2986,6 +2987,9 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(ENV_GLOW_BLEND_MODE_REPLACE); BIND_ENUM_CONSTANT(ENV_GLOW_BLEND_MODE_MIX); + BIND_ENUM_CONSTANT(ENV_FOG_MODE_EXPONENTIAL); + BIND_ENUM_CONSTANT(ENV_FOG_MODE_DEPTH); + BIND_ENUM_CONSTANT(ENV_TONE_MAPPER_LINEAR); BIND_ENUM_CONSTANT(ENV_TONE_MAPPER_REINHARD); BIND_ENUM_CONSTANT(ENV_TONE_MAPPER_FILMIC); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 19b6c35339..6b00213440 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -71,6 +71,11 @@ protected: static RenderingServer *(*create_func)(); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _environment_set_fog_bind_compat_84792(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect); + static void _bind_compatibility_methods(); +#endif + public: static RenderingServer *get_singleton(); static RenderingServer *create(); @@ -1178,7 +1183,13 @@ public: virtual void environment_set_sdfgi_frames_to_update_light(EnvironmentSDFGIFramesToUpdateLight p_update) = 0; - virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect) = 0; + enum EnvironmentFogMode { + ENV_FOG_MODE_EXPONENTIAL, + ENV_FOG_MODE_DEPTH, + }; + + virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, EnvironmentFogMode p_mode = EnvironmentFogMode::ENV_FOG_MODE_EXPONENTIAL) = 0; + virtual void environment_set_fog_depth(RID p_env, float p_curve, float p_begin, float p_end) = 0; virtual void environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_albedo, const Color &p_emission, float p_emission_energy, float p_anisotropy, float p_length, float p_detail_spread, float p_gi_inject, bool p_temporal_reprojection, float p_temporal_reprojection_amount, float p_ambient_inject, float p_sky_affect) = 0; virtual void environment_set_volumetric_fog_volume_size(int p_size, int p_depth) = 0; @@ -1730,6 +1741,7 @@ VARIANT_ENUM_CAST(RenderingServer::EnvironmentBG); VARIANT_ENUM_CAST(RenderingServer::EnvironmentAmbientSource); VARIANT_ENUM_CAST(RenderingServer::EnvironmentReflectionSource); VARIANT_ENUM_CAST(RenderingServer::EnvironmentGlowBlendMode); +VARIANT_ENUM_CAST(RenderingServer::EnvironmentFogMode); VARIANT_ENUM_CAST(RenderingServer::EnvironmentToneMapper); VARIANT_ENUM_CAST(RenderingServer::EnvironmentSSRRoughnessQuality); VARIANT_ENUM_CAST(RenderingServer::EnvironmentSSAOQuality); diff --git a/servers/xr/xr_face_tracker.cpp b/servers/xr/xr_face_tracker.cpp new file mode 100644 index 0000000000..a38ccfd527 --- /dev/null +++ b/servers/xr/xr_face_tracker.cpp @@ -0,0 +1,222 @@ +/**************************************************************************/ +/* xr_face_tracker.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "xr_face_tracker.h" + +void XRFaceTracker::_bind_methods() { + // Base Shapes + BIND_ENUM_CONSTANT(FT_EYE_LOOK_OUT_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_LOOK_IN_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_LOOK_UP_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_LOOK_DOWN_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_LOOK_OUT_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_LOOK_IN_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_LOOK_UP_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_LOOK_DOWN_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_CLOSED_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_CLOSED_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_SQUINT_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_SQUINT_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_WIDE_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_WIDE_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_DILATION_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_DILATION_LEFT); + BIND_ENUM_CONSTANT(FT_EYE_CONSTRICT_RIGHT); + BIND_ENUM_CONSTANT(FT_EYE_CONSTRICT_LEFT); + BIND_ENUM_CONSTANT(FT_BROW_PINCH_RIGHT); + BIND_ENUM_CONSTANT(FT_BROW_PINCH_LEFT); + BIND_ENUM_CONSTANT(FT_BROW_LOWERER_RIGHT); + BIND_ENUM_CONSTANT(FT_BROW_LOWERER_LEFT); + BIND_ENUM_CONSTANT(FT_BROW_INNER_UP_RIGHT); + BIND_ENUM_CONSTANT(FT_BROW_INNER_UP_LEFT); + BIND_ENUM_CONSTANT(FT_BROW_OUTER_UP_RIGHT); + BIND_ENUM_CONSTANT(FT_BROW_OUTER_UP_LEFT); + BIND_ENUM_CONSTANT(FT_NOSE_SNEER_RIGHT); + BIND_ENUM_CONSTANT(FT_NOSE_SNEER_LEFT); + BIND_ENUM_CONSTANT(FT_NASAL_DILATION_RIGHT); + BIND_ENUM_CONSTANT(FT_NASAL_DILATION_LEFT); + BIND_ENUM_CONSTANT(FT_NASAL_CONSTRICT_RIGHT); + BIND_ENUM_CONSTANT(FT_NASAL_CONSTRICT_LEFT); + BIND_ENUM_CONSTANT(FT_CHEEK_SQUINT_RIGHT); + BIND_ENUM_CONSTANT(FT_CHEEK_SQUINT_LEFT); + BIND_ENUM_CONSTANT(FT_CHEEK_PUFF_RIGHT); + BIND_ENUM_CONSTANT(FT_CHEEK_PUFF_LEFT); + BIND_ENUM_CONSTANT(FT_CHEEK_SUCK_RIGHT); + BIND_ENUM_CONSTANT(FT_CHEEK_SUCK_LEFT); + BIND_ENUM_CONSTANT(FT_JAW_OPEN); + BIND_ENUM_CONSTANT(FT_MOUTH_CLOSED); + BIND_ENUM_CONSTANT(FT_JAW_RIGHT); + BIND_ENUM_CONSTANT(FT_JAW_LEFT); + BIND_ENUM_CONSTANT(FT_JAW_FORWARD); + BIND_ENUM_CONSTANT(FT_JAW_BACKWARD); + BIND_ENUM_CONSTANT(FT_JAW_CLENCH); + BIND_ENUM_CONSTANT(FT_JAW_MANDIBLE_RAISE); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_UPPER_RIGHT); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_UPPER_LEFT); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_LOWER_RIGHT); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_LOWER_LEFT); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_CORNER_RIGHT); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_CORNER_LEFT); + BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_UPPER_RIGHT); + BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_UPPER_LEFT); + BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_LOWER_RIGHT); + BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_LOWER_LEFT); + BIND_ENUM_CONSTANT(FT_LIP_PUCKER_UPPER_RIGHT); + BIND_ENUM_CONSTANT(FT_LIP_PUCKER_UPPER_LEFT); + BIND_ENUM_CONSTANT(FT_LIP_PUCKER_LOWER_RIGHT); + BIND_ENUM_CONSTANT(FT_LIP_PUCKER_LOWER_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_UP_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_UP_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_DOWN_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_DOWN_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_DEEPEN_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_DEEPEN_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_PULL_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_PULL_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_SLANT_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_SLANT_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_FROWN_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_FROWN_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_STRETCH_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_STRETCH_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_DIMPLE_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_DIMPLE_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_RAISER_UPPER); + BIND_ENUM_CONSTANT(FT_MOUTH_RAISER_LOWER); + BIND_ENUM_CONSTANT(FT_MOUTH_PRESS_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_PRESS_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_TIGHTENER_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_TIGHTENER_LEFT); + BIND_ENUM_CONSTANT(FT_TONGUE_OUT); + BIND_ENUM_CONSTANT(FT_TONGUE_UP); + BIND_ENUM_CONSTANT(FT_TONGUE_DOWN); + BIND_ENUM_CONSTANT(FT_TONGUE_RIGHT); + BIND_ENUM_CONSTANT(FT_TONGUE_LEFT); + BIND_ENUM_CONSTANT(FT_TONGUE_ROLL); + BIND_ENUM_CONSTANT(FT_TONGUE_BLEND_DOWN); + BIND_ENUM_CONSTANT(FT_TONGUE_CURL_UP); + BIND_ENUM_CONSTANT(FT_TONGUE_SQUISH); + BIND_ENUM_CONSTANT(FT_TONGUE_FLAT); + BIND_ENUM_CONSTANT(FT_TONGUE_TWIST_RIGHT); + BIND_ENUM_CONSTANT(FT_TONGUE_TWIST_LEFT); + BIND_ENUM_CONSTANT(FT_SOFT_PALATE_CLOSE); + BIND_ENUM_CONSTANT(FT_THROAT_SWALLOW); + BIND_ENUM_CONSTANT(FT_NECK_FLEX_RIGHT); + BIND_ENUM_CONSTANT(FT_NECK_FLEX_LEFT); + // Blended Shapes + BIND_ENUM_CONSTANT(FT_EYE_CLOSED); + BIND_ENUM_CONSTANT(FT_EYE_WIDE); + BIND_ENUM_CONSTANT(FT_EYE_SQUINT); + BIND_ENUM_CONSTANT(FT_EYE_DILATION); + BIND_ENUM_CONSTANT(FT_EYE_CONSTRICT); + BIND_ENUM_CONSTANT(FT_BROW_DOWN_RIGHT); + BIND_ENUM_CONSTANT(FT_BROW_DOWN_LEFT); + BIND_ENUM_CONSTANT(FT_BROW_DOWN); + BIND_ENUM_CONSTANT(FT_BROW_UP_RIGHT); + BIND_ENUM_CONSTANT(FT_BROW_UP_LEFT); + BIND_ENUM_CONSTANT(FT_BROW_UP); + BIND_ENUM_CONSTANT(FT_NOSE_SNEER); + BIND_ENUM_CONSTANT(FT_NASAL_DILATION); + BIND_ENUM_CONSTANT(FT_NASAL_CONSTRICT); + BIND_ENUM_CONSTANT(FT_CHEEK_PUFF); + BIND_ENUM_CONSTANT(FT_CHEEK_SUCK); + BIND_ENUM_CONSTANT(FT_CHEEK_SQUINT); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_UPPER); + BIND_ENUM_CONSTANT(FT_LIP_SUCK_LOWER); + BIND_ENUM_CONSTANT(FT_LIP_SUCK); + BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_UPPER); + BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_LOWER); + BIND_ENUM_CONSTANT(FT_LIP_FUNNEL); + BIND_ENUM_CONSTANT(FT_LIP_PUCKER_UPPER); + BIND_ENUM_CONSTANT(FT_LIP_PUCKER_LOWER); + BIND_ENUM_CONSTANT(FT_LIP_PUCKER); + BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_UP); + BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_DOWN); + BIND_ENUM_CONSTANT(FT_MOUTH_OPEN); + BIND_ENUM_CONSTANT(FT_MOUTH_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_SMILE_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_SMILE_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_SMILE); + BIND_ENUM_CONSTANT(FT_MOUTH_SAD_RIGHT); + BIND_ENUM_CONSTANT(FT_MOUTH_SAD_LEFT); + BIND_ENUM_CONSTANT(FT_MOUTH_SAD); + BIND_ENUM_CONSTANT(FT_MOUTH_STRETCH); + BIND_ENUM_CONSTANT(FT_MOUTH_DIMPLE); + BIND_ENUM_CONSTANT(FT_MOUTH_TIGHTENER); + BIND_ENUM_CONSTANT(FT_MOUTH_PRESS); + BIND_ENUM_CONSTANT(FT_MAX); + + ClassDB::bind_method(D_METHOD("get_blend_shape", "blend_shape"), &XRFaceTracker::get_blend_shape); + ClassDB::bind_method(D_METHOD("set_blend_shape", "blend_shape", "weight"), &XRFaceTracker::set_blend_shape); + + ClassDB::bind_method(D_METHOD("get_blend_shapes"), &XRFaceTracker::get_blend_shapes); + ClassDB::bind_method(D_METHOD("set_blend_shapes", "weights"), &XRFaceTracker::set_blend_shapes); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "blend_shapes"), "set_blend_shapes", "get_blend_shapes"); + ADD_PROPERTY_DEFAULT("blend_shapes", PackedFloat32Array()); // To prevent ludicrously large default values. +} + +float XRFaceTracker::get_blend_shape(BlendShapeEntry p_blend_shape) const { + // Fail if the blend shape index is out of range. + ERR_FAIL_INDEX_V(p_blend_shape, FT_MAX, 0.0f); + + // Return the blend shape value. + return blend_shape_values[p_blend_shape]; +} + +void XRFaceTracker::set_blend_shape(BlendShapeEntry p_blend_shape, float p_value) { + // Fail if the blend shape index is out of range. + ERR_FAIL_INDEX(p_blend_shape, FT_MAX); + + // Save the new blend shape value. + blend_shape_values[p_blend_shape] = p_value; +} + +PackedFloat32Array XRFaceTracker::get_blend_shapes() const { + // Create a packed float32 array and copy the blend shape values into it. + PackedFloat32Array data; + data.resize(FT_MAX); + memcpy(data.ptrw(), blend_shape_values, sizeof(blend_shape_values)); + + // Return the blend shape array. + return data; +} + +void XRFaceTracker::set_blend_shapes(const PackedFloat32Array &p_blend_shapes) { + // Fail if the blend shape array is not the correct size. + ERR_FAIL_COND(p_blend_shapes.size() != FT_MAX); + + // Copy the blend shape values into the blend shape array. + memcpy(blend_shape_values, p_blend_shapes.ptr(), sizeof(blend_shape_values)); +} diff --git a/servers/xr/xr_face_tracker.h b/servers/xr/xr_face_tracker.h new file mode 100644 index 0000000000..b9f553cba6 --- /dev/null +++ b/servers/xr/xr_face_tracker.h @@ -0,0 +1,213 @@ +/**************************************************************************/ +/* xr_face_tracker.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef XR_FACE_TRACKER_H +#define XR_FACE_TRACKER_H + +#include "core/object/ref_counted.h" + +/** + The XRFaceTracker class provides face blend shape weights. + + The supported blend shapes are based on the Unified Expressions + standard, and as such have a well defined mapping to ARKit, SRanipal, + and Meta Movement standards. + */ + +class XRFaceTracker : public RefCounted { + GDCLASS(XRFaceTracker, RefCounted); + _THREAD_SAFE_CLASS_ + +public: + enum BlendShapeEntry { + // Base Shapes + FT_EYE_LOOK_OUT_RIGHT, // Right eye looks outwards. + FT_EYE_LOOK_IN_RIGHT, // Right eye looks inwards. + FT_EYE_LOOK_UP_RIGHT, // Right eye looks upwards. + FT_EYE_LOOK_DOWN_RIGHT, // Right eye looks downwards. + FT_EYE_LOOK_OUT_LEFT, // Left eye looks outwards. + FT_EYE_LOOK_IN_LEFT, // Left eye looks inwards. + FT_EYE_LOOK_UP_LEFT, // Left eye looks upwards. + FT_EYE_LOOK_DOWN_LEFT, // Left eye looks downwards. + FT_EYE_CLOSED_RIGHT, // Closes the right eyelid. + FT_EYE_CLOSED_LEFT, // Closes the left eyelid. + FT_EYE_SQUINT_RIGHT, // Squeezes the right eye socket muscles. + FT_EYE_SQUINT_LEFT, // Squeezes the left eye socket muscles. + FT_EYE_WIDE_RIGHT, // Right eyelid widens beyond relaxed. + FT_EYE_WIDE_LEFT, // Left eyelid widens beyond relaxed. + FT_EYE_DILATION_RIGHT, // Dilates the right eye pupil. + FT_EYE_DILATION_LEFT, // Dilates the left eye pupil. + FT_EYE_CONSTRICT_RIGHT, // Constricts the right eye pupil. + FT_EYE_CONSTRICT_LEFT, // Constricts the left eye pupil. + FT_BROW_PINCH_RIGHT, // Right eyebrow pinches in. + FT_BROW_PINCH_LEFT, // Left eyebrow pinches in. + FT_BROW_LOWERER_RIGHT, // Outer right eyebrow pulls down. + FT_BROW_LOWERER_LEFT, // Outer left eyebrow pulls down. + FT_BROW_INNER_UP_RIGHT, // Inner right eyebrow pulls up. + FT_BROW_INNER_UP_LEFT, // Inner left eyebrow pulls up. + FT_BROW_OUTER_UP_RIGHT, // Outer right eyebrow pulls up. + FT_BROW_OUTER_UP_LEFT, // Outer left eyebrow pulls up. + FT_NOSE_SNEER_RIGHT, // Right side face sneers. + FT_NOSE_SNEER_LEFT, // Left side face sneers. + FT_NASAL_DILATION_RIGHT, // Right side nose canal dilates. + FT_NASAL_DILATION_LEFT, // Left side nose canal dilates. + FT_NASAL_CONSTRICT_RIGHT, // Right side nose canal constricts. + FT_NASAL_CONSTRICT_LEFT, // Left side nose canal constricts. + FT_CHEEK_SQUINT_RIGHT, // Raises the right side cheek. + FT_CHEEK_SQUINT_LEFT, // Raises the left side cheek. + FT_CHEEK_PUFF_RIGHT, // Puffs the right side cheek. + FT_CHEEK_PUFF_LEFT, // Puffs the left side cheek. + FT_CHEEK_SUCK_RIGHT, // Sucks in the right side cheek. + FT_CHEEK_SUCK_LEFT, // Sucks in the left side cheek. + FT_JAW_OPEN, // Opens jawbone. + FT_MOUTH_CLOSED, // Closes the mouth. + FT_JAW_RIGHT, // Pushes jawbone right. + FT_JAW_LEFT, // Pushes jawbone left. + FT_JAW_FORWARD, // Pushes jawbone forward. + FT_JAW_BACKWARD, // Pushes jawbone backward. + FT_JAW_CLENCH, // Flexes jaw muscles. + FT_JAW_MANDIBLE_RAISE, // Raises the jawbone. + FT_LIP_SUCK_UPPER_RIGHT, // Upper right lip part tucks in the mouth. + FT_LIP_SUCK_UPPER_LEFT, // Upper left lip part tucks in the mouth. + FT_LIP_SUCK_LOWER_RIGHT, // Lower right lip part tucks in the mouth. + FT_LIP_SUCK_LOWER_LEFT, // Lower left lip part tucks in the mouth. + FT_LIP_SUCK_CORNER_RIGHT, // Right lip corner folds into the mouth. + FT_LIP_SUCK_CORNER_LEFT, // Left lip corner folds into the mouth. + FT_LIP_FUNNEL_UPPER_RIGHT, // Upper right lip part pushes into a funnel. + FT_LIP_FUNNEL_UPPER_LEFT, // Upper left lip part pushes into a funnel. + FT_LIP_FUNNEL_LOWER_RIGHT, // Lower right lip part pushes into a funnel. + FT_LIP_FUNNEL_LOWER_LEFT, // Lower left lip part pushes into a funnel. + FT_LIP_PUCKER_UPPER_RIGHT, // Upper right lip part pushes outwards. + FT_LIP_PUCKER_UPPER_LEFT, // Upper left lip part pushes outwards. + FT_LIP_PUCKER_LOWER_RIGHT, // Lower right lip part pushes outwards. + FT_LIP_PUCKER_LOWER_LEFT, // Lower left lip part pushes outwards. + FT_MOUTH_UPPER_UP_RIGHT, // Upper right part of the lip pulls up. + FT_MOUTH_UPPER_UP_LEFT, // Upper left part of the lip pulls up. + FT_MOUTH_LOWER_DOWN_RIGHT, // Lower right part of the lip pulls up. + FT_MOUTH_LOWER_DOWN_LEFT, // Lower left part of the lip pulls up. + FT_MOUTH_UPPER_DEEPEN_RIGHT, // Upper right lip part pushes in the cheek. + FT_MOUTH_UPPER_DEEPEN_LEFT, // Upper left lip part pushes in the cheek. + FT_MOUTH_UPPER_RIGHT, // Moves upper lip right. + FT_MOUTH_UPPER_LEFT, // Moves upper lip left. + FT_MOUTH_LOWER_RIGHT, // Moves lower lip right. + FT_MOUTH_LOWER_LEFT, // Moves lower lip left. + FT_MOUTH_CORNER_PULL_RIGHT, // Right lip corner pulls diagonally up and out. + FT_MOUTH_CORNER_PULL_LEFT, // Left lip corner pulls diagonally up and out. + FT_MOUTH_CORNER_SLANT_RIGHT, // Right corner lip slants up. + FT_MOUTH_CORNER_SLANT_LEFT, // Left corner lip slants up. + FT_MOUTH_FROWN_RIGHT, // Right corner lip pulls down. + FT_MOUTH_FROWN_LEFT, // Left corner lip pulls down. + FT_MOUTH_STRETCH_RIGHT, // Mouth corner lip pulls out and down. + FT_MOUTH_STRETCH_LEFT, // Mouth corner lip pulls out and down. + FT_MOUTH_DIMPLE_RIGHT, // Right lip corner is pushed backwards. + FT_MOUTH_DIMPLE_LEFT, // Left lip corner is pushed backwards. + FT_MOUTH_RAISER_UPPER, // Raises and slightly pushes out the upper mouth. + FT_MOUTH_RAISER_LOWER, // Raises and slightly pushes out the lower mouth. + FT_MOUTH_PRESS_RIGHT, // Right side lips press and flatten together vertically. + FT_MOUTH_PRESS_LEFT, // Left side lips press and flatten together vertically. + FT_MOUTH_TIGHTENER_RIGHT, // Right side lips squeeze together horizontally. + FT_MOUTH_TIGHTENER_LEFT, // Left side lips squeeze together horizontally. + FT_TONGUE_OUT, // Tongue visibly sticks out of the mouth. + FT_TONGUE_UP, // Tongue points upwards. + FT_TONGUE_DOWN, // Tongue points downwards. + FT_TONGUE_RIGHT, // Tongue points right. + FT_TONGUE_LEFT, // Tongue points left. + FT_TONGUE_ROLL, // Sides of the tongue funnel, creating a roll. + FT_TONGUE_BLEND_DOWN, // Tongue arches up then down inside the mouth. + FT_TONGUE_CURL_UP, // Tongue arches down then up inside the mouth. + FT_TONGUE_SQUISH, // Tongue squishes together and thickens. + FT_TONGUE_FLAT, // Tongue flattens and thins out. + FT_TONGUE_TWIST_RIGHT, // Tongue tip rotates clockwise, with the rest following gradually. + FT_TONGUE_TWIST_LEFT, // Tongue tip rotates counter-clockwise, with the rest following gradually. + FT_SOFT_PALATE_CLOSE, // Inner mouth throat closes. + FT_THROAT_SWALLOW, // The Adam's apple visibly swallows. + FT_NECK_FLEX_RIGHT, // Right side neck visibly flexes. + FT_NECK_FLEX_LEFT, // Left side neck visibly flexes. + // Blended Shapes + FT_EYE_CLOSED, // Closes both eye lids. + FT_EYE_WIDE, // Widens both eye lids. + FT_EYE_SQUINT, // Squints both eye lids. + FT_EYE_DILATION, // Dilates both pupils. + FT_EYE_CONSTRICT, // Constricts both pupils. + FT_BROW_DOWN_RIGHT, // Pulls the right eyebrow down and in. + FT_BROW_DOWN_LEFT, // Pulls the left eyebrow down and in. + FT_BROW_DOWN, // Pulls both eyebrows down and in. + FT_BROW_UP_RIGHT, // Right brow appears worried. + FT_BROW_UP_LEFT, // Left brow appears worried. + FT_BROW_UP, // Both brows appear worried. + FT_NOSE_SNEER, // Entire face sneers. + FT_NASAL_DILATION, // Both nose canals dilate. + FT_NASAL_CONSTRICT, // Both nose canals constrict. + FT_CHEEK_PUFF, // Puffs both cheeks. + FT_CHEEK_SUCK, // Sucks in both cheeks. + FT_CHEEK_SQUINT, // Raises both cheeks. + FT_LIP_SUCK_UPPER, // Tucks in the upper lips. + FT_LIP_SUCK_LOWER, // Tucks in the lower lips. + FT_LIP_SUCK, // Tucks in both lips. + FT_LIP_FUNNEL_UPPER, // Funnels in the upper lips. + FT_LIP_FUNNEL_LOWER, // Funnels in the lower lips. + FT_LIP_FUNNEL, // Funnels in both lips. + FT_LIP_PUCKER_UPPER, // Upper lip part pushes outwards. + FT_LIP_PUCKER_LOWER, // Lower lip part pushes outwards. + FT_LIP_PUCKER, // Lips push outwards. + FT_MOUTH_UPPER_UP, // Raises the upper lips. + FT_MOUTH_LOWER_DOWN, // Lowers the lower lips. + FT_MOUTH_OPEN, // Mouth opens, revealing teeth. + FT_MOUTH_RIGHT, // Moves mouth right. + FT_MOUTH_LEFT, // Moves mouth left. + FT_MOUTH_SMILE_RIGHT, // Right side of the mouth smiles. + FT_MOUTH_SMILE_LEFT, // Left side of the mouth smiles. + FT_MOUTH_SMILE, // Mouth expresses a smile. + FT_MOUTH_SAD_RIGHT, // Right side of the mouth expresses sadness. + FT_MOUTH_SAD_LEFT, // Left side of the mouth expresses sadness. + FT_MOUTH_SAD, // Mouth expresses sadness. + FT_MOUTH_STRETCH, // Mouth stretches. + FT_MOUTH_DIMPLE, // Lip corners dimple. + FT_MOUTH_TIGHTENER, // Mouth tightens. + FT_MOUTH_PRESS, // Mouth presses together. + FT_MAX // Maximum blend shape. + }; + + float get_blend_shape(BlendShapeEntry p_blend_shape) const; + void set_blend_shape(BlendShapeEntry p_blend_shape, float p_value); + + PackedFloat32Array get_blend_shapes() const; + void set_blend_shapes(const PackedFloat32Array &p_blend_shapes); + +protected: + static void _bind_methods(); + +private: + float blend_shape_values[FT_MAX] = {}; +}; + +VARIANT_ENUM_CAST(XRFaceTracker::BlendShapeEntry); + +#endif // XR_FACE_TRACKER_H diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index e7f644d53f..b3bb0a3702 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -30,6 +30,7 @@ #include "xr_server.h" #include "core/config/project_settings.h" +#include "xr/xr_face_tracker.h" #include "xr/xr_interface.h" #include "xr/xr_positional_tracker.h" @@ -74,6 +75,11 @@ void XRServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_trackers", "tracker_types"), &XRServer::get_trackers); ClassDB::bind_method(D_METHOD("get_tracker", "tracker_name"), &XRServer::get_tracker); + ClassDB::bind_method(D_METHOD("add_face_tracker", "tracker_name", "face_tracker"), &XRServer::add_face_tracker); + ClassDB::bind_method(D_METHOD("remove_face_tracker", "tracker_name"), &XRServer::remove_face_tracker); + ClassDB::bind_method(D_METHOD("get_face_trackers"), &XRServer::get_face_trackers); + ClassDB::bind_method(D_METHOD("get_face_tracker", "tracker_name"), &XRServer::get_face_tracker); + ClassDB::bind_method(D_METHOD("get_primary_interface"), &XRServer::get_primary_interface); ClassDB::bind_method(D_METHOD("set_primary_interface", "interface"), &XRServer::set_primary_interface); @@ -97,6 +103,10 @@ void XRServer::_bind_methods() { ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); + + ADD_SIGNAL(MethodInfo("face_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker"))); + ADD_SIGNAL(MethodInfo("face_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker"))); + ADD_SIGNAL(MethodInfo("face_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"))); }; double XRServer::get_world_scale() const { @@ -352,6 +362,44 @@ PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker return arr; } +void XRServer::add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker) { + ERR_FAIL_COND(p_face_tracker.is_null()); + + if (!face_trackers.has(p_tracker_name)) { + // We don't have a tracker with this name, we're going to add it. + face_trackers[p_tracker_name] = p_face_tracker; + emit_signal(SNAME("face_tracker_added"), p_tracker_name, p_face_tracker); + } else if (face_trackers[p_tracker_name] != p_face_tracker) { + // We already have a tracker with this name, we're going to replace it. + face_trackers[p_tracker_name] = p_face_tracker; + emit_signal(SNAME("face_tracker_updated"), p_tracker_name, p_face_tracker); + } +} + +void XRServer::remove_face_tracker(const StringName &p_tracker_name) { + // Skip if no face tracker is found. + if (!face_trackers.has(p_tracker_name)) { + return; + } + + // Send the removed signal, then remove the face tracker. + emit_signal(SNAME("face_tracker_removed"), p_tracker_name); + face_trackers.erase(p_tracker_name); +} + +Dictionary XRServer::get_face_trackers() const { + return face_trackers; +} + +Ref<XRFaceTracker> XRServer::get_face_tracker(const StringName &p_tracker_name) const { + // Skip if no tracker is found. + if (!face_trackers.has(p_tracker_name)) { + return Ref<XRFaceTracker>(); + } + + return face_trackers[p_tracker_name]; +} + void XRServer::_process() { // called from our main game loop before we handle physics and game logic // note that we can have multiple interfaces active if we have interfaces that purely handle tracking diff --git a/servers/xr_server.h b/servers/xr_server.h index fe59fc22cb..0a4e020a1f 100644 --- a/servers/xr_server.h +++ b/servers/xr_server.h @@ -39,6 +39,7 @@ class XRInterface; class XRPositionalTracker; +class XRFaceTracker; /** The XR server is a singleton object that gives access to the various @@ -86,6 +87,8 @@ private: Vector<Ref<XRInterface>> interfaces; Dictionary trackers; + Dictionary face_trackers; + Ref<XRInterface> primary_interface; /* we'll identify one interface as primary, this will be used by our viewports */ double world_scale; /* scale by which we multiply our tracker positions */ @@ -183,6 +186,14 @@ public: PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const; // Q: Should we add get_suggested_input_names and get_suggested_haptic_names even though we don't use them for the IDE? + /* + Face trackers are objects that expose the tracked blend shapes of a face. + */ + void add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker); + void remove_face_tracker(const StringName &p_tracker_name); + Dictionary get_face_trackers() const; + Ref<XRFaceTracker> get_face_tracker(const StringName &p_tracker_name) const; + // Process is called before we handle our physics process and game process. This is where our interfaces will update controller data and such. void _process(); diff --git a/tests/scene/test_image_texture.h b/tests/scene/test_image_texture.h new file mode 100644 index 0000000000..c9282165a6 --- /dev/null +++ b/tests/scene/test_image_texture.h @@ -0,0 +1,111 @@ +/**************************************************************************/ +/* test_image_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_IMAGE_TEXTURE_H +#define TEST_IMAGE_TEXTURE_H + +#include "core/io/image.h" +#include "scene/resources/image_texture.h" + +#include "tests/test_macros.h" +#include "tests/test_utils.h" + +namespace TestImageTexture { + +// [SceneTree] in a test case name enables initializing a mock render server, +// which ImageTexture is dependent on. +TEST_CASE("[SceneTree][ImageTexture] constructor") { + Ref<ImageTexture> image_texture = memnew(ImageTexture); + CHECK(image_texture->get_width() == 0); + CHECK(image_texture->get_height() == 0); + CHECK(image_texture->get_format() == 0); + CHECK(image_texture->has_alpha() == false); + CHECK(image_texture->get_image() == Ref<Image>()); +} + +TEST_CASE("[SceneTree][ImageTexture] create_from_image") { + Ref<Image> image = memnew(Image(16, 8, true, Image::FORMAT_RGBA8)); + Ref<ImageTexture> image_texture = ImageTexture::create_from_image(image); + CHECK(image_texture->get_width() == 16); + CHECK(image_texture->get_height() == 8); + CHECK(image_texture->get_format() == Image::FORMAT_RGBA8); + CHECK(image_texture->has_alpha() == true); + CHECK(image_texture->get_rid().is_valid() == true); +} + +TEST_CASE("[SceneTree][ImageTexture] set_image") { + Ref<ImageTexture> image_texture = memnew(ImageTexture); + Ref<Image> image = memnew(Image(8, 4, false, Image::FORMAT_RGB8)); + image_texture->set_image(image); + CHECK(image_texture->get_width() == 8); + CHECK(image_texture->get_height() == 4); + CHECK(image_texture->get_format() == Image::FORMAT_RGB8); + CHECK(image_texture->has_alpha() == false); + CHECK(image_texture->get_width() == image_texture->get_image()->get_width()); + CHECK(image_texture->get_height() == image_texture->get_image()->get_height()); + CHECK(image_texture->get_format() == image_texture->get_image()->get_format()); +} + +TEST_CASE("[SceneTree][ImageTexture] set_size_override") { + Ref<Image> image = memnew(Image(16, 8, false, Image::FORMAT_RGB8)); + Ref<ImageTexture> image_texture = ImageTexture::create_from_image(image); + CHECK(image_texture->get_width() == 16); + CHECK(image_texture->get_height() == 8); + image_texture->set_size_override(Size2i(32, 16)); + CHECK(image_texture->get_width() == 32); + CHECK(image_texture->get_height() == 16); +} + +TEST_CASE("[SceneTree][ImageTexture] is_pixel_opaque") { + Ref<Image> image = memnew(Image(8, 8, false, Image::FORMAT_RGBA8)); + image->set_pixel(0, 0, Color(0.0, 0.0, 0.0, 0.0)); // not opaque + image->set_pixel(0, 1, Color(0.0, 0.0, 0.0, 0.1)); // not opaque + image->set_pixel(0, 2, Color(0.0, 0.0, 0.0, 0.5)); // opaque + image->set_pixel(0, 3, Color(0.0, 0.0, 0.0, 0.9)); // opaque + image->set_pixel(0, 4, Color(0.0, 0.0, 0.0, 1.0)); // opaque + + Ref<ImageTexture> image_texture = ImageTexture::create_from_image(image); + CHECK(image_texture->is_pixel_opaque(0, 0) == false); + CHECK(image_texture->is_pixel_opaque(0, 1) == false); + CHECK(image_texture->is_pixel_opaque(0, 2) == true); + CHECK(image_texture->is_pixel_opaque(0, 3) == true); + CHECK(image_texture->is_pixel_opaque(0, 4) == true); +} + +TEST_CASE("[SceneTree][ImageTexture] set_path") { + Ref<ImageTexture> image_texture = memnew(ImageTexture); + String path = TestUtils::get_data_path("images/icon.png"); + image_texture->set_path(path, true); + CHECK(image_texture->get_path() == path); +} + +} //namespace TestImageTexture + +#endif // TEST_IMAGE_TEXTURE_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 7f49805274..bac5c3dd7c 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -30,8 +30,11 @@ #include "test_main.h" +#ifdef TOOLS_ENABLED #include "editor/editor_paths.h" #include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + #include "tests/core/config/test_project_settings.h" #include "tests/core/input/test_input_event.h" #include "tests/core/input/test_input_event_key.h" @@ -103,6 +106,7 @@ #include "tests/scene/test_curve_2d.h" #include "tests/scene/test_curve_3d.h" #include "tests/scene/test_gradient.h" +#include "tests/scene/test_image_texture.h" #include "tests/scene/test_navigation_agent_2d.h" #include "tests/scene/test_navigation_obstacle_2d.h" #include "tests/scene/test_navigation_region_2d.h" @@ -270,11 +274,13 @@ struct GodotTestCaseListener : public doctest::IReporter { SceneTree::get_singleton()->get_root()->set_embedding_subwindows(true); } +#ifdef TOOLS_ENABLED if (name.find("[Editor]") != -1) { Engine::get_singleton()->set_editor_hint(true); EditorPaths::create(); EditorSettings::create(); } +#endif // TOOLS_ENABLED return; } @@ -298,9 +304,11 @@ struct GodotTestCaseListener : public doctest::IReporter { } void test_case_end(const doctest::CurrentTestCaseStats &) override { +#ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { EditorSettings::destroy(); } +#endif // TOOLS_ENABLED Engine::get_singleton()->set_editor_hint(false); diff --git a/thirdparty/README.md b/thirdparty/README.md index 107c2b2589..cf3184ab04 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -64,7 +64,7 @@ Files extracted from upstream source: Files extracted from upstream source: -- `encoder/` and `transcoder/` folders +- `encoder/` and `transcoder/` folders, minus `jpgd.{cpp,h}` - `LICENSE` Applied upstream PR https://github.com/BinomialLLC/basis_universal/pull/344 to diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp index c431ceaf12..e87dd636a2 100644 --- a/thirdparty/basis_universal/encoder/basisu_enc.cpp +++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp @@ -409,7 +409,7 @@ namespace basisu bool load_jpg(const char *pFilename, image& img) { int width = 0, height = 0, actual_comps = 0; - uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagLinearChromaFiltering); + uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagBoxChromaFiltering); if (!pImage_data) return false; diff --git a/thirdparty/basis_universal/encoder/jpgd.cpp b/thirdparty/basis_universal/encoder/jpgd.cpp deleted file mode 100644 index fec8b71439..0000000000 --- a/thirdparty/basis_universal/encoder/jpgd.cpp +++ /dev/null @@ -1,3230 +0,0 @@ -// jpgd.cpp - C++ class for JPEG decompression. Written by Richard Geldreich <richgel99@gmail.com> between 1994-2020. -// Supports progressive and baseline sequential JPEG image files, and the most common chroma subsampling factors: Y, H1V1, H2V1, H1V2, and H2V2. -// Supports box and linear chroma upsampling. -// -// Released under two licenses. You are free to choose which license you want: -// License 1: -// Public Domain -// -// License 2: -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Alex Evans: Linear memory allocator (taken from jpge.h). -// v1.04, May. 19, 2012: Code tweaks to fix VS2008 static code analysis warnings -// v2.00, March 20, 2020: Fuzzed with zzuf and afl. Fixed several issues, converted most assert()'s to run-time checks. Added chroma upsampling. Removed freq. domain upsampling. gcc/clang warnings. -// - -#include "jpgd.h" -#include <string.h> -#include <algorithm> -#include <assert.h> - -#ifdef _MSC_VER -#pragma warning (disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable -#endif - -#define JPGD_TRUE (1) -#define JPGD_FALSE (0) - -#define JPGD_MAX(a,b) (((a)>(b)) ? (a) : (b)) -#define JPGD_MIN(a,b) (((a)<(b)) ? (a) : (b)) - -namespace jpgd { - - static inline void* jpgd_malloc(size_t nSize) { return malloc(nSize); } - static inline void jpgd_free(void* p) { free(p); } - - // DCT coefficients are stored in this sequence. - static int g_ZAG[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; - - enum JPEG_MARKER - { - M_SOF0 = 0xC0, M_SOF1 = 0xC1, M_SOF2 = 0xC2, M_SOF3 = 0xC3, M_SOF5 = 0xC5, M_SOF6 = 0xC6, M_SOF7 = 0xC7, M_JPG = 0xC8, - M_SOF9 = 0xC9, M_SOF10 = 0xCA, M_SOF11 = 0xCB, M_SOF13 = 0xCD, M_SOF14 = 0xCE, M_SOF15 = 0xCF, M_DHT = 0xC4, M_DAC = 0xCC, - M_RST0 = 0xD0, M_RST1 = 0xD1, M_RST2 = 0xD2, M_RST3 = 0xD3, M_RST4 = 0xD4, M_RST5 = 0xD5, M_RST6 = 0xD6, M_RST7 = 0xD7, - M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_DNL = 0xDC, M_DRI = 0xDD, M_DHP = 0xDE, M_EXP = 0xDF, - M_APP0 = 0xE0, M_APP15 = 0xEF, M_JPG0 = 0xF0, M_JPG13 = 0xFD, M_COM = 0xFE, M_TEM = 0x01, M_ERROR = 0x100, RST0 = 0xD0 - }; - - enum JPEG_SUBSAMPLING { JPGD_GRAYSCALE = 0, JPGD_YH1V1, JPGD_YH2V1, JPGD_YH1V2, JPGD_YH2V2 }; - -#define CONST_BITS 13 -#define PASS1_BITS 2 -#define SCALEDONE ((int32)1) - -#define FIX_0_298631336 ((int32)2446) /* FIX(0.298631336) */ -#define FIX_0_390180644 ((int32)3196) /* FIX(0.390180644) */ -#define FIX_0_541196100 ((int32)4433) /* FIX(0.541196100) */ -#define FIX_0_765366865 ((int32)6270) /* FIX(0.765366865) */ -#define FIX_0_899976223 ((int32)7373) /* FIX(0.899976223) */ -#define FIX_1_175875602 ((int32)9633) /* FIX(1.175875602) */ -#define FIX_1_501321110 ((int32)12299) /* FIX(1.501321110) */ -#define FIX_1_847759065 ((int32)15137) /* FIX(1.847759065) */ -#define FIX_1_961570560 ((int32)16069) /* FIX(1.961570560) */ -#define FIX_2_053119869 ((int32)16819) /* FIX(2.053119869) */ -#define FIX_2_562915447 ((int32)20995) /* FIX(2.562915447) */ -#define FIX_3_072711026 ((int32)25172) /* FIX(3.072711026) */ - -#define DESCALE(x,n) (((x) + (SCALEDONE << ((n)-1))) >> (n)) -#define DESCALE_ZEROSHIFT(x,n) (((x) + (128 << (n)) + (SCALEDONE << ((n)-1))) >> (n)) - -#define MULTIPLY(var, cnst) ((var) * (cnst)) - -#define CLAMP(i) ((static_cast<uint>(i) > 255) ? (((~i) >> 31) & 0xFF) : (i)) - - static inline int left_shifti(int val, uint32_t bits) - { - return static_cast<int>(static_cast<uint32_t>(val) << bits); - } - - // Compiler creates a fast path 1D IDCT for X non-zero columns - template <int NONZERO_COLS> - struct Row - { - static void idct(int* pTemp, const jpgd_block_t* pSrc) - { - // ACCESS_COL() will be optimized at compile time to either an array access, or 0. Good compilers will then optimize out muls against 0. -#define ACCESS_COL(x) (((x) < NONZERO_COLS) ? (int)pSrc[x] : 0) - - const int z2 = ACCESS_COL(2), z3 = ACCESS_COL(6); - - const int z1 = MULTIPLY(z2 + z3, FIX_0_541196100); - const int tmp2 = z1 + MULTIPLY(z3, -FIX_1_847759065); - const int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); - - const int tmp0 = left_shifti(ACCESS_COL(0) + ACCESS_COL(4), CONST_BITS); - const int tmp1 = left_shifti(ACCESS_COL(0) - ACCESS_COL(4), CONST_BITS); - - const int tmp10 = tmp0 + tmp3, tmp13 = tmp0 - tmp3, tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2; - - const int atmp0 = ACCESS_COL(7), atmp1 = ACCESS_COL(5), atmp2 = ACCESS_COL(3), atmp3 = ACCESS_COL(1); - - const int bz1 = atmp0 + atmp3, bz2 = atmp1 + atmp2, bz3 = atmp0 + atmp2, bz4 = atmp1 + atmp3; - const int bz5 = MULTIPLY(bz3 + bz4, FIX_1_175875602); - - const int az1 = MULTIPLY(bz1, -FIX_0_899976223); - const int az2 = MULTIPLY(bz2, -FIX_2_562915447); - const int az3 = MULTIPLY(bz3, -FIX_1_961570560) + bz5; - const int az4 = MULTIPLY(bz4, -FIX_0_390180644) + bz5; - - const int btmp0 = MULTIPLY(atmp0, FIX_0_298631336) + az1 + az3; - const int btmp1 = MULTIPLY(atmp1, FIX_2_053119869) + az2 + az4; - const int btmp2 = MULTIPLY(atmp2, FIX_3_072711026) + az2 + az3; - const int btmp3 = MULTIPLY(atmp3, FIX_1_501321110) + az1 + az4; - - pTemp[0] = DESCALE(tmp10 + btmp3, CONST_BITS - PASS1_BITS); - pTemp[7] = DESCALE(tmp10 - btmp3, CONST_BITS - PASS1_BITS); - pTemp[1] = DESCALE(tmp11 + btmp2, CONST_BITS - PASS1_BITS); - pTemp[6] = DESCALE(tmp11 - btmp2, CONST_BITS - PASS1_BITS); - pTemp[2] = DESCALE(tmp12 + btmp1, CONST_BITS - PASS1_BITS); - pTemp[5] = DESCALE(tmp12 - btmp1, CONST_BITS - PASS1_BITS); - pTemp[3] = DESCALE(tmp13 + btmp0, CONST_BITS - PASS1_BITS); - pTemp[4] = DESCALE(tmp13 - btmp0, CONST_BITS - PASS1_BITS); - } - }; - - template <> - struct Row<0> - { - static void idct(int* pTemp, const jpgd_block_t* pSrc) - { - (void)pTemp; - (void)pSrc; - } - }; - - template <> - struct Row<1> - { - static void idct(int* pTemp, const jpgd_block_t* pSrc) - { - const int dcval = left_shifti(pSrc[0], PASS1_BITS); - - pTemp[0] = dcval; - pTemp[1] = dcval; - pTemp[2] = dcval; - pTemp[3] = dcval; - pTemp[4] = dcval; - pTemp[5] = dcval; - pTemp[6] = dcval; - pTemp[7] = dcval; - } - }; - - // Compiler creates a fast path 1D IDCT for X non-zero rows - template <int NONZERO_ROWS> - struct Col - { - static void idct(uint8* pDst_ptr, const int* pTemp) - { - // ACCESS_ROW() will be optimized at compile time to either an array access, or 0. -#define ACCESS_ROW(x) (((x) < NONZERO_ROWS) ? pTemp[x * 8] : 0) - - const int z2 = ACCESS_ROW(2); - const int z3 = ACCESS_ROW(6); - - const int z1 = MULTIPLY(z2 + z3, FIX_0_541196100); - const int tmp2 = z1 + MULTIPLY(z3, -FIX_1_847759065); - const int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); - - const int tmp0 = left_shifti(ACCESS_ROW(0) + ACCESS_ROW(4), CONST_BITS); - const int tmp1 = left_shifti(ACCESS_ROW(0) - ACCESS_ROW(4), CONST_BITS); - - const int tmp10 = tmp0 + tmp3, tmp13 = tmp0 - tmp3, tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2; - - const int atmp0 = ACCESS_ROW(7), atmp1 = ACCESS_ROW(5), atmp2 = ACCESS_ROW(3), atmp3 = ACCESS_ROW(1); - - const int bz1 = atmp0 + atmp3, bz2 = atmp1 + atmp2, bz3 = atmp0 + atmp2, bz4 = atmp1 + atmp3; - const int bz5 = MULTIPLY(bz3 + bz4, FIX_1_175875602); - - const int az1 = MULTIPLY(bz1, -FIX_0_899976223); - const int az2 = MULTIPLY(bz2, -FIX_2_562915447); - const int az3 = MULTIPLY(bz3, -FIX_1_961570560) + bz5; - const int az4 = MULTIPLY(bz4, -FIX_0_390180644) + bz5; - - const int btmp0 = MULTIPLY(atmp0, FIX_0_298631336) + az1 + az3; - const int btmp1 = MULTIPLY(atmp1, FIX_2_053119869) + az2 + az4; - const int btmp2 = MULTIPLY(atmp2, FIX_3_072711026) + az2 + az3; - const int btmp3 = MULTIPLY(atmp3, FIX_1_501321110) + az1 + az4; - - int i = DESCALE_ZEROSHIFT(tmp10 + btmp3, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 0] = (uint8)CLAMP(i); - - i = DESCALE_ZEROSHIFT(tmp10 - btmp3, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 7] = (uint8)CLAMP(i); - - i = DESCALE_ZEROSHIFT(tmp11 + btmp2, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 1] = (uint8)CLAMP(i); - - i = DESCALE_ZEROSHIFT(tmp11 - btmp2, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 6] = (uint8)CLAMP(i); - - i = DESCALE_ZEROSHIFT(tmp12 + btmp1, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 2] = (uint8)CLAMP(i); - - i = DESCALE_ZEROSHIFT(tmp12 - btmp1, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 5] = (uint8)CLAMP(i); - - i = DESCALE_ZEROSHIFT(tmp13 + btmp0, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 3] = (uint8)CLAMP(i); - - i = DESCALE_ZEROSHIFT(tmp13 - btmp0, CONST_BITS + PASS1_BITS + 3); - pDst_ptr[8 * 4] = (uint8)CLAMP(i); - } - }; - - template <> - struct Col<1> - { - static void idct(uint8* pDst_ptr, const int* pTemp) - { - int dcval = DESCALE_ZEROSHIFT(pTemp[0], PASS1_BITS + 3); - const uint8 dcval_clamped = (uint8)CLAMP(dcval); - pDst_ptr[0 * 8] = dcval_clamped; - pDst_ptr[1 * 8] = dcval_clamped; - pDst_ptr[2 * 8] = dcval_clamped; - pDst_ptr[3 * 8] = dcval_clamped; - pDst_ptr[4 * 8] = dcval_clamped; - pDst_ptr[5 * 8] = dcval_clamped; - pDst_ptr[6 * 8] = dcval_clamped; - pDst_ptr[7 * 8] = dcval_clamped; - } - }; - - static const uint8 s_idct_row_table[] = - { - 1,0,0,0,0,0,0,0, 2,0,0,0,0,0,0,0, 2,1,0,0,0,0,0,0, 2,1,1,0,0,0,0,0, 2,2,1,0,0,0,0,0, 3,2,1,0,0,0,0,0, 4,2,1,0,0,0,0,0, 4,3,1,0,0,0,0,0, - 4,3,2,0,0,0,0,0, 4,3,2,1,0,0,0,0, 4,3,2,1,1,0,0,0, 4,3,2,2,1,0,0,0, 4,3,3,2,1,0,0,0, 4,4,3,2,1,0,0,0, 5,4,3,2,1,0,0,0, 6,4,3,2,1,0,0,0, - 6,5,3,2,1,0,0,0, 6,5,4,2,1,0,0,0, 6,5,4,3,1,0,0,0, 6,5,4,3,2,0,0,0, 6,5,4,3,2,1,0,0, 6,5,4,3,2,1,1,0, 6,5,4,3,2,2,1,0, 6,5,4,3,3,2,1,0, - 6,5,4,4,3,2,1,0, 6,5,5,4,3,2,1,0, 6,6,5,4,3,2,1,0, 7,6,5,4,3,2,1,0, 8,6,5,4,3,2,1,0, 8,7,5,4,3,2,1,0, 8,7,6,4,3,2,1,0, 8,7,6,5,3,2,1,0, - 8,7,6,5,4,2,1,0, 8,7,6,5,4,3,1,0, 8,7,6,5,4,3,2,0, 8,7,6,5,4,3,2,1, 8,7,6,5,4,3,2,2, 8,7,6,5,4,3,3,2, 8,7,6,5,4,4,3,2, 8,7,6,5,5,4,3,2, - 8,7,6,6,5,4,3,2, 8,7,7,6,5,4,3,2, 8,8,7,6,5,4,3,2, 8,8,8,6,5,4,3,2, 8,8,8,7,5,4,3,2, 8,8,8,7,6,4,3,2, 8,8,8,7,6,5,3,2, 8,8,8,7,6,5,4,2, - 8,8,8,7,6,5,4,3, 8,8,8,7,6,5,4,4, 8,8,8,7,6,5,5,4, 8,8,8,7,6,6,5,4, 8,8,8,7,7,6,5,4, 8,8,8,8,7,6,5,4, 8,8,8,8,8,6,5,4, 8,8,8,8,8,7,5,4, - 8,8,8,8,8,7,6,4, 8,8,8,8,8,7,6,5, 8,8,8,8,8,7,6,6, 8,8,8,8,8,7,7,6, 8,8,8,8,8,8,7,6, 8,8,8,8,8,8,8,6, 8,8,8,8,8,8,8,7, 8,8,8,8,8,8,8,8, - }; - - static const uint8 s_idct_col_table[] = - { - 1, 1, 2, 3, 3, 3, 3, 3, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 - }; - - // Scalar "fast pathing" IDCT. - static void idct(const jpgd_block_t* pSrc_ptr, uint8* pDst_ptr, int block_max_zag) - { - assert(block_max_zag >= 1); - assert(block_max_zag <= 64); - - if (block_max_zag <= 1) - { - int k = ((pSrc_ptr[0] + 4) >> 3) + 128; - k = CLAMP(k); - k = k | (k << 8); - k = k | (k << 16); - - for (int i = 8; i > 0; i--) - { - *(int*)&pDst_ptr[0] = k; - *(int*)&pDst_ptr[4] = k; - pDst_ptr += 8; - } - return; - } - - int temp[64]; - - const jpgd_block_t* pSrc = pSrc_ptr; - int* pTemp = temp; - - const uint8* pRow_tab = &s_idct_row_table[(block_max_zag - 1) * 8]; - int i; - for (i = 8; i > 0; i--, pRow_tab++) - { - switch (*pRow_tab) - { - case 0: Row<0>::idct(pTemp, pSrc); break; - case 1: Row<1>::idct(pTemp, pSrc); break; - case 2: Row<2>::idct(pTemp, pSrc); break; - case 3: Row<3>::idct(pTemp, pSrc); break; - case 4: Row<4>::idct(pTemp, pSrc); break; - case 5: Row<5>::idct(pTemp, pSrc); break; - case 6: Row<6>::idct(pTemp, pSrc); break; - case 7: Row<7>::idct(pTemp, pSrc); break; - case 8: Row<8>::idct(pTemp, pSrc); break; - } - - pSrc += 8; - pTemp += 8; - } - - pTemp = temp; - - const int nonzero_rows = s_idct_col_table[block_max_zag - 1]; - for (i = 8; i > 0; i--) - { - switch (nonzero_rows) - { - case 1: Col<1>::idct(pDst_ptr, pTemp); break; - case 2: Col<2>::idct(pDst_ptr, pTemp); break; - case 3: Col<3>::idct(pDst_ptr, pTemp); break; - case 4: Col<4>::idct(pDst_ptr, pTemp); break; - case 5: Col<5>::idct(pDst_ptr, pTemp); break; - case 6: Col<6>::idct(pDst_ptr, pTemp); break; - case 7: Col<7>::idct(pDst_ptr, pTemp); break; - case 8: Col<8>::idct(pDst_ptr, pTemp); break; - } - - pTemp++; - pDst_ptr++; - } - } - - // Retrieve one character from the input stream. - inline uint jpeg_decoder::get_char() - { - // Any bytes remaining in buffer? - if (!m_in_buf_left) - { - // Try to get more bytes. - prep_in_buffer(); - // Still nothing to get? - if (!m_in_buf_left) - { - // Pad the end of the stream with 0xFF 0xD9 (EOI marker) - int t = m_tem_flag; - m_tem_flag ^= 1; - if (t) - return 0xD9; - else - return 0xFF; - } - } - - uint c = *m_pIn_buf_ofs++; - m_in_buf_left--; - - return c; - } - - // Same as previous method, except can indicate if the character is a pad character or not. - inline uint jpeg_decoder::get_char(bool* pPadding_flag) - { - if (!m_in_buf_left) - { - prep_in_buffer(); - if (!m_in_buf_left) - { - *pPadding_flag = true; - int t = m_tem_flag; - m_tem_flag ^= 1; - if (t) - return 0xD9; - else - return 0xFF; - } - } - - *pPadding_flag = false; - - uint c = *m_pIn_buf_ofs++; - m_in_buf_left--; - - return c; - } - - // Inserts a previously retrieved character back into the input buffer. - inline void jpeg_decoder::stuff_char(uint8 q) - { - // This could write before the input buffer, but we've placed another array there. - *(--m_pIn_buf_ofs) = q; - m_in_buf_left++; - } - - // Retrieves one character from the input stream, but does not read past markers. Will continue to return 0xFF when a marker is encountered. - inline uint8 jpeg_decoder::get_octet() - { - bool padding_flag; - int c = get_char(&padding_flag); - - if (c == 0xFF) - { - if (padding_flag) - return 0xFF; - - c = get_char(&padding_flag); - if (padding_flag) - { - stuff_char(0xFF); - return 0xFF; - } - - if (c == 0x00) - return 0xFF; - else - { - stuff_char(static_cast<uint8>(c)); - stuff_char(0xFF); - return 0xFF; - } - } - - return static_cast<uint8>(c); - } - - // Retrieves a variable number of bits from the input stream. Does not recognize markers. - inline uint jpeg_decoder::get_bits(int num_bits) - { - if (!num_bits) - return 0; - - uint i = m_bit_buf >> (32 - num_bits); - - if ((m_bits_left -= num_bits) <= 0) - { - m_bit_buf <<= (num_bits += m_bits_left); - - uint c1 = get_char(); - uint c2 = get_char(); - m_bit_buf = (m_bit_buf & 0xFFFF0000) | (c1 << 8) | c2; - - m_bit_buf <<= -m_bits_left; - - m_bits_left += 16; - - assert(m_bits_left >= 0); - } - else - m_bit_buf <<= num_bits; - - return i; - } - - // Retrieves a variable number of bits from the input stream. Markers will not be read into the input bit buffer. Instead, an infinite number of all 1's will be returned when a marker is encountered. - inline uint jpeg_decoder::get_bits_no_markers(int num_bits) - { - if (!num_bits) - return 0; - - assert(num_bits <= 16); - - uint i = m_bit_buf >> (32 - num_bits); - - if ((m_bits_left -= num_bits) <= 0) - { - m_bit_buf <<= (num_bits += m_bits_left); - - if ((m_in_buf_left < 2) || (m_pIn_buf_ofs[0] == 0xFF) || (m_pIn_buf_ofs[1] == 0xFF)) - { - uint c1 = get_octet(); - uint c2 = get_octet(); - m_bit_buf |= (c1 << 8) | c2; - } - else - { - m_bit_buf |= ((uint)m_pIn_buf_ofs[0] << 8) | m_pIn_buf_ofs[1]; - m_in_buf_left -= 2; - m_pIn_buf_ofs += 2; - } - - m_bit_buf <<= -m_bits_left; - - m_bits_left += 16; - - assert(m_bits_left >= 0); - } - else - m_bit_buf <<= num_bits; - - return i; - } - - // Decodes a Huffman encoded symbol. - inline int jpeg_decoder::huff_decode(huff_tables* pH) - { - if (!pH) - stop_decoding(JPGD_DECODE_ERROR); - - int symbol; - // Check first 8-bits: do we have a complete symbol? - if ((symbol = pH->look_up[m_bit_buf >> 24]) < 0) - { - // Decode more bits, use a tree traversal to find symbol. - int ofs = 23; - do - { - unsigned int idx = -(int)(symbol + ((m_bit_buf >> ofs) & 1)); - - // This should never happen, but to be safe I'm turning these asserts into a run-time check. - if ((idx >= JPGD_HUFF_TREE_MAX_LENGTH) || (ofs < 0)) - stop_decoding(JPGD_DECODE_ERROR); - - symbol = pH->tree[idx]; - ofs--; - } while (symbol < 0); - - get_bits_no_markers(8 + (23 - ofs)); - } - else - { - assert(symbol < JPGD_HUFF_CODE_SIZE_MAX_LENGTH); - get_bits_no_markers(pH->code_size[symbol]); - } - - return symbol; - } - - // Decodes a Huffman encoded symbol. - inline int jpeg_decoder::huff_decode(huff_tables* pH, int& extra_bits) - { - int symbol; - - if (!pH) - stop_decoding(JPGD_DECODE_ERROR); - - // Check first 8-bits: do we have a complete symbol? - if ((symbol = pH->look_up2[m_bit_buf >> 24]) < 0) - { - // Use a tree traversal to find symbol. - int ofs = 23; - do - { - unsigned int idx = -(int)(symbol + ((m_bit_buf >> ofs) & 1)); - - // This should never happen, but to be safe I'm turning these asserts into a run-time check. - if ((idx >= JPGD_HUFF_TREE_MAX_LENGTH) || (ofs < 0)) - stop_decoding(JPGD_DECODE_ERROR); - - symbol = pH->tree[idx]; - ofs--; - } while (symbol < 0); - - get_bits_no_markers(8 + (23 - ofs)); - - extra_bits = get_bits_no_markers(symbol & 0xF); - } - else - { - if (symbol & 0x8000) - { - //get_bits_no_markers((symbol >> 8) & 31); - assert(((symbol >> 8) & 31) <= 15); - get_bits_no_markers((symbol >> 8) & 15); - extra_bits = symbol >> 16; - } - else - { - int code_size = (symbol >> 8) & 31; - int num_extra_bits = symbol & 0xF; - int bits = code_size + num_extra_bits; - - if (bits <= 16) - extra_bits = get_bits_no_markers(bits) & ((1 << num_extra_bits) - 1); - else - { - get_bits_no_markers(code_size); - extra_bits = get_bits_no_markers(num_extra_bits); - } - } - - symbol &= 0xFF; - } - - return symbol; - } - - // Tables and macro used to fully decode the DPCM differences. - static const int s_extend_test[16] = { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; - static const int s_extend_offset[16] = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 }; - //static const int s_extend_mask[] = { 0, (1 << 0), (1 << 1), (1 << 2), (1 << 3), (1 << 4), (1 << 5), (1 << 6), (1 << 7), (1 << 8), (1 << 9), (1 << 10), (1 << 11), (1 << 12), (1 << 13), (1 << 14), (1 << 15), (1 << 16) }; - -#define JPGD_HUFF_EXTEND(x, s) (((x) < s_extend_test[s & 15]) ? ((x) + s_extend_offset[s & 15]) : (x)) - - // Unconditionally frees all allocated m_blocks. - void jpeg_decoder::free_all_blocks() - { - m_pStream = nullptr; - for (mem_block* b = m_pMem_blocks; b; ) - { - mem_block* n = b->m_pNext; - jpgd_free(b); - b = n; - } - m_pMem_blocks = nullptr; - } - - // This method handles all errors. It will never return. - // It could easily be changed to use C++ exceptions. - JPGD_NORETURN void jpeg_decoder::stop_decoding(jpgd_status status) - { - m_error_code = status; - free_all_blocks(); - longjmp(m_jmp_state, status); - } - - void* jpeg_decoder::alloc(size_t nSize, bool zero) - { - nSize = (JPGD_MAX(nSize, 1) + 3) & ~3; - char* rv = nullptr; - for (mem_block* b = m_pMem_blocks; b; b = b->m_pNext) - { - if ((b->m_used_count + nSize) <= b->m_size) - { - rv = b->m_data + b->m_used_count; - b->m_used_count += nSize; - break; - } - } - if (!rv) - { - int capacity = JPGD_MAX(32768 - 256, ((int)nSize + 2047) & ~2047); - mem_block* b = (mem_block*)jpgd_malloc(sizeof(mem_block) + capacity); - if (!b) - { - stop_decoding(JPGD_NOTENOUGHMEM); - } - - b->m_pNext = m_pMem_blocks; - m_pMem_blocks = b; - b->m_used_count = nSize; - b->m_size = capacity; - rv = b->m_data; - } - if (zero) memset(rv, 0, nSize); - return rv; - } - - void jpeg_decoder::word_clear(void* p, uint16 c, uint n) - { - uint8* pD = (uint8*)p; - const uint8 l = c & 0xFF, h = (c >> 8) & 0xFF; - while (n) - { - pD[0] = l; - pD[1] = h; - pD += 2; - n--; - } - } - - // Refill the input buffer. - // This method will sit in a loop until (A) the buffer is full or (B) - // the stream's read() method reports and end of file condition. - void jpeg_decoder::prep_in_buffer() - { - m_in_buf_left = 0; - m_pIn_buf_ofs = m_in_buf; - - if (m_eof_flag) - return; - - do - { - int bytes_read = m_pStream->read(m_in_buf + m_in_buf_left, JPGD_IN_BUF_SIZE - m_in_buf_left, &m_eof_flag); - if (bytes_read == -1) - stop_decoding(JPGD_STREAM_READ); - - m_in_buf_left += bytes_read; - } while ((m_in_buf_left < JPGD_IN_BUF_SIZE) && (!m_eof_flag)); - - m_total_bytes_read += m_in_buf_left; - - // Pad the end of the block with M_EOI (prevents the decompressor from going off the rails if the stream is invalid). - // (This dates way back to when this decompressor was written in C/asm, and the all-asm Huffman decoder did some fancy things to increase perf.) - word_clear(m_pIn_buf_ofs + m_in_buf_left, 0xD9FF, 64); - } - - // Read a Huffman code table. - void jpeg_decoder::read_dht_marker() - { - int i, index, count; - uint8 huff_num[17]; - uint8 huff_val[256]; - - uint num_left = get_bits(16); - - if (num_left < 2) - stop_decoding(JPGD_BAD_DHT_MARKER); - - num_left -= 2; - - while (num_left) - { - index = get_bits(8); - - huff_num[0] = 0; - - count = 0; - - for (i = 1; i <= 16; i++) - { - huff_num[i] = static_cast<uint8>(get_bits(8)); - count += huff_num[i]; - } - - if (count > 255) - stop_decoding(JPGD_BAD_DHT_COUNTS); - - bool symbol_present[256]; - memset(symbol_present, 0, sizeof(symbol_present)); - - for (i = 0; i < count; i++) - { - const int s = get_bits(8); - - // Check for obviously bogus tables. - if (symbol_present[s]) - stop_decoding(JPGD_BAD_DHT_COUNTS); - - huff_val[i] = static_cast<uint8_t>(s); - symbol_present[s] = true; - } - - i = 1 + 16 + count; - - if (num_left < (uint)i) - stop_decoding(JPGD_BAD_DHT_MARKER); - - num_left -= i; - - if ((index & 0x10) > 0x10) - stop_decoding(JPGD_BAD_DHT_INDEX); - - index = (index & 0x0F) + ((index & 0x10) >> 4) * (JPGD_MAX_HUFF_TABLES >> 1); - - if (index >= JPGD_MAX_HUFF_TABLES) - stop_decoding(JPGD_BAD_DHT_INDEX); - - if (!m_huff_num[index]) - m_huff_num[index] = (uint8*)alloc(17); - - if (!m_huff_val[index]) - m_huff_val[index] = (uint8*)alloc(256); - - m_huff_ac[index] = (index & 0x10) != 0; - memcpy(m_huff_num[index], huff_num, 17); - memcpy(m_huff_val[index], huff_val, 256); - } - } - - // Read a quantization table. - void jpeg_decoder::read_dqt_marker() - { - int n, i, prec; - uint num_left; - uint temp; - - num_left = get_bits(16); - - if (num_left < 2) - stop_decoding(JPGD_BAD_DQT_MARKER); - - num_left -= 2; - - while (num_left) - { - n = get_bits(8); - prec = n >> 4; - n &= 0x0F; - - if (n >= JPGD_MAX_QUANT_TABLES) - stop_decoding(JPGD_BAD_DQT_TABLE); - - if (!m_quant[n]) - m_quant[n] = (jpgd_quant_t*)alloc(64 * sizeof(jpgd_quant_t)); - - // read quantization entries, in zag order - for (i = 0; i < 64; i++) - { - temp = get_bits(8); - - if (prec) - temp = (temp << 8) + get_bits(8); - - m_quant[n][i] = static_cast<jpgd_quant_t>(temp); - } - - i = 64 + 1; - - if (prec) - i += 64; - - if (num_left < (uint)i) - stop_decoding(JPGD_BAD_DQT_LENGTH); - - num_left -= i; - } - } - - // Read the start of frame (SOF) marker. - void jpeg_decoder::read_sof_marker() - { - int i; - uint num_left; - - num_left = get_bits(16); - - /* precision: sorry, only 8-bit precision is supported */ - if (get_bits(8) != 8) - stop_decoding(JPGD_BAD_PRECISION); - - m_image_y_size = get_bits(16); - - if ((m_image_y_size < 1) || (m_image_y_size > JPGD_MAX_HEIGHT)) - stop_decoding(JPGD_BAD_HEIGHT); - - m_image_x_size = get_bits(16); - - if ((m_image_x_size < 1) || (m_image_x_size > JPGD_MAX_WIDTH)) - stop_decoding(JPGD_BAD_WIDTH); - - m_comps_in_frame = get_bits(8); - - if (m_comps_in_frame > JPGD_MAX_COMPONENTS) - stop_decoding(JPGD_TOO_MANY_COMPONENTS); - - if (num_left != (uint)(m_comps_in_frame * 3 + 8)) - stop_decoding(JPGD_BAD_SOF_LENGTH); - - for (i = 0; i < m_comps_in_frame; i++) - { - m_comp_ident[i] = get_bits(8); - m_comp_h_samp[i] = get_bits(4); - m_comp_v_samp[i] = get_bits(4); - - if (!m_comp_h_samp[i] || !m_comp_v_samp[i] || (m_comp_h_samp[i] > 2) || (m_comp_v_samp[i] > 2)) - stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS); - - m_comp_quant[i] = get_bits(8); - if (m_comp_quant[i] >= JPGD_MAX_QUANT_TABLES) - stop_decoding(JPGD_DECODE_ERROR); - } - } - - // Used to skip unrecognized markers. - void jpeg_decoder::skip_variable_marker() - { - uint num_left; - - num_left = get_bits(16); - - if (num_left < 2) - stop_decoding(JPGD_BAD_VARIABLE_MARKER); - - num_left -= 2; - - while (num_left) - { - get_bits(8); - num_left--; - } - } - - // Read a define restart interval (DRI) marker. - void jpeg_decoder::read_dri_marker() - { - if (get_bits(16) != 4) - stop_decoding(JPGD_BAD_DRI_LENGTH); - - m_restart_interval = get_bits(16); - } - - // Read a start of scan (SOS) marker. - void jpeg_decoder::read_sos_marker() - { - uint num_left; - int i, ci, n, c, cc; - - num_left = get_bits(16); - - n = get_bits(8); - - m_comps_in_scan = n; - - num_left -= 3; - - if ((num_left != (uint)(n * 2 + 3)) || (n < 1) || (n > JPGD_MAX_COMPS_IN_SCAN)) - stop_decoding(JPGD_BAD_SOS_LENGTH); - - for (i = 0; i < n; i++) - { - cc = get_bits(8); - c = get_bits(8); - num_left -= 2; - - for (ci = 0; ci < m_comps_in_frame; ci++) - if (cc == m_comp_ident[ci]) - break; - - if (ci >= m_comps_in_frame) - stop_decoding(JPGD_BAD_SOS_COMP_ID); - - if (ci >= JPGD_MAX_COMPONENTS) - stop_decoding(JPGD_DECODE_ERROR); - - m_comp_list[i] = ci; - - m_comp_dc_tab[ci] = (c >> 4) & 15; - m_comp_ac_tab[ci] = (c & 15) + (JPGD_MAX_HUFF_TABLES >> 1); - - if (m_comp_dc_tab[ci] >= JPGD_MAX_HUFF_TABLES) - stop_decoding(JPGD_DECODE_ERROR); - - if (m_comp_ac_tab[ci] >= JPGD_MAX_HUFF_TABLES) - stop_decoding(JPGD_DECODE_ERROR); - } - - m_spectral_start = get_bits(8); - m_spectral_end = get_bits(8); - m_successive_high = get_bits(4); - m_successive_low = get_bits(4); - - if (!m_progressive_flag) - { - m_spectral_start = 0; - m_spectral_end = 63; - } - - num_left -= 3; - - /* read past whatever is num_left */ - while (num_left) - { - get_bits(8); - num_left--; - } - } - - // Finds the next marker. - int jpeg_decoder::next_marker() - { - uint c, bytes; - - bytes = 0; - - do - { - do - { - bytes++; - c = get_bits(8); - } while (c != 0xFF); - - do - { - c = get_bits(8); - } while (c == 0xFF); - - } while (c == 0); - - // If bytes > 0 here, there where extra bytes before the marker (not good). - - return c; - } - - // Process markers. Returns when an SOFx, SOI, EOI, or SOS marker is - // encountered. - int jpeg_decoder::process_markers() - { - int c; - - for (; ; ) - { - c = next_marker(); - - switch (c) - { - case M_SOF0: - case M_SOF1: - case M_SOF2: - case M_SOF3: - case M_SOF5: - case M_SOF6: - case M_SOF7: - // case M_JPG: - case M_SOF9: - case M_SOF10: - case M_SOF11: - case M_SOF13: - case M_SOF14: - case M_SOF15: - case M_SOI: - case M_EOI: - case M_SOS: - { - return c; - } - case M_DHT: - { - read_dht_marker(); - break; - } - // No arithmitic support - dumb patents! - case M_DAC: - { - stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT); - break; - } - case M_DQT: - { - read_dqt_marker(); - break; - } - case M_DRI: - { - read_dri_marker(); - break; - } - //case M_APP0: /* no need to read the JFIF marker */ - case M_JPG: - case M_RST0: /* no parameters */ - case M_RST1: - case M_RST2: - case M_RST3: - case M_RST4: - case M_RST5: - case M_RST6: - case M_RST7: - case M_TEM: - { - stop_decoding(JPGD_UNEXPECTED_MARKER); - break; - } - default: /* must be DNL, DHP, EXP, APPn, JPGn, COM, or RESn or APP0 */ - { - skip_variable_marker(); - break; - } - } - } - } - - // Finds the start of image (SOI) marker. - void jpeg_decoder::locate_soi_marker() - { - uint lastchar, thischar; - uint bytesleft; - - lastchar = get_bits(8); - - thischar = get_bits(8); - - /* ok if it's a normal JPEG file without a special header */ - - if ((lastchar == 0xFF) && (thischar == M_SOI)) - return; - - bytesleft = 4096; - - for (; ; ) - { - if (--bytesleft == 0) - stop_decoding(JPGD_NOT_JPEG); - - lastchar = thischar; - - thischar = get_bits(8); - - if (lastchar == 0xFF) - { - if (thischar == M_SOI) - break; - else if (thischar == M_EOI) // get_bits will keep returning M_EOI if we read past the end - stop_decoding(JPGD_NOT_JPEG); - } - } - - // Check the next character after marker: if it's not 0xFF, it can't be the start of the next marker, so the file is bad. - thischar = (m_bit_buf >> 24) & 0xFF; - - if (thischar != 0xFF) - stop_decoding(JPGD_NOT_JPEG); - } - - // Find a start of frame (SOF) marker. - void jpeg_decoder::locate_sof_marker() - { - locate_soi_marker(); - - int c = process_markers(); - - switch (c) - { - case M_SOF2: - { - m_progressive_flag = JPGD_TRUE; - read_sof_marker(); - break; - } - case M_SOF0: /* baseline DCT */ - case M_SOF1: /* extended sequential DCT */ - { - read_sof_marker(); - break; - } - case M_SOF9: /* Arithmitic coding */ - { - stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT); - break; - } - default: - { - stop_decoding(JPGD_UNSUPPORTED_MARKER); - break; - } - } - } - - // Find a start of scan (SOS) marker. - int jpeg_decoder::locate_sos_marker() - { - int c; - - c = process_markers(); - - if (c == M_EOI) - return JPGD_FALSE; - else if (c != M_SOS) - stop_decoding(JPGD_UNEXPECTED_MARKER); - - read_sos_marker(); - - return JPGD_TRUE; - } - - // Reset everything to default/uninitialized state. - void jpeg_decoder::init(jpeg_decoder_stream* pStream, uint32_t flags) - { - m_flags = flags; - m_pMem_blocks = nullptr; - m_error_code = JPGD_SUCCESS; - m_ready_flag = false; - m_image_x_size = m_image_y_size = 0; - m_pStream = pStream; - m_progressive_flag = JPGD_FALSE; - - memset(m_huff_ac, 0, sizeof(m_huff_ac)); - memset(m_huff_num, 0, sizeof(m_huff_num)); - memset(m_huff_val, 0, sizeof(m_huff_val)); - memset(m_quant, 0, sizeof(m_quant)); - - m_scan_type = 0; - m_comps_in_frame = 0; - - memset(m_comp_h_samp, 0, sizeof(m_comp_h_samp)); - memset(m_comp_v_samp, 0, sizeof(m_comp_v_samp)); - memset(m_comp_quant, 0, sizeof(m_comp_quant)); - memset(m_comp_ident, 0, sizeof(m_comp_ident)); - memset(m_comp_h_blocks, 0, sizeof(m_comp_h_blocks)); - memset(m_comp_v_blocks, 0, sizeof(m_comp_v_blocks)); - - m_comps_in_scan = 0; - memset(m_comp_list, 0, sizeof(m_comp_list)); - memset(m_comp_dc_tab, 0, sizeof(m_comp_dc_tab)); - memset(m_comp_ac_tab, 0, sizeof(m_comp_ac_tab)); - - m_spectral_start = 0; - m_spectral_end = 0; - m_successive_low = 0; - m_successive_high = 0; - m_max_mcu_x_size = 0; - m_max_mcu_y_size = 0; - m_blocks_per_mcu = 0; - m_max_blocks_per_row = 0; - m_mcus_per_row = 0; - m_mcus_per_col = 0; - - memset(m_mcu_org, 0, sizeof(m_mcu_org)); - - m_total_lines_left = 0; - m_mcu_lines_left = 0; - m_num_buffered_scanlines = 0; - m_real_dest_bytes_per_scan_line = 0; - m_dest_bytes_per_scan_line = 0; - m_dest_bytes_per_pixel = 0; - - memset(m_pHuff_tabs, 0, sizeof(m_pHuff_tabs)); - - memset(m_dc_coeffs, 0, sizeof(m_dc_coeffs)); - memset(m_ac_coeffs, 0, sizeof(m_ac_coeffs)); - memset(m_block_y_mcu, 0, sizeof(m_block_y_mcu)); - - m_eob_run = 0; - - m_pIn_buf_ofs = m_in_buf; - m_in_buf_left = 0; - m_eof_flag = false; - m_tem_flag = 0; - - memset(m_in_buf_pad_start, 0, sizeof(m_in_buf_pad_start)); - memset(m_in_buf, 0, sizeof(m_in_buf)); - memset(m_in_buf_pad_end, 0, sizeof(m_in_buf_pad_end)); - - m_restart_interval = 0; - m_restarts_left = 0; - m_next_restart_num = 0; - - m_max_mcus_per_row = 0; - m_max_blocks_per_mcu = 0; - m_max_mcus_per_col = 0; - - memset(m_last_dc_val, 0, sizeof(m_last_dc_val)); - m_pMCU_coefficients = nullptr; - m_pSample_buf = nullptr; - m_pSample_buf_prev = nullptr; - m_sample_buf_prev_valid = false; - - m_total_bytes_read = 0; - - m_pScan_line_0 = nullptr; - m_pScan_line_1 = nullptr; - - // Ready the input buffer. - prep_in_buffer(); - - // Prime the bit buffer. - m_bits_left = 16; - m_bit_buf = 0; - - get_bits(16); - get_bits(16); - - for (int i = 0; i < JPGD_MAX_BLOCKS_PER_MCU; i++) - m_mcu_block_max_zag[i] = 64; - } - -#define SCALEBITS 16 -#define ONE_HALF ((int) 1 << (SCALEBITS-1)) -#define FIX(x) ((int) ((x) * (1L<<SCALEBITS) + 0.5f)) - - // Create a few tables that allow us to quickly convert YCbCr to RGB. - void jpeg_decoder::create_look_ups() - { - for (int i = 0; i <= 255; i++) - { - int k = i - 128; - m_crr[i] = (FIX(1.40200f) * k + ONE_HALF) >> SCALEBITS; - m_cbb[i] = (FIX(1.77200f) * k + ONE_HALF) >> SCALEBITS; - m_crg[i] = (-FIX(0.71414f)) * k; - m_cbg[i] = (-FIX(0.34414f)) * k + ONE_HALF; - } - } - - // This method throws back into the stream any bytes that where read - // into the bit buffer during initial marker scanning. - void jpeg_decoder::fix_in_buffer() - { - // In case any 0xFF's where pulled into the buffer during marker scanning. - assert((m_bits_left & 7) == 0); - - if (m_bits_left == 16) - stuff_char((uint8)(m_bit_buf & 0xFF)); - - if (m_bits_left >= 8) - stuff_char((uint8)((m_bit_buf >> 8) & 0xFF)); - - stuff_char((uint8)((m_bit_buf >> 16) & 0xFF)); - stuff_char((uint8)((m_bit_buf >> 24) & 0xFF)); - - m_bits_left = 16; - get_bits_no_markers(16); - get_bits_no_markers(16); - } - - void jpeg_decoder::transform_mcu(int mcu_row) - { - jpgd_block_t* pSrc_ptr = m_pMCU_coefficients; - if (mcu_row * m_blocks_per_mcu >= m_max_blocks_per_row) - stop_decoding(JPGD_DECODE_ERROR); - - uint8* pDst_ptr = m_pSample_buf + mcu_row * m_blocks_per_mcu * 64; - - for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) - { - idct(pSrc_ptr, pDst_ptr, m_mcu_block_max_zag[mcu_block]); - pSrc_ptr += 64; - pDst_ptr += 64; - } - } - - // Loads and dequantizes the next row of (already decoded) coefficients. - // Progressive images only. - void jpeg_decoder::load_next_row() - { - int i; - jpgd_block_t* p; - jpgd_quant_t* q; - int mcu_row, mcu_block, row_block = 0; - int component_num, component_id; - int block_x_mcu[JPGD_MAX_COMPONENTS]; - - memset(block_x_mcu, 0, JPGD_MAX_COMPONENTS * sizeof(int)); - - for (mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) - { - int block_x_mcu_ofs = 0, block_y_mcu_ofs = 0; - - for (mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) - { - component_id = m_mcu_org[mcu_block]; - if (m_comp_quant[component_id] >= JPGD_MAX_QUANT_TABLES) - stop_decoding(JPGD_DECODE_ERROR); - - q = m_quant[m_comp_quant[component_id]]; - - p = m_pMCU_coefficients + 64 * mcu_block; - - jpgd_block_t* pAC = coeff_buf_getp(m_ac_coeffs[component_id], block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs); - jpgd_block_t* pDC = coeff_buf_getp(m_dc_coeffs[component_id], block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs); - p[0] = pDC[0]; - memcpy(&p[1], &pAC[1], 63 * sizeof(jpgd_block_t)); - - for (i = 63; i > 0; i--) - if (p[g_ZAG[i]]) - break; - - m_mcu_block_max_zag[mcu_block] = i + 1; - - for (; i >= 0; i--) - if (p[g_ZAG[i]]) - p[g_ZAG[i]] = static_cast<jpgd_block_t>(p[g_ZAG[i]] * q[i]); - - row_block++; - - if (m_comps_in_scan == 1) - block_x_mcu[component_id]++; - else - { - if (++block_x_mcu_ofs == m_comp_h_samp[component_id]) - { - block_x_mcu_ofs = 0; - - if (++block_y_mcu_ofs == m_comp_v_samp[component_id]) - { - block_y_mcu_ofs = 0; - - block_x_mcu[component_id] += m_comp_h_samp[component_id]; - } - } - } - } - - transform_mcu(mcu_row); - } - - if (m_comps_in_scan == 1) - m_block_y_mcu[m_comp_list[0]]++; - else - { - for (component_num = 0; component_num < m_comps_in_scan; component_num++) - { - component_id = m_comp_list[component_num]; - - m_block_y_mcu[component_id] += m_comp_v_samp[component_id]; - } - } - } - - // Restart interval processing. - void jpeg_decoder::process_restart() - { - int i; - int c = 0; - - // Align to a byte boundry - // FIXME: Is this really necessary? get_bits_no_markers() never reads in markers! - //get_bits_no_markers(m_bits_left & 7); - - // Let's scan a little bit to find the marker, but not _too_ far. - // 1536 is a "fudge factor" that determines how much to scan. - for (i = 1536; i > 0; i--) - if (get_char() == 0xFF) - break; - - if (i == 0) - stop_decoding(JPGD_BAD_RESTART_MARKER); - - for (; i > 0; i--) - if ((c = get_char()) != 0xFF) - break; - - if (i == 0) - stop_decoding(JPGD_BAD_RESTART_MARKER); - - // Is it the expected marker? If not, something bad happened. - if (c != (m_next_restart_num + M_RST0)) - stop_decoding(JPGD_BAD_RESTART_MARKER); - - // Reset each component's DC prediction values. - memset(&m_last_dc_val, 0, m_comps_in_frame * sizeof(uint)); - - m_eob_run = 0; - - m_restarts_left = m_restart_interval; - - m_next_restart_num = (m_next_restart_num + 1) & 7; - - // Get the bit buffer going again... - - m_bits_left = 16; - get_bits_no_markers(16); - get_bits_no_markers(16); - } - - static inline int dequantize_ac(int c, int q) { c *= q; return c; } - - // Decodes and dequantizes the next row of coefficients. - void jpeg_decoder::decode_next_row() - { - int row_block = 0; - - for (int mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) - { - if ((m_restart_interval) && (m_restarts_left == 0)) - process_restart(); - - jpgd_block_t* p = m_pMCU_coefficients; - for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++, p += 64) - { - int component_id = m_mcu_org[mcu_block]; - if (m_comp_quant[component_id] >= JPGD_MAX_QUANT_TABLES) - stop_decoding(JPGD_DECODE_ERROR); - - jpgd_quant_t* q = m_quant[m_comp_quant[component_id]]; - - int r, s; - s = huff_decode(m_pHuff_tabs[m_comp_dc_tab[component_id]], r); - if (s >= 16) - stop_decoding(JPGD_DECODE_ERROR); - - s = JPGD_HUFF_EXTEND(r, s); - - m_last_dc_val[component_id] = (s += m_last_dc_val[component_id]); - - p[0] = static_cast<jpgd_block_t>(s * q[0]); - - int prev_num_set = m_mcu_block_max_zag[mcu_block]; - - huff_tables* pH = m_pHuff_tabs[m_comp_ac_tab[component_id]]; - - int k; - for (k = 1; k < 64; k++) - { - int extra_bits; - s = huff_decode(pH, extra_bits); - - r = s >> 4; - s &= 15; - - if (s) - { - if (r) - { - if ((k + r) > 63) - stop_decoding(JPGD_DECODE_ERROR); - - if (k < prev_num_set) - { - int n = JPGD_MIN(r, prev_num_set - k); - int kt = k; - while (n--) - p[g_ZAG[kt++]] = 0; - } - - k += r; - } - - s = JPGD_HUFF_EXTEND(extra_bits, s); - - if (k >= 64) - stop_decoding(JPGD_DECODE_ERROR); - - p[g_ZAG[k]] = static_cast<jpgd_block_t>(dequantize_ac(s, q[k])); //s * q[k]; - } - else - { - if (r == 15) - { - if ((k + 16) > 64) - stop_decoding(JPGD_DECODE_ERROR); - - if (k < prev_num_set) - { - int n = JPGD_MIN(16, prev_num_set - k); - int kt = k; - while (n--) - { - if (kt > 63) - stop_decoding(JPGD_DECODE_ERROR); - p[g_ZAG[kt++]] = 0; - } - } - - k += 16 - 1; // - 1 because the loop counter is k - - if (p[g_ZAG[k & 63]] != 0) - stop_decoding(JPGD_DECODE_ERROR); - } - else - break; - } - } - - if (k < prev_num_set) - { - int kt = k; - while (kt < prev_num_set) - p[g_ZAG[kt++]] = 0; - } - - m_mcu_block_max_zag[mcu_block] = k; - - row_block++; - } - - transform_mcu(mcu_row); - - m_restarts_left--; - } - } - - // YCbCr H1V1 (1x1:1:1, 3 m_blocks per MCU) to RGB - void jpeg_decoder::H1V1Convert() - { - int row = m_max_mcu_y_size - m_mcu_lines_left; - uint8* d = m_pScan_line_0; - uint8* s = m_pSample_buf + row * 8; - - for (int i = m_max_mcus_per_row; i > 0; i--) - { - for (int j = 0; j < 8; j++) - { - int y = s[j]; - int cb = s[64 + j]; - int cr = s[128 + j]; - - d[0] = clamp(y + m_crr[cr]); - d[1] = clamp(y + ((m_crg[cr] + m_cbg[cb]) >> 16)); - d[2] = clamp(y + m_cbb[cb]); - d[3] = 255; - - d += 4; - } - - s += 64 * 3; - } - } - - // YCbCr H2V1 (2x1:1:1, 4 m_blocks per MCU) to RGB - void jpeg_decoder::H2V1Convert() - { - int row = m_max_mcu_y_size - m_mcu_lines_left; - uint8* d0 = m_pScan_line_0; - uint8* y = m_pSample_buf + row * 8; - uint8* c = m_pSample_buf + 2 * 64 + row * 8; - - for (int i = m_max_mcus_per_row; i > 0; i--) - { - for (int l = 0; l < 2; l++) - { - for (int j = 0; j < 4; j++) - { - int cb = c[0]; - int cr = c[64]; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - int yy = y[j << 1]; - d0[0] = clamp(yy + rc); - d0[1] = clamp(yy + gc); - d0[2] = clamp(yy + bc); - d0[3] = 255; - - yy = y[(j << 1) + 1]; - d0[4] = clamp(yy + rc); - d0[5] = clamp(yy + gc); - d0[6] = clamp(yy + bc); - d0[7] = 255; - - d0 += 8; - - c++; - } - y += 64; - } - - y += 64 * 4 - 64 * 2; - c += 64 * 4 - 8; - } - } - - // YCbCr H2V1 (2x1:1:1, 4 m_blocks per MCU) to RGB - void jpeg_decoder::H2V1ConvertFiltered() - { - const uint BLOCKS_PER_MCU = 4; - int row = m_max_mcu_y_size - m_mcu_lines_left; - uint8* d0 = m_pScan_line_0; - - const int half_image_x_size = (m_image_x_size >> 1) - 1; - const int row_x8 = row * 8; - - for (int x = 0; x < m_image_x_size; x++) - { - int y = m_pSample_buf[check_sample_buf_ofs((x >> 4) * BLOCKS_PER_MCU * 64 + ((x & 8) ? 64 : 0) + (x & 7) + row_x8)]; - - int c_x0 = (x - 1) >> 1; - int c_x1 = JPGD_MIN(c_x0 + 1, half_image_x_size); - c_x0 = JPGD_MAX(c_x0, 0); - - int a = (c_x0 >> 3) * BLOCKS_PER_MCU * 64 + (c_x0 & 7) + row_x8 + 128; - int cb0 = m_pSample_buf[check_sample_buf_ofs(a)]; - int cr0 = m_pSample_buf[check_sample_buf_ofs(a + 64)]; - - int b = (c_x1 >> 3) * BLOCKS_PER_MCU * 64 + (c_x1 & 7) + row_x8 + 128; - int cb1 = m_pSample_buf[check_sample_buf_ofs(b)]; - int cr1 = m_pSample_buf[check_sample_buf_ofs(b + 64)]; - - int w0 = (x & 1) ? 3 : 1; - int w1 = (x & 1) ? 1 : 3; - - int cb = (cb0 * w0 + cb1 * w1 + 2) >> 2; - int cr = (cr0 * w0 + cr1 * w1 + 2) >> 2; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - d0[0] = clamp(y + rc); - d0[1] = clamp(y + gc); - d0[2] = clamp(y + bc); - d0[3] = 255; - - d0 += 4; - } - } - - // YCbCr H2V1 (1x2:1:1, 4 m_blocks per MCU) to RGB - void jpeg_decoder::H1V2Convert() - { - int row = m_max_mcu_y_size - m_mcu_lines_left; - uint8* d0 = m_pScan_line_0; - uint8* d1 = m_pScan_line_1; - uint8* y; - uint8* c; - - if (row < 8) - y = m_pSample_buf + row * 8; - else - y = m_pSample_buf + 64 * 1 + (row & 7) * 8; - - c = m_pSample_buf + 64 * 2 + (row >> 1) * 8; - - for (int i = m_max_mcus_per_row; i > 0; i--) - { - for (int j = 0; j < 8; j++) - { - int cb = c[0 + j]; - int cr = c[64 + j]; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - int yy = y[j]; - d0[0] = clamp(yy + rc); - d0[1] = clamp(yy + gc); - d0[2] = clamp(yy + bc); - d0[3] = 255; - - yy = y[8 + j]; - d1[0] = clamp(yy + rc); - d1[1] = clamp(yy + gc); - d1[2] = clamp(yy + bc); - d1[3] = 255; - - d0 += 4; - d1 += 4; - } - - y += 64 * 4; - c += 64 * 4; - } - } - - // YCbCr H2V1 (1x2:1:1, 4 m_blocks per MCU) to RGB - void jpeg_decoder::H1V2ConvertFiltered() - { - const uint BLOCKS_PER_MCU = 4; - int y = m_image_y_size - m_total_lines_left; - int row = y & 15; - - const int half_image_y_size = (m_image_y_size >> 1) - 1; - - uint8* d0 = m_pScan_line_0; - - const int w0 = (row & 1) ? 3 : 1; - const int w1 = (row & 1) ? 1 : 3; - - int c_y0 = (y - 1) >> 1; - int c_y1 = JPGD_MIN(c_y0 + 1, half_image_y_size); - - const uint8_t* p_YSamples = m_pSample_buf; - const uint8_t* p_C0Samples = m_pSample_buf; - if ((c_y0 >= 0) && (((row & 15) == 0) || ((row & 15) == 15)) && (m_total_lines_left > 1)) - { - assert(y > 0); - assert(m_sample_buf_prev_valid); - - if ((row & 15) == 15) - p_YSamples = m_pSample_buf_prev; - - p_C0Samples = m_pSample_buf_prev; - } - - const int y_sample_base_ofs = ((row & 8) ? 64 : 0) + (row & 7) * 8; - const int y0_base = (c_y0 & 7) * 8 + 128; - const int y1_base = (c_y1 & 7) * 8 + 128; - - for (int x = 0; x < m_image_x_size; x++) - { - const int base_ofs = (x >> 3) * BLOCKS_PER_MCU * 64 + (x & 7); - - int y_sample = p_YSamples[check_sample_buf_ofs(base_ofs + y_sample_base_ofs)]; - - int a = base_ofs + y0_base; - int cb0_sample = p_C0Samples[check_sample_buf_ofs(a)]; - int cr0_sample = p_C0Samples[check_sample_buf_ofs(a + 64)]; - - int b = base_ofs + y1_base; - int cb1_sample = m_pSample_buf[check_sample_buf_ofs(b)]; - int cr1_sample = m_pSample_buf[check_sample_buf_ofs(b + 64)]; - - int cb = (cb0_sample * w0 + cb1_sample * w1 + 2) >> 2; - int cr = (cr0_sample * w0 + cr1_sample * w1 + 2) >> 2; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - d0[0] = clamp(y_sample + rc); - d0[1] = clamp(y_sample + gc); - d0[2] = clamp(y_sample + bc); - d0[3] = 255; - - d0 += 4; - } - } - - // YCbCr H2V2 (2x2:1:1, 6 m_blocks per MCU) to RGB - void jpeg_decoder::H2V2Convert() - { - int row = m_max_mcu_y_size - m_mcu_lines_left; - uint8* d0 = m_pScan_line_0; - uint8* d1 = m_pScan_line_1; - uint8* y; - uint8* c; - - if (row < 8) - y = m_pSample_buf + row * 8; - else - y = m_pSample_buf + 64 * 2 + (row & 7) * 8; - - c = m_pSample_buf + 64 * 4 + (row >> 1) * 8; - - for (int i = m_max_mcus_per_row; i > 0; i--) - { - for (int l = 0; l < 2; l++) - { - for (int j = 0; j < 8; j += 2) - { - int cb = c[0]; - int cr = c[64]; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - int yy = y[j]; - d0[0] = clamp(yy + rc); - d0[1] = clamp(yy + gc); - d0[2] = clamp(yy + bc); - d0[3] = 255; - - yy = y[j + 1]; - d0[4] = clamp(yy + rc); - d0[5] = clamp(yy + gc); - d0[6] = clamp(yy + bc); - d0[7] = 255; - - yy = y[j + 8]; - d1[0] = clamp(yy + rc); - d1[1] = clamp(yy + gc); - d1[2] = clamp(yy + bc); - d1[3] = 255; - - yy = y[j + 8 + 1]; - d1[4] = clamp(yy + rc); - d1[5] = clamp(yy + gc); - d1[6] = clamp(yy + bc); - d1[7] = 255; - - d0 += 8; - d1 += 8; - - c++; - } - y += 64; - } - - y += 64 * 6 - 64 * 2; - c += 64 * 6 - 8; - } - } - - uint32_t jpeg_decoder::H2V2ConvertFiltered() - { - const uint BLOCKS_PER_MCU = 6; - int y = m_image_y_size - m_total_lines_left; - int row = y & 15; - - const int half_image_y_size = (m_image_y_size >> 1) - 1; - - uint8* d0 = m_pScan_line_0; - - int c_y0 = (y - 1) >> 1; - int c_y1 = JPGD_MIN(c_y0 + 1, half_image_y_size); - - const uint8_t* p_YSamples = m_pSample_buf; - const uint8_t* p_C0Samples = m_pSample_buf; - if ((c_y0 >= 0) && (((row & 15) == 0) || ((row & 15) == 15)) && (m_total_lines_left > 1)) - { - assert(y > 0); - assert(m_sample_buf_prev_valid); - - if ((row & 15) == 15) - p_YSamples = m_pSample_buf_prev; - - p_C0Samples = m_pSample_buf_prev; - } - - const int y_sample_base_ofs = ((row & 8) ? 128 : 0) + (row & 7) * 8; - const int y0_base = (c_y0 & 7) * 8 + 256; - const int y1_base = (c_y1 & 7) * 8 + 256; - - const int half_image_x_size = (m_image_x_size >> 1) - 1; - - static const uint8_t s_muls[2][2][4] = - { - { { 1, 3, 3, 9 }, { 3, 9, 1, 3 }, }, - { { 3, 1, 9, 3 }, { 9, 3, 3, 1 } } - }; - - if (((row & 15) >= 1) && ((row & 15) <= 14)) - { - assert((row & 1) == 1); - assert(((y + 1 - 1) >> 1) == c_y0); - - assert(p_YSamples == m_pSample_buf); - assert(p_C0Samples == m_pSample_buf); - - uint8* d1 = m_pScan_line_1; - const int y_sample_base_ofs1 = (((row + 1) & 8) ? 128 : 0) + ((row + 1) & 7) * 8; - - for (int x = 0; x < m_image_x_size; x++) - { - int k = (x >> 4) * BLOCKS_PER_MCU * 64 + ((x & 8) ? 64 : 0) + (x & 7); - int y_sample0 = p_YSamples[check_sample_buf_ofs(k + y_sample_base_ofs)]; - int y_sample1 = p_YSamples[check_sample_buf_ofs(k + y_sample_base_ofs1)]; - - int c_x0 = (x - 1) >> 1; - int c_x1 = JPGD_MIN(c_x0 + 1, half_image_x_size); - c_x0 = JPGD_MAX(c_x0, 0); - - int a = (c_x0 >> 3) * BLOCKS_PER_MCU * 64 + (c_x0 & 7); - int cb00_sample = p_C0Samples[check_sample_buf_ofs(a + y0_base)]; - int cr00_sample = p_C0Samples[check_sample_buf_ofs(a + y0_base + 64)]; - - int cb01_sample = m_pSample_buf[check_sample_buf_ofs(a + y1_base)]; - int cr01_sample = m_pSample_buf[check_sample_buf_ofs(a + y1_base + 64)]; - - int b = (c_x1 >> 3) * BLOCKS_PER_MCU * 64 + (c_x1 & 7); - int cb10_sample = p_C0Samples[check_sample_buf_ofs(b + y0_base)]; - int cr10_sample = p_C0Samples[check_sample_buf_ofs(b + y0_base + 64)]; - - int cb11_sample = m_pSample_buf[check_sample_buf_ofs(b + y1_base)]; - int cr11_sample = m_pSample_buf[check_sample_buf_ofs(b + y1_base + 64)]; - - { - const uint8_t* pMuls = &s_muls[row & 1][x & 1][0]; - int cb = (cb00_sample * pMuls[0] + cb01_sample * pMuls[1] + cb10_sample * pMuls[2] + cb11_sample * pMuls[3] + 8) >> 4; - int cr = (cr00_sample * pMuls[0] + cr01_sample * pMuls[1] + cr10_sample * pMuls[2] + cr11_sample * pMuls[3] + 8) >> 4; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - d0[0] = clamp(y_sample0 + rc); - d0[1] = clamp(y_sample0 + gc); - d0[2] = clamp(y_sample0 + bc); - d0[3] = 255; - - d0 += 4; - } - - { - const uint8_t* pMuls = &s_muls[(row + 1) & 1][x & 1][0]; - int cb = (cb00_sample * pMuls[0] + cb01_sample * pMuls[1] + cb10_sample * pMuls[2] + cb11_sample * pMuls[3] + 8) >> 4; - int cr = (cr00_sample * pMuls[0] + cr01_sample * pMuls[1] + cr10_sample * pMuls[2] + cr11_sample * pMuls[3] + 8) >> 4; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - d1[0] = clamp(y_sample1 + rc); - d1[1] = clamp(y_sample1 + gc); - d1[2] = clamp(y_sample1 + bc); - d1[3] = 255; - - d1 += 4; - } - - if (((x & 1) == 1) && (x < m_image_x_size - 1)) - { - const int nx = x + 1; - assert(c_x0 == (nx - 1) >> 1); - - k = (nx >> 4) * BLOCKS_PER_MCU * 64 + ((nx & 8) ? 64 : 0) + (nx & 7); - y_sample0 = p_YSamples[check_sample_buf_ofs(k + y_sample_base_ofs)]; - y_sample1 = p_YSamples[check_sample_buf_ofs(k + y_sample_base_ofs1)]; - - { - const uint8_t* pMuls = &s_muls[row & 1][nx & 1][0]; - int cb = (cb00_sample * pMuls[0] + cb01_sample * pMuls[1] + cb10_sample * pMuls[2] + cb11_sample * pMuls[3] + 8) >> 4; - int cr = (cr00_sample * pMuls[0] + cr01_sample * pMuls[1] + cr10_sample * pMuls[2] + cr11_sample * pMuls[3] + 8) >> 4; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - d0[0] = clamp(y_sample0 + rc); - d0[1] = clamp(y_sample0 + gc); - d0[2] = clamp(y_sample0 + bc); - d0[3] = 255; - - d0 += 4; - } - - { - const uint8_t* pMuls = &s_muls[(row + 1) & 1][nx & 1][0]; - int cb = (cb00_sample * pMuls[0] + cb01_sample * pMuls[1] + cb10_sample * pMuls[2] + cb11_sample * pMuls[3] + 8) >> 4; - int cr = (cr00_sample * pMuls[0] + cr01_sample * pMuls[1] + cr10_sample * pMuls[2] + cr11_sample * pMuls[3] + 8) >> 4; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - d1[0] = clamp(y_sample1 + rc); - d1[1] = clamp(y_sample1 + gc); - d1[2] = clamp(y_sample1 + bc); - d1[3] = 255; - - d1 += 4; - } - - ++x; - } - } - - return 2; - } - else - { - for (int x = 0; x < m_image_x_size; x++) - { - int y_sample = p_YSamples[check_sample_buf_ofs((x >> 4) * BLOCKS_PER_MCU * 64 + ((x & 8) ? 64 : 0) + (x & 7) + y_sample_base_ofs)]; - - int c_x0 = (x - 1) >> 1; - int c_x1 = JPGD_MIN(c_x0 + 1, half_image_x_size); - c_x0 = JPGD_MAX(c_x0, 0); - - int a = (c_x0 >> 3) * BLOCKS_PER_MCU * 64 + (c_x0 & 7); - int cb00_sample = p_C0Samples[check_sample_buf_ofs(a + y0_base)]; - int cr00_sample = p_C0Samples[check_sample_buf_ofs(a + y0_base + 64)]; - - int cb01_sample = m_pSample_buf[check_sample_buf_ofs(a + y1_base)]; - int cr01_sample = m_pSample_buf[check_sample_buf_ofs(a + y1_base + 64)]; - - int b = (c_x1 >> 3) * BLOCKS_PER_MCU * 64 + (c_x1 & 7); - int cb10_sample = p_C0Samples[check_sample_buf_ofs(b + y0_base)]; - int cr10_sample = p_C0Samples[check_sample_buf_ofs(b + y0_base + 64)]; - - int cb11_sample = m_pSample_buf[check_sample_buf_ofs(b + y1_base)]; - int cr11_sample = m_pSample_buf[check_sample_buf_ofs(b + y1_base + 64)]; - - const uint8_t* pMuls = &s_muls[row & 1][x & 1][0]; - int cb = (cb00_sample * pMuls[0] + cb01_sample * pMuls[1] + cb10_sample * pMuls[2] + cb11_sample * pMuls[3] + 8) >> 4; - int cr = (cr00_sample * pMuls[0] + cr01_sample * pMuls[1] + cr10_sample * pMuls[2] + cr11_sample * pMuls[3] + 8) >> 4; - - int rc = m_crr[cr]; - int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); - int bc = m_cbb[cb]; - - d0[0] = clamp(y_sample + rc); - d0[1] = clamp(y_sample + gc); - d0[2] = clamp(y_sample + bc); - d0[3] = 255; - - d0 += 4; - } - - return 1; - } - } - - // Y (1 block per MCU) to 8-bit grayscale - void jpeg_decoder::gray_convert() - { - int row = m_max_mcu_y_size - m_mcu_lines_left; - uint8* d = m_pScan_line_0; - uint8* s = m_pSample_buf + row * 8; - - for (int i = m_max_mcus_per_row; i > 0; i--) - { - *(uint*)d = *(uint*)s; - *(uint*)(&d[4]) = *(uint*)(&s[4]); - - s += 64; - d += 8; - } - } - - // Find end of image (EOI) marker, so we can return to the user the exact size of the input stream. - void jpeg_decoder::find_eoi() - { - if (!m_progressive_flag) - { - // Attempt to read the EOI marker. - //get_bits_no_markers(m_bits_left & 7); - - // Prime the bit buffer - m_bits_left = 16; - get_bits(16); - get_bits(16); - - // The next marker _should_ be EOI - process_markers(); - } - - m_total_bytes_read -= m_in_buf_left; - } - - int jpeg_decoder::decode_next_mcu_row() - { - if (setjmp(m_jmp_state)) - return JPGD_FAILED; - - const bool chroma_y_filtering = (m_flags & cFlagLinearChromaFiltering) && ((m_scan_type == JPGD_YH2V2) || (m_scan_type == JPGD_YH1V2)) && (m_image_x_size >= 2) && (m_image_y_size >= 2); - if (chroma_y_filtering) - { - std::swap(m_pSample_buf, m_pSample_buf_prev); - - m_sample_buf_prev_valid = true; - } - - if (m_progressive_flag) - load_next_row(); - else - decode_next_row(); - - // Find the EOI marker if that was the last row. - if (m_total_lines_left <= m_max_mcu_y_size) - find_eoi(); - - m_mcu_lines_left = m_max_mcu_y_size; - return 0; - } - - int jpeg_decoder::decode(const void** pScan_line, uint* pScan_line_len) - { - if ((m_error_code) || (!m_ready_flag)) - return JPGD_FAILED; - - if (m_total_lines_left == 0) - return JPGD_DONE; - - const bool chroma_y_filtering = (m_flags & cFlagLinearChromaFiltering) && ((m_scan_type == JPGD_YH2V2) || (m_scan_type == JPGD_YH1V2)) && (m_image_x_size >= 2) && (m_image_y_size >= 2); - - bool get_another_mcu_row = false; - bool got_mcu_early = false; - if (chroma_y_filtering) - { - if (m_total_lines_left == m_image_y_size) - get_another_mcu_row = true; - else if ((m_mcu_lines_left == 1) && (m_total_lines_left > 1)) - { - get_another_mcu_row = true; - got_mcu_early = true; - } - } - else - { - get_another_mcu_row = (m_mcu_lines_left == 0); - } - - if (get_another_mcu_row) - { - int status = decode_next_mcu_row(); - if (status != 0) - return status; - } - - switch (m_scan_type) - { - case JPGD_YH2V2: - { - if ((m_flags & cFlagLinearChromaFiltering) && (m_image_x_size >= 2) && (m_image_y_size >= 2)) - { - if (m_num_buffered_scanlines == 1) - { - *pScan_line = m_pScan_line_1; - } - else if (m_num_buffered_scanlines == 0) - { - m_num_buffered_scanlines = H2V2ConvertFiltered(); - *pScan_line = m_pScan_line_0; - } - - m_num_buffered_scanlines--; - } - else - { - if ((m_mcu_lines_left & 1) == 0) - { - H2V2Convert(); - *pScan_line = m_pScan_line_0; - } - else - *pScan_line = m_pScan_line_1; - } - - break; - } - case JPGD_YH2V1: - { - if ((m_flags & cFlagLinearChromaFiltering) && (m_image_x_size >= 2) && (m_image_y_size >= 2)) - H2V1ConvertFiltered(); - else - H2V1Convert(); - *pScan_line = m_pScan_line_0; - break; - } - case JPGD_YH1V2: - { - if (chroma_y_filtering) - { - H1V2ConvertFiltered(); - *pScan_line = m_pScan_line_0; - } - else - { - if ((m_mcu_lines_left & 1) == 0) - { - H1V2Convert(); - *pScan_line = m_pScan_line_0; - } - else - *pScan_line = m_pScan_line_1; - } - - break; - } - case JPGD_YH1V1: - { - H1V1Convert(); - *pScan_line = m_pScan_line_0; - break; - } - case JPGD_GRAYSCALE: - { - gray_convert(); - *pScan_line = m_pScan_line_0; - - break; - } - } - - *pScan_line_len = m_real_dest_bytes_per_scan_line; - - if (!got_mcu_early) - { - m_mcu_lines_left--; - } - - m_total_lines_left--; - - return JPGD_SUCCESS; - } - - // Creates the tables needed for efficient Huffman decoding. - void jpeg_decoder::make_huff_table(int index, huff_tables* pH) - { - int p, i, l, si; - uint8 huffsize[258]; - uint huffcode[258]; - uint code; - uint subtree; - int code_size; - int lastp; - int nextfreeentry; - int currententry; - - pH->ac_table = m_huff_ac[index] != 0; - - p = 0; - - for (l = 1; l <= 16; l++) - { - for (i = 1; i <= m_huff_num[index][l]; i++) - { - if (p >= 257) - stop_decoding(JPGD_DECODE_ERROR); - huffsize[p++] = static_cast<uint8>(l); - } - } - - assert(p < 258); - huffsize[p] = 0; - - lastp = p; - - code = 0; - si = huffsize[0]; - p = 0; - - while (huffsize[p]) - { - while (huffsize[p] == si) - { - if (p >= 257) - stop_decoding(JPGD_DECODE_ERROR); - huffcode[p++] = code; - code++; - } - - code <<= 1; - si++; - } - - memset(pH->look_up, 0, sizeof(pH->look_up)); - memset(pH->look_up2, 0, sizeof(pH->look_up2)); - memset(pH->tree, 0, sizeof(pH->tree)); - memset(pH->code_size, 0, sizeof(pH->code_size)); - - nextfreeentry = -1; - - p = 0; - - while (p < lastp) - { - i = m_huff_val[index][p]; - - code = huffcode[p]; - code_size = huffsize[p]; - - assert(i < JPGD_HUFF_CODE_SIZE_MAX_LENGTH); - pH->code_size[i] = static_cast<uint8>(code_size); - - if (code_size <= 8) - { - code <<= (8 - code_size); - - for (l = 1 << (8 - code_size); l > 0; l--) - { - if (code >= 256) - stop_decoding(JPGD_DECODE_ERROR); - - pH->look_up[code] = i; - - bool has_extrabits = false; - int extra_bits = 0; - int num_extra_bits = i & 15; - - int bits_to_fetch = code_size; - if (num_extra_bits) - { - int total_codesize = code_size + num_extra_bits; - if (total_codesize <= 8) - { - has_extrabits = true; - extra_bits = ((1 << num_extra_bits) - 1) & (code >> (8 - total_codesize)); - - if (extra_bits > 0x7FFF) - stop_decoding(JPGD_DECODE_ERROR); - - bits_to_fetch += num_extra_bits; - } - } - - if (!has_extrabits) - pH->look_up2[code] = i | (bits_to_fetch << 8); - else - pH->look_up2[code] = i | 0x8000 | (extra_bits << 16) | (bits_to_fetch << 8); - - code++; - } - } - else - { - subtree = (code >> (code_size - 8)) & 0xFF; - - currententry = pH->look_up[subtree]; - - if (currententry == 0) - { - pH->look_up[subtree] = currententry = nextfreeentry; - pH->look_up2[subtree] = currententry = nextfreeentry; - - nextfreeentry -= 2; - } - - code <<= (16 - (code_size - 8)); - - for (l = code_size; l > 9; l--) - { - if ((code & 0x8000) == 0) - currententry--; - - unsigned int idx = -currententry - 1; - - if (idx >= JPGD_HUFF_TREE_MAX_LENGTH) - stop_decoding(JPGD_DECODE_ERROR); - - if (pH->tree[idx] == 0) - { - pH->tree[idx] = nextfreeentry; - - currententry = nextfreeentry; - - nextfreeentry -= 2; - } - else - { - currententry = pH->tree[idx]; - } - - code <<= 1; - } - - if ((code & 0x8000) == 0) - currententry--; - - if ((-currententry - 1) >= JPGD_HUFF_TREE_MAX_LENGTH) - stop_decoding(JPGD_DECODE_ERROR); - - pH->tree[-currententry - 1] = i; - } - - p++; - } - } - - // Verifies the quantization tables needed for this scan are available. - void jpeg_decoder::check_quant_tables() - { - for (int i = 0; i < m_comps_in_scan; i++) - if (m_quant[m_comp_quant[m_comp_list[i]]] == nullptr) - stop_decoding(JPGD_UNDEFINED_QUANT_TABLE); - } - - // Verifies that all the Huffman tables needed for this scan are available. - void jpeg_decoder::check_huff_tables() - { - for (int i = 0; i < m_comps_in_scan; i++) - { - if ((m_spectral_start == 0) && (m_huff_num[m_comp_dc_tab[m_comp_list[i]]] == nullptr)) - stop_decoding(JPGD_UNDEFINED_HUFF_TABLE); - - if ((m_spectral_end > 0) && (m_huff_num[m_comp_ac_tab[m_comp_list[i]]] == nullptr)) - stop_decoding(JPGD_UNDEFINED_HUFF_TABLE); - } - - for (int i = 0; i < JPGD_MAX_HUFF_TABLES; i++) - if (m_huff_num[i]) - { - if (!m_pHuff_tabs[i]) - m_pHuff_tabs[i] = (huff_tables*)alloc(sizeof(huff_tables)); - - make_huff_table(i, m_pHuff_tabs[i]); - } - } - - // Determines the component order inside each MCU. - // Also calcs how many MCU's are on each row, etc. - bool jpeg_decoder::calc_mcu_block_order() - { - int component_num, component_id; - int max_h_samp = 0, max_v_samp = 0; - - for (component_id = 0; component_id < m_comps_in_frame; component_id++) - { - if (m_comp_h_samp[component_id] > max_h_samp) - max_h_samp = m_comp_h_samp[component_id]; - - if (m_comp_v_samp[component_id] > max_v_samp) - max_v_samp = m_comp_v_samp[component_id]; - } - - for (component_id = 0; component_id < m_comps_in_frame; component_id++) - { - m_comp_h_blocks[component_id] = ((((m_image_x_size * m_comp_h_samp[component_id]) + (max_h_samp - 1)) / max_h_samp) + 7) / 8; - m_comp_v_blocks[component_id] = ((((m_image_y_size * m_comp_v_samp[component_id]) + (max_v_samp - 1)) / max_v_samp) + 7) / 8; - } - - if (m_comps_in_scan == 1) - { - m_mcus_per_row = m_comp_h_blocks[m_comp_list[0]]; - m_mcus_per_col = m_comp_v_blocks[m_comp_list[0]]; - } - else - { - m_mcus_per_row = (((m_image_x_size + 7) / 8) + (max_h_samp - 1)) / max_h_samp; - m_mcus_per_col = (((m_image_y_size + 7) / 8) + (max_v_samp - 1)) / max_v_samp; - } - - if (m_comps_in_scan == 1) - { - m_mcu_org[0] = m_comp_list[0]; - - m_blocks_per_mcu = 1; - } - else - { - m_blocks_per_mcu = 0; - - for (component_num = 0; component_num < m_comps_in_scan; component_num++) - { - int num_blocks; - - component_id = m_comp_list[component_num]; - - num_blocks = m_comp_h_samp[component_id] * m_comp_v_samp[component_id]; - - while (num_blocks--) - m_mcu_org[m_blocks_per_mcu++] = component_id; - } - } - - if (m_blocks_per_mcu > m_max_blocks_per_mcu) - return false; - - for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) - { - int comp_id = m_mcu_org[mcu_block]; - if (comp_id >= JPGD_MAX_QUANT_TABLES) - return false; - } - - return true; - } - - // Starts a new scan. - int jpeg_decoder::init_scan() - { - if (!locate_sos_marker()) - return JPGD_FALSE; - - if (!calc_mcu_block_order()) - return JPGD_FALSE; - - check_huff_tables(); - - check_quant_tables(); - - memset(m_last_dc_val, 0, m_comps_in_frame * sizeof(uint)); - - m_eob_run = 0; - - if (m_restart_interval) - { - m_restarts_left = m_restart_interval; - m_next_restart_num = 0; - } - - fix_in_buffer(); - - return JPGD_TRUE; - } - - // Starts a frame. Determines if the number of components or sampling factors - // are supported. - void jpeg_decoder::init_frame() - { - int i; - - if (m_comps_in_frame == 1) - { - if ((m_comp_h_samp[0] != 1) || (m_comp_v_samp[0] != 1)) - stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS); - - m_scan_type = JPGD_GRAYSCALE; - m_max_blocks_per_mcu = 1; - m_max_mcu_x_size = 8; - m_max_mcu_y_size = 8; - } - else if (m_comps_in_frame == 3) - { - if (((m_comp_h_samp[1] != 1) || (m_comp_v_samp[1] != 1)) || - ((m_comp_h_samp[2] != 1) || (m_comp_v_samp[2] != 1))) - stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS); - - if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) - { - m_scan_type = JPGD_YH1V1; - - m_max_blocks_per_mcu = 3; - m_max_mcu_x_size = 8; - m_max_mcu_y_size = 8; - } - else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) - { - m_scan_type = JPGD_YH2V1; - m_max_blocks_per_mcu = 4; - m_max_mcu_x_size = 16; - m_max_mcu_y_size = 8; - } - else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 2)) - { - m_scan_type = JPGD_YH1V2; - m_max_blocks_per_mcu = 4; - m_max_mcu_x_size = 8; - m_max_mcu_y_size = 16; - } - else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) - { - m_scan_type = JPGD_YH2V2; - m_max_blocks_per_mcu = 6; - m_max_mcu_x_size = 16; - m_max_mcu_y_size = 16; - } - else - stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS); - } - else - stop_decoding(JPGD_UNSUPPORTED_COLORSPACE); - - m_max_mcus_per_row = (m_image_x_size + (m_max_mcu_x_size - 1)) / m_max_mcu_x_size; - m_max_mcus_per_col = (m_image_y_size + (m_max_mcu_y_size - 1)) / m_max_mcu_y_size; - - // These values are for the *destination* pixels: after conversion. - if (m_scan_type == JPGD_GRAYSCALE) - m_dest_bytes_per_pixel = 1; - else - m_dest_bytes_per_pixel = 4; - - m_dest_bytes_per_scan_line = ((m_image_x_size + 15) & 0xFFF0) * m_dest_bytes_per_pixel; - - m_real_dest_bytes_per_scan_line = (m_image_x_size * m_dest_bytes_per_pixel); - - // Initialize two scan line buffers. - m_pScan_line_0 = (uint8*)alloc(m_dest_bytes_per_scan_line, true); - if ((m_scan_type == JPGD_YH1V2) || (m_scan_type == JPGD_YH2V2)) - m_pScan_line_1 = (uint8*)alloc(m_dest_bytes_per_scan_line, true); - - m_max_blocks_per_row = m_max_mcus_per_row * m_max_blocks_per_mcu; - - // Should never happen - if (m_max_blocks_per_row > JPGD_MAX_BLOCKS_PER_ROW) - stop_decoding(JPGD_DECODE_ERROR); - - // Allocate the coefficient buffer, enough for one MCU - m_pMCU_coefficients = (jpgd_block_t*)alloc(m_max_blocks_per_mcu * 64 * sizeof(jpgd_block_t)); - - for (i = 0; i < m_max_blocks_per_mcu; i++) - m_mcu_block_max_zag[i] = 64; - - m_pSample_buf = (uint8*)alloc(m_max_blocks_per_row * 64); - m_pSample_buf_prev = (uint8*)alloc(m_max_blocks_per_row * 64); - - m_total_lines_left = m_image_y_size; - - m_mcu_lines_left = 0; - - create_look_ups(); - } - - // The coeff_buf series of methods originally stored the coefficients - // into a "virtual" file which was located in EMS, XMS, or a disk file. A cache - // was used to make this process more efficient. Now, we can store the entire - // thing in RAM. - jpeg_decoder::coeff_buf* jpeg_decoder::coeff_buf_open(int block_num_x, int block_num_y, int block_len_x, int block_len_y) - { - coeff_buf* cb = (coeff_buf*)alloc(sizeof(coeff_buf)); - - cb->block_num_x = block_num_x; - cb->block_num_y = block_num_y; - cb->block_len_x = block_len_x; - cb->block_len_y = block_len_y; - cb->block_size = (block_len_x * block_len_y) * sizeof(jpgd_block_t); - cb->pData = (uint8*)alloc(cb->block_size * block_num_x * block_num_y, true); - return cb; - } - - inline jpgd_block_t* jpeg_decoder::coeff_buf_getp(coeff_buf* cb, int block_x, int block_y) - { - if ((block_x >= cb->block_num_x) || (block_y >= cb->block_num_y)) - stop_decoding(JPGD_DECODE_ERROR); - - return (jpgd_block_t*)(cb->pData + block_x * cb->block_size + block_y * (cb->block_size * cb->block_num_x)); - } - - // The following methods decode the various types of m_blocks encountered - // in progressively encoded images. - void jpeg_decoder::decode_block_dc_first(jpeg_decoder* pD, int component_id, int block_x, int block_y) - { - int s, r; - jpgd_block_t* p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y); - - if ((s = pD->huff_decode(pD->m_pHuff_tabs[pD->m_comp_dc_tab[component_id]])) != 0) - { - if (s >= 16) - pD->stop_decoding(JPGD_DECODE_ERROR); - - r = pD->get_bits_no_markers(s); - s = JPGD_HUFF_EXTEND(r, s); - } - - pD->m_last_dc_val[component_id] = (s += pD->m_last_dc_val[component_id]); - - p[0] = static_cast<jpgd_block_t>(s << pD->m_successive_low); - } - - void jpeg_decoder::decode_block_dc_refine(jpeg_decoder* pD, int component_id, int block_x, int block_y) - { - if (pD->get_bits_no_markers(1)) - { - jpgd_block_t* p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y); - - p[0] |= (1 << pD->m_successive_low); - } - } - - void jpeg_decoder::decode_block_ac_first(jpeg_decoder* pD, int component_id, int block_x, int block_y) - { - int k, s, r; - - if (pD->m_eob_run) - { - pD->m_eob_run--; - return; - } - - jpgd_block_t* p = pD->coeff_buf_getp(pD->m_ac_coeffs[component_id], block_x, block_y); - - for (k = pD->m_spectral_start; k <= pD->m_spectral_end; k++) - { - unsigned int idx = pD->m_comp_ac_tab[component_id]; - if (idx >= JPGD_MAX_HUFF_TABLES) - pD->stop_decoding(JPGD_DECODE_ERROR); - - s = pD->huff_decode(pD->m_pHuff_tabs[idx]); - - r = s >> 4; - s &= 15; - - if (s) - { - if ((k += r) > 63) - pD->stop_decoding(JPGD_DECODE_ERROR); - - r = pD->get_bits_no_markers(s); - s = JPGD_HUFF_EXTEND(r, s); - - p[g_ZAG[k]] = static_cast<jpgd_block_t>(s << pD->m_successive_low); - } - else - { - if (r == 15) - { - if ((k += 15) > 63) - pD->stop_decoding(JPGD_DECODE_ERROR); - } - else - { - pD->m_eob_run = 1 << r; - - if (r) - pD->m_eob_run += pD->get_bits_no_markers(r); - - pD->m_eob_run--; - - break; - } - } - } - } - - void jpeg_decoder::decode_block_ac_refine(jpeg_decoder* pD, int component_id, int block_x, int block_y) - { - int s, k, r; - - int p1 = 1 << pD->m_successive_low; - - //int m1 = (-1) << pD->m_successive_low; - int m1 = static_cast<int>((UINT32_MAX << pD->m_successive_low)); - - jpgd_block_t* p = pD->coeff_buf_getp(pD->m_ac_coeffs[component_id], block_x, block_y); - if (pD->m_spectral_end > 63) - pD->stop_decoding(JPGD_DECODE_ERROR); - - k = pD->m_spectral_start; - - if (pD->m_eob_run == 0) - { - for (; k <= pD->m_spectral_end; k++) - { - unsigned int idx = pD->m_comp_ac_tab[component_id]; - if (idx >= JPGD_MAX_HUFF_TABLES) - pD->stop_decoding(JPGD_DECODE_ERROR); - - s = pD->huff_decode(pD->m_pHuff_tabs[idx]); - - r = s >> 4; - s &= 15; - - if (s) - { - if (s != 1) - pD->stop_decoding(JPGD_DECODE_ERROR); - - if (pD->get_bits_no_markers(1)) - s = p1; - else - s = m1; - } - else - { - if (r != 15) - { - pD->m_eob_run = 1 << r; - - if (r) - pD->m_eob_run += pD->get_bits_no_markers(r); - - break; - } - } - - do - { - jpgd_block_t* this_coef = p + g_ZAG[k & 63]; - - if (*this_coef != 0) - { - if (pD->get_bits_no_markers(1)) - { - if ((*this_coef & p1) == 0) - { - if (*this_coef >= 0) - *this_coef = static_cast<jpgd_block_t>(*this_coef + p1); - else - *this_coef = static_cast<jpgd_block_t>(*this_coef + m1); - } - } - } - else - { - if (--r < 0) - break; - } - - k++; - - } while (k <= pD->m_spectral_end); - - if ((s) && (k < 64)) - { - p[g_ZAG[k]] = static_cast<jpgd_block_t>(s); - } - } - } - - if (pD->m_eob_run > 0) - { - for (; k <= pD->m_spectral_end; k++) - { - jpgd_block_t* this_coef = p + g_ZAG[k & 63]; // logical AND to shut up static code analysis - - if (*this_coef != 0) - { - if (pD->get_bits_no_markers(1)) - { - if ((*this_coef & p1) == 0) - { - if (*this_coef >= 0) - *this_coef = static_cast<jpgd_block_t>(*this_coef + p1); - else - *this_coef = static_cast<jpgd_block_t>(*this_coef + m1); - } - } - } - } - - pD->m_eob_run--; - } - } - - // Decode a scan in a progressively encoded image. - void jpeg_decoder::decode_scan(pDecode_block_func decode_block_func) - { - int mcu_row, mcu_col, mcu_block; - int block_x_mcu[JPGD_MAX_COMPONENTS], block_y_mcu[JPGD_MAX_COMPONENTS]; - - memset(block_y_mcu, 0, sizeof(block_y_mcu)); - - for (mcu_col = 0; mcu_col < m_mcus_per_col; mcu_col++) - { - int component_num, component_id; - - memset(block_x_mcu, 0, sizeof(block_x_mcu)); - - for (mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) - { - int block_x_mcu_ofs = 0, block_y_mcu_ofs = 0; - - if ((m_restart_interval) && (m_restarts_left == 0)) - process_restart(); - - for (mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) - { - component_id = m_mcu_org[mcu_block]; - - decode_block_func(this, component_id, block_x_mcu[component_id] + block_x_mcu_ofs, block_y_mcu[component_id] + block_y_mcu_ofs); - - if (m_comps_in_scan == 1) - block_x_mcu[component_id]++; - else - { - if (++block_x_mcu_ofs == m_comp_h_samp[component_id]) - { - block_x_mcu_ofs = 0; - - if (++block_y_mcu_ofs == m_comp_v_samp[component_id]) - { - block_y_mcu_ofs = 0; - block_x_mcu[component_id] += m_comp_h_samp[component_id]; - } - } - } - } - - m_restarts_left--; - } - - if (m_comps_in_scan == 1) - block_y_mcu[m_comp_list[0]]++; - else - { - for (component_num = 0; component_num < m_comps_in_scan; component_num++) - { - component_id = m_comp_list[component_num]; - block_y_mcu[component_id] += m_comp_v_samp[component_id]; - } - } - } - } - - // Decode a progressively encoded image. - void jpeg_decoder::init_progressive() - { - int i; - - if (m_comps_in_frame == 4) - stop_decoding(JPGD_UNSUPPORTED_COLORSPACE); - - // Allocate the coefficient buffers. - for (i = 0; i < m_comps_in_frame; i++) - { - m_dc_coeffs[i] = coeff_buf_open(m_max_mcus_per_row * m_comp_h_samp[i], m_max_mcus_per_col * m_comp_v_samp[i], 1, 1); - m_ac_coeffs[i] = coeff_buf_open(m_max_mcus_per_row * m_comp_h_samp[i], m_max_mcus_per_col * m_comp_v_samp[i], 8, 8); - } - - // See https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf - uint32_t total_scans = 0; - const uint32_t MAX_SCANS_TO_PROCESS = 1000; - - for (; ; ) - { - int dc_only_scan, refinement_scan; - pDecode_block_func decode_block_func; - - if (!init_scan()) - break; - - dc_only_scan = (m_spectral_start == 0); - refinement_scan = (m_successive_high != 0); - - if ((m_spectral_start > m_spectral_end) || (m_spectral_end > 63)) - stop_decoding(JPGD_BAD_SOS_SPECTRAL); - - if (dc_only_scan) - { - if (m_spectral_end) - stop_decoding(JPGD_BAD_SOS_SPECTRAL); - } - else if (m_comps_in_scan != 1) /* AC scans can only contain one component */ - stop_decoding(JPGD_BAD_SOS_SPECTRAL); - - if ((refinement_scan) && (m_successive_low != m_successive_high - 1)) - stop_decoding(JPGD_BAD_SOS_SUCCESSIVE); - - if (dc_only_scan) - { - if (refinement_scan) - decode_block_func = decode_block_dc_refine; - else - decode_block_func = decode_block_dc_first; - } - else - { - if (refinement_scan) - decode_block_func = decode_block_ac_refine; - else - decode_block_func = decode_block_ac_first; - } - - decode_scan(decode_block_func); - - m_bits_left = 16; - get_bits(16); - get_bits(16); - - total_scans++; - if (total_scans > MAX_SCANS_TO_PROCESS) - stop_decoding(JPGD_TOO_MANY_SCANS); - } - - m_comps_in_scan = m_comps_in_frame; - - for (i = 0; i < m_comps_in_frame; i++) - m_comp_list[i] = i; - - if (!calc_mcu_block_order()) - stop_decoding(JPGD_DECODE_ERROR); - } - - void jpeg_decoder::init_sequential() - { - if (!init_scan()) - stop_decoding(JPGD_UNEXPECTED_MARKER); - } - - void jpeg_decoder::decode_start() - { - init_frame(); - - if (m_progressive_flag) - init_progressive(); - else - init_sequential(); - } - - void jpeg_decoder::decode_init(jpeg_decoder_stream* pStream, uint32_t flags) - { - init(pStream, flags); - locate_sof_marker(); - } - - jpeg_decoder::jpeg_decoder(jpeg_decoder_stream* pStream, uint32_t flags) - { - if (setjmp(m_jmp_state)) - return; - decode_init(pStream, flags); - } - - int jpeg_decoder::begin_decoding() - { - if (m_ready_flag) - return JPGD_SUCCESS; - - if (m_error_code) - return JPGD_FAILED; - - if (setjmp(m_jmp_state)) - return JPGD_FAILED; - - decode_start(); - - m_ready_flag = true; - - return JPGD_SUCCESS; - } - - jpeg_decoder::~jpeg_decoder() - { - free_all_blocks(); - } - - jpeg_decoder_file_stream::jpeg_decoder_file_stream() - { - m_pFile = nullptr; - m_eof_flag = false; - m_error_flag = false; - } - - void jpeg_decoder_file_stream::close() - { - if (m_pFile) - { - fclose(m_pFile); - m_pFile = nullptr; - } - - m_eof_flag = false; - m_error_flag = false; - } - - jpeg_decoder_file_stream::~jpeg_decoder_file_stream() - { - close(); - } - - bool jpeg_decoder_file_stream::open(const char* Pfilename) - { - close(); - - m_eof_flag = false; - m_error_flag = false; - -#if defined(_MSC_VER) - m_pFile = nullptr; - fopen_s(&m_pFile, Pfilename, "rb"); -#else - m_pFile = fopen(Pfilename, "rb"); -#endif - return m_pFile != nullptr; - } - - int jpeg_decoder_file_stream::read(uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag) - { - if (!m_pFile) - return -1; - - if (m_eof_flag) - { - *pEOF_flag = true; - return 0; - } - - if (m_error_flag) - return -1; - - int bytes_read = static_cast<int>(fread(pBuf, 1, max_bytes_to_read, m_pFile)); - if (bytes_read < max_bytes_to_read) - { - if (ferror(m_pFile)) - { - m_error_flag = true; - return -1; - } - - m_eof_flag = true; - *pEOF_flag = true; - } - - return bytes_read; - } - - bool jpeg_decoder_mem_stream::open(const uint8* pSrc_data, uint size) - { - close(); - m_pSrc_data = pSrc_data; - m_ofs = 0; - m_size = size; - return true; - } - - int jpeg_decoder_mem_stream::read(uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag) - { - *pEOF_flag = false; - - if (!m_pSrc_data) - return -1; - - uint bytes_remaining = m_size - m_ofs; - if ((uint)max_bytes_to_read > bytes_remaining) - { - max_bytes_to_read = bytes_remaining; - *pEOF_flag = true; - } - - memcpy(pBuf, m_pSrc_data + m_ofs, max_bytes_to_read); - m_ofs += max_bytes_to_read; - - return max_bytes_to_read; - } - - unsigned char* decompress_jpeg_image_from_stream(jpeg_decoder_stream* pStream, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags) - { - if (!actual_comps) - return nullptr; - *actual_comps = 0; - - if ((!pStream) || (!width) || (!height) || (!req_comps)) - return nullptr; - - if ((req_comps != 1) && (req_comps != 3) && (req_comps != 4)) - return nullptr; - - jpeg_decoder decoder(pStream, flags); - if (decoder.get_error_code() != JPGD_SUCCESS) - return nullptr; - - const int image_width = decoder.get_width(), image_height = decoder.get_height(); - *width = image_width; - *height = image_height; - *actual_comps = decoder.get_num_components(); - - if (decoder.begin_decoding() != JPGD_SUCCESS) - return nullptr; - - const int dst_bpl = image_width * req_comps; - - uint8* pImage_data = (uint8*)jpgd_malloc(dst_bpl * image_height); - if (!pImage_data) - return nullptr; - - for (int y = 0; y < image_height; y++) - { - const uint8* pScan_line; - uint scan_line_len; - if (decoder.decode((const void**)&pScan_line, &scan_line_len) != JPGD_SUCCESS) - { - jpgd_free(pImage_data); - return nullptr; - } - - uint8* pDst = pImage_data + y * dst_bpl; - - if (((req_comps == 1) && (decoder.get_num_components() == 1)) || ((req_comps == 4) && (decoder.get_num_components() == 3))) - memcpy(pDst, pScan_line, dst_bpl); - else if (decoder.get_num_components() == 1) - { - if (req_comps == 3) - { - for (int x = 0; x < image_width; x++) - { - uint8 luma = pScan_line[x]; - pDst[0] = luma; - pDst[1] = luma; - pDst[2] = luma; - pDst += 3; - } - } - else - { - for (int x = 0; x < image_width; x++) - { - uint8 luma = pScan_line[x]; - pDst[0] = luma; - pDst[1] = luma; - pDst[2] = luma; - pDst[3] = 255; - pDst += 4; - } - } - } - else if (decoder.get_num_components() == 3) - { - if (req_comps == 1) - { - const int YR = 19595, YG = 38470, YB = 7471; - for (int x = 0; x < image_width; x++) - { - int r = pScan_line[x * 4 + 0]; - int g = pScan_line[x * 4 + 1]; - int b = pScan_line[x * 4 + 2]; - *pDst++ = static_cast<uint8>((r * YR + g * YG + b * YB + 32768) >> 16); - } - } - else - { - for (int x = 0; x < image_width; x++) - { - pDst[0] = pScan_line[x * 4 + 0]; - pDst[1] = pScan_line[x * 4 + 1]; - pDst[2] = pScan_line[x * 4 + 2]; - pDst += 3; - } - } - } - } - - return pImage_data; - } - - unsigned char* decompress_jpeg_image_from_memory(const unsigned char* pSrc_data, int src_data_size, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags) - { - jpgd::jpeg_decoder_mem_stream mem_stream(pSrc_data, src_data_size); - return decompress_jpeg_image_from_stream(&mem_stream, width, height, actual_comps, req_comps, flags); - } - - unsigned char* decompress_jpeg_image_from_file(const char* pSrc_filename, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags) - { - jpgd::jpeg_decoder_file_stream file_stream; - if (!file_stream.open(pSrc_filename)) - return nullptr; - return decompress_jpeg_image_from_stream(&file_stream, width, height, actual_comps, req_comps, flags); - } - -} // namespace jpgd diff --git a/thirdparty/basis_universal/encoder/jpgd.h b/thirdparty/basis_universal/encoder/jpgd.h deleted file mode 100644 index 86a7814cae..0000000000 --- a/thirdparty/basis_universal/encoder/jpgd.h +++ /dev/null @@ -1,347 +0,0 @@ -// jpgd.h - C++ class for JPEG decompression. -// Public domain, Rich Geldreich <richgel99@gmail.com> -#ifndef JPEG_DECODER_H -#define JPEG_DECODER_H - -#include <stdlib.h> -#include <stdio.h> -#include <setjmp.h> -#include <assert.h> -#include <stdint.h> - -#ifdef _MSC_VER -#define JPGD_NORETURN __declspec(noreturn) -#elif defined(__GNUC__) -#define JPGD_NORETURN __attribute__ ((noreturn)) -#else -#define JPGD_NORETURN -#endif - -#define JPGD_HUFF_TREE_MAX_LENGTH 512 -#define JPGD_HUFF_CODE_SIZE_MAX_LENGTH 256 - -namespace jpgd -{ - typedef unsigned char uint8; - typedef signed short int16; - typedef unsigned short uint16; - typedef unsigned int uint; - typedef signed int int32; - - // Loads a JPEG image from a memory buffer or a file. - // req_comps can be 1 (grayscale), 3 (RGB), or 4 (RGBA). - // On return, width/height will be set to the image's dimensions, and actual_comps will be set to the either 1 (grayscale) or 3 (RGB). - // Notes: For more control over where and how the source data is read, see the decompress_jpeg_image_from_stream() function below, or call the jpeg_decoder class directly. - // Requesting a 8 or 32bpp image is currently a little faster than 24bpp because the jpeg_decoder class itself currently always unpacks to either 8 or 32bpp. - unsigned char* decompress_jpeg_image_from_memory(const unsigned char* pSrc_data, int src_data_size, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags = 0); - unsigned char* decompress_jpeg_image_from_file(const char* pSrc_filename, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags = 0); - - // Success/failure error codes. - enum jpgd_status - { - JPGD_SUCCESS = 0, JPGD_FAILED = -1, JPGD_DONE = 1, - JPGD_BAD_DHT_COUNTS = -256, JPGD_BAD_DHT_INDEX, JPGD_BAD_DHT_MARKER, JPGD_BAD_DQT_MARKER, JPGD_BAD_DQT_TABLE, - JPGD_BAD_PRECISION, JPGD_BAD_HEIGHT, JPGD_BAD_WIDTH, JPGD_TOO_MANY_COMPONENTS, - JPGD_BAD_SOF_LENGTH, JPGD_BAD_VARIABLE_MARKER, JPGD_BAD_DRI_LENGTH, JPGD_BAD_SOS_LENGTH, - JPGD_BAD_SOS_COMP_ID, JPGD_W_EXTRA_BYTES_BEFORE_MARKER, JPGD_NO_ARITHMITIC_SUPPORT, JPGD_UNEXPECTED_MARKER, - JPGD_NOT_JPEG, JPGD_UNSUPPORTED_MARKER, JPGD_BAD_DQT_LENGTH, JPGD_TOO_MANY_BLOCKS, - JPGD_UNDEFINED_QUANT_TABLE, JPGD_UNDEFINED_HUFF_TABLE, JPGD_NOT_SINGLE_SCAN, JPGD_UNSUPPORTED_COLORSPACE, - JPGD_UNSUPPORTED_SAMP_FACTORS, JPGD_DECODE_ERROR, JPGD_BAD_RESTART_MARKER, - JPGD_BAD_SOS_SPECTRAL, JPGD_BAD_SOS_SUCCESSIVE, JPGD_STREAM_READ, JPGD_NOTENOUGHMEM, JPGD_TOO_MANY_SCANS - }; - - // Input stream interface. - // Derive from this class to read input data from sources other than files or memory. Set m_eof_flag to true when no more data is available. - // The decoder is rather greedy: it will keep on calling this method until its internal input buffer is full, or until the EOF flag is set. - // It the input stream contains data after the JPEG stream's EOI (end of image) marker it will probably be pulled into the internal buffer. - // Call the get_total_bytes_read() method to determine the actual size of the JPEG stream after successful decoding. - class jpeg_decoder_stream - { - public: - jpeg_decoder_stream() { } - virtual ~jpeg_decoder_stream() { } - - // The read() method is called when the internal input buffer is empty. - // Parameters: - // pBuf - input buffer - // max_bytes_to_read - maximum bytes that can be written to pBuf - // pEOF_flag - set this to true if at end of stream (no more bytes remaining) - // Returns -1 on error, otherwise return the number of bytes actually written to the buffer (which may be 0). - // Notes: This method will be called in a loop until you set *pEOF_flag to true or the internal buffer is full. - virtual int read(uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag) = 0; - }; - - // stdio FILE stream class. - class jpeg_decoder_file_stream : public jpeg_decoder_stream - { - jpeg_decoder_file_stream(const jpeg_decoder_file_stream&); - jpeg_decoder_file_stream& operator =(const jpeg_decoder_file_stream&); - - FILE* m_pFile; - bool m_eof_flag, m_error_flag; - - public: - jpeg_decoder_file_stream(); - virtual ~jpeg_decoder_file_stream(); - - bool open(const char* Pfilename); - void close(); - - virtual int read(uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag); - }; - - // Memory stream class. - class jpeg_decoder_mem_stream : public jpeg_decoder_stream - { - const uint8* m_pSrc_data; - uint m_ofs, m_size; - - public: - jpeg_decoder_mem_stream() : m_pSrc_data(NULL), m_ofs(0), m_size(0) { } - jpeg_decoder_mem_stream(const uint8* pSrc_data, uint size) : m_pSrc_data(pSrc_data), m_ofs(0), m_size(size) { } - - virtual ~jpeg_decoder_mem_stream() { } - - bool open(const uint8* pSrc_data, uint size); - void close() { m_pSrc_data = NULL; m_ofs = 0; m_size = 0; } - - virtual int read(uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag); - }; - - // Loads JPEG file from a jpeg_decoder_stream. - unsigned char* decompress_jpeg_image_from_stream(jpeg_decoder_stream* pStream, int* width, int* height, int* actual_comps, int req_comps, uint32_t flags = 0); - - enum - { - JPGD_IN_BUF_SIZE = 8192, JPGD_MAX_BLOCKS_PER_MCU = 10, JPGD_MAX_HUFF_TABLES = 8, JPGD_MAX_QUANT_TABLES = 4, - JPGD_MAX_COMPONENTS = 4, JPGD_MAX_COMPS_IN_SCAN = 4, JPGD_MAX_BLOCKS_PER_ROW = 16384, JPGD_MAX_HEIGHT = 32768, JPGD_MAX_WIDTH = 32768 - }; - - typedef int16 jpgd_quant_t; - typedef int16 jpgd_block_t; - - class jpeg_decoder - { - public: - enum - { - cFlagLinearChromaFiltering = 1 - }; - - // Call get_error_code() after constructing to determine if the stream is valid or not. You may call the get_width(), get_height(), etc. - // methods after the constructor is called. You may then either destruct the object, or begin decoding the image by calling begin_decoding(), then decode() on each scanline. - jpeg_decoder(jpeg_decoder_stream* pStream, uint32_t flags = cFlagLinearChromaFiltering); - - ~jpeg_decoder(); - - // Call this method after constructing the object to begin decompression. - // If JPGD_SUCCESS is returned you may then call decode() on each scanline. - - int begin_decoding(); - - // Returns the next scan line. - // For grayscale images, pScan_line will point to a buffer containing 8-bit pixels (get_bytes_per_pixel() will return 1). - // Otherwise, it will always point to a buffer containing 32-bit RGBA pixels (A will always be 255, and get_bytes_per_pixel() will return 4). - // Returns JPGD_SUCCESS if a scan line has been returned. - // Returns JPGD_DONE if all scan lines have been returned. - // Returns JPGD_FAILED if an error occurred. Call get_error_code() for a more info. - int decode(const void** pScan_line, uint* pScan_line_len); - - inline jpgd_status get_error_code() const { return m_error_code; } - - inline int get_width() const { return m_image_x_size; } - inline int get_height() const { return m_image_y_size; } - - inline int get_num_components() const { return m_comps_in_frame; } - - inline int get_bytes_per_pixel() const { return m_dest_bytes_per_pixel; } - inline int get_bytes_per_scan_line() const { return m_image_x_size * get_bytes_per_pixel(); } - - // Returns the total number of bytes actually consumed by the decoder (which should equal the actual size of the JPEG file). - inline int get_total_bytes_read() const { return m_total_bytes_read; } - - private: - jpeg_decoder(const jpeg_decoder&); - jpeg_decoder& operator =(const jpeg_decoder&); - - typedef void (*pDecode_block_func)(jpeg_decoder*, int, int, int); - - struct huff_tables - { - bool ac_table; - uint look_up[256]; - uint look_up2[256]; - uint8 code_size[JPGD_HUFF_CODE_SIZE_MAX_LENGTH]; - uint tree[JPGD_HUFF_TREE_MAX_LENGTH]; - }; - - struct coeff_buf - { - uint8* pData; - int block_num_x, block_num_y; - int block_len_x, block_len_y; - int block_size; - }; - - struct mem_block - { - mem_block* m_pNext; - size_t m_used_count; - size_t m_size; - char m_data[1]; - }; - - jmp_buf m_jmp_state; - uint32_t m_flags; - mem_block* m_pMem_blocks; - int m_image_x_size; - int m_image_y_size; - jpeg_decoder_stream* m_pStream; - - int m_progressive_flag; - - uint8 m_huff_ac[JPGD_MAX_HUFF_TABLES]; - uint8* m_huff_num[JPGD_MAX_HUFF_TABLES]; // pointer to number of Huffman codes per bit size - uint8* m_huff_val[JPGD_MAX_HUFF_TABLES]; // pointer to Huffman codes per bit size - jpgd_quant_t* m_quant[JPGD_MAX_QUANT_TABLES]; // pointer to quantization tables - int m_scan_type; // Gray, Yh1v1, Yh1v2, Yh2v1, Yh2v2 (CMYK111, CMYK4114 no longer supported) - int m_comps_in_frame; // # of components in frame - int m_comp_h_samp[JPGD_MAX_COMPONENTS]; // component's horizontal sampling factor - int m_comp_v_samp[JPGD_MAX_COMPONENTS]; // component's vertical sampling factor - int m_comp_quant[JPGD_MAX_COMPONENTS]; // component's quantization table selector - int m_comp_ident[JPGD_MAX_COMPONENTS]; // component's ID - int m_comp_h_blocks[JPGD_MAX_COMPONENTS]; - int m_comp_v_blocks[JPGD_MAX_COMPONENTS]; - int m_comps_in_scan; // # of components in scan - int m_comp_list[JPGD_MAX_COMPS_IN_SCAN]; // components in this scan - int m_comp_dc_tab[JPGD_MAX_COMPONENTS]; // component's DC Huffman coding table selector - int m_comp_ac_tab[JPGD_MAX_COMPONENTS]; // component's AC Huffman coding table selector - int m_spectral_start; // spectral selection start - int m_spectral_end; // spectral selection end - int m_successive_low; // successive approximation low - int m_successive_high; // successive approximation high - int m_max_mcu_x_size; // MCU's max. X size in pixels - int m_max_mcu_y_size; // MCU's max. Y size in pixels - int m_blocks_per_mcu; - int m_max_blocks_per_row; - int m_mcus_per_row, m_mcus_per_col; - int m_mcu_org[JPGD_MAX_BLOCKS_PER_MCU]; - int m_total_lines_left; // total # lines left in image - int m_mcu_lines_left; // total # lines left in this MCU - int m_num_buffered_scanlines; - int m_real_dest_bytes_per_scan_line; - int m_dest_bytes_per_scan_line; // rounded up - int m_dest_bytes_per_pixel; // 4 (RGB) or 1 (Y) - huff_tables* m_pHuff_tabs[JPGD_MAX_HUFF_TABLES]; - coeff_buf* m_dc_coeffs[JPGD_MAX_COMPONENTS]; - coeff_buf* m_ac_coeffs[JPGD_MAX_COMPONENTS]; - int m_eob_run; - int m_block_y_mcu[JPGD_MAX_COMPONENTS]; - uint8* m_pIn_buf_ofs; - int m_in_buf_left; - int m_tem_flag; - - uint8 m_in_buf_pad_start[64]; - uint8 m_in_buf[JPGD_IN_BUF_SIZE + 128]; - uint8 m_in_buf_pad_end[64]; - - int m_bits_left; - uint m_bit_buf; - int m_restart_interval; - int m_restarts_left; - int m_next_restart_num; - int m_max_mcus_per_row; - int m_max_blocks_per_mcu; - - int m_max_mcus_per_col; - uint m_last_dc_val[JPGD_MAX_COMPONENTS]; - jpgd_block_t* m_pMCU_coefficients; - int m_mcu_block_max_zag[JPGD_MAX_BLOCKS_PER_MCU]; - uint8* m_pSample_buf; - uint8* m_pSample_buf_prev; - int m_crr[256]; - int m_cbb[256]; - int m_crg[256]; - int m_cbg[256]; - uint8* m_pScan_line_0; - uint8* m_pScan_line_1; - jpgd_status m_error_code; - int m_total_bytes_read; - - bool m_ready_flag; - bool m_eof_flag; - bool m_sample_buf_prev_valid; - - inline int check_sample_buf_ofs(int ofs) const { assert(ofs >= 0); assert(ofs < m_max_blocks_per_row * 64); return ofs; } - void free_all_blocks(); - JPGD_NORETURN void stop_decoding(jpgd_status status); - void* alloc(size_t n, bool zero = false); - void word_clear(void* p, uint16 c, uint n); - void prep_in_buffer(); - void read_dht_marker(); - void read_dqt_marker(); - void read_sof_marker(); - void skip_variable_marker(); - void read_dri_marker(); - void read_sos_marker(); - int next_marker(); - int process_markers(); - void locate_soi_marker(); - void locate_sof_marker(); - int locate_sos_marker(); - void init(jpeg_decoder_stream* pStream, uint32_t flags); - void create_look_ups(); - void fix_in_buffer(); - void transform_mcu(int mcu_row); - coeff_buf* coeff_buf_open(int block_num_x, int block_num_y, int block_len_x, int block_len_y); - inline jpgd_block_t* coeff_buf_getp(coeff_buf* cb, int block_x, int block_y); - void load_next_row(); - void decode_next_row(); - void make_huff_table(int index, huff_tables* pH); - void check_quant_tables(); - void check_huff_tables(); - bool calc_mcu_block_order(); - int init_scan(); - void init_frame(); - void process_restart(); - void decode_scan(pDecode_block_func decode_block_func); - void init_progressive(); - void init_sequential(); - void decode_start(); - void decode_init(jpeg_decoder_stream* pStream, uint32_t flags); - void H2V2Convert(); - uint32_t H2V2ConvertFiltered(); - void H2V1Convert(); - void H2V1ConvertFiltered(); - void H1V2Convert(); - void H1V2ConvertFiltered(); - void H1V1Convert(); - void gray_convert(); - void find_eoi(); - inline uint get_char(); - inline uint get_char(bool* pPadding_flag); - inline void stuff_char(uint8 q); - inline uint8 get_octet(); - inline uint get_bits(int num_bits); - inline uint get_bits_no_markers(int numbits); - inline int huff_decode(huff_tables* pH); - inline int huff_decode(huff_tables* pH, int& extrabits); - - // Clamps a value between 0-255. - static inline uint8 clamp(int i) - { - if (static_cast<uint>(i) > 255) - i = (((~i) >> 31) & 0xFF); - return static_cast<uint8>(i); - } - int decode_next_mcu_row(); - - static void decode_block_dc_first(jpeg_decoder* pD, int component_id, int block_x, int block_y); - static void decode_block_dc_refine(jpeg_decoder* pD, int component_id, int block_x, int block_y); - static void decode_block_ac_first(jpeg_decoder* pD, int component_id, int block_x, int block_y); - static void decode_block_ac_refine(jpeg_decoder* pD, int component_id, int block_x, int block_y); - }; - -} // namespace jpgd - -#endif // JPEG_DECODER_H |
