summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYRIGHT.txt2
-rw-r--r--core/extension/extension_api_dump.cpp17
-rw-r--r--core/io/image.cpp13
-rw-r--r--core/io/image.h1
-rw-r--r--doc/classes/Animation.xml9
-rw-r--r--doc/classes/AnimationLibrary.xml7
-rw-r--r--doc/classes/AnimationNode.xml3
-rw-r--r--doc/classes/CanvasItem.xml13
-rw-r--r--doc/classes/CharFXTransform.xml3
-rw-r--r--doc/classes/Node2D.xml10
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp40
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp5
-rw-r--r--drivers/gles3/shaders/SCsub1
-rw-r--r--drivers/gles3/shaders/canvas.glsl9
-rw-r--r--drivers/gles3/shaders/skeleton.glsl269
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp581
-rw-r--r--drivers/gles3/storage/mesh_storage.h38
-rw-r--r--drivers/gles3/storage/texture_storage.cpp69
-rw-r--r--drivers/gles3/storage/texture_storage.h3
-rw-r--r--drivers/gles3/storage/utilities.cpp2
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/export/editor_export_plugin.cpp24
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp7
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp293
-rw-r--r--editor/plugins/node_3d_editor_plugin.h37
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp2
-rw-r--r--editor/project_manager.cpp3
-rw-r--r--editor/script_create_dialog.cpp36
-rw-r--r--editor/script_create_dialog.h1
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp3
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp18
-rw-r--r--modules/gdscript/gdscript_compiler.cpp9
-rw-r--r--modules/gdscript/gdscript_parser.cpp8
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out2
-rw-r--r--modules/mono/csharp_script.cpp14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs6
-rw-r--r--modules/openxr/config.py2
-rw-r--r--modules/openxr/extensions/openxr_android_extension.cpp6
-rw-r--r--modules/openxr/extensions/openxr_android_extension.h1
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml105
-rw-r--r--modules/webxr/godot_webxr.h41
-rw-r--r--modules/webxr/native/library_godot_webxr.js460
-rw-r--r--modules/webxr/native/webxr.externs.js682
-rw-r--r--modules/webxr/webxr_interface.cpp23
-rw-r--r--modules/webxr/webxr_interface.h14
-rw-r--r--modules/webxr/webxr_interface_js.cpp487
-rw-r--r--modules/webxr/webxr_interface_js.h156
-rw-r--r--platform/android/detect.py25
-rw-r--r--platform/android/export/export_plugin.cpp2
-rw-r--r--platform/android/java/app/config.gradle9
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt2
-rw-r--r--platform/macos/export/export_plugin.cpp2
-rw-r--r--platform/web/SCsub9
-rw-r--r--platform/web/js/libs/library_godot_webgl2.externs.js36
-rw-r--r--platform/web/js/libs/library_godot_webgl2.js5
-rw-r--r--scene/2d/node_2d.cpp46
-rw-r--r--scene/2d/node_2d.h12
-rw-r--r--scene/animation/animation_blend_tree.cpp54
-rw-r--r--scene/animation/animation_node_state_machine.cpp8
-rw-r--r--scene/animation/animation_player.cpp120
-rw-r--r--scene/animation/animation_player.h6
-rw-r--r--scene/animation/animation_tree.cpp23
-rw-r--r--scene/animation/animation_tree.h4
-rw-r--r--scene/gui/control.cpp12
-rw-r--r--scene/gui/range.cpp21
-rw-r--r--scene/gui/range.h1
-rw-r--r--scene/gui/rich_text_effect.cpp4
-rw-r--r--scene/gui/rich_text_effect.h4
-rw-r--r--scene/gui/rich_text_label.cpp10
-rw-r--r--scene/gui/tree.cpp64
-rw-r--r--scene/gui/tree.h4
-rw-r--r--scene/main/canvas_item.cpp48
-rw-r--r--scene/main/canvas_item.h15
-rw-r--r--scene/main/viewport.cpp22
-rw-r--r--scene/main/viewport.h1
-rw-r--r--scene/resources/animation.cpp486
-rw-r--r--scene/resources/animation.h12
-rw-r--r--scene/resources/animation_library.cpp13
-rw-r--r--scene/resources/animation_library.h2
-rw-r--r--scene/resources/material.cpp15
-rw-r--r--scene/resources/mesh.cpp100
-rw-r--r--scene/resources/style_box.cpp12
-rw-r--r--scene/resources/texture.cpp126
-rw-r--r--servers/audio/audio_effect.cpp10
-rw-r--r--servers/audio/audio_stream.cpp27
-rw-r--r--servers/movie_writer/movie_writer.cpp43
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp10
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h1
-rw-r--r--servers/rendering/renderer_scene_cull.cpp2
-rw-r--r--thirdparty/README.md4
-rw-r--r--thirdparty/miniupnpc/LICENSE39
-rw-r--r--thirdparty/miniupnpc/include/miniupnpc.h8
-rw-r--r--thirdparty/miniupnpc/src/miniupnpcstrings.h2
-rw-r--r--thirdparty/miniupnpc/src/minixml.c2
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/inc/thorvg.h18
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp6
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h2
-rw-r--r--thirdparty/thorvg/src/lib/tvgLoadModule.h1
-rw-r--r--thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp6
-rw-r--r--thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp2
-rw-r--r--thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp6
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp20
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp164
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h3
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h26
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp148
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h2
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp6
-rw-r--r--thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp6
-rw-r--r--thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp6
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh5
118 files changed, 3983 insertions, 1480 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 2dc58158ef..02cd733c92 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -293,7 +293,7 @@ License: CC0-1.0
Files: ./thirdparty/miniupnpc/
Comment: MiniUPnP Project
-Copyright: 2005-2021, Thomas Bernard
+Copyright: 2005-2022, Thomas Bernard
License: BSD-3-clause
Files: ./thirdparty/minizip/
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index a2c507e350..196bb68b2b 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -266,7 +266,7 @@ Dictionary NativeExtensionAPIDump::generate_extension_api() {
{ Variant::VECTOR2I, "x", 0, 0, 0, 0 },
{ Variant::VECTOR2I, "y", sizeof(int32_t), sizeof(int32_t), sizeof(int32_t), sizeof(int32_t) },
{ Variant::RECT2, "position", 0, 0, 0, 0 },
- { Variant::RECT2, "size", 2 * sizeof(Vector2), 2 * sizeof(float), 2 * sizeof(double), 2 * sizeof(double) },
+ { Variant::RECT2, "size", 2 * sizeof(float), 2 * sizeof(float), 2 * sizeof(double), 2 * sizeof(double) },
{ Variant::RECT2I, "position", 0, 0, 0, 0 },
{ Variant::RECT2I, "size", 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t) },
{ Variant::VECTOR3, "x", 0, 0, 0, 0 },
@@ -871,9 +871,18 @@ Dictionary NativeExtensionAPIDump::generate_extension_api() {
Dictionary d2;
d2["type"] = get_property_info_type_name(F);
d2["name"] = String(property_name);
- d2["setter"] = ClassDB::get_property_setter(class_name, F.name);
- d2["getter"] = ClassDB::get_property_getter(class_name, F.name);
- d2["index"] = ClassDB::get_property_index(class_name, F.name);
+ StringName setter = ClassDB::get_property_setter(class_name, F.name);
+ if (!(setter == "")) {
+ d2["setter"] = setter;
+ }
+ StringName getter = ClassDB::get_property_getter(class_name, F.name);
+ if (!(getter == "")) {
+ d2["getter"] = getter;
+ }
+ int index = ClassDB::get_property_index(class_name, F.name);
+ if (index != -1) {
+ d2["index"] = index;
+ }
properties.push_back(d2);
}
diff --git a/core/io/image.cpp b/core/io/image.cpp
index 21146dd80c..1b9538794a 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -3798,6 +3798,19 @@ void Image::convert_ra_rgba8_to_rg() {
}
}
+void Image::convert_rgba8_to_bgra8() {
+ ERR_FAIL_COND(format != FORMAT_RGBA8);
+ ERR_FAIL_COND(!data.size());
+
+ int s = data.size();
+ uint8_t *w = data.ptrw();
+ for (int i = 0; i < s; i += 4) {
+ uint8_t r = w[i];
+ w[i] = w[i + 2]; // Swap R to B
+ w[i + 2] = r; // Swap B to R
+ }
+}
+
Error Image::_load_from_buffer(const Vector<uint8_t> &p_array, ImageMemLoadFunc p_loader) {
int buffer_size = p_array.size();
diff --git a/core/io/image.h b/core/io/image.h
index 62df81e7c8..ad5c0b4a04 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -391,6 +391,7 @@ public:
void convert_rg_to_ra_rgba8();
void convert_ra_rgba8_to_rg();
+ void convert_rgba8_to_bgra8();
Image(const uint8_t *p_mem_png_jpg, int p_len = -1);
Image(const char **p_xpm);
diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml
index 80e0c81509..58681090b4 100644
--- a/doc/classes/Animation.xml
+++ b/doc/classes/Animation.xml
@@ -616,5 +616,14 @@
<constant name="LOOP_PINGPONG" value="2" enum="LoopMode">
Repeats playback and reverse playback at both ends of the animation.
</constant>
+ <constant name="LOOPED_FLAG_NONE" value="0" enum="LoopedFlag">
+ This flag indicates that the animation proceeds without any looping.
+ </constant>
+ <constant name="LOOPED_FLAG_END" value="1" enum="LoopedFlag">
+ This flag indicates that the animation has reached the end of the animation and just after loop processed.
+ </constant>
+ <constant name="LOOPED_FLAG_START" value="2" enum="LoopedFlag">
+ This flag indicates that the animation has reached the start of the animation and just after loop processed.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/AnimationLibrary.xml b/doc/classes/AnimationLibrary.xml
index 769b338063..9be4cda5d6 100644
--- a/doc/classes/AnimationLibrary.xml
+++ b/doc/classes/AnimationLibrary.xml
@@ -65,6 +65,13 @@
Emitted when an [Animation] is added, under the key [param name].
</description>
</signal>
+ <signal name="animation_changed">
+ <param index="0" name="name" type="StringName" />
+ <description>
+ Emitted when there's a change in one of the animations, e.g. tracks are added, moved or have changed paths. [param name] is the key of the animation that was changed.
+ See also [signal Resource.changed], which this acts as a relay for.
+ </description>
+ </signal>
<signal name="animation_removed">
<param index="0" name="name" type="StringName" />
<description>
diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml
index 915fbf53cd..79deb008d2 100644
--- a/doc/classes/AnimationNode.xml
+++ b/doc/classes/AnimationNode.xml
@@ -75,9 +75,10 @@
<param index="3" name="seeked" type="bool" />
<param index="4" name="is_external_seeking" type="bool" />
<param index="5" name="blend" type="float" />
- <param index="6" name="pingponged" type="int" default="0" />
+ <param index="6" name="looped_flag" type="int" enum="Animation.LoopedFlag" default="0" />
<description>
Blend an animation by [param blend] amount (name must be valid in the linked [AnimationPlayer]). A [param time] and [param delta] may be passed, as well as whether [param seeked] happened.
+ A [param looped_flag] is used by internal processing immediately after the loop. See also [enum Animation.LoopedFlag].
</description>
</method>
<method name="blend_input">
diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml
index 50bbbc71a0..499d53e63c 100644
--- a/doc/classes/CanvasItem.xml
+++ b/doc/classes/CanvasItem.xml
@@ -510,7 +510,7 @@
<method name="move_to_front">
<return type="void" />
<description>
- Moves this node to display on top of its siblings. This has more use in [Control], as [Node2D] can be ordered with [member Node2D.z_index].
+ Moves this node to display on top of its siblings.
Internally, the node is moved to the bottom of parent's children list. The method has no effect on nodes without a parent.
</description>
</method>
@@ -587,6 +587,17 @@
If [code]true[/code], this [CanvasItem] is drawn. The node is only visible if all of its antecedents are visible as well (in other words, [method is_visible_in_tree] must return [code]true[/code]).
[b]Note:[/b] For controls that inherit [Popup], the correct way to make them visible is to call one of the multiple [code]popup*()[/code] functions instead.
</member>
+ <member name="y_sort_enabled" type="bool" setter="set_y_sort_enabled" getter="is_y_sort_enabled" default="false">
+ If [code]true[/code], child nodes with the lowest Y position are drawn before those with a higher Y position. If [code]false[/code], Y-sorting is disabled. Y-sorting only affects children that inherit from [CanvasItem].
+ You can nest nodes with Y-sorting. Child Y-sorted nodes are sorted in the same space as the parent Y-sort. This feature allows you to organize a scene better or divide it into multiple ones without changing your scene tree.
+ </member>
+ <member name="z_as_relative" type="bool" setter="set_z_as_relative" getter="is_z_relative" default="true">
+ If [code]true[/code], the node's Z index is relative to its parent's Z index. If this node's Z index is 2 and its parent's effective Z index is 3, then this node's effective Z index will be 2 + 3 = 5.
+ </member>
+ <member name="z_index" type="int" setter="set_z_index" getter="get_z_index" default="0">
+ Z index. Controls the order in which the nodes render. A node with a higher Z index will display in front of others. Must be between [constant RenderingServer.CANVAS_ITEM_Z_MIN] and [constant RenderingServer.CANVAS_ITEM_Z_MAX] (inclusive).
+ [b]Note:[/b] Changing the Z index of a [Control] only affects the drawing order, not the order in which input events are handled. This can be useful to implement certain UI animations, e.g. a menu where hovered items are scaled and should overlap others.
+ </member>
</members>
<signals>
<signal name="draw">
diff --git a/doc/classes/CharFXTransform.xml b/doc/classes/CharFXTransform.xml
index c98b194a4d..a6f707383f 100644
--- a/doc/classes/CharFXTransform.xml
+++ b/doc/classes/CharFXTransform.xml
@@ -46,6 +46,9 @@
<member name="range" type="Vector2i" setter="set_range" getter="get_range" default="Vector2i(0, 0)">
Absolute character range in the string, corresponding to the glyph. Setting this property won't affect drawing.
</member>
+ <member name="relative_index" type="int" setter="set_relative_index" getter="get_relative_index" default="0">
+ The character offset of the glyph, relative to the current [RichTextEffect] custom block. Setting this property won't affect drawing.
+ </member>
<member name="visible" type="bool" setter="set_visibility" getter="is_visible" default="true">
If [code]true[/code], the character will be drawn. If [code]false[/code], the character will be hidden. Characters around hidden characters will reflow to take the space of hidden characters. If this is not desired, set their [member color] to [code]Color(1, 1, 1, 0)[/code] instead.
</member>
diff --git a/doc/classes/Node2D.xml b/doc/classes/Node2D.xml
index 054ab532af..26efe37dce 100644
--- a/doc/classes/Node2D.xml
+++ b/doc/classes/Node2D.xml
@@ -125,15 +125,5 @@
<member name="transform" type="Transform2D" setter="set_transform" getter="get_transform">
Local [Transform2D].
</member>
- <member name="y_sort_enabled" type="bool" setter="set_y_sort_enabled" getter="is_y_sort_enabled" default="false">
- If [code]true[/code], child nodes with the lowest Y position are drawn before those with a higher Y position. If [code]false[/code], Y-sorting is disabled. Y-sorting only affects children that inherit from [CanvasItem].
- You can nest nodes with Y-sorting. Child Y-sorted nodes are sorted in the same space as the parent Y-sort. This feature allows you to organize a scene better or divide it into multiple ones without changing your scene tree.
- </member>
- <member name="z_as_relative" type="bool" setter="set_z_as_relative" getter="is_z_relative" default="true">
- If [code]true[/code], the node's Z index is relative to its parent's Z index. If this node's Z index is 2 and its parent's effective Z index is 3, then this node's effective Z index will be 2 + 3 = 5.
- </member>
- <member name="z_index" type="int" setter="set_z_index" getter="get_z_index" default="0">
- Z index. Controls the order in which the nodes render. A node with a higher Z index will display in front of others. Must be between [constant RenderingServer.CANVAS_ITEM_Z_MIN] and [constant RenderingServer.CANVAS_ITEM_Z_MAX] (inclusive).
- </member>
</members>
</class>
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 07d56b156c..e5d4077393 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -106,6 +106,7 @@ void RasterizerCanvasGLES3::_update_transform_to_mat4(const Transform3D &p_trans
void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_light_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) {
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
+ GLES3::MeshStorage *mesh_storage = GLES3::MeshStorage::get_singleton();
Transform2D canvas_transform_inverse = p_canvas_transform.affine_inverse();
@@ -311,9 +312,14 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
Size2i ssize = texture_storage->render_target_get_size(p_to_render_target);
+ // If we've overridden the render target's color texture, then we need
+ // to invert the Y axis, so 2D texture appear right side up.
+ // We're probably rendering directly to an XR device.
+ float y_scale = texture_storage->render_target_get_override_color(p_to_render_target).is_valid() ? -2.0f : 2.0f;
+
Transform3D screen_transform;
screen_transform.translate_local(-(ssize.width / 2.0f), -(ssize.height / 2.0f), 0.0f);
- screen_transform.scale(Vector3(2.0f / ssize.width, 2.0f / ssize.height, 1.0f));
+ screen_transform.scale(Vector3(2.0f / ssize.width, y_scale / ssize.height, 1.0f));
_update_transform_to_mat4(screen_transform, state_buffer.screen_transform);
_update_transform_2d_to_mat4(p_canvas_transform, state_buffer.canvas_transform);
@@ -384,6 +390,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
Rect2 back_buffer_rect;
bool backbuffer_copy = false;
bool backbuffer_gen_mipmaps = false;
+ bool update_skeletons = false;
Item *ci = p_item_list;
Item *canvas_group_owner = nullptr;
@@ -425,8 +432,27 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
}
+ if (ci->skeleton.is_valid()) {
+ const Item::Command *c = ci->commands;
+
+ while (c) {
+ if (c->type == Item::Command::TYPE_MESH) {
+ const Item::CommandMesh *cm = static_cast<const Item::CommandMesh *>(c);
+ if (cm->mesh_instance.is_valid()) {
+ mesh_storage->mesh_instance_check_for_update(cm->mesh_instance);
+ update_skeletons = true;
+ }
+ }
+ c = c->next;
+ }
+ }
+
if (ci->canvas_group_owner != nullptr) {
if (canvas_group_owner == nullptr) {
+ if (update_skeletons) {
+ mesh_storage->update_mesh_instances();
+ update_skeletons = false;
+ }
// Canvas group begins here, render until before this item
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, false);
item_count = 0;
@@ -455,6 +481,10 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
if (ci == canvas_group_owner) {
+ if (update_skeletons) {
+ mesh_storage->update_mesh_instances();
+ update_skeletons = false;
+ }
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, true);
item_count = 0;
@@ -468,6 +498,10 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
if (backbuffer_copy) {
+ if (update_skeletons) {
+ mesh_storage->update_mesh_instances();
+ update_skeletons = false;
+ }
//render anything pending, including clearing if no items
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, false);
@@ -492,6 +526,10 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
items[item_count++] = ci;
if (!ci->next || item_count == MAX_RENDER_ITEMS - 1) {
+ if (update_skeletons) {
+ mesh_storage->update_mesh_instances();
+ update_skeletons = false;
+ }
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, false);
//then reset
item_count = 0;
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 026ec85e6b..8250140c3f 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -420,6 +420,11 @@ void RasterizerSceneGLES3::_geometry_instance_update(RenderGeometryInstance *p_g
}
} else if (ginstance->data->base_type == RS::INSTANCE_MESH) {
+ if (mesh_storage->skeleton_is_valid(ginstance->data->skeleton)) {
+ if (ginstance->data->dirty_dependencies) {
+ mesh_storage->skeleton_update_dependency(ginstance->data->skeleton, &ginstance->data->dependency_tracker);
+ }
+ }
}
ginstance->store_transform_cache = store_transform;
diff --git a/drivers/gles3/shaders/SCsub b/drivers/gles3/shaders/SCsub
index 2686b1aa48..34713e7e29 100644
--- a/drivers/gles3/shaders/SCsub
+++ b/drivers/gles3/shaders/SCsub
@@ -21,3 +21,4 @@ if "GLES3_GLSL" in env["BUILDERS"]:
env.GLES3_GLSL("canvas_sdf.glsl")
env.GLES3_GLSL("particles.glsl")
env.GLES3_GLSL("particles_copy.glsl")
+ env.GLES3_GLSL("skeleton.glsl")
diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl
index cdae05a516..60139de472 100644
--- a/drivers/gles3/shaders/canvas.glsl
+++ b/drivers/gles3/shaders/canvas.glsl
@@ -19,9 +19,6 @@ layout(location = 0) in vec2 vertex_attrib;
layout(location = 3) in vec4 color_attrib;
layout(location = 4) in vec2 uv_attrib;
-layout(location = 10) in uvec4 bone_attrib;
-layout(location = 11) in vec4 weight_attrib;
-
#ifdef USE_INSTANCING
layout(location = 1) in highp vec4 instance_xform0;
@@ -81,8 +78,6 @@ void main() {
uv = draw_data[draw_data_instance].uv_c;
color = vec4(unpackHalf2x16(draw_data[draw_data_instance].color_c_rg), unpackHalf2x16(draw_data[draw_data_instance].color_c_ba));
}
- uvec4 bones = uvec4(0, 0, 0, 0);
- vec4 bone_weights = vec4(0.0);
#elif defined(USE_ATTRIBUTES)
draw_data_instance = gl_InstanceID;
@@ -93,9 +88,6 @@ void main() {
vec4 color = color_attrib * draw_data[draw_data_instance].modulation;
vec2 uv = uv_attrib;
- uvec4 bones = bone_attrib;
- vec4 bone_weights = weight_attrib;
-
#ifdef USE_INSTANCING
vec4 instance_color = vec4(unpackHalf2x16(instance_color_custom_data.x), unpackHalf2x16(instance_color_custom_data.y));
color *= instance_color;
@@ -110,7 +102,6 @@ void main() {
vec2 uv = draw_data[draw_data_instance].src_rect.xy + abs(draw_data[draw_data_instance].src_rect.zw) * ((draw_data[draw_data_instance].flags & FLAGS_TRANSPOSE_RECT) != uint(0) ? vertex_base.yx : vertex_base.xy);
vec4 color = draw_data[draw_data_instance].modulation;
vec2 vertex = draw_data[draw_data_instance].dst_rect.xy + abs(draw_data[draw_data_instance].dst_rect.zw) * mix(vertex_base, vec2(1.0, 1.0) - vertex_base, lessThan(draw_data[draw_data_instance].src_rect.zw, vec2(0.0, 0.0)));
- uvec4 bones = uvec4(0, 0, 0, 0);
#endif
diff --git a/drivers/gles3/shaders/skeleton.glsl b/drivers/gles3/shaders/skeleton.glsl
new file mode 100644
index 0000000000..a1e3c098f4
--- /dev/null
+++ b/drivers/gles3/shaders/skeleton.glsl
@@ -0,0 +1,269 @@
+/* clang-format off */
+#[modes]
+
+mode_base_pass =
+mode_blend_pass = #define MODE_BLEND_PASS
+
+#[specializations]
+
+MODE_2D = true
+USE_BLEND_SHAPES = false
+USE_SKELETON = false
+USE_NORMAL = false
+USE_TANGENT = false
+FINAL_PASS = false
+USE_EIGHT_WEIGHTS = false
+
+#[vertex]
+
+#include "stdlib_inc.glsl"
+
+#ifdef MODE_2D
+#define VFORMAT vec2
+#else
+#define VFORMAT vec3
+#endif
+
+#ifdef FINAL_PASS
+#define OFORMAT vec2
+#else
+#define OFORMAT uvec2
+#endif
+
+// These come from the source mesh and the output from previous passes.
+layout(location = 0) in highp VFORMAT in_vertex;
+#ifdef MODE_BLEND_PASS
+#ifdef USE_NORMAL
+layout(location = 1) in highp uvec2 in_normal;
+#endif
+#ifdef USE_TANGENT
+layout(location = 2) in highp uvec2 in_tangent;
+#endif
+#else // MODE_BLEND_PASS
+#ifdef USE_NORMAL
+layout(location = 1) in highp vec2 in_normal;
+#endif
+#ifdef USE_TANGENT
+layout(location = 2) in highp vec2 in_tangent;
+#endif
+#endif // MODE_BLEND_PASS
+
+#ifdef USE_SKELETON
+#ifdef USE_EIGHT_WEIGHTS
+layout(location = 10) in highp uvec4 in_bone_attrib;
+layout(location = 11) in highp uvec4 in_bone_attrib2;
+layout(location = 12) in mediump vec4 in_weight_attrib;
+layout(location = 13) in mediump vec4 in_weight_attrib2;
+#else
+layout(location = 10) in highp uvec4 in_bone_attrib;
+layout(location = 11) in mediump vec4 in_weight_attrib;
+#endif
+
+uniform mediump sampler2D skeleton_texture; // texunit:0
+#endif
+
+/* clang-format on */
+#ifdef MODE_BLEND_PASS
+layout(location = 3) in highp VFORMAT blend_vertex;
+#ifdef USE_NORMAL
+layout(location = 4) in highp vec2 blend_normal;
+#endif
+#ifdef USE_TANGENT
+layout(location = 5) in highp vec2 blend_tangent;
+#endif
+#endif // MODE_BLEND_PASS
+
+out highp VFORMAT out_vertex; //tfb:
+
+#ifdef USE_NORMAL
+flat out highp OFORMAT out_normal; //tfb:USE_NORMAL
+#endif
+#ifdef USE_TANGENT
+flat out highp OFORMAT out_tangent; //tfb:USE_TANGENT
+#endif
+
+#ifdef USE_BLEND_SHAPES
+uniform highp float blend_weight;
+uniform lowp float blend_shape_count;
+#endif
+
+vec2 signNotZero(vec2 v) {
+ return mix(vec2(-1.0), vec2(1.0), greaterThanEqual(v.xy, vec2(0.0)));
+}
+
+vec3 oct_to_vec3(vec2 oct) {
+ oct = oct * 2.0 - 1.0;
+ vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));
+ if (v.z < 0.0) {
+ v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy);
+ }
+ return normalize(v);
+}
+
+vec2 vec3_to_oct(vec3 e) {
+ e /= abs(e.x) + abs(e.y) + abs(e.z);
+ vec2 oct = e.z >= 0.0f ? e.xy : (vec2(1.0f) - abs(e.yx)) * signNotZero(e.xy);
+ return oct * 0.5f + 0.5f;
+}
+
+vec4 oct_to_tang(vec2 oct_sign_encoded) {
+ // Binormal sign encoded in y component
+ vec2 oct = vec2(oct_sign_encoded.x, abs(oct_sign_encoded.y) * 2.0 - 1.0);
+ return vec4(oct_to_vec3(oct), sign(oct_sign_encoded.y));
+}
+
+vec2 tang_to_oct(vec4 base) {
+ vec2 oct = vec3_to_oct(base.xyz);
+ // Encode binormal sign in y component
+ oct.y = oct.y * 0.5f + 0.5f;
+ oct.y = base.w >= 0.0f ? oct.y : 1.0 - oct.y;
+ return oct;
+}
+
+// Our original input for normals and tangents is 2 16-bit floats.
+// Transform Feedback has to write out 32-bits per channel.
+// Octahedral compression requires normalized vectors, but we need to store
+// non-normalized vectors until the very end.
+// Therefore, we will compress our normals into 16 bits using signed-normalized
+// fixed point precision. This works well, because we know that each normal
+// is no larger than |1| so we can normalize by dividing by the number of blend
+// shapes.
+uvec2 vec4_to_vec2(vec4 p_vec) {
+ return uvec2(packSnorm2x16(p_vec.xy), packSnorm2x16(p_vec.zw));
+}
+
+vec4 vec2_to_vec4(uvec2 p_vec) {
+ return vec4(unpackSnorm2x16(p_vec.x), unpackSnorm2x16(p_vec.y));
+}
+
+void main() {
+#ifdef MODE_2D
+ out_vertex = in_vertex;
+
+#ifdef USE_BLEND_SHAPES
+#ifdef MODE_BLEND_PASS
+ out_vertex = in_vertex + blend_vertex * blend_weight;
+#else
+ out_vertex = in_vertex * blend_weight;
+#endif
+#ifdef FINAL_PASS
+ out_vertex = normalize(out_vertex);
+#endif
+#endif // USE_BLEND_SHAPES
+
+#ifdef USE_SKELETON
+
+#define TEX(m) texelFetch(skeleton_texture, ivec2(m % 256u, m / 256u), 0)
+#define GET_BONE_MATRIX(a, b, w) mat2x4(TEX(a), TEX(b)) * w
+
+ uvec4 bones = in_bone_attrib * uvec4(2u);
+ uvec4 bones_a = bones + uvec4(1u);
+
+ highp mat2x4 m = GET_BONE_MATRIX(bones.x, bones_a.x, in_weight_attrib.x);
+ m += GET_BONE_MATRIX(bones.y, bones_a.y, in_weight_attrib.y);
+ m += GET_BONE_MATRIX(bones.z, bones_a.z, in_weight_attrib.z);
+ m += GET_BONE_MATRIX(bones.w, bones_a.w, in_weight_attrib.w);
+
+ mat4 bone_matrix = mat4(m[0], m[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+
+ //reverse order because its transposed
+ out_vertex = (vec4(out_vertex, 0.0, 1.0) * bone_matrix).xy;
+#endif // USE_SKELETON
+
+#else // MODE_2D
+
+#ifdef USE_BLEND_SHAPES
+#ifdef MODE_BLEND_PASS
+ out_vertex = in_vertex + blend_vertex * blend_weight;
+
+#ifdef USE_NORMAL
+ vec3 normal = vec2_to_vec4(in_normal).xyz * blend_shape_count;
+ vec3 normal_blend = oct_to_vec3(blend_normal) * blend_weight;
+#ifdef FINAL_PASS
+ out_normal = vec3_to_oct(normalize(normal + normal_blend));
+#else
+ out_normal = vec4_to_vec2(vec4(normal + normal_blend, 0.0) / blend_shape_count);
+#endif
+#endif // USE_NORMAL
+
+#ifdef USE_TANGENT
+ vec4 tangent = vec2_to_vec4(in_tangent) * blend_shape_count;
+ vec4 tangent_blend = oct_to_tang(blend_tangent) * blend_weight;
+#ifdef FINAL_PASS
+ out_tangent = tang_to_oct(vec4(normalize(tangent.xyz + tangent_blend.xyz), tangent.w));
+#else
+ out_tangent = vec4_to_vec2(vec4((tangent.xyz + tangent_blend.xyz) / blend_shape_count, tangent.w));
+#endif
+#endif // USE_TANGENT
+
+#else // MODE_BLEND_PASS
+ out_vertex = in_vertex * blend_weight;
+
+#ifdef USE_NORMAL
+ vec3 normal = oct_to_vec3(in_normal);
+ out_normal = vec4_to_vec2(vec4(normal * blend_weight / blend_shape_count, 0.0));
+#endif
+#ifdef USE_TANGENT
+ vec4 tangent = oct_to_tang(in_tangent);
+ out_tangent = vec4_to_vec2(vec4(tangent.rgb * blend_weight / blend_shape_count, tangent.w));
+#endif
+#endif // MODE_BLEND_PASS
+#else // USE_BLEND_SHAPES
+
+ // Make attributes available to the skeleton shader if not written by blend shapes.
+ out_vertex = in_vertex;
+#ifdef USE_NORMAL
+ out_normal = in_normal;
+#endif
+#ifdef USE_TANGENT
+ out_tangent = in_tangent;
+#endif
+#endif // USE_BLEND_SHAPES
+
+#ifdef USE_SKELETON
+
+#define TEX(m) texelFetch(skeleton_texture, ivec2(m % 256u, m / 256u), 0)
+#define GET_BONE_MATRIX(a, b, c, w) mat4(TEX(a), TEX(b), TEX(c), vec4(0.0, 0.0, 0.0, 1.0)) * w
+
+ uvec4 bones = in_bone_attrib * uvec4(3);
+ uvec4 bones_a = bones + uvec4(1);
+ uvec4 bones_b = bones + uvec4(2);
+
+ highp mat4 m;
+ m = GET_BONE_MATRIX(bones.x, bones_a.x, bones_b.x, in_weight_attrib.x);
+ m += GET_BONE_MATRIX(bones.y, bones_a.y, bones_b.y, in_weight_attrib.y);
+ m += GET_BONE_MATRIX(bones.z, bones_a.z, bones_b.z, in_weight_attrib.z);
+ m += GET_BONE_MATRIX(bones.w, bones_a.w, bones_b.w, in_weight_attrib.w);
+
+#ifdef USE_EIGHT_WEIGHTS
+ bones = in_bone_attrib2 * uvec4(3);
+ bones_a = bones + uvec4(1);
+ bones_b = bones + uvec4(2);
+
+ m += GET_BONE_MATRIX(bones.x, bones_a.x, bones_b.x, in_weight_attrib2.x);
+ m += GET_BONE_MATRIX(bones.y, bones_a.y, bones_b.y, in_weight_attrib2.y);
+ m += GET_BONE_MATRIX(bones.z, bones_a.z, bones_b.z, in_weight_attrib2.z);
+ m += GET_BONE_MATRIX(bones.w, bones_a.w, bones_b.w, in_weight_attrib2.w);
+#endif
+
+ // Reverse order because its transposed.
+ out_vertex = (vec4(out_vertex, 1.0) * m).xyz;
+#ifdef USE_NORMAL
+ vec3 vertex_normal = oct_to_vec3(out_normal);
+ out_normal = vec3_to_oct(normalize((vec4(vertex_normal, 0.0) * m).xyz));
+#endif // USE_NORMAL
+#ifdef USE_TANGENT
+ vec4 vertex_tangent = oct_to_tang(out_tangent);
+ out_tangent = tang_to_oct(vec4(normalize((vec4(vertex_tangent.xyz, 0.0) * m).xyz), vertex_tangent.w));
+#endif // USE_TANGENT
+#endif // USE_SKELETON
+#endif // MODE_2D
+}
+
+/* clang-format off */
+#[fragment]
+
+void main() {
+
+}
+/* clang-format on */
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index a47df42500..285f32f1a5 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -44,10 +44,16 @@ MeshStorage *MeshStorage::get_singleton() {
MeshStorage::MeshStorage() {
singleton = this;
+
+ {
+ skeleton_shader.shader.initialize();
+ skeleton_shader.shader_version = skeleton_shader.shader.version_create();
+ }
}
MeshStorage::~MeshStorage() {
singleton = nullptr;
+ skeleton_shader.shader.version_free(skeleton_shader.shader_version);
}
/* MESH API */
@@ -88,10 +94,6 @@ void MeshStorage::mesh_set_blend_shape_count(RID p_mesh, int p_blend_shape_count
ERR_FAIL_COND(mesh->surface_count > 0); //surfaces already exist
mesh->blend_shape_count = p_blend_shape_count;
-
- if (p_blend_shape_count > 0) {
- WARN_PRINT_ONCE("blend shapes not supported by GLES3 renderer yet");
- }
}
bool MeshStorage::mesh_needs_instance(RID p_mesh, bool p_has_skeleton) {
@@ -114,7 +116,6 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface)
uint32_t attrib_stride = 0;
uint32_t skin_stride = 0;
- // TODO: I think this should be <=, but it is copied from RendererRD, will have to verify later
for (int i = 0; i < RS::ARRAY_WEIGHTS; i++) {
if ((p_surface.format & (1 << i))) {
switch (i) {
@@ -248,8 +249,77 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface)
s->aabb = p_surface.aabb;
s->bone_aabbs = p_surface.bone_aabbs; //only really useful for returning them.
- if (mesh->blend_shape_count > 0) {
- //s->blend_shape_buffer = RD::get_singleton()->storage_buffer_create(p_surface.blend_shape_data.size(), p_surface.blend_shape_data);
+ if (p_surface.skin_data.size() || mesh->blend_shape_count > 0) {
+ // Size must match the size of the vertex array.
+ int size = p_surface.vertex_data.size();
+ int vertex_size = 0;
+ int stride = 0;
+ int normal_offset = 0;
+ int tangent_offset = 0;
+ if ((p_surface.format & (1 << RS::ARRAY_VERTEX))) {
+ if (p_surface.format & RS::ARRAY_FLAG_USE_2D_VERTICES) {
+ vertex_size = 2;
+ } else {
+ vertex_size = 3;
+ }
+ stride = sizeof(float) * vertex_size;
+ }
+ if ((p_surface.format & (1 << RS::ARRAY_NORMAL))) {
+ normal_offset = stride;
+ stride += sizeof(uint16_t) * 2;
+ }
+ if ((p_surface.format & (1 << RS::ARRAY_TANGENT))) {
+ tangent_offset = stride;
+ stride += sizeof(uint16_t) * 2;
+ }
+
+ if (mesh->blend_shape_count > 0) {
+ // Blend shapes are passed as one large array, for OpenGL, we need to split each of them into their own buffer
+ s->blend_shapes = memnew_arr(Mesh::Surface::BlendShape, mesh->blend_shape_count);
+
+ for (uint32_t i = 0; i < mesh->blend_shape_count; i++) {
+ glGenVertexArrays(1, &s->blend_shapes[i].vertex_array);
+ glBindVertexArray(s->blend_shapes[i].vertex_array);
+ glGenBuffers(1, &s->blend_shapes[i].vertex_buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, s->blend_shapes[i].vertex_buffer);
+ glBufferData(GL_ARRAY_BUFFER, size, p_surface.blend_shape_data.ptr() + i * size, (s->format & RS::ARRAY_FLAG_USE_DYNAMIC_UPDATE) ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+
+ if ((p_surface.format & (1 << RS::ARRAY_VERTEX))) {
+ glEnableVertexAttribArray(RS::ARRAY_VERTEX + 3);
+ glVertexAttribPointer(RS::ARRAY_VERTEX + 3, vertex_size, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(0));
+ }
+ if ((p_surface.format & (1 << RS::ARRAY_NORMAL))) {
+ glEnableVertexAttribArray(RS::ARRAY_NORMAL + 3);
+ glVertexAttribPointer(RS::ARRAY_NORMAL + 3, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, CAST_INT_TO_UCHAR_PTR(normal_offset));
+ }
+ if ((p_surface.format & (1 << RS::ARRAY_TANGENT))) {
+ glEnableVertexAttribArray(RS::ARRAY_TANGENT + 3);
+ glVertexAttribPointer(RS::ARRAY_TANGENT + 3, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, CAST_INT_TO_UCHAR_PTR(tangent_offset));
+ }
+ }
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+
+ // Create a vertex array to use for skeleton/blend shapes.
+ glGenVertexArrays(1, &s->skeleton_vertex_array);
+ glBindVertexArray(s->skeleton_vertex_array);
+ glBindBuffer(GL_ARRAY_BUFFER, s->vertex_buffer);
+
+ if ((p_surface.format & (1 << RS::ARRAY_VERTEX))) {
+ glEnableVertexAttribArray(RS::ARRAY_VERTEX);
+ glVertexAttribPointer(RS::ARRAY_VERTEX, vertex_size, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(0));
+ }
+ if ((p_surface.format & (1 << RS::ARRAY_NORMAL))) {
+ glEnableVertexAttribArray(RS::ARRAY_NORMAL);
+ glVertexAttribPointer(RS::ARRAY_NORMAL, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, CAST_INT_TO_UCHAR_PTR(normal_offset));
+ }
+ if ((p_surface.format & (1 << RS::ARRAY_TANGENT))) {
+ glEnableVertexAttribArray(RS::ARRAY_TANGENT);
+ glVertexAttribPointer(RS::ARRAY_TANGENT, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, CAST_INT_TO_UCHAR_PTR(tangent_offset));
+ }
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
}
if (mesh->surface_count == 0) {
@@ -412,7 +482,13 @@ RS::SurfaceData MeshStorage::mesh_get_surface(RID p_mesh, int p_surface) const {
}
sd.bone_aabbs = s.bone_aabbs;
- glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ if (mesh->blend_shape_count) {
+ sd.blend_shape_data = Vector<uint8_t>();
+ for (uint32_t i = 0; i < mesh->blend_shape_count; i++) {
+ sd.blend_shape_data.append_array(Utilities::buffer_get_data(GL_ARRAY_BUFFER, s.blend_shapes[i].vertex_buffer, s.vertex_buffer_size));
+ }
+ }
return sd;
}
@@ -608,6 +684,24 @@ void MeshStorage::mesh_clear(RID p_mesh) {
memdelete_arr(s.lods);
}
+ if (mesh->blend_shape_count) {
+ for (uint32_t j = 0; j < mesh->blend_shape_count; j++) {
+ if (s.blend_shapes[j].vertex_buffer != 0) {
+ glDeleteBuffers(1, &s.blend_shapes[j].vertex_buffer);
+ s.blend_shapes[j].vertex_buffer = 0;
+ }
+ if (s.blend_shapes[j].vertex_array != 0) {
+ glDeleteVertexArrays(1, &s.blend_shapes[j].vertex_array);
+ s.blend_shapes[j].vertex_array = 0;
+ }
+ }
+ memdelete_arr(s.blend_shapes);
+ }
+ if (s.skeleton_vertex_array != 0) {
+ glDeleteVertexArrays(1, &s.skeleton_vertex_array);
+ s.skeleton_vertex_array = 0;
+ }
+
memdelete(mesh->surfaces[i]);
}
if (mesh->surfaces) {
@@ -663,15 +757,15 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
case RS::ARRAY_NORMAL: {
attribs[i].offset = vertex_stride;
attribs[i].size = 2;
- attribs[i].type = GL_UNSIGNED_SHORT;
- vertex_stride += sizeof(uint16_t) * 2;
+ attribs[i].type = (mis ? GL_FLOAT : GL_UNSIGNED_SHORT);
+ vertex_stride += sizeof(uint16_t) * 2 * (mis ? 2 : 1);
attribs[i].normalized = GL_TRUE;
} break;
case RS::ARRAY_TANGENT: {
attribs[i].offset = vertex_stride;
attribs[i].size = 2;
- attribs[i].type = GL_UNSIGNED_SHORT;
- vertex_stride += sizeof(uint16_t) * 2;
+ attribs[i].type = (mis ? GL_FLOAT : GL_UNSIGNED_SHORT);
+ vertex_stride += sizeof(uint16_t) * 2 * (mis ? 2 : 1);
attribs[i].normalized = GL_TRUE;
} break;
case RS::ARRAY_COLOR: {
@@ -716,7 +810,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
attribs[i].offset = skin_stride;
attribs[i].size = 4;
attribs[i].type = GL_UNSIGNED_SHORT;
- attributes_stride += 4 * sizeof(uint16_t);
+ skin_stride += 4 * sizeof(uint16_t);
attribs[i].normalized = GL_FALSE;
attribs[i].integer = true;
} break;
@@ -724,7 +818,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
attribs[i].offset = skin_stride;
attribs[i].size = 4;
attribs[i].type = GL_UNSIGNED_SHORT;
- attributes_stride += 4 * sizeof(uint16_t);
+ skin_stride += 4 * sizeof(uint16_t);
attribs[i].normalized = GL_TRUE;
} break;
}
@@ -815,7 +909,7 @@ void MeshStorage::mesh_instance_set_blend_shape_weight(RID p_mesh_instance, int
ERR_FAIL_COND(!mi);
ERR_FAIL_INDEX(p_shape, (int)mi->blend_weights.size());
mi->blend_weights[p_shape] = p_weight;
- mi->weights_dirty = true;
+ mi->dirty = true;
}
void MeshStorage::_mesh_instance_clear(MeshInstance *mi) {
@@ -827,38 +921,65 @@ void MeshStorage::_mesh_instance_clear(MeshInstance *mi) {
}
memfree(mi->surfaces[i].versions);
}
+
+ if (mi->surfaces[i].vertex_buffers[0] != 0) {
+ glDeleteBuffers(2, mi->surfaces[i].vertex_buffers);
+ mi->surfaces[i].vertex_buffers[0] = 0;
+ mi->surfaces[i].vertex_buffers[1] = 0;
+ }
+
if (mi->surfaces[i].vertex_buffer != 0) {
glDeleteBuffers(1, &mi->surfaces[i].vertex_buffer);
mi->surfaces[i].vertex_buffer = 0;
}
}
mi->surfaces.clear();
-
- if (mi->blend_weights_buffer != 0) {
- glDeleteBuffers(1, &mi->blend_weights_buffer);
- mi->blend_weights_buffer = 0;
- }
mi->blend_weights.clear();
- mi->weights_dirty = false;
mi->skeleton_version = 0;
}
void MeshStorage::_mesh_instance_add_surface(MeshInstance *mi, Mesh *mesh, uint32_t p_surface) {
- if (mesh->blend_shape_count > 0 && mi->blend_weights_buffer == 0) {
+ if (mesh->blend_shape_count > 0) {
mi->blend_weights.resize(mesh->blend_shape_count);
for (uint32_t i = 0; i < mi->blend_weights.size(); i++) {
- mi->blend_weights[i] = 0;
+ mi->blend_weights[i] = 0.0;
}
- // Todo allocate buffer for blend_weights and copy data to it
- //mi->blend_weights_buffer = RD::get_singleton()->storage_buffer_create(sizeof(float) * mi->blend_weights.size(), mi->blend_weights.to_byte_array());
-
- mi->weights_dirty = true;
}
MeshInstance::Surface s;
- if (mesh->blend_shape_count > 0 || (mesh->surfaces[p_surface]->format & RS::ARRAY_FORMAT_BONES)) {
- //surface warrants transform
- //s.vertex_buffer = RD::get_singleton()->vertex_buffer_create(mesh->surfaces[p_surface]->vertex_buffer_size, Vector<uint8_t>(), true);
+ if ((mesh->blend_shape_count > 0 || (mesh->surfaces[p_surface]->format & RS::ARRAY_FORMAT_BONES)) && mesh->surfaces[p_surface]->vertex_buffer_size > 0) {
+ // Cache surface properties
+ s.format_cache = mesh->surfaces[p_surface]->format;
+ if ((s.format_cache & (1 << RS::ARRAY_VERTEX))) {
+ if (s.format_cache & RS::ARRAY_FLAG_USE_2D_VERTICES) {
+ s.vertex_size_cache = 2;
+ } else {
+ s.vertex_size_cache = 3;
+ }
+ s.vertex_stride_cache = sizeof(float) * s.vertex_size_cache;
+ }
+ if ((s.format_cache & (1 << RS::ARRAY_NORMAL))) {
+ s.vertex_normal_offset_cache = s.vertex_stride_cache;
+ s.vertex_stride_cache += sizeof(uint32_t) * 2;
+ }
+ if ((s.format_cache & (1 << RS::ARRAY_TANGENT))) {
+ s.vertex_tangent_offset_cache = s.vertex_stride_cache;
+ s.vertex_stride_cache += sizeof(uint32_t) * 2;
+ }
+
+ // Buffer to be used for rendering. Final output of skeleton and blend shapes.
+ glGenBuffers(1, &s.vertex_buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, s.vertex_buffer);
+ glBufferData(GL_ARRAY_BUFFER, s.vertex_stride_cache * mesh->surfaces[p_surface]->vertex_count, nullptr, GL_DYNAMIC_DRAW);
+ if (mesh->blend_shape_count > 0) {
+ // Ping-Pong buffers for processing blendshapes.
+ glGenBuffers(2, s.vertex_buffers);
+ for (uint32_t i = 0; i < 2; i++) {
+ glBindBuffer(GL_ARRAY_BUFFER, s.vertex_buffers[i]);
+ glBufferData(GL_ARRAY_BUFFER, s.vertex_stride_cache * mesh->surfaces[p_surface]->vertex_count, nullptr, GL_DYNAMIC_DRAW);
+ }
+ }
+ glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
}
mi->surfaces.push_back(s);
@@ -870,11 +991,6 @@ void MeshStorage::mesh_instance_check_for_update(RID p_mesh_instance) {
bool needs_update = mi->dirty;
- if (mi->weights_dirty && !mi->weight_update_list.in_list()) {
- dirty_mesh_instance_weights.add(&mi->weight_update_list);
- needs_update = true;
- }
-
if (mi->array_update_list.in_list()) {
return;
}
@@ -891,22 +1007,223 @@ void MeshStorage::mesh_instance_check_for_update(RID p_mesh_instance) {
}
}
-void MeshStorage::update_mesh_instances() {
- while (dirty_mesh_instance_weights.first()) {
- MeshInstance *mi = dirty_mesh_instance_weights.first()->self();
+void MeshStorage::_blend_shape_bind_mesh_instance_buffer(MeshInstance *p_mi, uint32_t p_surface) {
+ glBindBuffer(GL_ARRAY_BUFFER, p_mi->surfaces[p_surface].vertex_buffers[0]);
- if (mi->blend_weights_buffer != 0) {
- //RD::get_singleton()->buffer_update(mi->blend_weights_buffer, 0, mi->blend_weights.size() * sizeof(float), mi->blend_weights.ptr());
- }
- dirty_mesh_instance_weights.remove(&mi->weight_update_list);
- mi->weights_dirty = false;
+ if ((p_mi->surfaces[p_surface].format_cache & (1 << RS::ARRAY_VERTEX))) {
+ glEnableVertexAttribArray(RS::ARRAY_VERTEX);
+ glVertexAttribPointer(RS::ARRAY_VERTEX, p_mi->surfaces[p_surface].vertex_size_cache, GL_FLOAT, GL_FALSE, p_mi->surfaces[p_surface].vertex_stride_cache, CAST_INT_TO_UCHAR_PTR(0));
+ } else {
+ glDisableVertexAttribArray(RS::ARRAY_VERTEX);
}
+ if ((p_mi->surfaces[p_surface].format_cache & (1 << RS::ARRAY_NORMAL))) {
+ glEnableVertexAttribArray(RS::ARRAY_NORMAL);
+ glVertexAttribIPointer(RS::ARRAY_NORMAL, 2, GL_UNSIGNED_INT, p_mi->surfaces[p_surface].vertex_stride_cache, CAST_INT_TO_UCHAR_PTR(p_mi->surfaces[p_surface].vertex_normal_offset_cache));
+ } else {
+ glDisableVertexAttribArray(RS::ARRAY_NORMAL);
+ }
+ if ((p_mi->surfaces[p_surface].format_cache & (1 << RS::ARRAY_TANGENT))) {
+ glEnableVertexAttribArray(RS::ARRAY_TANGENT);
+ glVertexAttribIPointer(RS::ARRAY_TANGENT, 2, GL_UNSIGNED_INT, p_mi->surfaces[p_surface].vertex_stride_cache, CAST_INT_TO_UCHAR_PTR(p_mi->surfaces[p_surface].vertex_tangent_offset_cache));
+ } else {
+ glDisableVertexAttribArray(RS::ARRAY_TANGENT);
+ }
+}
+
+void MeshStorage::_compute_skeleton(MeshInstance *p_mi, Skeleton *p_sk, uint32_t p_surface) {
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ // Add in the bones and weights.
+ glBindBuffer(GL_ARRAY_BUFFER, p_mi->mesh->surfaces[p_surface]->skin_buffer);
+
+ bool use_8_weights = p_mi->surfaces[p_surface].format_cache & RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS;
+ int skin_stride = sizeof(int16_t) * (use_8_weights ? 16 : 8);
+ glEnableVertexAttribArray(RS::ARRAY_BONES);
+ glVertexAttribIPointer(RS::ARRAY_BONES, 4, GL_UNSIGNED_SHORT, skin_stride, CAST_INT_TO_UCHAR_PTR(0));
+ if (use_8_weights) {
+ glEnableVertexAttribArray(11);
+ glVertexAttribIPointer(11, 4, GL_UNSIGNED_SHORT, skin_stride, CAST_INT_TO_UCHAR_PTR(4 * sizeof(uint16_t)));
+ glEnableVertexAttribArray(12);
+ glVertexAttribPointer(12, 4, GL_UNSIGNED_SHORT, GL_TRUE, skin_stride, CAST_INT_TO_UCHAR_PTR(8 * sizeof(uint16_t)));
+ glEnableVertexAttribArray(13);
+ glVertexAttribPointer(13, 4, GL_UNSIGNED_SHORT, GL_TRUE, skin_stride, CAST_INT_TO_UCHAR_PTR(12 * sizeof(uint16_t)));
+ } else {
+ glEnableVertexAttribArray(RS::ARRAY_WEIGHTS);
+ glVertexAttribPointer(RS::ARRAY_WEIGHTS, 4, GL_UNSIGNED_SHORT, GL_TRUE, skin_stride, CAST_INT_TO_UCHAR_PTR(4 * sizeof(uint16_t)));
+ }
+
+ glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, p_mi->surfaces[p_surface].vertex_buffer);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, p_sk->transforms_texture);
+
+ glBeginTransformFeedback(GL_POINTS);
+ glDrawArrays(GL_POINTS, 0, p_mi->mesh->surfaces[p_surface]->vertex_count);
+ glEndTransformFeedback();
+
+ glDisableVertexAttribArray(RS::ARRAY_BONES);
+ glDisableVertexAttribArray(RS::ARRAY_WEIGHTS);
+ glDisableVertexAttribArray(RS::ARRAY_BONES + 2);
+ glDisableVertexAttribArray(RS::ARRAY_WEIGHTS + 2);
+ glBindVertexArray(0);
+ glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
+}
+
+void MeshStorage::update_mesh_instances() {
if (dirty_mesh_instance_arrays.first() == nullptr) {
return; //nothing to do
}
+ glEnable(GL_RASTERIZER_DISCARD);
// Process skeletons and blend shapes using transform feedback
- // TODO: Implement when working on skeletons and blend shapes
+ while (dirty_mesh_instance_arrays.first()) {
+ MeshInstance *mi = dirty_mesh_instance_arrays.first()->self();
+
+ Skeleton *sk = skeleton_owner.get_or_null(mi->skeleton);
+
+ // Precompute base weight if using blend shapes.
+ float base_weight = 1.0;
+ if (mi->mesh->blend_shape_count && mi->mesh->blend_shape_mode == RS::BLEND_SHAPE_MODE_NORMALIZED) {
+ for (uint32_t i = 0; i < mi->mesh->blend_shape_count; i++) {
+ base_weight -= mi->blend_weights[i];
+ }
+ }
+
+ for (uint32_t i = 0; i < mi->surfaces.size(); i++) {
+ if (mi->surfaces[i].vertex_buffer == 0 || mi->mesh->surfaces[i]->skeleton_vertex_array == 0) {
+ continue;
+ }
+
+ bool array_is_2d = mi->surfaces[i].format_cache & RS::ARRAY_FLAG_USE_2D_VERTICES;
+ bool can_use_skeleton = sk != nullptr && sk->use_2d == array_is_2d && (mi->surfaces[i].format_cache & RS::ARRAY_FORMAT_BONES);
+ bool use_8_weights = mi->surfaces[i].format_cache & RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS;
+
+ // Always process blend shapes first.
+ if (mi->mesh->blend_shape_count) {
+ SkeletonShaderGLES3::ShaderVariant variant = SkeletonShaderGLES3::MODE_BASE_PASS;
+ uint64_t specialization = 0;
+ specialization |= array_is_2d ? SkeletonShaderGLES3::MODE_2D : 0;
+ specialization |= SkeletonShaderGLES3::USE_BLEND_SHAPES;
+ if (!array_is_2d) {
+ if ((mi->surfaces[i].format_cache & (1 << RS::ARRAY_NORMAL))) {
+ specialization |= SkeletonShaderGLES3::USE_NORMAL;
+ }
+ if ((mi->surfaces[i].format_cache & (1 << RS::ARRAY_TANGENT))) {
+ specialization |= SkeletonShaderGLES3::USE_TANGENT;
+ }
+ }
+
+ bool success = skeleton_shader.shader.version_bind_shader(skeleton_shader.shader_version, variant, specialization);
+ if (!success) {
+ continue;
+ }
+
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::BLEND_WEIGHT, base_weight, skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::BLEND_SHAPE_COUNT, float(mi->mesh->blend_shape_count), skeleton_shader.shader_version, variant, specialization);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(mi->mesh->surfaces[i]->skeleton_vertex_array);
+ glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mi->surfaces[i].vertex_buffers[0]);
+ glBeginTransformFeedback(GL_POINTS);
+ glDrawArrays(GL_POINTS, 0, mi->mesh->surfaces[i]->vertex_count);
+ glEndTransformFeedback();
+
+ variant = SkeletonShaderGLES3::MODE_BLEND_PASS;
+ success = skeleton_shader.shader.version_bind_shader(skeleton_shader.shader_version, variant, specialization);
+ if (!success) {
+ continue;
+ }
+
+ //Do the last blend shape separately, as it can be combined with the skeleton pass.
+ for (uint32_t bs = 0; bs < mi->mesh->blend_shape_count - 1; bs++) {
+ float weight = mi->blend_weights[bs];
+
+ if (Math::is_zero_approx(weight)) {
+ //not bother with this one
+ continue;
+ }
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::BLEND_WEIGHT, weight, skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::BLEND_SHAPE_COUNT, float(mi->mesh->blend_shape_count), skeleton_shader.shader_version, variant, specialization);
+
+ glBindVertexArray(mi->mesh->surfaces[i]->blend_shapes[bs].vertex_array);
+ _blend_shape_bind_mesh_instance_buffer(mi, i);
+ glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mi->surfaces[i].vertex_buffers[1]);
+
+ glBeginTransformFeedback(GL_POINTS);
+ glDrawArrays(GL_POINTS, 0, mi->mesh->surfaces[i]->vertex_count);
+ glEndTransformFeedback();
+
+ SWAP(mi->surfaces[i].vertex_buffers[0], mi->surfaces[i].vertex_buffers[1]);
+ }
+ uint32_t bs = mi->mesh->blend_shape_count - 1;
+
+ float weight = mi->blend_weights[bs];
+
+ glBindVertexArray(mi->mesh->surfaces[i]->blend_shapes[bs].vertex_array);
+ _blend_shape_bind_mesh_instance_buffer(mi, i);
+
+ specialization |= can_use_skeleton ? SkeletonShaderGLES3::USE_SKELETON : 0;
+ specialization |= (can_use_skeleton && use_8_weights) ? SkeletonShaderGLES3::USE_EIGHT_WEIGHTS : 0;
+ specialization |= SkeletonShaderGLES3::FINAL_PASS;
+ success = skeleton_shader.shader.version_bind_shader(skeleton_shader.shader_version, variant, specialization);
+ if (!success) {
+ continue;
+ }
+
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::BLEND_WEIGHT, weight, skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::BLEND_SHAPE_COUNT, float(mi->mesh->blend_shape_count), skeleton_shader.shader_version, variant, specialization);
+
+ if (can_use_skeleton) {
+ // Do last blendshape in the same pass as the Skeleton.
+ _compute_skeleton(mi, sk, i);
+ can_use_skeleton = false;
+ } else {
+ // Do last blendshape by itself and prepare vertex data for use by the renderer.
+ glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mi->surfaces[i].vertex_buffer);
+
+ glBeginTransformFeedback(GL_POINTS);
+ glDrawArrays(GL_POINTS, 0, mi->mesh->surfaces[i]->vertex_count);
+ glEndTransformFeedback();
+ }
+
+ glBindVertexArray(0);
+ glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
+ }
+
+ // This branch should only execute when Skeleton is run by itself.
+ if (can_use_skeleton) {
+ SkeletonShaderGLES3::ShaderVariant variant = SkeletonShaderGLES3::MODE_BASE_PASS;
+ uint64_t specialization = 0;
+ specialization |= array_is_2d ? SkeletonShaderGLES3::MODE_2D : 0;
+ specialization |= SkeletonShaderGLES3::USE_SKELETON;
+ specialization |= SkeletonShaderGLES3::FINAL_PASS;
+ specialization |= use_8_weights ? SkeletonShaderGLES3::USE_EIGHT_WEIGHTS : 0;
+ if (!array_is_2d) {
+ if ((mi->surfaces[i].format_cache & (1 << RS::ARRAY_NORMAL))) {
+ specialization |= SkeletonShaderGLES3::USE_NORMAL;
+ }
+ if ((mi->surfaces[i].format_cache & (1 << RS::ARRAY_TANGENT))) {
+ specialization |= SkeletonShaderGLES3::USE_TANGENT;
+ }
+ }
+
+ bool success = skeleton_shader.shader.version_bind_shader(skeleton_shader.shader_version, variant, specialization);
+ if (!success) {
+ continue;
+ }
+
+ glBindVertexArray(mi->mesh->surfaces[i]->skeleton_vertex_array);
+ _compute_skeleton(mi, sk, i);
+ }
+ }
+ mi->dirty = false;
+ if (sk) {
+ mi->skeleton_version = sk->version;
+ }
+ dirty_mesh_instance_arrays.remove(&mi->array_update_list);
+ }
+ glDisable(GL_RASTERIZER_DISCARD);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
}
/* MULTIMESH API */
@@ -1577,45 +1894,207 @@ void MeshStorage::_update_dirty_multimeshes() {
/* SKELETON API */
RID MeshStorage::skeleton_allocate() {
- return RID();
+ return skeleton_owner.allocate_rid();
}
void MeshStorage::skeleton_initialize(RID p_rid) {
+ skeleton_owner.initialize_rid(p_rid, Skeleton());
}
void MeshStorage::skeleton_free(RID p_rid) {
+ _update_dirty_skeletons();
+ skeleton_allocate_data(p_rid, 0);
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_rid);
+ skeleton->dependency.deleted_notify(p_rid);
+ skeleton_owner.free(p_rid);
+}
+
+void MeshStorage::_skeleton_make_dirty(Skeleton *skeleton) {
+ if (!skeleton->dirty) {
+ skeleton->dirty = true;
+ skeleton->dirty_list = skeleton_dirty_list;
+ skeleton_dirty_list = skeleton;
+ }
}
void MeshStorage::skeleton_allocate_data(RID p_skeleton, int p_bones, bool p_2d_skeleton) {
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+ ERR_FAIL_COND(!skeleton);
+ ERR_FAIL_COND(p_bones < 0);
+
+ if (skeleton->size == p_bones && skeleton->use_2d == p_2d_skeleton) {
+ return;
+ }
+
+ skeleton->size = p_bones;
+ skeleton->use_2d = p_2d_skeleton;
+ skeleton->height = (p_bones * (p_2d_skeleton ? 2 : 3)) / 256;
+ if ((p_bones * (p_2d_skeleton ? 2 : 3)) % 256) {
+ skeleton->height++;
+ }
+
+ if (skeleton->transforms_texture != 0) {
+ glDeleteTextures(1, &skeleton->transforms_texture);
+ skeleton->transforms_texture = 0;
+ skeleton->data.clear();
+ }
+
+ if (skeleton->size) {
+ skeleton->data.resize(256 * skeleton->height * 4);
+ glGenTextures(1, &skeleton->transforms_texture);
+ glBindTexture(GL_TEXTURE_2D, skeleton->transforms_texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 256, skeleton->height, 0, GL_RGBA, GL_FLOAT, nullptr);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ memset(skeleton->data.ptrw(), 0, skeleton->data.size() * sizeof(float));
+
+ _skeleton_make_dirty(skeleton);
+ }
+
+ skeleton->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_SKELETON_DATA);
}
void MeshStorage::skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) {
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+
+ ERR_FAIL_NULL(skeleton);
+ ERR_FAIL_COND(!skeleton->use_2d);
+
+ skeleton->base_transform_2d = p_base_transform;
}
int MeshStorage::skeleton_get_bone_count(RID p_skeleton) const {
- return 0;
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+ ERR_FAIL_COND_V(!skeleton, 0);
+
+ return skeleton->size;
}
void MeshStorage::skeleton_bone_set_transform(RID p_skeleton, int p_bone, const Transform3D &p_transform) {
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+
+ ERR_FAIL_COND(!skeleton);
+ ERR_FAIL_INDEX(p_bone, skeleton->size);
+ ERR_FAIL_COND(skeleton->use_2d);
+
+ float *dataptr = skeleton->data.ptrw() + p_bone * 12;
+
+ dataptr[0] = p_transform.basis.rows[0][0];
+ dataptr[1] = p_transform.basis.rows[0][1];
+ dataptr[2] = p_transform.basis.rows[0][2];
+ dataptr[3] = p_transform.origin.x;
+ dataptr[4] = p_transform.basis.rows[1][0];
+ dataptr[5] = p_transform.basis.rows[1][1];
+ dataptr[6] = p_transform.basis.rows[1][2];
+ dataptr[7] = p_transform.origin.y;
+ dataptr[8] = p_transform.basis.rows[2][0];
+ dataptr[9] = p_transform.basis.rows[2][1];
+ dataptr[10] = p_transform.basis.rows[2][2];
+ dataptr[11] = p_transform.origin.z;
+
+ _skeleton_make_dirty(skeleton);
}
Transform3D MeshStorage::skeleton_bone_get_transform(RID p_skeleton, int p_bone) const {
- return Transform3D();
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+
+ ERR_FAIL_COND_V(!skeleton, Transform3D());
+ ERR_FAIL_INDEX_V(p_bone, skeleton->size, Transform3D());
+ ERR_FAIL_COND_V(skeleton->use_2d, Transform3D());
+
+ const float *dataptr = skeleton->data.ptr() + p_bone * 12;
+
+ Transform3D t;
+
+ t.basis.rows[0][0] = dataptr[0];
+ t.basis.rows[0][1] = dataptr[1];
+ t.basis.rows[0][2] = dataptr[2];
+ t.origin.x = dataptr[3];
+ t.basis.rows[1][0] = dataptr[4];
+ t.basis.rows[1][1] = dataptr[5];
+ t.basis.rows[1][2] = dataptr[6];
+ t.origin.y = dataptr[7];
+ t.basis.rows[2][0] = dataptr[8];
+ t.basis.rows[2][1] = dataptr[9];
+ t.basis.rows[2][2] = dataptr[10];
+ t.origin.z = dataptr[11];
+
+ return t;
}
void MeshStorage::skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, const Transform2D &p_transform) {
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+
+ ERR_FAIL_COND(!skeleton);
+ ERR_FAIL_INDEX(p_bone, skeleton->size);
+ ERR_FAIL_COND(!skeleton->use_2d);
+
+ float *dataptr = skeleton->data.ptrw() + p_bone * 8;
+
+ dataptr[0] = p_transform.columns[0][0];
+ dataptr[1] = p_transform.columns[1][0];
+ dataptr[2] = 0;
+ dataptr[3] = p_transform.columns[2][0];
+ dataptr[4] = p_transform.columns[0][1];
+ dataptr[5] = p_transform.columns[1][1];
+ dataptr[6] = 0;
+ dataptr[7] = p_transform.columns[2][1];
+
+ _skeleton_make_dirty(skeleton);
}
Transform2D MeshStorage::skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const {
- return Transform2D();
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+
+ ERR_FAIL_COND_V(!skeleton, Transform2D());
+ ERR_FAIL_INDEX_V(p_bone, skeleton->size, Transform2D());
+ ERR_FAIL_COND_V(!skeleton->use_2d, Transform2D());
+
+ const float *dataptr = skeleton->data.ptr() + p_bone * 8;
+
+ Transform2D t;
+ t.columns[0][0] = dataptr[0];
+ t.columns[1][0] = dataptr[1];
+ t.columns[2][0] = dataptr[3];
+ t.columns[0][1] = dataptr[4];
+ t.columns[1][1] = dataptr[5];
+ t.columns[2][1] = dataptr[7];
+
+ return t;
}
-void MeshStorage::skeleton_update_dependency(RID p_base, DependencyTracker *p_instance) {
+void MeshStorage::_update_dirty_skeletons() {
+ while (skeleton_dirty_list) {
+ Skeleton *skeleton = skeleton_dirty_list;
+
+ if (skeleton->size) {
+ glBindTexture(GL_TEXTURE_2D, skeleton->transforms_texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 256, skeleton->height, 0, GL_RGBA, GL_FLOAT, skeleton->data.ptr());
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ skeleton_dirty_list = skeleton->dirty_list;
+
+ skeleton->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_SKELETON_BONES);
+
+ skeleton->version++;
+
+ skeleton->dirty = false;
+ skeleton->dirty_list = nullptr;
+ }
+
+ skeleton_dirty_list = nullptr;
}
-/* OCCLUDER */
+void MeshStorage::skeleton_update_dependency(RID p_skeleton, DependencyTracker *p_instance) {
+ Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton);
+ ERR_FAIL_COND(!skeleton);
-void MeshStorage::occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices) {
+ p_instance->update_dependency(&skeleton->dependency);
}
#endif // GLES3_ENABLED
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index 1aef3cbf78..0f30814928 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -33,6 +33,7 @@
#ifdef GLES3_ENABLED
+#include "../shaders/skeleton.glsl.gen.h"
#include "core/templates/local_vector.h"
#include "core/templates/rid_owner.h"
#include "core/templates/self_list.h"
@@ -102,7 +103,13 @@ struct Mesh {
Vector<AABB> bone_aabbs;
- GLuint blend_shape_buffer = 0;
+ struct BlendShape {
+ GLuint vertex_buffer = 0;
+ GLuint vertex_array = 0;
+ };
+
+ BlendShape *blend_shapes = nullptr;
+ GLuint skeleton_vertex_array = 0;
RID material;
};
@@ -136,7 +143,14 @@ struct MeshInstance {
Mesh *mesh = nullptr;
RID skeleton;
struct Surface {
+ GLuint vertex_buffers[2] = { 0, 0 };
+ GLuint vertex_arrays[2] = { 0, 0 };
GLuint vertex_buffer = 0;
+ int vertex_stride_cache = 0;
+ int vertex_size_cache = 0;
+ int vertex_normal_offset_cache = 0;
+ int vertex_tangent_offset_cache = 0;
+ uint32_t format_cache = 0;
Mesh::Surface::Version *versions = nullptr; //allocated on demand
uint32_t version_count = 0;
@@ -144,7 +158,6 @@ struct MeshInstance {
LocalVector<Surface> surfaces;
LocalVector<float> blend_weights;
- GLuint blend_weights_buffer = 0;
List<MeshInstance *>::Element *I = nullptr; //used to erase itself
uint64_t skeleton_version = 0;
bool dirty = false;
@@ -186,13 +199,15 @@ struct MultiMesh {
struct Skeleton {
bool use_2d = false;
int size = 0;
+ int height = 0;
Vector<float> data;
- GLuint buffer = 0;
bool dirty = false;
Skeleton *dirty_list = nullptr;
Transform2D base_transform_2d;
+ GLuint transforms_texture = 0;
+
uint64_t version = 1;
Dependency dependency;
@@ -202,6 +217,11 @@ class MeshStorage : public RendererMeshStorage {
private:
static MeshStorage *singleton;
+ struct {
+ SkeletonShaderGLES3 shader;
+ RID shader_version;
+ } skeleton_shader;
+
/* Mesh */
mutable RID_Owner<Mesh, true> mesh_owner;
@@ -214,6 +234,7 @@ private:
void _mesh_instance_clear(MeshInstance *mi);
void _mesh_instance_add_surface(MeshInstance *mi, Mesh *mesh, uint32_t p_surface);
+ void _blend_shape_bind_mesh_instance_buffer(MeshInstance *p_mi, uint32_t p_surface);
SelfList<MeshInstance>::List dirty_mesh_instance_weights;
SelfList<MeshInstance>::List dirty_mesh_instance_arrays;
@@ -232,9 +253,10 @@ private:
mutable RID_Owner<Skeleton, true> skeleton_owner;
- Skeleton *skeleton_dirty_list = nullptr;
-
_FORCE_INLINE_ void _skeleton_make_dirty(Skeleton *skeleton);
+ void _compute_skeleton(MeshInstance *p_mi, Skeleton *p_sk, uint32_t p_surface);
+
+ Skeleton *skeleton_dirty_list = nullptr;
public:
static MeshStorage *get_singleton();
@@ -534,9 +556,11 @@ public:
virtual void skeleton_update_dependency(RID p_base, DependencyTracker *p_instance) override;
- /* OCCLUDER */
+ void _update_dirty_skeletons();
- void occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices);
+ _FORCE_INLINE_ bool skeleton_is_valid(RID p_skeleton) {
+ return skeleton_owner.get_or_null(p_skeleton) != nullptr;
+ }
};
} // namespace GLES3
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 15743c2d78..99908d197a 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -1694,34 +1694,51 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) {
return;
}
+ // Dispose of the cached fbo's and the allocated textures
+ for (KeyValue<uint32_t, RenderTarget::RTOverridden::FBOCacheEntry> &E : rt->overridden.fbo_cache) {
+ glDeleteTextures(E.value.allocated_textures.size(), E.value.allocated_textures.ptr());
+ // Don't delete the current FBO, we'll do that a couple lines down.
+ if (E.value.fbo != rt->fbo) {
+ glDeleteFramebuffers(1, &E.value.fbo);
+ }
+ }
+ rt->overridden.fbo_cache.clear();
+
if (rt->fbo) {
glDeleteFramebuffers(1, &rt->fbo);
rt->fbo = 0;
}
if (rt->overridden.color.is_null()) {
- glDeleteTextures(1, &rt->color);
- rt->color = 0;
+ if (rt->texture.is_valid()) {
+ Texture *tex = get_texture(rt->texture);
+ tex->alloc_height = 0;
+ tex->alloc_width = 0;
+ tex->width = 0;
+ tex->height = 0;
+ tex->active = false;
+ }
+ } else {
+ Texture *tex = get_texture(rt->overridden.color);
+ tex->is_render_target = false;
}
- if (rt->overridden.depth.is_null()) {
- glDeleteTextures(1, &rt->depth);
- rt->depth = 0;
+ if (rt->overridden.color.is_valid()) {
+ rt->overridden.color = RID();
+ } else if (rt->color) {
+ glDeleteTextures(1, &rt->color);
}
+ rt->color = 0;
- if (rt->texture.is_valid()) {
- Texture *tex = get_texture(rt->texture);
- tex->alloc_height = 0;
- tex->alloc_width = 0;
- tex->width = 0;
- tex->height = 0;
- tex->active = false;
+ if (rt->overridden.depth.is_valid()) {
+ rt->overridden.depth = RID();
+ } else if (rt->depth) {
+ glDeleteTextures(1, &rt->depth);
}
+ rt->depth = 0;
- if (rt->overridden.color.is_valid()) {
- Texture *tex = get_texture(rt->overridden.color);
- tex->is_render_target = false;
- }
+ rt->overridden.velocity = RID();
+ rt->overridden.is_overridden = false;
if (rt->backbuffer_fbo != 0) {
glDeleteFramebuffers(1, &rt->backbuffer_fbo);
@@ -1732,15 +1749,6 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) {
_render_target_clear_sdf(rt);
}
-void TextureStorage::_clear_render_target_overridden_fbo_cache(RenderTarget *rt) {
- // Dispose of the cached fbo's and the allocated textures
- for (KeyValue<uint32_t, RenderTarget::RTOverridden::FBOCacheEntry> &E : rt->overridden.fbo_cache) {
- glDeleteTextures(E.value.allocated_textures.size(), E.value.allocated_textures.ptr());
- glDeleteFramebuffers(1, &E.value.fbo);
- }
- rt->overridden.fbo_cache.clear();
-}
-
RID TextureStorage::render_target_create() {
RenderTarget render_target;
//render_target.was_used = false;
@@ -1759,7 +1767,6 @@ RID TextureStorage::render_target_create() {
void TextureStorage::render_target_free(RID p_rid) {
RenderTarget *rt = render_target_owner.get_or_null(p_rid);
_clear_render_target(rt);
- _clear_render_target_overridden_fbo_cache(rt);
Texture *t = get_texture(rt->texture);
if (t) {
@@ -1826,11 +1833,7 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color
if (p_color_texture.is_null() && p_depth_texture.is_null()) {
_clear_render_target(rt);
- rt->overridden.is_overridden = false;
- rt->overridden.color = RID();
- rt->overridden.depth = RID();
- rt->size = Size2i();
- _clear_render_target_overridden_fbo_cache(rt);
+ _update_render_target(rt);
return;
}
@@ -1849,6 +1852,8 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color
RBMap<uint32_t, RenderTarget::RTOverridden::FBOCacheEntry>::Element *cache;
if ((cache = rt->overridden.fbo_cache.find(hash_key)) != nullptr) {
rt->fbo = cache->get().fbo;
+ rt->color = cache->get().color;
+ rt->depth = cache->get().depth;
rt->size = cache->get().size;
rt->texture = p_color_texture;
return;
@@ -1858,6 +1863,8 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color
RenderTarget::RTOverridden::FBOCacheEntry new_entry;
new_entry.fbo = rt->fbo;
+ new_entry.color = rt->color;
+ new_entry.depth = rt->depth;
new_entry.size = rt->size;
// Keep track of any textures we had to allocate because they weren't overridden.
if (p_color_texture.is_null()) {
diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h
index c465576347..169c50638d 100644
--- a/drivers/gles3/storage/texture_storage.h
+++ b/drivers/gles3/storage/texture_storage.h
@@ -344,6 +344,8 @@ struct RenderTarget {
struct FBOCacheEntry {
GLuint fbo;
+ GLuint color;
+ GLuint depth;
Size2i size;
Vector<GLuint> allocated_textures;
};
@@ -412,7 +414,6 @@ private:
mutable RID_Owner<RenderTarget> render_target_owner;
void _clear_render_target(RenderTarget *rt);
- void _clear_render_target_overridden_fbo_cache(RenderTarget *rt);
void _update_render_target(RenderTarget *rt);
void _create_render_target_backbuffer(RenderTarget *rt);
void _render_target_allocate_sdf(RenderTarget *rt);
diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp
index 393093c2a7..fe900c7cfb 100644
--- a/drivers/gles3/storage/utilities.cpp
+++ b/drivers/gles3/storage/utilities.cpp
@@ -281,7 +281,7 @@ String Utilities::get_captured_timestamp_name(uint32_t p_index) const {
void Utilities::update_dirty_resources() {
MaterialStorage::get_singleton()->_update_global_shader_uniforms();
MaterialStorage::get_singleton()->_update_queued_materials();
- //MeshStorage::get_singleton()->_update_dirty_skeletons();
+ MeshStorage::get_singleton()->_update_dirty_skeletons();
MeshStorage::get_singleton()->_update_dirty_multimeshes();
TextureStorage::get_singleton()->update_texture_atlas();
}
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index d00a64f796..06e1e8b22e 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -2274,7 +2274,9 @@ void EditorNode::_edit_current(bool p_skip_foreign) {
} else {
Node *selected_node = nullptr;
- if (current_obj->is_class("MultiNodeEdit")) {
+ if (current_obj->is_class("EditorDebuggerRemoteObject")) {
+ disable_folding = true;
+ } else if (current_obj->is_class("MultiNodeEdit")) {
Node *scene = get_edited_scene();
if (scene) {
MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(current_obj);
diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp
index 78d4f93dfe..f0e841f307 100644
--- a/editor/export/editor_export_plugin.cpp
+++ b/editor/export/editor_export_plugin.cpp
@@ -148,10 +148,8 @@ bool EditorExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatfo
Ref<Resource> EditorExportPlugin::_customize_resource(const Ref<Resource> &p_resource, const String &p_path) {
Ref<Resource> ret;
- if (GDVIRTUAL_REQUIRED_CALL(_customize_resource, p_resource, p_path, ret)) {
- return ret;
- }
- return Ref<Resource>();
+ GDVIRTUAL_REQUIRED_CALL(_customize_resource, p_resource, p_path, ret);
+ return ret;
}
bool EditorExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const {
@@ -162,18 +160,14 @@ bool EditorExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform>
Node *EditorExportPlugin::_customize_scene(Node *p_root, const String &p_path) {
Node *ret = nullptr;
- if (GDVIRTUAL_REQUIRED_CALL(_customize_scene, p_root, p_path, ret)) {
- return ret;
- }
- return nullptr;
+ GDVIRTUAL_REQUIRED_CALL(_customize_scene, p_root, p_path, ret);
+ return ret;
}
uint64_t EditorExportPlugin::_get_customization_configuration_hash() const {
uint64_t ret = 0;
- if (GDVIRTUAL_REQUIRED_CALL(_get_customization_configuration_hash, ret)) {
- return ret;
- }
- return 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_customization_configuration_hash, ret);
+ return ret;
}
void EditorExportPlugin::_end_customize_scenes() {
@@ -186,10 +180,8 @@ void EditorExportPlugin::_end_customize_resources() {
String EditorExportPlugin::_get_name() const {
String ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_name, ret)) {
- return ret;
- }
- return "";
+ GDVIRTUAL_REQUIRED_CALL(_get_name, ret);
+ return ret;
}
void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) {
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 8cb9a6712d..c08c9a83a7 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -5596,7 +5596,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
// make visible for certain node type
if (Object::cast_to<Control>(child)) {
Size2 texture_size = texture->get_size();
- undo_redo->add_do_property(child, "rect_size", texture_size);
+ undo_redo->add_do_property(child, "size", texture_size);
} else if (Object::cast_to<Polygon2D>(child)) {
Size2 texture_size = texture->get_size();
Vector<Vector2> list = {
@@ -5612,6 +5612,11 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
Transform2D xform = canvas_item_editor->get_canvas_transform();
Point2 target_position = xform.affine_inverse().xform(p_point);
+ // Adjust position for Control and TouchScreenButton
+ if (Object::cast_to<Control>(child) || Object::cast_to<TouchScreenButton>(child)) {
+ target_position -= texture->get_size() / 2;
+ }
+
// there's nothing to be used as source position so snapping will work as absolute if enabled
target_position = canvas_item_editor->snap_point(target_position);
undo_redo->add_do_method(child, "set_global_position", target_position);
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index df1fd52b69..44126b4497 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -86,6 +86,165 @@ constexpr real_t MAX_Z = 1000000.0;
constexpr real_t MIN_FOV = 0.01;
constexpr real_t MAX_FOV = 179;
+void ViewportNavigationControl::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (!is_connected("mouse_exited", callable_mp(this, &ViewportNavigationControl::_on_mouse_exited))) {
+ connect("mouse_exited", callable_mp(this, &ViewportNavigationControl::_on_mouse_exited));
+ }
+ } break;
+
+ case NOTIFICATION_DRAW: {
+ if (viewport != nullptr) {
+ _draw();
+ _update_navigation();
+ }
+ } break;
+ }
+}
+
+void ViewportNavigationControl::_draw() {
+ if (nav_mode == Node3DEditorViewport::NAVIGATION_NONE) {
+ return;
+ }
+
+ Vector2 center = get_size() / 2.0;
+ float radius = get_size().x / 2.0;
+
+ const bool focused = focused_index != -1;
+ draw_circle(center, radius, Color(0.5, 0.5, 0.5, focused ? 0.25 : 0.05));
+
+ const Color c = focused ? Color(0.9, 0.9, 0.9, 0.9) : Color(0.5, 0.5, 0.5, 0.25);
+
+ Vector2 circle_pos = focused ? center.move_toward(focused_pos, radius) : center;
+
+ draw_circle(circle_pos, AXIS_CIRCLE_RADIUS, c);
+ draw_circle(circle_pos, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4));
+}
+
+void ViewportNavigationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {
+ if (focused_index != -1 && focused_index != p_index) {
+ return;
+ }
+ if (p_pressed) {
+ if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
+ focused_pos = p_position;
+ focused_index = p_index;
+ queue_redraw();
+ }
+ } else {
+ focused_index = -1;
+ if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
+ Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
+ Input::get_singleton()->warp_mouse(focused_mouse_start);
+ }
+ }
+}
+
+void ViewportNavigationControl::_process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position) {
+ if (focused_index == p_index) {
+ if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {
+ Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
+ focused_mouse_start = p_position;
+ }
+ focused_pos += p_relative_position;
+ queue_redraw();
+ }
+}
+
+void ViewportNavigationControl::gui_input(const Ref<InputEvent> &p_event) {
+ // Mouse events
+ const Ref<InputEventMouseButton> mouse_button = p_event;
+ if (mouse_button.is_valid() && mouse_button->get_button_index() == MouseButton::LEFT) {
+ _process_click(100, mouse_button->get_position(), mouse_button->is_pressed());
+ }
+
+ const Ref<InputEventMouseMotion> mouse_motion = p_event;
+ if (mouse_motion.is_valid()) {
+ _process_drag(100, mouse_motion->get_global_position(), viewport->_get_warped_mouse_motion(mouse_motion));
+ }
+
+ // Touch events
+ const Ref<InputEventScreenTouch> screen_touch = p_event;
+ if (screen_touch.is_valid()) {
+ _process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());
+ }
+
+ const Ref<InputEventScreenDrag> screen_drag = p_event;
+ if (screen_drag.is_valid()) {
+ _process_drag(screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());
+ }
+}
+
+void ViewportNavigationControl::_update_navigation() {
+ if (focused_index == -1) {
+ return;
+ }
+
+ Vector2 delta = focused_pos - (get_size() / 2.0);
+ Vector2 delta_normalized = delta.normalized();
+ switch (nav_mode) {
+ case Node3DEditorViewport::NavigationMode::NAVIGATION_MOVE: {
+ real_t speed_multiplier = MIN(delta.length() / (get_size().x * 100.0), 3.0);
+ real_t speed = viewport->freelook_speed * speed_multiplier;
+
+ const Node3DEditorViewport::FreelookNavigationScheme navigation_scheme = (Node3DEditorViewport::FreelookNavigationScheme)EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_navigation_scheme").operator int();
+
+ Vector3 forward;
+ if (navigation_scheme == Node3DEditorViewport::FreelookNavigationScheme::FREELOOK_FULLY_AXIS_LOCKED) {
+ // Forward/backward keys will always go straight forward/backward, never moving on the Y axis.
+ forward = Vector3(0, 0, delta_normalized.y).rotated(Vector3(0, 1, 0), viewport->camera->get_rotation().y);
+ } else {
+ // Forward/backward keys will be relative to the camera pitch.
+ forward = viewport->camera->get_transform().basis.xform(Vector3(0, 0, delta_normalized.y));
+ }
+
+ const Vector3 right = viewport->camera->get_transform().basis.xform(Vector3(delta_normalized.x, 0, 0));
+
+ const Vector3 direction = forward + right;
+ const Vector3 motion = direction * speed;
+ viewport->cursor.pos += motion;
+ viewport->cursor.eye_pos += motion;
+ } break;
+
+ case Node3DEditorViewport::NavigationMode::NAVIGATION_LOOK: {
+ real_t speed_multiplier = MIN(delta.length() / (get_size().x * 2.5), 3.0);
+ real_t speed = viewport->freelook_speed * speed_multiplier;
+ viewport->_nav_look(nullptr, delta_normalized * speed);
+ } break;
+
+ case Node3DEditorViewport::NAVIGATION_PAN: {
+ real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);
+ real_t speed = viewport->freelook_speed * speed_multiplier;
+ viewport->_nav_pan(nullptr, -delta_normalized * speed);
+ } break;
+ case Node3DEditorViewport::NAVIGATION_ZOOM: {
+ real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);
+ real_t speed = viewport->freelook_speed * speed_multiplier;
+ viewport->_nav_zoom(nullptr, delta_normalized * speed);
+ } break;
+ case Node3DEditorViewport::NAVIGATION_ORBIT: {
+ real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);
+ real_t speed = viewport->freelook_speed * speed_multiplier;
+ viewport->_nav_orbit(nullptr, delta_normalized * speed);
+ } break;
+ case Node3DEditorViewport::NAVIGATION_NONE: {
+ } break;
+ }
+}
+
+void ViewportNavigationControl::_on_mouse_exited() {
+ queue_redraw();
+}
+
+void ViewportNavigationControl::set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode) {
+ nav_mode = p_nav_mode;
+}
+
+void ViewportNavigationControl::set_viewport(Node3DEditorViewport *p_viewport) {
+ viewport = p_viewport;
+}
+
void ViewportRotationControl::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -120,7 +279,7 @@ void ViewportRotationControl::_draw() {
const Vector2i center = get_size() / 2.0;
const real_t radius = get_size().x / 2.0;
- if (focused_axis > -2 || orbiting) {
+ if (focused_axis > -2 || orbiting_index != -1) {
draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25));
}
@@ -191,41 +350,63 @@ void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) {
r_axis.sort_custom<Axis2DCompare>();
}
+void ViewportRotationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {
+ if (orbiting_index != -1 && orbiting_index != p_index) {
+ return;
+ }
+ if (p_pressed) {
+ if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
+ orbiting_index = p_index;
+ }
+ } else {
+ if (focused_axis > -1) {
+ viewport->_menu_option(axis_menu_options[focused_axis]);
+ _update_focus();
+ }
+ orbiting_index = -1;
+ if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
+ Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
+ Input::get_singleton()->warp_mouse(orbiting_mouse_start);
+ }
+ }
+}
+
+void ViewportRotationControl::_process_drag(Ref<InputEventWithModifiers> p_event, int p_index, Vector2 p_position, Vector2 p_relative_position) {
+ if (orbiting_index == p_index) {
+ if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {
+ Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
+ orbiting_mouse_start = p_position;
+ }
+ viewport->_nav_orbit(p_event, p_relative_position);
+ focused_axis = -1;
+ } else {
+ _update_focus();
+ }
+}
+
void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
+ // Mouse events
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
- Vector2 pos = mb->get_position();
- if (mb->is_pressed()) {
- if (pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
- orbiting = true;
- }
- } else {
- if (focused_axis > -1) {
- viewport->_menu_option(axis_menu_options[focused_axis]);
- _update_focus();
- }
- orbiting = false;
- if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
- Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
- Input::get_singleton()->warp_mouse(orbiting_mouse_start);
- }
- }
+ _process_click(100, mb->get_position(), mb->is_pressed());
}
const Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
- if (orbiting) {
- if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {
- Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
- orbiting_mouse_start = mm->get_global_position();
- }
- viewport->_nav_orbit(mm, viewport->_get_warped_mouse_motion(mm));
- focused_axis = -1;
- } else {
- _update_focus();
- }
+ _process_drag(mm, 100, mm->get_global_position(), viewport->_get_warped_mouse_motion(mm));
+ }
+
+ // Touch events
+ const Ref<InputEventScreenTouch> screen_touch = p_event;
+ if (screen_touch.is_valid()) {
+ _process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());
+ }
+
+ const Ref<InputEventScreenDrag> screen_drag = p_event;
+ if (screen_drag.is_valid()) {
+ _process_drag(screen_drag, screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());
}
}
@@ -353,6 +534,8 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) {
update_transform_gizmo_view();
rotation_control->queue_redraw();
+ position_control->queue_redraw();
+ look_control->queue_redraw();
spatial_editor->update_grid();
}
}
@@ -2091,7 +2274,7 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const
const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
real_t pan_speed = 1 / 150.0;
- if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
+ if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
pan_speed *= 10;
}
@@ -2115,7 +2298,7 @@ void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const
const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
real_t zoom_speed = 1 / 80.0;
- if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
+ if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
zoom_speed *= 10;
}
@@ -2448,6 +2631,8 @@ void Node3DEditorViewport::_notification(int p_what) {
}
call_deferred(SNAME("update_transform_gizmo_view"));
rotation_control->set_visible(EDITOR_GET("editors/3d/navigation/show_viewport_rotation_gizmo"));
+ position_control->set_visible(EDITOR_GET("editors/3d/navigation/show_viewport_navigation_gizmo"));
+ look_control->set_visible(EDITOR_GET("editors/3d/navigation/show_viewport_navigation_gizmo"));
} break;
case NOTIFICATION_RESIZED: {
@@ -3370,6 +3555,8 @@ void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {
ERR_FAIL_COND(!p_activate && !previewing);
rotation_control->set_visible(!p_activate);
+ position_control->set_visible(!p_activate);
+ look_control->set_visible(!p_activate);
if (!p_activate) {
previewing->disconnect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
@@ -3391,6 +3578,8 @@ void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {
void Node3DEditorViewport::_toggle_cinema_preview(bool p_activate) {
previewing_cinema = p_activate;
rotation_control->set_visible(!p_activate);
+ position_control->set_visible(!p_activate);
+ look_control->set_visible(!p_activate);
if (!previewing_cinema) {
if (previewing != nullptr) {
@@ -4874,6 +5063,14 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
preview_node = nullptr;
+ bottom_center_vbox = memnew(VBoxContainer);
+ bottom_center_vbox->set_anchors_preset(LayoutPreset::PRESET_CENTER);
+ bottom_center_vbox->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -20 * EDSCALE);
+ bottom_center_vbox->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);
+ bottom_center_vbox->set_h_grow_direction(GROW_DIRECTION_BOTH);
+ bottom_center_vbox->set_v_grow_direction(GROW_DIRECTION_BEGIN);
+ surface->add_child(bottom_center_vbox);
+
info_label = memnew(Label);
info_label->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -90 * EDSCALE);
info_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -90 * EDSCALE);
@@ -4894,23 +5091,18 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
previewing_cinema = false;
locked_label = memnew(Label);
- locked_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -20 * EDSCALE);
- locked_label->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);
- locked_label->set_h_grow_direction(GROW_DIRECTION_END);
- locked_label->set_v_grow_direction(GROW_DIRECTION_BEGIN);
locked_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
- surface->add_child(locked_label);
+ locked_label->set_h_size_flags(SIZE_SHRINK_CENTER);
+ bottom_center_vbox->add_child(locked_label);
locked_label->set_text(TTR("View Rotation Locked"));
locked_label->hide();
zoom_limit_label = memnew(Label);
- zoom_limit_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
- zoom_limit_label->set_offset(Side::SIDE_TOP, -28 * EDSCALE);
zoom_limit_label->set_text(TTR("To zoom further, change the camera's clipping planes (View -> Settings...)"));
zoom_limit_label->set_name("ZoomLimitMessageLabel");
zoom_limit_label->add_theme_color_override("font_color", Color(1, 1, 1, 1));
zoom_limit_label->hide();
- surface->add_child(zoom_limit_label);
+ bottom_center_vbox->add_child(zoom_limit_label);
preview_material_label = memnew(Label);
preview_material_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
@@ -4941,6 +5133,30 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
// Prevent visible spacing between frame time labels.
top_right_vbox->add_theme_constant_override("separation", 0);
+ const int navigation_control_size = 200;
+
+ position_control = memnew(ViewportNavigationControl);
+ position_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_MOVE);
+ position_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);
+ position_control->set_h_size_flags(SIZE_SHRINK_END);
+ position_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0 * EDSCALE);
+ position_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);
+ position_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_BEGIN, navigation_control_size * EDSCALE);
+ position_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0 * EDSCALE);
+ position_control->set_viewport(this);
+ surface->add_child(position_control);
+
+ look_control = memnew(ViewportNavigationControl);
+ look_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_LOOK);
+ look_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);
+ look_control->set_h_size_flags(SIZE_SHRINK_END);
+ look_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -navigation_control_size * EDSCALE);
+ look_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);
+ look_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0 * EDSCALE);
+ look_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0 * EDSCALE);
+ look_control->set_viewport(this);
+ surface->add_child(look_control);
+
rotation_control = memnew(ViewportRotationControl);
rotation_control->set_custom_minimum_size(Size2(80, 80) * EDSCALE);
rotation_control->set_h_size_flags(SIZE_SHRINK_END);
@@ -8191,7 +8407,8 @@ Node3DEditor::Node3DEditor() {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d/manipulator_gizmo_size", PROPERTY_HINT_RANGE, "16,160,1"));
EDITOR_DEF("editors/3d/manipulator_gizmo_opacity", 0.9);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "editors/3d/manipulator_gizmo_opacity", PROPERTY_HINT_RANGE, "0,1,0.01"));
- EDITOR_DEF("editors/3d/navigation/show_viewport_rotation_gizmo", true);
+ EDITOR_DEF_RST("editors/3d/navigation/show_viewport_rotation_gizmo", true);
+ EDITOR_DEF_RST("editors/3d/navigation/show_viewport_navigation_gizmo", DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_TOUCHSCREEN));
current_hover_gizmo_handle = -1;
current_hover_gizmo_handle_secondary = false;
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index b7ac718182..04fc030f2b 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -57,6 +57,7 @@ class SubViewport;
class SubViewportContainer;
class VSplitContainer;
class WorldEnvironment;
+class ViewportNavigationControl;
class ViewportRotationControl : public Control {
GDCLASS(ViewportRotationControl, Control);
@@ -77,7 +78,7 @@ class ViewportRotationControl : public Control {
Vector<Color> axis_colors;
Vector<int> axis_menu_options;
Vector2i orbiting_mouse_start;
- bool orbiting = false;
+ int orbiting_index = -1;
int focused_axis = -2;
const float AXIS_CIRCLE_RADIUS = 8.0f * EDSCALE;
@@ -90,6 +91,8 @@ protected:
void _get_sorted_axis(Vector<Axis2D> &r_axis);
void _update_focus();
void _on_mouse_exited();
+ void _process_click(int p_index, Vector2 p_position, bool p_pressed);
+ void _process_drag(Ref<InputEventWithModifiers> p_event, int p_index, Vector2 p_position, Vector2 p_relative_position);
public:
void set_viewport(Node3DEditorViewport *p_viewport);
@@ -98,6 +101,7 @@ public:
class Node3DEditorViewport : public Control {
GDCLASS(Node3DEditorViewport, Control);
friend class Node3DEditor;
+ friend class ViewportNavigationControl;
friend class ViewportRotationControl;
enum {
VIEW_TOP,
@@ -236,6 +240,9 @@ private:
Label *preview_material_label_desc = nullptr;
VBoxContainer *top_right_vbox = nullptr;
+ VBoxContainer *bottom_center_vbox = nullptr;
+ ViewportNavigationControl *position_control = nullptr;
+ ViewportNavigationControl *look_control = nullptr;
ViewportRotationControl *rotation_control = nullptr;
Gradient *frame_time_gradient = nullptr;
Label *cpu_time_label = nullptr;
@@ -297,7 +304,8 @@ private:
NAVIGATION_PAN,
NAVIGATION_ZOOM,
NAVIGATION_ORBIT,
- NAVIGATION_LOOK
+ NAVIGATION_LOOK,
+ NAVIGATION_MOVE
};
enum TransformMode {
TRANSFORM_NONE,
@@ -916,4 +924,29 @@ public:
~Node3DEditorPlugin();
};
+class ViewportNavigationControl : public Control {
+ GDCLASS(ViewportNavigationControl, Control);
+
+ Node3DEditorViewport *viewport = nullptr;
+ Vector2i focused_mouse_start;
+ Vector2 focused_pos;
+ int focused_index = -1;
+ Node3DEditorViewport::NavigationMode nav_mode = Node3DEditorViewport::NavigationMode::NAVIGATION_NONE;
+
+ const float AXIS_CIRCLE_RADIUS = 30.0f * EDSCALE;
+
+protected:
+ void _notification(int p_what);
+ virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ void _draw();
+ void _on_mouse_exited();
+ void _process_click(int p_index, Vector2 p_position, bool p_pressed);
+ void _process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position);
+ void _update_navigation();
+
+public:
+ void set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode);
+ void set_viewport(Node3DEditorViewport *p_viewport);
+};
+
#endif // NODE_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index fc13393582..ae7570e161 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -152,7 +152,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na
// ID and size related properties.
if (tiles.size() == 1) {
- const Vector2i &coords = tiles.front()->get().tile;
+ const Vector2i coords = tiles.front()->get().tile;
const int &alternative = tiles.front()->get().alternative;
if (alternative == 0) {
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index b7cca72624..249504b6e8 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -1253,7 +1253,6 @@ void ProjectList::migrate_config() {
if (FileAccess::exists(_config_path)) {
return;
}
- print_line("Migrating legacy project list");
List<PropertyInfo> properties;
EditorSettings::get_singleton()->get_property_list(&properties);
@@ -1266,6 +1265,8 @@ void ProjectList::migrate_config() {
}
String path = EDITOR_GET(property_key);
+ print_line("Migrating legacy project '" + path + "'.");
+
String favoriteKey = "favorite_projects/" + property_key.get_slice("/", 1);
bool favorite = EditorSettings::get_singleton()->has_setting(favoriteKey);
add_project(path, favorite);
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index 4a3b0e979f..52f4ae553f 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -107,7 +107,15 @@ static Vector<String> _get_hierarchy(String p_class_name) {
void ScriptCreateDialog::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ Ref<Texture2D> language_icon = get_theme_icon(ScriptServer::get_language(i)->get_type(), SNAME("EditorIcons"));
+ if (language_icon.is_valid()) {
+ language_menu->set_item_icon(i, language_icon);
+ }
+ }
+
String last_language = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", "");
if (!last_language.is_empty()) {
for (int i = 0; i < language_menu->get_item_count(); i++) {
@@ -120,15 +128,9 @@ void ScriptCreateDialog::_notification(int p_what) {
} else {
language_menu->select(default_language);
}
-
- [[fallthrough]];
- }
- case NOTIFICATION_THEME_CHANGED: {
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- Ref<Texture2D> language_icon = get_theme_icon(ScriptServer::get_language(i)->get_type(), SNAME("EditorIcons"));
- if (language_icon.is_valid()) {
- language_menu->set_item_icon(i, language_icon);
- }
+ if (EditorSettings::get_singleton()->has_meta("script_setup_use_script_templates")) {
+ is_using_templates = bool(EditorSettings::get_singleton()->get_meta("script_setup_use_script_templates"));
+ use_templates->set_pressed(is_using_templates);
}
path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons")));
@@ -331,7 +333,12 @@ void ScriptCreateDialog::_template_changed(int p_template) {
EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project);
} else {
// Save template info to editor dictionary (not a project template).
- templates_dictionary[parent_name->get_text()] = sinfo.get_hash();
+ Dictionary dic_templates;
+ if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) {
+ dic_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary");
+ }
+ dic_templates[parent_name->get_text()] = sinfo.get_hash();
+ EditorSettings::get_singleton()->set_meta("script_setup_templates_dictionary", dic_templates);
// Remove template from project dictionary as we last used an editor level template.
Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary());
if (dic_templates_project.has(parent_name->get_text())) {
@@ -480,6 +487,7 @@ void ScriptCreateDialog::_built_in_pressed() {
void ScriptCreateDialog::_use_template_pressed() {
is_using_templates = use_templates->is_pressed();
+ EditorSettings::get_singleton()->set_meta("script_setup_use_script_templates", is_using_templates);
_update_dialog();
}
@@ -597,6 +605,10 @@ void ScriptCreateDialog::_update_template_menu() {
if (is_language_using_templates) {
// Get the latest templates used for each type of node from project settings then global settings.
Dictionary last_local_templates = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary());
+ Dictionary last_global_templates;
+ if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) {
+ last_global_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary");
+ }
String inherits_base_type = parent_name->get_text();
// If it inherits from a script, get its parent class first.
@@ -651,7 +663,7 @@ void ScriptCreateDialog::_update_template_menu() {
// Check for last used template for this node in project settings then in global settings.
if (last_local_templates.has(parent_name->get_text()) && t.get_hash() == String(last_local_templates[parent_name->get_text()])) {
last_used_template = id;
- } else if (last_used_template == -1 && templates_dictionary.has(parent_name->get_text()) && t.get_hash() == String(templates_dictionary[parent_name->get_text()])) {
+ } else if (last_used_template == -1 && last_global_templates.has(parent_name->get_text()) && t.get_hash() == String(last_global_templates[parent_name->get_text()])) {
last_used_template = id;
}
t.id = id;
diff --git a/editor/script_create_dialog.h b/editor/script_create_dialog.h
index fb1a49a1ca..25428508da 100644
--- a/editor/script_create_dialog.h
+++ b/editor/script_create_dialog.h
@@ -83,7 +83,6 @@ class ScriptCreateDialog : public ConfirmationDialog {
int current_language;
int default_language;
bool re_check_path = false;
- Dictionary templates_dictionary;
Control *path_controls[2];
Control *name_controls[2];
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp
index 3d66b27556..e467ed60ee 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -111,13 +111,16 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua
Image::Format target_format = Image::FORMAT_RGBA8;
if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) {
target_format = Image::FORMAT_ETC;
+ r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) {
target_format = Image::FORMAT_ETC2_RGB8;
+ r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) {
target_format = Image::FORMAT_ETC2_RA_AS_RG;
r_img->convert_rg_to_ra_rgba8();
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) {
target_format = Image::FORMAT_ETC2_RGBA8;
+ r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) {
target_format = Image::FORMAT_DXT1;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index c8eda53a2d..4981750b7d 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -547,7 +547,7 @@
<return type="void" />
<param index="0" name="icon_path" type="String" />
<description>
- Add a custom icon to the current script. After loading an icon at [param icon_path], the icon is displayed in the Scene dock for every node that the script is attached to. For named classes, the icon is also displayed in various editor dialogs.
+ Add a custom icon to the current script. The script must be registered as a global class using the [code]class_name[/code] keyword for this to have a visible effect. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as in various editor dialogs.
[codeblock]
@icon("res://path/to/class/icon.svg")
[/codeblock]
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 95e577c140..dff6e41dca 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1162,6 +1162,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
if (p_function->parameters[i]->default_value->is_constant) {
p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value);
+ } else {
+ p_function->default_arg_values.push_back(Variant()); // Prevent shift.
}
}
#endif // TOOLS_ENABLED
@@ -1214,11 +1216,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
if (!valid) {
// Compute parent signature as a string to show in the error message.
- String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant";
- if (parent_signature == "null") {
- parent_signature = "void";
- }
- parent_signature += " " + p_function->identifier->name.operator String() + "(";
+ String parent_signature = p_function->identifier->name.operator String() + "(";
int j = 0;
for (const GDScriptParser::DataType &par_type : parameters_types) {
if (j > 0) {
@@ -1235,7 +1233,15 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
j++;
}
- parent_signature += ")";
+ parent_signature += ") -> ";
+
+ const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant";
+ if (return_type == "null") {
+ parent_signature += "void";
+ } else {
+ parent_signature += return_type;
+ }
+
push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function);
}
}
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 103eb60da9..24241b712b 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1875,6 +1875,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name];
GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script);
+ bool initialized = false;
if (lv->initializer != nullptr) {
// For typed arrays we need to make sure this is already initialized correctly so typed assignment work.
if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) {
@@ -1896,15 +1897,23 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
+ initialized = true;
} else if (local_type.has_type) {
// Initialize with default for type.
if (local_type.has_container_element_type()) {
codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+ initialized = true;
} else if (local_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ initialized = true;
}
// The `else` branch is for objects, in such case we leave it as `null`.
}
+
+ // Assigns a null for the unassigned variables in loops.
+ if (!initialized && p_block->is_loop) {
+ codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>());
+ }
} break;
case GDScriptParser::Node::CONSTANT: {
// Local constants.
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 24dd94873b..f2aafe9f0c 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -1835,9 +1835,9 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
}
suite->add_local(SuiteNode::Local(n_for->variable, current_function));
}
- suite->parent_for = n_for;
n_for->loop = parse_suite(R"("for" block)", suite);
+ n_for->loop->is_loop = true;
complete_extents(n_for);
// Reset break/continue state.
@@ -2169,6 +2169,7 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
is_continue_match = false;
n_while->loop = parse_suite(R"("while" block)");
+ n_while->loop->is_loop = true;
complete_extents(n_while);
// Reset break/continue state.
@@ -3785,15 +3786,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
break;
case GDScriptParser::DataType::CLASS:
- // Can assume type is a global GDScript class.
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
- variable->export_info.hint_string = export_type.class_type->identifier->name;
+ variable->export_info.hint_string = export_type.to_string();
} else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
- variable->export_info.hint_string = export_type.class_type->identifier->name;
+ variable->export_info.hint_string = export_type.to_string();
} else {
push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable);
return false;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index f9a1c5a697..d092a2a5e9 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1055,12 +1055,12 @@ public:
HashMap<StringName, int> locals_indices;
FunctionNode *parent_function = nullptr;
- ForNode *parent_for = nullptr;
IfNode *parent_if = nullptr;
bool has_return = false;
bool has_continue = false;
bool has_unreachable_code = false; // Just so warnings aren't given more than once per block.
+ bool is_loop = false;
bool has_local(const StringName &p_name) const;
const Local &get_local(const StringName &p_name) const;
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out
index 3baeb17066..4ccd2da381 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-The function signature doesn't match the parent. Parent signature is "int my_function(int)".
+The function signature doesn't match the parent. Parent signature is "my_function(int) -> int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out
index 3baeb17066..4ccd2da381 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-The function signature doesn't match the parent. Parent signature is "int my_function(int)".
+The function signature doesn't match the parent. Parent signature is "my_function(int) -> int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out
index 665c229339..c70a1df10d 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-The function signature doesn't match the parent. Parent signature is "int my_function(int = default)".
+The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out
index 3baeb17066..4ccd2da381 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-The function signature doesn't match the parent. Parent signature is "int my_function(int)".
+The function signature doesn't match the parent. Parent signature is "my_function(int) -> int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out
index 5b22739a93..61004ff627 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-The function signature doesn't match the parent. Parent signature is "int my_function()".
+The function signature doesn't match the parent. Parent signature is "my_function() -> int".
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index cfb9a0fbfb..137fd61a25 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -391,10 +391,10 @@ bool CSharpLanguage::supports_builtin_mode() const {
#ifdef TOOLS_ENABLED
static String variant_type_to_managed_name(const String &p_var_type_name) {
if (p_var_type_name.is_empty()) {
- return "object";
+ return "Variant";
}
- if (!ClassDB::class_exists(p_var_type_name)) {
+ if (ClassDB::class_exists(p_var_type_name)) {
return p_var_type_name;
}
@@ -402,12 +402,12 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
return "Godot.Object";
}
+ if (p_var_type_name == Variant::get_type_name(Variant::INT)) {
+ return "long";
+ }
+
if (p_var_type_name == Variant::get_type_name(Variant::FLOAT)) {
-#ifdef REAL_T_IS_DOUBLE
return "double";
-#else
- return "float";
-#endif
}
if (p_var_type_name == Variant::get_type_name(Variant::STRING)) {
@@ -485,7 +485,7 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
}
}
- return "object";
+ return "Variant";
}
String CSharpLanguage::make_function(const String &, const String &p_name, const PackedStringArray &p_args) const {
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 3f588a4c90..63339b3487 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
@@ -78,10 +78,6 @@ namespace Godot.SourceGenerators
var source = new StringBuilder();
- source.Append("using Godot;\n");
- source.Append("using Godot.NativeInterop;\n");
- source.Append("\n");
-
if (hasNamespace)
{
source.Append("namespace ");
@@ -281,7 +277,7 @@ namespace Godot.SourceGenerators
{
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
- string dictionaryType = "System.Collections.Generic.Dictionary<StringName, object>";
+ string dictionaryType = "System.Collections.Generic.Dictionary<Godot.StringName, object>";
source.Append("#if TOOLS\n");
source.Append(" internal new static ");
diff --git a/modules/openxr/config.py b/modules/openxr/config.py
index 279168cc59..e503f12739 100644
--- a/modules/openxr/config.py
+++ b/modules/openxr/config.py
@@ -1,6 +1,6 @@
def can_build(env, platform):
if platform in ("linuxbsd", "windows", "android"):
- return env["openxr"]
+ return env["openxr"] and not env["disable_3d"]
else:
# not supported on these platforms
return False
diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp
index ea539f2053..753fc5fa89 100644
--- a/modules/openxr/extensions/openxr_android_extension.cpp
+++ b/modules/openxr/extensions/openxr_android_extension.cpp
@@ -47,11 +47,15 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() {
OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) :
OpenXRExtensionWrapper(p_openxr_api) {
singleton = this;
- request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = nullptr; // must be available
+ request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = &loader_init_extension_available;
request_extensions[XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME] = &create_instance_extension_available;
}
void OpenXRAndroidExtension::on_before_instance_created() {
+ if (!loader_init_extension_available) {
+ print_line("OpenXR: XR_KHR_loader_init_android is not reported as available - trying to initialize anyway...");
+ }
+
EXT_INIT_XR_FUNC(xrInitializeLoaderKHR);
JNIEnv *env = get_jni_env();
diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h
index ca6011559a..087b634756 100644
--- a/modules/openxr/extensions/openxr_android_extension.h
+++ b/modules/openxr/extensions/openxr_android_extension.h
@@ -48,6 +48,7 @@ public:
private:
static OpenXRAndroidExtension *singleton;
+ bool loader_init_extension_available = false;
bool create_instance_extension_available = false;
// Initialize the loader
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index 49dd9f7318..f5964eb4d1 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="WebXRInterface" inherits="XRInterface" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- AR/VR interface using WebXR.
+ XR interface using WebXR.
</brief_description>
<description>
WebXR is an open standard that allows creating VR and AR applications that run in the web browser.
As such, this interface is only available when running in Web exports.
WebXR supports a wide range of devices, from the very capable (like Valve Index, HTC Vive, Oculus Rift and Quest) down to the much less capable (like Google Cardboard, Oculus Go, GearVR, or plain smartphones).
- Since WebXR is based on JavaScript, it makes extensive use of callbacks, which means that [WebXRInterface] is forced to use signals, where other AR/VR interfaces would instead use functions that return a result immediately. This makes [WebXRInterface] quite a bit more complicated to initialize than other AR/VR interfaces.
+ Since WebXR is based on JavaScript, it makes extensive use of callbacks, which means that [WebXRInterface] is forced to use signals, where other XR interfaces would instead use functions that return a result immediately. This makes [WebXRInterface] quite a bit more complicated to initialize than other XR interfaces.
Here's the minimum code required to start an immersive VR session:
[codeblock]
extends Node3D
@@ -69,7 +69,7 @@
func _webxr_session_started():
$Button.visible = false
# This tells Godot to start rendering to the headset.
- get_viewport().xr = true
+ get_viewport().use_xr = true
# This will be the reference space type you ultimately got, out of the
# types that you requested above. This is useful if you want the game to
# work a little differently in 'bounded-floor' versus 'local-floor'.
@@ -79,28 +79,35 @@
$Button.visible = true
# If the user exits immersive mode, then we tell Godot to render to the web
# page again.
- get_viewport().xr = false
+ get_viewport().use_xr = false
func _webxr_session_failed(message):
OS.alert("Failed to initialize: " + message)
[/codeblock]
- There are several ways to handle "controller" input:
- - Using [XRController3D] nodes and their [signal XRController3D.button_pressed] and [signal XRController3D.button_released] signals. This is how controllers are typically handled in AR/VR apps in Godot, however, this will only work with advanced VR controllers like the Oculus Touch or Index controllers, for example. The buttons codes are defined by [url=https://immersive-web.github.io/webxr-gamepads-module/#xr-standard-gamepad-mapping]Section 3.3 of the WebXR Gamepads Module[/url].
- - Using [method Node._unhandled_input] and [InputEventJoypadButton] or [InputEventJoypadMotion]. This works the same as normal joypads, except the [member InputEvent.device] starts at 100, so the left controller is 100 and the right controller is 101, and the button codes are also defined by [url=https://immersive-web.github.io/webxr-gamepads-module/#xr-standard-gamepad-mapping]Section 3.3 of the WebXR Gamepads Module[/url].
- - Using the [signal select], [signal squeeze] and related signals. This method will work for both advanced VR controllers, and non-traditional "controllers" like a tap on the screen, a spoken voice command or a button press on the device itself.
- You can use one or all of these methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices.
+ There are a couple ways to handle "controller" input:
+ - Using [XRController3D] nodes and their [signal XRController3D.button_pressed] and [signal XRController3D.button_released] signals. This is how controllers are typically handled in XR apps in Godot, however, this will only work with advanced VR controllers like the Oculus Touch or Index controllers, for example.
+ - Using the [signal select], [signal squeeze] and related signals. This method will work for both advanced VR controllers, and non-traditional input sources like a tap on the screen, a spoken voice command or a button press on the device itself.
+ You can use both methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices.
</description>
<tutorials>
<link title="How to make a VR game for WebXR with Godot">https://www.snopekgames.com/blog/2020/how-make-vr-game-webxr-godot</link>
</tutorials>
<methods>
- <method name="get_controller" qualifiers="const">
+ <method name="get_input_source_target_ray_mode" qualifiers="const">
+ <return type="int" enum="WebXRInterface.TargetRayMode" />
+ <param index="0" name="input_source_id" type="int" />
+ <description>
+ Returns the target ray mode for the given [code]input_source_id[/code].
+ This can help interpret the input coming from that input source. See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource/targetRayMode]XRInputSource.targetRayMode[/url] for more information.
+ </description>
+ </method>
+ <method name="get_input_source_tracker" qualifiers="const">
<return type="XRPositionalTracker" />
- <param index="0" name="controller_id" type="int" />
+ <param index="0" name="input_source_id" type="int" />
<description>
- Gets an [XRPositionalTracker] for the given [code]controller_id[/code].
- In the context of WebXR, a "controller" can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional controller is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with.
- Use this method to get information about the controller that triggered one of these signals:
+ Gets an [XRPositionalTracker] for the given [code]input_source_id[/code].
+ In the context of WebXR, an input source can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional input source is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with.
+ Use this method to get information about the input source that triggered one of these signals:
- [signal selectstart]
- [signal select]
- [signal selectend]
@@ -109,6 +116,13 @@
- [signal squeezestart]
</description>
</method>
+ <method name="is_input_source_active" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="input_source_id" type="int" />
+ <description>
+ Returns [code]true[/code] if there is an active input source with the given [code]input_source_id[/code].
+ </description>
+ </method>
<method name="is_session_supported">
<return type="void" />
<param index="0" name="session_mode" type="String" />
@@ -120,11 +134,6 @@
</method>
</methods>
<members>
- <member name="bounds_geometry" type="PackedVector3Array" setter="" getter="get_bounds_geometry">
- The vertices of a polygon which defines the boundaries of the user's play area.
- This will only be available if [member reference_space_type] is [code]"bounded-floor"[/code] and only on certain browsers and devices that support it.
- The [signal reference_space_reset] signal may indicate when this changes.
- </member>
<member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features">
A comma-seperated list of optional features used by [method XRInterface.initialize] when setting up the WebXR session.
If a user's browser or device doesn't support one of the given features, initialization will continue, but you won't be able to use the requested feature.
@@ -137,7 +146,7 @@
</member>
<member name="requested_reference_space_types" type="String" setter="set_requested_reference_space_types" getter="get_requested_reference_space_types">
A comma-seperated list of reference space types used by [method XRInterface.initialize] when setting up the WebXR session.
- The reference space types are requested in order, and the first on supported by the users device or browser will be used. The [member reference_space_type] property contains the reference space type that was ultimately used.
+ The reference space types are requested in order, and the first one supported by the users device or browser will be used. The [member reference_space_type] property contains the reference space type that was ultimately selected.
This doesn't have any effect on the interface when already initialized.
Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features].
</member>
@@ -161,35 +170,35 @@
<signal name="reference_space_reset">
<description>
Emitted to indicate that the reference space has been reset or reconfigured.
- When (or whether) this is emitted depends on the user's browser or device, but may include when the user has changed the dimensions of their play space (which you may be able to access via [member bounds_geometry]) or pressed/held a button to recenter their position.
+ When (or whether) this is emitted depends on the user's browser or device, but may include when the user has changed the dimensions of their play space (which you may be able to access via [method XRInterface.get_play_area]) or pressed/held a button to recenter their position.
See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace/reset_event]WebXR's XRReferenceSpace reset event[/url] for more information.
</description>
</signal>
<signal name="select">
- <param index="0" name="controller_id" type="int" />
+ <param index="0" name="input_source_id" type="int" />
<description>
- Emitted after one of the "controllers" has finished its "primary action".
- Use [method get_controller] to get more information about the controller.
+ Emitted after one of the input sources has finished its "primary action".
+ Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="selectend">
- <param index="0" name="controller_id" type="int" />
+ <param index="0" name="input_source_id" type="int" />
<description>
- Emitted when one of the "controllers" has finished its "primary action".
- Use [method get_controller] to get more information about the controller.
+ Emitted when one of the input sources has finished its "primary action".
+ Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="selectstart">
- <param index="0" name="controller_id" type="int" />
+ <param index="0" name="input_source_id" type="int" />
<description>
- Emitted when one of the "controllers" has started its "primary action".
- Use [method get_controller] to get more information about the controller.
+ Emitted when one of the input source has started its "primary action".
+ Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="session_ended">
<description>
Emitted when the user ends the WebXR session (which can be done using UI from the browser or device).
- At this point, you should do [code]get_viewport().xr = false[/code] to instruct Godot to resume rendering to the screen.
+ At this point, you should do [code]get_viewport().use_xr = false[/code] to instruct Godot to resume rendering to the screen.
</description>
</signal>
<signal name="session_failed">
@@ -202,7 +211,7 @@
<signal name="session_started">
<description>
Emitted by [method XRInterface.initialize] if the session is successfully started.
- At this point, it's safe to do [code]get_viewport().xr = true[/code] to instruct Godot to start rendering to the AR/VR device.
+ At this point, it's safe to do [code]get_viewport().use_xr = true[/code] to instruct Godot to start rendering to the XR device.
</description>
</signal>
<signal name="session_supported">
@@ -213,24 +222,24 @@
</description>
</signal>
<signal name="squeeze">
- <param index="0" name="controller_id" type="int" />
+ <param index="0" name="input_source_id" type="int" />
<description>
- Emitted after one of the "controllers" has finished its "primary squeeze action".
- Use [method get_controller] to get more information about the controller.
+ Emitted after one of the input sources has finished its "primary squeeze action".
+ Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="squeezeend">
- <param index="0" name="controller_id" type="int" />
+ <param index="0" name="input_source_id" type="int" />
<description>
- Emitted when one of the "controllers" has finished its "primary squeeze action".
- Use [method get_controller] to get more information about the controller.
+ Emitted when one of the input sources has finished its "primary squeeze action".
+ Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="squeezestart">
- <param index="0" name="controller_id" type="int" />
+ <param index="0" name="input_source_id" type="int" />
<description>
- Emitted when one of the "controllers" has started its "primary squeeze action".
- Use [method get_controller] to get more information about the controller.
+ Emitted when one of the input sources has started its "primary squeeze action".
+ Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="visibility_state_changed">
@@ -239,4 +248,18 @@
</description>
</signal>
</signals>
+ <constants>
+ <constant name="TARGET_RAY_MODE_UNKNOWN" value="0" enum="TargetRayMode">
+ We don't know the the target ray mode.
+ </constant>
+ <constant name="TARGET_RAY_MODE_GAZE" value="1" enum="TargetRayMode">
+ Target ray originates at the viewer's eyes and points in the direction they are looking.
+ </constant>
+ <constant name="TARGET_RAY_MODE_TRACKED_POINTER" value="2" enum="TargetRayMode">
+ Target ray from a handheld pointer, most likely a VR touch controller.
+ </constant>
+ <constant name="TARGET_RAY_MODE_SCREEN" value="3" enum="TargetRayMode">
+ Target ray from touch screen, mouse or other tactile input device.
+ </constant>
+ </constants>
</class>
diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h
index d8d5bd99cc..e31a1d307e 100644
--- a/modules/webxr/godot_webxr.h
+++ b/modules/webxr/godot_webxr.h
@@ -37,12 +37,18 @@ extern "C" {
#include "stddef.h"
+enum WebXRInputEvent {
+ WEBXR_INPUT_EVENT_SELECTSTART,
+ WEBXR_INPUT_EVENT_SELECTEND,
+ WEBXR_INPUT_EVENT_SQUEEZESTART,
+ WEBXR_INPUT_EVENT_SQUEEZEEND,
+};
+
typedef void (*GodotWebXRSupportedCallback)(char *p_session_mode, int p_supported);
typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type);
typedef void (*GodotWebXREndedCallback)();
typedef void (*GodotWebXRFailedCallback)(char *p_message);
-typedef void (*GodotWebXRControllerCallback)();
-typedef void (*GodotWebXRInputEventCallback)(char *p_signal_name, int p_controller_id);
+typedef void (*GodotWebXRInputEventCallback)(int p_event_type, int p_input_source_id);
typedef void (*GodotWebXRSimpleEventCallback)(char *p_signal_name);
extern int godot_webxr_is_supported();
@@ -56,26 +62,33 @@ extern void godot_webxr_initialize(
GodotWebXRStartedCallback p_on_session_started,
GodotWebXREndedCallback p_on_session_ended,
GodotWebXRFailedCallback p_on_session_failed,
- GodotWebXRControllerCallback p_on_controller_changed,
GodotWebXRInputEventCallback p_on_input_event,
GodotWebXRSimpleEventCallback p_on_simple_event);
extern void godot_webxr_uninitialize();
extern int godot_webxr_get_view_count();
-extern int *godot_webxr_get_render_target_size();
-extern float *godot_webxr_get_transform_for_eye(int p_eye);
-extern float *godot_webxr_get_projection_for_eye(int p_eye);
-extern void godot_webxr_commit(unsigned int p_texture);
+extern bool godot_webxr_get_render_target_size(int *r_size);
+extern bool godot_webxr_get_transform_for_view(int p_view, float *r_transform);
+extern bool godot_webxr_get_projection_for_view(int p_view, float *r_transform);
+extern unsigned int godot_webxr_get_color_texture();
+extern unsigned int godot_webxr_get_depth_texture();
+extern unsigned int godot_webxr_get_velocity_texture();
-extern void godot_webxr_sample_controller_data();
-extern int godot_webxr_get_controller_count();
-extern int godot_webxr_is_controller_connected(int p_controller);
-extern float *godot_webxr_get_controller_transform(int p_controller);
-extern int *godot_webxr_get_controller_buttons(int p_controller);
-extern int *godot_webxr_get_controller_axes(int p_controller);
+extern bool godot_webxr_update_input_source(
+ int p_input_source_id,
+ float *r_target_pose,
+ int *r_target_ray_mode,
+ int *r_touch_index,
+ int *r_has_grip_pose,
+ float *r_grip_pose,
+ int *r_has_standard_mapping,
+ int *r_button_count,
+ float *r_buttons,
+ int *r_axes_count,
+ float *r_axes);
extern char *godot_webxr_get_visibility_state();
-extern int *godot_webxr_get_bounds_geometry();
+extern int godot_webxr_get_bounds_geometry(float **r_points);
#ifdef __cplusplus
}
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 714768347c..eaf251d48f 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -33,9 +33,14 @@ const GodotWebXR = {
gl: null,
session: null,
+ gl_binding: null,
+ layer: null,
space: null,
frame: null,
pose: null,
+ view_count: 1,
+ input_sources: new Array(16),
+ touches: new Array(5),
// Monkey-patch the requestAnimationFrame() used by Emscripten for the main
// loop, so that we can swap it out for XRSession.requestAnimationFrame()
@@ -76,34 +81,128 @@ const GodotWebXR = {
}, 0);
},
- // Holds the controllers list between function calls.
- controllers: [],
+ getLayer: () => {
+ const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length : 1;
+ let layer = GodotWebXR.layer;
- // Updates controllers array, where the left hand (or sole tracker) is
- // the first element, and the right hand is the second element, and any
- // others placed at the 3rd position and up.
- sampleControllers: () => {
- if (!GodotWebXR.session || !GodotWebXR.frame) {
- return;
+ // If the view count hasn't changed since creating this layer, then
+ // we can simply return it.
+ if (layer && GodotWebXR.view_count === new_view_count) {
+ return layer;
}
- let other_index = 2;
- const controllers = [];
- GodotWebXR.session.inputSources.forEach((input_source) => {
- if (input_source.targetRayMode === 'tracked-pointer') {
- if (input_source.handedness === 'right') {
- controllers[1] = input_source;
- } else if (input_source.handedness === 'left' || !controllers[0]) {
- controllers[0] = input_source;
+ if (!GodotWebXR.session || !GodotWebXR.gl_binding) {
+ return null;
+ }
+
+ const gl = GodotWebXR.gl;
+
+ layer = GodotWebXR.gl_binding.createProjectionLayer({
+ textureType: new_view_count > 1 ? 'texture-array' : 'texture',
+ colorFormat: gl.RGBA8,
+ depthFormat: gl.DEPTH_COMPONENT24,
+ });
+ GodotWebXR.session.updateRenderState({ layers: [layer] });
+
+ GodotWebXR.layer = layer;
+ GodotWebXR.view_count = new_view_count;
+ return layer;
+ },
+
+ getSubImage: () => {
+ if (!GodotWebXR.pose) {
+ return null;
+ }
+ const layer = GodotWebXR.getLayer();
+ if (layer === null) {
+ return null;
+ }
+
+ // Because we always use "texture-array" for multiview and "texture"
+ // when there is only 1 view, it should be safe to only grab the
+ // subimage for the first view.
+ return GodotWebXR.gl_binding.getViewSubImage(layer, GodotWebXR.pose.views[0]);
+ },
+
+ getTextureId: (texture) => {
+ if (texture.name !== undefined) {
+ return texture.name;
+ }
+
+ const id = GL.getNewId(GL.textures);
+ texture.name = id;
+ GL.textures[id] = texture;
+
+ return id;
+ },
+
+ addInputSource: (input_source) => {
+ let name = -1;
+ if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'left') {
+ name = 0;
+ } else if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'right') {
+ name = 1;
+ } else {
+ for (let i = 2; i < 16; i++) {
+ if (!GodotWebXR.input_sources[i]) {
+ name = i;
+ break;
}
- } else {
- controllers[other_index++] = input_source;
}
- });
- GodotWebXR.controllers = controllers;
+ }
+ if (name >= 0) {
+ GodotWebXR.input_sources[name] = input_source;
+ input_source.name = name;
+
+ // Find a free touch index for screen sources.
+ if (input_source.targetRayMode === 'screen') {
+ let touch_index = -1;
+ for (let i = 0; i < 5; i++) {
+ if (!GodotWebXR.touches[i]) {
+ touch_index = i;
+ break;
+ }
+ }
+ if (touch_index >= 0) {
+ GodotWebXR.touches[touch_index] = input_source;
+ input_source.touch_index = touch_index;
+ }
+ }
+ }
+ return name;
},
- getControllerId: (input_source) => GodotWebXR.controllers.indexOf(input_source),
+ removeInputSource: (input_source) => {
+ if (input_source.name !== undefined) {
+ const name = input_source.name;
+ if (name >= 0 && name < 16) {
+ GodotWebXR.input_sources[name] = null;
+ }
+
+ if (input_source.touch_index !== undefined) {
+ const touch_index = input_source.touch_index;
+ if (touch_index >= 0 && touch_index < 5) {
+ GodotWebXR.touches[touch_index] = null;
+ }
+ }
+ return name;
+ }
+ return -1;
+ },
+
+ getInputSourceId: (input_source) => {
+ if (input_source !== undefined) {
+ return input_source.name;
+ }
+ return -1;
+ },
+
+ getTouchIndex: (input_source) => {
+ if (input_source.touch_index !== undefined) {
+ return input_source.touch_index;
+ }
+ return -1;
+ },
},
godot_webxr_is_supported__proxy: 'sync',
@@ -132,8 +231,8 @@ const GodotWebXR = {
godot_webxr_initialize__deps: ['emscripten_webgl_get_current_context'],
godot_webxr_initialize__proxy: 'sync',
- godot_webxr_initialize__sig: 'viiiiiiiiii',
- godot_webxr_initialize: function (p_session_mode, p_required_features, p_optional_features, p_requested_reference_spaces, p_on_session_started, p_on_session_ended, p_on_session_failed, p_on_controller_changed, p_on_input_event, p_on_simple_event) {
+ godot_webxr_initialize__sig: 'viiiiiiiii',
+ godot_webxr_initialize: function (p_session_mode, p_required_features, p_optional_features, p_requested_reference_spaces, p_on_session_started, p_on_session_ended, p_on_session_failed, p_on_input_event, p_on_simple_event) {
GodotWebXR.monkeyPatchRequestAnimationFrame(true);
const session_mode = GodotRuntime.parseString(p_session_mode);
@@ -143,7 +242,6 @@ const GodotWebXR = {
const onstarted = GodotRuntime.get_func(p_on_session_started);
const onended = GodotRuntime.get_func(p_on_session_ended);
const onfailed = GodotRuntime.get_func(p_on_session_failed);
- const oncontroller = GodotRuntime.get_func(p_on_controller_changed);
const oninputevent = GodotRuntime.get_func(p_on_input_event);
const onsimpleevent = GodotRuntime.get_func(p_on_simple_event);
@@ -163,24 +261,18 @@ const GodotWebXR = {
});
session.addEventListener('inputsourceschange', function (evt) {
- let controller_changed = false;
- [evt.added, evt.removed].forEach((lst) => {
- lst.forEach((input_source) => {
- if (input_source.targetRayMode === 'tracked-pointer') {
- controller_changed = true;
- }
- });
- });
- if (controller_changed) {
- oncontroller();
- }
+ evt.added.forEach(GodotWebXR.addInputSource);
+ evt.removed.forEach(GodotWebXR.removeInputSource);
});
- ['selectstart', 'select', 'selectend', 'squeezestart', 'squeeze', 'squeezeend'].forEach((input_event) => {
+ ['selectstart', 'selectend', 'squeezestart', 'squeezeend'].forEach((input_event, index) => {
session.addEventListener(input_event, function (evt) {
- const c_str = GodotRuntime.allocString(input_event);
- oninputevent(c_str, GodotWebXR.getControllerId(evt.inputSource));
- GodotRuntime.free(c_str);
+ // Since this happens in-between normal frames, we need to
+ // grab the frame from the event in order to get poses for
+ // the input sources.
+ GodotWebXR.frame = evt.frame;
+ oninputevent(index, GodotWebXR.getInputSourceId(evt.inputSource));
+ GodotWebXR.frame = null;
});
});
@@ -195,9 +287,10 @@ const GodotWebXR = {
GodotWebXR.gl = gl;
gl.makeXRCompatible().then(function () {
- session.updateRenderState({
- baseLayer: new XRWebGLLayer(session, gl),
- });
+ GodotWebXR.gl_binding = new XRWebGLBinding(session, gl); // eslint-disable-line no-undef
+
+ // This will trigger the layer to get created.
+ GodotWebXR.getLayer();
function onReferenceSpaceSuccess(reference_space, reference_space_type) {
GodotWebXR.space = reference_space;
@@ -266,9 +359,14 @@ const GodotWebXR = {
}
GodotWebXR.session = null;
+ GodotWebXR.gl_binding = null;
+ GodotWebXR.layer = null;
GodotWebXR.space = null;
GodotWebXR.frame = null;
GodotWebXR.pose = null;
+ GodotWebXR.view_count = 1;
+ GodotWebXR.input_sources = new Array(16);
+ GodotWebXR.touches = new Array(5);
// Disable the monkey-patched window.requestAnimationFrame() and
// pause/restart the main loop to activate it on all platforms.
@@ -280,215 +378,186 @@ const GodotWebXR = {
godot_webxr_get_view_count__sig: 'i',
godot_webxr_get_view_count: function () {
if (!GodotWebXR.session || !GodotWebXR.pose) {
- return 0;
+ return 1;
}
- return GodotWebXR.pose.views.length;
+ const view_count = GodotWebXR.pose.views.length;
+ return view_count > 0 ? view_count : 1;
},
godot_webxr_get_render_target_size__proxy: 'sync',
- godot_webxr_get_render_target_size__sig: 'i',
- godot_webxr_get_render_target_size: function () {
- if (!GodotWebXR.session || !GodotWebXR.pose) {
- return 0;
+ godot_webxr_get_render_target_size__sig: 'ii',
+ godot_webxr_get_render_target_size: function (r_size) {
+ const subimage = GodotWebXR.getSubImage();
+ if (subimage === null) {
+ return false;
}
- const glLayer = GodotWebXR.session.renderState.baseLayer;
- const view = GodotWebXR.pose.views[0];
- const viewport = glLayer.getViewport(view);
+ GodotRuntime.setHeapValue(r_size + 0, subimage.viewport.width, 'i32');
+ GodotRuntime.setHeapValue(r_size + 4, subimage.viewport.height, 'i32');
- const buf = GodotRuntime.malloc(2 * 4);
- GodotRuntime.setHeapValue(buf + 0, viewport.width, 'i32');
- GodotRuntime.setHeapValue(buf + 4, viewport.height, 'i32');
- return buf;
+ return true;
},
- godot_webxr_get_transform_for_eye__proxy: 'sync',
- godot_webxr_get_transform_for_eye__sig: 'ii',
- godot_webxr_get_transform_for_eye: function (p_eye) {
+ godot_webxr_get_transform_for_view__proxy: 'sync',
+ godot_webxr_get_transform_for_view__sig: 'iii',
+ godot_webxr_get_transform_for_view: function (p_view, r_transform) {
if (!GodotWebXR.session || !GodotWebXR.pose) {
- return 0;
+ return false;
}
const views = GodotWebXR.pose.views;
let matrix;
- if (p_eye === 0) {
- matrix = GodotWebXR.pose.transform.matrix;
+ if (p_view >= 0) {
+ matrix = views[p_view].transform.matrix;
} else {
- matrix = views[p_eye - 1].transform.matrix;
- }
- const buf = GodotRuntime.malloc(16 * 4);
- for (let i = 0; i < 16; i++) {
- GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
- }
- return buf;
- },
-
- godot_webxr_get_projection_for_eye__proxy: 'sync',
- godot_webxr_get_projection_for_eye__sig: 'ii',
- godot_webxr_get_projection_for_eye: function (p_eye) {
- if (!GodotWebXR.session || !GodotWebXR.pose) {
- return 0;
+ // For -1 (or any other negative value) return the HMD transform.
+ matrix = GodotWebXR.pose.transform.matrix;
}
- const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
- const matrix = GodotWebXR.pose.views[view_index].projectionMatrix;
- const buf = GodotRuntime.malloc(16 * 4);
for (let i = 0; i < 16; i++) {
- GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
+ GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float');
}
- return buf;
+
+ return true;
},
- godot_webxr_commit__proxy: 'sync',
- godot_webxr_commit__sig: 'vi',
- godot_webxr_commit: function (p_texture) {
+ godot_webxr_get_projection_for_view__proxy: 'sync',
+ godot_webxr_get_projection_for_view__sig: 'iii',
+ godot_webxr_get_projection_for_view: function (p_view, r_transform) {
if (!GodotWebXR.session || !GodotWebXR.pose) {
- return;
+ return false;
}
- const glLayer = GodotWebXR.session.renderState.baseLayer;
- const views = GodotWebXR.pose.views;
- const gl = GodotWebXR.gl;
-
- const texture = GL.textures[p_texture];
-
- const orig_framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
- const orig_read_framebuffer = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
- const orig_read_buffer = gl.getParameter(gl.READ_BUFFER);
- const orig_draw_framebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
-
- // Copy from Godot render target into framebuffer from WebXR.
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- for (let i = 0; i < views.length; i++) {
- const viewport = glLayer.getViewport(views[i]);
-
- const read_fbo = gl.createFramebuffer();
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read_fbo);
- if (views.length > 1) {
- gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 0, i);
- } else {
- gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
- }
- gl.readBuffer(gl.COLOR_ATTACHMENT0);
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, glLayer.framebuffer);
-
- // Flip Y upside down on destination.
- gl.blitFramebuffer(0, 0, viewport.width, viewport.height,
- viewport.x, viewport.y + viewport.height, viewport.x + viewport.width, viewport.y,
- gl.COLOR_BUFFER_BIT, gl.NEAREST);
-
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
- gl.deleteFramebuffer(read_fbo);
+ const matrix = GodotWebXR.pose.views[p_view].projectionMatrix;
+ for (let i = 0; i < 16; i++) {
+ GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float');
}
- // Restore state.
- gl.bindFramebuffer(gl.FRAMEBUFFER, orig_framebuffer);
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, orig_read_framebuffer);
- gl.readBuffer(orig_read_buffer);
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, orig_draw_framebuffer);
+ return true;
},
- godot_webxr_sample_controller_data__proxy: 'sync',
- godot_webxr_sample_controller_data__sig: 'v',
- godot_webxr_sample_controller_data: function () {
- GodotWebXR.sampleControllers();
+ godot_webxr_get_color_texture__proxy: 'sync',
+ godot_webxr_get_color_texture__sig: 'i',
+ godot_webxr_get_color_texture: function () {
+ const subimage = GodotWebXR.getSubImage();
+ if (subimage === null) {
+ return 0;
+ }
+ return GodotWebXR.getTextureId(subimage.colorTexture);
},
- godot_webxr_get_controller_count__proxy: 'sync',
- godot_webxr_get_controller_count__sig: 'i',
- godot_webxr_get_controller_count: function () {
- if (!GodotWebXR.session || !GodotWebXR.frame) {
+ godot_webxr_get_depth_texture__proxy: 'sync',
+ godot_webxr_get_depth_texture__sig: 'i',
+ godot_webxr_get_depth_texture: function () {
+ const subimage = GodotWebXR.getSubImage();
+ if (subimage === null) {
return 0;
}
- return GodotWebXR.controllers.length;
+ if (!subimage.depthStencilTexture) {
+ return 0;
+ }
+ return GodotWebXR.getTextureId(subimage.depthStencilTexture);
},
- godot_webxr_is_controller_connected__proxy: 'sync',
- godot_webxr_is_controller_connected__sig: 'ii',
- godot_webxr_is_controller_connected: function (p_controller) {
- if (!GodotWebXR.session || !GodotWebXR.frame) {
- return false;
+ godot_webxr_get_velocity_texture__proxy: 'sync',
+ godot_webxr_get_velocity_texture__sig: 'i',
+ godot_webxr_get_velocity_texture: function () {
+ const subimage = GodotWebXR.getSubImage();
+ if (subimage === null) {
+ return 0;
+ }
+ if (!subimage.motionVectorTexture) {
+ return 0;
}
- return !!GodotWebXR.controllers[p_controller];
+ return GodotWebXR.getTextureId(subimage.motionVectorTexture);
},
- godot_webxr_get_controller_transform__proxy: 'sync',
- godot_webxr_get_controller_transform__sig: 'ii',
- godot_webxr_get_controller_transform: function (p_controller) {
+ godot_webxr_update_input_source__proxy: 'sync',
+ godot_webxr_update_input_source__sig: 'iiiiiiiiiiii',
+ godot_webxr_update_input_source: function (p_input_source_id, r_target_pose, r_target_ray_mode, r_touch_index, r_has_grip_pose, r_grip_pose, r_has_standard_mapping, r_button_count, r_buttons, r_axes_count, r_axes) {
if (!GodotWebXR.session || !GodotWebXR.frame) {
return 0;
}
- const controller = GodotWebXR.controllers[p_controller];
- if (!controller) {
- return 0;
+ if (p_input_source_id < 0 || p_input_source_id >= GodotWebXR.input_sources.length || !GodotWebXR.input_sources[p_input_source_id]) {
+ return false;
}
+ const input_source = GodotWebXR.input_sources[p_input_source_id];
const frame = GodotWebXR.frame;
const space = GodotWebXR.space;
- const pose = frame.getPose(controller.targetRaySpace, space);
- if (!pose) {
+ // Target pose.
+ const target_pose = frame.getPose(input_source.targetRaySpace, space);
+ if (!target_pose) {
// This can mean that the controller lost tracking.
- return 0;
+ return false;
}
- const matrix = pose.transform.matrix;
-
- const buf = GodotRuntime.malloc(16 * 4);
+ const target_pose_matrix = target_pose.transform.matrix;
for (let i = 0; i < 16; i++) {
- GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
+ GodotRuntime.setHeapValue(r_target_pose + (i * 4), target_pose_matrix[i], 'float');
}
- return buf;
- },
- godot_webxr_get_controller_buttons__proxy: 'sync',
- godot_webxr_get_controller_buttons__sig: 'ii',
- godot_webxr_get_controller_buttons: function (p_controller) {
- if (GodotWebXR.controllers.length === 0) {
- return 0;
- }
+ // Target ray mode.
+ let target_ray_mode = 0;
+ switch (input_source.targetRayMode) {
+ case 'gaze':
+ target_ray_mode = 1;
+ break;
- const controller = GodotWebXR.controllers[p_controller];
- if (!controller || !controller.gamepad) {
- return 0;
- }
-
- const button_count = controller.gamepad.buttons.length;
+ case 'tracked-pointer':
+ target_ray_mode = 2;
+ break;
- const buf = GodotRuntime.malloc((button_count + 1) * 4);
- GodotRuntime.setHeapValue(buf, button_count, 'i32');
- for (let i = 0; i < button_count; i++) {
- GodotRuntime.setHeapValue(buf + 4 + (i * 4), controller.gamepad.buttons[i].value, 'float');
- }
- return buf;
- },
+ case 'screen':
+ target_ray_mode = 3;
+ break;
- godot_webxr_get_controller_axes__proxy: 'sync',
- godot_webxr_get_controller_axes__sig: 'ii',
- godot_webxr_get_controller_axes: function (p_controller) {
- if (GodotWebXR.controllers.length === 0) {
- return 0;
+ default:
}
-
- const controller = GodotWebXR.controllers[p_controller];
- if (!controller || !controller.gamepad) {
- return 0;
+ GodotRuntime.setHeapValue(r_target_ray_mode, target_ray_mode, 'i32');
+
+ // Touch index.
+ GodotRuntime.setHeapValue(r_touch_index, GodotWebXR.getTouchIndex(input_source), 'i32');
+
+ // Grip pose.
+ let has_grip_pose = false;
+ if (input_source.gripSpace) {
+ const grip_pose = frame.getPose(input_source.gripSpace, space);
+ if (grip_pose) {
+ const grip_pose_matrix = grip_pose.transform.matrix;
+ for (let i = 0; i < 16; i++) {
+ GodotRuntime.setHeapValue(r_grip_pose + (i * 4), grip_pose_matrix[i], 'float');
+ }
+ has_grip_pose = true;
+ }
}
+ GodotRuntime.setHeapValue(r_has_grip_pose, has_grip_pose ? 1 : 0, 'i32');
+
+ // Gamepad data (mapping, buttons and axes).
+ let has_standard_mapping = false;
+ let button_count = 0;
+ let axes_count = 0;
+ if (input_source.gamepad) {
+ if (input_source.gamepad.mapping === 'xr-standard') {
+ has_standard_mapping = true;
+ }
- const axes_count = controller.gamepad.axes.length;
+ button_count = Math.min(input_source.gamepad.buttons.length, 10);
+ for (let i = 0; i < button_count; i++) {
+ GodotRuntime.setHeapValue(r_buttons + (i * 4), input_source.gamepad.buttons[i].value, 'float');
+ }
- const buf = GodotRuntime.malloc((axes_count + 1) * 4);
- GodotRuntime.setHeapValue(buf, axes_count, 'i32');
- for (let i = 0; i < axes_count; i++) {
- let value = controller.gamepad.axes[i];
- if (i === 1 || i === 3) {
- // Invert the Y-axis on thumbsticks and trackpads, in order to
- // match OpenXR and other XR platform SDKs.
- value *= -1.0;
+ axes_count = Math.min(input_source.gamepad.axes.length, 10);
+ for (let i = 0; i < axes_count; i++) {
+ GodotRuntime.setHeapValue(r_axes + (i * 4), input_source.gamepad.axes[i], 'float');
}
- GodotRuntime.setHeapValue(buf + 4 + (i * 4), value, 'float');
}
- return buf;
+ GodotRuntime.setHeapValue(r_has_standard_mapping, has_standard_mapping ? 1 : 0, 'i32');
+ GodotRuntime.setHeapValue(r_button_count, button_count, 'i32');
+ GodotRuntime.setHeapValue(r_axes_count, axes_count, 'i32');
+
+ return true;
},
godot_webxr_get_visibility_state__proxy: 'sync',
@@ -502,8 +571,8 @@ const GodotWebXR = {
},
godot_webxr_get_bounds_geometry__proxy: 'sync',
- godot_webxr_get_bounds_geometry__sig: 'i',
- godot_webxr_get_bounds_geometry: function () {
+ godot_webxr_get_bounds_geometry__sig: 'ii',
+ godot_webxr_get_bounds_geometry: function (r_points) {
if (!GodotWebXR.space || !GodotWebXR.space.boundsGeometry) {
return 0;
}
@@ -513,7 +582,7 @@ const GodotWebXR = {
return 0;
}
- const buf = GodotRuntime.malloc(((point_count * 3) + 1) * 4);
+ const buf = GodotRuntime.malloc(point_count * 3 * 4);
GodotRuntime.setHeapValue(buf, point_count, 'i32');
for (let i = 0; i < point_count; i++) {
const point = GodotWebXR.space.boundsGeometry[i];
@@ -521,8 +590,9 @@ const GodotWebXR = {
GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.y, 'float');
GodotRuntime.setHeapValue(buf + ((i * 3) + 3) * 4, point.z, 'float');
}
+ GodotRuntime.setHeapValue(r_points, buf, 'i32');
- return buf;
+ return point_count;
},
};
diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js
index 9ea105aa93..4b88820b19 100644
--- a/modules/webxr/native/webxr.externs.js
+++ b/modules/webxr/native/webxr.externs.js
@@ -1,3 +1,7 @@
+/*
+ * WebXR Device API
+ */
+
/**
* @type {XR}
*/
@@ -497,3 +501,681 @@ XRPose.prototype.transform;
* @type {boolean}
*/
XRPose.prototype.emulatedPosition;
+
+/*
+ * WebXR Layers API Level 1
+ */
+
+/**
+ * @constructor XRLayer
+ */
+function XRLayer() {}
+
+/**
+ * @constructor XRLayerEventInit
+ */
+function XRLayerEventInit() {}
+
+/**
+ * @type {XRLayer}
+ */
+XRLayerEventInit.prototype.layer;
+
+/**
+ * @constructor XRLayerEvent
+ *
+ * @param {string} type
+ * @param {XRLayerEventInit} init
+ */
+function XRLayerEvent(type, init) {};
+
+/**
+ * @type {XRLayer}
+ */
+XRLayerEvent.prototype.layer;
+
+/**
+ * @constructor XRCompositionLayer
+ * @extends {XRLayer}
+ */
+function XRCompositionLayer() {};
+
+/**
+ * @type {string}
+ */
+XRCompositionLayer.prototype.layout;
+
+/**
+ * @type {boolean}
+ */
+XRCompositionLayer.prototype.blendTextureAberrationCorrection;
+
+/**
+ * @type {?boolean}
+ */
+XRCompositionLayer.prototype.chromaticAberrationCorrection;
+
+/**
+ * @type {boolean}
+ */
+XRCompositionLayer.prototype.forceMonoPresentation;
+
+/**
+ * @type {number}
+ */
+XRCompositionLayer.prototype.opacity;
+
+/**
+ * @type {number}
+ */
+XRCompositionLayer.prototype.mipLevels;
+
+/**
+ * @type {boolean}
+ */
+XRCompositionLayer.prototype.needsRedraw;
+
+/**
+ * @return {void}
+ */
+XRCompositionLayer.prototype.destroy = function () {};
+
+/**
+ * @constructor XRProjectionLayer
+ * @extends {XRCompositionLayer}
+ */
+function XRProjectionLayer() {}
+
+/**
+ * @type {number}
+ */
+XRProjectionLayer.prototype.textureWidth;
+
+/**
+ * @type {number}
+ */
+XRProjectionLayer.prototype.textureHeight;
+
+/**
+ * @type {number}
+ */
+XRProjectionLayer.prototype.textureArrayLength;
+
+/**
+ * @type {boolean}
+ */
+XRProjectionLayer.prototype.ignoreDepthValues;
+
+/**
+ * @type {?number}
+ */
+XRProjectionLayer.prototype.fixedFoveation;
+
+/**
+ * @type {XRRigidTransform}
+ */
+XRProjectionLayer.prototype.deltaPose;
+
+/**
+ * @constructor XRQuadLayer
+ * @extends {XRCompositionLayer}
+ */
+function XRQuadLayer() {}
+
+/**
+ * @type {XRSpace}
+ */
+XRQuadLayer.prototype.space;
+
+/**
+ * @type {XRRigidTransform}
+ */
+XRQuadLayer.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XRQuadLayer.prototype.width;
+
+/**
+ * @type {number}
+ */
+XRQuadLayer.prototype.height;
+
+/**
+ * @type {?function (XRLayerEvent)}
+ */
+XRQuadLayer.prototype.onredraw;
+
+/**
+ * @constructor XRCylinderLayer
+ * @extends {XRCompositionLayer}
+ */
+function XRCylinderLayer() {}
+
+/**
+ * @type {XRSpace}
+ */
+XRCylinderLayer.prototype.space;
+
+/**
+ * @type {XRRigidTransform}
+ */
+XRCylinderLayer.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XRCylinderLayer.prototype.radius;
+
+/**
+ * @type {number}
+ */
+XRCylinderLayer.prototype.centralAngle;
+
+/**
+ * @type {number}
+ */
+XRCylinderLayer.prototype.aspectRatio;
+
+/**
+ * @type {?function (XRLayerEvent)}
+ */
+XRCylinderLayer.prototype.onredraw;
+
+/**
+ * @constructor XREquirectLayer
+ * @extends {XRCompositionLayer}
+ */
+function XREquirectLayer() {}
+
+/**
+ * @type {XRSpace}
+ */
+XREquirectLayer.prototype.space;
+
+/**
+ * @type {XRRigidTransform}
+ */
+XREquirectLayer.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XREquirectLayer.prototype.radius;
+
+/**
+ * @type {number}
+ */
+XREquirectLayer.prototype.centralHorizontalAngle;
+
+/**
+ * @type {number}
+ */
+XREquirectLayer.prototype.upperVerticalAngle;
+
+/**
+ * @type {number}
+ */
+XREquirectLayer.prototype.lowerVerticalAngle;
+
+/**
+ * @type {?function (XRLayerEvent)}
+ */
+XREquirectLayer.prototype.onredraw;
+
+/**
+ * @constructor XRCubeLayer
+ * @extends {XRCompositionLayer}
+ */
+function XRCubeLayer() {}
+
+/**
+ * @type {XRSpace}
+ */
+XRCubeLayer.prototype.space;
+
+/**
+ * @type {DOMPointReadOnly}
+ */
+XRCubeLayer.prototype.orientation;
+
+/**
+ * @type {?function (XRLayerEvent)}
+ */
+XRCubeLayer.prototype.onredraw;
+
+/**
+ * @constructor XRSubImage
+ */
+function XRSubImage() {}
+
+/**
+ * @type {XRViewport}
+ */
+XRSubImage.prototype.viewport;
+
+/**
+ * @constructor XRWebGLSubImage
+ * @extends {XRSubImage}
+ */
+function XRWebGLSubImage () {}
+
+/**
+ * @type {WebGLTexture}
+ */
+XRWebGLSubImage.prototype.colorTexture;
+
+/**
+ * @type {?WebGLTexture}
+ */
+XRWebGLSubImage.prototype.depthStencilTexture;
+
+/**
+ * @type {?WebGLTexture}
+ */
+XRWebGLSubImage.prototype.motionVectorTexture;
+
+/**
+ * @type {?number}
+ */
+XRWebGLSubImage.prototype.imageIndex;
+
+/**
+ * @type {number}
+ */
+XRWebGLSubImage.prototype.colorTextureWidth;
+
+/**
+ * @type {number}
+ */
+XRWebGLSubImage.prototype.colorTextureHeight;
+
+/**
+ * @type {?number}
+ */
+XRWebGLSubImage.prototype.depthStencilTextureWidth;
+
+/**
+ * @type {?number}
+ */
+XRWebGLSubImage.prototype.depthStencilTextureHeight;
+
+/**
+ * @type {?number}
+ */
+
+XRWebGLSubImage.prototype.motionVectorTextureWidth;
+
+/**
+ * @type {?number}
+ */
+XRWebGLSubImage.prototype.motionVectorTextureHeight;
+
+/**
+ * @constructor XRProjectionLayerInit
+ */
+function XRProjectionLayerInit() {}
+
+/**
+ * @type {string}
+ */
+XRProjectionLayerInit.prototype.textureType;
+
+/**
+ * @type {number}
+ */
+XRProjectionLayerInit.prototype.colorFormat;
+
+/**
+ * @type {number}
+ */
+XRProjectionLayerInit.prototype.depthFormat;
+
+/**
+ * @type {number}
+ */
+XRProjectionLayerInit.prototype.scaleFactor;
+
+/**
+ * @constructor XRLayerInit
+ */
+function XRLayerInit() {}
+
+/**
+ * @type {XRSpace}
+ */
+XRLayerInit.prototype.space;
+
+/**
+ * @type {number}
+ */
+XRLayerInit.prototype.colorFormat;
+
+/**
+ * @type {number}
+ */
+XRLayerInit.prototype.depthFormat;
+
+/**
+ * @type {number}
+ */
+XRLayerInit.prototype.mipLevels;
+
+/**
+ * @type {number}
+ */
+XRLayerInit.prototype.viewPixelWidth;
+
+/**
+ * @type {number}
+ */
+XRLayerInit.prototype.viewPixelHeight;
+
+/**
+ * @type {string}
+ */
+XRLayerInit.prototype.layout;
+
+/**
+ * @type {boolean}
+ */
+XRLayerInit.prototype.isStatic;
+
+/**
+ * @constructor XRQuadLayerInit
+ * @extends {XRLayerInit}
+ */
+function XRQuadLayerInit() {}
+
+/**
+ * @type {string}
+ */
+XRQuadLayerInit.prototype.textureType;
+
+/**
+ * @type {?XRRigidTransform}
+ */
+XRQuadLayerInit.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XRQuadLayerInit.prototype.width;
+
+/**
+ * @type {number}
+ */
+XRQuadLayerInit.prototype.height;
+
+/**
+ * @constructor XRCylinderLayerInit
+ * @extends {XRLayerInit}
+ */
+function XRCylinderLayerInit() {}
+
+/**
+ * @type {string}
+ */
+XRCylinderLayerInit.prototype.textureType;
+
+/**
+ * @type {?XRRigidTransform}
+ */
+XRCylinderLayerInit.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XRCylinderLayerInit.prototype.radius;
+
+/**
+ * @type {number}
+ */
+XRCylinderLayerInit.prototype.centralAngle;
+
+/**
+ * @type {number}
+ */
+XRCylinderLayerInit.prototype.aspectRatio;
+
+/**
+ * @constructor XREquirectLayerInit
+ * @extends {XRLayerInit}
+ */
+function XREquirectLayerInit() {}
+
+/**
+ * @type {string}
+ */
+XREquirectLayerInit.prototype.textureType;
+
+/**
+ * @type {?XRRigidTransform}
+ */
+XREquirectLayerInit.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XREquirectLayerInit.prototype.radius;
+
+/**
+ * @type {number}
+ */
+XREquirectLayerInit.prototype.centralHorizontalAngle;
+
+/**
+ * @type {number}
+ */
+XREquirectLayerInit.prototype.upperVerticalAngle;
+
+/**
+ * @type {number}
+ */
+XREquirectLayerInit.prototype.lowerVerticalAngle;
+
+/**
+ * @constructor XRCubeLayerInit
+ * @extends {XRLayerInit}
+ */
+function XRCubeLayerInit() {}
+
+/**
+ * @type {DOMPointReadOnly}
+ */
+XRCubeLayerInit.prototype.orientation;
+
+/**
+ * @constructor XRWebGLBinding
+ *
+ * @param {XRSession} session
+ * @param {WebGLRenderContext|WebGL2RenderingContext} context
+ */
+function XRWebGLBinding(session, context) {}
+
+/**
+ * @type {number}
+ */
+XRWebGLBinding.prototype.nativeProjectionScaleFactor;
+
+/**
+ * @type {number}
+ */
+XRWebGLBinding.prototype.usesDepthValues;
+
+/**
+ * @param {XRProjectionLayerInit} init
+ * @return {XRProjectionLayer}
+ */
+XRWebGLBinding.prototype.createProjectionLayer = function (init) {};
+
+/**
+ * @param {XRQuadLayerInit} init
+ * @return {XRQuadLayer}
+ */
+XRWebGLBinding.prototype.createQuadLayer = function (init) {};
+
+/**
+ * @param {XRCylinderLayerInit} init
+ * @return {XRCylinderLayer}
+ */
+XRWebGLBinding.prototype.createCylinderLayer = function (init) {};
+
+/**
+ * @param {XREquirectLayerInit} init
+ * @return {XREquirectLayer}
+ */
+XRWebGLBinding.prototype.createEquirectLayer = function (init) {};
+
+/**
+ * @param {XRCubeLayerInit} init
+ * @return {XRCubeLayer}
+ */
+XRWebGLBinding.prototype.createCubeLayer = function (init) {};
+
+/**
+ * @param {XRCompositionLayer} layer
+ * @param {XRFrame} frame
+ * @param {string} eye
+ * @return {XRWebGLSubImage}
+ */
+XRWebGLBinding.prototype.getSubImage = function (layer, frame, eye) {};
+
+/**
+ * @param {XRProjectionLayer} layer
+ * @param {XRView} view
+ * @return {XRWebGLSubImage}
+ */
+XRWebGLBinding.prototype.getViewSubImage = function (layer, view) {};
+
+/**
+ * @constructor XRMediaLayerInit
+ */
+function XRMediaLayerInit() {}
+
+/**
+ * @type {XRSpace}
+ */
+XRMediaLayerInit.prototype.space;
+
+/**
+ * @type {string}
+ */
+XRMediaLayerInit.prototype.layout;
+
+/**
+ * @type {boolean}
+ */
+XRMediaLayerInit.prototype.invertStereo;
+
+/**
+ * @constructor XRMediaQuadLayerInit
+ * @extends {XRMediaLayerInit}
+ */
+function XRMediaQuadLayerInit() {}
+
+/**
+ * @type {XRRigidTransform}
+ */
+XRMediaQuadLayerInit.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XRMediaQuadLayerInit.prototype.width;
+
+/**
+ * @type {number}
+ */
+XRMediaQuadLayerInit.prototype.height;
+
+/**
+ * @constructor XRMediaCylinderLayerInit
+ * @extends {XRMediaLayerInit}
+ */
+function XRMediaCylinderLayerInit() {}
+
+/**
+ * @type {XRRigidTransform}
+ */
+XRMediaCylinderLayerInit.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XRMediaCylinderLayerInit.prototype.radius;
+
+/**
+ * @type {number}
+ */
+XRMediaCylinderLayerInit.prototype.centralAngle;
+
+/**
+ * @type {?number}
+ */
+XRMediaCylinderLayerInit.prototype.aspectRatio;
+
+/**
+ * @constructor XRMediaEquirectLayerInit
+ * @extends {XRMediaLayerInit}
+ */
+function XRMediaEquirectLayerInit() {}
+
+/**
+ * @type {XRRigidTransform}
+ */
+XRMediaEquirectLayerInit.prototype.transform;
+
+/**
+ * @type {number}
+ */
+XRMediaEquirectLayerInit.prototype.radius;
+
+/**
+ * @type {number}
+ */
+XRMediaEquirectLayerInit.prototype.centralHorizontalAngle;
+
+/**
+ * @type {number}
+ */
+XRMediaEquirectLayerInit.prototype.upperVerticalAngle;
+
+/**
+ * @type {number}
+ */
+XRMediaEquirectLayerInit.prototype.lowerVerticalAngle;
+
+/**
+ * @constructor XRMediaBinding
+ *
+ * @param {XRSession} session
+ */
+function XRMediaBinding(session) {}
+
+/**
+ * @param {HTMLVideoElement} video
+ * @param {XRMediaQuadLayerInit} init
+ * @return {XRQuadLayer}
+ */
+XRMediaBinding.prototype.createQuadLayer = function(video, init) {};
+
+/**
+ * @param {HTMLVideoElement} video
+ * @param {XRMediaCylinderLayerInit} init
+ * @return {XRCylinderLayer}
+ */
+XRMediaBinding.prototype.createCylinderLayer = function(video, init) {};
+
+/**
+ * @param {HTMLVideoElement} video
+ * @param {XRMediaEquirectLayerInit} init
+ * @return {XREquirectLayer}
+ */
+XRMediaBinding.prototype.createEquirectLayer = function(video, init) {};
+
+/**
+ * @type {Array<XRLayer>}
+ */
+XRRenderState.prototype.layers;
diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp
index b0ad53523a..c0580df172 100644
--- a/modules/webxr/webxr_interface.cpp
+++ b/modules/webxr/webxr_interface.cpp
@@ -42,9 +42,10 @@ void WebXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_reference_space_type"), &WebXRInterface::get_reference_space_type);
ClassDB::bind_method(D_METHOD("set_requested_reference_space_types", "requested_reference_space_types"), &WebXRInterface::set_requested_reference_space_types);
ClassDB::bind_method(D_METHOD("get_requested_reference_space_types"), &WebXRInterface::get_requested_reference_space_types);
- ClassDB::bind_method(D_METHOD("get_controller", "controller_id"), &WebXRInterface::get_controller);
+ ClassDB::bind_method(D_METHOD("is_input_source_active", "input_source_id"), &WebXRInterface::is_input_source_active);
+ ClassDB::bind_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::get_input_source_tracker);
+ ClassDB::bind_method(D_METHOD("get_input_source_target_ray_mode", "input_source_id"), &WebXRInterface::get_input_source_target_ray_mode);
ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state);
- ClassDB::bind_method(D_METHOD("get_bounds_geometry"), &WebXRInterface::get_bounds_geometry);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features");
@@ -52,20 +53,24 @@ void WebXRInterface::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "requested_reference_space_types", PROPERTY_HINT_NONE), "set_requested_reference_space_types", "get_requested_reference_space_types");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "reference_space_type", PROPERTY_HINT_NONE), "", "get_reference_space_type");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "visibility_state", PROPERTY_HINT_NONE), "", "get_visibility_state");
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "bounds_geometry", PROPERTY_HINT_NONE), "", "get_bounds_geometry");
ADD_SIGNAL(MethodInfo("session_supported", PropertyInfo(Variant::STRING, "session_mode"), PropertyInfo(Variant::BOOL, "supported")));
ADD_SIGNAL(MethodInfo("session_started"));
ADD_SIGNAL(MethodInfo("session_ended"));
ADD_SIGNAL(MethodInfo("session_failed", PropertyInfo(Variant::STRING, "message")));
- ADD_SIGNAL(MethodInfo("selectstart", PropertyInfo(Variant::INT, "controller_id")));
- ADD_SIGNAL(MethodInfo("select", PropertyInfo(Variant::INT, "controller_id")));
- ADD_SIGNAL(MethodInfo("selectend", PropertyInfo(Variant::INT, "controller_id")));
- ADD_SIGNAL(MethodInfo("squeezestart", PropertyInfo(Variant::INT, "controller_id")));
- ADD_SIGNAL(MethodInfo("squeeze", PropertyInfo(Variant::INT, "controller_id")));
- ADD_SIGNAL(MethodInfo("squeezeend", PropertyInfo(Variant::INT, "controller_id")));
+ ADD_SIGNAL(MethodInfo("selectstart", PropertyInfo(Variant::INT, "input_source_id")));
+ ADD_SIGNAL(MethodInfo("select", PropertyInfo(Variant::INT, "input_source_id")));
+ ADD_SIGNAL(MethodInfo("selectend", PropertyInfo(Variant::INT, "input_source_id")));
+ ADD_SIGNAL(MethodInfo("squeezestart", PropertyInfo(Variant::INT, "input_source_id")));
+ ADD_SIGNAL(MethodInfo("squeeze", PropertyInfo(Variant::INT, "input_source_id")));
+ ADD_SIGNAL(MethodInfo("squeezeend", PropertyInfo(Variant::INT, "input_source_id")));
ADD_SIGNAL(MethodInfo("visibility_state_changed"));
ADD_SIGNAL(MethodInfo("reference_space_reset"));
+
+ BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN);
+ BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE);
+ BIND_ENUM_CONSTANT(TARGET_RAY_MODE_TRACKED_POINTER);
+ BIND_ENUM_CONSTANT(TARGET_RAY_MODE_SCREEN);
}
diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h
index 801643bfa6..1afeb5bab0 100644
--- a/modules/webxr/webxr_interface.h
+++ b/modules/webxr/webxr_interface.h
@@ -45,6 +45,13 @@ protected:
static void _bind_methods();
public:
+ enum TargetRayMode {
+ TARGET_RAY_MODE_UNKNOWN,
+ TARGET_RAY_MODE_GAZE,
+ TARGET_RAY_MODE_TRACKED_POINTER,
+ TARGET_RAY_MODE_SCREEN,
+ };
+
virtual void is_session_supported(const String &p_session_mode) = 0;
virtual void set_session_mode(String p_session_mode) = 0;
virtual String get_session_mode() const = 0;
@@ -55,9 +62,12 @@ public:
virtual void set_requested_reference_space_types(String p_requested_reference_space_types) = 0;
virtual String get_requested_reference_space_types() const = 0;
virtual String get_reference_space_type() const = 0;
- virtual Ref<XRPositionalTracker> get_controller(int p_controller_id) const = 0;
+ virtual bool is_input_source_active(int p_input_source_id) const = 0;
+ virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0;
+ virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0;
virtual String get_visibility_state() const = 0;
- virtual PackedVector3Array get_bounds_geometry() const = 0;
};
+VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode);
+
#endif // WEBXR_INTERFACE_H
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index f6ed9f027e..265f6626a7 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -37,6 +37,8 @@
#include "drivers/gles3/storage/texture_storage.h"
#include "emscripten.h"
#include "godot_webxr.h"
+#include "scene/main/scene_tree.h"
+#include "scene/main/window.h"
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering/rendering_server_globals.h"
@@ -89,25 +91,14 @@ void _emwebxr_on_session_failed(char *p_message) {
interface->emit_signal(SNAME("session_failed"), message);
}
-void _emwebxr_on_controller_changed() {
+extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_input_event(int p_event_type, int p_input_source_id) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<XRInterface> interface = xr_server->find_interface("WebXR");
ERR_FAIL_COND(interface.is_null());
- static_cast<WebXRInterfaceJS *>(interface.ptr())->_on_controller_changed();
-}
-
-extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_input_event(char *p_signal_name, int p_input_source) {
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL(xr_server);
-
- Ref<XRInterface> interface = xr_server->find_interface("WebXR");
- ERR_FAIL_COND(interface.is_null());
-
- StringName signal_name = StringName(p_signal_name);
- interface->emit_signal(signal_name, p_input_source + 1);
+ ((WebXRInterfaceJS *)interface.ptr())->_on_input_event(p_event_type, p_input_source_id);
}
extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_simple_event(char *p_signal_name) {
@@ -165,16 +156,22 @@ String WebXRInterfaceJS::get_reference_space_type() const {
return reference_space_type;
}
-Ref<XRPositionalTracker> WebXRInterfaceJS::get_controller(int p_controller_id) const {
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, Ref<XRPositionalTracker>());
+bool WebXRInterfaceJS::is_input_source_active(int p_input_source_id) const {
+ ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, false);
+ return input_sources[p_input_source_id].active;
+}
- // TODO support more then two controllers
- if (p_controller_id >= 0 && p_controller_id < 2) {
- return controllers[p_controller_id];
- };
+Ref<XRPositionalTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const {
+ ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRPositionalTracker>());
+ return input_sources[p_input_source_id].tracker;
+}
- return Ref<XRPositionalTracker>();
+WebXRInterface::TargetRayMode WebXRInterfaceJS::get_input_source_target_ray_mode(int p_input_source_id) const {
+ ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, WebXRInterface::TARGET_RAY_MODE_UNKNOWN);
+ if (!input_sources[p_input_source_id].active) {
+ return WebXRInterface::TARGET_RAY_MODE_UNKNOWN;
+ }
+ return input_sources[p_input_source_id].target_ray_mode;
}
String WebXRInterfaceJS::get_visibility_state() const {
@@ -188,17 +185,18 @@ String WebXRInterfaceJS::get_visibility_state() const {
return String();
}
-PackedVector3Array WebXRInterfaceJS::get_bounds_geometry() const {
+PackedVector3Array WebXRInterfaceJS::get_play_area() const {
PackedVector3Array ret;
- int *js_bounds = godot_webxr_get_bounds_geometry();
- if (js_bounds) {
- ret.resize(js_bounds[0]);
- for (int i = 0; i < js_bounds[0]; i++) {
- float *js_vector3 = ((float *)js_bounds) + (i * 3) + 1;
+ float *points;
+ int point_count = godot_webxr_get_bounds_geometry(&points);
+ if (point_count > 0) {
+ ret.resize(point_count);
+ for (int i = 0; i < point_count; i++) {
+ float *js_vector3 = points + (i * 3);
ret.set(i, Vector3(js_vector3[0], js_vector3[1], js_vector3[2]));
}
- free(js_bounds);
+ free(points);
}
return ret;
@@ -209,7 +207,7 @@ StringName WebXRInterfaceJS::get_name() const {
};
uint32_t WebXRInterfaceJS::get_capabilities() const {
- return XRInterface::XR_STEREO | XRInterface::XR_MONO;
+ return XRInterface::XR_STEREO | XRInterface::XR_MONO | XRInterface::XR_VR | XRInterface::XR_AR;
};
uint32_t WebXRInterfaceJS::get_view_count() {
@@ -261,7 +259,6 @@ bool WebXRInterfaceJS::initialize() {
&_emwebxr_on_session_started,
&_emwebxr_on_session_ended,
&_emwebxr_on_session_failed,
- &_emwebxr_on_controller_changed,
&_emwebxr_on_input_event,
&_emwebxr_on_simple_event);
};
@@ -287,6 +284,18 @@ void WebXRInterfaceJS::uninitialize() {
godot_webxr_uninitialize();
+ GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage);
+ if (texture_storage != nullptr) {
+ for (KeyValue<unsigned int, RID> &E : texture_cache) {
+ // Forcibly mark as not part of a render target so we can free it.
+ GLES3::Texture *texture = texture_storage->get_texture(E.value);
+ texture->is_render_target = false;
+
+ texture_storage->texture_free(E.value);
+ }
+ }
+
+ texture_cache.clear();
reference_space_type = "";
initialized = false;
};
@@ -316,27 +325,26 @@ Size2 WebXRInterfaceJS::get_render_target_size() {
return render_targetsize;
}
- int *js_size = godot_webxr_get_render_target_size();
- if (!initialized || js_size == nullptr) {
- // As a temporary default (until WebXR is fully initialized), use half the window size.
- Size2 temp = DisplayServer::get_singleton()->window_get_size();
- temp.width /= 2.0;
- return temp;
- }
+ int js_size[2];
+ bool has_size = godot_webxr_get_render_target_size(js_size);
- render_targetsize.width = js_size[0];
- render_targetsize.height = js_size[1];
+ if (!initialized || !has_size) {
+ // As a temporary default (until WebXR is fully initialized), use the
+ // window size.
+ return DisplayServer::get_singleton()->window_get_size();
+ }
- free(js_size);
+ render_targetsize.width = (float)js_size[0];
+ render_targetsize.height = (float)js_size[1];
return render_targetsize;
};
Transform3D WebXRInterfaceJS::get_camera_transform() {
- Transform3D transform_for_eye;
+ Transform3D camera_transform;
XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, transform_for_eye);
+ ERR_FAIL_NULL_V(xr_server, camera_transform);
if (initialized) {
float world_scale = xr_server->get_world_scale();
@@ -345,181 +353,382 @@ Transform3D WebXRInterfaceJS::get_camera_transform() {
Transform3D _head_transform = head_transform;
_head_transform.origin *= world_scale;
- transform_for_eye = (xr_server->get_reference_frame()) * _head_transform;
+ camera_transform = (xr_server->get_reference_frame()) * _head_transform;
}
- return transform_for_eye;
+ return camera_transform;
};
Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) {
- Transform3D transform_for_eye;
-
XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, transform_for_eye);
+ ERR_FAIL_NULL_V(xr_server, p_cam_transform);
+ ERR_FAIL_COND_V(!initialized, p_cam_transform);
- float *js_matrix = godot_webxr_get_transform_for_eye(p_view + 1);
- if (!initialized || js_matrix == nullptr) {
- transform_for_eye = p_cam_transform;
- return transform_for_eye;
+ float js_matrix[16];
+ bool has_transform = godot_webxr_get_transform_for_view(p_view, js_matrix);
+ if (!has_transform) {
+ return p_cam_transform;
}
- transform_for_eye = _js_matrix_to_transform(js_matrix);
- free(js_matrix);
+ Transform3D transform_for_view = _js_matrix_to_transform(js_matrix);
float world_scale = xr_server->get_world_scale();
// Scale only the center point of our eye transform, so we don't scale the
// distance between the eyes.
Transform3D _head_transform = head_transform;
- transform_for_eye.origin -= _head_transform.origin;
+ transform_for_view.origin -= _head_transform.origin;
_head_transform.origin *= world_scale;
- transform_for_eye.origin += _head_transform.origin;
+ transform_for_view.origin += _head_transform.origin;
- return p_cam_transform * xr_server->get_reference_frame() * transform_for_eye;
+ return p_cam_transform * xr_server->get_reference_frame() * transform_for_view;
};
Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) {
- Projection eye;
+ Projection view;
+
+ ERR_FAIL_COND_V(!initialized, view);
- float *js_matrix = godot_webxr_get_projection_for_eye(p_view + 1);
- if (!initialized || js_matrix == nullptr) {
- return eye;
+ float js_matrix[16];
+ bool has_projection = godot_webxr_get_projection_for_view(p_view, js_matrix);
+ if (!has_projection) {
+ return view;
}
int k = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
- eye.columns[i][j] = js_matrix[k++];
+ view.columns[i][j] = js_matrix[k++];
}
}
- free(js_matrix);
-
// Copied from godot_oculus_mobile's ovr_mobile_session.cpp
- eye.columns[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near);
- eye.columns[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near);
+ view.columns[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near);
+ view.columns[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near);
- return eye;
+ return view;
+}
+
+bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) {
+ GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage);
+ if (texture_storage == nullptr) {
+ return false;
+ }
+
+ GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target);
+ if (rt == nullptr) {
+ return false;
+ }
+
+ // Cache the resources so we don't have to get them from JS twice.
+ color_texture = _get_color_texture();
+ depth_texture = _get_depth_texture();
+
+ // Per the WebXR spec, it returns "opaque textures" to us, which may be the
+ // same WebGLTexture object (which would be the same GLuint in C++) but
+ // represent a different underlying resource (probably the next texture in
+ // the XR device's swap chain). In order to render to this texture, we need
+ // to re-attach it to the FBO, otherwise we get an "incomplete FBO" error.
+ //
+ // See: https://immersive-web.github.io/layers/#xropaquetextures
+ //
+ // This is why we're doing this sort of silly check: if the color and depth
+ // textures are the same this frame as last frame, we need to attach them
+ // again, despite the fact that the GLuint for them hasn't changed.
+ if (rt->overridden.is_overridden && rt->overridden.color == color_texture && rt->overridden.depth == depth_texture) {
+ GLES3::Config *config = GLES3::Config::get_singleton();
+ bool use_multiview = rt->view_count > 1 && config->multiview_supported;
+
+ glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo);
+ if (use_multiview) {
+ glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->color, 0, 0, rt->view_count);
+ glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count);
+ } else {
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->color, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->depth, 0);
+ }
+ glBindFramebuffer(GL_FRAMEBUFFER, texture_storage->system_fbo);
+ }
+
+ return true;
}
Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) {
Vector<BlitToScreen> blit_to_screen;
- if (!initialized) {
- return blit_to_screen;
+ // We don't need to do anything here.
+
+ return blit_to_screen;
+};
+
+RID WebXRInterfaceJS::_get_color_texture() {
+ unsigned int texture_id = godot_webxr_get_color_texture();
+ if (texture_id == 0) {
+ return RID();
+ }
+
+ return _get_texture(texture_id);
+}
+
+RID WebXRInterfaceJS::_get_depth_texture() {
+ unsigned int texture_id = godot_webxr_get_depth_texture();
+ if (texture_id == 0) {
+ return RID();
+ }
+
+ return _get_texture(texture_id);
+}
+
+RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) {
+ RBMap<unsigned int, RID>::Element *cache = texture_cache.find(p_texture_id);
+ if (cache != nullptr) {
+ return cache->get();
}
GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage);
- if (!texture_storage) {
- return blit_to_screen;
+ if (texture_storage == nullptr) {
+ return RID();
}
- GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target);
+ uint32_t view_count = godot_webxr_get_view_count();
+ Size2 texture_size = get_render_target_size();
- godot_webxr_commit(rt->color);
+ RID texture = texture_storage->texture_create_external(
+ view_count == 1 ? GLES3::Texture::TYPE_2D : GLES3::Texture::TYPE_LAYERED,
+ Image::FORMAT_RGBA8,
+ p_texture_id,
+ (int)texture_size.width,
+ (int)texture_size.height,
+ 1,
+ view_count);
- return blit_to_screen;
-};
+ texture_cache.insert(p_texture_id, texture);
+
+ return texture;
+}
+
+RID WebXRInterfaceJS::get_color_texture() {
+ return color_texture;
+}
+
+RID WebXRInterfaceJS::get_depth_texture() {
+ return depth_texture;
+}
+
+RID WebXRInterfaceJS::get_velocity_texture() {
+ unsigned int texture_id = godot_webxr_get_velocity_texture();
+ if (texture_id == 0) {
+ return RID();
+ }
+
+ return _get_texture(texture_id);
+}
void WebXRInterfaceJS::process() {
if (initialized) {
// Get the "head" position.
- float *js_matrix = godot_webxr_get_transform_for_eye(0);
- if (js_matrix != nullptr) {
+ float js_matrix[16];
+ if (godot_webxr_get_transform_for_view(-1, js_matrix)) {
head_transform = _js_matrix_to_transform(js_matrix);
- free(js_matrix);
}
if (head_tracker.is_valid()) {
head_tracker->set_pose("default", head_transform, Vector3(), Vector3());
}
- godot_webxr_sample_controller_data();
- int controller_count = godot_webxr_get_controller_count();
- for (int i = 0; i < controller_count; i++) {
- _update_tracker(i);
+ // Update all input sources.
+ for (int i = 0; i < input_source_count; i++) {
+ _update_input_source(i);
}
};
};
-void WebXRInterfaceJS::_update_tracker(int p_controller_id) {
+void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
- // need to support more then two controllers...
- if (p_controller_id < 0 || p_controller_id > 1) {
+ InputSource &input_source = input_sources[p_input_source_id];
+
+ float target_pose[16];
+ int tmp_target_ray_mode;
+ int touch_index;
+ int has_grip_pose;
+ float grip_pose[16];
+ int has_standard_mapping;
+ int button_count;
+ float buttons[10];
+ int axes_count;
+ float axes[10];
+
+ input_source.active = godot_webxr_update_input_source(
+ p_input_source_id,
+ target_pose,
+ &tmp_target_ray_mode,
+ &touch_index,
+ &has_grip_pose,
+ grip_pose,
+ &has_standard_mapping,
+ &button_count,
+ buttons,
+ &axes_count,
+ axes);
+
+ if (!input_source.active) {
+ if (input_source.tracker.is_valid()) {
+ xr_server->remove_tracker(input_source.tracker);
+ input_source.tracker.unref();
+ }
return;
}
- Ref<XRPositionalTracker> tracker = controllers[p_controller_id];
- if (godot_webxr_is_controller_connected(p_controller_id)) {
- if (tracker.is_null()) {
- tracker.instantiate();
- tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
- // Controller id's 0 and 1 are always the left and right hands.
- if (p_controller_id < 2) {
- tracker->set_tracker_name(p_controller_id == 0 ? "left_hand" : "right_hand");
- tracker->set_tracker_desc(p_controller_id == 0 ? "Left hand controller" : "Right hand controller");
- tracker->set_tracker_hand(p_controller_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
- } else {
- char name[1024];
- sprintf(name, "tracker_%i", p_controller_id);
- tracker->set_tracker_name(name);
- tracker->set_tracker_desc(name);
- }
- xr_server->add_tracker(tracker);
+ input_source.target_ray_mode = (WebXRInterface::TargetRayMode)tmp_target_ray_mode;
+ input_source.touch_index = touch_index;
+
+ Ref<XRPositionalTracker> &tracker = input_source.tracker;
+
+ if (tracker.is_null()) {
+ tracker.instantiate();
+
+ StringName tracker_name;
+ if (input_source.target_ray_mode == WebXRInterface::TargetRayMode::TARGET_RAY_MODE_SCREEN) {
+ tracker_name = touch_names[touch_index];
+ } else {
+ tracker_name = tracker_names[p_input_source_id];
}
- float *tracker_matrix = godot_webxr_get_controller_transform(p_controller_id);
- if (tracker_matrix) {
- // Note, poses should NOT have world scale and our reference frame applied!
- Transform3D transform = _js_matrix_to_transform(tracker_matrix);
- tracker->set_pose("default", transform, Vector3(), Vector3());
- free(tracker_matrix);
+ // Input source id's 0 and 1 are always the left and right hands.
+ if (p_input_source_id < 2) {
+ tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
+ tracker->set_tracker_name(tracker_name);
+ tracker->set_tracker_desc(p_input_source_id == 0 ? "Left hand controller" : "Right hand controller");
+ tracker->set_tracker_hand(p_input_source_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
+ } else {
+ tracker->set_tracker_name(tracker_name);
+ tracker->set_tracker_desc(tracker_name);
}
+ xr_server->add_tracker(tracker);
+ }
- // TODO implement additional poses such as "aim" and "grip"
+ Transform3D aim_transform = _js_matrix_to_transform(target_pose);
+ tracker->set_pose(SNAME("default"), aim_transform, Vector3(), Vector3());
+ tracker->set_pose(SNAME("aim"), aim_transform, Vector3(), Vector3());
+ if (has_grip_pose) {
+ tracker->set_pose(SNAME("grip"), _js_matrix_to_transform(grip_pose), Vector3(), Vector3());
+ }
- int *buttons = godot_webxr_get_controller_buttons(p_controller_id);
- if (buttons) {
- // TODO buttons should be named properly, this is just a temporary fix
- for (int i = 0; i < buttons[0]; i++) {
- char name[1024];
- sprintf(name, "button_%i", i);
+ for (int i = 0; i < button_count; i++) {
+ StringName button_name = has_standard_mapping ? standard_button_names[i] : unknown_button_names[i];
+ StringName button_pressure_name = has_standard_mapping ? standard_button_pressure_names[i] : unknown_button_pressure_names[i];
+ float value = buttons[i];
+ bool state = value > 0.0;
+ tracker->set_input(button_name, state);
+ tracker->set_input(button_pressure_name, value);
+ }
- float value = *((float *)buttons + (i + 1));
- bool state = value > 0.0;
- tracker->set_input(name, state);
- }
- free(buttons);
+ for (int i = 0; i < axes_count; i++) {
+ StringName axis_name = has_standard_mapping ? standard_axis_names[i] : unknown_axis_names[i];
+ float value = axes[i];
+ if (has_standard_mapping && (i == 1 || i == 3)) {
+ // Invert the Y-axis on thumbsticks and trackpads, in order to
+ // match OpenXR and other XR platform SDKs.
+ value = -value;
}
+ tracker->set_input(axis_name, value);
+ }
- int *axes = godot_webxr_get_controller_axes(p_controller_id);
- if (axes) {
- // TODO again just a temporary fix, split these between proper float and vector2 inputs
- for (int i = 0; i < axes[0]; i++) {
- char name[1024];
- sprintf(name, "axis_%i", i);
+ // Also create Vector2's for the thumbstick and trackpad when we have the
+ // standard mapping.
+ if (has_standard_mapping) {
+ if (axes_count >= 2) {
+ tracker->set_input(standard_vector_names[0], Vector2(axes[0], -axes[1]));
+ }
+ if (axes_count >= 4) {
+ tracker->set_input(standard_vector_names[1], Vector2(axes[2], -axes[3]));
+ }
+ }
- float value = *((float *)axes + (i + 1));
- tracker->set_input(name, value);
+ if (input_source.target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) {
+ if (touch_index < 5 && axes_count >= 2) {
+ Vector2 joy_vector = Vector2(axes[0], axes[1]);
+ Vector2 position = _get_screen_position_from_joy_vector(joy_vector);
+
+ if (touches[touch_index].is_touching) {
+ Vector2 delta = position - touches[touch_index].position;
+
+ // If position has changed by at least 1 pixel, generate a drag event.
+ if (abs(delta.x) >= 1.0 || abs(delta.y) >= 1.0) {
+ Ref<InputEventScreenDrag> event;
+ event.instantiate();
+ event->set_index(touch_index);
+ event->set_position(position);
+ event->set_relative(delta);
+ Input::get_singleton()->parse_input_event(event);
+ }
}
- free(axes);
+
+ touches[touch_index].position = position;
}
- } else if (tracker.is_valid()) {
- xr_server->remove_tracker(tracker);
- controllers[p_controller_id].unref();
}
}
-void WebXRInterfaceJS::_on_controller_changed() {
- // Register "virtual" gamepads with Godot for the ones we get from WebXR.
- godot_webxr_sample_controller_data();
- for (int i = 0; i < 2; i++) {
- bool controller_connected = godot_webxr_is_controller_connected(i);
- if (controllers_state[i] != controller_connected) {
- // Input::get_singleton()->joy_connection_changed(i + 100, controller_connected, i == 0 ? "Left" : "Right", "");
- controllers_state[i] = controller_connected;
+void WebXRInterfaceJS::_on_input_event(int p_event_type, int p_input_source_id) {
+ // Get the latest data for this input source. For transient input sources,
+ // we may not have any data at all yet!
+ _update_input_source(p_input_source_id);
+
+ if (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART || p_event_type == WEBXR_INPUT_EVENT_SELECTEND) {
+ const InputSource &input_source = input_sources[p_input_source_id];
+ if (input_source.target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) {
+ int touch_index = input_source.touch_index;
+ if (touch_index >= 0 && touch_index < 5) {
+ touches[touch_index].is_touching = (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART);
+
+ Ref<InputEventScreenTouch> event;
+ event.instantiate();
+ event->set_index(touch_index);
+ event->set_position(touches[touch_index].position);
+ event->set_pressed(p_event_type == WEBXR_INPUT_EVENT_SELECTSTART);
+
+ Input::get_singleton()->parse_input_event(event);
+ }
}
}
+
+ switch (p_event_type) {
+ case WEBXR_INPUT_EVENT_SELECTSTART:
+ emit_signal("selectstart", p_input_source_id);
+ break;
+
+ case WEBXR_INPUT_EVENT_SELECTEND:
+ emit_signal("selectend", p_input_source_id);
+ // Emit the 'select' event on our own (rather than intercepting the
+ // one from JavaScript) so that we don't have to needlessly call
+ // _update_input_source() a second time.
+ emit_signal("select", p_input_source_id);
+ break;
+
+ case WEBXR_INPUT_EVENT_SQUEEZESTART:
+ emit_signal("squeezestart", p_input_source_id);
+ break;
+
+ case WEBXR_INPUT_EVENT_SQUEEZEEND:
+ emit_signal("squeezeend", p_input_source_id);
+ // Again, we emit the 'squeeze' event on our own to avoid extra work.
+ emit_signal("squeeze", p_input_source_id);
+ break;
+ }
+}
+
+Vector2 WebXRInterfaceJS::_get_screen_position_from_joy_vector(const Vector2 &p_joy_vector) {
+ SceneTree *scene_tree = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop());
+ if (!scene_tree) {
+ return Vector2();
+ }
+
+ Window *viewport = scene_tree->get_root();
+
+ Vector2 position_percentage((p_joy_vector.x + 1.0f) / 2.0f, ((p_joy_vector.y) + 1.0f) / 2.0f);
+ Vector2 position = (Size2)viewport->get_size() * position_percentage;
+
+ return position;
}
WebXRInterfaceJS::WebXRInterfaceJS() {
diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h
index 319adc2ac9..6b484a8872 100644
--- a/modules/webxr/webxr_interface_js.h
+++ b/modules/webxr/webxr_interface_js.h
@@ -39,6 +39,10 @@
The WebXR interface is a VR/AR interface that can be used on the web.
*/
+namespace GLES3 {
+class TextureStorage;
+}
+
class WebXRInterfaceJS : public WebXRInterface {
GDCLASS(WebXRInterfaceJS, WebXRInterface);
@@ -53,13 +57,32 @@ private:
String requested_reference_space_types;
String reference_space_type;
- // TODO maybe turn into a vector to support more then 2 controllers...
- bool controllers_state[2];
- Ref<XRPositionalTracker> controllers[2];
Size2 render_targetsize;
-
+ RBMap<unsigned int, RID> texture_cache;
+ struct Touch {
+ bool is_touching = false;
+ Vector2 position;
+ } touches[5];
+
+ static constexpr uint8_t input_source_count = 16;
+
+ struct InputSource {
+ Ref<XRPositionalTracker> tracker;
+ bool active = false;
+ TargetRayMode target_ray_mode;
+ int touch_index = -1;
+ } input_sources[input_source_count];
+
+ RID color_texture;
+ RID depth_texture;
+
+ RID _get_color_texture();
+ RID _get_depth_texture();
+ RID _get_texture(unsigned int p_texture_id);
Transform3D _js_matrix_to_transform(float *p_js_matrix);
- void _update_tracker(int p_controller_id);
+ void _update_input_source(int p_input_source_id);
+
+ Vector2 _get_screen_position_from_joy_vector(const Vector2 &p_joy_vector);
public:
virtual void is_session_supported(const String &p_session_mode) override;
@@ -73,9 +96,11 @@ public:
virtual String get_requested_reference_space_types() const override;
void _set_reference_space_type(String p_reference_space_type);
virtual String get_reference_space_type() const override;
- virtual Ref<XRPositionalTracker> get_controller(int p_controller_id) const override;
+ virtual bool is_input_source_active(int p_input_source_id) const override;
+ virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const override;
+ virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const override;
virtual String get_visibility_state() const override;
- virtual PackedVector3Array get_bounds_geometry() const override;
+ virtual PackedVector3Array get_play_area() const override;
virtual StringName get_name() const override;
virtual uint32_t get_capabilities() const override;
@@ -89,14 +114,129 @@ public:
virtual Transform3D get_camera_transform() override;
virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override;
virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override;
+ virtual bool pre_draw_viewport(RID p_render_target) override;
virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override;
+ virtual RID get_color_texture() override;
+ virtual RID get_depth_texture() override;
+ virtual RID get_velocity_texture() override;
virtual void process() override;
- void _on_controller_changed();
+ void _on_input_event(int p_event_type, int p_input_source_id);
WebXRInterfaceJS();
~WebXRInterfaceJS();
+
+private:
+ StringName tracker_names[16] = {
+ StringName("left_hand"),
+ StringName("right_hand"),
+ StringName("tracker_2"),
+ StringName("tracker_3"),
+ StringName("tracker_4"),
+ StringName("tracker_5"),
+ StringName("tracker_6"),
+ StringName("tracker_7"),
+ StringName("tracker_8"),
+ StringName("tracker_9"),
+ StringName("tracker_10"),
+ StringName("tracker_11"),
+ StringName("tracker_12"),
+ StringName("tracker_13"),
+ StringName("tracker_14"),
+ StringName("tracker_15"),
+ };
+
+ StringName touch_names[5] = {
+ StringName("touch_0"),
+ StringName("touch_1"),
+ StringName("touch_2"),
+ StringName("touch_3"),
+ StringName("touch_4"),
+ };
+
+ StringName standard_axis_names[10] = {
+ StringName("touchpad_x"),
+ StringName("touchpad_y"),
+ StringName("thumbstick_x"),
+ StringName("thumbstick_y"),
+ StringName("axis_4"),
+ StringName("axis_5"),
+ StringName("axis_6"),
+ StringName("axis_7"),
+ StringName("axis_8"),
+ StringName("axis_9"),
+ };
+
+ StringName standard_vector_names[2] = {
+ StringName("touchpad"),
+ StringName("thumbstick"),
+ };
+
+ StringName standard_button_names[10] = {
+ StringName("trigger_click"),
+ StringName("grip_click"),
+ StringName("touchpad_click"),
+ StringName("thumbstick_click"),
+ StringName("ax_button"),
+ StringName("by_button"),
+ StringName("button_6"),
+ StringName("button_7"),
+ StringName("button_8"),
+ StringName("button_9"),
+ };
+
+ StringName standard_button_pressure_names[10] = {
+ StringName("trigger"),
+ StringName("grip"),
+ StringName("touchpad_click_pressure"),
+ StringName("thumbstick_click_pressure"),
+ StringName("ax_button_pressure"),
+ StringName("by_button_pressure"),
+ StringName("button_pressure_6"),
+ StringName("button_pressure_7"),
+ StringName("button_pressure_8"),
+ StringName("button_pressure_9"),
+ };
+
+ StringName unknown_button_names[10] = {
+ StringName("button_0"),
+ StringName("button_1"),
+ StringName("button_2"),
+ StringName("button_3"),
+ StringName("button_4"),
+ StringName("button_5"),
+ StringName("button_6"),
+ StringName("button_7"),
+ StringName("button_8"),
+ StringName("button_9"),
+ };
+
+ StringName unknown_axis_names[10] = {
+ StringName("axis_0"),
+ StringName("axis_1"),
+ StringName("axis_2"),
+ StringName("axis_3"),
+ StringName("axis_4"),
+ StringName("axis_5"),
+ StringName("axis_6"),
+ StringName("axis_7"),
+ StringName("axis_8"),
+ StringName("axis_9"),
+ };
+
+ StringName unknown_button_pressure_names[10] = {
+ StringName("button_pressure_0"),
+ StringName("button_pressure_1"),
+ StringName("button_pressure_2"),
+ StringName("button_pressure_3"),
+ StringName("button_pressure_4"),
+ StringName("button_pressure_5"),
+ StringName("button_pressure_6"),
+ StringName("button_pressure_7"),
+ StringName("button_pressure_8"),
+ StringName("button_pressure_9"),
+ };
};
#endif // WEB_ENABLED
diff --git a/platform/android/detect.py b/platform/android/detect.py
index d4d6154472..ec36a40941 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -24,7 +24,11 @@ def can_build():
def get_opts():
return [
("ANDROID_SDK_ROOT", "Path to the Android SDK", get_env_android_sdk_root()),
- ("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"),
+ (
+ "ndk_platform",
+ 'Target platform (android-<api>, e.g. "android-' + str(get_min_target_api()) + '")',
+ "android-" + str(get_min_target_api()),
+ ),
]
@@ -46,6 +50,11 @@ def get_ndk_version():
return "23.2.8568313"
+# This is kept in sync with the value in 'platform/android/java/app/config.gradle'.
+def get_min_target_api():
+ return 21
+
+
def get_flags():
return [
("arch", "arm64"), # Default for convenience.
@@ -87,18 +96,18 @@ def configure(env: "Environment"):
)
sys.exit()
+ if get_min_sdk_version(env["ndk_platform"]) < get_min_target_api():
+ print(
+ "WARNING: minimum supported Android target api is %d. Forcing target api %d."
+ % (get_min_target_api(), get_min_target_api())
+ )
+ env["ndk_platform"] = "android-" + str(get_min_target_api())
+
install_ndk_if_needed(env)
ndk_root = env["ANDROID_NDK_ROOT"]
# Architecture
- if get_min_sdk_version(env["ndk_platform"]) < 21 and env["arch"] in ["x86_64", "arm64"]:
- print(
- 'WARNING: arch="%s" is not supported with "ndk_platform" lower than "android-21". Forcing platform 21.'
- % env["arch"]
- )
- env["ndk_platform"] = "android-21"
-
if env["arch"] == "arm32":
target_triple = "armv7a-linux-androideabi"
elif env["arch"] == "arm64":
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index c3fba625c6..737e25b270 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -245,7 +245,7 @@ static const int EXPORT_FORMAT_AAB = 1;
static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets";
static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets";
-static const int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
+static const int DEFAULT_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
#ifndef ANDROID_ENABLED
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index e1d9dc4cde..f1b4bfd534 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,14 +1,17 @@
ext.versions = [
androidGradlePlugin: '7.2.1',
compileSdk : 32,
- minSdk : 19, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
- targetSdk : 32, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
+ // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
+ minSdk : 21,
+ // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
+ targetSdk : 32,
buildTools : '32.0.0',
kotlinVersion : '1.7.0',
fragmentVersion : '1.3.6',
nexusPublishVersion: '1.1.0',
javaVersion : 11,
- ndkVersion : '23.2.8568313' // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
+ // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
+ ndkVersion : '23.2.8568313'
]
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index 1325063a57..e1534c7685 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -17,4 +17,4 @@ target_include_directories(${PROJECT_NAME}
SYSTEM PUBLIC
${GODOT_ROOT_DIR})
-add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED)
+add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED)
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 5860a4f0ae..5e71d10a3f 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -1834,8 +1834,8 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor
if (p_preset->get("notarization/apple_id_name") != "") {
if (p_preset->get("notarization/apple_id_password") == "") {
err += TTR("Notarization: Apple ID password not specified.") + "\n";
+ valid = false;
}
- valid = false;
}
if (p_preset->get("notarization/api_uuid") != "") {
if (p_preset->get("notarization/api_key") == "") {
diff --git a/platform/web/SCsub b/platform/web/SCsub
index 077024507a..e44e59bfb9 100644
--- a/platform/web/SCsub
+++ b/platform/web/SCsub
@@ -38,15 +38,20 @@ sys_env.AddJSLibraries(
"js/libs/library_godot_webgl2.js",
]
)
+sys_env.AddJSExterns(
+ [
+ "js/libs/library_godot_webgl2.externs.js",
+ ]
+)
if env["javascript_eval"]:
sys_env.AddJSLibraries(["js/libs/library_godot_javascript_singleton.js"])
for lib in sys_env["JS_LIBS"]:
sys_env.Append(LINKFLAGS=["--js-library", lib.abspath])
-for js in env["JS_PRE"]:
+for js in sys_env["JS_PRE"]:
sys_env.Append(LINKFLAGS=["--pre-js", js.abspath])
-for ext in env["JS_EXTERNS"]:
+for ext in sys_env["JS_EXTERNS"]:
sys_env["ENV"]["EMCC_CLOSURE_ARGS"] += " --externs " + ext.abspath
build = []
diff --git a/platform/web/js/libs/library_godot_webgl2.externs.js b/platform/web/js/libs/library_godot_webgl2.externs.js
new file mode 100644
index 0000000000..18d8d0815a
--- /dev/null
+++ b/platform/web/js/libs/library_godot_webgl2.externs.js
@@ -0,0 +1,36 @@
+
+/**
+ * @constructor OVR_multiview2
+ */
+function OVR_multiview2() {}
+
+/**
+ * @type {number}
+ */
+OVR_multiview2.prototype.FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR;
+
+/**
+ * @type {number}
+ */
+OVR_multiview2.prototype.FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR;
+
+/**
+ * @type {number}
+ */
+OVR_multiview2.prototype.MAX_VIEWS_OVR;
+
+/**
+ * @type {number}
+ */
+OVR_multiview2.prototype.FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR;
+
+/**
+ * @param {number} target
+ * @param {number} attachment
+ * @param {WebGLTexture} texture
+ * @param {number} level
+ * @param {number} baseViewIndex
+ * @param {number} numViews
+ * @return {void}
+ */
+OVR_multiview2.prototype.framebufferTextureMultiviewOVR = function(target, attachment, texture, level, baseViewIndex, numViews) {};
diff --git a/platform/web/js/libs/library_godot_webgl2.js b/platform/web/js/libs/library_godot_webgl2.js
index 365f712be7..ba990b3ee0 100644
--- a/platform/web/js/libs/library_godot_webgl2.js
+++ b/platform/web/js/libs/library_godot_webgl2.js
@@ -37,14 +37,15 @@ const GodotWebGL2 = {
godot_webgl2_glFramebufferTextureMultiviewOVR: function (target, attachment, texture, level, base_view_index, num_views) {
const context = GL.currentContext;
if (typeof context.multiviewExt === 'undefined') {
- const ext = context.GLctx.getExtension('OVR_multiview2');
+ const /** OVR_multiview2 */ ext = context.GLctx.getExtension('OVR_multiview2');
if (!ext) {
console.error('Trying to call glFramebufferTextureMultiviewOVR() without the OVR_multiview2 extension');
return;
}
context.multiviewExt = ext;
}
- context.multiviewExt.framebufferTextureMultiviewOVR(target, attachment, GL.textures[texture], level, base_view_index, num_views);
+ const /** OVR_multiview2 */ ext = context.multiviewExt;
+ ext.framebufferTextureMultiviewOVR(target, attachment, GL.textures[texture], level, base_view_index, num_views);
},
};
diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp
index 84bfc48a43..4788a1b813 100644
--- a/scene/2d/node_2d.cpp
+++ b/scene/2d/node_2d.cpp
@@ -328,29 +328,6 @@ void Node2D::set_global_transform(const Transform2D &p_transform) {
}
}
-void Node2D::set_z_index(int p_z) {
- ERR_FAIL_COND(p_z < RS::CANVAS_ITEM_Z_MIN);
- ERR_FAIL_COND(p_z > RS::CANVAS_ITEM_Z_MAX);
- z_index = p_z;
- RS::get_singleton()->canvas_item_set_z_index(get_canvas_item(), z_index);
-}
-
-void Node2D::set_z_as_relative(bool p_enabled) {
- if (z_relative == p_enabled) {
- return;
- }
- z_relative = p_enabled;
- RS::get_singleton()->canvas_item_set_z_as_relative_to_parent(get_canvas_item(), p_enabled);
-}
-
-bool Node2D::is_z_relative() const {
- return z_relative;
-}
-
-int Node2D::get_z_index() const {
- return z_index;
-}
-
Transform2D Node2D::get_relative_transform_to_parent(const Node *p_parent) const {
if (p_parent == this) {
return Transform2D();
@@ -382,15 +359,6 @@ Point2 Node2D::to_global(Point2 p_local) const {
return get_global_transform().xform(p_local);
}
-void Node2D::set_y_sort_enabled(bool p_enabled) {
- y_sort_enabled = p_enabled;
- RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), y_sort_enabled);
-}
-
-bool Node2D::is_y_sort_enabled() const {
- return y_sort_enabled;
-}
-
void Node2D::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_MOVED_IN_PARENT: {
@@ -437,15 +405,6 @@ void Node2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("to_local", "global_point"), &Node2D::to_local);
ClassDB::bind_method(D_METHOD("to_global", "local_point"), &Node2D::to_global);
- ClassDB::bind_method(D_METHOD("set_z_index", "z_index"), &Node2D::set_z_index);
- ClassDB::bind_method(D_METHOD("get_z_index"), &Node2D::get_z_index);
-
- ClassDB::bind_method(D_METHOD("set_z_as_relative", "enable"), &Node2D::set_z_as_relative);
- ClassDB::bind_method(D_METHOD("is_z_relative"), &Node2D::is_z_relative);
-
- ClassDB::bind_method(D_METHOD("set_y_sort_enabled", "enabled"), &Node2D::set_y_sort_enabled);
- ClassDB::bind_method(D_METHOD("is_y_sort_enabled"), &Node2D::is_y_sort_enabled);
-
ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent);
ADD_GROUP("Transform", "");
@@ -460,9 +419,4 @@ void Node2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_scale", "get_global_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_skew", PROPERTY_HINT_NONE, "radians", PROPERTY_USAGE_NONE), "set_global_skew", "get_global_skew");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform");
-
- ADD_GROUP("Ordering", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_index", "get_z_index");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "z_as_relative"), "set_z_as_relative", "is_z_relative");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sort_enabled"), "set_y_sort_enabled", "is_y_sort_enabled");
}
diff --git a/scene/2d/node_2d.h b/scene/2d/node_2d.h
index 04bbdf639d..76707dc422 100644
--- a/scene/2d/node_2d.h
+++ b/scene/2d/node_2d.h
@@ -40,9 +40,6 @@ class Node2D : public CanvasItem {
real_t rotation = 0.0;
Size2 scale = Vector2(1, 1);
real_t skew = 0.0;
- int z_index = 0;
- bool z_relative = true;
- bool y_sort_enabled = false;
Transform2D transform;
@@ -103,21 +100,12 @@ public:
void set_global_skew(const real_t p_radians);
void set_global_scale(const Size2 &p_scale);
- void set_z_index(int p_z);
- int get_z_index() const;
-
void look_at(const Vector2 &p_pos);
real_t get_angle_to(const Vector2 &p_pos) const;
Point2 to_local(Point2 p_global) const;
Point2 to_global(Point2 p_local) const;
- void set_z_as_relative(bool p_enabled);
- bool is_z_relative() const;
-
- virtual void set_y_sort_enabled(bool p_enabled);
- virtual bool is_y_sort_enabled() const;
-
Transform2D get_relative_transform_to_parent(const Node *p_parent) const;
Transform2D get_transform() const override;
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index 015b5b27e3..6200062f60 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -87,36 +87,38 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
double anim_size = (double)anim->get_length();
double step = 0.0;
double prev_time = cur_time;
- int pingponged = 0;
- bool current_backward = signbit(p_time);
+ Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
+ bool node_backward = play_mode == PLAY_MODE_BACKWARD;
if (p_seek) {
step = p_time - cur_time;
cur_time = p_time;
} else {
p_time *= backward ? -1.0 : 1.0;
- if (!(cur_time == anim_size && !current_backward) && !(cur_time == 0 && current_backward)) {
- cur_time = cur_time + p_time;
- step = p_time;
- }
+ cur_time = cur_time + p_time;
+ step = p_time;
}
if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) {
if (!Math::is_zero_approx(anim_size)) {
- if ((int)Math::floor(abs(cur_time - prev_time) / anim_size) % 2 == 0) {
- if (prev_time >= 0 && cur_time < 0) {
- backward = !backward;
- pingponged = -1;
- }
- if (prev_time <= anim_size && cur_time > anim_size) {
- backward = !backward;
- pingponged = 1;
- }
+ if (prev_time >= 0 && cur_time < 0) {
+ backward = !backward;
+ looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
+ }
+ if (prev_time <= anim_size && cur_time > anim_size) {
+ backward = !backward;
+ looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
}
cur_time = Math::pingpong(cur_time, anim_size);
}
} else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) {
if (!Math::is_zero_approx(anim_size)) {
+ if (prev_time >= 0 && cur_time < 0) {
+ looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
+ }
+ if (prev_time <= anim_size && cur_time > anim_size) {
+ looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
+ }
cur_time = Math::fposmod(cur_time, anim_size);
}
backward = false;
@@ -145,9 +147,9 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
}
if (play_mode == PLAY_MODE_FORWARD) {
- blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, pingponged);
+ blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag);
} else {
- blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, pingponged);
+ blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag);
}
set_parameter(time, cur_time);
@@ -309,9 +311,7 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter
set_parameter(time_to_restart, cur_time_to_restart);
}
- if (!cur_active) {
- return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
- }
+ return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
}
bool os_seek = p_seek;
@@ -349,10 +349,9 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter
if (mix == MIX_MODE_ADD) {
main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
} else {
- main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync);
+ main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync); // Unlike below, processing this edge is a corner case.
}
-
- double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, blend, FILTER_PASS, true);
+ double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, MAX(CMP_EPSILON, blend), FILTER_PASS, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (do_start) {
cur_remaining = os_rem;
@@ -769,17 +768,18 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
blend = xfade_curve->sample(blend);
}
+ // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (from_start && !p_seek && switched) { //just switched, seek to start of current
- rem = blend_input(cur_current, 0, true, p_is_external_seeking, 1.0 - blend, FILTER_IGNORE, true);
+ rem = blend_input(cur_current, 0, true, p_is_external_seeking, MAX(CMP_EPSILON, 1.0 - blend), FILTER_IGNORE, true);
} else {
- rem = blend_input(cur_current, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_IGNORE, true);
+ rem = blend_input(cur_current, p_time, p_seek, p_is_external_seeking, MAX(CMP_EPSILON, 1.0 - blend), FILTER_IGNORE, true);
}
if (p_seek) {
- blend_input(cur_prev, p_time, true, p_is_external_seeking, blend, FILTER_IGNORE, true);
+ blend_input(cur_prev, p_time, true, p_is_external_seeking, MAX(CMP_EPSILON, blend), FILTER_IGNORE, true);
cur_time = p_time;
} else {
- blend_input(cur_prev, p_time, false, p_is_external_seeking, blend, FILTER_IGNORE, true);
+ blend_input(cur_prev, p_time, false, p_is_external_seeking, MAX(CMP_EPSILON, blend), FILTER_IGNORE, true);
cur_time += p_time;
cur_prev_xfading -= p_time;
if (cur_prev_xfading < 0) {
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index 360f16de02..d3746c1144 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -433,10 +433,10 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
if (current_curve.is_valid()) {
fade_blend = current_curve->sample(fade_blend);
}
- float rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true);
+ float rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, MAX(CMP_EPSILON, fade_blend), AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (fading_from != StringName()) {
- p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, 1.0 - fade_blend, AnimationNode::FILTER_IGNORE, true);
+ p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, MAX(CMP_EPSILON, 1.0 - fade_blend), AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
}
//guess playback position
@@ -599,13 +599,11 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
current = next;
+ len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, CMP_EPSILON, AnimationNode::FILTER_IGNORE, true); // Process next node's first key in here.
if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
- len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true);
pos_current = MIN(pos_current, len_current);
p_state_machine->blend_node(current, p_state_machine->states[current].node, pos_current, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true);
-
} else {
- len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true);
pos_current = 0;
}
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 45eeff71f2..ee6fb58583 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -468,7 +468,7 @@ Variant AnimationPlayer::_post_process_key_value(const Ref<Animation> &p_anim, i
return p_value;
}
-void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, int p_pingponged) {
+void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_prev_time, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, Animation::LoopedFlag p_looped_flag) {
_ensure_node_caches(p_anim);
ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
@@ -683,7 +683,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
} else if (p_is_current && p_delta != 0) {
List<int> indices;
- a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_pingponged);
+ if (p_started) {
+ int first_key = a->track_find_key(i, p_prev_time, true);
+ if (first_key >= 0) {
+ indices.push_back(first_key);
+ }
+ }
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag);
for (int &F : indices) {
Variant value = a->track_get_key_value(i, F);
@@ -742,7 +748,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
}
List<int> indices;
- a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_pingponged);
+ if (p_started) {
+ int first_key = a->track_find_key(i, p_prev_time, true);
+ if (first_key >= 0) {
+ indices.push_back(first_key);
+ }
+ }
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag);
for (int &E : indices) {
StringName method = a->method_track_get_name(i, E);
@@ -832,7 +844,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged);
+ if (p_started) {
+ int first_key = a->track_find_key(i, p_prev_time, true);
+ if (first_key >= 0) {
+ to_play.push_back(first_key);
+ }
+ }
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -939,7 +957,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged);
+ if (p_started) {
+ int first_key = a->track_find_key(i, p_prev_time, true);
+ if (first_key >= 0) {
+ to_play.push_back(first_key);
+ }
+ }
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -969,7 +993,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta,
double next_pos = cd.pos + delta;
real_t len = cd.from->animation->get_length();
- int pingponged = 0;
+ Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
switch (cd.from->animation->get_loop_mode()) {
case Animation::LOOP_NONE: {
@@ -998,44 +1022,33 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta,
} break;
case Animation::LOOP_LINEAR: {
- double looped_next_pos = Math::fposmod(next_pos, (double)len);
- if (looped_next_pos == 0 && next_pos != 0) {
- // Loop multiples of the length to it, rather than 0
- // so state at time=length is previewable in the editor
- next_pos = len;
- } else {
- next_pos = looped_next_pos;
+ if (next_pos < 0 && cd.pos >= 0) {
+ looped_flag = Animation::LOOPED_FLAG_START;
}
+ if (next_pos > len && cd.pos <= len) {
+ looped_flag = Animation::LOOPED_FLAG_END;
+ }
+ next_pos = Math::fposmod(next_pos, (double)len);
} break;
case Animation::LOOP_PINGPONG: {
- if ((int)Math::floor(abs(next_pos - cd.pos) / len) % 2 == 0) {
- if (next_pos < 0 && cd.pos >= 0) {
- cd.speed_scale *= -1.0;
- pingponged = -1;
- }
- if (next_pos > len && cd.pos <= len) {
- cd.speed_scale *= -1.0;
- pingponged = 1;
- }
+ if (next_pos < 0 && cd.pos >= 0) {
+ cd.speed_scale *= -1.0;
+ looped_flag = Animation::LOOPED_FLAG_START;
}
- double looped_next_pos = Math::pingpong(next_pos, (double)len);
- if (looped_next_pos == 0 && next_pos != 0) {
- // Loop multiples of the length to it, rather than 0
- // so state at time=length is previewable in the editor
- next_pos = len;
- } else {
- next_pos = looped_next_pos;
+ if (next_pos > len && cd.pos <= len) {
+ cd.speed_scale *= -1.0;
+ looped_flag = Animation::LOOPED_FLAG_END;
}
+ next_pos = Math::pingpong(next_pos, (double)len);
} break;
default:
break;
}
+ _animation_process_animation(cd.from, cd.pos, next_pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, looped_flag);
cd.pos = next_pos;
-
- _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, pingponged);
}
void AnimationPlayer::_animation_process2(double p_delta, bool p_started) {
@@ -1273,23 +1286,6 @@ void AnimationPlayer::_animation_set_cache_update() {
}
void AnimationPlayer::_animation_added(const StringName &p_name, const StringName &p_library) {
- {
- int at_pos = -1;
-
- for (uint32_t i = 0; i < animation_libraries.size(); i++) {
- if (animation_libraries[i].name == p_library) {
- at_pos = i;
- break;
- }
- }
-
- ERR_FAIL_COND(at_pos == -1);
-
- ERR_FAIL_COND(!animation_libraries[at_pos].library->animations.has(p_name));
-
- _ref_anim(animation_libraries[at_pos].library->animations[p_name]);
- }
-
_animation_set_cache_update();
}
@@ -1300,11 +1296,6 @@ void AnimationPlayer::_animation_removed(const StringName &p_name, const StringN
return; // No need to update because not the one from the library being used.
}
- AnimationData animation_data = animation_set[name];
- if (animation_data.animation_library == p_library) {
- _unref_anim(animation_data.animation);
- }
-
_animation_set_cache_update();
// Erase blends if needed
@@ -1400,10 +1391,7 @@ Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref
ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_name));
ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed).bind(p_name));
ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed).bind(p_name));
-
- for (const KeyValue<StringName, Ref<Animation>> &K : ald.library->animations) {
- _ref_anim(K.value);
- }
+ ald.library->connect(SNAME("animation_changed"), callable_mp(this, &AnimationPlayer::_animation_changed));
_animation_set_cache_update();
@@ -1427,27 +1415,16 @@ void AnimationPlayer::remove_animation_library(const StringName &p_name) {
animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added));
animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed));
animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed));
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_changed"), callable_mp(this, &AnimationPlayer::_animation_changed));
stop();
- for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[at_pos].library->animations) {
- _unref_anim(K.value);
- }
-
animation_libraries.remove_at(at_pos);
_animation_set_cache_update();
notify_property_list_changed();
}
-void AnimationPlayer::_ref_anim(const Ref<Animation> &p_anim) {
- Ref<Animation>(p_anim)->connect("changed", callable_mp(this, &AnimationPlayer::_animation_changed), CONNECT_REFERENCE_COUNTED);
-}
-
-void AnimationPlayer::_unref_anim(const Ref<Animation> &p_anim) {
- Ref<Animation>(p_anim)->disconnect("changed", callable_mp(this, &AnimationPlayer::_animation_changed));
-}
-
void AnimationPlayer::rename_animation_library(const StringName &p_name, const StringName &p_new_name) {
if (p_name == p_new_name) {
return;
@@ -1798,9 +1775,8 @@ double AnimationPlayer::get_current_animation_length() const {
return playback.current.from->animation->get_length();
}
-void AnimationPlayer::_animation_changed() {
+void AnimationPlayer::_animation_changed(const StringName &p_name) {
clear_caches();
- emit_signal(SNAME("caches_cleared"));
if (is_playing()) {
playback.seeked = true; //need to restart stuff, like audio
}
@@ -1839,6 +1815,8 @@ void AnimationPlayer::clear_caches() {
cache_update_size = 0;
cache_update_prop_size = 0;
cache_update_bezier_size = 0;
+
+ emit_signal(SNAME("caches_cleared"));
}
void AnimationPlayer::set_active(bool p_active) {
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 4f32927d25..0b95ee4e9e 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -268,7 +268,7 @@ private:
NodePath root;
- void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, int p_pingponged = 0);
+ void _animation_process_animation(AnimationData *p_anim, double p_prev_time, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = nullptr);
void _animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started);
@@ -291,9 +291,7 @@ private:
return ret;
}
- void _animation_changed();
- void _ref_anim(const Ref<Animation> &p_anim);
- void _unref_anim(const Ref<Animation> &p_anim);
+ void _animation_changed(const StringName &p_name);
void _set_process(bool p_process, bool p_force = false);
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 50165773d0..16621faa33 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -86,7 +86,7 @@ void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
}
}
-void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, int p_pingponged) {
+void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag) {
ERR_FAIL_COND(!state);
ERR_FAIL_COND(!state->player->has_animation(p_animation));
@@ -112,7 +112,7 @@ void AnimationNode::blend_animation(const StringName &p_animation, double p_time
anim_state.time = p_time;
anim_state.animation = animation;
anim_state.seeked = p_seeked;
- anim_state.pingponged = p_pingponged;
+ anim_state.looped_flag = p_looped_flag;
anim_state.is_external_seeking = p_is_external_seeking;
state->animation_states.push_back(anim_state);
@@ -413,7 +413,7 @@ void AnimationNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters);
ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters);
- ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "pingponged"), &AnimationNode::blend_animation, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation, DEFVAL(Animation::LOOPED_FLAG_NONE));
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
@@ -807,10 +807,10 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
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 (was_discrete != track_value->is_discrete) {
- WARN_PRINT_ONCE("Tracks with different update modes are blended. Blending prioritizes Discrete/Trigger mode, so other update mode tracks will not be blended.");
+ ERR_PRINT_ED("Value track: " + String(path) + " with different update modes are blended. Blending prioritizes Discrete/Trigger mode, so other update mode tracks will not be blended.");
}
if (was_using_angle != track_value->is_using_angle) {
- WARN_PRINT_ONCE("Tracks for rotation with different interpolation types are blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
+ WARN_PRINT_ED("Value track: " + String(path) + " with different interpolation types for rotation are blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
}
}
@@ -859,7 +859,6 @@ void AnimationTree::_clear_caches() {
memdelete(K.value);
}
playing_caches.clear();
-
track_cache.clear();
cache_valid = false;
}
@@ -1019,7 +1018,7 @@ void AnimationTree::_process_graph(double p_delta) {
double delta = as.delta;
real_t weight = as.blend;
bool seeked = as.seeked;
- int pingponged = as.pingponged;
+ Animation::LoopedFlag looped_flag = as.looped_flag;
bool is_external_seeking = as.is_external_seeking;
#ifndef _3D_DISABLED
bool backward = signbit(delta); // This flag is required only for the root motion since it calculates the difference between the previous and current frames.
@@ -1391,7 +1390,7 @@ void AnimationTree::_process_graph(double p_delta) {
t->object->set_indexed(t->subpath, value);
} else {
List<int> indices;
- a->track_get_key_indices_in_range(i, time, delta, &indices, pingponged);
+ a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag);
for (int &F : indices) {
Variant value = a->track_get_key_value(i, F);
value = _post_process_key_value(a, i, value, t->object);
@@ -1416,7 +1415,7 @@ void AnimationTree::_process_graph(double p_delta) {
}
} else {
List<int> indices;
- a->track_get_key_indices_in_range(i, time, delta, &indices, pingponged);
+ a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag);
for (int &F : indices) {
StringName method = a->method_track_get_name(i, F);
Vector<Variant> params = a->method_track_get_params(i, F);
@@ -1479,7 +1478,7 @@ void AnimationTree::_process_graph(double p_delta) {
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
+ a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -1594,7 +1593,7 @@ void AnimationTree::_process_graph(double p_delta) {
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
+ a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -1630,7 +1629,7 @@ void AnimationTree::_process_graph(double p_delta) {
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (t->root_motion) {
- root_motion_position = root_motion_rotation.xform_inv(t->loc);
+ root_motion_position = t->loc;
root_motion_rotation = t->rot;
root_motion_scale = t->scale - Vector3(1, 1, 1);
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index a4b0f992dc..be0dc1af4e 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -69,7 +69,7 @@ public:
real_t blend = 0.0;
bool seeked = false;
bool is_external_seeking = false;
- int pingponged = 0;
+ Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
};
struct State {
@@ -102,7 +102,7 @@ public:
double _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr);
protected:
- void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, int p_pingponged = 0);
+ void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
double blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true);
double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 92ee21a916..e90a6a69ab 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -220,6 +220,10 @@ PackedStringArray Control::get_configuration_warnings() const {
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\"."));
}
+ if (get_z_index() != 0) {
+ warnings.push_back(RTR("Changing the Z index of a control only affects the drawing order, not the input event handling order."));
+ }
+
return warnings;
}
@@ -481,10 +485,10 @@ void Control::_validate_property(PropertyInfo &p_property) const {
}
} else if (Object::cast_to<Container>(parent_node)) {
// If the parent is a container, display only container-related properties.
- if (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_") || p_property.name == "anchors_preset" ||
- p_property.name == "position" || p_property.name == "rotation" || p_property.name == "scale" || p_property.name == "size" || p_property.name == "pivot_offset") {
- p_property.usage ^= PROPERTY_USAGE_EDITOR;
-
+ if (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_") || p_property.name == "anchors_preset") {
+ p_property.usage ^= PROPERTY_USAGE_DEFAULT;
+ } else if (p_property.name == "position" || p_property.name == "rotation" || p_property.name == "scale" || p_property.name == "size" || p_property.name == "pivot_offset") {
+ p_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY;
} else if (p_property.name == "layout_mode") {
// Set the layout mode to be disabled with the proper value.
p_property.hint_string = "Position,Anchors,Container,Uncontrolled";
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 27002fad38..e0e4ead55f 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -64,11 +64,6 @@ void Range::_changed_notify(const char *p_what) {
queue_redraw();
}
-void Range::_validate_values() {
- shared->max = MAX(shared->max, shared->min);
- shared->page = CLAMP(shared->page, 0, shared->max - shared->min);
-}
-
void Range::Shared::emit_changed(const char *p_what) {
for (Range *E : owners) {
Range *r = E;
@@ -118,8 +113,9 @@ void Range::set_min(double p_min) {
}
shared->min = p_min;
+ shared->max = MAX(shared->max, shared->min);
+ shared->page = CLAMP(shared->page, 0, shared->max - shared->min);
set_value(shared->val);
- _validate_values();
shared->emit_changed("min");
@@ -127,13 +123,14 @@ void Range::set_min(double p_min) {
}
void Range::set_max(double p_max) {
- if (shared->max == p_max) {
+ double max_validated = MAX(p_max, shared->min);
+ if (shared->max == max_validated) {
return;
}
- shared->max = p_max;
+ shared->max = max_validated;
+ shared->page = CLAMP(shared->page, 0, shared->max - shared->min);
set_value(shared->val);
- _validate_values();
shared->emit_changed("max");
}
@@ -148,13 +145,13 @@ void Range::set_step(double p_step) {
}
void Range::set_page(double p_page) {
- if (shared->page == p_page) {
+ double page_validated = CLAMP(p_page, 0, shared->max - shared->min);
+ if (shared->page == page_validated) {
return;
}
- shared->page = p_page;
+ shared->page = page_validated;
set_value(shared->val);
- _validate_values();
shared->emit_changed("page");
}
diff --git a/scene/gui/range.h b/scene/gui/range.h
index f804155dec..5267216f12 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -59,7 +59,6 @@ class Range : public Control {
void _value_changed_notify();
void _changed_notify(const char *p_what = "");
- void _validate_values();
protected:
virtual void _value_changed(double p_value);
diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp
index 0dece1c287..20d82095a1 100644
--- a/scene/gui/rich_text_effect.cpp
+++ b/scene/gui/rich_text_effect.cpp
@@ -88,6 +88,9 @@ void CharFXTransform::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_glyph_index"), &CharFXTransform::get_glyph_index);
ClassDB::bind_method(D_METHOD("set_glyph_index", "glyph_index"), &CharFXTransform::set_glyph_index);
+ ClassDB::bind_method(D_METHOD("get_relative_index"), &CharFXTransform::get_relative_index);
+ ClassDB::bind_method(D_METHOD("set_relative_index", "relative_index"), &CharFXTransform::set_relative_index);
+
ClassDB::bind_method(D_METHOD("get_glyph_count"), &CharFXTransform::get_glyph_count);
ClassDB::bind_method(D_METHOD("set_glyph_count", "glyph_count"), &CharFXTransform::set_glyph_count);
@@ -107,5 +110,6 @@ void CharFXTransform::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index");
ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_count"), "set_glyph_count", "get_glyph_count");
ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_flags"), "set_glyph_flags", "get_glyph_flags");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "relative_index"), "set_relative_index", "get_relative_index");
ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font");
}
diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h
index 4532a812ee..66b8a21760 100644
--- a/scene/gui/rich_text_effect.h
+++ b/scene/gui/rich_text_effect.h
@@ -52,6 +52,7 @@ public:
uint32_t glyph_index = 0;
uint16_t glyph_flags = 0;
uint8_t glyph_count = 0;
+ int32_t relative_index = 0;
RID font;
CharFXTransform();
@@ -84,6 +85,9 @@ public:
uint8_t get_glyph_count() const { return glyph_count; };
void set_glyph_count(uint8_t p_glyph_count) { glyph_count = p_glyph_count; };
+ int32_t get_relative_index() const { return relative_index; };
+ void set_relative_index(int32_t p_relative_index) { relative_index = p_relative_index; };
+
RID get_font() const { return font; };
void set_font(RID p_font) { font = p_font; };
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 642a94b23e..df41863e74 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -890,7 +890,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Color odd_row_bg = theme_cache.table_odd_row_bg;
Color even_row_bg = theme_cache.table_even_row_bg;
Color border = theme_cache.table_border;
- int hseparation = theme_cache.table_h_separation;
+ int h_separation = theme_cache.table_h_separation;
int col_count = table->columns.size();
int row_count = table->rows.size();
@@ -908,11 +908,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
coff.x = rect.size.width - table->columns[col].width - coff.x;
}
if (row % 2 == 0) {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg), true);
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg), true);
} else {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg), true);
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg), true);
}
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false);
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false);
}
for (int j = 0; j < (int)frame->lines.size(); j++) {
@@ -1005,6 +1005,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (!custom_effect.is_null()) {
charfx->elapsed_time = item_custom->elapsed_time;
charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end);
+ charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs;
charfx->visibility = txt_visible;
charfx->outline = true;
charfx->font = frid;
@@ -1222,6 +1223,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (!custom_effect.is_null()) {
charfx->elapsed_time = item_custom->elapsed_time;
charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end);
+ charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs;
charfx->visibility = txt_visible;
charfx->outline = false;
charfx->font = frid;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 2da76883b4..93c910a7f0 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1337,14 +1337,14 @@ Size2 TreeItem::get_minimum_size(int p_column) {
// Icon.
if (cell.mode == CELL_MODE_CHECK) {
- size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.hseparation;
+ size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.h_separation;
}
if (cell.icon.is_valid()) {
Size2i icon_size = cell.get_icon_size();
if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) {
icon_size.width = cell.icon_max_w;
}
- size.width += icon_size.width + parent_tree->theme_cache.hseparation;
+ size.width += icon_size.width + parent_tree->theme_cache.h_separation;
size.height = MAX(size.height, icon_size.height);
}
@@ -1624,8 +1624,8 @@ void Tree::_update_theme_item_cache() {
theme_cache.font_color = get_theme_color(SNAME("font_color"));
theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color"));
theme_cache.drop_position_color = get_theme_color(SNAME("drop_position_color"));
- theme_cache.hseparation = get_theme_constant(SNAME("h_separation"));
- theme_cache.vseparation = get_theme_constant(SNAME("v_separation"));
+ theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
+ theme_cache.v_separation = get_theme_constant(SNAME("v_separation"));
theme_cache.item_margin = get_theme_constant(SNAME("item_margin"));
theme_cache.button_margin = get_theme_constant(SNAME("button_margin"));
@@ -1710,7 +1710,7 @@ int Tree::compute_item_height(TreeItem *p_item) const {
height = item_min_height;
}
- height += theme_cache.vseparation;
+ height += theme_cache.v_separation;
return height;
}
@@ -1720,7 +1720,7 @@ int Tree::get_item_height(TreeItem *p_item) const {
return 0;
}
int height = compute_item_height(p_item);
- height += theme_cache.vseparation;
+ height += theme_cache.v_separation;
if (!p_item->collapsed) { /* if not collapsed, check the children */
@@ -1749,7 +1749,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) {
bmsize.width = p_cell.icon_max_w;
}
- w += bmsize.width + theme_cache.hseparation;
+ w += bmsize.width + theme_cache.h_separation;
if (rect.size.width > 0 && (w + ts.width) > rect.size.width) {
ts.width = rect.size.width - w;
}
@@ -1783,8 +1783,8 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color);
}
p_cell.text_buf->draw(ci, draw_pos, p_color);
- rect.position.x += ts.width + theme_cache.hseparation;
- rect.size.x -= ts.width + theme_cache.hseparation;
+ rect.position.x += ts.width + theme_cache.h_separation;
+ rect.size.x -= ts.width + theme_cache.h_separation;
}
if (!p_cell.icon.is_null()) {
@@ -1796,8 +1796,8 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
}
p_cell.draw_icon(ci, rect.position + Size2i(0, Math::floor((real_t)(rect.size.y - bmsize.y) / 2)), bmsize, p_icon_color);
- rect.position.x += bmsize.x + theme_cache.hseparation;
- rect.size.x -= bmsize.x + theme_cache.hseparation;
+ rect.position.x += bmsize.x + theme_cache.h_separation;
+ rect.size.x -= bmsize.x + theme_cache.h_separation;
}
if (!rtl) {
@@ -1911,7 +1911,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
bool rtl = cache.rtl;
/* Calculate height of the label part */
- label_h += theme_cache.vseparation;
+ label_h += theme_cache.v_separation;
/* Draw label, if height fits */
@@ -1922,7 +1922,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
ERR_FAIL_COND_V(theme_cache.font.is_null(), -1);
- int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.hseparation : theme_cache.item_margin);
+ int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin);
int skip2 = 0;
for (int i = 0; i < columns.size(); i++) {
if (skip2) {
@@ -1940,8 +1940,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
continue;
}
} else {
- ofs += theme_cache.hseparation;
- w -= theme_cache.hseparation;
+ ofs += theme_cache.h_separation;
+ w -= theme_cache.h_separation;
}
if (p_item->cells[i].expand_right) {
@@ -1998,8 +1998,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
Rect2i item_rect = Rect2i(Point2i(ofs, p_pos.y) - theme_cache.offset + p_draw_ofs, Size2i(w, label_h));
Rect2i cell_rect = item_rect;
if (i != 0) {
- cell_rect.position.x -= theme_cache.hseparation;
- cell_rect.size.x += theme_cache.hseparation;
+ cell_rect.position.x -= theme_cache.h_separation;
+ cell_rect.size.x += theme_cache.h_separation;
}
if (theme_cache.draw_guides) {
@@ -2051,8 +2051,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
r.position.x = p_draw_ofs.x;
r.size.x = w + ofs;
} else {
- r.position.x -= theme_cache.hseparation;
- r.size.x += theme_cache.hseparation;
+ r.position.x -= theme_cache.h_separation;
+ r.size.x += theme_cache.h_separation;
}
if (rtl) {
r.position.x = get_size().width - r.position.x - r.size.x;
@@ -2136,7 +2136,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
unchecked->draw(ci, check_ofs);
}
- int check_w = checked->get_width() + theme_cache.hseparation;
+ int check_w = checked->get_width() + theme_cache.h_separation;
text_pos.x += check_w;
@@ -2328,7 +2328,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
// Draw relationship lines.
if (theme_cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) {
- int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.hseparation : theme_cache.item_margin);
+ int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin);
int parent_ofs = p_pos.x + theme_cache.item_margin;
Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - theme_cache.offset + p_draw_ofs;
@@ -2615,7 +2615,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
return 0;
}
- int item_h = compute_item_height(p_item) + theme_cache.vseparation;
+ int item_h = compute_item_height(p_item) + theme_cache.v_separation;
bool skip = (p_item == root && hide_root);
@@ -2649,7 +2649,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
if (p_item->cells[i].expand_right) {
int plus = 1;
while (i + plus < columns.size() && !p_item->cells[i + plus].editable && p_item->cells[i + plus].mode == TreeItem::CELL_MODE_STRING && p_item->cells[i + plus].text.is_empty() && p_item->cells[i + plus].icon.is_null()) {
- col_width += theme_cache.hseparation;
+ col_width += theme_cache.h_separation;
col_width += get_column_width(i + plus);
plus++;
}
@@ -2669,16 +2669,16 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
if (col == -1) {
return -1;
} else if (col == 0) {
- int margin = x_ofs + theme_cache.item_margin; //-theme_cache.hseparation;
+ int margin = x_ofs + theme_cache.item_margin; //-theme_cache.h_separation;
//int lm = theme_cache.panel_style->get_margin(SIDE_LEFT);
col_width -= margin;
limit_w -= margin;
col_ofs += margin;
x -= margin;
} else {
- col_width -= theme_cache.hseparation;
- limit_w -= theme_cache.hseparation;
- x -= theme_cache.hseparation;
+ col_width -= theme_cache.h_separation;
+ limit_w -= theme_cache.h_separation;
+ x -= theme_cache.h_separation;
}
if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) {
@@ -4426,7 +4426,7 @@ int Tree::get_column_minimum_width(int p_column) const {
if (p_column == 0) {
item_size.width += theme_cache.item_margin * depth;
} else {
- item_size.width += theme_cache.hseparation;
+ item_size.width += theme_cache.h_separation;
}
// Check if the item is wider.
@@ -4522,7 +4522,7 @@ int Tree::get_item_offset(TreeItem *p_item) const {
ofs += compute_item_height(it);
if (it != root || !hide_root) {
- ofs += theme_cache.vseparation;
+ ofs += theme_cache.v_separation;
}
if (it->first_child && !it->collapsed) {
@@ -4561,7 +4561,7 @@ void Tree::ensure_cursor_is_visible() {
const int tbh = _get_title_button_height();
y_offset -= tbh;
- const int cell_h = compute_item_height(selected_item) + theme_cache.vseparation;
+ const int cell_h = compute_item_height(selected_item) + theme_cache.v_separation;
int screen_h = area_size.height - tbh;
if (h_scroll->is_visible()) {
screen_h -= h_scroll->get_combined_minimum_size().height;
@@ -4727,7 +4727,7 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) {
const int tbh = _get_title_button_height();
y_offset -= tbh;
- const int cell_h = compute_item_height(p_item) + theme_cache.vseparation;
+ const int cell_h = compute_item_height(p_item) + theme_cache.v_separation;
int screen_h = area_size.height - tbh;
if (h_scroll->is_visible()) {
screen_h -= h_scroll->get_combined_minimum_size().height;
@@ -4855,7 +4855,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
Point2 pos = p_pos;
if ((root != p_item || !hide_root) && p_item->is_visible()) {
- h = compute_item_height(p_item) + theme_cache.vseparation;
+ h = compute_item_height(p_item) + theme_cache.v_separation;
if (pos.y < h) {
if (drop_mode_flags == DROP_MODE_ON_ITEM) {
section = 0;
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 77a62e1d6a..cdd90fe4c7 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -531,8 +531,8 @@ private:
float base_scale = 1.0;
- int hseparation = 0;
- int vseparation = 0;
+ int h_separation = 0;
+ int v_separation = 0;
int item_margin = 0;
int button_margin = 0;
Point2 offset;
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 7bcd4721fc..2563fa5914 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -450,6 +450,39 @@ void CanvasItem::item_rect_changed(bool p_size_changed) {
emit_signal(SceneStringNames::get_singleton()->item_rect_changed);
}
+void CanvasItem::set_z_index(int p_z) {
+ ERR_FAIL_COND(p_z < RS::CANVAS_ITEM_Z_MIN);
+ ERR_FAIL_COND(p_z > RS::CANVAS_ITEM_Z_MAX);
+ z_index = p_z;
+ RS::get_singleton()->canvas_item_set_z_index(canvas_item, z_index);
+ update_configuration_warnings();
+}
+
+void CanvasItem::set_z_as_relative(bool p_enabled) {
+ if (z_relative == p_enabled) {
+ return;
+ }
+ z_relative = p_enabled;
+ RS::get_singleton()->canvas_item_set_z_as_relative_to_parent(canvas_item, p_enabled);
+}
+
+bool CanvasItem::is_z_relative() const {
+ return z_relative;
+}
+
+int CanvasItem::get_z_index() const {
+ return z_index;
+}
+
+void CanvasItem::set_y_sort_enabled(bool p_enabled) {
+ y_sort_enabled = p_enabled;
+ RS::get_singleton()->canvas_item_set_sort_children_by_y(canvas_item, y_sort_enabled);
+}
+
+bool CanvasItem::is_y_sort_enabled() const {
+ return y_sort_enabled;
+}
+
void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash) {
ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");
@@ -913,9 +946,19 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &CanvasItem::set_modulate);
ClassDB::bind_method(D_METHOD("get_modulate"), &CanvasItem::get_modulate);
+
ClassDB::bind_method(D_METHOD("set_self_modulate", "self_modulate"), &CanvasItem::set_self_modulate);
ClassDB::bind_method(D_METHOD("get_self_modulate"), &CanvasItem::get_self_modulate);
+ ClassDB::bind_method(D_METHOD("set_z_index", "z_index"), &Node2D::set_z_index);
+ ClassDB::bind_method(D_METHOD("get_z_index"), &Node2D::get_z_index);
+
+ ClassDB::bind_method(D_METHOD("set_z_as_relative", "enable"), &Node2D::set_z_as_relative);
+ ClassDB::bind_method(D_METHOD("is_z_relative"), &Node2D::is_z_relative);
+
+ ClassDB::bind_method(D_METHOD("set_y_sort_enabled", "enabled"), &Node2D::set_y_sort_enabled);
+ ClassDB::bind_method(D_METHOD("is_y_sort_enabled"), &Node2D::is_y_sort_enabled);
+
ClassDB::bind_method(D_METHOD("set_draw_behind_parent", "enable"), &CanvasItem::set_draw_behind_parent);
ClassDB::bind_method(D_METHOD("is_draw_behind_parent_enabled"), &CanvasItem::is_draw_behind_parent_enabled);
@@ -1005,6 +1048,11 @@ void CanvasItem::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_light_mask", "get_light_mask");
ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_layer", PROPERTY_HINT_LAYERS_2D_RENDER), "set_visibility_layer", "get_visibility_layer");
+ ADD_GROUP("Ordering", "");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_index", "get_z_index");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "z_as_relative"), "set_z_as_relative", "is_z_relative");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sort_enabled"), "set_y_sort_enabled", "is_y_sort_enabled");
+
ADD_GROUP("Texture", "texture_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Inherit,Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_repeat", PROPERTY_HINT_ENUM, "Inherit,Disabled,Enabled,Mirror"), "set_texture_repeat", "get_texture_repeat");
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 4e78a175dc..4ace982825 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -91,6 +91,10 @@ private:
int light_mask = 1;
uint32_t visibility_layer = 1;
+ int z_index = 0;
+ bool z_relative = true;
+ bool y_sort_enabled = false;
+
Window *window = nullptr;
bool visible = true;
bool parent_visible_in_tree = false;
@@ -230,6 +234,17 @@ public:
void set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable);
bool get_visibility_layer_bit(uint32_t p_visibility_layer) const;
+ /* ORDERING */
+
+ void set_z_index(int p_z);
+ int get_z_index() const;
+
+ void set_z_as_relative(bool p_enabled);
+ bool is_z_relative() const;
+
+ virtual void set_y_sort_enabled(bool p_enabled);
+ virtual bool is_y_sort_enabled() const;
+
/* DRAWING API */
void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = 1.0, real_t p_dash = 2.0);
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 7430a0a835..fdbcb20d30 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1880,9 +1880,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
Ref<InputEventScreenTouch> touch_event = p_event;
if (touch_event.is_valid()) {
Size2 pos = touch_event->get_position();
+ const int touch_index = touch_event->get_index();
if (touch_event->is_pressed()) {
Control *over = gui_find_control(pos);
if (over) {
+ gui.touch_focus[touch_index] = over->get_instance_id();
bool stopped = false;
if (over->can_process()) {
touch_event = touch_event->xformed_by(Transform2D()); // Make a copy.
@@ -1899,17 +1901,25 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
}
return;
}
- } else if (touch_event->get_index() == 0 && gui.last_mouse_focus) {
+ } else {
bool stopped = false;
- if (gui.last_mouse_focus->can_process()) {
+ ObjectID control_id = gui.touch_focus[touch_index];
+ Control *over = control_id.is_valid() ? Object::cast_to<Control>(ObjectDB::get_instance(control_id)) : nullptr;
+ if (over && over->can_process()) {
touch_event = touch_event->xformed_by(Transform2D()); // Make a copy.
- touch_event->set_position(gui.focus_inv_xform.xform(pos));
+ if (over == gui.last_mouse_focus) {
+ pos = gui.focus_inv_xform.xform(pos);
+ } else {
+ pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos);
+ }
+ touch_event->set_position(pos);
- stopped = _gui_call_input(gui.last_mouse_focus, touch_event);
+ stopped = _gui_call_input(over, touch_event);
}
if (stopped) {
set_input_as_handled();
}
+ gui.touch_focus.erase(touch_index);
return;
}
}
@@ -1944,7 +1954,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
Ref<InputEventScreenDrag> drag_event = p_event;
if (drag_event.is_valid()) {
- Control *over = gui.mouse_focus;
+ const int drag_event_index = drag_event->get_index();
+ ObjectID control_id = gui.touch_focus[drag_event_index];
+ Control *over = control_id.is_valid() ? Object::cast_to<Control>(ObjectDB::get_instance(control_id)) : nullptr;
if (!over) {
over = gui_find_control(drag_event->get_position());
}
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 5659ee4000..bc8cd54603 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -354,6 +354,7 @@ private:
bool forced_mouse_focus = false; //used for menu buttons
bool mouse_in_viewport = true;
bool key_event_accepted = false;
+ HashMap<int, ObjectID> touch_focus;
Control *mouse_focus = nullptr;
Control *last_mouse_focus = nullptr;
Control *mouse_click_grabber = nullptr;
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index ed9a709382..3967858d47 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -2726,40 +2726,60 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const
}
template <class T>
-void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const {
- if (to_time == length) {
- to_time = length + CMP_EPSILON; //include a little more if at the end
+void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const {
+ int len = p_array.size();
+ if (len == 0) {
+ return;
}
- int to = _find(p_array, to_time);
-
- // can't really send the events == time, will be sent in the next frame.
- // if event>=len then it will probably never be requested by the anim player.
-
- if (to >= 0 && p_array[to].time >= to_time) {
- to--;
- }
+ int from = 0;
+ int to = len - 1;
- if (to < 0) {
- return; // not bother
+ if (!p_is_backward) {
+ while (p_array[from].time < from_time || Math::is_equal_approx(p_array[from].time, from_time)) {
+ from++;
+ if (to < from) {
+ return;
+ }
+ }
+ while (p_array[to].time > to_time && !Math::is_equal_approx(p_array[to].time, to_time)) {
+ to--;
+ if (to < from) {
+ return;
+ }
+ }
+ } else {
+ while (p_array[from].time < from_time && !Math::is_equal_approx(p_array[from].time, from_time)) {
+ from++;
+ if (to < from) {
+ return;
+ }
+ }
+ while (p_array[to].time > to_time || Math::is_equal_approx(p_array[to].time, to_time)) {
+ to--;
+ if (to < from) {
+ return;
+ }
+ }
}
- int from = _find(p_array, from_time);
-
- // position in the right first event.+
- if (from < 0 || p_array[from].time < from_time) {
- from++;
+ if (from == to) {
+ p_indices->push_back(from);
+ return;
}
- int max = p_array.size();
-
- for (int i = from; i <= to; i++) {
- ERR_CONTINUE(i < 0 || i >= max); // shouldn't happen
- p_indices->push_back(i);
+ if (!p_is_backward) {
+ for (int i = from; i <= to; i++) {
+ p_indices->push_back(i);
+ }
+ } else {
+ for (int i = to; i >= to; i--) {
+ p_indices->push_back(i);
+ }
}
}
-void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const {
+void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, Animation::LoopedFlag p_looped_flag) const {
ERR_FAIL_INDEX(p_track, tracks.size());
if (p_delta == 0) {
@@ -2771,7 +2791,9 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
double from_time = p_time - p_delta;
double to_time = p_time;
+ bool is_backward = false;
if (from_time > to_time) {
+ is_backward = true;
SWAP(from_time, to_time);
}
@@ -2800,7 +2822,10 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
}
if (from_time > to_time) {
- // handle loop by splitting
+ // Handle loop by splitting.
+ double anim_end = length + CMP_EPSILON;
+ double anim_start = -CMP_EPSILON;
+
switch (t->type) {
case TYPE_POSITION_3D: {
const PositionTrack *tt = static_cast<const PositionTrack *>(t);
@@ -2808,8 +2833,13 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
_get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
_get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices);
} else {
- _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
- _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(tt->positions, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(tt->positions, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(tt->positions, from_time, anim_end, p_indices, is_backward);
+ }
}
} break;
case TYPE_ROTATION_3D: {
@@ -2818,8 +2848,13 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
_get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
_get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices);
} else {
- _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
- _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(rt->rotations, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(rt->rotations, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(rt->rotations, from_time, anim_end, p_indices, is_backward);
+ }
}
} break;
case TYPE_SCALE_3D: {
@@ -2828,8 +2863,13 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
_get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
_get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices);
} else {
- _track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
- _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(st->scales, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(st->scales, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(st->scales, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(st->scales, from_time, anim_end, p_indices, is_backward);
+ }
}
} break;
case TYPE_BLEND_SHAPE: {
@@ -2838,38 +2878,83 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
_get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
_get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices);
} else {
- _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
- _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, p_indices, is_backward);
+ }
}
} break;
case TYPE_VALUE: {
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
- _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(vt->values, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(vt->values, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(vt->values, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(vt->values, from_time, anim_end, p_indices, is_backward);
+ }
} break;
case TYPE_METHOD: {
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
- _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
- _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(mt->methods, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(mt->methods, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(mt->methods, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(mt->methods, from_time, anim_end, p_indices, is_backward);
+ }
} break;
case TYPE_BEZIER: {
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
- _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(bz->values, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(bz->values, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(bz->values, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(bz->values, from_time, anim_end, p_indices, is_backward);
+ }
} break;
case TYPE_AUDIO: {
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
- _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(ad->values, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(ad->values, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(ad->values, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(ad->values, from_time, anim_end, p_indices, is_backward);
+ }
} break;
case TYPE_ANIMATION: {
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
- _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
+ if (!is_backward) {
+ _track_get_key_indices_in_range(an->values, from_time, anim_end, p_indices, is_backward);
+ _track_get_key_indices_in_range(an->values, anim_start, to_time, p_indices, is_backward);
+ } else {
+ _track_get_key_indices_in_range(an->values, anim_start, to_time, p_indices, is_backward);
+ _track_get_key_indices_in_range(an->values, from_time, anim_end, p_indices, is_backward);
+ }
} break;
}
return;
}
+
+ // Not from_time > to_time but most recent of looping...
+ if (p_looped_flag != Animation::LOOPED_FLAG_NONE) {
+ if (!is_backward && Math::is_equal_approx(from_time, 0)) {
+ int edge = track_find_key(p_track, 0, true);
+ if (edge >= 0) {
+ p_indices->push_back(edge);
+ }
+ } else if (is_backward && Math::is_equal_approx(to_time, length)) {
+ int edge = track_find_key(p_track, length, true);
+ if (edge >= 0) {
+ p_indices->push_back(edge);
+ }
+ }
+ }
} break;
case LOOP_PINGPONG: {
if (from_time > length || from_time < 0) {
@@ -2879,162 +2964,164 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
to_time = Math::pingpong(to_time, length);
}
- if ((int)Math::floor(abs(p_delta) / length) % 2 == 0) {
- if (p_pingponged == -1) {
- // handle loop by splitting
- to_time = MAX(CMP_EPSILON, to_time); // To avoid overlapping keys at the turnaround point, one of the point will needs to be shifted slightly.
- switch (t->type) {
- case TYPE_POSITION_3D: {
- const PositionTrack *tt = static_cast<const PositionTrack *>(t);
- if (tt->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices);
- _get_compressed_key_indices_in_range<3>(tt->compressed_track, CMP_EPSILON, to_time, p_indices);
- } else {
- _track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices);
- _track_get_key_indices_in_range(tt->positions, CMP_EPSILON, to_time, p_indices);
- }
- } break;
- case TYPE_ROTATION_3D: {
- const RotationTrack *rt = static_cast<const RotationTrack *>(t);
- if (rt->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices);
- _get_compressed_key_indices_in_range<3>(rt->compressed_track, CMP_EPSILON, to_time, p_indices);
- } else {
- _track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices);
- _track_get_key_indices_in_range(rt->rotations, CMP_EPSILON, to_time, p_indices);
- }
- } break;
- case TYPE_SCALE_3D: {
- const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
- if (st->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, p_indices);
- _get_compressed_key_indices_in_range<3>(st->compressed_track, CMP_EPSILON, to_time, p_indices);
- } else {
- _track_get_key_indices_in_range(st->scales, 0, from_time, p_indices);
- _track_get_key_indices_in_range(st->scales, CMP_EPSILON, to_time, p_indices);
- }
- } break;
- case TYPE_BLEND_SHAPE: {
- const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
- if (bst->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, p_indices);
- _get_compressed_key_indices_in_range<1>(bst->compressed_track, CMP_EPSILON, to_time, p_indices);
- } else {
- _track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, p_indices);
- _track_get_key_indices_in_range(bst->blend_shapes, CMP_EPSILON, to_time, p_indices);
- }
- } break;
- case TYPE_VALUE: {
- const ValueTrack *vt = static_cast<const ValueTrack *>(t);
- _track_get_key_indices_in_range(vt->values, 0, from_time, p_indices);
- _track_get_key_indices_in_range(vt->values, CMP_EPSILON, to_time, p_indices);
- } break;
- case TYPE_METHOD: {
- const MethodTrack *mt = static_cast<const MethodTrack *>(t);
- _track_get_key_indices_in_range(mt->methods, 0, from_time, p_indices);
- _track_get_key_indices_in_range(mt->methods, CMP_EPSILON, to_time, p_indices);
- } break;
- case TYPE_BEZIER: {
- const BezierTrack *bz = static_cast<const BezierTrack *>(t);
- _track_get_key_indices_in_range(bz->values, 0, from_time, p_indices);
- _track_get_key_indices_in_range(bz->values, CMP_EPSILON, to_time, p_indices);
- } break;
- case TYPE_AUDIO: {
- const AudioTrack *ad = static_cast<const AudioTrack *>(t);
- _track_get_key_indices_in_range(ad->values, 0, from_time, p_indices);
- _track_get_key_indices_in_range(ad->values, CMP_EPSILON, to_time, p_indices);
- } break;
- case TYPE_ANIMATION: {
- const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
- _track_get_key_indices_in_range(an->values, 0, from_time, p_indices);
- _track_get_key_indices_in_range(an->values, CMP_EPSILON, to_time, p_indices);
- } break;
- }
- return;
+ if (p_looped_flag == Animation::LOOPED_FLAG_START) {
+ // Handle loop by splitting.
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, CMP_EPSILON, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices, false);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, CMP_EPSILON, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices, false);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(st->scales, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices, false);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices, false);
+ }
+ } break;
+ case TYPE_VALUE: {
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices, false);
+ } break;
+ case TYPE_METHOD: {
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices, false);
+ } break;
+ case TYPE_BEZIER: {
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices, false);
+ } break;
+ case TYPE_AUDIO: {
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices, false);
+ } break;
+ case TYPE_ANIMATION: {
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, 0, from_time, p_indices, true);
+ _track_get_key_indices_in_range(an->values, 0, to_time, p_indices, false);
+ } break;
}
- if (p_pingponged == 1) {
- // handle loop by splitting
- to_time = MIN(length - CMP_EPSILON, to_time);
- switch (t->type) {
- case TYPE_POSITION_3D: {
- const PositionTrack *tt = static_cast<const PositionTrack *>(t);
- if (tt->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
- _get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length - CMP_EPSILON, p_indices);
- } else {
- _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
- _track_get_key_indices_in_range(tt->positions, to_time, length - CMP_EPSILON, p_indices);
- }
- } break;
- case TYPE_ROTATION_3D: {
- const RotationTrack *rt = static_cast<const RotationTrack *>(t);
- if (rt->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
- _get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, p_indices);
- } else {
- _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
- _track_get_key_indices_in_range(rt->rotations, to_time, length - CMP_EPSILON, p_indices);
- }
- } break;
- case TYPE_SCALE_3D: {
- const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
- if (st->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
- _get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, p_indices);
- } else {
- _track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
- _track_get_key_indices_in_range(st->scales, to_time, length - CMP_EPSILON, p_indices);
- }
- } break;
- case TYPE_BLEND_SHAPE: {
- const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
- if (bst->compressed_track >= 0) {
- _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
- _get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length - CMP_EPSILON, p_indices);
- } else {
- _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
- _track_get_key_indices_in_range(bst->blend_shapes, to_time, length - CMP_EPSILON, p_indices);
- }
- } break;
- case TYPE_VALUE: {
- const ValueTrack *vt = static_cast<const ValueTrack *>(t);
- _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(vt->values, to_time, length - CMP_EPSILON, p_indices);
- } break;
- case TYPE_METHOD: {
- const MethodTrack *mt = static_cast<const MethodTrack *>(t);
- _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
- _track_get_key_indices_in_range(mt->methods, to_time, length - CMP_EPSILON, p_indices);
- } break;
- case TYPE_BEZIER: {
- const BezierTrack *bz = static_cast<const BezierTrack *>(t);
- _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(bz->values, to_time, length - CMP_EPSILON, p_indices);
- } break;
- case TYPE_AUDIO: {
- const AudioTrack *ad = static_cast<const AudioTrack *>(t);
- _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(ad->values, to_time, length - CMP_EPSILON, p_indices);
- } break;
- case TYPE_ANIMATION: {
- const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
- _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(an->values, to_time, length - CMP_EPSILON, p_indices);
- } break;
- }
- return;
+ return;
+ }
+ if (p_looped_flag == Animation::LOOPED_FLAG_END) {
+ // Handle loop by splitting.
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length, p_indices);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(tt->positions, to_time, length, p_indices, true);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, p_indices);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(rt->rotations, to_time, length, p_indices, true);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, p_indices);
+ } else {
+ _track_get_key_indices_in_range(st->scales, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(st->scales, to_time, length, p_indices, true);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length - CMP_EPSILON, p_indices);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(bst->blend_shapes, to_time, length, p_indices, true);
+ }
+ } break;
+ case TYPE_VALUE: {
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(vt->values, to_time, length, p_indices, true);
+ } break;
+ case TYPE_METHOD: {
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(mt->methods, to_time, length, p_indices, true);
+ } break;
+ case TYPE_BEZIER: {
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(bz->values, to_time, length, p_indices, true);
+ } break;
+ case TYPE_AUDIO: {
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(ad->values, to_time, length, p_indices, true);
+ } break;
+ case TYPE_ANIMATION: {
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, length, p_indices, false);
+ _track_get_key_indices_in_range(an->values, to_time, length, p_indices, true);
+ } break;
}
+ return;
+ }
+
+ // The edge will be pingponged in the next frame and processed there, so let's ignore it now...
+ if (!is_backward && Math::is_equal_approx(to_time, length)) {
+ to_time = length - CMP_EPSILON;
+ } else if (is_backward && Math::is_equal_approx(from_time, 0)) {
+ from_time = CMP_EPSILON;
}
} break;
}
-
switch (t->type) {
case TYPE_POSITION_3D: {
const PositionTrack *tt = static_cast<const PositionTrack *>(t);
if (tt->compressed_track >= 0) {
_get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, to_time - from_time, p_indices);
} else {
- _track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices, is_backward);
}
} break;
case TYPE_ROTATION_3D: {
@@ -3042,7 +3129,7 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
if (rt->compressed_track >= 0) {
_get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, to_time - from_time, p_indices);
} else {
- _track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices, is_backward);
}
} break;
case TYPE_SCALE_3D: {
@@ -3050,7 +3137,7 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
if (st->compressed_track >= 0) {
_get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, to_time - from_time, p_indices);
} else {
- _track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices, is_backward);
}
} break;
case TYPE_BLEND_SHAPE: {
@@ -3058,28 +3145,28 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
if (bst->compressed_track >= 0) {
_get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, to_time - from_time, p_indices);
} else {
- _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices, is_backward);
}
} break;
case TYPE_VALUE: {
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
- _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices, is_backward);
} break;
case TYPE_METHOD: {
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
- _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices, is_backward);
} break;
case TYPE_BEZIER: {
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
- _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices, is_backward);
} break;
case TYPE_AUDIO: {
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
- _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices, is_backward);
} break;
case TYPE_ANIMATION: {
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
- _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices);
+ _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices, is_backward);
} break;
}
}
@@ -3815,6 +3902,10 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(LOOP_NONE);
BIND_ENUM_CONSTANT(LOOP_LINEAR);
BIND_ENUM_CONSTANT(LOOP_PINGPONG);
+
+ BIND_ENUM_CONSTANT(LOOPED_FLAG_NONE);
+ BIND_ENUM_CONSTANT(LOOPED_FLAG_END);
+ BIND_ENUM_CONSTANT(LOOPED_FLAG_START);
}
void Animation::clear() {
@@ -5798,7 +5889,8 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float
}
}
-Animation::Animation() {}
+Animation::Animation() {
+}
Animation::~Animation() {
for (int i = 0; i < tracks.size(); i++) {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 6c1ca3cd05..e66af77018 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -74,6 +74,12 @@ public:
LOOP_PINGPONG,
};
+ enum LoopedFlag {
+ LOOPED_FLAG_NONE,
+ LOOPED_FLAG_END,
+ LOOPED_FLAG_START,
+ };
+
#ifdef TOOLS_ENABLED
enum HandleMode {
HANDLE_MODE_FREE,
@@ -250,12 +256,11 @@ private:
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const;
template <class T>
- _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const;
+ _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const;
double length = 1.0;
real_t step = 0.1;
LoopMode loop_mode = LOOP_NONE;
- int pingponged = 0;
/* Animation compression page format (version 1):
*
@@ -454,7 +459,7 @@ public:
void copy_track(int p_track, Ref<Animation> p_to_animation);
- void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const;
+ void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE) const;
void set_length(real_t p_length);
real_t get_length() const;
@@ -484,6 +489,7 @@ VARIANT_ENUM_CAST(Animation::TrackType);
VARIANT_ENUM_CAST(Animation::InterpolationType);
VARIANT_ENUM_CAST(Animation::UpdateMode);
VARIANT_ENUM_CAST(Animation::LoopMode);
+VARIANT_ENUM_CAST(Animation::LoopedFlag);
#ifdef TOOLS_ENABLED
VARIANT_ENUM_CAST(Animation::HandleMode);
VARIANT_ENUM_CAST(Animation::HandleSetMode);
diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp
index 427d418551..b37bfbae62 100644
--- a/scene/resources/animation_library.cpp
+++ b/scene/resources/animation_library.cpp
@@ -52,11 +52,13 @@ Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<Animat
ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER);
if (animations.has(p_name)) {
+ animations.get(p_name)->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed));
animations.erase(p_name);
emit_signal(SNAME("animation_removed"), p_name);
}
animations.insert(p_name, p_animation);
+ animations.get(p_name)->connect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_name));
emit_signal(SNAME("animation_added"), p_name);
notify_property_list_changed();
return OK;
@@ -65,6 +67,7 @@ Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<Animat
void AnimationLibrary::remove_animation(const StringName &p_name) {
ERR_FAIL_COND_MSG(!animations.has(p_name), vformat("Animation not found: %s.", p_name));
+ animations.get(p_name)->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed));
animations.erase(p_name);
emit_signal(SNAME("animation_removed"), p_name);
notify_property_list_changed();
@@ -75,6 +78,8 @@ void AnimationLibrary::rename_animation(const StringName &p_name, const StringNa
ERR_FAIL_COND_MSG(!is_valid_animation_name(p_new_name), "Invalid animation name: '" + String(p_new_name) + "'.");
ERR_FAIL_COND_MSG(animations.has(p_new_name), vformat("Animation name \"%s\" already exists in library.", p_new_name));
+ animations.get(p_name)->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed));
+ animations.get(p_name)->connect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_new_name));
animations.insert(p_new_name, animations[p_name]);
animations.erase(p_name);
emit_signal(SNAME("animation_renamed"), p_name, p_new_name);
@@ -100,6 +105,10 @@ TypedArray<StringName> AnimationLibrary::_get_animation_list() const {
return ret;
}
+void AnimationLibrary::_animation_changed(const StringName &p_name) {
+ emit_signal(SNAME("animation_changed"), p_name);
+}
+
void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const {
List<StringName> anims;
@@ -115,6 +124,9 @@ void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const
}
void AnimationLibrary::_set_data(const Dictionary &p_data) {
+ for (KeyValue<StringName, Ref<Animation>> &K : animations) {
+ K.value->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed));
+ }
animations.clear();
List<Variant> keys;
p_data.get_key_list(&keys);
@@ -146,6 +158,7 @@ void AnimationLibrary::_bind_methods() {
ADD_SIGNAL(MethodInfo("animation_added", PropertyInfo(Variant::STRING_NAME, "name")));
ADD_SIGNAL(MethodInfo("animation_removed", PropertyInfo(Variant::STRING_NAME, "name")));
ADD_SIGNAL(MethodInfo("animation_renamed", PropertyInfo(Variant::STRING_NAME, "name"), PropertyInfo(Variant::STRING_NAME, "to_name")));
+ ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING_NAME, "name")));
}
AnimationLibrary::AnimationLibrary() {
}
diff --git a/scene/resources/animation_library.h b/scene/resources/animation_library.h
index d63807b6d7..54bd641b6d 100644
--- a/scene/resources/animation_library.h
+++ b/scene/resources/animation_library.h
@@ -42,6 +42,8 @@ class AnimationLibrary : public Resource {
TypedArray<StringName> _get_animation_list() const;
+ void _animation_changed(const StringName &p_name);
+
friend class AnimationPlayer; //for faster access
HashMap<StringName, Ref<Animation>> animations;
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 10d193f950..e457b2d377 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -92,18 +92,13 @@ void Material::inspect_native_shader_code() {
RID Material::get_shader_rid() const {
RID ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_shader_rid, ret)) {
- return ret;
- }
- return RID();
+ GDVIRTUAL_REQUIRED_CALL(_get_shader_rid, ret);
+ return ret;
}
Shader::Mode Material::get_shader_mode() const {
- Shader::Mode ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_shader_mode, ret)) {
- return ret;
- }
-
- return Shader::MODE_MAX;
+ Shader::Mode ret = Shader::MODE_MAX;
+ GDVIRTUAL_REQUIRED_CALL(_get_shader_mode, ret);
+ return ret;
}
bool Material::_can_do_next_pass() const {
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index 4f68a6f69b..a5e7602c8b 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -40,119 +40,83 @@
Mesh::ConvexDecompositionFunc Mesh::convex_decomposition_function = nullptr;
int Mesh::get_surface_count() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_surface_count, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_surface_count, ret);
+ return ret;
}
int Mesh::surface_get_array_len(int p_idx) const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_array_len, p_idx, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_array_len, p_idx, ret);
+ return ret;
}
int Mesh::surface_get_array_index_len(int p_idx) const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_array_index_len, p_idx, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_array_index_len, p_idx, ret);
+ return ret;
}
Array Mesh::surface_get_arrays(int p_surface) const {
Array ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_arrays, p_surface, ret)) {
- return ret;
- }
- return Array();
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_arrays, p_surface, ret);
+ return ret;
}
TypedArray<Array> Mesh::surface_get_blend_shape_arrays(int p_surface) const {
TypedArray<Array> ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_blend_shape_arrays, p_surface, ret)) {
- return ret;
- }
-
- return TypedArray<Array>();
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_blend_shape_arrays, p_surface, ret);
+ return ret;
}
Dictionary Mesh::surface_get_lods(int p_surface) const {
Dictionary ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_lods, p_surface, ret)) {
- return ret;
- }
-
- return Dictionary();
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_lods, p_surface, ret);
+ return ret;
}
uint32_t Mesh::surface_get_format(int p_idx) const {
- uint32_t ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_format, p_idx, ret)) {
- return ret;
- }
-
- return 0;
+ uint32_t ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_format, p_idx, ret);
+ return ret;
}
Mesh::PrimitiveType Mesh::surface_get_primitive_type(int p_idx) const {
- uint32_t ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_primitive_type, p_idx, ret)) {
- return (Mesh::PrimitiveType)ret;
- }
-
- return PRIMITIVE_MAX;
+ uint32_t ret = PRIMITIVE_MAX;
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_primitive_type, p_idx, ret);
+ return (Mesh::PrimitiveType)ret;
}
void Mesh::surface_set_material(int p_idx, const Ref<Material> &p_material) {
- if (GDVIRTUAL_REQUIRED_CALL(_surface_set_material, p_idx, p_material)) {
- return;
- }
+ GDVIRTUAL_REQUIRED_CALL(_surface_set_material, p_idx, p_material);
}
Ref<Material> Mesh::surface_get_material(int p_idx) const {
Ref<Material> ret;
- if (GDVIRTUAL_REQUIRED_CALL(_surface_get_material, p_idx, ret)) {
- return ret;
- }
-
- return Ref<Material>();
+ GDVIRTUAL_REQUIRED_CALL(_surface_get_material, p_idx, ret);
+ return ret;
}
int Mesh::get_blend_shape_count() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_blend_shape_count, ret)) {
- return ret;
- }
-
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_blend_shape_count, ret);
+ return ret;
}
StringName Mesh::get_blend_shape_name(int p_index) const {
StringName ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_blend_shape_name, p_index, ret)) {
- return ret;
- }
-
- return StringName();
+ GDVIRTUAL_REQUIRED_CALL(_get_blend_shape_name, p_index, ret);
+ return ret;
}
void Mesh::set_blend_shape_name(int p_index, const StringName &p_name) {
- if (GDVIRTUAL_REQUIRED_CALL(_set_blend_shape_name, p_index, p_name)) {
- return;
- }
+ GDVIRTUAL_REQUIRED_CALL(_set_blend_shape_name, p_index, p_name);
}
AABB Mesh::get_aabb() const {
AABB ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_aabb, ret)) {
- return ret;
- }
-
- return AABB();
+ GDVIRTUAL_REQUIRED_CALL(_get_aabb, ret);
+ return ret;
}
Ref<TriangleMesh> Mesh::generate_triangle_mesh() const {
diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp
index f379f88bed..ea341152e6 100644
--- a/scene/resources/style_box.cpp
+++ b/scene/resources/style_box.cpp
@@ -35,11 +35,9 @@
#include <limits.h>
float StyleBox::get_style_margin(Side p_side) const {
- float ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_style_margin, p_side, ret)) {
- return ret;
- }
- return 0;
+ float ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_style_margin, p_side, ret);
+ return ret;
}
bool StyleBox::test_mask(const Point2 &p_point, const Rect2 &p_rect) const {
@@ -49,9 +47,7 @@ bool StyleBox::test_mask(const Point2 &p_point, const Rect2 &p_rect) const {
}
void StyleBox::draw(RID p_canvas_item, const Rect2 &p_rect) const {
- if (GDVIRTUAL_REQUIRED_CALL(_draw, p_canvas_item, p_rect)) {
- return;
- }
+ GDVIRTUAL_REQUIRED_CALL(_draw, p_canvas_item, p_rect);
}
void StyleBox::set_default_margin(Side p_side, float p_value) {
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index b5754caa6a..2106619a6b 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -40,19 +40,15 @@
#include "servers/camera/camera_feed.h"
int Texture2D::get_width() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_width, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_width, ret);
+ return ret;
}
int Texture2D::get_height() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_height, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_height, ret);
+ return ret;
}
Size2 Texture2D::get_size() const {
@@ -1092,57 +1088,44 @@ TypedArray<Image> Texture3D::_get_datai() const {
}
Image::Format Texture3D::get_format() const {
- Image::Format ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_format, ret)) {
- return ret;
- }
- return Image::FORMAT_MAX;
+ Image::Format ret = Image::FORMAT_MAX;
+ GDVIRTUAL_REQUIRED_CALL(_get_format, ret);
+ return ret;
}
int Texture3D::get_width() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_width, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_width, ret);
+ return ret;
}
int Texture3D::get_height() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_height, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_height, ret);
+ return ret;
}
int Texture3D::get_depth() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_depth, ret)) {
- return ret;
- }
-
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_depth, ret);
+ return ret;
}
bool Texture3D::has_mipmaps() const {
- bool ret;
- if (GDVIRTUAL_REQUIRED_CALL(_has_mipmaps, ret)) {
- return ret;
- }
- return false;
+ bool ret = false;
+ GDVIRTUAL_REQUIRED_CALL(_has_mipmaps, ret);
+ return ret;
}
Vector<Ref<Image>> Texture3D::get_data() const {
TypedArray<Image> ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_data, ret)) {
- Vector<Ref<Image>> data;
- data.resize(ret.size());
- for (int i = 0; i < data.size(); i++) {
- data.write[i] = ret[i];
- }
- return data;
+ GDVIRTUAL_REQUIRED_CALL(_get_data, ret);
+ Vector<Ref<Image>> data;
+ data.resize(ret.size());
+ for (int i = 0; i < data.size(); i++) {
+ data.write[i] = ret[i];
}
- return Vector<Ref<Image>>();
+ return data;
}
void Texture3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_format"), &Texture3D::get_format);
@@ -2859,60 +2842,45 @@ AnimatedTexture::~AnimatedTexture() {
///////////////////////////////
Image::Format TextureLayered::get_format() const {
- Image::Format ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_format, ret)) {
- return ret;
- }
- return Image::FORMAT_MAX;
+ Image::Format ret = Image::FORMAT_MAX;
+ GDVIRTUAL_REQUIRED_CALL(_get_format, ret);
+ return ret;
}
TextureLayered::LayeredType TextureLayered::get_layered_type() const {
- uint32_t ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_layered_type, ret)) {
- return (LayeredType)ret;
- }
- return LAYERED_TYPE_2D_ARRAY;
+ uint32_t ret = LAYERED_TYPE_2D_ARRAY;
+ GDVIRTUAL_REQUIRED_CALL(_get_layered_type, ret);
+ return (LayeredType)ret;
}
int TextureLayered::get_width() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_width, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_width, ret);
+ return ret;
}
int TextureLayered::get_height() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_height, ret)) {
- return ret;
- }
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_height, ret);
+ return ret;
}
int TextureLayered::get_layers() const {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_layers, ret)) {
- return ret;
- }
-
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_layers, ret);
+ return ret;
}
bool TextureLayered::has_mipmaps() const {
- bool ret;
- if (GDVIRTUAL_REQUIRED_CALL(_has_mipmaps, ret)) {
- return ret;
- }
- return false;
+ bool ret = false;
+ GDVIRTUAL_REQUIRED_CALL(_has_mipmaps, ret);
+ return ret;
}
Ref<Image> TextureLayered::get_layer_data(int p_layer) const {
Ref<Image> ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_layer_data, p_layer, ret)) {
- return ret;
- }
- return Ref<Image>();
+ GDVIRTUAL_REQUIRED_CALL(_get_layer_data, p_layer, ret);
+ return ret;
}
void TextureLayered::_bind_methods() {
diff --git a/servers/audio/audio_effect.cpp b/servers/audio/audio_effect.cpp
index ef0e3197e1..870a77806b 100644
--- a/servers/audio/audio_effect.cpp
+++ b/servers/audio/audio_effect.cpp
@@ -31,9 +31,7 @@
#include "audio_effect.h"
void AudioEffectInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
- if (GDVIRTUAL_REQUIRED_CALL(_process, p_src_frames, p_dst_frames, p_frame_count)) {
- return;
- }
+ GDVIRTUAL_REQUIRED_CALL(_process, p_src_frames, p_dst_frames, p_frame_count);
}
bool AudioEffectInstance::process_silence() const {
bool ret = false;
@@ -50,10 +48,8 @@ void AudioEffectInstance::_bind_methods() {
Ref<AudioEffectInstance> AudioEffect::instantiate() {
Ref<AudioEffectInstance> ret;
- if (GDVIRTUAL_REQUIRED_CALL(_instantiate, ret)) {
- return ret;
- }
- return Ref<AudioEffectInstance>();
+ GDVIRTUAL_REQUIRED_CALL(_instantiate, ret);
+ return ret;
}
void AudioEffect::_bind_methods() {
GDVIRTUAL_BIND(_instantiate);
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 33d4c816f2..4b68515e18 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -71,12 +71,9 @@ void AudioStreamPlayback::seek(double p_time) {
}
int AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_mix, p_buffer, p_rate_scale, p_frames, ret)) {
- return ret;
- }
-
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_mix, p_buffer, p_rate_scale, p_frames, ret);
+ return ret;
}
void AudioStreamPlayback::tag_used_streams() {
@@ -107,20 +104,14 @@ void AudioStreamPlaybackResampled::begin_resample() {
}
int AudioStreamPlaybackResampled::_mix_internal(AudioFrame *p_buffer, int p_frames) {
- int ret;
- if (GDVIRTUAL_REQUIRED_CALL(_mix_resampled, p_buffer, p_frames, ret)) {
- return ret;
- }
-
- return 0;
+ int ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_mix_resampled, p_buffer, p_frames, ret);
+ return ret;
}
float AudioStreamPlaybackResampled::get_stream_sampling_rate() {
- float ret;
- if (GDVIRTUAL_REQUIRED_CALL(_get_stream_sampling_rate, ret)) {
- return ret;
- }
-
- return 0;
+ float ret = 0;
+ GDVIRTUAL_REQUIRED_CALL(_get_stream_sampling_rate, ret);
+ return ret;
}
void AudioStreamPlaybackResampled::_bind_methods() {
diff --git a/servers/movie_writer/movie_writer.cpp b/servers/movie_writer/movie_writer.cpp
index 0edce4ced6..419943f2d2 100644
--- a/servers/movie_writer/movie_writer.cpp
+++ b/servers/movie_writer/movie_writer.cpp
@@ -52,34 +52,26 @@ MovieWriter *MovieWriter::find_writer_for_file(const String &p_file) {
}
uint32_t MovieWriter::get_audio_mix_rate() const {
- uint32_t ret = 0;
- if (GDVIRTUAL_REQUIRED_CALL(_get_audio_mix_rate, ret)) {
- return ret;
- }
- return 48000;
+ uint32_t ret = 48000;
+ GDVIRTUAL_REQUIRED_CALL(_get_audio_mix_rate, ret);
+ return ret;
}
AudioServer::SpeakerMode MovieWriter::get_audio_speaker_mode() const {
AudioServer::SpeakerMode ret = AudioServer::SPEAKER_MODE_STEREO;
- if (GDVIRTUAL_REQUIRED_CALL(_get_audio_speaker_mode, ret)) {
- return ret;
- }
- return AudioServer::SPEAKER_MODE_STEREO;
+ GDVIRTUAL_REQUIRED_CALL(_get_audio_speaker_mode, ret);
+ return ret;
}
Error MovieWriter::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {
- Error ret = OK;
- if (GDVIRTUAL_REQUIRED_CALL(_write_begin, p_movie_size, p_fps, p_base_path, ret)) {
- return ret;
- }
- return ERR_UNCONFIGURED;
+ Error ret = ERR_UNCONFIGURED;
+ GDVIRTUAL_REQUIRED_CALL(_write_begin, p_movie_size, p_fps, p_base_path, ret);
+ return ret;
}
Error MovieWriter::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) {
- Error ret = OK;
- if (GDVIRTUAL_REQUIRED_CALL(_write_frame, p_image, p_audio_data, ret)) {
- return ret;
- }
- return ERR_UNCONFIGURED;
+ Error ret = ERR_UNCONFIGURED;
+ GDVIRTUAL_REQUIRED_CALL(_write_frame, p_image, p_audio_data, ret);
+ return ret;
}
void MovieWriter::write_end() {
@@ -88,18 +80,15 @@ void MovieWriter::write_end() {
bool MovieWriter::handles_file(const String &p_path) const {
bool ret = false;
- if (GDVIRTUAL_REQUIRED_CALL(_handles_file, p_path, ret)) {
- return ret;
- }
- return false;
+ GDVIRTUAL_REQUIRED_CALL(_handles_file, p_path, ret);
+ return ret;
}
void MovieWriter::get_supported_extensions(List<String> *r_extensions) const {
Vector<String> exts;
- if (GDVIRTUAL_REQUIRED_CALL(_get_supported_extensions, exts)) {
- for (int i = 0; i < exts.size(); i++) {
- r_extensions->push_back(exts[i]);
- }
+ GDVIRTUAL_REQUIRED_CALL(_get_supported_extensions, exts);
+ for (int i = 0; i < exts.size(); i++) {
+ r_extensions->push_back(exts[i]);
}
}
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index 16d382a5f3..075a21b818 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -275,12 +275,12 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (ci->clip) {
if (p_canvas_clip != nullptr) {
ci->final_clip_rect = p_canvas_clip->final_clip_rect.intersection(global_rect);
- if (ci->final_clip_rect == Rect2()) {
- // Clip rects do not intersect, so don't draw this item.
- return;
- }
} else {
- ci->final_clip_rect = global_rect;
+ ci->final_clip_rect = p_clip_rect.intersection(global_rect);
+ }
+ if (ci->final_clip_rect.size.width < 0.5 || ci->final_clip_rect.size.height < 0.5) {
+ // The clip rect area is 0, so don't draw the item.
+ return;
}
ci->final_clip_rect.position = ci->final_clip_rect.position.round();
ci->final_clip_rect.size = ci->final_clip_rect.size.round();
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index 4765475804..27c82213e4 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -665,7 +665,6 @@ public:
virtual void skeleton_allocate_data(RID p_skeleton, int p_bones, bool p_2d_skeleton = false) override;
virtual void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) override;
- void skeleton_set_world_transform(RID p_skeleton, bool p_enable, const Transform3D &p_world_transform);
virtual int skeleton_get_bone_count(RID p_skeleton) const override;
virtual void skeleton_bone_set_transform(RID p_skeleton, int p_bone, const Transform3D &p_transform) override;
virtual Transform3D skeleton_bone_get_transform(RID p_skeleton, int p_bone) const override;
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 4ee355ee9f..4b68d1f5c0 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -2520,7 +2520,7 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
Projection projections[RendererSceneRender::MAX_RENDER_VIEWS];
uint32_t view_count = p_xr_interface->get_view_count();
- ERR_FAIL_COND_MSG(view_count > RendererSceneRender::MAX_RENDER_VIEWS, "Requested view count is not supported");
+ ERR_FAIL_COND_MSG(view_count == 0 || view_count > RendererSceneRender::MAX_RENDER_VIEWS, "Requested view count is not supported");
float aspect = p_viewport_size.width / (float)p_viewport_size.height;
diff --git a/thirdparty/README.md b/thirdparty/README.md
index a75ceda7d3..937d149747 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -398,7 +398,7 @@ to solve some MSVC warnings. See the patches in the `patches` directory.
## miniupnpc
- Upstream: https://github.com/miniupnp/miniupnp
-- Version: 2.2.3 (2df8120326ed4246e049a7a6de707539604cd514, 2021)
+- Version: 2.2.4 (7d1d8bc3868b08ad003bad235eee57562b95b76d, 2022)
- License: BSD-3-Clause
Files extracted from upstream source:
@@ -668,7 +668,7 @@ instead of `miniz.h` as an external dependency.
## thorvg
- Upstream: https://github.com/Samsung/thorvg
-- Version: 0.8.2 (496796f1e5e85bd5fbba36dae987edb1b3945592, 2022)
+- Version: 0.8.3 (a0fcf51f80a75f63a066df085f60cdaf715188b6, 2022)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/miniupnpc/LICENSE b/thirdparty/miniupnpc/LICENSE
index fe9118c07e..67ff3bb627 100644
--- a/thirdparty/miniupnpc/LICENSE
+++ b/thirdparty/miniupnpc/LICENSE
@@ -1,26 +1,29 @@
-MiniUPnP Project
-Copyright (c) 2005-2020, Thomas BERNARD
+BSD 3-Clause License
+
+Copyright (c) 2005-2022, Thomas BERNARD
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- * The name of the author may not be used to endorse or promote products
- derived from this software without specific prior written permission.
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/thirdparty/miniupnpc/include/miniupnpc.h b/thirdparty/miniupnpc/include/miniupnpc.h
index a10bd950a8..75fb8b702d 100644
--- a/thirdparty/miniupnpc/include/miniupnpc.h
+++ b/thirdparty/miniupnpc/include/miniupnpc.h
@@ -1,9 +1,9 @@
-/* $Id: miniupnpc.h,v 1.59 2021/09/28 21:39:17 nanard Exp $ */
+/* $Id: miniupnpc.h,v 1.61 2022/10/21 21:15:02 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* Project: miniupnp
- * http://miniupnp.free.fr/
+ * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
* Author: Thomas Bernard
- * Copyright (c) 2005-2021 Thomas Bernard
+ * Copyright (c) 2005-2022 Thomas Bernard
* This software is subjects to the conditions detailed
* in the LICENCE file provided within this distribution */
#ifndef MINIUPNPC_H_INCLUDED
@@ -20,7 +20,7 @@
#define UPNPDISCOVER_MEMORY_ERROR (-102)
/* versions : */
-#define MINIUPNPC_VERSION "2.2.3"
+#define MINIUPNPC_VERSION "2.2.4"
#define MINIUPNPC_API_VERSION 17
/* Source port:
diff --git a/thirdparty/miniupnpc/src/miniupnpcstrings.h b/thirdparty/miniupnpc/src/miniupnpcstrings.h
index eefbc8dbdd..25abc1016e 100644
--- a/thirdparty/miniupnpc/src/miniupnpcstrings.h
+++ b/thirdparty/miniupnpc/src/miniupnpcstrings.h
@@ -4,7 +4,7 @@
#include "core/version.h"
#define OS_STRING VERSION_NAME "/1.0"
-#define MINIUPNPC_VERSION_STRING "2.2.3"
+#define MINIUPNPC_VERSION_STRING "2.2.4"
#if 0
/* according to "UPnP Device Architecture 1.0" */
diff --git a/thirdparty/miniupnpc/src/minixml.c b/thirdparty/miniupnpc/src/minixml.c
index ed2d3c759c..935ec443e7 100644
--- a/thirdparty/miniupnpc/src/minixml.c
+++ b/thirdparty/miniupnpc/src/minixml.c
@@ -1,4 +1,4 @@
-/* $Id: minixml.c,v 1.10 2012/03/05 19:42:47 nanard Exp $ */
+/* $Id: minixml.c,v 1.12 2017/12/12 11:17:40 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* minixml.c : the minimum size a xml parser can be ! */
/* Project : miniupnp
diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h
index 68935c583b..eda302aec0 100644
--- a/thirdparty/thorvg/inc/config.h
+++ b/thirdparty/thorvg/inc/config.h
@@ -13,5 +13,5 @@
#define THORVG_JPG_LOADER_SUPPORT 1
-#define THORVG_VERSION_STRING "0.8.2"
+#define THORVG_VERSION_STRING "0.8.3"
#endif
diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h
index b08356d9d5..7e8988a65e 100644
--- a/thirdparty/thorvg/inc/thorvg.h
+++ b/thirdparty/thorvg/inc/thorvg.h
@@ -18,7 +18,7 @@
#include <string>
#ifdef TVG_BUILD
- #if defined(_MSC_VER) && !defined(__clang__)
+ #if defined(_WIN32) && !defined(__clang__)
#define TVG_EXPORT __declspec(dllexport)
#define TVG_DEPRECATED __declspec(deprecated)
#else
@@ -74,7 +74,7 @@ class Accessor;
/**
* @brief Enumeration specifying the result from the APIs.
*/
-enum class TVG_EXPORT Result
+enum class Result
{
Success = 0, ///< The value returned in case of a correct request execution.
InvalidArguments, ///< The value returned in the event of a problem with the arguments given to the API - e.g. empty paths or null pointers.
@@ -91,7 +91,7 @@ enum class TVG_EXPORT Result
* Not to be confused with the path commands from the svg path element (like M, L, Q, H and many others).
* TVG interprets all of them and translates to the ones from the PathCommand values.
*/
-enum class TVG_EXPORT PathCommand
+enum class PathCommand
{
Close = 0, ///< Ends the current sub-path and connects it with its initial point. This command doesn't expect any points.
MoveTo, ///< Sets a new initial point of the sub-path and a new current point. This command expects 1 point: the starting position.
@@ -102,7 +102,7 @@ enum class TVG_EXPORT PathCommand
/**
* @brief Enumeration determining the ending type of a stroke in the open sub-paths.
*/
-enum class TVG_EXPORT StrokeCap
+enum class StrokeCap
{
Square = 0, ///< The stroke is extended in both end-points of a sub-path by a rectangle, with the width equal to the stroke width and the length equal to the half of the stroke width. For zero length sub-paths the square is rendered with the size of the stroke width.
Round, ///< The stroke is extended in both end-points of a sub-path by a half circle, with a radius equal to the half of a stroke width. For zero length sub-paths a full circle is rendered.
@@ -112,7 +112,7 @@ enum class TVG_EXPORT StrokeCap
/**
* @brief Enumeration determining the style used at the corners of joined stroked path segments.
*/
-enum class TVG_EXPORT StrokeJoin
+enum class StrokeJoin
{
Bevel = 0, ///< The outer corner of the joined path segments is bevelled at the join point. The triangular region of the corner is enclosed by a straight line between the outer corners of each stroke.
Round, ///< The outer corner of the joined path segments is rounded. The circular region is centered at the join point.
@@ -122,7 +122,7 @@ enum class TVG_EXPORT StrokeJoin
/**
* @brief Enumeration specifying how to fill the area outside the gradient bounds.
*/
-enum class TVG_EXPORT FillSpread
+enum class FillSpread
{
Pad = 0, ///< The remaining area is filled with the closest stop color.
Reflect, ///< The gradient pattern is reflected outside the gradient area until the expected region is filled.
@@ -132,7 +132,7 @@ enum class TVG_EXPORT FillSpread
/**
* @brief Enumeration specifying the algorithm used to establish which parts of the shape are treated as the inside of the shape.
*/
-enum class TVG_EXPORT FillRule
+enum class FillRule
{
Winding = 0, ///< A line from the point to a location outside the shape is drawn. The intersections of the line with the path segment of the shape are counted. Starting from zero, if the path segment of the shape crosses the line clockwise, one is added, otherwise one is subtracted. If the resulting sum is non zero, the point is inside the shape.
EvenOdd ///< A line from the point to a location outside the shape is drawn and its intersections with the path segments of the shape are counted. If the number of intersections is an odd number, the point is inside the shape.
@@ -141,7 +141,7 @@ enum class TVG_EXPORT FillRule
/**
* @brief Enumeration indicating the method used in the composition of two objects - the target and the source.
*/
-enum class TVG_EXPORT CompositeMethod
+enum class CompositeMethod
{
None = 0, ///< No composition is applied.
ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered.
@@ -153,7 +153,7 @@ enum class TVG_EXPORT CompositeMethod
/**
* @brief Enumeration specifying the engine type used for the graphics backend. For multiple backends bitwise operation is allowed.
*/
-enum class TVG_EXPORT CanvasEngine
+enum class CanvasEngine
{
Sw = (1 << 1), ///< CPU rasterizer.
Gl = (1 << 2) ///< OpenGL rasterizer.
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp
index bf1c10a0c3..ffd74bdd47 100644
--- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp
@@ -22,10 +22,10 @@
#ifdef _WIN32
#include <malloc.h>
-#elif __FreeBSD__
- #include<stdlib.h>
-#else
+#elif defined(__linux__)
#include <alloca.h>
+#else
+ #include <stdlib.h>
#endif
#include "tvgMath.h"
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h
index f31ea1eb97..50536299b1 100644
--- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h
@@ -30,7 +30,7 @@
int32_t dw = surface->stride;
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
- int32_t minx, maxx;
+ int32_t minx = INT32_MAX, maxx = INT32_MIN;
float dx, u, v, iptr;
uint32_t* buf;
SwSpan* span = nullptr; //used only when rle based.
diff --git a/thirdparty/thorvg/src/lib/tvgLoadModule.h b/thirdparty/thorvg/src/lib/tvgLoadModule.h
index bfcc165f31..004983152b 100644
--- a/thirdparty/thorvg/src/lib/tvgLoadModule.h
+++ b/thirdparty/thorvg/src/lib/tvgLoadModule.h
@@ -36,7 +36,6 @@ public:
float vw = 0;
float vh = 0;
float w = 0, h = 0; //default image size
- bool preserveAspect = true; //keep aspect ratio by default.
virtual ~LoadModule() {}
diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp
index ffdef3004c..d11dfc1e7c 100644
--- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp
@@ -118,9 +118,9 @@ unique_ptr<Surface> JpgLoader::bitmap()
auto surface = static_cast<Surface*>(malloc(sizeof(Surface)));
surface->buffer = (uint32_t*)(image);
- surface->stride = w;
- surface->w = w;
- surface->h = h;
+ surface->stride = static_cast<uint32_t>(w);
+ surface->w = static_cast<uint32_t>(w);
+ surface->h = static_cast<uint32_t>(h);
surface->cs = SwCanvas::ARGB8888;
return unique_ptr<Surface>(surface);
diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp
index dacd45ed03..56b40acf0b 100644
--- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp
+++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp
@@ -808,7 +808,7 @@ inline int jpeg_decoder::huff_decode(huff_tables *pH, int& extra_bits)
// 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 unsigned int s_extend_offset[16] = { 0, ((-1u)<<1) + 1, ((-1u)<<2) + 1, ((-1u)<<3) + 1, ((-1u)<<4) + 1, ((-1u)<<5) + 1, ((-1u)<<6) + 1, ((-1u)<<7) + 1, ((-1u)<<8) + 1, ((-1u)<<9) + 1, ((-1u)<<10) + 1, ((-1u)<<11) + 1, ((-1u)<<12) + 1, ((-1u)<<13) + 1, ((-1u)<<14) + 1, ((-1u)<<15) + 1 };
+static const unsigned int s_extend_offset[16] = { 0, ((~0u)<<1) + 1, ((~0u)<<2) + 1, ((~0u)<<3) + 1, ((~0u)<<4) + 1, ((~0u)<<5) + 1, ((~0u)<<6) + 1, ((~0u)<<7) + 1, ((~0u)<<8) + 1, ((~0u)<<9) + 1, ((~0u)<<10) + 1, ((~0u)<<11) + 1, ((~0u)<<12) + 1, ((~0u)<<13) + 1, ((~0u)<<14) + 1, ((~0u)<<15) + 1 };
// The logical AND's in this macro are to shut up static code analysis (aren't really necessary - couldn't find another way to do this)
#define JPGD_HUFF_EXTEND(x, s) (((x) < s_extend_test[s & 15]) ? ((x) + s_extend_offset[s & 15]) : (x))
diff --git a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp
index 2da399d8c3..889f130ce9 100644
--- a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp
@@ -78,9 +78,9 @@ unique_ptr<Surface> RawLoader::bitmap()
auto surface = static_cast<Surface*>(malloc(sizeof(Surface)));
surface->buffer = (uint32_t*)(content);
- surface->stride = w;
- surface->w = w;
- surface->h = h;
+ surface->stride = (uint32_t)w;
+ surface->w = (uint32_t)w;
+ surface->h = (uint32_t)h;
surface->cs = SwCanvas::ARGB8888;
return unique_ptr<Surface>(surface);
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp
index 8f46b62ce9..478ba5d3d1 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp
@@ -42,7 +42,10 @@ static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
to->fill.paint.color = from->fill.paint.color;
to->fill.paint.none = from->fill.paint.none;
to->fill.paint.curColor = from->fill.paint.curColor;
- if (from->fill.paint.url) to->fill.paint.url = strdup(from->fill.paint.url);
+ if (from->fill.paint.url) {
+ if (to->fill.paint.url) free(to->fill.paint.url);
+ to->fill.paint.url = strdup(from->fill.paint.url);
+ }
to->fill.flags = (SvgFillFlags)((int)to->fill.flags | (int)SvgFillFlags::Paint);
to->flags = (SvgStyleFlags)((int)to->flags | (int)SvgStyleFlags::Fill);
}
@@ -61,7 +64,10 @@ static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
to->stroke.paint.color = from->stroke.paint.color;
to->stroke.paint.none = from->stroke.paint.none;
to->stroke.paint.curColor = from->stroke.paint.curColor;
- if (from->stroke.paint.url) to->stroke.paint.url = strdup(from->stroke.paint.url);
+ if (from->stroke.paint.url) {
+ if (to->stroke.paint.url) free(to->stroke.paint.url);
+ to->stroke.paint.url = strdup(from->stroke.paint.url);
+ }
to->stroke.flags = (SvgStrokeFlags)((int)to->stroke.flags | (int)SvgStrokeFlags::Paint);
to->flags = (SvgStyleFlags)((int)to->flags | (int)SvgStyleFlags::Stroke);
}
@@ -122,8 +128,14 @@ void cssCopyStyleAttr(SvgNode* to, const SvgNode* from)
//Copy style attribute
_copyStyle(to->style, from->style);
- if (from->style->clipPath.url) to->style->clipPath.url = strdup(from->style->clipPath.url);
- if (from->style->mask.url) to->style->mask.url = strdup(from->style->mask.url);
+ if (from->style->clipPath.url) {
+ if (to->style->clipPath.url) free(to->style->clipPath.url);
+ to->style->clipPath.url = strdup(from->style->clipPath.url);
+ }
+ if (from->style->mask.url) {
+ if (to->style->mask.url) free(to->style->mask.url);
+ to->style->mask.url = strdup(from->style->mask.url);
+ }
}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
index 42bfd4de70..737fd96455 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -115,9 +115,54 @@ static bool _parseNumber(const char** content, float* number)
if ((*content) == end) return false;
//Skip comma if any
*content = _skipComma(end);
+
return true;
}
+
+static constexpr struct
+{
+ AspectRatioAlign align;
+ const char* tag;
+} alignTags[] = {
+ { AspectRatioAlign::XMinYMin, "xMinYMin" },
+ { AspectRatioAlign::XMidYMin, "xMidYMin" },
+ { AspectRatioAlign::XMaxYMin, "xMaxYMin" },
+ { AspectRatioAlign::XMinYMid, "xMinYMid" },
+ { AspectRatioAlign::XMidYMid, "xMidYMid" },
+ { AspectRatioAlign::XMaxYMid, "xMaxYMid" },
+ { AspectRatioAlign::XMinYMax, "xMinYMax" },
+ { AspectRatioAlign::XMidYMax, "xMidYMax" },
+ { AspectRatioAlign::XMaxYMax, "xMaxYMax" },
+};
+
+
+static bool _parseAspectRatio(const char** content, AspectRatioAlign* align, AspectRatioMeetOrSlice* meetOrSlice)
+{
+ if (!strcmp(*content, "none")) {
+ *align = AspectRatioAlign::None;
+ return true;
+ }
+
+ for (unsigned int i = 0; i < sizeof(alignTags) / sizeof(alignTags[0]); i++) {
+ if (!strncmp(*content, alignTags[i].tag, 8)) {
+ *align = alignTags[i].align;
+ *content += 8;
+ *content = _skipSpace(*content, nullptr);
+ break;
+ }
+ }
+
+ if (!strcmp(*content, "meet")) {
+ *meetOrSlice = AspectRatioMeetOrSlice::Meet;
+ } else if (!strcmp(*content, "slice")) {
+ *meetOrSlice = AspectRatioMeetOrSlice::Slice;
+ }
+
+ return true;
+}
+
+
/**
* According to https://www.w3.org/TR/SVG/coords.html#Units
*/
@@ -554,6 +599,7 @@ static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
}
}
} else if (ref && len >= 3 && !strncmp(str, "url", 3)) {
+ if (*ref) free(*ref);
*ref = _idFromUrl((const char*)(str + 3));
} else {
//Handle named color
@@ -802,7 +848,7 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value)
}
loader->svgParse->global.x = (int)doc->vx;
} else if (!strcmp(key, "preserveAspectRatio")) {
- if (!strcmp(value, "none")) doc->preserveAspect = false;
+ _parseAspectRatio(&value, &doc->align, &doc->meetOrSlice);
} else if (!strcmp(key, "style")) {
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
#ifdef THORVG_LOG_ENABLED
@@ -1156,7 +1202,7 @@ static bool _attrParseSymbolNode(void* data, const char* key, const char* value)
symbol->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
symbol->hasHeight = true;
} else if (!strcmp(key, "preserveAspectRatio")) {
- if (!strcmp(value, "none")) symbol->preserveAspect = false;
+ _parseAspectRatio(&value, &symbol->align, &symbol->meetOrSlice);
} else if (!strcmp(key, "overflow")) {
if (!strcmp(value, "visible")) symbol->overflowVisible = true;
} else {
@@ -1248,7 +1294,8 @@ static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const cha
loader->svgParse->global.w = 0;
loader->svgParse->global.h = 0;
- doc->preserveAspect = true;
+ doc->align = AspectRatioAlign::XMidYMid;
+ doc->meetOrSlice = AspectRatioMeetOrSlice::Meet;
func(buf, bufLength, _attrParseSvgNode, loader);
if (loader->svgParse->global.w == 0) {
@@ -1309,7 +1356,8 @@ static SvgNode* _createSymbolNode(SvgLoaderData* loader, SvgNode* parent, const
if (!loader->svgParse->node) return nullptr;
loader->svgParse->node->display = false;
- loader->svgParse->node->node.symbol.preserveAspect = true;
+ loader->svgParse->node->node.symbol.align = AspectRatioAlign::XMidYMid;
+ loader->svgParse->node->node.symbol.meetOrSlice = AspectRatioMeetOrSlice::Meet;
loader->svgParse->node->node.symbol.overflowVisible = false;
loader->svgParse->node->node.symbol.hasViewBox = false;
@@ -1331,6 +1379,7 @@ static bool _attrParsePathNode(void* data, const char* key, const char* value)
SvgPathNode* path = &(node->node.path);
if (!strcmp(key, "d")) {
+ if (path->path) free(path->path);
//Temporary: need to copy
path->path = _copyId(value);
} else if (!strcmp(key, "style")) {
@@ -1801,19 +1850,10 @@ static SvgNode* _getDefsNode(SvgNode* node)
}
-static SvgNode* _findChildById(const SvgNode* node, const char* id)
+static SvgNode* _findNodeById(SvgNode *node, const char* id)
{
if (!node) return nullptr;
- auto child = node->child.data;
- for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
- if (((*child)->id) && !strcmp((*child)->id, id)) return (*child);
- }
- return nullptr;
-}
-
-static SvgNode* _findNodeById(SvgNode *node, const char* id)
-{
SvgNode* result = nullptr;
if (node->id && !strcmp(node->id, id)) return node;
@@ -1827,6 +1867,7 @@ static SvgNode* _findNodeById(SvgNode *node, const char* id)
return result;
}
+
static void _cloneGradStops(Array<Fill::ColorStop>& dst, const Array<Fill::ColorStop>& src)
{
for (uint32_t i = 0; i < src.count; ++i) {
@@ -1889,7 +1930,10 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren
child->fill.paint.color = parent->fill.paint.color;
child->fill.paint.none = parent->fill.paint.none;
child->fill.paint.curColor = parent->fill.paint.curColor;
- if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url);
+ if (parent->fill.paint.url) {
+ if (child->fill.paint.url) free(child->fill.paint.url);
+ child->fill.paint.url = _copyId(parent->fill.paint.url);
+ }
}
if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) {
child->fill.opacity = parent->fill.opacity;
@@ -1902,7 +1946,12 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren
child->stroke.paint.color = parent->stroke.paint.color;
child->stroke.paint.none = parent->stroke.paint.none;
child->stroke.paint.curColor = parent->stroke.paint.curColor;
- child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url) : nullptr;
+ if (parent->stroke.paint.url) {
+ if (child->stroke.paint.url) free(child->stroke.paint.url);
+ child->stroke.paint.url = _copyId(parent->stroke.paint.url);
+ } else {
+ child->stroke.paint.url = nullptr;
+ }
}
if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) {
child->stroke.opacity = parent->stroke.opacity;
@@ -1942,7 +1991,10 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from)
to->fill.paint.color = from->fill.paint.color;
to->fill.paint.none = from->fill.paint.none;
to->fill.paint.curColor = from->fill.paint.curColor;
- if (from->fill.paint.url) to->fill.paint.url = _copyId(from->fill.paint.url);
+ if (from->fill.paint.url) {
+ if (to->fill.paint.url) free(to->fill.paint.url);
+ to->fill.paint.url = _copyId(from->fill.paint.url);
+ }
}
if (((int)from->fill.flags & (int)SvgFillFlags::Opacity)) {
to->fill.opacity = from->fill.opacity;
@@ -1956,7 +2008,12 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from)
to->stroke.paint.color = from->stroke.paint.color;
to->stroke.paint.none = from->stroke.paint.none;
to->stroke.paint.curColor = from->stroke.paint.curColor;
- to->stroke.paint.url = from->stroke.paint.url ? _copyId(from->stroke.paint.url) : nullptr;
+ if (from->stroke.paint.url) {
+ if (to->stroke.paint.url) free(to->stroke.paint.url);
+ to->stroke.paint.url = _copyId(from->stroke.paint.url);
+ } else {
+ to->stroke.paint.url = nullptr;
+ }
}
if (((int)from->stroke.flags & (int)SvgStrokeFlags::Opacity)) {
to->stroke.opacity = from->stroke.opacity;
@@ -1992,10 +2049,14 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
//Copy style attribute
_styleCopy(to->style, from->style);
to->style->flags = (SvgStyleFlags)((int)to->style->flags | (int)from->style->flags);
- if (from->style->fill.paint.url) to->style->fill.paint.url = strdup(from->style->fill.paint.url);
- if (from->style->stroke.paint.url) to->style->stroke.paint.url = strdup(from->style->stroke.paint.url);
- if (from->style->clipPath.url) to->style->clipPath.url = strdup(from->style->clipPath.url);
- if (from->style->mask.url) to->style->mask.url = strdup(from->style->mask.url);
+ if (from->style->clipPath.url) {
+ if (to->style->clipPath.url) free(to->style->clipPath.url);
+ to->style->clipPath.url = strdup(from->style->clipPath.url);
+ }
+ if (from->style->mask.url) {
+ if (to->style->mask.url) free(to->style->mask.url);
+ to->style->mask.url = strdup(from->style->mask.url);
+ }
//Copy node attribute
switch (from->type) {
@@ -2031,7 +2092,10 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
break;
}
case SvgNodeType::Path: {
- if (from->node.path.path) to->node.path.path = strdup(from->node.path.path);
+ if (from->node.path.path) {
+ if (to->node.path.path) free(to->node.path.path);
+ to->node.path.path = strdup(from->node.path.path);
+ }
break;
}
case SvgNodeType::Polygon: {
@@ -2053,7 +2117,10 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
to->node.image.y = from->node.image.y;
to->node.image.w = from->node.image.w;
to->node.image.h = from->node.image.h;
- if (from->node.image.href) to->node.image.href = strdup(from->node.image.href);
+ if (from->node.image.href) {
+ if (to->node.image.href) free(to->node.image.href);
+ to->node.image.href = strdup(from->node.image.href);
+ }
break;
}
default: {
@@ -2093,8 +2160,8 @@ static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc)
for (uint32_t i = 0; i < cloneNodes->count; ++i) {
auto nodeIdPair = cloneNodes->data[i];
auto defs = _getDefsNode(nodeIdPair.node);
- auto nodeFrom = _findChildById(defs, nodeIdPair.id);
- if (!nodeFrom) nodeFrom = _findChildById(doc, nodeIdPair.id);
+ auto nodeFrom = _findNodeById(defs, nodeIdPair.id);
+ if (!nodeFrom) nodeFrom = _findNodeById(doc, nodeIdPair.id);
_cloneNode(nodeFrom, nodeIdPair.node, 0);
if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair.node->type == SvgNodeType::Use) {
nodeIdPair.node->node.use.symbol = nodeFrom;
@@ -2141,7 +2208,7 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value)
if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
id = _idFromHref(value);
defs = _getDefsNode(node);
- nodeFrom = _findChildById(defs, id);
+ nodeFrom = _findNodeById(defs, id);
if (nodeFrom) {
_cloneNode(nodeFrom, node, 0);
if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom;
@@ -2695,10 +2762,14 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1];
else parent = loader->doc;
if (!strcmp(tagName, "style")) {
- node = method(loader, nullptr, attrs, attrsLength, simpleXmlParseAttributes);
- loader->cssStyle = node;
- loader->doc->node.doc.style = node;
- loader->style = true;
+ // TODO: For now only the first style node is saved. After the css id selector
+ // is introduced this if condition shouldin't be necessary any more
+ if (!loader->cssStyle) {
+ node = method(loader, nullptr, attrs, attrsLength, simpleXmlParseAttributes);
+ loader->cssStyle = node;
+ loader->doc->node.doc.style = node;
+ loader->style = true;
+ }
} else {
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
}
@@ -3127,7 +3198,7 @@ void SvgLoader::run(unsigned tid)
_updateStyle(loaderData.doc, nullptr);
}
- root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, preserveAspect, svgPath);
+ root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, align, meetOrSlice, svgPath);
}
@@ -3160,7 +3231,8 @@ bool SvgLoader::header()
if (vh < FLT_EPSILON) vh = h;
}
- preserveAspect = loaderData.doc->node.doc.preserveAspect;
+ align = loaderData.doc->node.doc.align;
+ meetOrSlice = loaderData.doc->node.doc.meetOrSlice;
} else {
TVGLOG("SVG", "No SVG File. There is no <svg/>");
return false;
@@ -3215,31 +3287,9 @@ bool SvgLoader::resize(Paint* paint, float w, float h)
auto sx = w / this->w;
auto sy = h / this->h;
+ Matrix m = {sx, 0, 0, 0, sy, 0, 0, 0, 1};
+ paint->transform(m);
- if (preserveAspect) {
- //Scale
- auto scale = sx < sy ? sx : sy;
- paint->scale(scale);
- //Align
- auto tx = 0.0f;
- auto ty = 0.0f;
- auto tw = this->w * scale;
- auto th = this->h * scale;
- if (tw > th) ty -= (h - th) * 0.5f;
- else tx -= (w - tw) * 0.5f;
- paint->translate(-tx, -ty);
- } else {
- //Align
- auto tx = 0.0f;
- auto ty = 0.0f;
- auto tw = this->w * sx;
- auto th = this->h * sy;
- if (tw > th) ty -= (h - th) * 0.5f;
- else tx -= (w - tw) * 0.5f;
-
- Matrix m = {sx, 0, -tx, 0, sy, -ty, 0, 0, 1};
- paint->transform(m);
- }
return true;
}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
index 093fb671b3..f224d1a4ac 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
@@ -50,6 +50,9 @@ public:
unique_ptr<Paint> paint() override;
private:
+ AspectRatioAlign align = AspectRatioAlign::XMidYMid;
+ AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet;
+
bool header();
void clear();
void run(unsigned tid) override;
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
index dc9ed558c3..c657c0e21a 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
@@ -145,6 +145,26 @@ enum class SvgParserLengthType
Other
};
+enum class AspectRatioAlign
+{
+ None,
+ XMinYMin,
+ XMidYMin,
+ XMaxYMin,
+ XMinYMid,
+ XMidYMid,
+ XMaxYMid,
+ XMinYMax,
+ XMidYMax,
+ XMaxYMax
+};
+
+enum class AspectRatioMeetOrSlice
+{
+ Meet,
+ Slice
+};
+
struct SvgDocNode
{
float w;
@@ -155,7 +175,8 @@ struct SvgDocNode
float vh;
SvgNode* defs;
SvgNode* style;
- bool preserveAspect;
+ AspectRatioAlign align;
+ AspectRatioMeetOrSlice meetOrSlice;
};
struct SvgGNode
@@ -171,7 +192,8 @@ struct SvgSymbolNode
{
float w, h;
float vx, vy, vw, vh;
- bool preserveAspect;
+ AspectRatioAlign align;
+ AspectRatioMeetOrSlice meetOrSlice;
bool overflowVisible;
bool hasViewBox;
bool hasWidth;
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
index a3f34fd46b..4cb4ffdaeb 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
@@ -49,9 +49,9 @@
*/
+#include "tvgMath.h" /* to include math.h before cstring */
#include <cstring>
#include <string>
-#include "tvgMath.h"
#include "tvgSvgLoaderCommon.h"
#include "tvgSvgSceneBuilder.h"
#include "tvgSvgPath.h"
@@ -68,7 +68,7 @@ struct Box
static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
-static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, bool* isMaskWhite = nullptr);
+static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr);
static inline bool _isGroupType(SvgNodeType type)
@@ -282,7 +282,7 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox
node->style->mask.applying = true;
bool isMaskWhite = true;
- auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true, &isMaskWhite);
+ auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true, 0, &isMaskWhite);
if (comp) {
if (node->transform) comp->transform(*node->transform);
@@ -560,10 +560,84 @@ static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, con
}
-static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool* isMaskWhite)
+static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, float width, float height, const Box& box)
+{
+ auto sx = width / box.w;
+ auto sy = height / box.h;
+ auto tvx = box.x * sx;
+ auto tvy = box.y * sy;
+
+ if (align == AspectRatioAlign::None)
+ return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+
+ //Scale
+ if (meetOrSlice == AspectRatioMeetOrSlice::Meet) {
+ if (sx < sy) sy = sx;
+ else sx = sy;
+ } else {
+ if (sx < sy) sx = sy;
+ else sy = sx;
+ }
+
+ //Align
+ tvx = box.x * sx;
+ tvy = box.y * sy;
+ auto tvw = box.w * sx;
+ auto tvh = box.h * sy;
+
+ switch (align) {
+ case AspectRatioAlign::XMinYMin: {
+ break;
+ }
+ case AspectRatioAlign::XMidYMin: {
+ tvx -= (width - tvw) * 0.5f;
+ break;
+ }
+ case AspectRatioAlign::XMaxYMin: {
+ tvx -= width - tvw;
+ break;
+ }
+ case AspectRatioAlign::XMinYMid: {
+ tvy -= (height - tvh) * 0.5f;
+ break;
+ }
+ case AspectRatioAlign::XMidYMid: {
+ tvx -= (width - tvw) * 0.5f;
+ tvy -= (height - tvh) * 0.5f;
+ break;
+ }
+ case AspectRatioAlign::XMaxYMid: {
+ tvx -= width - tvw;
+ tvy -= (height - tvh) * 0.5f;
+ break;
+ }
+ case AspectRatioAlign::XMinYMax: {
+ tvy -= height - tvh;
+ break;
+ }
+ case AspectRatioAlign::XMidYMax: {
+ tvx -= (width - tvw) * 0.5f;
+ tvy -= height - tvh;
+ break;
+ }
+ case AspectRatioAlign::XMaxYMax: {
+ tvx -= width - tvw;
+ tvy -= height - tvh;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+}
+
+
+static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
{
unique_ptr<Scene> finalScene;
- auto scene = _sceneBuildHelper(node, vBox, svgPath, false, isMaskWhite);
+ auto scene = _sceneBuildHelper(node, vBox, svgPath, false, depth + 1, isMaskWhite);
// mUseTransform = mUseTransform * mTranslate
Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
@@ -585,20 +659,8 @@ static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, c
Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) {
- auto sx = width / vw;
- auto sy = height / vh;
- if (symbol.preserveAspect) {
- if (sx < sy) sy = sx;
- else sx = sy;
- }
-
- auto tvx = symbol.vx * sx;
- auto tvy = symbol.vy * sy;
- auto tvw = vw * sx;
- auto tvh = vh * sy;
- tvy -= (symbol.h - tvh) * 0.5f;
- tvx -= (symbol.w - tvw) * 0.5f;
- mViewBox = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+ Box box = {symbol.vx, symbol.vy, vw, vh};
+ mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
} else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) {
mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
}
@@ -642,8 +704,15 @@ static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, c
}
-static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, bool* isMaskWhite)
+static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite)
{
+ /* Exception handling: Prevent invalid SVG data input.
+ The size is the arbitrary value, we need an experimental size. */
+ if (depth > 2192) {
+ TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth);
+ return nullptr;
+ }
+
if (_isGroupType(node->type) || mask) {
auto scene = Scene::gen();
// For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper()
@@ -654,12 +723,15 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
if (_isGroupType((*child)->type)) {
if ((*child)->type == SvgNodeType::Use)
- scene->push(_useBuildHelper(*child, vBox, svgPath, isMaskWhite));
+ scene->push(_useBuildHelper(*child, vBox, svgPath, depth + 1, isMaskWhite));
else
- scene->push(_sceneBuildHelper(*child, vBox, svgPath, false, isMaskWhite));
+ scene->push(_sceneBuildHelper(*child, vBox, svgPath, false, depth + 1, isMaskWhite));
} else if ((*child)->type == SvgNodeType::Image) {
auto image = _imageBuildHelper(*child, vBox, svgPath);
- if (image) scene->push(move(image));
+ if (image) {
+ scene->push(move(image));
+ if (isMaskWhite) *isMaskWhite = false;
+ }
} else if ((*child)->type != SvgNodeType::Mask) {
auto shape = _shapeBuildHelper(*child, vBox, svgPath);
if (shape) {
@@ -688,36 +760,18 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
/* External Class Implementation */
/************************************************************************/
-unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath)
{
+ //TODO: aspect ratio is valid only if viewBox was set
+
if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
Box vBox = {vx, vy, vw, vh};
- auto docNode = _sceneBuildHelper(node, vBox, svgPath, false);
+ auto docNode = _sceneBuildHelper(node, vBox, svgPath, false, 0);
if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
- auto sx = w / vw;
- auto sy = h / vh;
-
- if (preserveAspect) {
- //Scale
- auto scale = sx < sy ? sx : sy;
- docNode->scale(scale);
- //Align
- auto tvx = vx * scale;
- auto tvy = vy * scale;
- auto tvw = vw * scale;
- auto tvh = vh * scale;
- tvx -= (w - tvw) * 0.5f;
- tvy -= (h - tvh) * 0.5f;
- docNode->translate(-tvx, -tvy);
- } else {
- //Align
- auto tvx = vx * sx;
- auto tvy = vy * sy;
- Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
- docNode->transform(m);
- }
+ Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
+ docNode->transform(m);
} else if (!mathZero(vx) || !mathZero(vy)) {
docNode->translate(-vx, -vy);
}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
index cecbbf02a8..311f3c80e6 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
@@ -25,6 +25,6 @@
#include "tvgCommon.h"
-unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath);
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath);
#endif //_TVG_SVG_SCENE_BUILDER_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
index c373da2dd5..231badd27d 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
@@ -26,10 +26,10 @@
#ifdef _WIN32
#include <malloc.h>
-#elif __FreeBSD__
- #include<stdlib.h>
-#else
+#elif defined(__linux__)
#include <alloca.h>
+#else
+ #include <stdlib.h>
#endif
#include "tvgXmlParser.h"
diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp b/thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp
index 62a75ecd3d..01a39b6e17 100644
--- a/thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp
+++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp
@@ -23,10 +23,10 @@
#ifdef _WIN32
#include <malloc.h>
-#elif __FreeBSD__
- #include<stdlib.h>
-#else
+#elif defined(__linux__)
#include <alloca.h>
+#else
+ #include <stdlib.h>
#endif
#include "tvgTvgCommon.h"
diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
index adf85836c3..57a21dcce1 100644
--- a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
+++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
@@ -28,10 +28,10 @@
#ifdef _WIN32
#include <malloc.h>
-#elif __FreeBSD__
- #include<stdlib.h>
-#else
+#elif defined(__linux__)
#include <alloca.h>
+#else
+ #include <stdlib.h>
#endif
static FILE* _fopen(const char* filename, const char* mode)
diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh
index f2fd2a80e4..8cccc947ce 100755
--- a/thirdparty/thorvg/update-thorvg.sh
+++ b/thirdparty/thorvg/update-thorvg.sh
@@ -1,4 +1,4 @@
-VERSION=0.8.2
+VERSION=0.8.3
rm -rf AUTHORS inc LICENSE src *.zip
curl -L -O https://github.com/Samsung/thorvg/archive/v$VERSION.zip
bsdtar --strip-components=1 -xvf *.zip
@@ -26,3 +26,6 @@ cat << EOF > inc/config.h
#define THORVG_VERSION_STRING "$VERSION"
#endif
EOF
+for source in $(find ./ -type f \( -iname \*.h -o -iname \*.cpp \)); do
+ sed -i -e '$a\' $source
+done