diff --git a/.sconsign.dblite b/.sconsign.dblite
index d4ef975..50648c6 100644
Binary files a/.sconsign.dblite and b/.sconsign.dblite differ
diff --git a/demo/addons/debug_draw_3d/LICENSE b/demo/addons/debug_draw_3d/LICENSE
new file mode 100644
index 0000000..617a15b
--- /dev/null
+++ b/demo/addons/debug_draw_3d/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 DmitriySalnikov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the Software), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, andor sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/demo/addons/debug_draw_3d/README.md b/demo/addons/debug_draw_3d/README.md
new file mode 100644
index 0000000..3cd3961
--- /dev/null
+++ b/demo/addons/debug_draw_3d/README.md
@@ -0,0 +1,157 @@
+
+
+# Debug drawing utility for Godot
+
+This is an add-on for debug drawing in 3D and for some 2D overlays, which is written in `C++` and can be used with `GDScript` or `C#`.
+
+Based on my previous addon, which was developed [only for C#](https://github.com/DmitriySalnikov/godot_debug_draw_cs), and which was inspired by [Zylann's GDScript addon](https://github.com/Zylann/godot_debug_draw)
+
+## [Documentation](https://dd3d.dmitriysalnikov.ru/docs/)
+
+## [Godot 3 version](https://github.com/DmitriySalnikov/godot_debug_draw_3d/tree/godot_3)
+
+## Support me
+
+Your support adds motivation to develop my public projects.
+
+
+
+
+
+USDT-TRC20 TEw934PrsffHsAn5M63SoHYRuZo984EF6v
+
+## Features
+
+3D:
+
+* Arrow
+* Billboard opaque square
+* Box
+* Camera Frustum
+* Cylinder
+* Gizmo
+* Grid
+* Line
+* Line Path
+* Line with Arrow
+* Plane
+* Points
+* Position 3D (3 crossing axes)
+* Sphere
+* 3D Text
+
+2D:
+
+* **[Work in progress]**
+
+Overlay:
+
+* Text (with grouping and coloring)
+
+Precompiled for:
+
+* Windows
+* Linux (built on Ubuntu 22.04)
+* macOS (10.15+)
+* Android (5.0+)
+* iOS
+* Web (Firefox is supported by Godot 4.3+)
+
+This addon supports working with several World3D and different Viewports.
+There is also a no depth test mode and other settings that can be changed for each instance.
+
+This library supports double-precision builds, for more information, [see the documentation](https://dd3d.dmitriysalnikov.ru/docs/?page=md_docs_2DoublePrecision.html).
+
+## [Interactive Web Demo](https://dd3d.dmitriysalnikov.ru/demo/)
+
+[](https://dd3d.dmitriysalnikov.ru/demo/)
+
+## Download
+
+To download, use the [Godot Asset Library](https://godotengine.org/asset-library/asset/1766) or use one of the stable versions from the [GitHub Releases](https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases) page.
+
+For versions prior to `1.4.5`, just download one of the `source codes` in the assets. For newer versions, download `debug-draw-3d_[version].zip`.
+
+### Installation
+
+* Close editor
+* Copy `addons/debug_draw_3d` to your `addons` folder, create it if the folder doesn't exist
+* Launch editor
+
+## Examples
+
+More examples can be found in the `examples_dd3d/` folder.
+
+Simple test:
+
+```gdscript
+func _process(delta: float) -> void:
+ var _time = Time.get_ticks_msec() / 1000.0
+ var box_pos = Vector3(0, sin(_time * 4), 0)
+ var line_begin = Vector3(-1, sin(_time * 4), 0)
+ var line_end = Vector3(1, cos(_time * 4), 0)
+
+ DebugDraw3D.draw_box(box_pos, Quaternion.IDENTITY, Vector3(1, 2, 1), Color(0, 1, 0))
+ DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0))
+ DebugDraw2D.set_text("Time", _time)
+ DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn())
+ DebugDraw2D.set_text("FPS", Engine.get_frames_per_second())
+ DebugDraw2D.set_text("delta", delta)
+```
+
+
+
+An example of using scoped configs:
+
+```gdscript
+@tool
+extends Node3D
+
+func _ready():
+ # Set the base scoped_config.
+ # Each frame will be reset to these scoped values.
+ DebugDraw3D.scoped_config().set_thickness(0.1).set_center_brightness(0.6)
+
+func _process(delta):
+ # Draw using the base scoped config.
+ DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE * 2, Color.CORNFLOWER_BLUE)
+ if true:
+ # Create a scoped config that will exist until exiting this if.
+ var _s = DebugDraw3D.new_scoped_config().set_thickness(0).set_center_brightness(0.1)
+ # Draw with a thickness of 0
+ DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE, Color.RED)
+ # If necessary, the values inside this scope can be changed
+ # even before each call to draw_*.
+ _s.set_thickness(0.05)
+ DebugDraw3D.draw_box(Vector3(1,0,1), Quaternion.IDENTITY, Vector3.ONE * 1, Color.BLUE_VIOLET)
+```
+
+
+
+> [!TIP]
+>
+> If you want to use a non-standard Viewport for rendering a 3d scene, then do not forget to specify it in the scoped config!
+
+## API
+
+This project has a separate [documentation](https://dd3d.dmitriysalnikov.ru/docs/) page.
+
+Also, a list of all functions is available in the documentation inside the editor (see `DebugDraw3D` and `DebugDraw2D`).
+
+
+
+## Known issues and limitations
+
+The text in the keys and values of a text group cannot contain multi-line strings.
+
+The entire text overlay can only be placed in one corner.
+
+[Frustum of Camera3D does not take into account the window size from ProjectSettings](https://github.com/godotengine/godot/issues/70362).
+
+## More screenshots
+
+`DebugDrawDemoScene.tscn` in editor
+
+
+`DebugDrawDemoScene.tscn` in play mode
+
diff --git a/demo/addons/debug_draw_3d/debug_draw_3d.gdextension b/demo/addons/debug_draw_3d/debug_draw_3d.gdextension
new file mode 100644
index 0000000..cead527
--- /dev/null
+++ b/demo/addons/debug_draw_3d/debug_draw_3d.gdextension
@@ -0,0 +1,153 @@
+[configuration]
+
+entry_symbol = "debug_draw_3d_library_init"
+compatibility_minimum = "4.2.2"
+reloadable = false
+
+[dependencies]
+
+; example.x86_64 = { "relative or absolute path to the dependency" : "the path relative to the exported project", }
+; -------------------------------------
+; debug
+
+macos = { }
+windows.x86_64 = { }
+linux.x86_64 = { }
+
+; by default godot is using threads
+web.wasm32.nothreads = {}
+web.wasm32 = {}
+
+android.arm32 = { }
+android.arm64 = { }
+android.x86_32 = { }
+android.x86_64 = { }
+
+ios = {}
+
+; -------------------------------------
+; release no debug draw
+
+macos.template_release = { }
+windows.template_release.x86_64 = { }
+linux.template_release.x86_64 = { }
+
+web.template_release.wasm32.nothreads = { }
+web.template_release.wasm32 = { }
+
+android.template_release.arm32 = { }
+android.template_release.arm64 = { }
+android.template_release.x86_32 = { }
+android.template_release.x86_64 = { }
+
+ios.template_release = {}
+
+; -------------------------------------
+; release forced debug draw
+
+macos.template_release.forced_dd3d = { }
+windows.template_release.x86_64.forced_dd3d = { }
+linux.template_release.x86_64.forced_dd3d = { }
+
+web.template_release.wasm32.nothreads.forced_dd3d = { }
+web.template_release.wasm32.forced_dd3d = { }
+
+ios.template_release.forced_dd3d = {}
+
+[libraries]
+
+; -------------------------------------
+; debug
+
+macos = "libs/libdd3d.macos.editor.universal.framework"
+windows.x86_64 = "libs/libdd3d.windows.editor.x86_64.dll"
+linux.x86_64 = "libs/libdd3d.linux.editor.x86_64.so"
+
+web.wasm32.nothreads = "libs/libdd3d.web.template_debug.wasm32.wasm"
+web.wasm32 = "libs/libdd3d.web.template_debug.wasm32.threads.wasm"
+
+android.arm32 = "libs/libdd3d.android.template_debug.arm32.so"
+android.arm64 = "libs/libdd3d.android.template_debug.arm64.so"
+android.x86_32 = "libs/libdd3d.android.template_debug.x86_32.so"
+android.x86_64 = "libs/libdd3d.android.template_debug.x86_64.so"
+
+ios = "libs/libdd3d.ios.template_debug.universal.dylib"
+
+; -------------------------------------
+; release no debug draw
+
+macos.template_release = "libs/libdd3d.macos.template_release.universal.framework"
+windows.template_release.x86_64 = "libs/libdd3d.windows.template_release.x86_64.dll"
+linux.template_release.x86_64 = "libs/libdd3d.linux.template_release.x86_64.so"
+
+web.template_release.wasm32.nothreads = "libs/libdd3d.web.template_release.wasm32.wasm"
+web.template_release.wasm32 = "libs/libdd3d.web.template_release.wasm32.threads.wasm"
+
+android.template_release.arm32 = "libs/libdd3d.android.template_release.arm32.so"
+android.template_release.arm64 = "libs/libdd3d.android.template_release.arm64.so"
+android.template_release.x86_32 = "libs/libdd3d.android.template_release.x86_32.so"
+android.template_release.x86_64 = "libs/libdd3d.android.template_release.x86_64.so"
+
+ios.template_release = "libs/libdd3d.ios.template_release.universal.dylib"
+
+; -------------------------------------
+; release forced debug draw
+
+macos.template_release.forced_dd3d = "libs/libdd3d.macos.template_release.universal.enabled.framework"
+windows.template_release.x86_64.forced_dd3d = "libs/libdd3d.windows.template_release.x86_64.enabled.dll"
+linux.template_release.x86_64.forced_dd3d = "libs/libdd3d.linux.template_release.x86_64.enabled.so"
+
+web.template_release.wasm32.nothreads.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.enabled.wasm"
+web.template_release.wasm32.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm"
+
+ios.template_release.forced_dd3d = "libs/libdd3d.ios.template_release.universal.enabled.dylib"
+
+; -------------------------------------
+; DOUBLE PRECISION
+; -------------------------------------
+
+; -------------------------------------
+; debug
+
+macos.double = "libs/libdd3d.macos.editor.universal.double.framework"
+windows.x86_64.double = "libs/libdd3d.windows.editor.x86_64.double.dll"
+linux.x86_64.double = "libs/libdd3d.linux.editor.x86_64.double.so"
+
+web.wasm32.nothreads.double = "libs/libdd3d.web.template_debug.wasm32.double.wasm"
+web.wasm32.double = "libs/libdd3d.web.template_debug.wasm32.threads.double.wasm"
+
+android.arm32.double = "libs/libdd3d.android.template_debug.arm32.double.so"
+android.arm64.double = "libs/libdd3d.android.template_debug.arm64.double.so"
+android.x86_32.double = "libs/libdd3d.android.template_debug.x86_32.double.so"
+android.x86_64.double = "libs/libdd3d.android.template_debug.x86_64.double.so"
+
+ios.double = "libs/libdd3d.ios.template_debug.universal.dylib"
+
+; -------------------------------------
+; release no debug draw
+
+macos.template_release.double = "libs/libdd3d.macos.template_release.universal.double.framework"
+windows.template_release.x86_64.double = "libs/libdd3d.windows.template_release.x86_64.double.dll"
+linux.template_release.x86_64.double = "libs/libdd3d.linux.template_release.x86_64.double.so"
+
+web.template_release.wasm32.nothreads.double = "libs/libdd3d.web.template_release.wasm32.double.wasm"
+web.template_release.wasm32.double = "libs/libdd3d.web.template_release.wasm32.threads.double.wasm"
+
+android.template_release.arm32.double = "libs/libdd3d.android.template_release.arm32.double.so"
+android.template_release.arm64.double = "libs/libdd3d.android.template_release.arm64.double.so"
+android.template_release.x86_32.double = "libs/libdd3d.android.template_release.x86_32.double.so"
+android.template_release.x86_64.double = "libs/libdd3d.android.template_release.x86_64.double.so"
+
+ios.template_release.double = "libs/libdd3d.ios.template_release.universal.double.dylib"
+
+; -------------------------------------
+; release forced debug draw
+
+macos.template_release.forced_dd3d.double = "libs/libdd3d.macos.template_release.universal.enabled.double.framework"
+windows.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.windows.template_release.x86_64.enabled.double.dll"
+linux.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.linux.template_release.x86_64.enabled.double.so"
+
+web.template_release.wasm32.nothreads.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.enabled.double.wasm"
+web.template_release.wasm32.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.threads.enabled.double.wasm"
+
+ios.template_release.forced_dd3d.double = "libs/libdd3d.ios.template_release.universal.enabled.double.dylib"
diff --git a/demo/addons/debug_draw_3d/debug_draw_3d.gdextension.uid b/demo/addons/debug_draw_3d/debug_draw_3d.gdextension.uid
new file mode 100644
index 0000000..15da0d3
--- /dev/null
+++ b/demo/addons/debug_draw_3d/debug_draw_3d.gdextension.uid
@@ -0,0 +1 @@
+uid://svqaxfp5kyrl
diff --git a/demo/addons/debug_draw_3d/libs/.gdignore b/demo/addons/debug_draw_3d/libs/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist b/demo/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist
new file mode 100644
index 0000000..933a7f8
--- /dev/null
+++ b/demo/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist
@@ -0,0 +1,37 @@
+
+
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ libdd3d.macos.editor.universal.dylib
+ CFBundleName
+ Debug Draw 3D
+ CFBundleDisplayName
+ Debug Draw 3D
+ CFBundleIdentifier
+ ru.dmitriysalnikov.dd3d
+ NSHumanReadableCopyright
+ Copyright (c) Dmitriy Salnikov.
+ CFBundleVersion
+ 1.5.1
+ CFBundleShortVersionString
+ 1.5.1
+ CFBundlePackageType
+ FMWK
+ CSResourcesFileMapped
+
+ DTPlatformName
+ macosx
+ LSMinimumSystemVersion
+ 10.14
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+
+
+
\ No newline at end of file
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist b/demo/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist
new file mode 100644
index 0000000..33bf631
--- /dev/null
+++ b/demo/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist
@@ -0,0 +1,37 @@
+
+
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ libdd3d.macos.template_release.universal.enabled.dylib
+ CFBundleName
+ Debug Draw 3D
+ CFBundleDisplayName
+ Debug Draw 3D
+ CFBundleIdentifier
+ ru.dmitriysalnikov.dd3d
+ NSHumanReadableCopyright
+ Copyright (c) Dmitriy Salnikov.
+ CFBundleVersion
+ 1.5.1
+ CFBundleShortVersionString
+ 1.5.1
+ CFBundlePackageType
+ FMWK
+ CSResourcesFileMapped
+
+ DTPlatformName
+ macosx
+ LSMinimumSystemVersion
+ 10.14
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+
+
+
\ No newline at end of file
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist b/demo/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist
new file mode 100644
index 0000000..0b36e0b
--- /dev/null
+++ b/demo/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist
@@ -0,0 +1,37 @@
+
+
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ libdd3d.macos.template_release.universal.dylib
+ CFBundleName
+ Debug Draw 3D
+ CFBundleDisplayName
+ Debug Draw 3D
+ CFBundleIdentifier
+ ru.dmitriysalnikov.dd3d
+ NSHumanReadableCopyright
+ Copyright (c) Dmitriy Salnikov.
+ CFBundleVersion
+ 1.5.1
+ CFBundleShortVersionString
+ 1.5.1
+ CFBundlePackageType
+ FMWK
+ CSResourcesFileMapped
+
+ DTPlatformName
+ macosx
+ LSMinimumSystemVersion
+ 10.14
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+
+
+
\ No newline at end of file
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm
new file mode 100644
index 0000000..33fe474
Binary files /dev/null and b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm differ
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm
new file mode 100644
index 0000000..a27ea99
Binary files /dev/null and b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm differ
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm
new file mode 100644
index 0000000..a8445e3
Binary files /dev/null and b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm differ
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm
new file mode 100644
index 0000000..c90fb24
Binary files /dev/null and b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm differ
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm
new file mode 100644
index 0000000..4ee4c42
Binary files /dev/null and b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm differ
diff --git a/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm
new file mode 100644
index 0000000..b5e7a64
Binary files /dev/null and b/demo/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm differ
diff --git a/demo/examples_dd3d/DebugDrawDemoScene.gd b/demo/examples_dd3d/DebugDrawDemoScene.gd
new file mode 100644
index 0000000..8c4ef15
--- /dev/null
+++ b/demo/examples_dd3d/DebugDrawDemoScene.gd
@@ -0,0 +1,621 @@
+@tool
+extends Node3D
+
+@export var custom_font : Font
+@export var custom_3d_font : Font
+@export var zylann_example := false
+@export var update_in_physics := false
+@export var test_text := true
+@export var more_test_cases := true
+@export var draw_3d_text := true
+@export var draw_array_of_boxes := false
+@export var draw_text_with_boxes := false
+@export var draw_1m_boxes := false
+@export_range(0, 5, 0.001) var debug_thickness := 0.1
+@export_range(0, 1, 0.001) var debug_center_brightness := 0.8
+@export_range(0, 1) var camera_frustum_scale := 0.9
+
+@export_group("Text groups", "text_groups")
+@export var text_groups_show_examples := true
+@export var text_groups_show_hints := true
+@export var text_groups_show_stats := false
+@export var text_groups_show_stats_2d := false
+@export var text_groups_position := DebugDraw2DConfig.POSITION_LEFT_TOP
+@export var text_groups_offset := Vector2i(8, 8)
+@export var text_groups_padding := Vector2i(3, 1)
+@export_range(1, 100) var text_groups_default_font_size := 15
+@export_range(1, 100) var text_groups_title_font_size := 20
+@export_range(1, 100) var text_groups_text_font_size := 17
+
+@export_group("Tests", "tests")
+@export var tests_use_threads := false
+var test_thread : Thread = null
+var test_thread_closing := false
+
+var button_presses := {}
+var frame_rendered := false
+var physics_tick_processed := false
+
+var timer_1 := 0.0
+var timer_cubes := 0.0
+var timer_3 := 0.0
+var timer_text := 0.0
+
+
+func _process(delta) -> void:
+ #print("Label3Ds count: %d" % get_child(0).get_child_count() if Engine.is_editor_hint() else get_tree().root.get_child(0).get_child_count())
+
+ $OtherWorld.mesh.material.set_shader_parameter("albedo_texture", $OtherWorld/SubViewport.get_texture())
+
+ physics_tick_processed = false
+ if not update_in_physics:
+ main_update(delta)
+ _update_timers(delta)
+
+ _call_from_thread()
+
+
+## Since physics frames may not be called every frame or may be called multiple times in one frame,
+## there is an additional check to ensure that a new frame has been drawn before updating the data.
+func _physics_process(delta: float) -> void:
+ if not physics_tick_processed:
+ physics_tick_processed = true
+ if update_in_physics:
+ main_update(delta)
+ _update_timers(delta)
+
+ # Physics specific:
+ if not zylann_example:
+ DebugDraw3D.draw_line($"Lines/8".global_position, $Lines/Target.global_position, Color.YELLOW)
+
+ if more_test_cases:
+ _draw_rays_casts()
+
+ ## Additional drawing in the Viewport
+ if true:
+ var _w1 = DebugDraw3D.new_scoped_config().set_viewport(%OtherWorldBox.get_viewport()).set_thickness(0.01).set_center_brightness(1).set_no_depth_test(true)
+ DebugDraw3D.draw_box_xf(Transform3D(Basis()
+ .scaled(Vector3.ONE*0.3)
+ .rotated(Vector3(0,0,1), PI/4)
+ .rotated(Vector3(0,1,0), wrapf(Time.get_ticks_msec() / -1500.0, 0, TAU) - PI/4), %OtherWorldBox.global_transform.origin),
+ Color.BROWN, true, 0.4)
+
+
+func main_update(delta: float) -> void:
+ DebugDraw3D.scoped_config().set_thickness(debug_thickness).set_center_brightness(debug_center_brightness)
+
+ _update_keys_just_press()
+
+ if _is_key_just_pressed(KEY_F1):
+ zylann_example = !zylann_example
+
+ # Zylann's example :D
+ if zylann_example:
+ var _time = Time.get_ticks_msec() / 1000.0
+ var box_pos = Vector3(0, sin(_time * 4), 0)
+ var line_begin = Vector3(-1, sin(_time * 4), 0)
+ var line_end = Vector3(1, cos(_time * 4), 0)
+
+ DebugDraw3D.draw_box(box_pos, Quaternion.IDENTITY, Vector3(1, 2, 1), Color(0, 1, 0))
+ DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0))
+ DebugDraw2D.set_text("Time", _time)
+ DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn())
+ DebugDraw2D.set_text("FPS", Engine.get_frames_per_second())
+ DebugDraw2D.set_text("delta", delta)
+
+ $HitTest.visible = false
+ $LagTest.visible = false
+ $PlaneOrigin.visible = false
+ $OtherWorld.visible = false
+ %ZDepthTestCube.visible = false
+ return
+
+ $HitTest.visible = true
+ $LagTest.visible = true
+ $PlaneOrigin.visible = true
+ $OtherWorld.visible = true
+ %ZDepthTestCube.visible = true
+
+ # Testing the rendering layers by showing the image from the second camera inside the 2D panel
+ DebugDraw3D.config.geometry_render_layers = 1 if not Input.is_key_pressed(KEY_ALT) else 0b10010
+ $Panel.visible = Input.is_key_pressed(KEY_ALT)
+ DebugDraw2D.custom_canvas = %CustomCanvas if Input.is_key_pressed(KEY_ALT) else null
+
+ # More property toggles
+ DebugDraw3D.config.freeze_3d_render = Input.is_key_pressed(KEY_DOWN)
+ DebugDraw3D.config.visible_instance_bounds = Input.is_key_pressed(KEY_RIGHT)
+
+ # Regenerate meshes
+ if Input.is_action_just_pressed("ui_end"):
+ DebugDraw3D.regenerate_geometry_meshes()
+
+ # Some property toggles
+ if _is_key_just_pressed(KEY_LEFT):
+ DebugDraw3D.config.use_frustum_culling = !DebugDraw3D.config.use_frustum_culling
+ if _is_key_just_pressed(KEY_UP):
+ DebugDraw3D.config.force_use_camera_from_scene = !DebugDraw3D.config.force_use_camera_from_scene
+ if _is_key_just_pressed(KEY_CTRL):
+ if not Engine.is_editor_hint():
+ get_viewport().msaa_3d = Viewport.MSAA_DISABLED if get_viewport().msaa_3d == Viewport.MSAA_4X else Viewport.MSAA_4X
+
+ if not Engine.is_editor_hint():
+ if _is_key_just_pressed(KEY_1):
+ DebugDraw3D.debug_enabled = !DebugDraw3D.debug_enabled
+ if _is_key_just_pressed(KEY_2):
+ DebugDraw2D.debug_enabled = !DebugDraw2D.debug_enabled
+ if _is_key_just_pressed(KEY_3):
+ DebugDrawManager.debug_enabled = !DebugDrawManager.debug_enabled
+
+
+ DebugDraw3D.config.frustum_length_scale = camera_frustum_scale
+
+ # Zones with black borders
+ for z in $Zones.get_children():
+ DebugDraw3D.draw_box_xf(z.global_transform, Color.BLACK)
+
+ # Spheres
+ _draw_zone_title(%SpheresBox, "Spheres")
+
+ DebugDraw3D.draw_sphere_xf($Spheres/SphereTransform.global_transform, Color.CRIMSON)
+ if true:
+ var _shd = DebugDraw3D.new_scoped_config().set_hd_sphere(true)
+ DebugDraw3D.draw_sphere_xf($Spheres/SphereHDTransform.global_transform, Color.ORANGE_RED)
+
+ ## Delayed spheres
+ if timer_1 < 0:
+ DebugDraw3D.draw_sphere($Spheres/SpherePosition.global_position, 2.0, Color.BLUE_VIOLET, 2.0)
+ var _shd = DebugDraw3D.new_scoped_config().set_hd_sphere(true)
+ DebugDraw3D.draw_sphere($Spheres/SpherePosition.global_position + Vector3.FORWARD * 4, 2.0, Color.CORNFLOWER_BLUE, 2.0)
+ timer_1 = 2
+
+ # Cylinders
+ _draw_zone_title(%CylindersBox, "Cylinders")
+
+ DebugDraw3D.draw_cylinder($Cylinders/Cylinder1.global_transform, Color.CRIMSON)
+ DebugDraw3D.draw_cylinder(Transform3D(Basis.IDENTITY.scaled(Vector3(1,2,1)), $Cylinders/Cylinder2.global_position), Color.RED)
+ DebugDraw3D.draw_cylinder_ab($"Cylinders/Cylinder3/1".global_position, $"Cylinders/Cylinder3/2".global_position, 0.7)
+
+ # Boxes
+ _draw_zone_title(%BoxesBox, "Boxes")
+
+ DebugDraw3D.draw_box_xf($Boxes/Box1.global_transform, Color.MEDIUM_PURPLE)
+ DebugDraw3D.draw_box($Boxes/Box2.global_position, Quaternion.from_euler(Vector3(0, deg_to_rad(45), deg_to_rad(45))), Vector3.ONE, Color.REBECCA_PURPLE)
+ DebugDraw3D.draw_box_xf(Transform3D(Basis(Vector3.UP, PI * 0.25).scaled(Vector3.ONE * 2), $Boxes/Box3.global_position), Color.ROSY_BROWN)
+
+ DebugDraw3D.draw_aabb(AABB($Boxes/AABB_fixed.global_position, Vector3(2, 1, 2)), Color.AQUA)
+ DebugDraw3D.draw_aabb_ab($Boxes/AABB/a.global_position, $Boxes/AABB/b.global_position, Color.DEEP_PINK)
+
+ # Boxes AB
+ DebugDraw3D.draw_arrow($Boxes/BoxAB.global_position, $Boxes/BoxAB/o/up.global_position, Color.GOLD, 0.1, true)
+ DebugDraw3D.draw_box_ab($Boxes/BoxAB/a.global_position, $Boxes/BoxAB/b.global_position, $Boxes/BoxAB/o/up.global_position - $Boxes/BoxAB.global_position, Color.PERU)
+
+ DebugDraw3D.draw_arrow($Boxes/BoxABEdge.global_position, $Boxes/BoxABEdge/o/up.global_position, Color.DARK_RED, 0.1, true)
+ DebugDraw3D.draw_box_ab($Boxes/BoxABEdge/a.global_position, $Boxes/BoxABEdge/b.global_position, $Boxes/BoxABEdge/o/up.global_position - $Boxes/BoxABEdge.global_position, Color.DARK_OLIVE_GREEN, false)
+
+ # Lines
+ _draw_zone_title(%LinesBox, "Lines")
+
+ var target = $Lines/Target
+ DebugDraw3D.draw_square(target.global_position, 0.5, Color.RED)
+
+ DebugDraw3D.draw_line($"Lines/1".global_position, target.global_position, Color.FUCHSIA)
+ DebugDraw3D.draw_ray($"Lines/3".global_position, (target.global_position - $"Lines/3".global_position).normalized(), 3.0, Color.CRIMSON)
+
+ if timer_3 < 0:
+ DebugDraw3D.draw_line($"Lines/6".global_position, target.global_position, Color.FUCHSIA, 2.0)
+ timer_3 = 2
+
+ # Test UP vector
+ DebugDraw3D.draw_line($"Lines/7".global_position, target.global_position, Color.RED)
+
+ # Lines with Arrow
+ DebugDraw3D.draw_arrow($"Lines/2".global_position, target.global_position, Color.BLUE, 0.5, true)
+ DebugDraw3D.draw_arrow_ray($"Lines/4".global_position, (target.global_position - $"Lines/4".global_position).normalized(), 8.0, Color.LAVENDER, 0.5, true)
+
+ DebugDraw3D.draw_line_hit_offset($"Lines/5".global_position, target.global_position, true, abs(sin(Time.get_ticks_msec() / 1000.0)), 0.25, Color.AQUA)
+
+ # Paths
+ _draw_zone_title(%PathsBox, "Paths")
+
+ ## preparing data
+ var points: PackedVector3Array = []
+ var points_below: PackedVector3Array = []
+ var points_below2: PackedVector3Array = []
+ var points_below3: PackedVector3Array = []
+ var points_below4: PackedVector3Array = []
+ var lines_above: PackedVector3Array = []
+
+ for c in $LinePath.get_children():
+ if not c is Node3D:
+ break
+ points.append(c.global_position)
+ points_below.append(c.global_position + Vector3.DOWN)
+ points_below2.append(c.global_position + Vector3.DOWN * 2)
+ points_below3.append(c.global_position + Vector3.DOWN * 3)
+ points_below4.append(c.global_position + Vector3.DOWN * 4)
+
+ for x in points.size()-1:
+ lines_above.append(points[x] + Vector3.UP)
+ lines_above.append(points[x+1] + Vector3.UP)
+
+ ## drawing lines
+ DebugDraw3D.draw_lines(lines_above)
+ DebugDraw3D.draw_line_path(points, Color.BEIGE)
+ DebugDraw3D.draw_points(points_below, DebugDraw3D.POINT_TYPE_SQUARE, 0.2, Color.DARK_GREEN)
+ DebugDraw3D.draw_point_path(points_below2, DebugDraw3D.POINT_TYPE_SQUARE, 0.25, Color.BLUE, Color.TOMATO)
+ DebugDraw3D.draw_arrow_path(points_below3, Color.GOLD, 0.5)
+ if true:
+ var _sl = DebugDraw3D.new_scoped_config().set_thickness(0.05)
+ DebugDraw3D.draw_point_path(points_below4, DebugDraw3D.POINT_TYPE_SPHERE, 0.25, Color.MEDIUM_SEA_GREEN, Color.MEDIUM_VIOLET_RED)
+
+ # Misc
+ _draw_zone_title(%MiscBox, "Misc")
+
+ if Engine.is_editor_hint():
+ #for i in 1000:
+ var _a11 = DebugDraw3D.new_scoped_config().set_thickness(0)
+ DebugDraw3D.draw_camera_frustum($Camera, Color.DARK_ORANGE)
+
+ if true:
+ var _s123 = DebugDraw3D.new_scoped_config().set_center_brightness(0.1)
+ DebugDraw3D.draw_arrowhead($Misc/Arrow.global_transform, Color.YELLOW_GREEN)
+
+ DebugDraw3D.draw_square($Misc/Billboard.global_position, 0.5, Color.GREEN)
+
+ DebugDraw3D.draw_position($Misc/Position.global_transform, Color.BROWN)
+
+ DebugDraw3D.draw_gizmo($Misc/GizmoTransform.global_transform, DebugDraw3D.empty_color, true)
+ DebugDraw3D.draw_gizmo($Misc/GizmoOneColor.global_transform, Color.BROWN, true)
+ if true:
+ var _s123 = DebugDraw3D.new_scoped_config().set_center_brightness(0.5).set_no_depth_test(true)
+ DebugDraw3D.draw_gizmo($Misc/GizmoNormal.global_transform.orthonormalized(), DebugDraw3D.empty_color, false)
+
+ # Grids
+ _draw_zone_title_pos($Grids/GridCentered.global_position + Vector3(0, 1.5, 0), "Grids", 96, 36)
+
+ var tg : Transform3D = $Grids/Grid.global_transform
+ var tn : Vector3 = $Grids/Grid/Subdivision.transform.origin
+ DebugDraw3D.draw_grid(tg.origin, tg.basis.x, tg.basis.z, Vector2i(int(tn.x*10), int(tn.z*10)), Color.LIGHT_CORAL, false)
+
+ var tn1 = $Grids/GridCentered/Subdivision.transform.origin
+ DebugDraw3D.draw_grid_xf($Grids/GridCentered.global_transform, Vector2i(tn1.x*10, tn1.z*10))
+
+ if true:
+ var _s32 = DebugDraw3D.new_scoped_config().set_thickness(0.05)
+ DebugDraw3D.draw_box_xf($PostProcess.global_transform, Color.SEA_GREEN)
+
+ # Local transform
+ _draw_local_xf_box(%LocalTransformRecursiveOrigin.global_transform, 0.05, 10)
+
+ # 2D
+ DebugDraw2D.config.text_default_size = text_groups_default_font_size
+ DebugDraw2D.config.text_block_offset = text_groups_offset
+ DebugDraw2D.config.text_block_position = text_groups_position
+ DebugDraw2D.config.text_padding = text_groups_padding
+
+ DebugDraw2D.config.text_custom_font = custom_font
+
+ if test_text:
+ _text_tests()
+
+ # Lag Test
+ var lag_test_pos = $LagTest/RESET.get_animation("RESET").track_get_key_value(0,0)
+ _draw_zone_title_pos(lag_test_pos, "Lag test")
+
+ $LagTest.position = lag_test_pos + Vector3(sin(Time.get_ticks_msec() / 100.0) * 2.5, 0, 0)
+ DebugDraw3D.draw_box($LagTest.global_position, Quaternion.IDENTITY, Vector3.ONE * 2.01, Color.CHOCOLATE, true)
+
+ if more_test_cases:
+ for ray in $HitTest/RayEmitter.get_children():
+ ray.set_physics_process_internal(true)
+
+ _more_tests()
+ else:
+ for ray in $HitTest/RayEmitter.get_children():
+ ray.set_physics_process_internal(false)
+
+ _draw_other_world()
+
+ if draw_array_of_boxes:
+ _draw_array_of_boxes()
+
+
+func _text_tests():
+ DebugDraw2D.set_text("FPS", "%.2f" % Engine.get_frames_per_second(), 0, Color.GOLD)
+
+ if text_groups_show_examples:
+ if timer_text < 0:
+ DebugDraw2D.set_text("Some delayed text", "for 2.5s", -1, Color.BLACK, 2.5) # it's supposed to show text for 2.5 seconds
+ timer_text = 5
+
+ DebugDraw2D.begin_text_group("-- First Group --", 2, Color.LIME_GREEN, true, text_groups_title_font_size, text_groups_text_font_size)
+ DebugDraw2D.set_text("Simple text")
+ DebugDraw2D.set_text("Text", "Value", 0, Color.AQUAMARINE)
+ DebugDraw2D.set_text("Text out of order", null, -1, Color.SILVER)
+ DebugDraw2D.begin_text_group("-- Second Group --", 1, Color.BEIGE)
+ DebugDraw2D.set_text("Rendered frames", Engine.get_frames_drawn())
+ DebugDraw2D.end_text_group()
+
+ if text_groups_show_stats or text_groups_show_stats_2d:
+ DebugDraw2D.begin_text_group("-- Stats --", 3, Color.WHEAT)
+
+ var render_stats := DebugDraw3D.get_render_stats()
+ if render_stats && text_groups_show_stats:
+ DebugDraw2D.set_text("Total", render_stats.total_geometry)
+ DebugDraw2D.set_text("Instances", render_stats.instances + render_stats.instances_physics, 1)
+ DebugDraw2D.set_text("Lines", render_stats.lines + render_stats.lines_physics, 2)
+ DebugDraw2D.set_text("Total Visible", render_stats.total_visible, 3)
+ DebugDraw2D.set_text("Visible Instances", render_stats.visible_instances, 4)
+ DebugDraw2D.set_text("Visible Lines", render_stats.visible_lines, 5)
+
+ DebugDraw2D.set_text("---", null, 12)
+
+ DebugDraw2D.set_text("Culling time", "%.2f ms" % (render_stats.total_time_culling_usec / 1000.0), 13)
+ DebugDraw2D.set_text("Filling instances buffer", "%.2f ms" % (render_stats.time_filling_buffers_instances_usec / 1000.0), 14)
+ DebugDraw2D.set_text("Filling lines buffer", "%.2f ms" % (render_stats.time_filling_buffers_lines_usec / 1000.0), 15)
+ DebugDraw2D.set_text("Filling time", "%.2f ms" % (render_stats.total_time_filling_buffers_usec / 1000.0), 16)
+ DebugDraw2D.set_text("Total time", "%.2f ms" % (render_stats.total_time_spent_usec / 1000.0), 17)
+
+ DebugDraw2D.set_text("----", null, 32)
+
+ DebugDraw2D.set_text("Total Label3D", render_stats.nodes_label3d_exists_total, 33)
+ DebugDraw2D.set_text("Visible Label3D", render_stats.nodes_label3d_visible + render_stats.nodes_label3d_visible_physics, 34)
+
+ DebugDraw2D.set_text("-----", null, 48)
+
+ DebugDraw2D.set_text("Created scoped configs", "%d" % render_stats.created_scoped_configs, 49)
+
+ if text_groups_show_stats && text_groups_show_stats_2d:
+ DebugDraw2D.set_text("------", null, 64)
+
+ var render_stats_2d := DebugDraw2D.get_render_stats()
+ if render_stats_2d && text_groups_show_stats_2d:
+ DebugDraw2D.set_text("Text groups", render_stats_2d.overlay_text_groups, 96)
+ DebugDraw2D.set_text("Text lines", render_stats_2d.overlay_text_lines, 97)
+
+ DebugDraw2D.end_text_group()
+
+ if text_groups_show_hints:
+ DebugDraw2D.begin_text_group("controls", 1024, Color.WHITE, false)
+ if not Engine.is_editor_hint():
+ DebugDraw2D.set_text("WASD QE, LMB", "To move", 0)
+ DebugDraw2D.set_text("Alt: change render layers", DebugDraw3D.config.geometry_render_layers, 1)
+ if not OS.has_feature("web"):
+ DebugDraw2D.set_text("Ctrl: toggle anti-aliasing", "MSAA 4x" if get_viewport().msaa_3d == Viewport.MSAA_4X else "Disabled", 2)
+ DebugDraw2D.set_text("Down: freeze render", DebugDraw3D.config.freeze_3d_render, 3)
+ if Engine.is_editor_hint():
+ DebugDraw2D.set_text("Up: use scene camera", DebugDraw3D.config.force_use_camera_from_scene, 4)
+ DebugDraw2D.set_text("1,2,3: toggle debug", "%s, %s 😐, %s 😏" % [DebugDraw3D.debug_enabled, DebugDraw2D.debug_enabled, DebugDrawManager.debug_enabled], 5)
+ DebugDraw2D.set_text("Left: toggle frustum culling", DebugDraw3D.config.use_frustum_culling, 6)
+ DebugDraw2D.set_text("Right: draw bounds for culling", DebugDraw3D.config.visible_instance_bounds, 7)
+ DebugDraw2D.end_text_group()
+
+
+func _draw_zone_title(node: Node3D, title: String):
+ if draw_3d_text:
+ var _s1 = DebugDraw3D.new_scoped_config().set_text_outline_size(72)
+ DebugDraw3D.draw_text(node.global_position + node.global_basis.y * 0.85, title, 128)
+
+
+func _draw_zone_title_pos(pos: Vector3, title: String, font_size: int = 128, outline: int = 72):
+ if draw_3d_text:
+ var _s1 = DebugDraw3D.new_scoped_config().set_text_outline_size(outline)
+ DebugDraw3D.draw_text(pos, title, font_size)
+
+
+const _local_mul := 0.45
+const _local_mul_vec := Vector3(_local_mul, _local_mul, _local_mul)
+var __local_lines_cross_recursive = PackedVector3Array([Vector3(-0.5, -0.5, -0.5), Vector3(0.5, -0.5, 0.5), Vector3(-0.5, -0.5, 0.5), Vector3(0.5, -0.5, -0.5)])
+var __local_box_recursive = Transform3D.IDENTITY.rotated_local(Vector3.UP, deg_to_rad(30)).translated(Vector3(-0.25, -0.55, 0.25)).scaled(_local_mul_vec)
+var __local_sphere_recursive = Transform3D.IDENTITY.translated(Vector3(0.5, 0.55, -0.5)).scaled(_local_mul_vec)
+
+func _draw_local_xf_box(xf: Transform3D, thickness: float, max_depth: int, depth: int = 0):
+ if depth >= max_depth:
+ return
+
+ var _s1 = DebugDraw3D.new_scoped_config().set_thickness(thickness).set_transform(xf)
+
+ # a box with a small offset
+ DebugDraw3D.draw_box_xf(Transform3D(Basis(), Vector3(0, 0.001, 0)), Color.BROWN)
+ # a box and a stand for the next depth
+ DebugDraw3D.draw_box_xf(__local_box_recursive, Color.CHARTREUSE)
+ # just a sphere and lines
+ DebugDraw3D.draw_sphere_xf(__local_sphere_recursive, Color.DARK_ORANGE)
+ _s1.set_thickness(0)
+ DebugDraw3D.draw_lines(__local_lines_cross_recursive, Color.CRIMSON)
+
+ # A simple animation generator with descent into the depth of the scene
+ if false:
+ var anim: Animation = %RecursiveTransformTest.get_animation("recursive")
+ # clear keys
+ if depth == 0: for i in anim.track_get_key_count(0): anim.track_remove_key(0, 0); anim.track_remove_key(1, 0)
+
+ var time = depth * 2
+ var s_xf = xf * __local_sphere_recursive
+ var next_s_xf = (xf * __local_box_recursive.translated(__local_box_recursive.basis.y)) * __local_sphere_recursive
+ var get_sphere_pos = func(l_xf): return l_xf.origin + (l_xf).basis.y
+ anim.position_track_insert_key(0, time, get_sphere_pos.call(s_xf))
+ anim.rotation_track_insert_key(1, time, Transform3D(Basis(), get_sphere_pos.call(s_xf)).looking_at(get_sphere_pos.call(next_s_xf), xf.basis.y).basis.get_rotation_quaternion())
+
+ _draw_local_xf_box(xf * __local_box_recursive.translated(__local_box_recursive.basis.y), thickness * _local_mul, max_depth, depth + 1)
+
+
+func _draw_other_world():
+ var _w1 = DebugDraw3D.new_scoped_config().set_viewport(%OtherWorldBox.get_viewport())
+ DebugDraw3D.draw_box_xf(%OtherWorldBox.global_transform.rotated_local(Vector3(1,1,-1).normalized(), wrapf(Time.get_ticks_msec() / 1000.0, 0, TAU)), Color.SANDY_BROWN)
+ DebugDraw3D.draw_box_xf(%OtherWorldBox.global_transform.rotated_local(Vector3(-1,1,-1).normalized(), wrapf(Time.get_ticks_msec() / -1000.0, 0, TAU) - PI/4), Color.SANDY_BROWN)
+
+ if draw_3d_text:
+ var angle = wrapf(Time.get_ticks_msec() / 1000.0, 0, TAU)
+ if true:
+ var _w2 = DebugDraw3D.new_scoped_config().set_text_font(custom_3d_font)
+ DebugDraw3D.draw_text(%OtherWorldBox.global_position + Vector3(cos(angle), -0.25, sin(angle)), "Hello world!", 32, Color.CRIMSON, 0)
+
+ if true:
+ var _w3 = DebugDraw3D.new_scoped_config().set_no_depth_test(true).set_text_outline_color(Color.INDIAN_RED).set_text_outline_size(6)
+ DebugDraw3D.draw_text(%OtherWorldBox.global_position + Vector3(cos(angle), +0.25, sin(-angle)), "World without depth", 20, Color.PINK, 0)
+
+
+func _draw_rays_casts():
+ # Line hits render
+ _draw_zone_title_pos(%HitTestSphere.global_position, "Line hits", 96, 36)
+
+ for ray in $HitTest/RayEmitter.get_children():
+ if ray is RayCast3D:
+ ray.force_raycast_update()
+ DebugDraw3D.draw_line_hit(ray.global_position, ray.to_global(ray.target_position), ray.get_collision_point(), ray.is_colliding(), 0.3)
+
+
+func _more_tests():
+ # Delayed line render
+ if true:
+ var _a12 = DebugDraw3D.new_scoped_config().set_thickness(0.035)
+ DebugDraw3D.draw_line($LagTest.global_position + Vector3.UP, $LagTest.global_position + Vector3(0,3,sin(Time.get_ticks_msec() / 50.0)), DebugDraw3D.empty_color, 0.35)
+
+ if draw_3d_text:
+ DebugDraw3D.draw_text($LagTest.global_position + Vector3(0,3,sin(Time.get_ticks_msec() / 50.0)), "%.1f" % sin(Time.get_ticks_msec() / 50.0), 16, DebugDraw3D.empty_color, 0.35)
+
+ # Draw plane
+ if true:
+ var _s11 = DebugDraw3D.new_scoped_config().set_thickness(0.02).set_plane_size(10)
+
+ var pl_node: Node3D = $PlaneOrigin
+ var xf: Transform3D = pl_node.global_transform
+ var normal: = xf.basis.y.normalized()
+ var plane = Plane(normal, xf.origin.dot(normal))
+
+ var vp: Viewport = get_viewport()
+ if Engine.is_editor_hint() and Engine.get_singleton(&"EditorInterface").get_editor_viewport_3d(0):
+ vp = Engine.get_singleton(&"EditorInterface").get_editor_viewport_3d(0)
+
+ var cam = vp.get_camera_3d()
+ if cam:
+ var dir = vp.get_camera_3d().project_ray_normal(vp.get_mouse_position())
+ var intersect = plane.intersects_ray(cam.global_position, dir)
+
+ DebugDraw3D.draw_plane(plane, Color.CORAL * Color(1,1,1, 0.4), pl_node.global_position)
+ if intersect and intersect.distance_to(pl_node.global_position) < _s11.get_plane_size() * 0.5:
+ # Need to test different colors on both sides of the plane
+ var col = Color.FIREBRICK if plane.is_point_over(cam.global_position) else Color.AQUAMARINE
+ DebugDraw3D.draw_sphere(intersect, 0.3, col)
+
+
+func _draw_array_of_boxes():
+ # Lots of boxes to check performance..
+ var x_size := 50
+ var y_size := 50
+ var z_size := 3
+ var mul := 1
+ var cubes_max_time := 1.25
+ var show_text := draw_text_with_boxes
+ var cfg = DebugDraw3D.new_scoped_config()
+
+ if draw_1m_boxes:
+ x_size = 100
+ y_size = 100
+ z_size = 100
+ mul = 4
+ cubes_max_time = 60
+ show_text = false
+
+ var size := Vector3.ONE
+ var half_size := size * 0.5
+
+ if timer_cubes < 0:
+ var _start_time = Time.get_ticks_usec()
+ for x in x_size:
+ for y in y_size:
+ for z in z_size:
+ cfg.set_thickness(randf_range(0, 0.1))
+ var pos := Vector3(x * mul, (-4-z) * mul, y * mul) + global_position
+ DebugDraw3D.draw_box(pos, Quaternion.IDENTITY, size, DebugDraw3D.empty_color, false, cubes_max_time)
+
+ if show_text and z == 0:
+ DebugDraw3D.draw_text(pos + half_size, str(pos), 32, DebugDraw3D.empty_color, cubes_max_time)
+ #print("Draw Cubes: %.3fms" % ((Time.get_ticks_usec() - _start_time) / 1000.0))
+ timer_cubes = cubes_max_time
+
+
+func _ready() -> void:
+ _update_keys_just_press()
+
+ await get_tree().process_frame
+
+ # this check is required for inherited scenes, because an instance of this
+ # script is created first, and then overridden by another
+ if not is_inside_tree():
+ return
+
+ DebugDraw2D.config.text_background_color = Color(0.3, 0.3, 0.3, 0.8)
+
+
+func _is_key_just_pressed(key):
+ if (button_presses[key] == 1):
+ button_presses[key] = 2
+ return true
+ return false
+
+
+func _update_keys_just_press():
+ var set_key = func (k: Key):
+ if Input.is_key_pressed(k) and button_presses.has(k):
+ if button_presses[k] == 0:
+ return 1
+ else:
+ return button_presses[k]
+ else:
+ return 0
+ button_presses[KEY_LEFT] = set_key.call(KEY_LEFT)
+ button_presses[KEY_UP] = set_key.call(KEY_UP)
+ button_presses[KEY_CTRL] = set_key.call(KEY_CTRL)
+ button_presses[KEY_F1] = set_key.call(KEY_F1)
+ button_presses[KEY_1] = set_key.call(KEY_1)
+ button_presses[KEY_2] = set_key.call(KEY_2)
+ button_presses[KEY_3] = set_key.call(KEY_3)
+
+
+func _update_timers(delta : float):
+ timer_1 -= delta
+ timer_cubes -= delta
+ timer_3 -= delta
+ timer_text -= delta
+
+
+func _notification(what: int) -> void:
+ if what == NOTIFICATION_EDITOR_PRE_SAVE or what == NOTIFICATION_EXIT_TREE:
+ _thread_stop()
+
+
+func _call_from_thread():
+ if tests_use_threads and (not test_thread or not test_thread.is_alive()):
+ test_thread_closing = false
+ test_thread = Thread.new()
+ test_thread.start(_thread_body)
+ elif not tests_use_threads and (test_thread and test_thread.is_alive()):
+ _thread_stop()
+
+
+func _thread_stop():
+ if test_thread and test_thread.is_alive():
+ tests_use_threads = false
+ test_thread_closing = true
+ test_thread.wait_to_finish()
+
+
+func _thread_body():
+ print("Thread started!")
+ while not test_thread_closing:
+ DebugDraw3D.draw_box(Vector3(0,-1,0), Quaternion.IDENTITY, Vector3.ONE, Color.BROWN, true, 0.016)
+
+ var boxes = 10
+ for y in boxes:
+ var offset := sin(TAU/boxes * y + wrapf(Time.get_ticks_msec() / 100.0, 0, TAU))
+ var pos := Vector3(offset, y, 0)
+ DebugDraw3D.draw_box(pos, Quaternion.IDENTITY, Vector3.ONE, Color.GREEN_YELLOW, true, 0.016)
+ DebugDraw3D.draw_text(pos, str(y), 64, Color.WHITE , 0.016)
+
+ if y == 0:
+ DebugDraw2D.set_text("thread. sin", offset)
+
+ OS.delay_msec(16)
+ print("Thread finished!")
diff --git a/demo/examples_dd3d/DebugDrawDemoScene.gd.uid b/demo/examples_dd3d/DebugDrawDemoScene.gd.uid
new file mode 100644
index 0000000..39b5273
--- /dev/null
+++ b/demo/examples_dd3d/DebugDrawDemoScene.gd.uid
@@ -0,0 +1 @@
+uid://ba2ie81p2x3x7
diff --git a/demo/examples_dd3d/DebugDrawDemoScene.tscn b/demo/examples_dd3d/DebugDrawDemoScene.tscn
new file mode 100644
index 0000000..2193d51
--- /dev/null
+++ b/demo/examples_dd3d/DebugDrawDemoScene.tscn
@@ -0,0 +1,1042 @@
+[gd_scene load_steps=43 format=3 uid="uid://c3sccy6x0ht5j"]
+
+[ext_resource type="Script" path="res://examples_dd3d/DebugDrawDemoScene.gd" id="1"]
+[ext_resource type="FontFile" uid="uid://erdgllynwqkw" path="res://examples_dd3d/Roboto-Bold.ttf" id="2_aedbq"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_camera_movement.gd" id="3_3m1mp"]
+[ext_resource type="FontFile" uid="uid://7am1h57ldd6" path="res://examples_dd3d/PixelatedElegance.ttf" id="3_tkhi8"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_music_visualizer.gd" id="4_eq2lt"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_settings_panel.gd" id="5_31v5h"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_web_docs_version_select.gd" id="6_07f7q"]
+
+[sub_resource type="Animation" id="Animation_ucqh5"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("..:tests_use_threads")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [false]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath(".:mesh:material:shader_parameter/albedo_texture")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [null]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_cq37i"]
+_data = {
+"RESET": SubResource("Animation_ucqh5")
+}
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_87638"]
+sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+
+[sub_resource type="Sky" id="Sky_4jfme"]
+sky_material = SubResource("ProceduralSkyMaterial_87638")
+
+[sub_resource type="Environment" id="Environment_38m85"]
+sky = SubResource("Sky_4jfme")
+tonemap_mode = 2
+fog_light_energy = 0.41
+fog_density = 0.0757
+fog_height = 0.5
+fog_height_density = 4.6102
+
+[sub_resource type="Animation" id="9"]
+resource_name = "New Anim"
+length = 1.5
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Spatial2:transform")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.7),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 1), Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.31558, 1)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Spatial5:transform")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1, 1), Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1.5801, 1)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Spatial4:transform")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 1),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, 0, 1.53767), Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, -0.791383, 1.53767)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Spatial7:position")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0.4, 1),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(1.33, -0.119, -0.025), Vector3(1.32989, -0.583818, -0.025198)]
+}
+
+[sub_resource type="Animation" id="10"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Spatial2:transform")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 1)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Spatial5:transform")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1, 1)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Spatial4:transform")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, 0, 1.53767)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Spatial7:position")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(1.32989, -0.583818, -0.025198)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_nj4nv"]
+_data = {
+"New Anim": SubResource("9"),
+"RESET": SubResource("10")
+}
+
+[sub_resource type="Shader" id="Shader_621vv"]
+code = "shader_type spatial;
+render_mode unshaded;
+
+uniform sampler2D albedo_texture : source_color;
+
+void fragment() {
+ ALBEDO = texture(albedo_texture,UV).rgb;
+}
+"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_ho0aq"]
+render_priority = 0
+shader = SubResource("Shader_621vv")
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_c6mie"]
+material = SubResource("ShaderMaterial_ho0aq")
+size = Vector2(4, 4)
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_tigpa"]
+radius = 0.395
+height = 1.825
+
+[sub_resource type="BoxMesh" id="BoxMesh_b14rm"]
+
+[sub_resource type="Animation" id="Animation_w1m7s"]
+length = 0.001
+tracks/0/type = "position_3d"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = PackedFloat32Array(0, 1, -6.988, 10.986, 29.206)
+tracks/1/type = "rotation_3d"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = PackedFloat32Array(0, 1, -0.16935, 0, 0, 0.985556)
+
+[sub_resource type="Animation" id="Animation_h4e34"]
+resource_name = "recursive"
+length = 18.0
+tracks/0/type = "position_3d"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = PackedFloat32Array(0, 1, -4.43594, -0.0101277, 8.56634, 2, 1, -4.63897, -0.279309, 8.78785, 4, 1, -4.65315, -0.433226, 8.88306, 6, 1, -4.6267, -0.506496, 8.90766, 8, 1, -4.60482, -0.535954, 8.90541, 10, 1, -4.59385, -0.545658, 8.89771, 12, 1, -4.59006, -0.547969, 8.89174, 14, 1, -4.58948, -0.548125, 8.88844, 16, 1, -4.58985, -0.547923, 8.887, 18, 1, -4.5903, -0.547799, 8.8865)
+tracks/1/type = "rotation_3d"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = PackedFloat32Array(0, 1, 0.190215, 0.859282, 0.43192, 0.197228, 2, 1, 0.183697, 0.853511, 0.484111, -0.0584063, 4, 1, 0.164659, 0.789579, 0.503307, -0.310057, 6, 1, -0.134401, -0.671836, -0.48821, 0.540577, 8, 1, -0.0949895, -0.508291, -0.439844, 0.734271, 10, 1, -0.0490975, -0.310157, -0.361506, 0.877898, 12, 1, 0.000153813, -0.090853, -0.258524, 0.961723, 14, 1, 0.0493618, 0.134434, -0.138051, 0.980017, 16, 1, 0.0953059, 0.351263, -0.00774742, 0.931381, 18, 1, 0.13493, 0.543814, 0.122741, 0.819143)
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_rcwnp"]
+_data = {
+"RESET": SubResource("Animation_w1m7s"),
+"recursive": SubResource("Animation_h4e34")
+}
+
+[sub_resource type="SphereShape3D" id="4"]
+radius = 1.0
+
+[sub_resource type="StandardMaterial3D" id="5"]
+transparency = 1
+albedo_color = Color(0.54902, 0.54902, 0.729412, 0.403922)
+emission_enabled = true
+emission = Color(0.752941, 0.741176, 0.862745, 1)
+
+[sub_resource type="Animation" id="6"]
+resource_name = "New Anim"
+length = 3.0
+loop_mode = 1
+tracks/0/type = "rotation_3d"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("RayEmitter")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = PackedFloat32Array(0, 1, 0, 0, 0, 1, 1.3, 1, 1.31237e-06, -9.55543e-07, -2.2333e-06, 1, 2.3, 1, -0.158418, 0.0315871, 0.980558, -0.111409)
+tracks/1/type = "position_3d"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("RayEmitter")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = PackedFloat32Array(0, 1, -1.03574, 2.47907, -0.819963, 0.5, 1, 0.914907, 1.78507, -0.103575, 1.3, 1, 0.00863326, 2.47907, -0.595551, 2.3, 1, 1.00051, 1.4046, 1.02585)
+
+[sub_resource type="Animation" id="7"]
+length = 0.001
+tracks/0/type = "position_3d"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("RayEmitter")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = PackedFloat32Array(0, 1, -1.03574, 2.47907, -0.819963)
+tracks/1/type = "rotation_3d"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("RayEmitter")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = PackedFloat32Array(0, 1, 0, 0, 0, 1)
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_vh8ml"]
+_data = {
+"New Anim": SubResource("6"),
+"RESET": SubResource("7")
+}
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_rbfyn"]
+transparency = 1
+cull_mode = 2
+shading_mode = 0
+albedo_color = Color(0.215686, 0.215686, 0.215686, 0.764706)
+
+[sub_resource type="QuadMesh" id="QuadMesh_1t0id"]
+material = SubResource("StandardMaterial3D_rbfyn")
+orientation = 1
+
+[sub_resource type="StandardMaterial3D" id="1"]
+shading_mode = 0
+albedo_color = Color(0.533333, 0.105882, 0.105882, 1)
+
+[sub_resource type="Animation" id="8"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath(".:position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(7, -2, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_a7f1a"]
+_data = {
+"RESET": SubResource("8")
+}
+
+[sub_resource type="Shader" id="Shader_3cmiq"]
+code = "shader_type spatial;
+render_mode unshaded;
+
+uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
+
+void fragment() {
+ vec4 col = texture(screen_texture, SCREEN_UV);
+ ALBEDO = col.brg;
+ ALPHA = col.a;
+}
+"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_t3isk"]
+render_priority = 0
+shader = SubResource("Shader_3cmiq")
+
+[sub_resource type="BoxMesh" id="BoxMesh_0xv07"]
+material = SubResource("ShaderMaterial_t3isk")
+
+[sub_resource type="Gradient" id="Gradient_tup4c"]
+offsets = PackedFloat32Array(0.00471698, 0.316038, 0.646226, 1)
+colors = PackedColorArray(0, 0.0156863, 1, 1, 0.0988327, 1, 0.122977, 1, 1, 0.111986, 0.118936, 1, 0, 0.0156863, 1, 1)
+
+[sub_resource type="Animation" id="Animation_n750a"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("../MusicPlayer:stream")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [null]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_0ity1"]
+_data = {
+"RESET": SubResource("Animation_n750a")
+}
+
+[sub_resource type="Theme" id="3"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_oj5gf"]
+content_margin_top = 5.0
+content_margin_bottom = 7.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_boyhr"]
+content_margin_left = 5.0
+content_margin_top = 5.0
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+bg_color = Color(0.0705882, 0.0705882, 0.0705882, 0.784314)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[node name="DebugDrawDemoScene" type="Node3D"]
+process_priority = 1
+script = ExtResource("1")
+custom_font = ExtResource("2_aedbq")
+custom_3d_font = ExtResource("3_tkhi8")
+text_groups_position = 2
+
+[node name="RESET" type="AnimationPlayer" parent="."]
+root_node = NodePath("../OtherWorld")
+libraries = {
+"": SubResource("AnimationLibrary_cq37i")
+}
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
+visible = false
+directional_shadow_max_distance = 200.0
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_38m85")
+
+[node name="Camera" type="Camera3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.942642, 0.333808, 0, -0.333808, 0.942642, -6.988, 10.986, 29.206)
+cull_mask = 1
+current = true
+fov = 53.0
+near = 0.001
+far = 100.0
+script = ExtResource("3_3m1mp")
+
+[node name="Panel" type="PanelContainer" parent="."]
+visible = false
+custom_minimum_size = Vector2(300, 300)
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_top = -300.0
+offset_right = 300.0
+grow_vertical = 0
+
+[node name="ViewportContainer" type="SubViewportContainer" parent="Panel"]
+layout_mode = 2
+
+[node name="Viewport" type="SubViewport" parent="Panel/ViewportContainer"]
+handle_input_locally = false
+size = Vector2i(300, 300)
+render_target_update_mode = 0
+
+[node name="CameraLayer2_5" type="Camera3D" parent="Panel/ViewportContainer/Viewport"]
+transform = Transform3D(1, 0, 0, 0, 0.34202, 0.939693, 0, -0.939693, 0.34202, -3.988, 39.474, 14.053)
+cull_mask = 2
+current = true
+fov = 38.8
+near = 2.63
+far = 52.5
+
+[node name="Zones" type="Node3D" parent="."]
+
+[node name="SpheresBox" type="Node3D" parent="Zones"]
+unique_name_in_owner = true
+transform = Transform3D(8.3761, 0, 0, 0, 4.89771, 0, 0, 0, 9.36556, -11.1864, 0.645876, -7.86506)
+
+[node name="CylindersBox" type="Node3D" parent="Zones"]
+unique_name_in_owner = true
+transform = Transform3D(9.78549, 0, 0, 0, 4.20302, 0, 0, 0, 5.62455, -23.6827, -0.015712, -6.19233)
+
+[node name="BoxesBox" type="Node3D" parent="Zones"]
+unique_name_in_owner = true
+transform = Transform3D(10.0513, 0, 0, 0, 5.99877, 0, 0, 0, 12.1174, -16.0257, -0.206735, 6.27643)
+
+[node name="LinesBox" type="Node3D" parent="Zones"]
+unique_name_in_owner = true
+transform = Transform3D(10.7186, 0, 0, 0, 3.9777, 0, 0, 0, 7.05487, 10.6302, 1.91174, -7.11416)
+
+[node name="PathsBox" type="Node3D" parent="Zones"]
+unique_name_in_owner = true
+transform = Transform3D(5.95153, 0, 0, 0, 7.71864, 0, 0, 0, 6.31617, 0.184938, 1.12881, -7.18731)
+
+[node name="MiscBox" type="Node3D" parent="Zones"]
+unique_name_in_owner = true
+transform = Transform3D(4.38886, 0, 0, 0, 2.72083, 0, 0, 0, 8.81683, -5.69728, -0.206735, 5.58232)
+
+[node name="LinesAnim" type="AnimationPlayer" parent="."]
+root_node = NodePath("../LinePath")
+libraries = {
+"": SubResource("AnimationLibrary_nj4nv")
+}
+autoplay = "New Anim"
+
+[node name="LinePath" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.0543, -8)
+
+[node name="Spatial" type="Node3D" parent="LinePath"]
+
+[node name="Spatial2" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 1)
+
+[node name="Spatial3" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.462435, 0, 3)
+
+[node name="Spatial4" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, 0, 1.53767)
+
+[node name="Spatial5" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1, 1)
+
+[node name="Spatial6" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, -1)
+
+[node name="Spatial7" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.32989, -0.583818, -0.025198)
+
+[node name="Cylinders" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -23.5266, 4.76837e-07, -5.82213)
+
+[node name="Cylinder1" type="Node3D" parent="Cylinders"]
+transform = Transform3D(1.20775, 0.591481, -3.4521e-07, 0.554162, -1.12986, 0.858242, 0.208031, -0.424147, -2.28622, -3.03832, 0, -0.377882)
+
+[node name="Cylinder2" type="Node3D" parent="Cylinders"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.234978, -0.4237, 0.332998)
+
+[node name="Cylinder3" type="Node3D" parent="Cylinders"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.35527, -0.655492, -0.352802)
+
+[node name="1" type="Node3D" parent="Cylinders/Cylinder3"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.419773, -2.38419e-07, -1.40591)
+
+[node name="2" type="Node3D" parent="Cylinders/Cylinder3"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.01018, 0.486778, 1.32635)
+
+[node name="Spheres" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.1201, 0.166728, -7.893)
+
+[node name="SphereTransform" type="Node3D" parent="Spheres"]
+transform = Transform3D(3.018, 0, 0, 0, 0.945452, -3.30182, 0, 1.04515, 2.98686, -2.14465, 4.76837e-07, 2.11952)
+
+[node name="SphereHDTransform" type="Node3D" parent="Spheres"]
+transform = Transform3D(1.26984, 1.16629, -2.42095, 0.098772, 0.80937, 4.21576, -2.65493, 0.587941, -1.00109, -2.13175, 4.76837e-07, -2.62531)
+
+[node name="SpherePosition" type="Node3D" parent="Spheres"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.76745, 0.458486, 1.95921)
+
+[node name="Boxes" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15.2493, 0, 6.42043)
+
+[node name="Box1" type="Node3D" parent="Boxes"]
+transform = Transform3D(2.90583, -0.000527017, -5.34615, 0.00469241, 3.92788, 0.0141019, 0.556318, -0.0303774, 1.91619, -0.961557, 0, -3.78672)
+rotation_edit_mode = 2
+
+[node name="Box2" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.531922, -1.34723, 1.44924)
+
+[node name="Box3" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.34837, -1.08298, 4.36414)
+
+[node name="AABB_fixed" type="Node3D" parent="Boxes"]
+transform = Transform3D(0.834492, 0, -0.551019, 0, 1, 0, 0.55102, 0, 0.834493, -3.71325, -1.03995, 0.470324)
+
+[node name="AABB" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.99963, -0.869998, 0.205034)
+
+[node name="a" type="Node3D" parent="Boxes/AABB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, 1.48526, -1.45318, 1.96619)
+
+[node name="b" type="Node3D" parent="Boxes/AABB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, -1.24128, 1.47773, -2.13102)
+
+[node name="BoxAB" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.66169, -2.2624, 4.04042)
+
+[node name="a" type="Node3D" parent="Boxes/BoxAB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, 0.556136, -0.666145, 0.951601)
+
+[node name="b" type="Node3D" parent="Boxes/BoxAB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, -0.548804, 0.715255, -0.942184)
+
+[node name="o" type="Node3D" parent="Boxes/BoxAB"]
+transform = Transform3D(0.826805, 0.360538, 0.431748, -0.102949, 0.851596, -0.51399, -0.552988, 0.380522, 0.741221, 0, 0, 0)
+metadata/_edit_group_ = true
+
+[node name="up" type="Node3D" parent="Boxes/BoxAB/o"]
+transform = Transform3D(1, -1.49012e-08, 0, -1.04308e-07, 1, 0, 0, 0, 1, 0, 0.553809, -0.331842)
+
+[node name="BoxABEdge" type="Node3D" parent="Boxes"]
+transform = Transform3D(0.965926, -0.0669873, -0.25, 0, 0.965926, -0.258819, 0.258819, 0.25, 0.933013, 0.348115, -1.30239, 4.88007)
+
+[node name="a" type="Node3D" parent="Boxes/BoxABEdge"]
+transform = Transform3D(0.241143, 0.650584, 0.720132, -0.123077, 0.756539, -0.642262, -0.962654, 0.066246, 0.262507, 0.384618, -0.635015, 0.0956135)
+
+[node name="b" type="Node3D" parent="Boxes/BoxABEdge"]
+transform = Transform3D(0.241143, 0.650584, 0.720133, -0.123077, 0.756539, -0.642261, -0.962654, 0.0662459, 0.262507, -0.287622, 0.997905, -0.144578)
+
+[node name="o" type="Node3D" parent="Boxes/BoxABEdge"]
+transform = Transform3D(1, 1.49012e-08, 2.98023e-08, 7.45058e-09, 1, -1.49012e-08, -1.49012e-08, -1.49012e-08, 1, 0, 0, 0)
+metadata/_edit_group_ = true
+
+[node name="up" type="Node3D" parent="Boxes/BoxABEdge/o"]
+transform = Transform3D(1, -7.45058e-09, 0, -7.45058e-09, 1, 0, 2.98023e-08, -1.49012e-08, 1, -9.53674e-07, 0.6, 0)
+
+[node name="OtherWorld" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.53219, -2.5, 5.30229)
+mesh = SubResource("PlaneMesh_c6mie")
+skeleton = NodePath("")
+
+[node name="RESET" type="AnimationPlayer" parent="OtherWorld"]
+libraries = {
+"": SubResource("AnimationLibrary_cq37i")
+}
+
+[node name="SubViewport" type="SubViewport" parent="OtherWorld"]
+own_world_3d = true
+handle_input_locally = false
+render_target_update_mode = 4
+
+[node name="SubViewportContainer" type="SubViewportContainer" parent="OtherWorld/SubViewport"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+stretch = true
+
+[node name="SubViewport" type="SubViewport" parent="OtherWorld/SubViewport/SubViewportContainer"]
+handle_input_locally = false
+render_target_update_mode = 4
+
+[node name="Camera3D" type="Camera3D" parent="OtherWorld/SubViewport/SubViewportContainer/SubViewport"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57063, 0.6, 7.25557)
+current = true
+far = 5.0
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="OtherWorld/SubViewport/SubViewportContainer/SubViewport"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57063, 0.6, 5.72253)
+mesh = SubResource("CapsuleMesh_tigpa")
+skeleton = NodePath("../../..")
+
+[node name="OtherWorldBox" type="Node3D" parent="OtherWorld/SubViewport/SubViewportContainer/SubViewport"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57063, 0.6, 5.72253)
+
+[node name="Misc" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.68259, 0, 4.46741)
+
+[node name="Billboard" type="Node3D" parent="Misc"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.403353, -0.331599, 2.22542)
+
+[node name="Arrow" type="Node3D" parent="Misc"]
+transform = Transform3D(0.802141, -0.286294, -0.524028, -0.539546, 0.0285125, -0.841473, 0.25585, 0.957718, -0.131597, -0.475607, -0.670307, 2.30581)
+
+[node name="Position" type="Node3D" parent="Misc"]
+transform = Transform3D(1.51514, 0.589536, 1.00858, -1.34875, 0.662262, 1.133, 0, -0.462445, 2.90833, 0.853743, 0.0843356, -1.73676)
+
+[node name="GizmoNormal" type="Node3D" parent="Misc"]
+transform = Transform3D(0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, 0.890203, -0.306246, 0.356159)
+
+[node name="ZDepthTestCube" type="MeshInstance3D" parent="Misc/GizmoNormal"]
+unique_name_in_owner = true
+transform = Transform3D(0.591801, 0, 4.47035e-08, 0, 0.591801, 0, -4.47035e-08, 0, 0.591801, 0, 0, 0)
+mesh = SubResource("BoxMesh_b14rm")
+
+[node name="GizmoTransform" type="Node3D" parent="Misc"]
+transform = Transform3D(0.879881, 0.248446, -0.405072, -0.346604, 0.918688, -0.189411, 0.325077, 0.307059, 0.894449, -0.838587, -0.458, -0.176491)
+
+[node name="GizmoOneColor" type="Node3D" parent="Misc"]
+transform = Transform3D(0.385568, 0.0415614, 0.921743, 0.082879, 0.993386, -0.0794599, -0.91895, 0.107031, 0.379573, -0.838587, -0.139425, -1.93055)
+
+[node name="LocalTransformRecursiveOrigin" type="Node3D" parent="Misc"]
+unique_name_in_owner = true
+transform = Transform3D(0.785829, 0.365814, 0.498651, 0.0146361, 0.795073, -0.606337, -0.618271, 0.483775, 0.619438, 0.92688, -0.70441, 4.03998)
+
+[node name="RecursiveTransformTest" type="AnimationPlayer" parent="Misc/LocalTransformRecursiveOrigin"]
+unique_name_in_owner = true
+root_node = NodePath("../../..")
+libraries = {
+"": SubResource("AnimationLibrary_rcwnp")
+}
+
+[node name="HitTest" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.724359, -1.03227, 7.85404)
+
+[node name="StaticBody" type="StaticBody3D" parent="HitTest"]
+
+[node name="CollisionShape" type="CollisionShape3D" parent="HitTest/StaticBody"]
+shape = SubResource("4")
+
+[node name="HitTestSphere" type="CSGSphere3D" parent="HitTest/StaticBody"]
+unique_name_in_owner = true
+radius = 1.0
+radial_segments = 16
+rings = 10
+material = SubResource("5")
+
+[node name="RayEmitter" type="Node3D" parent="HitTest"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.03574, 2.47907, -0.819963)
+
+[node name="RayCast" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.732104, 0, -0.814761)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast2" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.684873, 0, -0.791145)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast3" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.708488, 0, 0.543175)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast4" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.708489, 0, 0.566791)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast5" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(0.974217, -0.225614, 0, 0.225614, 0.974217, 0, 0, 0, 1, -0.447564, 0, -0.259778)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast6" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(0.935992, 0.352021, 0, -0.352021, 0.935992, 0, 0, 0, 1, 0.35227, -0.245904, -0.25849)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayEmitterAnimationPlayer" type="AnimationPlayer" parent="HitTest"]
+unique_name_in_owner = true
+libraries = {
+"": SubResource("AnimationLibrary_vh8ml")
+}
+autoplay = "New Anim"
+
+[node name="Grids" type="Node3D" parent="."]
+transform = Transform3D(0.707106, 0, -0.707108, 0, 1, 0, 0.707108, 0, 0.707106, 0.730597, -2.5, 2.76274)
+
+[node name="GridCentered" type="Node3D" parent="Grids"]
+transform = Transform3D(1.74492, 0.723785, -1.74493, -1.24976, -7.72562e-08, -1.24975, -1.74493, 0.723783, 1.74493, 1.74919, -0.0010004, 1.75466)
+rotation_edit_mode = 2
+
+[node name="Subdivision" type="Node3D" parent="Grids/GridCentered"]
+transform = Transform3D(1, -6.03961e-14, -2.68221e-07, 3.55271e-13, 1, 1.42109e-14, -1.19209e-07, 1.1724e-13, 1, -0.2, 4.76837e-07, 0.4)
+
+[node name="Grid" type="Node3D" parent="Grids"]
+transform = Transform3D(5, 0, 4.76837e-07, 0, 1, 0, -4.76837e-07, 0, 5, 0, 0, 0)
+
+[node name="Subdivision" type="Node3D" parent="Grids/Grid"]
+transform = Transform3D(1, 0, -2.98023e-08, 0, 0.999999, 1.90735e-05, 0, 4.65661e-10, 0.999999, 1, 0, 1)
+
+[node name="PlaneOrigin" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 11.0482, 7.33669, -13.1715)
+mesh = SubResource("QuadMesh_1t0id")
+
+[node name="Lines" type="Node3D" parent="."]
+transform = Transform3D(1.51514, 0.589536, 1.00858, -1.34875, 0.662262, 1.133, 0, -0.462445, 2.90833, 10.2488, -0.331599, -10.3326)
+
+[node name="1" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 9.40939e-07, 1, 0, -2.76085e-08, -1.49012e-08, 1, -1.46213, -4.03317, 0.61692)
+
+[node name="2" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 9.40939e-07, 1, 0, -2.76085e-08, -1.49012e-08, 1, -1.01875, -1.79584, -0.163045)
+
+[node name="3" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 6.87561e-07, 1, 0, -2.87275e-08, -1.49012e-08, 1, -0.1559, -0.407045, 0.0523388)
+
+[node name="4" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 4.9239e-07, 1, 0, -3.40677e-08, -1.49012e-08, 1, 1.18591, 1.8987, 0.301906)
+
+[node name="5" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998871, -0.0207882, -0.0355643, 0.0855375, -0.5714, -2.68836, 0.0136011, -0.249864, 0.572532, 1.43126, 0.26242, 1.92347)
+
+[node name="6" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998872, -0.0207882, -0.0355643, 0.085537, -0.5714, -2.68836, 0.0136012, -0.249864, 0.572533, 1.43441, 1.50606, 1.20028)
+
+[node name="7" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998873, -0.0207882, -0.0355641, 0.0855357, -0.5714, -2.68836, 0.0136014, -0.249864, 0.572533, 0.0511096, -1.3236, 1.06745)
+
+[node name="8" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998873, -0.0207882, -0.0355641, 0.0855353, -0.5714, -2.68836, 0.0136016, -0.249864, 0.572533, -1.01372, -3.80486, 1.25019)
+
+[node name="Target" type="Node3D" parent="Lines"]
+transform = Transform3D(1, -2.7352e-06, 2.60722e-07, 4.10378e-06, 1, 0, -4.28605e-07, -1.49012e-08, 1, -0.69134, 0.176475, 1.30597)
+
+[node name="LagTest" type="CSGBox3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7, -2, 0)
+size = Vector3(2, 2, 2)
+material = SubResource("1")
+
+[node name="RESET" type="AnimationPlayer" parent="LagTest"]
+libraries = {
+"": SubResource("AnimationLibrary_a7f1a")
+}
+
+[node name="PostProcess" type="MeshInstance3D" parent="."]
+transform = Transform3D(-2.18557e-07, 0, 1.5, 0, 5, 0, -5, 0, -6.55671e-08, 16, 0, 0)
+mesh = SubResource("BoxMesh_0xv07")
+skeleton = NodePath("../Lines")
+
+[node name="MusicVisualizer" type="VBoxContainer" parent="."]
+offset_left = 10.0
+offset_top = 10.0
+offset_right = 50.0
+offset_bottom = 50.0
+script = ExtResource("4_eq2lt")
+colors = SubResource("Gradient_tup4c")
+
+[node name="OpenFile" type="Button" parent="MusicVisualizer"]
+layout_mode = 2
+size_flags_horizontal = 0
+text = "Open music"
+
+[node name="RESET" type="AnimationPlayer" parent="MusicVisualizer"]
+root_node = NodePath("../OpenFile")
+libraries = {
+"": SubResource("AnimationLibrary_0ity1")
+}
+
+[node name="MusicPlayer" type="AudioStreamPlayer" parent="MusicVisualizer"]
+unique_name_in_owner = true
+autoplay = true
+bus = &"MusicAnalyzer"
+
+[node name="VBox" type="VBoxContainer" parent="MusicVisualizer"]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MusicVisualizer/VBox"]
+layout_mode = 2
+
+[node name="VolumeSlider" type="HSlider" parent="MusicVisualizer/VBox/HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+max_value = 1.0
+step = 0.01
+value = 0.1
+
+[node name="MuteMaster" type="CheckBox" parent="MusicVisualizer/VBox/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+button_pressed = true
+text = "Mute"
+
+[node name="AudioVisualizer" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(0.2, 0, 0, 0, 5, 0, 0, 0, 0.2, -5.31036, -1.422, 14.14)
+
+[node name="CustomCanvas" type="Control" parent="."]
+unique_name_in_owner = true
+layout_mode = 3
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -545.0
+offset_top = 46.0
+offset_right = -37.0
+offset_bottom = 638.0
+grow_horizontal = 0
+mouse_filter = 2
+metadata/_edit_lock_ = true
+
+[node name="Settings" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme = SubResource("3")
+script = ExtResource("5_31v5h")
+switch_to_scene = "res://examples_dd3d/DebugDrawDemoSceneCS.tscn"
+metadata/_edit_lock_ = true
+
+[node name="HBox" type="HBoxContainer" parent="Settings"]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -497.0
+offset_top = -372.0
+offset_right = -10.0006
+offset_bottom = -10.0
+grow_horizontal = 0
+grow_vertical = 0
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Settings/HBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+
+[node name="VersionBlock" type="HBoxContainer" parent="Settings/HBox/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+script = ExtResource("6_07f7q")
+
+[node name="Label" type="Label" parent="Settings/HBox/VBoxContainer/VersionBlock"]
+layout_mode = 2
+size_flags_horizontal = 10
+theme_override_font_sizes/font_size = 13
+text = "Demo version:"
+
+[node name="OptionButton" type="OptionButton" parent="Settings/HBox/VBoxContainer/VersionBlock"]
+layout_mode = 2
+size_flags_horizontal = 8
+theme_override_font_sizes/font_size = 13
+item_count = 1
+popup/item_0/text = "1.0.0"
+popup/item_0/id = 0
+
+[node name="Label" type="Label" parent="Settings/HBox/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+theme_override_styles/normal = SubResource("StyleBoxEmpty_oj5gf")
+text = "GDScript example"
+horizontal_alignment = 2
+metadata/_edit_use_anchors_ = true
+
+[node name="VBox" type="VBoxContainer" parent="Settings/HBox"]
+layout_mode = 2
+alignment = 2
+
+[node name="HideShowPanelButton" type="Button" parent="Settings/HBox/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 4
+theme_override_font_sizes/font_size = 13
+text = "Hide panel"
+
+[node name="SettingsPanel" type="PanelContainer" parent="Settings/HBox/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+theme_override_styles/panel = SubResource("StyleBoxFlat_boyhr")
+
+[node name="VBox" type="VBoxContainer" parent="Settings/HBox/VBox/SettingsPanel"]
+layout_mode = 2
+size_flags_horizontal = 3
+alignment = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.792157, 0.792157, 0.792157, 1)
+text = "Common:"
+
+[node name="HBox3" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox3"]
+layout_mode = 2
+text = "Thickness "
+
+[node name="ThicknessSlider" type="HSlider" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox3"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+max_value = 0.5
+step = 0.001
+value = 0.05
+
+[node name="HBox5" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox5"]
+layout_mode = 2
+text = "Frustum Scale"
+
+[node name="FrustumScaleSlider" type="HSlider" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox5"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+max_value = 1.0
+step = 0.001
+value = 0.5
+
+[node name="UpdateInPhysics" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Update in physics (15 Ticks) *"
+
+[node name="Label4" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.792157, 0.792157, 0.792157, 1)
+text = "Text:"
+
+[node name="ShowText" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Show text"
+
+[node name="ShowExamples" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Examples"
+
+[node name="ShowStats" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Debug stats"
+
+[node name="ShowHints" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Hints"
+
+[node name="Draw3DText" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "3D Text"
+
+[node name="Label3" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.792157, 0.792157, 0.792157, 1)
+text = "Boxes:"
+
+[node name="HBox4" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="DrawBoxes" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox4"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Draw an array of boxes"
+
+[node name="Draw1MBoxes" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox4"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Draw 1 Million boxes, otherwise 7500pcs."
+text = "1M"
+
+[node name="DrawBoxesAddText" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Add text to boxes"
+
+[node name="SwitchLang" type="Button" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Switch to C#"
+
+[connection signal="pressed" from="MusicVisualizer/OpenFile" to="MusicVisualizer" method="_pressed"]
+[connection signal="value_changed" from="MusicVisualizer/VBox/HBoxContainer/VolumeSlider" to="MusicVisualizer" method="_on_volume_slider_value_changed"]
+[connection signal="toggled" from="MusicVisualizer/VBox/HBoxContainer/MuteMaster" to="MusicVisualizer" method="_on_mute_master_toggled"]
+[connection signal="pressed" from="Settings/HBox/VBox/HideShowPanelButton" to="Settings" method="_on_hide_show_panel_pressed"]
+[connection signal="value_changed" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox3/ThicknessSlider" to="Settings" method="_on_thickness_slider_value_changed"]
+[connection signal="value_changed" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox5/FrustumScaleSlider" to="Settings" method="_on_frustum_scale_slider_value_changed"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/UpdateInPhysics" to="Settings" method="_on_update_in_physics_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/ShowText" to="Settings" method="_on_show_text_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/ShowExamples" to="Settings" method="_on_show_examples_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/ShowStats" to="Settings" method="_on_show_stats_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/ShowHints" to="Settings" method="_on_show_hints_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/Draw3DText" to="Settings" method="_on_draw_3d_text_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox4/DrawBoxes" to="Settings" method="_on_draw_boxes_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox4/Draw1MBoxes" to="Settings" method="_on_draw_1m_boxes_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/DrawBoxesAddText" to="Settings" method="_on_add_text_to_boxes_toggled"]
+[connection signal="pressed" from="Settings/HBox/VBox/SettingsPanel/VBox/SwitchLang" to="Settings" method="_on_Button_pressed"]
diff --git a/demo/examples_dd3d/DebugDrawDemoSceneCS.cs b/demo/examples_dd3d/DebugDrawDemoSceneCS.cs
new file mode 100644
index 0000000..f847d05
--- /dev/null
+++ b/demo/examples_dd3d/DebugDrawDemoSceneCS.cs
@@ -0,0 +1,854 @@
+
+using Godot;
+using System;
+using System.Collections.Generic;
+
+[Tool]
+public partial class DebugDrawDemoSceneCS : Node3D
+{
+ Random random = new Random();
+
+ [Export] Font custom_font;
+ [Export] Font custom_3d_font;
+ [Export] bool zylann_example = false;
+ [Export] bool update_in_physics = false;
+ [Export] bool test_text = true;
+ [Export] bool more_test_cases = true;
+ [Export] bool draw_3d_text = true;
+ [Export] bool draw_array_of_boxes = false;
+ [Export] bool draw_text_with_boxes = false;
+ [Export] bool draw_1m_boxes = false;
+ [Export(PropertyHint.Range, "0, 5, 0.001")] float debug_thickness = 0.1f;
+ [Export(PropertyHint.Range, "0, 1")] float camera_frustum_scale = 0.9f;
+
+ [ExportGroup("Text groups", "text_groups")]
+ [Export] bool text_groups_show_examples = true;
+ [Export] bool text_groups_show_hints = true;
+ [Export] bool text_groups_show_stats = true;
+ [Export] bool text_groups_show_stats_2d = true;
+ [Export] DebugDraw2DConfig.BlockPosition text_groups_position = DebugDraw2DConfig.BlockPosition.LeftTop;
+ [Export] Vector2I text_groups_offset = new Vector2I(8, 8);
+ [Export] Vector2I text_groups_padding = new Vector2I(3, 1);
+ [Export(PropertyHint.Range, "1, 100")] int text_groups_default_font_size = 15;
+ [Export(PropertyHint.Range, "1, 100")] int text_groups_title_font_size = 20;
+ [Export(PropertyHint.Range, "1, 100")] int text_groups_text_font_size = 17;
+
+ Dictionary button_presses = new Dictionary() {
+ { Key.Left, 0 },
+ { Key.Up, 0 },
+ { Key.Ctrl, 0 },
+ { Key.F1, 0 },
+ { Key.Key1, 0 },
+ { Key.Key2, 0 },
+ { Key.Key3, 0 },
+ };
+
+ double timer_1 = 0.0;
+ double timer_cubes = 0.0;
+ double timer_3 = 0.0;
+ double timer_text = 0.0;
+
+ public override async void _Ready()
+ {
+ _get_nodes();
+ _update_keys_just_press();
+
+ await new SignalAwaiter(GetTree(), "process_frame", this);
+
+ // this check is required for inherited scenes, because an instance of this
+ // script is created first, and then overridden by another
+ if (!IsInsideTree())
+ return;
+
+ DebugDraw2D.Config.TextBackgroundColor = new Color(0.3f, 0.3f, 0.3f, 0.8f);
+ }
+
+ bool _is_key_just_pressed(Key key)
+ {
+ if (button_presses[key] == 1)
+ {
+ button_presses[key] = 2;
+ return true;
+ }
+ return false;
+ }
+
+ void _update_timers(double delta)
+ {
+ timer_1 -= delta;
+ timer_cubes -= delta;
+ timer_3 -= delta;
+ timer_text -= delta;
+ }
+
+ void _update_keys_just_press()
+ {
+ var set = (Key k) => Input.IsKeyPressed(k) ? (button_presses[k] == 0 ? 1 : button_presses[k]) : 0;
+ button_presses[Key.Left] = set(Key.Left);
+ button_presses[Key.Up] = set(Key.Up);
+ button_presses[Key.Ctrl] = set(Key.Ctrl);
+ button_presses[Key.F1] = set(Key.F1);
+ button_presses[Key.Key1] = set(Key.Key1);
+ button_presses[Key.Key2] = set(Key.Key2);
+ button_presses[Key.Key3] = set(Key.Key3);
+ }
+
+ bool phys_frame_called = false;
+ public override void _Process(double delta)
+ {
+ ((ShaderMaterial)((PrimitiveMesh)dOtherWorld.Mesh).Material).SetShaderParameter("albedo_texture", dOtherWorldViewport.GetTexture());
+
+ phys_frame_called = false;
+ if (!update_in_physics)
+ {
+ MainUpdate(delta);
+ _update_timers(delta);
+ }
+ }
+
+ public override void _PhysicsProcess(double delta)
+ {
+ if (!phys_frame_called)
+ {
+ phys_frame_called = true;
+ if (update_in_physics)
+ {
+ MainUpdate(delta);
+ _update_timers(delta);
+ }
+ }
+
+ // Physics specific:
+ if (!zylann_example)
+ {
+ DebugDraw3D.DrawLine(dLines_8.GlobalPosition, dLines_Target.GlobalPosition, Colors.Yellow);
+ if (more_test_cases)
+ {
+ _draw_rays_casts();
+ }
+
+ // Additional drawing in the Viewport
+ using (var _w1 = DebugDraw3D.NewScopedConfig().SetViewport(dOtherWorldBox.GetViewport()).SetThickness(0.01f).SetCenterBrightness(1).SetNoDepthTest(true))
+ {
+ DebugDraw3D.DrawBoxXf(new Transform3D(Basis.Identity
+ .Scaled(Vector3.One * 0.3f)
+ .Rotated(new Vector3(0, 0, 1), Mathf.Pi / 4)
+ .Rotated(new Vector3(0, 1, 0), Mathf.Wrap(Time.GetTicksMsec() / -1500.0f, 0, Mathf.Tau) - Mathf.Pi / 4), dOtherWorldBox.GlobalPosition),
+ Colors.Brown, true, 0.4f);
+ }
+ }
+ }
+
+ void MainUpdate(double delta)
+ {
+ DebugDraw3D.ScopedConfig().SetThickness(debug_thickness);
+
+ _update_keys_just_press();
+
+ if (_is_key_just_pressed(Key.F1))
+ zylann_example = !zylann_example;
+
+ // Zylann's example :D
+ if (zylann_example)
+ {
+ var _time = Time.GetTicksMsec() / 1000.0f;
+ var box_pos = new Vector3(0, Mathf.Sin(_time * 4f), 0);
+ var line_begin = new Vector3(-1, Mathf.Sin(_time * 4f), 0);
+ var line_end = new Vector3(1, Mathf.Cos(_time * 4f), 0);
+ DebugDraw3D.DrawBox(box_pos, Quaternion.Identity, new Vector3(1, 2, 1), new Color(0, 1, 0));
+ DebugDraw3D.DrawLine(line_begin, line_end, new Color(1, 1, 0));
+ DebugDraw2D.SetText("Time", _time);
+ DebugDraw2D.SetText("Frames drawn", Engine.GetFramesDrawn());
+ DebugDraw2D.SetText("FPS", Engine.GetFramesPerSecond());
+ DebugDraw2D.SetText("delta", delta);
+
+ dHitTest.Visible = false;
+ dLagTest.Visible = false;
+ dPlaneOrigin.Visible = false;
+ pZDepthTestCube.Visible = false;
+ dOtherWorld.Visible = false;
+ return;
+ }
+
+ dHitTest.Visible = true;
+ dLagTest.Visible = true;
+ dPlaneOrigin.Visible = true;
+ pZDepthTestCube.Visible = true;
+ dOtherWorld.Visible = true;
+
+ // Testing the rendering layers by showing the image from the second camera inside the 2D panel
+ DebugDraw3D.Config.GeometryRenderLayers = !Input.IsKeyPressed(Key.Alt) ? 1 : 0b10010;
+ dPanel.Visible = Input.IsKeyPressed(Key.Alt);
+ DebugDraw2D.CustomCanvas = Input.IsKeyPressed(Key.Alt) ? dCustomCanvas : null;
+
+ // More property toggles
+ DebugDraw3D.Config.Freeze3dRender = Input.IsKeyPressed(Key.Down);
+ DebugDraw3D.Config.VisibleInstanceBounds = Input.IsKeyPressed(Key.Right);
+
+ // Regenerate meshes
+ if (Input.IsActionJustPressed("ui_end"))
+ DebugDraw3D.RegenerateGeometryMeshes();
+
+ // Some property toggles
+ if (_is_key_just_pressed(Key.Left))
+ DebugDraw3D.Config.UseFrustumCulling = !DebugDraw3D.Config.UseFrustumCulling;
+
+ if (_is_key_just_pressed(Key.Up))
+ DebugDraw3D.Config.ForceUseCameraFromScene = !DebugDraw3D.Config.ForceUseCameraFromScene;
+
+ if (_is_key_just_pressed(Key.Ctrl))
+ if (!Engine.IsEditorHint())
+ GetViewport().Msaa3D = GetViewport().Msaa3D == Viewport.Msaa.Msaa4X ? Viewport.Msaa.Disabled : Viewport.Msaa.Msaa4X;
+
+ if (!Engine.IsEditorHint())
+ {
+ if (_is_key_just_pressed(Key.Key1))
+ DebugDraw3D.DebugEnabled = !DebugDraw3D.DebugEnabled;
+ if (_is_key_just_pressed(Key.Key2))
+ DebugDraw2D.DebugEnabled = !DebugDraw2D.DebugEnabled;
+ if (_is_key_just_pressed(Key.Key3))
+ DebugDrawManager.DebugEnabled = !DebugDrawManager.DebugEnabled;
+ }
+
+
+ DebugDraw3D.Config.FrustumLengthScale = camera_frustum_scale;
+
+ // Zones with black borders
+ foreach (var node in dZones.GetChildren())
+ {
+ if (node is Node3D z)
+ {
+ DebugDraw3D.DrawBoxXf(z.GlobalTransform, Colors.Black);
+ }
+ }
+
+ // Spheres
+ _draw_zone_title(pSpheresBox, "Spheres");
+
+ DebugDraw3D.DrawSphereXf(dSphereTransform.GlobalTransform, Colors.Crimson);
+ using (var _s1 = DebugDraw3D.NewScopedConfig().SetHdSphere(true))
+ DebugDraw3D.DrawSphereXf(dSphereHDTransform.GlobalTransform, Colors.OrangeRed);
+
+ /// Delayed spheres
+ if (timer_1 <= 0)
+ {
+ DebugDraw3D.DrawSphere(dSpherePosition.GlobalPosition, 2.0f, Colors.BlueViolet, 2.0f);
+ using (var _s1 = DebugDraw3D.NewScopedConfig().SetHdSphere(true))
+ DebugDraw3D.DrawSphere(dSpherePosition.GlobalPosition + Vector3.Forward * 4, 2.0f, Colors.CornflowerBlue, 2.0f);
+ timer_1 = 2;
+ }
+
+ timer_1 -= delta;
+
+ // Cylinders
+ _draw_zone_title(pCylindersBox, "Cylinders");
+
+ DebugDraw3D.DrawCylinder(dCylinder1.GlobalTransform, Colors.Crimson);
+ DebugDraw3D.DrawCylinder(new Transform3D(Basis.Identity.Scaled(new Vector3(1, 2, 1)), dCylinder2.GlobalPosition), Colors.Red);
+ DebugDraw3D.DrawCylinderAb(dCylinder3a.GlobalPosition, dCylinder3b.GlobalPosition, 0.7f);
+
+ // Boxes
+ _draw_zone_title(pBoxesBox, "Boxes");
+
+ DebugDraw3D.DrawBoxXf(dBox1.GlobalTransform, Colors.MediumPurple);
+ DebugDraw3D.DrawBox(dBox2.GlobalPosition, Quaternion.FromEuler(new Vector3(0, Mathf.DegToRad(45), Mathf.DegToRad(45))), Vector3.One, Colors.RebeccaPurple);
+ DebugDraw3D.DrawBoxXf(new Transform3D(new Basis(Vector3.Up, Mathf.Pi * 0.25f).Scaled(Vector3.One * 2), dBox3.GlobalPosition), Colors.RosyBrown);
+
+ DebugDraw3D.DrawAabb(new Aabb(dAABB_fixed.GlobalPosition, new Vector3(2, 1, 2)), Colors.Aqua);
+ DebugDraw3D.DrawAabbAb(dAABB.GetChild(0).GlobalPosition, dAABB.GetChild(1).GlobalPosition, Colors.DeepPink);
+
+ // Boxes AB
+
+ DebugDraw3D.DrawArrow(dBoxAB.GlobalPosition, dBoxABup.GlobalPosition, Colors.Gold, 0.1f, true);
+ DebugDraw3D.DrawBoxAb(dBoxABa.GlobalPosition, dBoxABb.GlobalPosition, dBoxABup.GlobalPosition - dBoxAB.GlobalPosition, Colors.Peru);
+
+ DebugDraw3D.DrawArrow(dBoxABEdge.GlobalPosition, dBoxABEdgeup.GlobalPosition, Colors.DarkRed, 0.1f, true);
+ DebugDraw3D.DrawBoxAb(dBoxABEdgea.GlobalPosition, dBoxABEdgeb.GlobalPosition, dBoxABEdgeup.GlobalPosition - dBoxABEdge.GlobalPosition, Colors.DarkOliveGreen, false);
+
+ // Lines
+ _draw_zone_title(pLinesBox, "Lines");
+
+ DebugDraw3D.DrawSquare(dLines_Target.GlobalPosition, 0.5f, Colors.Red);
+
+ DebugDraw3D.DrawLine(dLines_1.GlobalPosition, dLines_Target.GlobalPosition, Colors.Fuchsia);
+ DebugDraw3D.DrawRay(dLines_3.GlobalPosition, (dLines_Target.GlobalPosition - dLines_3.GlobalPosition).Normalized(), 3.0f, Colors.Crimson);
+
+
+ if (timer_3 <= 0)
+ {
+ DebugDraw3D.DrawLine(dLines_6.GlobalPosition, dLines_Target.GlobalPosition, Colors.Fuchsia, 2.0f);
+ timer_3 = 2;
+ }
+
+ timer_3 -= delta;
+
+ // Test UP vector
+ DebugDraw3D.DrawLine(dLines_7.GlobalPosition, dLines_Target.GlobalPosition, Colors.Red);
+
+ // Lines with Arrow
+ DebugDraw3D.DrawArrow(dLines_2.GlobalPosition, dLines_Target.GlobalPosition, Colors.Blue, 0.5f, true);
+ DebugDraw3D.DrawArrowRay(dLines_4.GlobalPosition, (dLines_Target.GlobalPosition - dLines_4.GlobalPosition).Normalized(), 8.0f, Colors.Lavender, 0.5f, true);
+
+ DebugDraw3D.DrawLineHitOffset(dLines_5.GlobalPosition, dLines_Target.GlobalPosition, true, Mathf.Abs(Mathf.Sin(Time.GetTicksMsec() / 1000.0f)), 0.25f, Colors.Aqua);
+
+ // Paths
+ _draw_zone_title(pPathsBox, "Paths");
+
+ /// preparing data
+ List points = new List();
+ List points_below = new List();
+ List points_below2 = new List();
+ List points_below3 = new List();
+ List points_below4 = new List();
+ List lines_above = new List();
+
+ foreach (var node in dLinePath.GetChildren())
+ {
+ if (node is Node3D c)
+ {
+ points.Add(c.GlobalPosition);
+ points_below.Add(c.GlobalPosition + Vector3.Down);
+ points_below2.Add(c.GlobalPosition + Vector3.Down * 2);
+ points_below3.Add(c.GlobalPosition + Vector3.Down * 3);
+ points_below4.Add(c.GlobalPosition + Vector3.Down * 4);
+ }
+ }
+
+ for (int x = 0; x < points.Count - 1; x++)
+ {
+ lines_above.Add(points[x] + Vector3.Up);
+ lines_above.Add(points[x + 1] + Vector3.Up);
+ }
+
+ /// drawing lines
+ DebugDraw3D.DrawLines(lines_above.ToArray());
+ DebugDraw3D.DrawLinePath(points.ToArray(), Colors.Beige);
+ DebugDraw3D.DrawPoints(points_below.ToArray(), DebugDraw3D.PointType.TypeSquare, 0.2f, Colors.DarkGreen);
+ DebugDraw3D.DrawPointPath(points_below2.ToArray(), DebugDraw3D.PointType.TypeSquare, 0.25f, Colors.Blue, Colors.Tomato);
+ DebugDraw3D.DrawArrowPath(points_below3.ToArray(), Colors.Gold, 0.5f);
+ using (var _sl = DebugDraw3D.NewScopedConfig().SetThickness(0.05f))
+ DebugDraw3D.DrawPointPath(points_below4.ToArray(), DebugDraw3D.PointType.TypeSphere, 0.25f, Colors.MediumSeaGreen, Colors.MediumVioletRed);
+
+ // Misc
+ _draw_zone_title(pMiscBox, "Misc");
+
+ if (Engine.IsEditorHint())
+ {
+ using var s = DebugDraw3D.NewScopedConfig().SetThickness(0);
+ DebugDraw3D.DrawCameraFrustum(dCamera, Colors.DarkOrange);
+ }
+
+ using (var s = DebugDraw3D.NewScopedConfig().SetCenterBrightness(0.1f))
+ {
+ DebugDraw3D.DrawArrowhead(dMisc_Arrow.GlobalTransform, Colors.YellowGreen);
+ }
+
+ DebugDraw3D.DrawSquare(dMisc_Billboard.GlobalPosition, 0.5f, Colors.Green);
+
+ DebugDraw3D.DrawPosition(dMisc_Position.GlobalTransform, Colors.Brown);
+
+ DebugDraw3D.DrawGizmo(dMisc_GizmoTransform.GlobalTransform, null, true);
+ DebugDraw3D.DrawGizmo(dMisc_GizmoOneColor.GlobalTransform, Colors.Brown, true);
+ using (var s = DebugDraw3D.NewScopedConfig().SetCenterBrightness(0.5f).SetNoDepthTest(true))
+ {
+ DebugDraw3D.DrawGizmo(dMisc_GizmoNormal.GlobalTransform.Orthonormalized(), null, false);
+ }
+
+ // Grids
+ _draw_zone_title_pos(dGrids_GridCentered.GlobalPosition + new Vector3(0, 1.5f, 0), "Grids", 96, 36);
+
+ Transform3D tg = dGrids_Grid.GlobalTransform;
+ Vector3 tn = dGrids_Grid_Subdivision.Transform.Origin;
+ DebugDraw3D.DrawGrid(tg.Origin, tg.Basis.X, tg.Basis.Z, new Vector2I((int)tn.X * 10, (int)tn.Z * 10), Colors.LightCoral, false);
+
+ var tn1 = dGrids_GridCentered_Subdivision.Transform.Origin;
+ DebugDraw3D.DrawGridXf(dGrids_GridCentered.GlobalTransform, new Vector2I((int)(tn1.X * 10), (int)(tn1.Z * 10)));
+
+ using (var s = DebugDraw3D.NewScopedConfig().SetThickness(0.05f))
+ {
+ DebugDraw3D.DrawBoxXf(dPostProcess.GlobalTransform, Colors.SeaGreen);
+ }
+
+ // Local transform
+ _draw_local_xf_box(pLocalTransformRecursiveOrigin.GlobalTransform, 0.05f, 10);
+
+ // 2D
+ DebugDraw2D.Config.TextDefaultSize = text_groups_default_font_size;
+ DebugDraw2D.Config.TextBlockOffset = text_groups_offset;
+ DebugDraw2D.Config.TextBlockPosition = text_groups_position;
+ DebugDraw2D.Config.TextPadding = text_groups_padding;
+
+ DebugDraw2D.Config.TextCustomFont = custom_font;
+
+
+ if (test_text)
+ {
+ _text_tests();
+ }
+
+ // Lag Test
+ var lag_test_pos = (Vector3)dLagTest_RESET.GetAnimation("RESET").TrackGetKeyValue(0, 0);
+ _draw_zone_title_pos(lag_test_pos, "Lag test");
+
+ dLagTest.Position = lag_test_pos + new Vector3(Mathf.Sin(Time.GetTicksMsec() / 100.0f) * 2.5f, 0, 0);
+ DebugDraw3D.DrawBox(dLagTest.GlobalPosition, Quaternion.Identity, Vector3.One * 2.01f, Colors.Chocolate, true);
+
+ if (more_test_cases)
+ {
+ foreach (var node in dHitTest_RayEmitter.GetChildren())
+ {
+ if (node is RayCast3D ray)
+ ray.SetPhysicsProcessInternal(true);
+ }
+
+ _more_tests();
+ }
+ else
+ {
+ foreach (var node in dHitTest_RayEmitter.GetChildren())
+ {
+ if (node is RayCast3D ray)
+ ray.SetPhysicsProcessInternal(false);
+ }
+ }
+
+ _draw_other_world();
+
+ if (draw_array_of_boxes)
+ {
+ _draw_array_of_boxes();
+ }
+
+ }
+
+ void _text_tests()
+ {
+ DebugDraw2D.SetText("FPS", $"{Engine.GetFramesPerSecond():F2}", 0, Colors.Gold);
+
+ if (text_groups_show_examples)
+ {
+ if (timer_text < 0)
+ {
+ DebugDraw2D.SetText("Some delayed text", "for 2.5s", -1, Colors.Black, 2.5f); // it's supposed to show text for 2.5 seconds
+ timer_text += 5;
+ }
+
+ DebugDraw2D.BeginTextGroup("-- First Group --", 2, Colors.LimeGreen, true, text_groups_title_font_size, text_groups_text_font_size);
+ DebugDraw2D.SetText("Simple text");
+ DebugDraw2D.SetText("Text", "Value", 0, Colors.Aquamarine);
+ DebugDraw2D.SetText("Text out of order", null, -1, Colors.Silver);
+ DebugDraw2D.BeginTextGroup("-- Second Group --", 1, Colors.Beige);
+ DebugDraw2D.SetText("Rendered frames", Engine.GetFramesDrawn());
+ DebugDraw2D.EndTextGroup();
+ }
+
+ if (text_groups_show_stats)
+ {
+ DebugDraw2D.BeginTextGroup("-- Stats --", 3, Colors.Wheat);
+ var render_stats = DebugDraw3D.GetRenderStats();
+
+ if (render_stats != null && text_groups_show_stats)
+ {
+ DebugDraw2D.SetText("Total", render_stats.TotalGeometry);
+ DebugDraw2D.SetText("Instances", render_stats.Instances, 1);
+ DebugDraw2D.SetText("Lines", render_stats.Lines, 2);
+ DebugDraw2D.SetText("Total Visible", render_stats.TotalVisible, 3);
+ DebugDraw2D.SetText("Visible Instances", render_stats.VisibleInstances, 4);
+ DebugDraw2D.SetText("Visible Lines", render_stats.VisibleLines, 5);
+
+ DebugDraw2D.SetText("---", "", 12);
+
+ DebugDraw2D.SetText("Culling time", $"{(render_stats.TotalTimeCullingUsec / 1000.0):F2} ms", 13);
+ DebugDraw2D.SetText("Filling instances buffer", $"{(render_stats.TimeFillingBuffersInstancesUsec / 1000.0):F2} ms", 14);
+ DebugDraw2D.SetText("Filling lines buffer", $"{(render_stats.TimeFillingBuffersLinesUsec / 1000.0):F2} ms", 15);
+ DebugDraw2D.SetText("Filling time", $"{(render_stats.TotalTimeFillingBuffersUsec / 1000.0):F2} ms", 16);
+ DebugDraw2D.SetText("Total time", $"{(render_stats.TotalTimeSpentUsec / 1000.0):F2} ms", 17);
+
+ DebugDraw2D.SetText("----", null, 32);
+
+ DebugDraw2D.SetText("Total Label3D", render_stats.NodesLabel3dExistsTotal, 33);
+ DebugDraw2D.SetText("Visible Label3D", render_stats.NodesLabel3dVisible + render_stats.NodesLabel3dVisiblePhysics, 34);
+
+ DebugDraw2D.SetText("-----", null, 48);
+
+ DebugDraw2D.SetText("Created scoped configs", $"{render_stats.CreatedScopedConfigs}", 49);
+ }
+
+ if (text_groups_show_stats && text_groups_show_stats_2d)
+ {
+ DebugDraw2D.SetText("------", null, 64);
+ }
+
+ var render_stats_2d = DebugDraw2D.GetRenderStats();
+ if (render_stats_2d != null && text_groups_show_stats_2d)
+ {
+ DebugDraw2D.SetText("Text groups", render_stats_2d.OverlayTextGroups, 96);
+ DebugDraw2D.SetText("Text lines", render_stats_2d.OverlayTextLines, 97);
+ }
+ DebugDraw2D.EndTextGroup();
+ }
+
+ if (text_groups_show_hints)
+ {
+ DebugDraw2D.BeginTextGroup("controls", 1024, Colors.White, false);
+ if (!Engine.IsEditorHint())
+ {
+ DebugDraw2D.SetText("WASD QE, LMB", "To move", 0);
+ }
+ DebugDraw2D.SetText("Alt: change render layers", DebugDraw3D.Config.GeometryRenderLayers, 1);
+ if (!OS.HasFeature("web"))
+ {
+ DebugDraw2D.SetText("Ctrl: toggle anti-aliasing", GetViewport().Msaa3D == Viewport.Msaa.Msaa4X ? "MSAA 4x" : "Disabled", 2);
+ }
+ DebugDraw2D.SetText("Down: freeze render", DebugDraw3D.Config.Freeze3dRender, 3);
+ if (Engine.IsEditorHint())
+ {
+ DebugDraw2D.SetText("Up: use scene camera", DebugDraw3D.Config.ForceUseCameraFromScene, 4);
+ }
+ DebugDraw2D.SetText("1,2,3: toggle debug", $"{DebugDraw3D.DebugEnabled}, {DebugDraw2D.DebugEnabled} 😐, {DebugDrawManager.DebugEnabled} 😏", 5);
+ DebugDraw2D.SetText("Left: toggle frustum culling", DebugDraw3D.Config.UseFrustumCulling, 6);
+ DebugDraw2D.SetText("Right: draw bounds for culling", DebugDraw3D.Config.VisibleInstanceBounds, 7);
+ }
+ }
+
+ void _draw_zone_title(Node3D node, string title)
+ {
+ if (draw_3d_text)
+ {
+ using var _s1 = DebugDraw3D.NewScopedConfig().SetTextOutlineSize(72);
+ DebugDraw3D.DrawText(node.GlobalPosition + node.GlobalBasis.Y * 0.85f, title, 128);
+ }
+ }
+
+ void _draw_zone_title_pos(Vector3 pos, string title, int font_size = 128, int outline = 72)
+ {
+ if (draw_3d_text)
+ {
+ using var _s1 = DebugDraw3D.NewScopedConfig().SetTextOutlineSize(outline);
+ DebugDraw3D.DrawText(pos, title, font_size);
+ }
+ }
+
+ const float _local_mul = 0.45f;
+ static readonly Vector3 _local_mul_vec = new(_local_mul, _local_mul, _local_mul);
+ Vector3[] __local_lines_cross_recursive = [new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(0.5f, -0.5f, 0.5f), new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(0.5f, -0.5f, -0.5f)];
+ Transform3D __local_box_recursive = Transform3D.Identity.RotatedLocal(Vector3.Up, Mathf.DegToRad(30)).Translated(new Vector3(-0.25f, -0.55f, 0.25f)).Scaled(_local_mul_vec);
+ Transform3D __local_sphere_recursive = Transform3D.Identity.Translated(new Vector3(0.5f, 0.55f, -0.5f)).Scaled(_local_mul_vec);
+
+ void _draw_local_xf_box(Transform3D xf, float thickness, int max_depth, int depth = 0)
+ {
+ if (depth >= max_depth)
+ return;
+
+ using var _s1 = DebugDraw3D.NewScopedConfig().SetThickness(thickness).SetTransform(xf);
+
+ // a box with a small offset
+ DebugDraw3D.DrawBoxXf(new Transform3D(Basis.Identity, new Vector3(0, 0.001f, 0)), Colors.Brown);
+ // a box and a stand for the next depth
+ DebugDraw3D.DrawBoxXf(__local_box_recursive, Colors.Chartreuse);
+ // just a sphere and lines
+ DebugDraw3D.DrawSphereXf(__local_sphere_recursive, Colors.DarkOrange);
+
+ _s1.SetThickness(0);
+
+ DebugDraw3D.DrawLines(__local_lines_cross_recursive, Colors.Crimson);
+
+ // A simple animation generator with descent into the depth of the scene
+#if false
+ {
+ Animation anim = pRecursiveTransformTest.GetAnimation("recursive");
+ // clear keys
+ if (depth == 0)
+ for (var i = 0; i < anim.TrackGetKeyCount(0); i++)
+ {
+ anim.TrackRemoveKey(0, 0);
+ anim.TrackRemoveKey(1, 0);
+ }
+
+ var time = depth * 2;
+ var s_xf = xf * __local_sphere_recursive;
+ var next_s_xf = (xf * __local_box_recursive.Translated(__local_box_recursive.Basis.Y)) * __local_sphere_recursive;
+ var get_sphere_pos = (Transform3D l_xf) => l_xf.Origin + (l_xf).Basis.Y;
+
+ anim.PositionTrackInsertKey(0, time, get_sphere_pos(s_xf));
+ anim.RotationTrackInsertKey(1, time, new Transform3D(Basis.Identity, get_sphere_pos(s_xf)).LookingAt(get_sphere_pos(next_s_xf), xf.Basis.Y).Basis.GetRotationQuaternion());
+ }
+#endif
+
+ _draw_local_xf_box(xf * __local_box_recursive.Translated(__local_box_recursive.Basis.Y), thickness * _local_mul, max_depth, depth + 1);
+ }
+
+
+ void _draw_other_world()
+ {
+ using var s = DebugDraw3D.NewScopedConfig().SetViewport(dOtherWorldBox.GetViewport());
+ DebugDraw3D.DrawBoxXf(dOtherWorldBox.GlobalTransform.RotatedLocal(new Vector3(1, 1, -1).Normalized(), Mathf.Wrap(Time.GetTicksMsec() / 1000.0f, 0f, Mathf.Tau)), Colors.SandyBrown);
+ DebugDraw3D.DrawBoxXf(dOtherWorldBox.GlobalTransform.RotatedLocal(new Vector3(-1, 1, -1).Normalized(), Mathf.Wrap(Time.GetTicksMsec() / 1000.0f, 0f, Mathf.Tau) - Mathf.Pi / 4), Colors.SandyBrown);
+
+ if (draw_3d_text)
+ {
+ var angle = Mathf.Wrap(Time.GetTicksMsec() / 1000.0f, 0, Mathf.Tau);
+ using (var _w2 = DebugDraw3D.NewScopedConfig().SetTextFont(custom_3d_font))
+ {
+ DebugDraw3D.DrawText(dOtherWorldBox.GlobalPosition + new Vector3(Mathf.Cos(angle), -0.25f, Mathf.Sin(angle)), "Hello world!", 32, Colors.Crimson, 0);
+ }
+
+ using (var _w3 = DebugDraw3D.NewScopedConfig().SetNoDepthTest(true).SetTextOutlineColor(Colors.IndianRed).SetTextOutlineSize(6))
+ {
+ DebugDraw3D.DrawText(dOtherWorldBox.GlobalPosition + new Vector3(Mathf.Cos(angle), +0.25f, Mathf.Sin(-angle)), "World without depth", 20, Colors.Pink, 0);
+ }
+ }
+ }
+
+ void _draw_rays_casts()
+ {
+ // Line hits render
+ _draw_zone_title_pos(pHitTestSphere.GlobalPosition, "Line hits", 96, 36);
+
+ foreach (var node in dHitTest_RayEmitter.GetChildren())
+ {
+ if (node is RayCast3D ray)
+ {
+ ray.ForceRaycastUpdate();
+ DebugDraw3D.DrawLineHit(ray.GlobalPosition, ray.ToGlobal(ray.TargetPosition), ray.GetCollisionPoint(), ray.IsColliding(), 0.3f);
+ }
+ }
+ }
+
+ void _more_tests()
+ {
+ // Delayed line render
+ using (var s = DebugDraw3D.NewScopedConfig().SetThickness(0.035f))
+ {
+ DebugDraw3D.DrawLine(dLagTest.GlobalPosition + Vector3.Up, dLagTest.GlobalPosition + new Vector3(0, 3, Mathf.Sin(Time.GetTicksMsec() / 50.0f)), null, 0.35f);
+
+ if (draw_3d_text)
+ {
+ DebugDraw3D.DrawText(dLagTest.GlobalPosition + new Vector3(0, 3, Mathf.Sin(Time.GetTicksMsec() / 50.0f)), $"{Mathf.Sin(Time.GetTicksMsec() / 50.0f):F1}", 16, null, 0.35f);
+ }
+ }
+
+ // Draw plane
+ using (var _s11 = DebugDraw3D.NewScopedConfig().SetThickness(0.02f).SetPlaneSize(10))
+ {
+ var pl_node = GetNode("PlaneOrigin");
+ var xf = pl_node.GlobalTransform;
+ var normal = xf.Basis.Y.Normalized();
+ var plane = new Plane(normal, xf.Origin.Dot(normal));
+
+ var vp = GetViewport();
+ if (Engine.IsEditorHint() && (Viewport)Engine.GetSingleton("EditorInterface").Call("get_editor_viewport_3d", 0) != null)
+ {
+ vp = (Viewport)Engine.GetSingleton("EditorInterface").Call("get_editor_viewport_3d", 0);
+ }
+
+ var cam = vp.GetCamera3D();
+ if (cam != null)
+ {
+ var dir = vp.GetCamera3D().ProjectRayNormal(vp.GetMousePosition());
+ Vector3? intersect = plane.IntersectsRay(cam.GlobalPosition, dir);
+
+ DebugDraw3D.DrawPlane(plane, Colors.Coral * new Color(1, 1, 1, 0.4f), pl_node.GlobalPosition);
+ if (intersect.HasValue && intersect.Value.DistanceTo(pl_node.GlobalPosition) < _s11.GetPlaneSize() * 0.5f)
+ {
+ // Need to test different colors on both sides of the plane
+ var col = plane.IsPointOver(cam.GlobalPosition) ? Colors.Firebrick : Colors.Aquamarine;
+ DebugDraw3D.DrawSphere(intersect.Value, 0.3f, col);
+ }
+ }
+ }
+ }
+
+ void _draw_array_of_boxes()
+ {
+ // Lots of boxes to check performance..
+ var x_size = 50;
+ var y_size = 50;
+ var z_size = 3;
+ var mul = 1.0f;
+ var cubes_max_time = 1.25f;
+ var show_text = draw_text_with_boxes;
+ using var cfg = DebugDraw3D.NewScopedConfig();
+
+ if (draw_1m_boxes)
+ {
+ x_size = 100;
+ y_size = 100;
+ z_size = 100;
+ mul = 4.0f;
+ cubes_max_time = 60f;
+ draw_text_with_boxes = false;
+ }
+
+ var size = Vector3.One;
+ var half_size = size * 0.5f;
+
+ if (timer_cubes <= 0)
+ {
+ var start_time = Time.GetTicksUsec();
+ for (int x = 0; x < x_size; x++)
+ {
+ for (int y = 0; y < y_size; y++)
+ {
+ for (int z = 0; z < z_size; z++)
+ {
+ cfg.SetThickness(Random.Shared.NextSingle() * 0.1f);
+ var pos = new Vector3(x * mul, (-4 - z) * mul, y * mul) + GlobalPosition;
+ DebugDraw3D.DrawBox(pos, Quaternion.Identity, size, null, false, cubes_max_time);
+
+ if (show_text && z == 0)
+ {
+ DebugDraw3D.DrawText(pos + half_size, pos.ToString(), 32, null, cubes_max_time);
+ }
+ }
+ }
+ }
+ //GD.Print($"Draw Cubes: {((Time.GetTicksUsec() - start_time) / 1000.0):F3}ms");
+ timer_cubes = cubes_max_time;
+ }
+ }
+
+ Node3D dHitTest;
+ CsgBox3D dLagTest;
+ PanelContainer dPanel;
+ Node3D dZones;
+ Node3D dSpherePosition;
+ Node3D dSphereTransform;
+ Node3D dSphereHDTransform;
+ Node3D dAABB;
+ Node3D dAABB_fixed;
+ Node3D dBox1;
+ Node3D dBox2;
+ Node3D dBox3;
+ Node3D dBoxAB;
+ Node3D dBoxABa;
+ Node3D dBoxABb;
+ Node3D dBoxABup;
+ Node3D dBoxABEdge;
+ Node3D dBoxABEdgea;
+ Node3D dBoxABEdgeb;
+ Node3D dBoxABEdgeup;
+ Node3D dLines_1;
+ Node3D dLines_2;
+ Node3D dLines_3;
+ Node3D dLines_4;
+ Node3D dLines_5;
+ Node3D dLines_6;
+ Node3D dLines_7;
+ Node3D dLines_8;
+ Node3D dLines_Target;
+ Node3D dLinePath;
+ Node3D dCylinder1;
+ Node3D dCylinder2;
+ Node3D dCylinder3a;
+ Node3D dCylinder3b;
+
+ Node3D pSpheresBox;
+ Node3D pCylindersBox;
+ Node3D pBoxesBox;
+ Node3D pLinesBox;
+ Node3D pPathsBox;
+ Node3D pMiscBox;
+
+ MeshInstance3D dPlaneOrigin;
+ MeshInstance3D pZDepthTestCube;
+
+ MeshInstance3D dOtherWorld;
+ SubViewport dOtherWorldViewport;
+ Node3D dOtherWorldBox;
+
+ Control dCustomCanvas;
+ Node3D dMisc_Arrow;
+ Camera3D dCamera;
+ Node3D dMisc_Billboard;
+ Node3D dMisc_Position;
+ Node3D dMisc_GizmoTransform;
+ Node3D dMisc_GizmoNormal;
+ Node3D dMisc_GizmoOneColor;
+ Node3D pLocalTransformRecursiveOrigin;
+ AnimationPlayer pRecursiveTransformTest;
+
+ Node3D dGrids_Grid;
+ Node3D dGrids_Grid_Subdivision;
+ Node3D dGrids_GridCentered_Subdivision;
+ Node3D dGrids_GridCentered;
+
+ MeshInstance3D dPostProcess;
+ AnimationPlayer dLagTest_RESET;
+ Node3D dHitTest_RayEmitter;
+ Node3D pHitTestSphere;
+
+ void _get_nodes()
+ {
+ dHitTest = GetNode("HitTest");
+ dLagTest = GetNode("LagTest");
+ dPanel = GetNode("Panel");
+ dZones = GetNode("Zones");
+ dSpherePosition = GetNode("Spheres/SpherePosition");
+ dSphereTransform = GetNode("Spheres/SphereTransform");
+ dSphereHDTransform = GetNode("Spheres/SphereHDTransform");
+ dAABB = GetNode("Boxes/AABB");
+ dAABB_fixed = GetNode("Boxes/AABB_fixed");
+ dBox1 = GetNode("Boxes/Box1");
+ dBox2 = GetNode("Boxes/Box2");
+ dBox3 = GetNode("Boxes/Box3");
+ dBoxAB = GetNode("Boxes/BoxAB");
+ dBoxABa = GetNode("Boxes/BoxAB/a");
+ dBoxABb = GetNode("Boxes/BoxAB/b");
+ dBoxABup = GetNode("Boxes/BoxAB/o/up");
+ dBoxABEdge = GetNode("Boxes/BoxABEdge");
+ dBoxABEdgea = GetNode("Boxes/BoxABEdge/a");
+ dBoxABEdgeb = GetNode("Boxes/BoxABEdge/b");
+ dBoxABEdgeup = GetNode("Boxes/BoxABEdge/o/up");
+ dLines_1 = GetNode("Lines/1");
+ dLines_2 = GetNode("Lines/2");
+ dLines_3 = GetNode("Lines/3");
+ dLines_4 = GetNode("Lines/4");
+ dLines_5 = GetNode("Lines/5");
+ dLines_6 = GetNode("Lines/6");
+ dLines_7 = GetNode("Lines/7");
+ dLines_8 = GetNode("Lines/8");
+ dLines_Target = GetNode("Lines/Target");
+ dLinePath = GetNode("LinePath");
+ dCylinder1 = GetNode("Cylinders/Cylinder1");
+ dCylinder2 = GetNode("Cylinders/Cylinder2");
+ dCylinder3a = GetNode("Cylinders/Cylinder3/1");
+ dCylinder3b = GetNode("Cylinders/Cylinder3/2");
+
+ pSpheresBox = GetNode("%SpheresBox");
+ pCylindersBox = GetNode("%CylindersBox");
+ pBoxesBox = GetNode("%BoxesBox");
+ pLinesBox = GetNode("%LinesBox");
+ pPathsBox = GetNode("%PathsBox");
+ pMiscBox = GetNode("%MiscBox");
+
+ dPlaneOrigin = GetNode("PlaneOrigin");
+ pZDepthTestCube = GetNode("%ZDepthTestCube");
+
+ dOtherWorld = GetNode("OtherWorld");
+ dOtherWorldViewport = GetNode("OtherWorld/SubViewport");
+ dOtherWorldBox = GetNode("OtherWorld/SubViewport/SubViewportContainer/SubViewport/OtherWorldBox");
+
+ dCustomCanvas = GetNode("CustomCanvas");
+ dMisc_Arrow = GetNode("Misc/Arrow");
+ dCamera = GetNode("Camera");
+ dMisc_Billboard = GetNode("Misc/Billboard");
+ dMisc_Position = GetNode("Misc/Position");
+ dMisc_GizmoTransform = GetNode("Misc/GizmoTransform");
+ dMisc_GizmoNormal = GetNode("Misc/GizmoNormal");
+ dMisc_GizmoOneColor = GetNode("Misc/GizmoOneColor");
+ pLocalTransformRecursiveOrigin = GetNode("%LocalTransformRecursiveOrigin");
+ pRecursiveTransformTest = GetNode("%RecursiveTransformTest");
+
+ dGrids_Grid = GetNode("Grids/Grid");
+ dGrids_Grid_Subdivision = GetNode("Grids/Grid/Subdivision");
+ dGrids_GridCentered_Subdivision = GetNode("Grids/GridCentered/Subdivision");
+ dGrids_GridCentered = GetNode("Grids/GridCentered");
+
+ dPostProcess = GetNode("PostProcess");
+
+ dLagTest_RESET = GetNode("LagTest/RESET");
+ dHitTest_RayEmitter = GetNode("HitTest/RayEmitter");
+ pHitTestSphere = GetNode("%HitTestSphere");
+ }
+}
diff --git a/demo/examples_dd3d/DebugDrawDemoSceneCS.cs.uid b/demo/examples_dd3d/DebugDrawDemoSceneCS.cs.uid
new file mode 100644
index 0000000..8576e05
--- /dev/null
+++ b/demo/examples_dd3d/DebugDrawDemoSceneCS.cs.uid
@@ -0,0 +1 @@
+uid://dnf8ejsrnlvxb
diff --git a/demo/examples_dd3d/DebugDrawDemoSceneCS.tscn b/demo/examples_dd3d/DebugDrawDemoSceneCS.tscn
new file mode 100644
index 0000000..670c547
--- /dev/null
+++ b/demo/examples_dd3d/DebugDrawDemoSceneCS.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=3 format=3 uid="uid://sxtw8fme7g63"]
+
+[ext_resource type="PackedScene" uid="uid://c3sccy6x0ht5j" path="res://examples_dd3d/DebugDrawDemoScene.tscn" id="2"]
+[ext_resource type="Script" path="res://examples_dd3d/DebugDrawDemoSceneCS.cs" id="2_ipqea"]
+
+[node name="DebugDrawDemoSceneCS" instance=ExtResource("2")]
+script = ExtResource("2_ipqea")
+
+[node name="Settings" parent="." index="23"]
+switch_to_scene = "res://examples_dd3d/DebugDrawDemoScene.tscn"
+
+[node name="Label" parent="Settings/HBox/VBoxContainer" index="1"]
+text = "C# example"
+
+[node name="SwitchLang" parent="Settings/HBox/VBox/SettingsPanel/VBox" index="13"]
+text = "Switch to GDScript"
diff --git a/demo/examples_dd3d/PixelatedElegance.ttf b/demo/examples_dd3d/PixelatedElegance.ttf
new file mode 100644
index 0000000..ce55ead
Binary files /dev/null and b/demo/examples_dd3d/PixelatedElegance.ttf differ
diff --git a/demo/examples_dd3d/PixelatedElegance.ttf.import b/demo/examples_dd3d/PixelatedElegance.ttf.import
new file mode 100644
index 0000000..57b02dc
--- /dev/null
+++ b/demo/examples_dd3d/PixelatedElegance.ttf.import
@@ -0,0 +1,36 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://7am1h57ldd6"
+path="res://.godot/imported/PixelatedElegance.ttf-aac00801d681e5d2b42b23257b2692a7.fontdata"
+
+[deps]
+
+source_file="res://examples_dd3d/PixelatedElegance.ttf"
+dest_files=["res://.godot/imported/PixelatedElegance.ttf-aac00801d681e5d2b42b23257b2692a7.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+modulate_color_glyphs=false
+hinting=1
+subpixel_positioning=1
+keep_rounding_remainders=true
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/demo/examples_dd3d/Roboto-Bold.ttf b/demo/examples_dd3d/Roboto-Bold.ttf
new file mode 100644
index 0000000..d3f01ad
Binary files /dev/null and b/demo/examples_dd3d/Roboto-Bold.ttf differ
diff --git a/demo/examples_dd3d/Roboto-Bold.ttf.import b/demo/examples_dd3d/Roboto-Bold.ttf.import
new file mode 100644
index 0000000..f5260f6
--- /dev/null
+++ b/demo/examples_dd3d/Roboto-Bold.ttf.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://erdgllynwqkw"
+path="res://.godot/imported/Roboto-Bold.ttf-3674de3d9ad3ee757cd4b4a89f1e126d.fontdata"
+
+[deps]
+
+source_file="res://examples_dd3d/Roboto-Bold.ttf"
+dest_files=["res://.godot/imported/Roboto-Bold.ttf-3674de3d9ad3ee757cd4b4a89f1e126d.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+modulate_color_glyphs=false
+hinting=1
+subpixel_positioning=1
+keep_rounding_remainders=true
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[{
+"chars": [],
+"glyphs": [],
+"name": "New Configuration"
+}]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/demo/examples_dd3d/VisualizerAudioBus.tres b/demo/examples_dd3d/VisualizerAudioBus.tres
new file mode 100644
index 0000000..4c7b662
--- /dev/null
+++ b/demo/examples_dd3d/VisualizerAudioBus.tres
@@ -0,0 +1,17 @@
+[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://7sy4h4ibftrk"]
+
+[sub_resource type="AudioEffectSpectrumAnalyzer" id="AudioEffectSpectrumAnalyzer_odciy"]
+resource_name = "SpectrumAnalyzer"
+fft_size = 3
+
+[resource]
+bus/0/mute = true
+bus/0/volume_db = -20.0
+bus/1/name = &"MusicAnalyzer"
+bus/1/solo = false
+bus/1/mute = false
+bus/1/bypass_fx = false
+bus/1/volume_db = 0.0
+bus/1/send = &"Master"
+bus/1/effect/0/effect = SubResource("AudioEffectSpectrumAnalyzer_odciy")
+bus/1/effect/0/enabled = true
diff --git a/demo/examples_dd3d/addon_icon.gd b/demo/examples_dd3d/addon_icon.gd
new file mode 100644
index 0000000..4283024
--- /dev/null
+++ b/demo/examples_dd3d/addon_icon.gd
@@ -0,0 +1,11 @@
+@tool
+extends Node3D
+
+func _process(delta: float) -> void:
+ var a = DebugDraw3D.new_scoped_config().set_thickness(0.015)
+ DebugDraw3D.draw_box_xf($box.global_transform, Color.GREEN)
+ DebugDraw3D.draw_gizmo($gizmo.global_transform)
+ DebugDraw3D.draw_grid_xf($gizmo/grid.global_transform, Vector2i(2,2), DebugDraw3D.empty_color, false)
+ DebugDraw3D.draw_sphere_xf($sphere.global_transform, Color.RED)
+ DebugDraw3D.draw_cylinder($cylinder.global_transform, Color.BLUE)
+ DebugDraw3D.draw_line_hit_offset($"line/1".global_transform.origin, $"line/2".global_transform.origin, true, 0.3, 0.1)
diff --git a/demo/examples_dd3d/addon_icon.gd.uid b/demo/examples_dd3d/addon_icon.gd.uid
new file mode 100644
index 0000000..c3fca43
--- /dev/null
+++ b/demo/examples_dd3d/addon_icon.gd.uid
@@ -0,0 +1 @@
+uid://b2lj85riqyno0
diff --git a/demo/examples_dd3d/addon_icon.tscn b/demo/examples_dd3d/addon_icon.tscn
new file mode 100644
index 0000000..b577312
--- /dev/null
+++ b/demo/examples_dd3d/addon_icon.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=3 format=3 uid="uid://1lhiwf8tgleh"]
+
+[ext_resource type="Script" path="res://examples_dd3d/addon_icon.gd" id="1_bq18y"]
+
+[sub_resource type="Environment" id="1"]
+background_mode = 1
+
+[node name="icon" type="Node3D"]
+script = ExtResource("1_bq18y")
+
+[node name="Camera" type="Camera3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5.39732)
+environment = SubResource("1")
+current = true
+
+[node name="box" type="Node3D" parent="."]
+transform = Transform3D(0.316305, 0.0204714, -0.293415, -0.239575, 0.267896, -0.239575, 0.170631, 0.338191, 0.207538, -0.410294, 0.312541, 0.243199)
+
+[node name="gizmo" type="Node3D" parent="."]
+transform = Transform3D(0.707107, 0, -0.707107, -0.294265, 0.909294, -0.294265, 0.642968, 0.416154, 0.642968, 0, 0, 0)
+
+[node name="grid" type="Node3D" parent="gizmo"]
+transform = Transform3D(1, -2.98023e-08, 1.19209e-07, 0, 1, 0, 1.19209e-07, -2.98023e-08, 1, -0.0263093, -0.0170284, -0.0263093)
+
+[node name="sphere" type="Node3D" parent="."]
+transform = Transform3D(0.401341, 0.207831, -0.437109, -0.449118, 0.371584, -0.235691, 0.180418, 0.46267, 0.385639, 0.466197, 0.322665, 0.200436)
+
+[node name="cylinder" type="Node3D" parent="."]
+transform = Transform3D(0.155034, 0.231693, -0.112783, -0.160003, 0.264761, -0.0839674, 0.0232275, 0.277352, 0.174372, -0.0566943, -0.290515, 0.905274)
+
+[node name="line" type="Node3D" parent="."]
+
+[node name="1" type="Node3D" parent="line"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.568458, -0.615948, 0.653444)
+
+[node name="2" type="Node3D" parent="line"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0051975, 0.373791, 0.0974927)
diff --git a/demo/examples_dd3d/demo_camera_movement.gd b/demo/examples_dd3d/demo_camera_movement.gd
new file mode 100644
index 0000000..51f758d
--- /dev/null
+++ b/demo/examples_dd3d/demo_camera_movement.gd
@@ -0,0 +1,60 @@
+extends Camera3D
+
+@export var mouse_sensitivity := 0.25
+@export var camera_speed := 10.0
+@export var camera_speed_fast := 30.0
+
+var btn_clicked := false
+const hPI := PI/2
+var rot_x := 0.0
+var rot_y := 0.0
+
+
+func _ready():
+ reset_input_rotation()
+
+
+func _unhandled_input(event) -> void:
+ if event is InputEventMouseButton:
+ btn_clicked = event.pressed
+
+
+func reset_input_rotation():
+ rot_x = rotation.y
+ rot_y = rotation.x
+
+
+func _input(event) -> void:
+ if btn_clicked:
+ if event is InputEventMouseMotion:
+ if event.button_mask == MOUSE_BUTTON_LEFT:
+ rot_x += -deg_to_rad(event.relative.x * mouse_sensitivity)
+ rot_y += -deg_to_rad(event.relative.y * mouse_sensitivity)
+ rot_y = clamp(rot_y, -hPI, hPI)
+
+ transform.basis = Basis()
+ rotate_object_local(Vector3.UP, rot_x)
+ rotate_object_local(Vector3.RIGHT, rot_y)
+
+
+func get_axis(neg : Array[Key], pos : Array[Key]) -> float:
+ var pressed = func (arr: Array[Key]):
+ var p: float = 0
+ for k in arr:
+ if Input.is_physical_key_pressed(k):
+ p = 1
+ break
+ return p
+
+ return pressed.call(pos) - pressed.call(neg)
+
+
+func _process(delta) -> void:
+ var motion := Vector2(get_axis([KEY_S], [KEY_W]), get_axis([KEY_A], [KEY_D]))
+ var lift := get_axis([KEY_Q, KEY_CTRL], [KEY_E, KEY_SPACE])
+ var speed := camera_speed_fast if Input.is_physical_key_pressed(KEY_SHIFT) else camera_speed
+ motion = motion.limit_length()
+
+ var b := global_transform.basis
+ var v := (-b.z * motion.x) + (b.x * motion.y) + (b.y * lift)
+ global_position += v.limit_length() * speed * delta
diff --git a/demo/examples_dd3d/demo_camera_movement.gd.uid b/demo/examples_dd3d/demo_camera_movement.gd.uid
new file mode 100644
index 0000000..03651ca
--- /dev/null
+++ b/demo/examples_dd3d/demo_camera_movement.gd.uid
@@ -0,0 +1 @@
+uid://b5mdrjubj0lg5
diff --git a/demo/examples_dd3d/demo_music_visualizer.gd b/demo/examples_dd3d/demo_music_visualizer.gd
new file mode 100644
index 0000000..73e9867
--- /dev/null
+++ b/demo/examples_dd3d/demo_music_visualizer.gd
@@ -0,0 +1,175 @@
+@tool
+extends VBoxContainer
+
+@export_range(1, 128) var bars_count := 32
+var transform: Transform3D:
+ get:
+ return %AudioVisualizer.global_transform
+@export_exp_easing("inout") var motion_smoothing := 0.025
+@export_range(0, 0.5) var bar_thickness := 0.065
+@export_range(0, 10) var bars_separation := 0.325
+@export_exp_easing("inout") var color_offset_speed := 0.4
+@export var colors: Gradient = null
+
+var MusicAnalyzerBus := &"MusicAnalyzer"
+var MasterBus := &"Master"
+var MAX_HZ := 16000.0
+var MIN_HZ := 20.0
+var MIN_DB := 60.0
+var spectrum: AudioEffectSpectrumAnalyzerInstance = null
+
+var smoothed_energy: Array[float] = []
+var color_offset := 0.0
+
+var _on_data_loaded_callback = null
+
+
+func _ready():
+ var bus = AudioServer.get_bus_index(MusicAnalyzerBus)
+ if bus == -1:
+ print("'MusicVisualizer' audio bus not found.\nSet 'VisualizerAudioBus.tres' as the default bus to use the audio visualizer.")
+
+ spectrum = AudioServer.get_bus_effect_instance(bus, 0)
+ %MuteMaster.button_pressed = AudioServer.is_bus_mute(AudioServer.get_bus_index(MasterBus))
+ %VolumeSlider.value = db_to_linear(AudioServer.get_bus_volume_db(AudioServer.get_bus_index(MasterBus)))
+
+ if OS.has_feature('web'):
+ motion_smoothing = motion_smoothing * 1.5
+
+ _on_data_loaded_callback = JavaScriptBridge.create_callback(_on_data_loaded)
+ # Retrieve the 'gd_callbacks' object
+ var gdcallbacks: JavaScriptObject = JavaScriptBridge.get_interface("gd_callbacks")
+ # Assign the callbacks
+ gdcallbacks.dataLoaded = _on_data_loaded_callback
+
+
+func _process(_delta):
+ if %MusicPlayer.playing:
+ draw_spectrum()
+
+
+func _pressed():
+ var open_file = func(filepath: String):
+ print("Opening '%s'" % filepath)
+ var file = FileAccess.open(filepath, FileAccess.READ)
+ var data = file.get_buffer(file.get_length())
+ open_stream(filepath.get_extension(), data)
+
+ if DisplayServer.has_feature(DisplayServer.FEATURE_NATIVE_DIALOG):
+ DisplayServer.file_dialog_show("Select audio file", "", "", true, DisplayServer.FILE_DIALOG_MODE_OPEN_FILE, ["*.mp3"],
+ func (status: bool, selected: PackedStringArray, _fileter: int):
+ if status and selected.size():
+ open_file.call(selected[0])
+ )
+ elif OS.has_feature('web'):
+ JavaScriptBridge.eval("loadData()")
+ else:
+ var fd := FileDialog.new()
+ add_child(fd)
+
+ fd.title = "Select audio file"
+ fd.access = FileDialog.ACCESS_FILESYSTEM
+ fd.file_mode = FileDialog.FILE_MODE_OPEN_FILE
+ fd.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS)
+ fd.add_filter("*.mp3")
+ fd.popup_centered_ratio(0.8)
+
+ fd.file_selected.connect(func(path: String):
+ open_file.call(path)
+ )
+
+ fd.visibility_changed.connect(func():
+ if not fd.visible:
+ fd.queue_free()
+ )
+
+
+func _on_data_loaded(data: Array) -> void:
+ # Make sure there is something
+ if (data.size() == 0):
+ return
+
+ var file_name: String = data[0]
+ print("Opening '%s'" % file_name)
+
+ var arr: PackedByteArray = JavaScriptBridge.eval("gd_callbacks.dataLoadedResult;")
+ open_stream(file_name.get_extension(), arr)
+
+
+func open_stream(file_ext: String, data: PackedByteArray):
+ var stream: AudioStream = null
+ if file_ext == "mp3":
+ stream = AudioStreamMP3.new()
+ stream.data = data
+
+ if not stream.data:
+ print("Failed to load MP3!")
+ return
+
+ if not stream:
+ print("Failed to load music!")
+ return
+
+ %MusicPlayer.stream = stream
+ %MusicPlayer.bus = MusicAnalyzerBus
+ %MusicPlayer.play()
+
+ # Debugging frequencies
+ for ih in range(1, bars_count + 1):
+ var _hz: float = log_freq(ih / float(bars_count), MIN_HZ, MAX_HZ)
+ #print("%.0f hz %.2f" % [_hz, ih / float(bars_count)])
+
+
+func draw_spectrum():
+ var _s1 = DebugDraw3D.scoped_config().set_thickness(bar_thickness).set_center_brightness(0.9)
+ var prev_hz = MIN_HZ
+ smoothed_energy.resize(bars_count)
+
+ var xf := transform
+ var y := xf.basis.y
+ var h := y.length()
+ var x := xf.basis.x
+ var z := xf.basis.z
+ var origin := xf.origin - (x * bars_count + (x * bars_separation) * (bars_count - 1)) * 0.5
+ var sum := 0.0
+
+ for ih in range(1, bars_count + 1):
+ var i := ih - 1
+ var hz: float = log_freq(ih / float(bars_count), MIN_HZ, MAX_HZ)
+ var magnitude: float = spectrum.get_magnitude_for_frequency_range(prev_hz, hz, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_AVERAGE).length()
+ var energy: float = clampf((MIN_DB + linear_to_db(magnitude)) / MIN_DB, 0, 1)
+ var e: float = lerp(smoothed_energy[i], energy, clampf(get_process_delta_time() / motion_smoothing if motion_smoothing else 1.0, 0, 1))
+ smoothed_energy[i] = e
+ var height: float = e * h
+ sum += e
+
+ var s := x * bars_separation
+
+ var a := origin + x * i + s * i + (z * 0.5)
+ var b := origin + x * (i + 1) + s * i + (z * -0.5) + xf.basis.y.normalized() * clampf(height, 0.001, h)
+ var c := Color.HOT_PINK
+ if colors:
+ c = colors.sample(wrapf(float(ih) / bars_count + color_offset, 0, 1))
+ c.s = clamp(c.s - smoothed_energy[i] * 0.3, 0, 1.0)
+
+ DebugDraw3D.draw_box_ab(a, b, y, c)
+
+ prev_hz = hz
+
+ color_offset = wrapf(color_offset + sum / smoothed_energy.size() * clampf(get_process_delta_time() / color_offset_speed if color_offset_speed else 1.0, 0, 1), 0, 1)
+
+
+func log10(val: float) -> float:
+ return log(val) / 2.302585
+
+
+func log_freq(pos: float, min_hz: float, max_hz: float) -> float:
+ return pow(10, log10(min_hz) + (log10(max_hz) - log10(min_hz)) * pos)
+
+
+func _on_volume_slider_value_changed(value):
+ AudioServer.set_bus_volume_db(AudioServer.get_bus_index(MasterBus), linear_to_db(value))
+
+
+func _on_mute_master_toggled(toggled_on):
+ AudioServer.set_bus_mute(AudioServer.get_bus_index(MasterBus), toggled_on)
diff --git a/demo/examples_dd3d/demo_music_visualizer.gd.uid b/demo/examples_dd3d/demo_music_visualizer.gd.uid
new file mode 100644
index 0000000..7463855
--- /dev/null
+++ b/demo/examples_dd3d/demo_music_visualizer.gd.uid
@@ -0,0 +1 @@
+uid://bebbekatkxaoe
diff --git a/demo/examples_dd3d/demo_settings_panel.gd b/demo/examples_dd3d/demo_settings_panel.gd
new file mode 100644
index 0000000..4896b47
--- /dev/null
+++ b/demo/examples_dd3d/demo_settings_panel.gd
@@ -0,0 +1,103 @@
+@tool
+extends Control
+
+@export var switch_to_scene = ""
+var is_ready := false
+
+func _ready():
+ if Engine.is_editor_hint():
+ return
+
+ if ProjectSettings.has_setting("application/config/no_csharp_support"):
+ %SwitchLang.visible = false
+
+ %SwitchLang.disabled = true
+
+ %ThicknessSlider.value = get_parent().debug_thickness
+ %FrustumScaleSlider.value = get_parent().camera_frustum_scale
+ %UpdateInPhysics.text = "Update in physics (%d Ticks) *" % ProjectSettings.get_setting("physics/common/physics_ticks_per_second")
+ %UpdateInPhysics.button_pressed = get_parent().update_in_physics
+
+ %ShowText.button_pressed = get_parent().test_text
+ %ShowExamples.button_pressed = get_parent().text_groups_show_examples
+ %ShowStats.button_pressed = get_parent().text_groups_show_stats
+ %ShowHints.button_pressed = get_parent().text_groups_show_hints
+ %Draw3DText.button_pressed = get_parent().draw_3d_text
+
+ %DrawBoxes.button_pressed = get_parent().draw_array_of_boxes
+ %Draw1MBoxes.button_pressed = get_parent().draw_1m_boxes
+ %DrawBoxesAddText.button_pressed = get_parent().draw_text_with_boxes
+
+ if get_tree():
+ await get_tree().create_timer(0.2).timeout
+
+ %SwitchLang.disabled = false
+ is_ready = true
+
+
+func _on_Button_pressed() -> void:
+ get_tree().call_deferred("change_scene_to_file", switch_to_scene)
+
+
+func _on_hide_show_panel_pressed():
+ if %SettingsPanel.visible:
+ %SettingsPanel.hide()
+ %HideShowPanelButton.text = "Show panel"
+ else:
+ %SettingsPanel.show()
+ %HideShowPanelButton.text = "Hide panel"
+
+
+func _on_thickness_slider_value_changed(value):
+ if not is_ready: return
+
+ get_parent().debug_thickness = value
+
+
+func _on_frustum_scale_slider_value_changed(value):
+ if not is_ready: return
+
+ get_parent().camera_frustum_scale = value
+
+
+func _on_update_in_physics_toggled(toggled_on):
+ get_parent().update_in_physics = toggled_on
+
+
+func _on_show_text_toggled(toggled_on: bool) -> void:
+ get_parent().test_text = toggled_on
+
+
+func _on_show_examples_toggled(toggled_on: bool) -> void:
+ get_parent().text_groups_show_examples = toggled_on
+
+
+func _on_show_stats_toggled(toggled_on):
+ get_parent().text_groups_show_stats = toggled_on
+
+
+func _on_show_hints_toggled(toggled_on: bool) -> void:
+ get_parent().text_groups_show_hints = toggled_on
+
+
+func _on_draw_3d_text_toggled(toggled_on: bool) -> void:
+ get_parent().draw_3d_text = toggled_on
+
+
+func _on_draw_boxes_toggled(toggled_on):
+ get_parent().draw_array_of_boxes = toggled_on
+
+ DebugDraw3D.clear_all()
+ get_parent().timer_cubes = 0
+
+
+func _on_draw_1m_boxes_toggled(toggled_on):
+ get_parent().draw_1m_boxes = toggled_on
+
+ if get_parent().draw_array_of_boxes:
+ DebugDraw3D.clear_all()
+ get_parent().timer_cubes = 0
+
+
+func _on_add_text_to_boxes_toggled(toggled_on: bool) -> void:
+ get_parent().draw_text_with_boxes = toggled_on
diff --git a/demo/examples_dd3d/demo_settings_panel.gd.uid b/demo/examples_dd3d/demo_settings_panel.gd.uid
new file mode 100644
index 0000000..3b41e62
--- /dev/null
+++ b/demo/examples_dd3d/demo_settings_panel.gd.uid
@@ -0,0 +1 @@
+uid://83dhsep7l725
diff --git a/demo/examples_dd3d/demo_web_docs_version_select.gd b/demo/examples_dd3d/demo_web_docs_version_select.gd
new file mode 100644
index 0000000..c63bc49
--- /dev/null
+++ b/demo/examples_dd3d/demo_web_docs_version_select.gd
@@ -0,0 +1,42 @@
+extends HBoxContainer
+
+var _on_versions_loaded_callback = null
+@onready var btn: OptionButton = $OptionButton
+
+func _enter_tree():
+ hide()
+
+
+func _ready():
+ if OS.has_feature('web'):
+ _on_versions_loaded_callback = JavaScriptBridge.create_callback(_on_versions_loaded)
+ var versions_callbacks: JavaScriptObject = JavaScriptBridge.get_interface("versions_callbacks")
+ versions_callbacks.loaded = _on_versions_loaded_callback
+
+ JavaScriptBridge.eval("loadVersions()")
+
+
+func _on_versions_loaded(args: Array) -> void:
+ if (args.size() == 0):
+ return
+
+ var current_version: String = args[0]
+
+ var versions_str: String = JavaScriptBridge.eval("versions_callbacks.versions;")
+ var version_urls_str: String = JavaScriptBridge.eval("versions_callbacks.version_urls;")
+ var versions: PackedStringArray = versions_str.split(";", false)
+ var version_urls: PackedStringArray = version_urls_str.split(";", false)
+
+ if versions:
+ show()
+ btn.clear()
+ btn.item_selected.connect(func(idx):
+ # move to another version
+ JavaScriptBridge.eval("window.location.href = \"%s\"" % version_urls[idx])
+ )
+
+ for i in range(versions.size()):
+ btn.add_item(versions[i], i)
+
+ if versions[i] == current_version:
+ btn.select(i)
diff --git a/demo/examples_dd3d/demo_web_docs_version_select.gd.uid b/demo/examples_dd3d/demo_web_docs_version_select.gd.uid
new file mode 100644
index 0000000..a058966
--- /dev/null
+++ b/demo/examples_dd3d/demo_web_docs_version_select.gd.uid
@@ -0,0 +1 @@
+uid://hvx3t70syvkm
diff --git a/demo/main.tscn b/demo/main.tscn
index b5048b4..c97362e 100644
--- a/demo/main.tscn
+++ b/demo/main.tscn
@@ -1,9 +1,23 @@
-[gd_scene load_steps=2 format=3 uid="uid://dvgdgi3jrhrvt"]
+[gd_scene load_steps=3 format=3 uid="uid://dvgdgi3jrhrvt"]
-[ext_resource type="Texture2D" uid="uid://ch0aljsxfwrd6" path="res://icon.svg" id="1_ig7tw"]
+[ext_resource type="Script" uid="uid://dysaws7hlg4td" path="res://scripts/test.gd" id="1_ig7tw"]
-[node name="main" type="Node2D"]
+[sub_resource type="Environment" id="Environment_ig7tw"]
-[node name="GDExample" type="GDExample" parent="."]
-texture = ExtResource("1_ig7tw")
-centered = false
+[node name="Node3D" type="Node3D"]
+script = ExtResource("1_ig7tw")
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_ig7tw")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
+shadow_enabled = true
+
+[node name="Polygon2D" type="Polygon2D" parent="."]
+
+[node name="Polygon2D2" type="Polygon2D" parent="."]
+
+[node name="Camera3D" type="Camera3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5.2934875)
+current = true
diff --git a/demo/project.godot b/demo/project.godot
index 3835f69..7c7eb94 100644
--- a/demo/project.godot
+++ b/demo/project.godot
@@ -11,6 +11,10 @@ config_version=5
[application]
config/name="demo"
-run/main_scene="res://main.tscn"
+run/main_scene="uid://dvgdgi3jrhrvt"
config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg"
+
+[debug_draw_3d]
+
+settings/addon_root_folder="res://addons/debug_draw_3d"
diff --git a/demo/scripts/test.gd b/demo/scripts/test.gd
new file mode 100644
index 0000000..d0ca08c
--- /dev/null
+++ b/demo/scripts/test.gd
@@ -0,0 +1,56 @@
+extends Node
+
+@onready var p1 = $Polygon2D
+@onready var p2 = $Polygon2D2
+
+var lines = []
+
+func _process(delta: float) -> void:
+ for l in lines:
+ DebugDraw3D.draw_lines(l, Color.DARK_RED)
+
+func chunk_array(arr: Array, chunk_size: int) -> Array:
+ var result_chunks = []
+ var i = 0
+ while i < arr.size():
+ var chunk = arr.slice(i, i + chunk_size)
+ result_chunks.append(chunk)
+ i += chunk_size
+ return result_chunks
+
+func _ready() -> void:
+ var tri = Triangulization.new()
+
+ var p1_vectors = [Vector2(4, 0), Vector2(4, 4), Vector2(0, 4), Vector2(0, 0)]
+ var p2_vectors = [Vector2(2, 1), Vector2(2, 2), Vector2(1, 2), Vector2(1, 1)]
+
+ p1.polygon = p1_vectors
+ p2.polygon = p2_vectors
+
+ p2.color = Color(1, 0, 1)
+
+ var result = tri.triangulate_with_holes(p1, p2)
+
+ var packedVector2 = p1_vectors
+ packedVector2.append_array(p2_vectors)
+
+ var triangles = chunk_array(result, 3)
+ print(triangles)
+
+ print(packedVector2)
+
+ for t in triangles:
+ var triangle_line = []
+ for ti in range(len(t)):
+ var rindex = t[ti]
+ var rindexnext = t[(ti+1) % len(t)]
+ var v1 = packedVector2[rindex]
+ var v2 = packedVector2[rindexnext]
+ var vector1 = Vector3(v1.x, v1.y, 0)
+ var vector2 = Vector3(v2.x, v2.y, 0)
+
+ triangle_line.append(vector1)
+ triangle_line.append(vector2)
+ lines.append(triangle_line)
+
+ print(lines)
diff --git a/demo/scripts/test.gd.uid b/demo/scripts/test.gd.uid
new file mode 100644
index 0000000..43a4476
--- /dev/null
+++ b/demo/scripts/test.gd.uid
@@ -0,0 +1 @@
+uid://dysaws7hlg4td
diff --git a/src/earcut.hpp b/src/earcut.hpp
new file mode 100644
index 0000000..d35ecf0
--- /dev/null
+++ b/src/earcut.hpp
@@ -0,0 +1,932 @@
+#pragma once
+
+#include "godot_cpp/variant/vector2.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace mapbox {
+
+namespace util {
+
+template struct nth {
+ inline static typename std::tuple_element::type get(const T &t) {
+ return std::get(t);
+ };
+};
+
+} // namespace util
+
+namespace detail {
+
+template class Earcut {
+public:
+ std::vector indices;
+ std::size_t vertices = 0;
+
+ template void operator()(const Polygon &points);
+
+private:
+ struct Node {
+ Node(N index, double x_, double y_) : x(x_), y(y_), i(index), steiner(0) {}
+ Node(const Node &) = delete;
+ Node &operator=(const Node &) = delete;
+ Node(Node &&) = delete;
+ Node &operator=(Node &&) = delete;
+
+ const double x;
+ const double y;
+
+ // previous and next vertice nodes in a polygon ring
+ Node *prev = nullptr;
+ Node *next = nullptr;
+
+ // z-order curve value
+ int32_t z = 0;
+
+ // original index in polygon
+ const N i : (sizeof(N) * 8 - 1);
+
+ // indicates whether this is a steiner point
+ N steiner : 1;
+
+ // previous and next nodes in z-order
+ Node *prevZ = nullptr;
+ Node *nextZ = nullptr;
+ };
+
+ // Cache-optimized Triangle structure for repeated geometric tests
+ struct Triangle {
+ const double ax, ay;
+ const double bx, by;
+ const double cx, cy;
+
+ Triangle(const Node *a, const Node *b, const Node *c)
+ : ax(a->x), ay(a->y), bx(b->x), by(b->y), cx(c->x), cy(c->y) {}
+
+ inline double area() const {
+ return (by - ay) * (cx - bx) - (bx - ax) * (cy - by);
+ }
+
+ inline bool containsPoint(double px, double py) const {
+ return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
+ (ax - px) * (by - py) >= (bx - px) * (ay - py) &&
+ (bx - px) * (cy - py) >= (cx - px) * (by - py);
+ }
+ };
+
+ template
+ Node *linkedList(const Ring &points, const bool clockwise);
+ Node *filterPoints(Node *start, Node *end = nullptr);
+ void earcutLinked(Node *ear, int pass = 0);
+ bool isEar(Node *ear);
+ bool isEarHashed(Node *ear);
+ Node *cureLocalIntersections(Node *start);
+ void splitEarcut(Node *start);
+ template
+ Node *eliminateHoles(const Polygon &points, Node *outerNode);
+ Node *eliminateHole(Node *hole, Node *outerNode);
+ Node *findHoleBridge(Node *hole, Node *outerNode);
+ bool sectorContainsSector(const Node *m, const Node *p);
+ void indexCurve(Node *start);
+ Node *sortLinked(Node *list);
+ int32_t zOrder(const double x_, const double y_);
+ Node *getLeftmost(Node *start);
+ bool pointInTriangle(double ax, double ay, double bx, double by, double cx,
+ double cy, double px, double py) const;
+ bool isValidDiagonal(Node *a, Node *b);
+ double area(const Node *p, const Node *q, const Node *r) const;
+ bool equals(const Node *p1, const Node *p2);
+ bool intersects(const Node *p1, const Node *q1, const Node *p2,
+ const Node *q2);
+ bool onSegment(const Node *p, const Node *q, const Node *r);
+ int sign(double val);
+ bool intersectsPolygon(const Node *a, const Node *b);
+ bool locallyInside(const Node *a, const Node *b);
+ bool middleInside(const Node *a, const Node *b);
+ Node *splitPolygon(Node *a, Node *b);
+ template
+ Node *insertNode(std::size_t i, const Point &p, Node *last);
+ void removeNode(Node *p);
+
+ bool hashing;
+ double minX, maxX;
+ double minY, maxY;
+ double inv_size = 0;
+
+ template > class ObjectPool {
+ public:
+ ObjectPool() { allocateNewBlock(256); }
+ ObjectPool(std::size_t blockSize_) : baseBlockSize(blockSize_) {
+ allocateNewBlock(std::max(blockSize_, 256));
+ }
+ ~ObjectPool() { clear(); }
+ template T *construct(Args &&...args) {
+ // If current block is full, move to next block or allocate new one
+ if (currentIndex >= baseBlockSize) {
+ currentBlockIndex++;
+ if (currentBlockIndex < memoryBlocks.size()) {
+ // Reuse existing block
+ currentIndex = 0;
+ } else {
+ // Allocate a new one
+ allocateNewBlock(baseBlockSize);
+ }
+ }
+
+ T *object = memoryBlocks[currentBlockIndex].get() + currentIndex;
+ alloc_traits::construct(alloc, object, std::forward(args)...);
+ totalObjects++;
+ currentIndex++;
+ return object;
+ }
+ void reset() { clear(); }
+ void clear() {
+ // Destroy all objects, but keep blocks allocated for reuse
+ std::size_t objectsDestroyed = 0;
+ for (std::size_t blockIdx = 0;
+ blockIdx < memoryBlocks.size() && objectsDestroyed < totalObjects;
+ ++blockIdx) {
+ // check if we are in the last block
+ std::size_t objectsInThisBlock =
+ std::min(baseBlockSize, totalObjects - objectsDestroyed);
+ for (std::size_t i = 0; i < objectsInThisBlock; ++i) {
+ T *object = memoryBlocks[blockIdx].get() + i;
+ alloc_traits::destroy(alloc, object);
+ }
+ objectsDestroyed += objectsInThisBlock;
+ }
+ // Reset to start from first block again
+ currentBlockIndex = 0;
+ currentIndex = 0;
+ totalObjects = 0;
+ }
+
+ private:
+ Alloc alloc;
+ typedef typename std::allocator_traits alloc_traits;
+
+ // Custom deleter that uses the allocator
+ struct AllocDeleter {
+ Alloc alloc;
+ std::size_t capacity;
+ void operator()(T *ptr) {
+ alloc_traits::deallocate(alloc, ptr, capacity);
+ }
+ };
+
+ std::vector> memoryBlocks;
+ std::vector blockCapacities;
+ std::size_t currentBlockIndex = 0;
+ std::size_t currentIndex = 0;
+ std::size_t totalObjects = 0;
+ std::size_t baseBlockSize = 256;
+
+ void allocateNewBlock(std::size_t capacity) {
+ T *rawMemory = alloc_traits::allocate(alloc, capacity);
+ auto newBlock = std::unique_ptr(
+ rawMemory, AllocDeleter{alloc, capacity});
+ memoryBlocks.push_back(std::move(newBlock));
+ blockCapacities.push_back(capacity);
+ currentBlockIndex = memoryBlocks.size() - 1;
+ currentIndex = 0;
+ }
+ };
+
+ std::unique_ptr> nodes;
+ std::vector holeQueue;
+};
+
+template
+template
+void Earcut::operator()(const Polygon &points) {
+ // reset
+ indices.clear();
+ vertices = 0;
+
+ if (points.empty())
+ return;
+
+ double x;
+ double y;
+ int threshold = 80;
+ std::size_t len = 0;
+
+ for (size_t i = 0; threshold >= 0 && i < points.size(); i++) {
+ threshold -= static_cast(points[i].size());
+ len += points[i].size();
+ }
+
+ // estimate size of nodes and indices
+ if (!nodes) {
+ std::size_t estimatedNodes = len * 3 / 2;
+ nodes = std::make_unique>(
+ std::max(estimatedNodes, 256));
+ }
+ indices.reserve(len + points[0].size());
+
+ Node *outerNode = linkedList(points[0], true);
+ if (!outerNode || outerNode->prev == outerNode->next)
+ return;
+
+ if (points.size() > 1)
+ outerNode = eliminateHoles(points, outerNode);
+
+ // if the shape is not too simple, we'll use z-order curve hash later;
+ // calculate polygon bbox
+ hashing = threshold < 0;
+ if (hashing) {
+ Node *p = outerNode->next;
+ minX = maxX = outerNode->x;
+ minY = maxY = outerNode->y;
+ do {
+ x = p->x;
+ y = p->y;
+ minX = std::min(minX, x);
+ minY = std::min(minY, y);
+ maxX = std::max(maxX, x);
+ maxY = std::max(maxY, y);
+ p = p->next;
+ } while (p != outerNode);
+
+ // minX, minY and inv_size are later used to transform coords into integers
+ // for z-order calculation
+ inv_size = std::max(maxX - minX, maxY - minY);
+ inv_size = inv_size != .0 ? (32767. / inv_size) : .0;
+ }
+
+ earcutLinked(outerNode);
+
+ nodes->clear();
+ holeQueue.clear();
+}
+
+// create a circular doubly linked list from polygon points in the specified
+// winding order
+template
+template
+typename Earcut::Node *Earcut::linkedList(const Ring &points,
+ const bool clockwise) {
+ using Point = typename Ring::value_type;
+ double sum = 0;
+ const std::size_t len = points.size();
+ std::size_t i, j;
+ Node *last = nullptr;
+
+ // calculate original winding order of a polygon ring
+ for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) {
+ const auto &p1 = points[i];
+ const auto &p2 = points[j];
+ const double p20 = util::nth<0, Point>::get(p2);
+ const double p10 = util::nth<0, Point>::get(p1);
+ const double p11 = util::nth<1, Point>::get(p1);
+ const double p21 = util::nth<1, Point>::get(p2);
+ sum += (p20 - p10) * (p11 + p21);
+ }
+
+ // link points into circular doubly-linked list in the specified winding order
+ if (clockwise == (sum > 0)) {
+ for (i = 0; i < len; i++)
+ last = insertNode(vertices + i, points[i], last);
+ } else {
+ for (i = len; i-- > 0;)
+ last = insertNode(vertices + i, points[i], last);
+ }
+
+ if (last && equals(last, last->next)) {
+ removeNode(last);
+ last = last->next;
+ }
+
+ vertices += len;
+
+ return last;
+}
+
+// eliminate colinear or duplicate points
+template
+typename Earcut::Node *Earcut::filterPoints(Node *start, Node *end) {
+ if (!end)
+ end = start;
+
+ Node *p = start;
+ bool again;
+ do {
+ again = false;
+
+ if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) {
+ removeNode(p);
+ p = end = p->prev;
+
+ if (p == p->next)
+ break;
+ again = true;
+
+ } else {
+ p = p->next;
+ }
+ } while (again || p != end);
+
+ return end;
+}
+
+// main ear slicing loop which triangulates a polygon (given as a linked list)
+template void Earcut::earcutLinked(Node *ear, int pass) {
+ if (!ear)
+ return;
+
+ // interlink polygon nodes in z-order
+ if (!pass && hashing)
+ indexCurve(ear);
+
+ Node *stop = ear;
+ Node *prev;
+ Node *next;
+
+ // iterate through ears, slicing them one by one
+ while (ear->prev != ear->next) {
+ prev = ear->prev;
+ next = ear->next;
+
+ if (hashing ? isEarHashed(ear) : isEar(ear)) {
+ // cut off the triangle
+ indices.emplace_back(prev->i);
+ indices.emplace_back(ear->i);
+ indices.emplace_back(next->i);
+
+ removeNode(ear);
+
+ // skipping the next vertice leads to less sliver triangles
+ ear = next->next;
+ stop = next->next;
+
+ continue;
+ }
+
+ ear = next;
+
+ // if we looped through the whole remaining polygon and can't find any more
+ // ears
+ if (ear == stop) {
+ // try filtering points and slicing again
+ if (!pass)
+ earcutLinked(filterPoints(ear), 1);
+
+ // if this didn't work, try curing all small self-intersections locally
+ else if (pass == 1) {
+ ear = cureLocalIntersections(filterPoints(ear));
+ earcutLinked(ear, 2);
+
+ // as a last resort, try splitting the remaining polygon into two
+ } else if (pass == 2)
+ splitEarcut(ear);
+
+ break;
+ }
+ }
+}
+
+// check whether a polygon node forms a valid ear with adjacent nodes
+template bool Earcut::isEar(Node *ear) {
+ const Node *a = ear->prev;
+ const Node *b = ear;
+ const Node *c = ear->next;
+
+ // Create triangle with cached coordinates and bounding box
+ const Triangle tri(a, b, c);
+ if (tri.area() >= 0)
+ return false; // reflex, can't be an ear
+
+ // now make sure we don't have other points inside the potential ear
+ Node *p = ear->next->next;
+
+ while (p != ear->prev) {
+ if (tri.containsPoint(p->x, p->y) && area(p->prev, p, p->next) >= 0)
+ return false;
+ p = p->next;
+ }
+
+ return true;
+}
+
+template bool Earcut::isEarHashed(Node *ear) {
+ const Node *a = ear->prev;
+ const Node *b = ear;
+ const Node *c = ear->next;
+
+ // Create triangle with cached coordinates and bounding box
+ const Triangle tri(a, b, c);
+ if (tri.area() >= 0)
+ return false; // reflex, can't be an ear
+
+ // triangle bbox; min & max are calculated like this for speed
+ const double minTX =
+ std::min(tri.ax, std::min(tri.bx, tri.cx));
+ const double minTY =
+ std::min(tri.ay, std::min(tri.by, tri.cy));
+ const double maxTX =
+ std::max(tri.ax, std::max(tri.bx, tri.cx));
+ const double maxTY =
+ std::max(tri.ay, std::max(tri.by, tri.cy));
+
+ // z-order range for the current triangle bbox;
+ const int32_t minZ = zOrder(minTX, minTY);
+ const int32_t maxZ = zOrder(maxTX, maxTY);
+
+ // first look for points inside the triangle in increasing z-order
+ Node *p = ear->nextZ;
+
+ while (p && p->z <= maxZ) {
+ if (p != ear->prev && p != ear->next && tri.containsPoint(p->x, p->y) &&
+ area(p->prev, p, p->next) >= 0)
+ return false;
+ p = p->nextZ;
+ }
+
+ // then look for points in decreasing z-order
+ p = ear->prevZ;
+
+ while (p && p->z >= minZ) {
+ if (p != ear->prev && p != ear->next && tri.containsPoint(p->x, p->y) &&
+ area(p->prev, p, p->next) >= 0)
+ return false;
+ p = p->prevZ;
+ }
+
+ return true;
+}
+
+// go through all polygon nodes and cure small local self-intersections
+template
+typename Earcut::Node *Earcut::cureLocalIntersections(Node *start) {
+ Node *p = start;
+ do {
+ Node *a = p->prev;
+ Node *b = p->next->next;
+
+ // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
+ if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) &&
+ locallyInside(b, a)) {
+ indices.emplace_back(a->i);
+ indices.emplace_back(p->i);
+ indices.emplace_back(b->i);
+
+ // remove two nodes involved
+ removeNode(p);
+ removeNode(p->next);
+
+ p = start = b;
+ }
+ p = p->next;
+ } while (p != start);
+
+ return filterPoints(p);
+}
+
+// try splitting polygon into two and triangulate them independently
+template void Earcut::splitEarcut(Node *start) {
+ // look for a valid diagonal that divides the polygon into two
+ Node *a = start;
+ do {
+ Node *b = a->next->next;
+ while (b != a->prev) {
+ if (a->i != b->i && isValidDiagonal(a, b)) {
+ // split the polygon in two by the diagonal
+ Node *c = splitPolygon(a, b);
+
+ // filter colinear points around the cuts
+ a = filterPoints(a, a->next);
+ c = filterPoints(c, c->next);
+
+ // run earcut on each half
+ earcutLinked(a);
+ earcutLinked(c);
+ return;
+ }
+ b = b->next;
+ }
+ a = a->next;
+ } while (a != start);
+}
+
+// link every hole into the outer loop, producing a single-ring polygon without
+// holes
+template
+template
+typename Earcut::Node *Earcut::eliminateHoles(const Polygon &points,
+ Node *outerNode) {
+ const size_t len = points.size();
+
+ holeQueue.clear();
+ for (size_t i = 1; i < len; i++) {
+ Node *list = linkedList(points[i], false);
+ if (list) {
+ if (list == list->next)
+ list->steiner = true;
+ holeQueue.push_back(getLeftmost(list));
+ }
+ }
+ std::sort(holeQueue.begin(), holeQueue.end(),
+ [](const Node *a, const Node *b) { return a->x < b->x; });
+
+ // process holes from left to right
+ for (size_t i = 0; i < holeQueue.size(); i++) {
+ outerNode = eliminateHole(holeQueue[i], outerNode);
+ }
+
+ return outerNode;
+}
+
+// find a bridge between vertices that connects hole with an outer ring and and
+// link it
+template
+typename Earcut::Node *Earcut::eliminateHole(Node *hole,
+ Node *outerNode) {
+ Node *bridge = findHoleBridge(hole, outerNode);
+ if (!bridge) {
+ return outerNode;
+ }
+
+ Node *bridgeReverse = splitPolygon(bridge, hole);
+
+ // filter collinear points around the cuts
+ filterPoints(bridgeReverse, bridgeReverse->next);
+
+ // Check if input node was removed by the filtering
+ return filterPoints(bridge, bridge->next);
+}
+
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+template
+typename Earcut::Node *Earcut::findHoleBridge(Node *hole,
+ Node *outerNode) {
+ Node *p = outerNode;
+ double hx = hole->x;
+ double hy = hole->y;
+ double qx = -std::numeric_limits::infinity();
+ Node *m = nullptr;
+
+ // find a segment intersected by a ray from the hole's leftmost Vertex to the
+ // left; segment's endpoint with lesser x will be potential connection Vertex
+ do {
+ if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) {
+ double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y);
+ if (x <= hx && x > qx) {
+ qx = x;
+ m = p->x < p->next->x ? p : p->next;
+ if (x == hx)
+ return m; // hole touches outer segment; pick leftmost endpoint
+ }
+ }
+ p = p->next;
+ } while (p != outerNode);
+
+ if (!m)
+ return 0;
+
+ // look for points inside the triangle of hole Vertex, segment intersection
+ // and endpoint; if there are no points found, we have a valid connection;
+ // otherwise choose the Vertex of the minimum angle with the ray as connection
+ // Vertex
+
+ const Node *stop = m;
+ double tanMin = std::numeric_limits::infinity();
+ double tanCur = 0;
+
+ p = m;
+ double mx = m->x;
+ double my = m->y;
+
+ do {
+ if (hx >= p->x && p->x >= mx && hx != p->x &&
+ pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy,
+ p->x, p->y)) {
+ tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential
+
+ if (locallyInside(p, hole) &&
+ (tanCur < tanMin ||
+ (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) {
+ m = p;
+ tanMin = tanCur;
+ }
+ }
+
+ p = p->next;
+ } while (p != stop);
+
+ return m;
+}
+
+// whether sector in vertex m contains sector in vertex p in the same
+// coordinates
+template
+bool Earcut::sectorContainsSector(const Node *m, const Node *p) {
+ return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0;
+}
+
+// interlink polygon nodes in z-order
+template void Earcut::indexCurve(Node *start) {
+ assert(start);
+ Node *p = start;
+
+ do {
+ p->z = p->z ? p->z : zOrder(p->x, p->y);
+ p->prevZ = p->prev;
+ p->nextZ = p->next;
+ p = p->next;
+ } while (p != start);
+
+ p->prevZ->nextZ = nullptr;
+ p->prevZ = nullptr;
+
+ sortLinked(p);
+}
+
+// Simon Tatham's linked list merge sort algorithm
+// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+template
+typename Earcut::Node *Earcut::sortLinked(Node *list) {
+ assert(list);
+ Node *p;
+ Node *q;
+ Node *e;
+ Node *tail;
+ int i, numMerges, pSize, qSize;
+ int inSize = 1;
+
+ for (;;) {
+ p = list;
+ list = nullptr;
+ tail = nullptr;
+ numMerges = 0;
+
+ while (p) {
+ numMerges++;
+ q = p;
+ pSize = 0;
+ for (i = 0; i < inSize; i++) {
+ pSize++;
+ q = q->nextZ;
+ if (!q)
+ break;
+ }
+
+ qSize = inSize;
+
+ while (pSize > 0 || (qSize > 0 && q)) {
+ if (pSize == 0) {
+ e = q;
+ q = q->nextZ;
+ qSize--;
+ } else if (qSize == 0 || !q) {
+ e = p;
+ p = p->nextZ;
+ pSize--;
+ } else if (p->z <= q->z) {
+ e = p;
+ p = p->nextZ;
+ pSize--;
+ } else {
+ e = q;
+ q = q->nextZ;
+ qSize--;
+ }
+
+ if (tail)
+ tail->nextZ = e;
+ else
+ list = e;
+
+ e->prevZ = tail;
+ tail = e;
+ }
+
+ p = q;
+ }
+
+ tail->nextZ = nullptr;
+
+ if (numMerges <= 1)
+ return list;
+
+ inSize *= 2;
+ }
+}
+
+// z-order of a Vertex given coords and size of the data bounding box
+template
+int32_t Earcut::zOrder(const double x_, const double y_) {
+ // coords are transformed into non-negative 15-bit integer range
+ int32_t x = static_cast((x_ - minX) * inv_size);
+ int32_t y = static_cast((y_ - minY) * inv_size);
+
+ x = (x | (x << 8)) & 0x00FF00FF;
+ x = (x | (x << 4)) & 0x0F0F0F0F;
+ x = (x | (x << 2)) & 0x33333333;
+ x = (x | (x << 1)) & 0x55555555;
+
+ y = (y | (y << 8)) & 0x00FF00FF;
+ y = (y | (y << 4)) & 0x0F0F0F0F;
+ y = (y | (y << 2)) & 0x33333333;
+ y = (y | (y << 1)) & 0x55555555;
+
+ return x | (y << 1);
+}
+
+// find the leftmost node of a polygon ring
+template
+typename Earcut::Node *Earcut::getLeftmost(Node *start) {
+ Node *p = start;
+ Node *leftmost = start;
+ do {
+ if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y))
+ leftmost = p;
+ p = p->next;
+ } while (p != start);
+
+ return leftmost;
+}
+
+// check if a point lies within a convex triangle
+template
+bool Earcut::pointInTriangle(double ax, double ay, double bx, double by,
+ double cx, double cy, double px,
+ double py) const {
+ return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
+ (ax - px) * (by - py) >= (bx - px) * (ay - py) &&
+ (bx - px) * (cy - py) >= (cx - px) * (by - py);
+}
+
+// check if a diagonal between two polygon nodes is valid (lies in polygon
+// interior)
+template bool Earcut::isValidDiagonal(Node *a, Node *b) {
+ return a->next->i != b->i && a->prev->i != b->i &&
+ !intersectsPolygon(a, b) && // dones't intersect other edges
+ ((locallyInside(a, b) && locallyInside(b, a) &&
+ middleInside(a, b) && // locally visible
+ (area(a->prev, a, b->prev) != 0.0 ||
+ area(a, b->prev, b) !=
+ 0.0)) || // does not create opposite-facing sectors
+ (equals(a, b) && area(a->prev, a, a->next) > 0 &&
+ area(b->prev, b, b->next) > 0)); // special zero-length case
+}
+
+// signed area of a triangle
+template
+double Earcut::area(const Node *p, const Node *q, const Node *r) const {
+ return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y);
+}
+
+// check if two points are equal
+template bool Earcut::equals(const Node *p1, const Node *p2) {
+ return p1->x == p2->x && p1->y == p2->y;
+}
+
+// check if two segments intersect
+template
+bool Earcut::intersects(const Node *p1, const Node *q1, const Node *p2,
+ const Node *q2) {
+ int o1 = sign(area(p1, q1, p2));
+ int o2 = sign(area(p1, q1, q2));
+ int o3 = sign(area(p2, q2, p1));
+ int o4 = sign(area(p2, q2, q1));
+
+ if (o1 != o2 && o3 != o4)
+ return true; // general case
+
+ if (o1 == 0 && onSegment(p1, p2, q1))
+ return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
+ if (o2 == 0 && onSegment(p1, q2, q1))
+ return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
+ if (o3 == 0 && onSegment(p2, p1, q2))
+ return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
+ if (o4 == 0 && onSegment(p2, q1, q2))
+ return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
+
+ return false;
+}
+
+// for collinear points p, q, r, check if point q lies on segment pr
+template
+bool Earcut::onSegment(const Node *p, const Node *q, const Node *r) {
+ return q->x <= std::max(p->x, r->x) &&
+ q->x >= std::min(p->x, r->x) &&
+ q->y <= std::max(p->y, r->y) &&
+ q->y >= std::min(p->y, r->y);
+}
+
+template int Earcut::sign(double val) {
+ return (0.0 < val) - (val < 0.0);
+}
+
+// check if a polygon diagonal intersects any polygon segments
+template
+bool Earcut::intersectsPolygon(const Node *a, const Node *b) {
+ const Node *p = a;
+ do {
+ if (p->i != a->i && p->next->i != a->i && p->i != b->i &&
+ p->next->i != b->i && intersects(p, p->next, a, b))
+ return true;
+ p = p->next;
+ } while (p != a);
+
+ return false;
+}
+
+// check if a polygon diagonal is locally inside the polygon
+template
+bool Earcut::locallyInside(const Node *a, const Node *b) {
+ return area(a->prev, a, a->next) < 0
+ ? area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0
+ : area(a, b, a->prev) < 0 || area(a, a->next, b) < 0;
+}
+
+// check if the middle Vertex of a polygon diagonal is inside the polygon
+template
+bool Earcut::middleInside(const Node *a, const Node *b) {
+ const Node *p = a;
+ bool inside = false;
+ double px = (a->x + b->x) / 2;
+ double py = (a->y + b->y) / 2;
+ do {
+ if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y &&
+ (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x))
+ inside = !inside;
+ p = p->next;
+ } while (p != a);
+
+ return inside;
+}
+
+// link two polygon vertices with a bridge; if the vertices belong to the same
+// ring, it splits polygon into two; if one belongs to the outer ring and
+// another to a hole, it merges it into a single ring
+template
+typename Earcut::Node *Earcut::splitPolygon(Node *a, Node *b) {
+ Node *a2 = nodes->construct(a->i, a->x, a->y);
+ Node *b2 = nodes->construct(b->i, b->x, b->y);
+ Node *an = a->next;
+ Node *bp = b->prev;
+
+ a->next = b;
+ b->prev = a;
+
+ a2->next = an;
+ an->prev = a2;
+
+ b2->next = a2;
+ a2->prev = b2;
+
+ bp->next = b2;
+ b2->prev = bp;
+
+ return b2;
+}
+
+// create a node and util::optionally link it with previous one (in a circular
+// doubly linked list)
+template
+template
+typename Earcut::Node *Earcut::insertNode(std::size_t i, const Point &pt,
+ Node *last) {
+ Node *p = nodes->construct(static_cast(i), util::nth<0, Point>::get(pt),
+ util::nth<1, Point>::get(pt));
+
+ if (!last) {
+ p->prev = p;
+ p->next = p;
+
+ } else {
+ assert(last);
+ p->next = last->next;
+ p->prev = last;
+ last->next->prev = p;
+ last->next = p;
+ }
+ return p;
+}
+
+template void Earcut::removeNode(Node *p) {
+ p->next->prev = p->prev;
+ p->prev->next = p->next;
+
+ if (p->prevZ)
+ p->prevZ->nextZ = p->nextZ;
+ if (p->nextZ)
+ p->nextZ->prevZ = p->prevZ;
+}
+} // namespace detail
+
+template
+std::vector earcut(const Polygon &poly) {
+ mapbox::detail::Earcut earcut;
+ earcut(poly);
+ return std::move(earcut.indices);
+}
+} // namespace mapbox
diff --git a/src/gdexample.cpp b/src/gdexample.cpp
deleted file mode 100644
index 43c9661..0000000
--- a/src/gdexample.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#include "gdexample.h"
-#include
-
-using namespace godot;
-
-void GDExample::_bind_methods() {}
-
-GDExample::GDExample() {
- // Initialize any variables here.
- time_passed = 0.0;
-}
-
-GDExample::~GDExample() {
- // Add your cleanup here.
-}
-
-void GDExample::_process(double delta) {
- time_passed += delta;
-
- Vector2 new_position = Vector2(10.0 + (10.0 * sin(time_passed * 2.0)),
- 10.0 + (10.0 * cos(time_passed * 1.5)));
-
- set_position(new_position);
-}
diff --git a/src/gdexample.h b/src/gdexample.h
deleted file mode 100644
index 5a77349..0000000
--- a/src/gdexample.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include
-
-namespace godot {
-
-class GDExample : public Sprite2D {
- GDCLASS(GDExample, Sprite2D)
-
-private:
- double time_passed;
-
-protected:
- static void _bind_methods();
-
-public:
- GDExample();
- ~GDExample();
-
- void _process(double delta) override;
-};
-
-} // namespace godot
diff --git a/src/register_types.cpp b/src/register_types.cpp
index 5f3f0ed..e24c4e2 100644
--- a/src/register_types.cpp
+++ b/src/register_types.cpp
@@ -1,6 +1,6 @@
#include "register_types.h"
-
-#include "gdexample.h"
+#include "godot_cpp/core/class_db.hpp"
+#include "triangulization.hpp"
#include
#include
@@ -13,7 +13,7 @@ void initialize_triangulation_module(ModuleInitializationLevel p_level) {
return;
}
- GDREGISTER_RUNTIME_CLASS(GDExample);
+ GDREGISTER_CLASS(Triangulization)
}
void uninitialize_triangulation_module(ModuleInitializationLevel p_level) {
diff --git a/src/register_types.os b/src/register_types.os
index 7c332df..8f35388 100644
Binary files a/src/register_types.os and b/src/register_types.os differ
diff --git a/src/triangulization.cpp b/src/triangulization.cpp
new file mode 100644
index 0000000..1f7c311
--- /dev/null
+++ b/src/triangulization.cpp
@@ -0,0 +1,61 @@
+#include "triangulization.hpp"
+#include "earcut.hpp"
+#include "godot_cpp/classes/polygon2d.hpp"
+#include "godot_cpp/core/class_db.hpp"
+#include "godot_cpp/variant/array.hpp"
+#include "godot_cpp/variant/packed_int32_array.hpp"
+#include "godot_cpp/variant/packed_vector2_array.hpp"
+#include "godot_cpp/variant/string.hpp"
+#include "godot_cpp/variant/utility_functions.hpp"
+#include "godot_cpp/variant/vector2.hpp"
+#include
+#include
+#include
+
+Triangulization::Triangulization() {}
+
+void Triangulization::_bind_methods() {
+ ClassDB::bind_method(
+ godot::D_METHOD("triangulate_with_holes", "outer", "inner"),
+ &Triangulization::triangulate_with_holes);
+}
+
+std::vector>
+_convert_to_std_vec(const godot::PackedVector2Array &packed_array) {
+ std::vector> std_vec;
+ std_vec.reserve(packed_array.size());
+
+ for (int i = 0; i < packed_array.size(); i++) {
+ Vector2 tmp = packed_array[i];
+ std_vec.push_back({tmp.x, tmp.y});
+ }
+
+ return std_vec;
+}
+
+PackedInt32Array Triangulization::triangulate_with_holes(Polygon2D *outer,
+ Polygon2D *inner) {
+ std::vector>> p;
+
+ p.push_back(_convert_to_std_vec(outer->get_polygon()));
+ p.push_back(_convert_to_std_vec(inner->get_polygon()));
+
+ // for (int i = 0; i < p.size(); i++) {
+ // for (int j = 0; j < p[i].size(); j++) {
+ // UtilityFunctions::print(String("Value: "),
+ // String::num_uint64(p[i][j][0]),
+ // String(","), String::num_uint64(p[i][j][1]));
+ // }
+ // }
+
+ std::vector indices = mapbox::earcut(p);
+ // reverse to counter clockwise creating of triangles
+ std::reverse(indices.begin(), indices.end());
+
+ PackedInt32Array return_values;
+ for (int i = 0; i < indices.size(); i++) {
+ return_values.push_back(indices[i]);
+ }
+
+ return return_values;
+};
diff --git a/src/triangulization.hpp b/src/triangulization.hpp
new file mode 100644
index 0000000..1bf5217
--- /dev/null
+++ b/src/triangulization.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "godot_cpp/classes/wrapped.hpp"
+#include "godot_cpp/variant/packed_int32_array.hpp"
+#include
+#include
+
+using namespace godot;
+
+class Triangulization : public Object {
+ GDCLASS(Triangulization, Object);
+
+protected:
+ static void _bind_methods();
+
+public:
+ Triangulization();
+
+ PackedInt32Array triangulate_with_holes(godot::Polygon2D *inner,
+ godot::Polygon2D *outer);
+};
diff --git a/src/triangulization.os b/src/triangulization.os
new file mode 100644
index 0000000..4224111
Binary files /dev/null and b/src/triangulization.os differ
diff --git a/src/util.hpp b/src/util.hpp
new file mode 100644
index 0000000..e69de29