Working Triangulization with Hole
This commit is contained in:
BIN
.sconsign.dblite
BIN
.sconsign.dblite
Binary file not shown.
21
demo/addons/debug_draw_3d/LICENSE
Normal file
21
demo/addons/debug_draw_3d/LICENSE
Normal file
@@ -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.
|
||||
157
demo/addons/debug_draw_3d/README.md
Normal file
157
demo/addons/debug_draw_3d/README.md
Normal file
@@ -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.
|
||||
|
||||
<a href="https://boosty.to/dmitriysalnikov/donate"><img src="/docs/images/boosty.png" alt="Boosty" width=150px/></a>
|
||||
|
||||
<a href="#"><img src="/docs/images/USDT-TRC20.png" alt="USDT-TRC20" width=150px/></a>
|
||||
|
||||
<b>USDT-TRC20 TEw934PrsffHsAn5M63SoHYRuZo984EF6v</b>
|
||||
|
||||
## 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
|
||||

|
||||
153
demo/addons/debug_draw_3d/debug_draw_3d.gdextension
Normal file
153
demo/addons/debug_draw_3d/debug_draw_3d.gdextension
Normal file
@@ -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"
|
||||
1
demo/addons/debug_draw_3d/debug_draw_3d.gdextension.uid
Normal file
1
demo/addons/debug_draw_3d/debug_draw_3d.gdextension.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://svqaxfp5kyrl
|
||||
0
demo/addons/debug_draw_3d/libs/.gdignore
Normal file
0
demo/addons/debug_draw_3d/libs/.gdignore
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libdd3d.macos.editor.universal.dylib</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>ru.dmitriysalnikov.dd3d</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Dmitriy Salnikov.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>macosx</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libdd3d.macos.template_release.universal.enabled.dylib</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>ru.dmitriysalnikov.dd3d</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Dmitriy Salnikov.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>macosx</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libdd3d.macos.template_release.universal.dylib</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>ru.dmitriysalnikov.dd3d</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Dmitriy Salnikov.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>macosx</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
621
demo/examples_dd3d/DebugDrawDemoScene.gd
Normal file
621
demo/examples_dd3d/DebugDrawDemoScene.gd
Normal file
@@ -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!")
|
||||
1
demo/examples_dd3d/DebugDrawDemoScene.gd.uid
Normal file
1
demo/examples_dd3d/DebugDrawDemoScene.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ba2ie81p2x3x7
|
||||
1042
demo/examples_dd3d/DebugDrawDemoScene.tscn
Normal file
1042
demo/examples_dd3d/DebugDrawDemoScene.tscn
Normal file
File diff suppressed because it is too large
Load Diff
854
demo/examples_dd3d/DebugDrawDemoSceneCS.cs
Normal file
854
demo/examples_dd3d/DebugDrawDemoSceneCS.cs
Normal file
@@ -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<Key, int> button_presses = new Dictionary<Key, int>() {
|
||||
{ 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<Node3D>(0).GlobalPosition, dAABB.GetChild<Node3D>(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<Vector3> points = new List<Vector3>();
|
||||
List<Vector3> points_below = new List<Vector3>();
|
||||
List<Vector3> points_below2 = new List<Vector3>();
|
||||
List<Vector3> points_below3 = new List<Vector3>();
|
||||
List<Vector3> points_below4 = new List<Vector3>();
|
||||
List<Vector3> lines_above = new List<Vector3>();
|
||||
|
||||
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<Node3D>("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<Node3D>("HitTest");
|
||||
dLagTest = GetNode<CsgBox3D>("LagTest");
|
||||
dPanel = GetNode<PanelContainer>("Panel");
|
||||
dZones = GetNode<Node3D>("Zones");
|
||||
dSpherePosition = GetNode<Node3D>("Spheres/SpherePosition");
|
||||
dSphereTransform = GetNode<Node3D>("Spheres/SphereTransform");
|
||||
dSphereHDTransform = GetNode<Node3D>("Spheres/SphereHDTransform");
|
||||
dAABB = GetNode<Node3D>("Boxes/AABB");
|
||||
dAABB_fixed = GetNode<Node3D>("Boxes/AABB_fixed");
|
||||
dBox1 = GetNode<Node3D>("Boxes/Box1");
|
||||
dBox2 = GetNode<Node3D>("Boxes/Box2");
|
||||
dBox3 = GetNode<Node3D>("Boxes/Box3");
|
||||
dBoxAB = GetNode<Node3D>("Boxes/BoxAB");
|
||||
dBoxABa = GetNode<Node3D>("Boxes/BoxAB/a");
|
||||
dBoxABb = GetNode<Node3D>("Boxes/BoxAB/b");
|
||||
dBoxABup = GetNode<Node3D>("Boxes/BoxAB/o/up");
|
||||
dBoxABEdge = GetNode<Node3D>("Boxes/BoxABEdge");
|
||||
dBoxABEdgea = GetNode<Node3D>("Boxes/BoxABEdge/a");
|
||||
dBoxABEdgeb = GetNode<Node3D>("Boxes/BoxABEdge/b");
|
||||
dBoxABEdgeup = GetNode<Node3D>("Boxes/BoxABEdge/o/up");
|
||||
dLines_1 = GetNode<Node3D>("Lines/1");
|
||||
dLines_2 = GetNode<Node3D>("Lines/2");
|
||||
dLines_3 = GetNode<Node3D>("Lines/3");
|
||||
dLines_4 = GetNode<Node3D>("Lines/4");
|
||||
dLines_5 = GetNode<Node3D>("Lines/5");
|
||||
dLines_6 = GetNode<Node3D>("Lines/6");
|
||||
dLines_7 = GetNode<Node3D>("Lines/7");
|
||||
dLines_8 = GetNode<Node3D>("Lines/8");
|
||||
dLines_Target = GetNode<Node3D>("Lines/Target");
|
||||
dLinePath = GetNode<Node3D>("LinePath");
|
||||
dCylinder1 = GetNode<Node3D>("Cylinders/Cylinder1");
|
||||
dCylinder2 = GetNode<Node3D>("Cylinders/Cylinder2");
|
||||
dCylinder3a = GetNode<Node3D>("Cylinders/Cylinder3/1");
|
||||
dCylinder3b = GetNode<Node3D>("Cylinders/Cylinder3/2");
|
||||
|
||||
pSpheresBox = GetNode<Node3D>("%SpheresBox");
|
||||
pCylindersBox = GetNode<Node3D>("%CylindersBox");
|
||||
pBoxesBox = GetNode<Node3D>("%BoxesBox");
|
||||
pLinesBox = GetNode<Node3D>("%LinesBox");
|
||||
pPathsBox = GetNode<Node3D>("%PathsBox");
|
||||
pMiscBox = GetNode<Node3D>("%MiscBox");
|
||||
|
||||
dPlaneOrigin = GetNode<MeshInstance3D>("PlaneOrigin");
|
||||
pZDepthTestCube = GetNode<MeshInstance3D>("%ZDepthTestCube");
|
||||
|
||||
dOtherWorld = GetNode<MeshInstance3D>("OtherWorld");
|
||||
dOtherWorldViewport = GetNode<SubViewport>("OtherWorld/SubViewport");
|
||||
dOtherWorldBox = GetNode<Node3D>("OtherWorld/SubViewport/SubViewportContainer/SubViewport/OtherWorldBox");
|
||||
|
||||
dCustomCanvas = GetNode<Control>("CustomCanvas");
|
||||
dMisc_Arrow = GetNode<Node3D>("Misc/Arrow");
|
||||
dCamera = GetNode<Camera3D>("Camera");
|
||||
dMisc_Billboard = GetNode<Node3D>("Misc/Billboard");
|
||||
dMisc_Position = GetNode<Node3D>("Misc/Position");
|
||||
dMisc_GizmoTransform = GetNode<Node3D>("Misc/GizmoTransform");
|
||||
dMisc_GizmoNormal = GetNode<Node3D>("Misc/GizmoNormal");
|
||||
dMisc_GizmoOneColor = GetNode<Node3D>("Misc/GizmoOneColor");
|
||||
pLocalTransformRecursiveOrigin = GetNode<Node3D>("%LocalTransformRecursiveOrigin");
|
||||
pRecursiveTransformTest = GetNode<AnimationPlayer>("%RecursiveTransformTest");
|
||||
|
||||
dGrids_Grid = GetNode<Node3D>("Grids/Grid");
|
||||
dGrids_Grid_Subdivision = GetNode<Node3D>("Grids/Grid/Subdivision");
|
||||
dGrids_GridCentered_Subdivision = GetNode<Node3D>("Grids/GridCentered/Subdivision");
|
||||
dGrids_GridCentered = GetNode<Node3D>("Grids/GridCentered");
|
||||
|
||||
dPostProcess = GetNode<MeshInstance3D>("PostProcess");
|
||||
|
||||
dLagTest_RESET = GetNode<AnimationPlayer>("LagTest/RESET");
|
||||
dHitTest_RayEmitter = GetNode<Node3D>("HitTest/RayEmitter");
|
||||
pHitTestSphere = GetNode<Node3D>("%HitTestSphere");
|
||||
}
|
||||
}
|
||||
1
demo/examples_dd3d/DebugDrawDemoSceneCS.cs.uid
Normal file
1
demo/examples_dd3d/DebugDrawDemoSceneCS.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dnf8ejsrnlvxb
|
||||
16
demo/examples_dd3d/DebugDrawDemoSceneCS.tscn
Normal file
16
demo/examples_dd3d/DebugDrawDemoSceneCS.tscn
Normal file
@@ -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"
|
||||
BIN
demo/examples_dd3d/PixelatedElegance.ttf
Normal file
BIN
demo/examples_dd3d/PixelatedElegance.ttf
Normal file
Binary file not shown.
36
demo/examples_dd3d/PixelatedElegance.ttf.import
Normal file
36
demo/examples_dd3d/PixelatedElegance.ttf.import
Normal file
@@ -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={}
|
||||
BIN
demo/examples_dd3d/Roboto-Bold.ttf
Normal file
BIN
demo/examples_dd3d/Roboto-Bold.ttf
Normal file
Binary file not shown.
40
demo/examples_dd3d/Roboto-Bold.ttf.import
Normal file
40
demo/examples_dd3d/Roboto-Bold.ttf.import
Normal file
@@ -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={}
|
||||
17
demo/examples_dd3d/VisualizerAudioBus.tres
Normal file
17
demo/examples_dd3d/VisualizerAudioBus.tres
Normal file
@@ -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
|
||||
11
demo/examples_dd3d/addon_icon.gd
Normal file
11
demo/examples_dd3d/addon_icon.gd
Normal file
@@ -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)
|
||||
1
demo/examples_dd3d/addon_icon.gd.uid
Normal file
1
demo/examples_dd3d/addon_icon.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b2lj85riqyno0
|
||||
37
demo/examples_dd3d/addon_icon.tscn
Normal file
37
demo/examples_dd3d/addon_icon.tscn
Normal file
@@ -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)
|
||||
60
demo/examples_dd3d/demo_camera_movement.gd
Normal file
60
demo/examples_dd3d/demo_camera_movement.gd
Normal file
@@ -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
|
||||
1
demo/examples_dd3d/demo_camera_movement.gd.uid
Normal file
1
demo/examples_dd3d/demo_camera_movement.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b5mdrjubj0lg5
|
||||
175
demo/examples_dd3d/demo_music_visualizer.gd
Normal file
175
demo/examples_dd3d/demo_music_visualizer.gd
Normal file
@@ -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)
|
||||
1
demo/examples_dd3d/demo_music_visualizer.gd.uid
Normal file
1
demo/examples_dd3d/demo_music_visualizer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bebbekatkxaoe
|
||||
103
demo/examples_dd3d/demo_settings_panel.gd
Normal file
103
demo/examples_dd3d/demo_settings_panel.gd
Normal file
@@ -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
|
||||
1
demo/examples_dd3d/demo_settings_panel.gd.uid
Normal file
1
demo/examples_dd3d/demo_settings_panel.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://83dhsep7l725
|
||||
42
demo/examples_dd3d/demo_web_docs_version_select.gd
Normal file
42
demo/examples_dd3d/demo_web_docs_version_select.gd
Normal file
@@ -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)
|
||||
1
demo/examples_dd3d/demo_web_docs_version_select.gd.uid
Normal file
1
demo/examples_dd3d/demo_web_docs_version_select.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://hvx3t70syvkm
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
56
demo/scripts/test.gd
Normal file
56
demo/scripts/test.gd
Normal file
@@ -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)
|
||||
1
demo/scripts/test.gd.uid
Normal file
1
demo/scripts/test.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dysaws7hlg4td
|
||||
932
src/earcut.hpp
Normal file
932
src/earcut.hpp
Normal file
@@ -0,0 +1,932 @@
|
||||
#pragma once
|
||||
|
||||
#include "godot_cpp/variant/vector2.hpp"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace mapbox {
|
||||
|
||||
namespace util {
|
||||
|
||||
template <std::size_t I, typename T> struct nth {
|
||||
inline static typename std::tuple_element<I, T>::type get(const T &t) {
|
||||
return std::get<I>(t);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename N = uint32_t> class Earcut {
|
||||
public:
|
||||
std::vector<N> indices;
|
||||
std::size_t vertices = 0;
|
||||
|
||||
template <typename Polygon> 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 <typename Ring>
|
||||
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 <typename Polygon>
|
||||
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 <typename Point>
|
||||
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 <typename T, typename Alloc = std::allocator<T>> class ObjectPool {
|
||||
public:
|
||||
ObjectPool() { allocateNewBlock(256); }
|
||||
ObjectPool(std::size_t blockSize_) : baseBlockSize(blockSize_) {
|
||||
allocateNewBlock(std::max<std::size_t>(blockSize_, 256));
|
||||
}
|
||||
~ObjectPool() { clear(); }
|
||||
template <typename... Args> 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>(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> 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<std::unique_ptr<T[], AllocDeleter>> memoryBlocks;
|
||||
std::vector<std::size_t> 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<T[], AllocDeleter>(
|
||||
rawMemory, AllocDeleter{alloc, capacity});
|
||||
memoryBlocks.push_back(std::move(newBlock));
|
||||
blockCapacities.push_back(capacity);
|
||||
currentBlockIndex = memoryBlocks.size() - 1;
|
||||
currentIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ObjectPool<Node>> nodes;
|
||||
std::vector<Node *> holeQueue;
|
||||
};
|
||||
|
||||
template <typename N>
|
||||
template <typename Polygon>
|
||||
void Earcut<N>::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<int>(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<ObjectPool<Node>>(
|
||||
std::max<std::size_t>(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<double>(minX, x);
|
||||
minY = std::min<double>(minY, y);
|
||||
maxX = std::max<double>(maxX, x);
|
||||
maxY = std::max<double>(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<double>(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 <typename N>
|
||||
template <typename Ring>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 <typename N> void Earcut<N>::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 <typename N> bool Earcut<N>::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 <typename N> bool Earcut<N>::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<double>(tri.ax, std::min<double>(tri.bx, tri.cx));
|
||||
const double minTY =
|
||||
std::min<double>(tri.ay, std::min<double>(tri.by, tri.cy));
|
||||
const double maxTX =
|
||||
std::max<double>(tri.ax, std::max<double>(tri.bx, tri.cx));
|
||||
const double maxTY =
|
||||
std::max<double>(tri.ay, std::max<double>(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 N>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 <typename N> void Earcut<N>::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 <typename N>
|
||||
template <typename Polygon>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node *Earcut<N>::findHoleBridge(Node *hole,
|
||||
Node *outerNode) {
|
||||
Node *p = outerNode;
|
||||
double hx = hole->x;
|
||||
double hy = hole->y;
|
||||
double qx = -std::numeric_limits<double>::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<double>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N> void Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 <typename N>
|
||||
int32_t Earcut<N>::zOrder(const double x_, const double y_) {
|
||||
// coords are transformed into non-negative 15-bit integer range
|
||||
int32_t x = static_cast<int32_t>((x_ - minX) * inv_size);
|
||||
int32_t y = static_cast<int32_t>((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 N>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N> bool Earcut<N>::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 <typename N>
|
||||
double Earcut<N>::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 <typename N> bool Earcut<N>::equals(const Node *p1, const Node *p2) {
|
||||
return p1->x == p2->x && p1->y == p2->y;
|
||||
}
|
||||
|
||||
// check if two segments intersect
|
||||
template <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::onSegment(const Node *p, const Node *q, const Node *r) {
|
||||
return q->x <= std::max<double>(p->x, r->x) &&
|
||||
q->x >= std::min<double>(p->x, r->x) &&
|
||||
q->y <= std::max<double>(p->y, r->y) &&
|
||||
q->y >= std::min<double>(p->y, r->y);
|
||||
}
|
||||
|
||||
template <typename N> int Earcut<N>::sign(double val) {
|
||||
return (0.0 < val) - (val < 0.0);
|
||||
}
|
||||
|
||||
// check if a polygon diagonal intersects any polygon segments
|
||||
template <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node *Earcut<N>::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 <typename N>
|
||||
template <typename Point>
|
||||
typename Earcut<N>::Node *Earcut<N>::insertNode(std::size_t i, const Point &pt,
|
||||
Node *last) {
|
||||
Node *p = nodes->construct(static_cast<N>(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 <typename N> void Earcut<N>::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 <typename N = uint32_t, typename Polygon>
|
||||
std::vector<N> earcut(const Polygon &poly) {
|
||||
mapbox::detail::Earcut<N> earcut;
|
||||
earcut(poly);
|
||||
return std::move(earcut.indices);
|
||||
}
|
||||
} // namespace mapbox
|
||||
@@ -1,24 +0,0 @@
|
||||
#include "gdexample.h"
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/classes/sprite2d.hpp>
|
||||
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "register_types.h"
|
||||
|
||||
#include "gdexample.h"
|
||||
#include "godot_cpp/core/class_db.hpp"
|
||||
#include "triangulization.hpp"
|
||||
|
||||
#include <gdextension_interface.h>
|
||||
#include <godot_cpp/core/defs.hpp>
|
||||
@@ -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) {
|
||||
|
||||
Binary file not shown.
61
src/triangulization.cpp
Normal file
61
src/triangulization.cpp
Normal file
@@ -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 <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
Triangulization::Triangulization() {}
|
||||
|
||||
void Triangulization::_bind_methods() {
|
||||
ClassDB::bind_method(
|
||||
godot::D_METHOD("triangulate_with_holes", "outer", "inner"),
|
||||
&Triangulization::triangulate_with_holes);
|
||||
}
|
||||
|
||||
std::vector<std::array<double, 2>>
|
||||
_convert_to_std_vec(const godot::PackedVector2Array &packed_array) {
|
||||
std::vector<std::array<double, 2>> 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<std::vector<std::array<double, 2>>> 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<uint32_t> indices = mapbox::earcut<uint32_t>(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;
|
||||
};
|
||||
21
src/triangulization.hpp
Normal file
21
src/triangulization.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "godot_cpp/classes/wrapped.hpp"
|
||||
#include "godot_cpp/variant/packed_int32_array.hpp"
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/classes/polygon2d.hpp>
|
||||
|
||||
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);
|
||||
};
|
||||
BIN
src/triangulization.os
Normal file
BIN
src/triangulization.os
Normal file
Binary file not shown.
0
src/util.hpp
Normal file
0
src/util.hpp
Normal file
Reference in New Issue
Block a user