diff options
26 files changed, 406 insertions, 270 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e7e88e95d7..bd4eb906c0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -114,7 +114,6 @@ doc_classes/* @godotengine/documentation /modules/gdscript/ @godotengine/gdscript /modules/jsonrpc/ @godotengine/gdscript /modules/mono/ @godotengine/mono -/modules/visual_script/ @godotengine/visualscript ## Text /modules/freetype/ @godotengine/buildsystem diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml index 19cd9d21d7..331862ebfa 100644 --- a/doc/classes/AStarGrid2D.xml +++ b/doc/classes/AStarGrid2D.xml @@ -6,14 +6,24 @@ <description> Compared to [AStar2D] you don't need to manually create points or connect them together. It also supports multiple type of heuristics and modes for diagonal movement. This class also provides a jumping mode which is faster to calculate than without it in the [AStar2D] class. In contrast to [AStar2D], you only need set the [member size] of the grid, optionally set the [member cell_size] and then call the [method update] method: - [codeblock] + [codeblocks] + [gdscript] var astar_grid = AStarGrid2D.new() astar_grid.size = Vector2i(32, 32) astar_grid.cell_size = Vector2(16, 16) astar_grid.update() print(astar_grid.get_id_path(Vector2i(0, 0), Vector2i(3, 4))) # prints (0, 0), (1, 1), (2, 2), (3, 3), (3, 4) print(astar_grid.get_point_path(Vector2i(0, 0), Vector2i(3, 4))) # prints (0, 0), (16, 16), (32, 32), (48, 48), (48, 64) - [/codeblock] + [/gdscript] + [csharp] + AStarGrid2D astarGrid = new AStarGrid2D(); + astarGrid.Size = new Vector2i(32, 32); + astarGrid.CellSize = new Vector2i(16, 16); + astarGrid.Update(); + GD.Print(astarGrid.GetIdPath(Vector2i.Zero, new Vector2i(3, 4))); // prints (0, 0), (1, 1), (2, 2), (3, 3), (3, 4) + GD.Print(astarGrid.GetPointPath(Vector2i.Zero, new Vector2i(3, 4))); // prints (0, 0), (16, 16), (32, 32), (48, 48), (48, 64) + [/csharp] + [/codeblocks] </description> <tutorials> </tutorials> diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml index ddb98030eb..de2e32b17b 100644 --- a/doc/classes/DirAccess.xml +++ b/doc/classes/DirAccess.xml @@ -27,7 +27,7 @@ [csharp] public void DirContents(string path) { - var dir = DirAccess.Open(path); + using var dir = DirAccess.Open(path); if (dir != null) { dir.ListDirBegin(); diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 329cd3fe63..6007128965 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -405,7 +405,7 @@ If [code]true[/code], allows panning by holding down [kbd]Space[/kbd] in the 2D editor viewport (in addition to panning with the middle or right mouse buttons). If [code]false[/code], the left mouse button must be held down while holding down [kbd]Space[/kbd] to pan in the 2D editor viewport. </member> <member name="editors/panning/sub_editors_panning_scheme" type="int" setter="" getter=""> - Controls whether the mouse wheel scroll zooms or pans in subeditors. The list of affected subeditors is: animation blend tree editor, [Polygon2D] editor, tileset editor, texture region editor, visual shader editor and visual script editor. See also [member editors/panning/2d_editor_panning_scheme] and [member editors/panning/animation_editors_panning_scheme]. + Controls whether the mouse wheel scroll zooms or pans in subeditors. The list of affected subeditors is: animation blend tree editor, [Polygon2D] editor, tileset editor, texture region editor and visual shader editor. See also [member editors/panning/2d_editor_panning_scheme] and [member editors/panning/animation_editors_panning_scheme]. </member> <member name="editors/panning/warped_mouse_panning" type="bool" setter="" getter=""> If [code]true[/code], warps the mouse around the 2D viewport while panning in the 2D editor. This makes it possible to pan over a large area without having to exit panning then mouse the mouse back constantly. @@ -424,10 +424,10 @@ [b]Note:[/b] Only effective if [member editors/tiles_editor/display_grid] is [code]true[/code]. </member> <member name="editors/visual_editors/lines_curvature" type="float" setter="" getter=""> - The curvature to use for connection lines in the visual script and visual shader editors. Higher values will make connection lines appear more curved, with values above [code]0.5[/code] resulting in more "angular" turns in the middle of connection lines. + The curvature to use for connection lines in the visual shader editor. Higher values will make connection lines appear more curved, with values above [code]0.5[/code] resulting in more "angular" turns in the middle of connection lines. </member> <member name="editors/visual_editors/minimap_opacity" type="float" setter="" getter=""> - The opacity of the minimap displayed in the bottom-right corner of the visual script and visual shader editors. + The opacity of the minimap displayed in the bottom-right corner of the visual shader editor. </member> <member name="editors/visual_editors/visual_shader/port_preview_size" type="int" setter="" getter=""> The size to use for port previews in the visual shader uniforms (toggled by clicking the "eye" icon next to an output). The value is defined in pixels at 100% zoom, and will scale with zoom automatically. diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 301a3e55fb..ecf3d87a70 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -14,12 +14,20 @@ <description> Returns the name of the CPU architecture the Godot binary was built for. Possible return values are [code]x86_64[/code], [code]x86_32[/code], [code]arm64[/code], [code]armv7[/code], [code]rv64[/code], [code]riscv[/code], [code]ppc64[/code], [code]ppc[/code], [code]wasm64[/code] and [code]wasm32[/code]. To detect whether the current CPU architecture is 64-bit, you can use the fact that all 64-bit architecture names have [code]64[/code] in their name: - [codeblock] + [codeblocks] + [gdscript] if "64" in Engine.get_architecture_name(): print("Running on 64-bit CPU.") else: print("Running on 32-bit CPU.") - [/codeblock] + [/gdscript] + [csharp] + if (Engine.GetArchitectureName().Contains("64")) + GD.Print("Running on 64-bit CPU."); + else + GD.Print("Running on 32-bit CPU."); + [/csharp] + [/codeblocks] [b]Note:[/b] [method get_architecture_name] does [i]not[/i] return the name of the host CPU architecture. For example, if running an x86_32 Godot binary on a x86_64 system, the returned value will be [code]x86_32[/code]. </description> </method> @@ -83,11 +91,24 @@ <description> Returns the total number of frames passed since engine initialization which is advanced on each [b]physics frame[/b]. See also [method get_process_frames]. [method get_physics_frames] can be used to run expensive logic less often without relying on a [Timer]: - [codeblock] + [codeblocks] + [gdscript] func _physics_process(_delta): if Engine.get_physics_frames() % 2 == 0: pass # Run expensive logic only once every 2 physics frames here. - [/codeblock] + [/gdscript] + [csharp] + public override void _PhysicsProcess(double delta) + { + base._PhysicsProcess(delta); + + if (Engine.GetPhysicsFrames() % 2 == 0) + { + // Run expensive logic only once every 2 physics frames here. + } + } + [/csharp] + [/codeblocks] </description> </method> <method name="get_physics_interpolation_fraction" qualifiers="const"> @@ -101,11 +122,24 @@ <description> Returns the total number of frames passed since engine initialization which is advanced on each [b]process frame[/b], regardless of whether the render loop is enabled. See also [method get_frames_drawn] and [method get_physics_frames]. [method get_process_frames] can be used to run expensive logic less often without relying on a [Timer]: - [codeblock] + [codeblocks] + [gdscript] func _process(_delta): if Engine.get_process_frames() % 2 == 0: pass # Run expensive logic only once every 2 process (render) frames here. - [/codeblock] + [/gdscript] + [csharp] + public override void _Process(double delta) + { + base._Process(delta); + + if (Engine.GetProcessFrames() % 2 == 0) + { + // Run expensive logic only once every 2 physics frames here. + } + } + [/csharp] + [/codeblocks] </description> </method> <method name="get_script_language" qualifiers="const"> @@ -182,12 +216,20 @@ <return type="bool" /> <description> Returns [code]true[/code] if the script is currently running inside the editor, [code]false[/code] otherwise. This is useful for [code]@tool[/code] scripts to conditionally draw editor helpers, or prevent accidentally running "game" code that would affect the scene state while in the editor: - [codeblock] + [codeblocks] + [gdscript] if Engine.is_editor_hint(): draw_gizmos() else: simulate_physics() - [/codeblock] + [/gdscript] + [csharp] + if (Engine.IsEditorHint()) + DrawGizmos(); + else + SimulatePhysics(); + [/csharp] + [/codeblocks] See [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]Running code in the editor[/url] in the documentation for more information. [b]Note:[/b] To detect whether the script is run from an editor [i]build[/i] (e.g. when pressing [kbd]F5[/kbd]), use [method OS.has_feature] with the [code]"editor"[/code] argument instead. [code]OS.has_feature("editor")[/code] will evaluate to [code]true[/code] both when the code is running in the editor and when running the project from the editor, but it will evaluate to [code]false[/code] when the code is run from an exported project. </description> diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 61377fb13a..32f6a1dd3e 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -20,24 +20,30 @@ [csharp] public void Save(string content) { - var file = FileAccess.Open("user://save_game.dat", File.ModeFlags.Write); + using var file = FileAccess.Open("user://save_game.dat", File.ModeFlags.Write); file.StoreString(content); } public string Load() { - var file = FileAccess.Open("user://save_game.dat", File.ModeFlags.Read); + using var file = FileAccess.Open("user://save_game.dat", File.ModeFlags.Read); string content = file.GetAsText(); return content; } [/csharp] [/codeblocks] In the example above, the file will be saved in the user data folder as specified in the [url=$DOCS_URL/tutorials/io/data_paths.html]Data paths[/url] documentation. - There is no method to close a file in order to free it from use. Instead, [FileAccess] will close when it's freed, which happens when it goes out of scope or when it gets assigned with [code]null[/code]. - [codeblock] + There is no method to close a file in order to free it from use. Instead, [FileAccess] will close when it's freed, which happens when it goes out of scope or when it gets assigned with [code]null[/code]. In C# the reference must be disposed after we are done using it, this can be done with the [code]using[/code] statement or calling the [code]Dispose[/code] method directly. + [codeblocks] + [gdscript] var file = FileAccess.open("res://something") # File is opened and locked for use. file = null # File is closed. - [/codeblock] + [/gdscript] + [csharp] + using var file = FileAccess.Open("res://something"); // File is opened and locked for use. + // The using statement calls Dispose when going out of scope. + [/csharp] + [/codeblocks] [b]Note:[/b] To access project resources once exported, it is recommended to use [ResourceLoader] instead of the [FileAccess] API, as some files are converted to engine-specific formats and their original source files might not be present in the exported PCK package. [b]Note:[/b] Files are automatically closed only if the process exits "normally" (such as by clicking the window manager's close button or pressing [b]Alt + F4[/b]). If you stop the project execution by pressing [b]F8[/b] while the project is running, the file won't be closed as the game process will be killed. You can work around this by calling [method flush] at regular intervals. </description> diff --git a/doc/classes/Font.xml b/doc/classes/Font.xml index ad3a16afbb..6a42b62bcf 100644 --- a/doc/classes/Font.xml +++ b/doc/classes/Font.xml @@ -228,9 +228,15 @@ <description> Returns the size of a bounding box of a single-line string, taking kerning and advance into account. See also [method get_multiline_string_size] and [method draw_string]. For example, to get the string size as displayed by a single-line Label, use: - [codeblock] + [codeblocks] + [gdscript] var string_size = $Label.get_theme_font("font").get_string_size($Label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, $Label.get_theme_font_size("font_size")) - [/codeblock] + [/gdscript] + [csharp] + Label label = GetNode<Label>("Label"); + Vector2 stringSize = label.GetThemeFont("font").GetStringSize(label.Text, HorizontalAlignment.Left, -1, label.GetThemeFontSize("font_size")); + [/csharp] + [/codeblocks] [b]Note:[/b] Real height of the string is context-dependent and can be significantly different from the value returned by [method get_height]. </description> </method> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index d8ad65082f..d9732da3a3 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -179,9 +179,14 @@ <return type="Tween" /> <description> Creates a new [Tween] and binds it to this node. This is equivalent of doing: - [codeblock] + [codeblocks] + [gdscript] get_tree().create_tween().bind_node(self) - [/codeblock] + [/gdscript] + [csharp] + GetTree().CreateTween().BindNode(this); + [/csharp] + [/codeblocks] </description> </method> <method name="duplicate" qualifiers="const"> @@ -267,13 +272,24 @@ Returns an array listing the groups that the node is a member of. [b]Note:[/b] For performance reasons, the order of node groups is [i]not[/i] guaranteed. The order of node groups should not be relied upon as it can vary across project runs. [b]Note:[/b] The engine uses some group names internally (all starting with an underscore). To avoid conflicts with internal groups, do not add custom groups whose name starts with an underscore. To exclude internal groups while looping over [method get_groups], use the following snippet: - [codeblock] + [codeblocks] + [gdscript] # Stores the node's non-internal groups only (as an array of Strings). var non_internal_groups = [] for group in get_groups(): if not group.begins_with("_"): non_internal_groups.push_back(group) - [/codeblock] + [/gdscript] + [csharp] + // Stores the node's non-internal groups only (as a List of strings). + List<string> nonInternalGroups = new List<string>(); + foreach (string group in GetGroups()) + { + if (!group.BeginsWith("_")) + nonInternalGroups.Add(group); + } + [/csharp] + [/codeblocks] </description> </method> <method name="get_index" qualifiers="const"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index d920c45de4..be7bacd994 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -525,10 +525,16 @@ Moves the file or directory to the system's recycle bin. See also [method DirAccess.remove]. The method takes only global paths, so you may need to use [method ProjectSettings.globalize_path]. Do not use it for files in [code]res://[/code] as it will not work in exported project. [b]Note:[/b] If the user has disabled the recycle bin on their system, the file will be permanently deleted instead. - [codeblock] + [codeblocks] + [gdscript] var file_to_remove = "user://slot1.sav" OS.move_to_trash(ProjectSettings.globalize_path(file_to_remove)) - [/codeblock] + [/gdscript] + [csharp] + var fileToRemove = "user://slot1.sav"; + OS.MoveToTrash(ProjectSettings.GlobalizePath(fileToRemove)); + [/csharp] + [/codeblocks] </description> </method> <method name="open_midi_inputs"> diff --git a/doc/classes/ScriptEditorBase.xml b/doc/classes/ScriptEditorBase.xml index c365e0971b..68834839f3 100644 --- a/doc/classes/ScriptEditorBase.xml +++ b/doc/classes/ScriptEditorBase.xml @@ -19,14 +19,14 @@ <method name="get_base_editor" qualifiers="const"> <return type="Control" /> <description> - Returns the underlying [Control] used for editing scripts. This can be either [CodeEdit] (for text scripts) or [GraphEdit] (for visual scripts). + Returns the underlying [Control] used for editing scripts. For text scripts, this is a [CodeEdit]. </description> </method> </methods> <signals> <signal name="edited_script_changed"> <description> - Emitted after script validation. For visual scripts on modification. + Emitted after script validation. </description> </signal> <signal name="go_to_help"> @@ -37,13 +37,13 @@ </signal> <signal name="name_changed"> <description> - Emitted after script validation or when the edited resource has changed. Not used by visual scripts. + Emitted after script validation or when the edited resource has changed. </description> </signal> <signal name="replace_in_files_requested"> <param index="0" name="text" type="String" /> <description> - Emitted when the user request to find and replace text in the file system. Not used by visual scripts. + Emitted when the user request to find and replace text in the file system. </description> </signal> <signal name="request_help"> @@ -67,7 +67,7 @@ <signal name="search_in_files_requested"> <param index="0" name="text" type="String" /> <description> - Emitted when the user request to search text in the file system. Not used by visual scripts. + Emitted when the user request to search text in the file system. </description> </signal> </signals> diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index 5186972477..acf900ae55 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -8,42 +8,85 @@ [Tween] is more suited than [AnimationPlayer] for animations where you don't know the final values in advance. For example, interpolating a dynamically-chosen camera zoom value is best done with a [Tween]; it would be difficult to do the same thing with an [AnimationPlayer] node. Tweens are also more light-weight than [AnimationPlayer], so they are very much suited for simple animations or general tasks that don't require visual tweaking provided by the editor. They can be used in a fire-and-forget manner for some logic that normally would be done by code. You can e.g. make something shoot periodically by using a looped [CallbackTweener] with a delay. A [Tween] can be created by using either [method SceneTree.create_tween] or [method Node.create_tween]. [Tween]s created manually (i.e. by using [code]Tween.new()[/code]) are invalid and can't be used for tweening values. A tween animation is created by adding [Tweener]s to the [Tween] object, using [method tween_property], [method tween_interval], [method tween_callback] or [method tween_method]: - [codeblock] + [codeblocks] + [gdscript] var tween = get_tree().create_tween() tween.tween_property($Sprite, "modulate", Color.red, 1) tween.tween_property($Sprite, "scale", Vector2(), 1) tween.tween_callback($Sprite.queue_free) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = GetTree().CreateTween(); + tween.TweenProperty(GetNode("Sprite"), "modulate", Colors.Red, 1.0f); + tween.TweenProperty(GetNode("Sprite"), "scale", Vector2.Zero, 1.0f); + tween.TweenCallback(new Callable(GetNode("Sprite").QueueFree)); + [/csharp] + [/codeblocks] This sequence will make the [code]$Sprite[/code] node turn red, then shrink, before finally calling [method Node.queue_free] to free the sprite. [Tweener]s are executed one after another by default. This behavior can be changed using [method parallel] and [method set_parallel]. When a [Tweener] is created with one of the [code]tween_*[/code] methods, a chained method call can be used to tweak the properties of this [Tweener]. For example, if you want to set a different transition type in the above example, you can use [method set_trans]: - [codeblock] + [codeblocks] + [gdscript] var tween = get_tree().create_tween() tween.tween_property($Sprite, "modulate", Color.red, 1).set_trans(Tween.TRANS_SINE) tween.tween_property($Sprite, "scale", Vector2(), 1).set_trans(Tween.TRANS_BOUNCE) tween.tween_callback($Sprite.queue_free) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = GetTree().CreateTween(); + tween.TweenProperty(GetNode("Sprite"), "modulate", Colors.Red, 1.0f).SetTrans(Tween.TransitionType.Sine); + tween.TweenProperty(GetNode("Sprite"), "scale", Vector2.Zero, 1.0f).SetTrans(Tween.TransitionType.Bounce); + tween.TweenCallback(new Callable(GetNode("Sprite").QueueFree)); + [/csharp] + [/codeblocks] Most of the [Tween] methods can be chained this way too. In the following example the [Tween] is bound to the running script's node and a default transition is set for its [Tweener]s: - [codeblock] + [codeblocks] + [gdscript] var tween = get_tree().create_tween().bind_node(self).set_trans(Tween.TRANS_ELASTIC) tween.tween_property($Sprite, "modulate", Color.red, 1) tween.tween_property($Sprite, "scale", Vector2(), 1) tween.tween_callback($Sprite.queue_free) - [/codeblock] + [/gdscript] + [csharp] + var tween = GetTree().CreateTween().BindNode(this).SetTrans(Tween.TransitionType.Elastic); + tween.TweenProperty(GetNode("Sprite"), "modulate", Colors.Red, 1.0f); + tween.TweenProperty(GetNode("Sprite"), "scale", Vector2.Zero, 1.0f); + tween.TweenCallback(new Callable(GetNode("Sprite").QueueFree)); + [/csharp] + [/codeblocks] Another interesting use for [Tween]s is animating arbitrary sets of objects: - [codeblock] + [codeblocks] + [gdscript] var tween = create_tween() for sprite in get_children(): tween.tween_property(sprite, "position", Vector2(0, 0), 1) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = CreateTween(); + foreach (Node sprite in GetChildren()) + tween.TweenProperty(sprite, "position", Vector2.Zero, 1.0f); + [/csharp] + [/codeblocks] In the example above, all children of a node are moved one after another to position (0, 0). You should avoid using more than one [Tween] per object's property. If two or more tweens animate one property at the same time, the last one created will take priority and assign the final value. If you want to interrupt and restart an animation, consider assigning the [Tween] to a variable: - [codeblock] + [codeblocks] + [gdscript] var tween func animate(): if tween: tween.kill() # Abort the previous animation. tween = create_tween() - [/codeblock] + [/gdscript] + [csharp] + private Tween tween; + + public void Animate() + { + if (tween != null) + tween.Kill(); // Abort the previous animation + tween = CreateTween(); + } + [/csharp] + [/codeblocks] Some [Tweener]s use transitions and eases. The first accepts a [enum TransitionType] constant, and refers to the way the timing of the animation is handled (see [url=https://easings.net/]easings.net[/url] for some examples). The second accepts an [enum EaseType] constant, and controls where the [code]trans_type[/code] is applied to the interpolation (in the beginning, the end, or both). If you don't know which transition and easing to pick, you can try different [enum TransitionType] constants with [constant EASE_IN_OUT], and use the one that looks best. [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/tween_cheatsheet.png]Tween easing and transition types cheatsheet[/url] [b]Note:[/b] All [Tween]s will automatically start by default. To prevent a [Tween] from autostarting, you can call [method stop] immediately after it is created. @@ -64,12 +107,20 @@ <return type="Tween" /> <description> Used to chain two [Tweener]s after [method set_parallel] is called with [code]true[/code]. - [codeblock] + [codeblocks] + [gdscript] var tween = create_tween().set_parallel(true) tween.tween_property(...) tween.tween_property(...) # Will run parallelly with above. tween.chain().tween_property(...) # Will run after two above are finished. - [/codeblock] + [/gdscript] + [csharp] + Tween tween = CreateTween().SetParallel(true); + tween.TweenProperty(...); + tween.TweenProperty(...); // Will run parallelly with above. + tween.Chain().TweenProperty(...); // Will run after two above are finished. + [/csharp] + [/codeblocks] </description> </method> <method name="custom_step"> @@ -127,12 +178,20 @@ <return type="Tween" /> <description> Makes the next [Tweener] run parallelly to the previous one. Example: - [codeblock] + [codeblocks] + [gdscript] var tween = create_tween() tween.tween_property(...) tween.parallel().tween_property(...) tween.parallel().tween_property(...) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = CreateTween(); + tween.TweenProperty(...); + tween.Parallel().TweenProperty(...); + tween.Parallel().TweenProperty(...); + [/csharp] + [/codeblocks] All [Tweener]s in the example will run at the same time. You can make the [Tween] parallel by default by using [method set_parallel]. </description> @@ -214,16 +273,30 @@ <description> Creates and appends a [CallbackTweener]. This method can be used to call an arbitrary method in any object. Use [method Callable.bind] to bind additional arguments for the call. Example: object that keeps shooting every 1 second. - [codeblock] + [codeblocks] + [gdscript] var tween = get_tree().create_tween().set_loops() tween.tween_callback(shoot).set_delay(1) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = GetTree().CreateTween().SetLoops(); + tween.TweenCallback(new Callable(Shoot)).SetDelay(1.0f); + [/csharp] + [/codeblocks] Example: turning a sprite red and then blue, with 2 second delay. - [codeblock] + [codeblocks] + [gdscript] var tween = get_tree().create_tween() tween.tween_callback($Sprite.set_modulate.bind(Color.red)).set_delay(2) tween.tween_callback($Sprite.set_modulate.bind(Color.blue)).set_delay(2) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = GetTree().CreateTween(); + Sprite2D sprite = GetNode<Sprite2D>("Sprite"); + tween.TweenCallback(new Callable(() => sprite.Modulate = Colors.Red)).SetDelay(2.0f); + tween.TweenCallback(new Callable(() => sprite.Modulate = Colors.Blue)).SetDelay(2.0f); + [/csharp] + [/codeblocks] </description> </method> <method name="tween_interval"> @@ -232,13 +305,21 @@ <description> Creates and appends an [IntervalTweener]. This method can be used to create delays in the tween animation, as an alternative to using the delay in other [Tweener]s, or when there's no animation (in which case the [Tween] acts as a timer). [param time] is the length of the interval, in seconds. Example: creating an interval in code execution. - [codeblock] + [codeblocks] + [gdscript] # ... some code await create_tween().tween_interval(2).finished # ... more code - [/codeblock] + [/gdscript] + [csharp] + // ... some code + await ToSignal(CreateTween().TweenInterval(2.0f), Tween.SignalName.Finished); + // ... more code + [/csharp] + [/codeblocks] Example: creating an object that moves back and forth and jumps every few seconds. - [codeblock] + [codeblocks] + [gdscript] var tween = create_tween().set_loops() tween.tween_property($Sprite, "position:x", 200.0, 1).as_relative() tween.tween_callback(jump) @@ -246,7 +327,17 @@ tween.tween_property($Sprite, "position:x", -200.0, 1).as_relative() tween.tween_callback(jump) tween.tween_interval(2) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = CreateTween().SetLoops(); + tween.TweenProperty(GetNode("Sprite"), "position:x", 200.0f, 1.0f).AsRelative(); + tween.TweenCallback(new Callable(Jump)); + tween.TweenInterval(2.0f); + tween.TweenProperty(GetNode("Sprite"), "position:x", -200.0f, 1.0f).AsRelative(); + tween.TweenCallback(new Callable(Jump)); + tween.TweenInterval(2.0f); + [/csharp] + [/codeblocks] </description> </method> <method name="tween_method"> @@ -258,19 +349,41 @@ <description> Creates and appends a [MethodTweener]. This method is similar to a combination of [method tween_callback] and [method tween_property]. It calls a method over time with a tweened value provided as an argument. The value is tweened between [param from] and [param to] over the time specified by [param duration], in seconds. Use [method Callable.bind] to bind additional arguments for the call. You can use [method MethodTweener.set_ease] and [method MethodTweener.set_trans] to tweak the easing and transition of the value or [method MethodTweener.set_delay] to delay the tweening. Example: making a 3D object look from one point to another point. - [codeblock] + [codeblocks] + [gdscript] var tween = create_tween() tween.tween_method(look_at.bind(Vector3.UP), Vector3(-1, 0, -1), Vector3(1, 0, -1), 1) # The look_at() method takes up vector as second argument. - [/codeblock] + [/gdscript] + [csharp] + Tween tween = CreateTween(); + tween.TweenMethod(new Callable(() => LookAt(Vector3.Up)), new Vector3(-1.0f, 0.0f, -1.0f), new Vector3(1.0f, 0.0f, -1.0f), 1.0f); // The LookAt() method takes up vector as second argument. + [/csharp] + [/codeblocks] Example: setting a text of a [Label], using an intermediate method and after a delay. - [codeblock] + [codeblocks] + [gdscript] func _ready(): var tween = create_tween() tween.tween_method(set_label_text, 0, 10, 1).set_delay(1) func set_label_text(value: int): $Label.text = "Counting " + str(value) - [/codeblock] + [/gdscript] + [csharp] + public override void _Ready() + { + base._Ready(); + + Tween tween = CreateTween(); + tween.TweenMethod(new Callable(SetLabelText), 0.0f, 10.0f, 1.0f).SetDelay(1.0f); + } + + private void SetLabelText(int value) + { + GetNode<Label>("Label").Text = $"Counting {value}"; + } + [/csharp] + [/codeblocks] </description> </method> <method name="tween_property"> @@ -281,19 +394,33 @@ <param index="3" name="duration" type="float" /> <description> Creates and appends a [PropertyTweener]. This method tweens a [param property] of an [param object] between an initial value and [param final_val] in a span of time equal to [param duration], in seconds. The initial value by default is the property's value at the time the tweening of the [PropertyTweener] starts. For example: - [codeblock] + [codeblocks] + [gdscript] var tween = create_tween() tween.tween_property($Sprite, "position", Vector2(100, 200), 1) tween.tween_property($Sprite, "position", Vector2(200, 300), 1) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = CreateTween(); + tween.TweenProperty(GetNode("Sprite"), "position", new Vector2(100.0f, 200.0f), 1.0f); + tween.TweenProperty(GetNode("Sprite"), "position", new Vector2(200.0f, 300.0f), 1.0f); + [/csharp] + [/codeblocks] will move the sprite to position (100, 200) and then to (200, 300). If you use [method PropertyTweener.from] or [method PropertyTweener.from_current], the starting position will be overwritten by the given value instead. See other methods in [PropertyTweener] to see how the tweening can be tweaked further. [b]Note:[/b] You can find the correct property name by hovering over the property in the Inspector. You can also provide the components of a property directly by using [code]"property:component"[/code] (eg. [code]position:x[/code]), where it would only apply to that particular component. Example: moving object twice from the same position, with different transition types. - [codeblock] + [codeblocks] + [gdscript] var tween = create_tween() tween.tween_property($Sprite, "position", Vector2.RIGHT * 300, 1).as_relative().set_trans(Tween.TRANS_SINE) tween.tween_property($Sprite, "position", Vector2.RIGHT * 300, 1).as_relative().from_current().set_trans(Tween.TRANS_EXPO) - [/codeblock] + [/gdscript] + [csharp] + Tween tween = CreateTween(); + tween.TweenProperty(GetNode("Sprite"), "position", Vector2.Right * 300.0f, 1.0f).AsRelative().SetTrans(Tween.TransitionType.Sine); + tween.TweenProperty(GetNode("Sprite"), "position", Vector2.Right * 300.0f, 1.0f).AsRelative().FromCurrent().SetTrans(Tween.TransitionType.Expo); + [/csharp] + [/codeblocks] </description> </method> </methods> diff --git a/editor/icons/VisualScriptComment.svg b/editor/icons/VisualScriptComment.svg deleted file mode 100644 index 3887853b58..0000000000 --- a/editor/icons/VisualScriptComment.svg +++ /dev/null @@ -1 +0,0 @@ -<svg height="14" viewBox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m3 1v2h-2v2h2v4h-2v2h2v2h2v-2h4v2h2v-2h2v-2h-2v-4h2v-2h-2v-2h-2v2h-4v-2zm2 4h4v4h-4z" fill="#e0e0e0"/></svg> diff --git a/editor/icons/VisualScriptExpression.svg b/editor/icons/VisualScriptExpression.svg deleted file mode 100644 index d6a3c2d9a8..0000000000 --- a/editor/icons/VisualScriptExpression.svg +++ /dev/null @@ -1 +0,0 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="m4.859536 3.0412379c-2.0539867 0-3.7190721 1.6650852-3.7190721 3.719072v6.1984521h2.4793814v-2.479381h2.4793814v-2.4793803h-2.4793814v-1.2396908c0-.6846622.5550285-1.2396907 1.2396907-1.2396907h1.2396907v-2.4793813z"/><path d="m7.5889175 3.0000003 2.5000005 4.9999997-2.5000005 5h2.5000005l1.135249-2.727 1.36475 2.727h2.499999l-2.499999-5 2.499999-4.9999997h-2.499999l-1.13525 2.7269998-1.364749-2.7269998zm7.4999985 9.9999997v-6.25z"/></g></svg> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 205f6c0f8f..936794976d 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -30,6 +30,12 @@ <description> </description> </method> + <method name="_get_supported_extensions" qualifiers="virtual"> + <return type="PackedStringArray" /> + <description> + Returns an array of the GLTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a GLTF file with required extensions can be loaded. + </description> + </method> <method name="_import_node" qualifiers="virtual"> <return type="int" /> <param index="0" name="state" type="GLTFState" /> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 1dbd89aed8..6c2f488c1c 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -7,6 +7,14 @@ <tutorials> </tutorials> <methods> + <method name="add_used_extension"> + <return type="void" /> + <param index="0" name="extension_name" type="String" /> + <param index="1" name="required" type="bool" /> + <description> + Appends an extension to the list of extensions used by this GLTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically. + </description> + </method> <method name="get_accessors"> <return type="GLTFAccessor[]" /> <description> diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 53cf7285f9..8d2e37be3a 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -198,7 +198,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) { } /* STEP SERIALIZE EXTENSIONS */ - err = _serialize_extensions(state); + err = _serialize_gltf_extensions(state); if (err != OK) { return Error::FAILED; } @@ -219,9 +219,9 @@ Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) { return OK; } -Error GLTFDocument::_serialize_extensions(Ref<GLTFState> state) const { - Array extensions_used; - Array extensions_required; +Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> state) const { + Vector<String> extensions_used = state->extensions_used; + Vector<String> extensions_required = state->extensions_required; if (!state->lights.is_empty()) { extensions_used.push_back("KHR_lights_punctual"); } @@ -230,9 +230,11 @@ Error GLTFDocument::_serialize_extensions(Ref<GLTFState> state) const { extensions_required.push_back("KHR_texture_transform"); } if (!extensions_used.is_empty()) { + extensions_used.sort(); state->json["extensionsUsed"] = extensions_used; } if (!extensions_required.is_empty()) { + extensions_required.sort(); state->json["extensionsRequired"] = extensions_required; } return OK; @@ -6917,12 +6919,32 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> state) { ERR_FAIL_NULL_V(state, ERR_PARSE_ERROR); - if (state->json.has("extensionsRequired") && state->json["extensionsRequired"].get_type() == Variant::ARRAY) { - Array extensions_required = state->json["extensionsRequired"]; - if (extensions_required.find("KHR_draco_mesh_compression") != -1) { - ERR_PRINT("glTF2 extension KHR_draco_mesh_compression is not supported."); - return ERR_UNAVAILABLE; + if (state->json.has("extensionsUsed")) { + Vector<String> ext_array = state->json["extensionsUsed"]; + state->extensions_used = ext_array; + } + if (state->json.has("extensionsRequired")) { + Vector<String> ext_array = state->json["extensionsRequired"]; + state->extensions_required = ext_array; + } + HashSet<String> supported_extensions; + supported_extensions.insert("KHR_lights_punctual"); + supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); + supported_extensions.insert("KHR_texture_transform"); + for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { + Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + ERR_CONTINUE(ext.is_null()); + Vector<String> ext_supported_extensions = ext->get_supported_extensions(); + for (int i = 0; i < ext_supported_extensions.size(); ++i) { + supported_extensions.insert(ext_supported_extensions[i]); } } - return OK; + Error ret = Error::OK; + for (int i = 0; i < state->extensions_required.size(); i++) { + if (!supported_extensions.has(state->extensions_required[i])) { + ERR_PRINT("GLTF: Can't import file '" + state->filename + "', required extension '" + String(state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); + ret = ERR_UNAVAILABLE; + } + } + return ret; } diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index b3ed786a39..750d3d403e 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -265,7 +265,7 @@ private: Dictionary _serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material); Error _serialize_version(Ref<GLTFState> state); Error _serialize_file(Ref<GLTFState> state, const String p_path); - Error _serialize_extensions(Ref<GLTFState> state) const; + Error _serialize_gltf_extensions(Ref<GLTFState> state) const; public: // https://www.itu.int/rec/R-REC-BT.601 diff --git a/modules/gltf/gltf_document_extension.cpp b/modules/gltf/gltf_document_extension.cpp index d0bd7651e0..3b952f8246 100644 --- a/modules/gltf/gltf_document_extension.cpp +++ b/modules/gltf/gltf_document_extension.cpp @@ -31,6 +31,7 @@ #include "gltf_document_extension.h" void GLTFDocumentExtension::_bind_methods() { + GDVIRTUAL_BIND(_get_supported_extensions); GDVIRTUAL_BIND(_import_preflight, "state"); GDVIRTUAL_BIND(_import_post_parse, "state"); GDVIRTUAL_BIND(_import_node, "state", "gltf_node", "json", "node"); @@ -40,6 +41,12 @@ void GLTFDocumentExtension::_bind_methods() { GDVIRTUAL_BIND(_export_post, "state"); } +Vector<String> GLTFDocumentExtension::get_supported_extensions() { + Vector<String> ret; + GDVIRTUAL_CALL(_get_supported_extensions, ret); + return ret; +} + Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); diff --git a/modules/gltf/gltf_document_extension.h b/modules/gltf/gltf_document_extension.h index 0ef9109584..d4bb3993dc 100644 --- a/modules/gltf/gltf_document_extension.h +++ b/modules/gltf/gltf_document_extension.h @@ -41,6 +41,7 @@ protected: static void _bind_methods(); public: + virtual Vector<String> get_supported_extensions(); virtual Error import_preflight(Ref<GLTFState> p_state); virtual Error import_post_parse(Ref<GLTFState> p_state); virtual Error export_post(Ref<GLTFState> p_state); @@ -48,6 +49,7 @@ public: virtual Error export_preflight(Node *p_state); virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); + GDVIRTUAL0R(Vector<String>, _get_supported_extensions); GDVIRTUAL1R(int, _import_preflight, Ref<GLTFState>); GDVIRTUAL1R(int, _import_post_parse, Ref<GLTFState>); GDVIRTUAL4R(int, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index 85bac446cc..a23fb39503 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -31,6 +31,7 @@ #include "gltf_state.h" void GLTFState::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_used_extension", "extension_name", "required"), &GLTFState::add_used_extension); ClassDB::bind_method(D_METHOD("get_json"), &GLTFState::get_json); ClassDB::bind_method(D_METHOD("set_json", "json"), &GLTFState::set_json); ClassDB::bind_method(D_METHOD("get_major_version"), &GLTFState::get_major_version); @@ -112,6 +113,17 @@ void GLTFState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector<Ref<GLTFAnimation>> } +void GLTFState::add_used_extension(const String &p_extension_name, bool p_required) { + if (!extensions_used.has(p_extension_name)) { + extensions_used.push_back(p_extension_name); + } + if (p_required) { + if (!extensions_required.has(p_extension_name)) { + extensions_required.push_back(p_extension_name); + } + } +} + Dictionary GLTFState::get_json() { return json; } diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index 6b2d1ca228..791431f376 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -78,6 +78,8 @@ class GLTFState : public Resource { Vector<int> root_nodes; Vector<Ref<GLTFTexture>> textures; Vector<Ref<Texture2D>> images; + Vector<String> extensions_used; + Vector<String> extensions_required; Vector<Ref<GLTFSkin>> skins; Vector<Ref<GLTFCamera>> cameras; @@ -97,6 +99,8 @@ protected: static void _bind_methods(); public: + void add_used_extension(const String &p_extension, bool p_required = false); + Dictionary get_json(); void set_json(Dictionary p_json); diff --git a/modules/webxr/config.py b/modules/webxr/config.py index f676ef3483..8d75e7f531 100644 --- a/modules/webxr/config.py +++ b/modules/webxr/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return not env["disable_3d"] + return env["opengl3"] and not env["disable_3d"] def configure(env): diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index 52104895d4..34d068be3e 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -65,8 +65,7 @@ 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 int godot_webxr_get_external_texture_for_eye(int p_eye); -extern void godot_webxr_commit_for_eye(int p_eye); +extern void godot_webxr_commit_for_eye(int p_eye, unsigned int p_destination_fbo); extern void godot_webxr_sample_controller_data(); extern int godot_webxr_get_controller_count(); diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index c4b21defce..9b75796ee5 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -32,9 +32,6 @@ const GodotWebXR = { $GodotWebXR: { gl: null, - texture_ids: [null, null], - textures: [null, null], - session: null, space: null, frame: null, @@ -77,110 +74,6 @@ const GodotWebXR = { }, 0); }, - // Some custom WebGL code for blitting our eye textures to the - // framebuffer we get from WebXR. - shaderProgram: null, - programInfo: null, - buffer: null, - // Vertex shader source. - vsSource: ` - const vec2 scale = vec2(0.5, 0.5); - attribute vec4 aVertexPosition; - - varying highp vec2 vTextureCoord; - - void main () { - gl_Position = aVertexPosition; - vTextureCoord = aVertexPosition.xy * scale + scale; - } - `, - // Fragment shader source. - fsSource: ` - varying highp vec2 vTextureCoord; - - uniform sampler2D uSampler; - - void main() { - gl_FragColor = texture2D(uSampler, vTextureCoord); - } - `, - - initShaderProgram: (gl, vsSource, fsSource) => { - const vertexShader = GodotWebXR.loadShader(gl, gl.VERTEX_SHADER, vsSource); - const fragmentShader = GodotWebXR.loadShader(gl, gl.FRAGMENT_SHADER, fsSource); - - const shaderProgram = gl.createProgram(); - gl.attachShader(shaderProgram, vertexShader); - gl.attachShader(shaderProgram, fragmentShader); - gl.linkProgram(shaderProgram); - - if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { - GodotRuntime.error(`Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}`); - return null; - } - - return shaderProgram; - }, - loadShader: (gl, type, source) => { - const shader = gl.createShader(type); - gl.shaderSource(shader, source); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - GodotRuntime.error(`An error occurred compiling the shader: ${gl.getShaderInfoLog(shader)}`); - gl.deleteShader(shader); - return null; - } - - return shader; - }, - initBuffer: (gl) => { - const positionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - const positions = [ - -1.0, -1.0, - 1.0, -1.0, - -1.0, 1.0, - 1.0, 1.0, - ]; - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); - return positionBuffer; - }, - blitTexture: (gl, texture) => { - if (GodotWebXR.shaderProgram === null) { - GodotWebXR.shaderProgram = GodotWebXR.initShaderProgram(gl, GodotWebXR.vsSource, GodotWebXR.fsSource); - GodotWebXR.programInfo = { - program: GodotWebXR.shaderProgram, - attribLocations: { - vertexPosition: gl.getAttribLocation(GodotWebXR.shaderProgram, 'aVertexPosition'), - }, - uniformLocations: { - uSampler: gl.getUniformLocation(GodotWebXR.shaderProgram, 'uSampler'), - }, - }; - GodotWebXR.buffer = GodotWebXR.initBuffer(gl); - } - - const orig_program = gl.getParameter(gl.CURRENT_PROGRAM); - gl.useProgram(GodotWebXR.shaderProgram); - - gl.bindBuffer(gl.ARRAY_BUFFER, GodotWebXR.buffer); - gl.vertexAttribPointer(GodotWebXR.programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.uniform1i(GodotWebXR.programInfo.uniformLocations.uSampler, 0); - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - - // Restore state. - gl.bindTexture(gl.TEXTURE_2D, null); - gl.disableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.useProgram(orig_program); - }, - // Holds the controllers list between function calls. controllers: [], @@ -370,22 +263,6 @@ const GodotWebXR = { .catch((e) => { }); } - // Clean-up the textures we allocated for each view. - const gl = GodotWebXR.gl; - for (let i = 0; i < GodotWebXR.textures.length; i++) { - const texture = GodotWebXR.textures[i]; - if (texture !== null) { - gl.deleteTexture(texture); - } - GodotWebXR.textures[i] = null; - - const texture_id = GodotWebXR.texture_ids[i]; - if (texture_id !== null) { - GL.textures[texture_id] = null; - } - GodotWebXR.texture_ids[i] = null; - } - GodotWebXR.session = null; GodotWebXR.space = null; GodotWebXR.frame = null; @@ -460,50 +337,9 @@ const GodotWebXR = { return buf; }, - godot_webxr_get_external_texture_for_eye__proxy: 'sync', - godot_webxr_get_external_texture_for_eye__sig: 'ii', - godot_webxr_get_external_texture_for_eye: function (p_eye) { - if (!GodotWebXR.session) { - return 0; - } - - const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0; - if (GodotWebXR.texture_ids[view_index]) { - return GodotWebXR.texture_ids[view_index]; - } - - // Check pose separately and after returning the cached texture id, - // because we won't get a pose in some cases if we lose tracking, and - // we don't want to return 0 just because tracking was lost. - if (!GodotWebXR.pose) { - return 0; - } - - const glLayer = GodotWebXR.session.renderState.baseLayer; - const view = GodotWebXR.pose.views[view_index]; - const viewport = glLayer.getViewport(view); - const gl = GodotWebXR.gl; - - const texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.width, viewport.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.bindTexture(gl.TEXTURE_2D, null); - - const texture_id = GL.getNewId(GL.textures); - GL.textures[texture_id] = texture; - GodotWebXR.textures[view_index] = texture; - GodotWebXR.texture_ids[view_index] = texture_id; - return texture_id; - }, - godot_webxr_commit_for_eye__proxy: 'sync', - godot_webxr_commit_for_eye__sig: 'vi', - godot_webxr_commit_for_eye: function (p_eye) { + godot_webxr_commit_for_eye__sig: 'vii', + godot_webxr_commit_for_eye: function (p_eye, p_destination_fbo) { if (!GodotWebXR.session || !GodotWebXR.pose) { return; } @@ -514,18 +350,29 @@ const GodotWebXR = { const viewport = glLayer.getViewport(view); const gl = GodotWebXR.gl; + const framebuffer = GL.framebuffers[p_destination_fbo]; + const orig_framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); - const orig_viewport = gl.getParameter(gl.VIEWPORT); + 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); - // Bind to WebXR's framebuffer. - gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer); - gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height); + // Copy from Godot render target into framebuffer from WebXR. + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer); + gl.readBuffer(gl.COLOR_ATTACHMENT0); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, glLayer.framebuffer); - GodotWebXR.blitTexture(gl, GodotWebXR.textures[view_index]); + // Flip Y upside down on destination. + gl.blitFramebuffer(0, 0, viewport.width, viewport.height, + viewport.x, viewport.height, viewport.width, viewport.y, + gl.COLOR_BUFFER_BIT, gl.NEAREST); // Restore state. gl.bindFramebuffer(gl.FRAMEBUFFER, orig_framebuffer); - gl.viewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, orig_read_framebuffer); + gl.readBuffer(orig_read_buffer); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, orig_draw_framebuffer); }, godot_webxr_sample_controller_data__proxy: 'sync', diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 7d97dbfa0b..d0c7484aa1 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -34,9 +34,11 @@ #include "core/input/input.h" #include "core/os/os.h" +#include "drivers/gles3/storage/texture_storage.h" #include "emscripten.h" #include "godot_webxr.h" #include "servers/rendering/renderer_compositor.h" +#include "servers/rendering/rendering_server_globals.h" #include <stdlib.h> @@ -232,6 +234,8 @@ bool WebXRInterfaceJS::initialize() { } // we must create a tracker for our head + head_transform.basis = Basis(); + head_transform.origin = Vector3(); head_tracker.instantiate(); head_tracker->set_tracker_type(XRServer::TRACKER_HEAD); head_tracker->set_tracker_name("head"); @@ -334,15 +338,17 @@ Transform3D WebXRInterfaceJS::get_camera_transform() { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, transform_for_eye); - float *js_matrix = godot_webxr_get_transform_for_eye(0); - if (!initialized || js_matrix == nullptr) { - return transform_for_eye; - } + if (initialized) { + float world_scale = xr_server->get_world_scale(); - transform_for_eye = _js_matrix_to_transform(js_matrix); - free(js_matrix); + // just scale our origin point of our transform + Transform3D _head_transform = head_transform; + _head_transform.origin *= world_scale; + + transform_for_eye = (xr_server->get_reference_frame()) * _head_transform; + } - return xr_server->get_reference_frame() * transform_for_eye; + return transform_for_eye; }; Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { @@ -360,6 +366,14 @@ Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Tran transform_for_eye = _js_matrix_to_transform(js_matrix); free(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; + _head_transform.origin *= world_scale; + transform_for_eye.origin += _head_transform.origin; + return p_cam_transform * xr_server->get_reference_frame() * transform_for_eye; }; @@ -394,29 +408,33 @@ Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, c return blit_to_screen; } - // @todo Refactor this to be based on "views" rather than "eyes". - godot_webxr_commit_for_eye(1); - if (godot_webxr_get_view_count() > 1) { - godot_webxr_commit_for_eye(2); + GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage); + if (!texture_storage) { + return blit_to_screen; } + GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target); + + // @todo Support multiple eyes! + godot_webxr_commit_for_eye(1, rt->fbo); + return blit_to_screen; }; void WebXRInterfaceJS::process() { if (initialized) { - godot_webxr_sample_controller_data(); - + // Get the "head" position. + float *js_matrix = godot_webxr_get_transform_for_eye(0); + if (js_matrix != nullptr) { + head_transform = _js_matrix_to_transform(js_matrix); + free(js_matrix); + } if (head_tracker.is_valid()) { - // TODO set default pose to our head location (i.e. get_camera_transform without world scale and reference frame applied) - // head_tracker->set_pose("default", head_transform, Vector3(), Vector3()); + head_tracker->set_pose("default", head_transform, Vector3(), Vector3()); } + godot_webxr_sample_controller_data(); int controller_count = godot_webxr_get_controller_count(); - if (controller_count == 0) { - return; - } - for (int i = 0; i < controller_count; i++) { _update_tracker(i); } diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index dbe89dad83..319adc2ac9 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -45,6 +45,7 @@ class WebXRInterfaceJS : public WebXRInterface { private: bool initialized; Ref<XRPositionalTracker> head_tracker; + Transform3D head_transform; String session_mode; String required_features; |