mirror of
https://gitlab.com/open-fpsz/open-fpsz.git
synced 2026-01-19 19:44:46 +00:00
Merge branch 'develop' into 'main'
🐛 Fixed blank collision mask in PlayerWalkSensor See merge request open-fpsz/open-fpsz!100
This commit is contained in:
commit
d5c4e84725
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -19,4 +19,4 @@ addons/godot-jolt/windows/~godot-jolt_windows-x64_editor.dll
|
|||
addons/terrain_3d/bin/~libterrain.windows.debug.x86_64.dll
|
||||
|
||||
build
|
||||
|
||||
dist
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@ Download `Godot Engine` at https://godotengine.org/download
|
|||
|
||||
## Getting started
|
||||
|
||||
1. Clone the repository
|
||||
2. Open Godot Engine
|
||||
3. Navigate to the repository and import the `project.godot`
|
||||
4. Start coding or run the project!
|
||||
1. Clone the repository and open `Godot Engine`
|
||||
2. Navigate to the repository and import the `project.godot`
|
||||
3. Start coding or run the project!
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
@ -21,7 +20,7 @@ We welcome contributions from the community, feel free to submit pull requests,
|
|||
|
||||
## Community
|
||||
|
||||
Connect with fellow contributors, ask questions and stay updated on the latest developments by joining our [Discord](https://discord.gg/tdmV3MxCn5).
|
||||
Connect, ask questions and stay updated on the latest developments by joining our [Discord](https://discord.gg/tdmV3MxCn5).
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,36 +1,37 @@
|
|||
[configuration]
|
||||
|
||||
entry_symbol = "godot_jolt_main"
|
||||
compatibility_minimum = "4.2"
|
||||
compatibility_minimum = "4.3"
|
||||
compatibility_maximum = "4.3"
|
||||
|
||||
[libraries]
|
||||
|
||||
windows.release.x86_64 = "windows/godot-jolt_windows-x64.dll"
|
||||
windows.debug.x86_64 = "windows/godot-jolt_windows-x64_editor.dll"
|
||||
windows.release.single.x86_64 = "windows/godot-jolt_windows-x64.dll"
|
||||
windows.debug.single.x86_64 = "windows/godot-jolt_windows-x64_editor.dll"
|
||||
|
||||
windows.release.x86_32 = "windows/godot-jolt_windows-x86.dll"
|
||||
windows.debug.x86_32 = "windows/godot-jolt_windows-x86_editor.dll"
|
||||
windows.release.single.x86_32 = "windows/godot-jolt_windows-x86.dll"
|
||||
windows.debug.single.x86_32 = "windows/godot-jolt_windows-x86_editor.dll"
|
||||
|
||||
linux.release.x86_64 = "linux/godot-jolt_linux-x64.so"
|
||||
linux.debug.x86_64 = "linux/godot-jolt_linux-x64_editor.so"
|
||||
linux.release.single.x86_64 = "linux/godot-jolt_linux-x64.so"
|
||||
linux.debug.single.x86_64 = "linux/godot-jolt_linux-x64_editor.so"
|
||||
|
||||
linux.release.x86_32 = "linux/godot-jolt_linux-x86.so"
|
||||
linux.debug.x86_32 = "linux/godot-jolt_linux-x86_editor.so"
|
||||
linux.release.single.x86_32 = "linux/godot-jolt_linux-x86.so"
|
||||
linux.debug.single.x86_32 = "linux/godot-jolt_linux-x86_editor.so"
|
||||
|
||||
macos.release = "macos/godot-jolt_macos.framework"
|
||||
macos.debug = "macos/godot-jolt_macos_editor.framework"
|
||||
macos.release.single = "macos/godot-jolt_macos.framework"
|
||||
macos.debug.single = "macos/godot-jolt_macos_editor.framework"
|
||||
|
||||
ios.release = "ios/godot-jolt_ios.framework"
|
||||
ios.debug = "ios/godot-jolt_ios_editor.framework"
|
||||
ios.release.single = "ios/godot-jolt_ios.framework"
|
||||
ios.debug.single = "ios/godot-jolt_ios_editor.framework"
|
||||
|
||||
android.release.arm64 = "android/libgodot-jolt_android-arm64.so"
|
||||
android.debug.arm64 = "android/libgodot-jolt_android-arm64_editor.so"
|
||||
android.release.single.arm64 = "android/libgodot-jolt_android-arm64.so"
|
||||
android.debug.single.arm64 = "android/libgodot-jolt_android-arm64_editor.so"
|
||||
|
||||
android.release.arm32 = "android/libgodot-jolt_android-arm32.so"
|
||||
android.debug.arm32 = "android/libgodot-jolt_android-arm32_editor.so"
|
||||
android.release.single.arm32 = "android/libgodot-jolt_android-arm32.so"
|
||||
android.debug.single.arm32 = "android/libgodot-jolt_android-arm32_editor.so"
|
||||
|
||||
android.release.x86_64 = "android/libgodot-jolt_android-x64.so"
|
||||
android.debug.x86_64 = "android/libgodot-jolt_android-x64_editor.so"
|
||||
android.release.single.x86_64 = "android/libgodot-jolt_android-x64.so"
|
||||
android.debug.single.x86_64 = "android/libgodot-jolt_android-x64_editor.so"
|
||||
|
||||
android.release.x86_32 = "android/libgodot-jolt_android-x86.so"
|
||||
android.debug.x86_32 = "android/libgodot-jolt_android-x86_editor.so"
|
||||
android.release.single.x86_32 = "android/libgodot-jolt_android-x86.so"
|
||||
android.debug.single.x86_32 = "android/libgodot-jolt_android-x86_editor.so"
|
||||
|
|
|
|||
|
|
@ -17,16 +17,35 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
|
||||
<!--
|
||||
HACK(mihe): This is to work around a bug in Godot 4.3-beta1, where it treats Framework
|
||||
bundles the same as XCFramework bundles, and expects there to be an `AvailableLibraries`
|
||||
entry, which is really only a thing in XCFramework bundles. Note that we also lie about the
|
||||
binary path having a `.dylib` extension in order for Godot to correctly identify this as a
|
||||
dynamically linked bundle.
|
||||
-->
|
||||
<key>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>godot-jolt_ios.dylib</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -17,16 +17,35 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
|
||||
<!--
|
||||
HACK(mihe): This is to work around a bug in Godot 4.3-beta1, where it treats Framework
|
||||
bundles the same as XCFramework bundles, and expects there to be an `AvailableLibraries`
|
||||
entry, which is really only a thing in XCFramework bundles. Note that we also lie about the
|
||||
binary path having a `.dylib` extension in order for Godot to correctly identify this as a
|
||||
dynamically linked bundle.
|
||||
-->
|
||||
<key>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>godot-jolt_ios_editor.dylib</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -17,11 +17,15 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
HxpaqrKUN+D+lkF90Phqlb+CZKo=
|
||||
et0C7sxAlu4eIDcq2ihFQ2BhDSk=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
ZTtvl19PLRzCoTuDRMZ2FnJXIXsLYRhaBFxjroNsLDw=
|
||||
ZnG0hD4DciikOVWrf1Ai1Qedz9hESuIFvUujZAebHRY=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -17,11 +17,15 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.12.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
CpU1kp9qZhdCVqKdSia+981vm40=
|
||||
oIAzxlQz4Hun6JnLVOu9jafYxGE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
4CTRXI34pj3JIJzQDUUZTlAagKWQeohPQN20M0VCK4o=
|
||||
FA6I/u5+Ww0DzXAvawYXs792eum+8Bim8uHBbg98jqY=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60af
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e77
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a8
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a47881
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd
|
|||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
Copyright (c) 2024 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
<img src="doc/docs/images/terrain3d.png">
|
||||

|
||||
|
||||
# Terrain3D
|
||||
A high performance, editable terrain system for Godot 4.
|
||||
|
||||
## Features
|
||||
* Written in C++ as a GDExtension plugin, which works with official engine builds
|
||||
* Written in C++ as a GDExtension addon, which works with official engine builds
|
||||
* Can be accessed by GDScript, C#, and any language Godot supports
|
||||
* Geometric Clipmap Mesh Terrain, as used in The Witcher 3. See [System Architecture](https://terrain3d.readthedocs.io/en/stable/docs/system_architecture.html)
|
||||
* Up to 16k x 16k in 1k regions (imagine multiple islands without paying for 16k^2 vram)
|
||||
* Up to 10 Levels of Detail (LODs)
|
||||
* Up to 32 texture sets using albedo, normal, roughness, height
|
||||
* Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in variable sized regions
|
||||
* Up to 32 textures
|
||||
* Up to 10 levels of detail
|
||||
* Foliage instancing
|
||||
* Sculpting, holes, texture painting, texture detiling, painting colors and wetness
|
||||
* Supports importing heightmaps from [HTerrain](https://github.com/Zylann/godot_heightmap_plugin/), WorldMachine, Unity, Unreal and any tool that can export a heightmap (raw/r16/exr/+). See [importing data](https://terrain3d.readthedocs.io/en/stable/docs/import_export.html)
|
||||
* Imports heightmaps from [HTerrain](https://github.com/Zylann/godot_heightmap_plugin/), WorldMachine, Unity, Unreal and any tool that can export a heightmap (raw/r16/exr/+). See [importing data](https://terrain3d.readthedocs.io/en/stable/docs/import_export.html)
|
||||
|
||||
See [Project Status](https://terrain3d.readthedocs.io/en/stable/docs/project_status.html) for details.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Read through our [documentation](https://terrain3d.readthedocs.io/en/stable/index.html), starting with [Installation](https://terrain3d.readthedocs.io/en/stable/docs/installation.html).
|
||||
1. Read the [Installation & Upgrades](https://terrain3d.readthedocs.io/en/stable/docs/installation.html) instructions.
|
||||
|
||||
2. For support, read [Getting Help](https://terrain3d.readthedocs.io/en/stable/docs/getting_help.html) or join our [Discord server](https://tokisan.com/discord).
|
||||
|
||||
|
|
@ -51,5 +52,5 @@ Please see [CONTRIBUTING.md](https://github.com/TokisanGames/Terrain3D/blob/main
|
|||
|
||||
## License
|
||||
|
||||
This plugin has been released under the [MIT License](https://github.com/TokisanGames/Terrain3D/blob/main/LICENSE.txt).
|
||||
This addon has been released under the [MIT License](https://github.com/TokisanGames/Terrain3D/blob/main/LICENSE.txt).
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
413
addons/terrain_3d/editor.gd
Normal file
413
addons/terrain_3d/editor.gd
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
#class_name Terrain3DEditorPlugin Cannot be named until Godot #75388
|
||||
|
||||
|
||||
# Includes
|
||||
const UI: Script = preload("res://addons/terrain_3d/src/ui.gd")
|
||||
const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd")
|
||||
const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn"
|
||||
|
||||
var modifier_ctrl: bool
|
||||
var modifier_alt: bool
|
||||
var modifier_shift: bool
|
||||
var _last_modifiers: int = 0
|
||||
var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating
|
||||
|
||||
var terrain: Terrain3D
|
||||
var _last_terrain: Terrain3D
|
||||
var nav_region: NavigationRegion3D
|
||||
|
||||
var editor: Terrain3DEditor
|
||||
var editor_settings: EditorSettings
|
||||
var ui: Node # Terrain3DUI see Godot #75388
|
||||
var asset_dock: PanelContainer
|
||||
var region_gizmo: RegionGizmo
|
||||
var current_region_position: Vector2
|
||||
var mouse_global_position: Vector3 = Vector3.ZERO
|
||||
var godot_editor_window: Window # The Godot Editor window
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control
|
||||
godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent()
|
||||
godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
editor = Terrain3DEditor.new()
|
||||
setup_editor_settings()
|
||||
ui = UI.new()
|
||||
ui.plugin = self
|
||||
add_child(ui)
|
||||
|
||||
region_gizmo = RegionGizmo.new()
|
||||
|
||||
scene_changed.connect(_on_scene_changed)
|
||||
|
||||
asset_dock = load(ASSET_DOCK).instantiate()
|
||||
asset_dock.initialize(self)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
asset_dock.remove_dock(true)
|
||||
asset_dock.queue_free()
|
||||
ui.queue_free()
|
||||
editor.free()
|
||||
|
||||
scene_changed.disconnect(_on_scene_changed)
|
||||
godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||
|
||||
|
||||
func _on_godot_focus_entered() -> void:
|
||||
_read_input()
|
||||
ui.update_decal()
|
||||
|
||||
|
||||
## EditorPlugin selection function call chain isn't consistent. Here's the map of calls:
|
||||
## Assume we handle Terrain3D and NavigationRegion3D
|
||||
# Click Terrain3D: _handles(Terrain3D), _make_visible(true), _edit(Terrain3D)
|
||||
# Deselect: _make_visible(false), _edit(null)
|
||||
# Click other node: _handles(OtherNode)
|
||||
# Click NavRegion3D: _handles(NavReg3D), _make_visible(true), _edit(NavReg3D)
|
||||
# Click NavRegion3D, Terrain3D: _handles(Terrain3D), _edit(Terrain3D)
|
||||
# Click Terrain3D, NavRegion3D: _handles(NavReg3D), _edit(NavReg3D)
|
||||
func _handles(p_object: Object) -> bool:
|
||||
if p_object is Terrain3D:
|
||||
return true
|
||||
elif p_object is NavigationRegion3D and is_instance_valid(_last_terrain):
|
||||
return true
|
||||
|
||||
# Terrain3DObjects requires access to EditorUndoRedoManager. The only way to make sure it
|
||||
# always has it, is to pass it in here. _edit is NOT called if the node is cut and pasted.
|
||||
elif p_object is Terrain3DObjects:
|
||||
p_object.editor_setup(self)
|
||||
elif p_object is Node3D and p_object.get_parent() is Terrain3DObjects:
|
||||
p_object.get_parent().editor_setup(self)
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _make_visible(p_visible: bool, p_redraw: bool = false) -> void:
|
||||
if p_visible and is_selected():
|
||||
ui.set_visible(true)
|
||||
asset_dock.update_dock()
|
||||
else:
|
||||
ui.set_visible(false)
|
||||
|
||||
|
||||
func _edit(p_object: Object) -> void:
|
||||
if !p_object:
|
||||
_clear()
|
||||
|
||||
if p_object is Terrain3D:
|
||||
if p_object == terrain:
|
||||
return
|
||||
terrain = p_object
|
||||
_last_terrain = terrain
|
||||
terrain.set_plugin(self)
|
||||
terrain.set_editor(editor)
|
||||
editor.set_terrain(terrain)
|
||||
region_gizmo.set_node_3d(terrain)
|
||||
terrain.add_gizmo(region_gizmo)
|
||||
ui.set_visible(true)
|
||||
terrain.set_meta("_edit_lock_", true)
|
||||
|
||||
# Deprecated 0.9.3 - Remove 1.0
|
||||
if terrain.storage:
|
||||
ui.terrain_menu.directory_setup.directory_setup_popup()
|
||||
|
||||
# Get alerted when a new asset list is loaded
|
||||
if not terrain.assets_changed.is_connected(asset_dock.update_assets):
|
||||
terrain.assets_changed.connect(asset_dock.update_assets)
|
||||
asset_dock.update_assets()
|
||||
# Get alerted when the region map changes
|
||||
if not terrain.data.region_map_changed.is_connected(update_region_grid):
|
||||
terrain.data.region_map_changed.connect(update_region_grid)
|
||||
update_region_grid()
|
||||
else:
|
||||
_clear()
|
||||
|
||||
if is_terrain_valid(_last_terrain):
|
||||
if p_object is NavigationRegion3D:
|
||||
ui.set_visible(true, true)
|
||||
nav_region = p_object
|
||||
else:
|
||||
nav_region = null
|
||||
|
||||
|
||||
func _clear() -> void:
|
||||
if is_terrain_valid():
|
||||
if terrain.data.region_map_changed.is_connected(update_region_grid):
|
||||
terrain.data.region_map_changed.disconnect(update_region_grid)
|
||||
|
||||
terrain.clear_gizmos()
|
||||
terrain = null
|
||||
editor.set_terrain(null)
|
||||
|
||||
ui.clear_picking()
|
||||
|
||||
region_gizmo.clear()
|
||||
|
||||
|
||||
func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> int:
|
||||
if not is_terrain_valid():
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
_read_input(p_event)
|
||||
|
||||
## Handle mouse movement
|
||||
if p_event is InputEventMouseMotion:
|
||||
## Setup active camera & viewport
|
||||
|
||||
# Snap terrain to current camera
|
||||
terrain.set_camera(p_viewport_camera)
|
||||
|
||||
# Detect if viewport is set to half_resolution
|
||||
# Structure is: Node3DEditorViewportContainer/Node3DEditorViewport(4)/SubViewportContainer/SubViewport/Camera3D
|
||||
var editor_vpc: SubViewportContainer = p_viewport_camera.get_parent().get_parent()
|
||||
var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true
|
||||
|
||||
## Get mouse location on terrain
|
||||
|
||||
# Project 2D mouse position to 3D position and direction
|
||||
var mouse_pos: Vector2 = p_event.position if full_resolution else p_event.position/2
|
||||
var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos)
|
||||
var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos)
|
||||
|
||||
# If region tool, grab mouse position without considering height
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir)
|
||||
mouse_global_position = (camera_pos + t * camera_dir)
|
||||
else:
|
||||
# Else look for intersection with terrain
|
||||
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir)
|
||||
if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
mouse_global_position = intersection_point
|
||||
|
||||
ui.update_decal()
|
||||
|
||||
if _input_mode != -1: # Not cam rotation
|
||||
## Update region highlight
|
||||
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
|
||||
/ (terrain.get_region_size() * terrain.get_vertex_spacing()) ).floor()
|
||||
if current_region_position != region_position:
|
||||
current_region_position = region_position
|
||||
update_region_grid()
|
||||
|
||||
if _input_mode > 0 and editor.is_operating():
|
||||
# Inject pressure - Relies on C++ set_brush_data() using same dictionary instance
|
||||
ui.brush_data["mouse_pressure"] = p_event.pressure
|
||||
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
ui.update_decal()
|
||||
|
||||
if p_event is InputEventMouseButton and _input_mode > 0:
|
||||
if p_event.is_pressed():
|
||||
# If picking
|
||||
if ui.is_picking():
|
||||
ui.pick(mouse_global_position)
|
||||
if not ui.operation_builder or not ui.operation_builder.is_ready():
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If adjusting regions
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
# Skip regions that already exist or don't
|
||||
var has_region: bool = terrain.data.has_regionp(mouse_global_position)
|
||||
var op: int = editor.get_operation()
|
||||
if ( has_region and op == Terrain3DEditor.ADD) or \
|
||||
( not has_region and op == Terrain3DEditor.SUBTRACT ):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If an automatic operation is ready to go (e.g. gradient)
|
||||
if ui.operation_builder and ui.operation_builder.is_ready():
|
||||
ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# Mouse clicked, start editing
|
||||
editor.start_operation(mouse_global_position)
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# _input_apply released, save undo data
|
||||
elif editor.is_operating():
|
||||
editor.stop_operation()
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
|
||||
func _read_input(p_event: InputEvent = null) -> void:
|
||||
## Determine if user is moving camera or applying
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) or \
|
||||
p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
_input_mode = 1
|
||||
else:
|
||||
_input_mode = 0
|
||||
|
||||
match get_setting("editors/3d/navigation/navigation_scheme", 0):
|
||||
2, 1: # Modo, Maya
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
|
||||
( Input.is_key_pressed(KEY_ALT) and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) ):
|
||||
_input_mode = -1
|
||||
if p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
|
||||
( Input.is_key_pressed(KEY_ALT) and p_event.get_button_index() == MOUSE_BUTTON_LEFT )):
|
||||
ui.last_rmb_time = Time.get_ticks_msec()
|
||||
0, _: # Godot
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
|
||||
Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE):
|
||||
_input_mode = -1
|
||||
if p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
|
||||
p_event.get_button_index() == MOUSE_BUTTON_MIDDLE ):
|
||||
ui.last_rmb_time = Time.get_ticks_msec()
|
||||
if _input_mode < 0:
|
||||
return
|
||||
|
||||
## Determine modifiers pressed
|
||||
modifier_shift = Input.is_key_pressed(KEY_SHIFT)
|
||||
modifier_ctrl = Input.is_key_pressed(KEY_CTRL)
|
||||
# Keybind enum: Alt,Space,Meta,Capslock
|
||||
var alt_key: int
|
||||
match get_setting("terrain3d/config/alt_key_bind", 0):
|
||||
3: alt_key = KEY_CAPSLOCK
|
||||
2: alt_key = KEY_META
|
||||
1: alt_key = KEY_SPACE
|
||||
0, _: alt_key = KEY_ALT
|
||||
modifier_alt = Input.is_key_pressed(alt_key)
|
||||
|
||||
# Return if modifiers haven't changed AND brush_data has them;
|
||||
# modifiers disappear from brush_data when clicking asset_dock (Why?)
|
||||
var current_mods: int = int(modifier_shift) | int(modifier_ctrl) << 1 | int(modifier_alt) << 2
|
||||
if _last_modifiers == current_mods and ui.brush_data.has("modifier_shift"):
|
||||
return
|
||||
|
||||
_last_modifiers = current_mods
|
||||
ui.brush_data["modifier_shift"] = modifier_shift
|
||||
ui.brush_data["modifier_ctrl"] = modifier_ctrl
|
||||
ui.brush_data["modifier_alt"] = modifier_alt
|
||||
ui.update_modifiers()
|
||||
|
||||
|
||||
func update_region_grid() -> void:
|
||||
if not region_gizmo:
|
||||
return
|
||||
region_gizmo.set_hidden(not ui.visible)
|
||||
|
||||
if is_terrain_valid():
|
||||
region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
|
||||
region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
|
||||
region_gizmo.region_position = current_region_position
|
||||
region_gizmo.region_size = terrain.get_region_size() * terrain.get_vertex_spacing()
|
||||
region_gizmo.grid = terrain.get_data().get_region_locations()
|
||||
|
||||
terrain.update_gizmos()
|
||||
return
|
||||
|
||||
region_gizmo.show_rect = false
|
||||
region_gizmo.region_size = 1024
|
||||
region_gizmo.grid = [Vector2i.ZERO]
|
||||
|
||||
|
||||
func _on_scene_changed(scene_root: Node) -> void:
|
||||
if not scene_root:
|
||||
return
|
||||
|
||||
for node in scene_root.find_children("", "Terrain3DObjects"):
|
||||
node.editor_setup(self)
|
||||
|
||||
asset_dock.update_assets()
|
||||
await get_tree().create_timer(2).timeout
|
||||
asset_dock.update_thumbnails()
|
||||
|
||||
|
||||
func is_terrain_valid(p_terrain: Terrain3D = null) -> bool:
|
||||
var t: Terrain3D
|
||||
if p_terrain:
|
||||
t = p_terrain
|
||||
else:
|
||||
t = terrain
|
||||
if is_instance_valid(t) and t.is_inside_tree() and t.data:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func is_selected() -> bool:
|
||||
var selected: Array[Node] = EditorInterface.get_selection().get_selected_nodes()
|
||||
for node in selected:
|
||||
if ( is_instance_valid(_last_terrain) and node.get_instance_id() == _last_terrain.get_instance_id() ) or \
|
||||
node is Terrain3D:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func select_terrain() -> void:
|
||||
if is_instance_valid(_last_terrain) and is_terrain_valid(_last_terrain) and not is_selected():
|
||||
var es: EditorSelection = EditorInterface.get_selection()
|
||||
es.clear()
|
||||
es.add_node(_last_terrain)
|
||||
|
||||
|
||||
## Editor Settings
|
||||
|
||||
|
||||
func setup_editor_settings() -> void:
|
||||
editor_settings = EditorInterface.get_editor_settings()
|
||||
if not editor_settings.has_setting("terrain3d/config/alt_key_bind"):
|
||||
editor_settings.set("terrain3d/config/alt_key_bind", 0)
|
||||
var property_info = {
|
||||
"name": "terrain3d/config/alt_key_bind",
|
||||
"type": TYPE_INT,
|
||||
"hint": PROPERTY_HINT_ENUM,
|
||||
"hint_string": "Alt,Space,Meta,Capslock"
|
||||
}
|
||||
editor_settings.add_property_info(property_info)
|
||||
|
||||
_cleanup_old_settings()
|
||||
|
||||
|
||||
# Remove or rename old settings
|
||||
func _cleanup_old_settings() -> void:
|
||||
# Rename deprecated settings - Remove in 1.0
|
||||
var value: Variant
|
||||
var rename_arr := [ "terrain3d/config/dock_slot", "terrain3d/config/dock_tile_size",
|
||||
"terrain3d/config/dock_floating", "terrain3d/config/dock_always_on_top",
|
||||
"terrain3d/config/dock_window_size", "terrain3d/config/dock_window_position", ]
|
||||
for es: String in rename_arr:
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
editor_settings.erase(es)
|
||||
editor_settings.set_setting(es.replace("/config/dock_", "/dock/"), value)
|
||||
|
||||
# Special handling
|
||||
var es: String = "terrain3d/tool_settings/slope"
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
if typeof(value) == TYPE_FLOAT:
|
||||
editor_settings.erase(es)
|
||||
|
||||
|
||||
func set_setting(p_str: String, p_value: Variant) -> void:
|
||||
editor_settings.set_setting(p_str, p_value)
|
||||
|
||||
|
||||
func get_setting(p_str: String, p_default: Variant) -> Variant:
|
||||
if editor_settings.has_setting(p_str):
|
||||
return editor_settings.get_setting(p_str)
|
||||
else:
|
||||
return p_default
|
||||
|
||||
|
||||
func has_setting(p_str: String) -> bool:
|
||||
return editor_settings.has_setting(p_str)
|
||||
|
||||
|
||||
func erase_setting(p_str: String) -> void:
|
||||
editor_settings.erase(p_str)
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://bhvrrmb8bk1bt"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/terrain_3d/editor/components/bake_lod_dialog.gd" id="1_57670"]
|
||||
|
||||
[node name="bake_lod_dialog" type="ConfirmationDialog"]
|
||||
title = "Bake Terrain3D Mesh"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(400, 115)
|
||||
visible = true
|
||||
script = ExtResource("1_57670")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "LOD:"
|
||||
|
||||
[node name="LodBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
max_value = 8.0
|
||||
value = 4.0
|
||||
|
||||
[node name="DescriptionLabel" type="Label" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
autowrap_mode = 2
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
extends Object
|
||||
|
||||
const WINDOW_SCENE: String = "res://addons/terrain_3d/editor/components/channel_packer.tscn"
|
||||
const TEMPLATE_PATH: String = "res://addons/terrain_3d/editor/components/channel_packer_import_template.txt"
|
||||
|
||||
enum {
|
||||
IMAGE_ALBEDO,
|
||||
IMAGE_HEIGHT,
|
||||
IMAGE_NORMAL,
|
||||
IMAGE_ROUGHNESS,
|
||||
}
|
||||
|
||||
var plugin: EditorPlugin
|
||||
var editor_interface: EditorInterface
|
||||
var dialog: AcceptDialog
|
||||
var save_file_dialog: FileDialog
|
||||
var open_file_dialog: FileDialog
|
||||
var invert_green_checkbox: CheckBox
|
||||
var last_opened_directory: String
|
||||
var last_saved_directory: String
|
||||
var packing_albedo: bool = false
|
||||
var queue_pack_normal_roughness: bool = false
|
||||
var images: Array[Image] = [null, null, null, null]
|
||||
var status_label: Label
|
||||
var no_op: Callable = func(): pass
|
||||
var last_file_selected_fn: Callable = no_op
|
||||
|
||||
|
||||
func pack_textures_popup() -> void:
|
||||
if dialog != null:
|
||||
print("Terrain3DChannelPacker: Cannot open pack tool, dialog already open.")
|
||||
return
|
||||
|
||||
dialog = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
||||
dialog.confirmed.connect(_on_close_requested)
|
||||
dialog.canceled.connect(_on_close_requested)
|
||||
status_label = dialog.find_child("StatusLabel")
|
||||
invert_green_checkbox = dialog.find_child("InvertGreenChannelCheckBox")
|
||||
|
||||
editor_interface = plugin.get_editor_interface()
|
||||
_init_file_dialogs()
|
||||
editor_interface.popup_dialog_centered(dialog)
|
||||
|
||||
_init_texture_picker(dialog.find_child("AlbedoVBox"), IMAGE_ALBEDO)
|
||||
_init_texture_picker(dialog.find_child("HeightVBox"), IMAGE_HEIGHT)
|
||||
_init_texture_picker(dialog.find_child("NormalVBox"), IMAGE_NORMAL)
|
||||
_init_texture_picker(dialog.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
|
||||
var pack_button_path: String = "Panel/MarginContainer/VBoxContainer/PackButton"
|
||||
(dialog.get_node(pack_button_path) as Button).pressed.connect(_on_pack_button_pressed)
|
||||
|
||||
|
||||
func _on_close_requested() -> void:
|
||||
last_file_selected_fn = no_op
|
||||
images = [null, null, null, null]
|
||||
dialog.queue_free()
|
||||
dialog = null
|
||||
|
||||
|
||||
func _init_file_dialogs() -> void:
|
||||
save_file_dialog = FileDialog.new()
|
||||
save_file_dialog.set_filters(PackedStringArray(["*.png"]))
|
||||
save_file_dialog.set_file_mode(FileDialog.FILE_MODE_SAVE_FILE)
|
||||
save_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
save_file_dialog.file_selected.connect(_on_save_file_selected)
|
||||
|
||||
open_file_dialog = FileDialog.new()
|
||||
open_file_dialog.set_filters(PackedStringArray(["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", ".ktx"]))
|
||||
open_file_dialog.set_file_mode(FileDialog.FILE_MODE_OPEN_FILE)
|
||||
open_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
|
||||
dialog.add_child(save_file_dialog)
|
||||
dialog.add_child(open_file_dialog)
|
||||
|
||||
|
||||
func _init_texture_picker(p_parent: Node, p_image_index: int) -> void:
|
||||
var line_edit: LineEdit = p_parent.find_child("LineEdit")
|
||||
var file_pick_button: Button = p_parent.find_child("PickButton")
|
||||
var clear_button: Button = p_parent.find_child("ClearButton")
|
||||
var texture_rect: TextureRect = p_parent.find_child("TextureRect")
|
||||
var texture_button: Button = p_parent.find_child("TextureButton")
|
||||
|
||||
var open_fn: Callable = func() -> void:
|
||||
open_file_dialog.current_path = last_opened_directory
|
||||
if last_file_selected_fn != no_op:
|
||||
open_file_dialog.file_selected.disconnect(last_file_selected_fn)
|
||||
last_file_selected_fn = func(path: String) -> void:
|
||||
line_edit.text = path
|
||||
line_edit.caret_column = path.length()
|
||||
last_opened_directory = path.get_base_dir() + "/"
|
||||
var image: Image = Image.new()
|
||||
var code: int = image.load(path)
|
||||
if code != OK:
|
||||
_show_error("Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
_show_success("Loaded texture '" + path + "'")
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
open_file_dialog.file_selected.connect(last_file_selected_fn)
|
||||
open_file_dialog.popup_centered_ratio()
|
||||
|
||||
var clear_fn: Callable = func() -> void:
|
||||
line_edit.text = ""
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
|
||||
# allow user to edit textbox and press enter because Godot's file picker doesn't work 100% of the time
|
||||
var line_edit_submit_fn: Callable = func(path: String) -> void:
|
||||
var image: Image = Image.new()
|
||||
var code: int = image.load(path)
|
||||
if code != OK:
|
||||
_show_error("Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
|
||||
line_edit.text_submitted.connect(line_edit_submit_fn)
|
||||
file_pick_button.pressed.connect(open_fn)
|
||||
texture_button.pressed.connect(open_fn)
|
||||
clear_button.pressed.connect(clear_fn)
|
||||
_set_button_icon(file_pick_button, "Folder")
|
||||
_set_button_icon(clear_button, "Remove")
|
||||
|
||||
|
||||
func _set_button_icon(p_button: Button, p_icon_name: String) -> void:
|
||||
var editor_base: Control = editor_interface.get_base_control()
|
||||
var icon: Texture2D = editor_base.get_theme_icon(p_icon_name, "EditorIcons")
|
||||
p_button.icon = icon
|
||||
|
||||
|
||||
func _show_error(p_text: String) -> void:
|
||||
push_error("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.text = p_text
|
||||
status_label.add_theme_color_override("font_color", Color(0.9, 0, 0))
|
||||
|
||||
|
||||
func _show_success(p_text: String) -> void:
|
||||
print("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.text = p_text
|
||||
status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
|
||||
|
||||
|
||||
func _create_import_file(png_path: String) -> void:
|
||||
var dst_import_path: String = png_path + ".import"
|
||||
|
||||
var file: FileAccess = FileAccess.open(TEMPLATE_PATH, FileAccess.READ)
|
||||
var template_content: String = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
var import_content: String = template_content.replace("$SOURCE_FILE", png_path)
|
||||
file = FileAccess.open(dst_import_path, FileAccess.WRITE)
|
||||
file.store_string(import_content)
|
||||
file.close()
|
||||
|
||||
|
||||
func _on_pack_button_pressed() -> void:
|
||||
packing_albedo = images[IMAGE_ALBEDO] != null and images[IMAGE_HEIGHT] != null
|
||||
var packing_normal_roughness: bool = images[IMAGE_NORMAL] != null and images[IMAGE_ROUGHNESS] != null
|
||||
|
||||
if not packing_albedo and not packing_normal_roughness:
|
||||
_show_error("Please select an albedo and height texture or a normal and roughness texture.")
|
||||
return
|
||||
|
||||
if packing_albedo:
|
||||
save_file_dialog.current_path = last_saved_directory + "packed_albedo_height"
|
||||
save_file_dialog.title = "Save Packed Albedo/Height Texture"
|
||||
save_file_dialog.popup_centered_ratio()
|
||||
if packing_normal_roughness:
|
||||
queue_pack_normal_roughness = true
|
||||
return
|
||||
if packing_normal_roughness:
|
||||
save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
|
||||
save_file_dialog.title = "Save Packed Normal/Roughness Texture"
|
||||
save_file_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
func _on_save_file_selected(p_dst_path) -> void:
|
||||
last_saved_directory = p_dst_path.get_base_dir() + "/"
|
||||
if packing_albedo:
|
||||
_pack_textures(images[IMAGE_ALBEDO], images[IMAGE_HEIGHT], p_dst_path, false)
|
||||
else:
|
||||
_pack_textures(images[IMAGE_NORMAL], images[IMAGE_ROUGHNESS], p_dst_path, invert_green_checkbox.button_pressed)
|
||||
|
||||
if queue_pack_normal_roughness:
|
||||
queue_pack_normal_roughness = false
|
||||
packing_albedo = false
|
||||
save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
|
||||
save_file_dialog.title = "Save Packed Normal/Roughness Texture"
|
||||
save_file_dialog.call_deferred("popup_centered_ratio")
|
||||
|
||||
|
||||
func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_invert_green: bool) -> void:
|
||||
if p_rgb_image and p_a_image:
|
||||
if p_rgb_image.get_size() != p_a_image.get_size():
|
||||
_show_error("Textures must be the same size.")
|
||||
return
|
||||
|
||||
var output_image: Image = Terrain3D.pack_image(p_rgb_image, p_a_image, p_invert_green)
|
||||
|
||||
if not output_image:
|
||||
_show_error("Failed to pack textures.")
|
||||
return
|
||||
|
||||
output_image.save_png(p_dst_path)
|
||||
editor_interface.get_resource_filesystem().scan_sources()
|
||||
_create_import_file(p_dst_path)
|
||||
_show_success("Packed to " + p_dst_path + ".")
|
||||
else:
|
||||
_show_error("Failed to load one or more textures.")
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
extends PanelContainer
|
||||
|
||||
|
||||
signal resource_changed(resource: Resource, index: int)
|
||||
signal resource_inspected(resource: Resource)
|
||||
signal resource_selected
|
||||
|
||||
var list: ListContainer
|
||||
var entries: Array[ListEntry]
|
||||
var selected_index: int = 0
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
list = ListContainer.new()
|
||||
|
||||
var root: VBoxContainer = VBoxContainer.new()
|
||||
var scroll: ScrollContainer = ScrollContainer.new()
|
||||
var label: Label = Label.new()
|
||||
|
||||
label.set_text("Textures")
|
||||
label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
label.set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER)
|
||||
scroll.set_v_size_flags(SIZE_EXPAND_FILL)
|
||||
scroll.set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
list.set_v_size_flags(SIZE_EXPAND_FILL)
|
||||
list.set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
|
||||
scroll.add_child(list)
|
||||
root.add_child(label)
|
||||
root.add_child(scroll)
|
||||
add_child(root)
|
||||
|
||||
set_custom_minimum_size(Vector2(256, 448))
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
get_child(0).get_child(0).set("theme_override_styles/normal", get_theme_stylebox("bg", "EditorInspectorCategory"))
|
||||
get_child(0).get_child(0).set("theme_override_fonts/font", get_theme_font("bold", "EditorFonts"))
|
||||
get_child(0).get_child(0).set("theme_override_font_sizes/font_size",get_theme_font_size("bold_size", "EditorFonts"))
|
||||
set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel"))
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
for i in entries:
|
||||
i.get_parent().remove_child(i)
|
||||
i.queue_free()
|
||||
entries.clear()
|
||||
|
||||
|
||||
func add_item(p_resource: Resource = null) -> void:
|
||||
var entry: ListEntry = ListEntry.new()
|
||||
var index: int = entries.size()
|
||||
|
||||
entry.set_edited_resource(p_resource)
|
||||
entry.selected.connect(set_selected_index.bind(index))
|
||||
entry.inspected.connect(notify_resource_inspected)
|
||||
entry.changed.connect(notify_resource_changed.bind(index))
|
||||
|
||||
if p_resource:
|
||||
entry.set_selected(index == selected_index)
|
||||
if not p_resource.id_changed.is_connected(set_selected_after_swap):
|
||||
p_resource.id_changed.connect(set_selected_after_swap)
|
||||
|
||||
list.add_child(entry)
|
||||
entries.push_back(entry)
|
||||
|
||||
|
||||
func set_selected_after_swap(p_old_index: int, p_new_index: int) -> void:
|
||||
set_selected_index(clamp(p_new_index, 0, entries.size() - 2))
|
||||
|
||||
|
||||
func set_selected_index(p_index: int) -> void:
|
||||
selected_index = p_index
|
||||
emit_signal("resource_selected")
|
||||
|
||||
for i in entries.size():
|
||||
var entry: ListEntry = entries[i]
|
||||
entry.set_selected(i == selected_index)
|
||||
|
||||
|
||||
func get_selected_index() -> int:
|
||||
return selected_index
|
||||
|
||||
|
||||
func notify_resource_inspected(p_resource: Resource) -> void:
|
||||
emit_signal("resource_inspected", p_resource)
|
||||
|
||||
|
||||
func notify_resource_changed(p_resource: Resource, p_index: int) -> void:
|
||||
emit_signal("resource_changed", p_resource, p_index)
|
||||
if !p_resource:
|
||||
var last_offset: int = 2
|
||||
if p_index == entries.size()-2:
|
||||
last_offset = 3
|
||||
selected_index = clamp(selected_index, 0, entries.size() - last_offset)
|
||||
|
||||
|
||||
##############################################################
|
||||
## class ListContainer
|
||||
##############################################################
|
||||
|
||||
|
||||
class ListContainer extends Container:
|
||||
var height: float = 0
|
||||
|
||||
func _notification(p_what) -> void:
|
||||
if p_what == NOTIFICATION_SORT_CHILDREN:
|
||||
height = 0
|
||||
var index: int = 0
|
||||
var separation: float = 4
|
||||
for c in get_children():
|
||||
if is_instance_valid(c):
|
||||
var width: float = size.x / 3
|
||||
c.size = Vector2(width,width) - Vector2(separation, separation)
|
||||
c.position = Vector2(index % 3, index / 3) * width + Vector2(separation/3, separation/3)
|
||||
height = max(height, c.position.y + width)
|
||||
index += 1
|
||||
|
||||
func _get_minimum_size() -> Vector2:
|
||||
return Vector2(0, height)
|
||||
|
||||
|
||||
##############################################################
|
||||
## class ListEntry
|
||||
##############################################################
|
||||
|
||||
|
||||
class ListEntry extends VBoxContainer:
|
||||
signal selected()
|
||||
signal changed(resource: Terrain3DTexture)
|
||||
signal inspected(resource: Terrain3DTexture)
|
||||
|
||||
var resource: Terrain3DTexture
|
||||
var drop_data: bool = false
|
||||
var is_hovered: bool = false
|
||||
var is_selected: bool = false
|
||||
|
||||
var button_clear: TextureButton
|
||||
var button_edit: TextureButton
|
||||
var name_label: Label
|
||||
|
||||
@onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons")
|
||||
@onready var clear_icon: Texture2D = get_theme_icon("Close", "EditorIcons")
|
||||
@onready var edit_icon: Texture2D = get_theme_icon("Edit", "EditorIcons")
|
||||
@onready var background: StyleBox = get_theme_stylebox("pressed", "Button")
|
||||
@onready var focus: StyleBox = get_theme_stylebox("focus", "Button")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var icon_size: Vector2 = Vector2(12, 12)
|
||||
|
||||
button_clear = TextureButton.new()
|
||||
button_clear.set_texture_normal(clear_icon)
|
||||
button_clear.set_custom_minimum_size(icon_size)
|
||||
button_clear.set_h_size_flags(Control.SIZE_SHRINK_END)
|
||||
button_clear.set_visible(resource != null)
|
||||
button_clear.pressed.connect(clear)
|
||||
add_child(button_clear)
|
||||
|
||||
button_edit = TextureButton.new()
|
||||
button_edit.set_texture_normal(edit_icon)
|
||||
button_edit.set_custom_minimum_size(icon_size)
|
||||
button_edit.set_h_size_flags(Control.SIZE_SHRINK_END)
|
||||
button_edit.set_visible(resource != null)
|
||||
button_edit.pressed.connect(edit)
|
||||
add_child(button_edit)
|
||||
|
||||
name_label = Label.new()
|
||||
add_child(name_label, true)
|
||||
name_label.visible = false
|
||||
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
name_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
|
||||
name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
name_label.add_theme_color_override("font_shadow_color", Color.BLACK)
|
||||
name_label.add_theme_constant_override("shadow_offset_x", 1)
|
||||
name_label.add_theme_constant_override("shadow_offset_y", 1)
|
||||
name_label.add_theme_font_size_override("font_size", 15)
|
||||
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
name_label.text = "Add New"
|
||||
|
||||
|
||||
func _notification(p_what) -> void:
|
||||
match p_what:
|
||||
NOTIFICATION_DRAW:
|
||||
var rect: Rect2 = Rect2(Vector2.ZERO, get_size())
|
||||
if !resource:
|
||||
draw_style_box(background, rect)
|
||||
draw_texture(add_icon, (get_size() / 2) - (add_icon.get_size() / 2))
|
||||
else:
|
||||
name_label.text = resource.get_name()
|
||||
self_modulate = resource.get_albedo_color()
|
||||
var texture: Texture2D = resource.get_albedo_texture()
|
||||
if texture:
|
||||
draw_texture_rect(texture, rect, false)
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST_WITH_MIPMAPS
|
||||
name_label.add_theme_font_size_override("font_size", 4 + rect.size.x/10)
|
||||
if drop_data:
|
||||
draw_style_box(focus, rect)
|
||||
if is_hovered:
|
||||
draw_rect(rect, Color(1,1,1,0.2))
|
||||
if is_selected:
|
||||
draw_style_box(focus, rect)
|
||||
NOTIFICATION_MOUSE_ENTER:
|
||||
is_hovered = true
|
||||
name_label.visible = true
|
||||
queue_redraw()
|
||||
NOTIFICATION_MOUSE_EXIT:
|
||||
is_hovered = false
|
||||
name_label.visible = false
|
||||
drop_data = false
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _gui_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton:
|
||||
if p_event.is_pressed():
|
||||
match p_event.get_button_index():
|
||||
MOUSE_BUTTON_LEFT:
|
||||
# If `Add new` is clicked
|
||||
if !resource:
|
||||
set_edited_resource(Terrain3DTexture.new(), false)
|
||||
edit()
|
||||
else:
|
||||
emit_signal("selected")
|
||||
MOUSE_BUTTON_RIGHT:
|
||||
if resource:
|
||||
edit()
|
||||
MOUSE_BUTTON_MIDDLE:
|
||||
if resource:
|
||||
clear()
|
||||
|
||||
|
||||
func _can_drop_data(p_at_position: Vector2, p_data: Variant) -> bool:
|
||||
drop_data = false
|
||||
if typeof(p_data) == TYPE_DICTIONARY:
|
||||
if p_data.files.size() == 1:
|
||||
queue_redraw()
|
||||
drop_data = true
|
||||
return drop_data
|
||||
|
||||
|
||||
func _drop_data(p_at_position: Vector2, p_data: Variant) -> void:
|
||||
if typeof(p_data) == TYPE_DICTIONARY:
|
||||
var res: Resource = load(p_data.files[0])
|
||||
if res is Terrain3DTexture:
|
||||
set_edited_resource(res, false)
|
||||
if res is Texture2D:
|
||||
var surf: Terrain3DTexture = Terrain3DTexture.new()
|
||||
surf.set_albedo_texture(res)
|
||||
set_edited_resource(surf, false)
|
||||
|
||||
|
||||
func set_edited_resource(p_res: Terrain3DTexture, p_no_signal: bool = true) -> void:
|
||||
resource = p_res
|
||||
if resource:
|
||||
resource.setting_changed.connect(_on_texture_changed)
|
||||
resource.file_changed.connect(_on_texture_changed)
|
||||
|
||||
if button_clear:
|
||||
button_clear.set_visible(resource != null)
|
||||
|
||||
queue_redraw()
|
||||
if !p_no_signal:
|
||||
emit_signal("changed", resource)
|
||||
|
||||
|
||||
func _on_texture_changed() -> void:
|
||||
emit_signal("changed", resource)
|
||||
|
||||
|
||||
func set_selected(value: bool) -> void:
|
||||
is_selected = value
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
if resource:
|
||||
set_edited_resource(null, false)
|
||||
|
||||
|
||||
func edit() -> void:
|
||||
emit_signal("selected")
|
||||
emit_signal("inspected", resource)
|
||||
|
|
@ -1,460 +0,0 @@
|
|||
extends PanelContainer
|
||||
|
||||
signal picking(type, callback)
|
||||
signal setting_changed
|
||||
|
||||
enum Layout {
|
||||
HORIZONTAL,
|
||||
VERTICAL,
|
||||
GRID,
|
||||
}
|
||||
|
||||
enum SettingType {
|
||||
CHECKBOX,
|
||||
SLIDER,
|
||||
DOUBLE_SLIDER,
|
||||
COLOR_SELECT,
|
||||
PICKER,
|
||||
POINT_PICKER,
|
||||
}
|
||||
|
||||
const PointPicker: Script = preload("res://addons/terrain_3d/editor/components/point_picker.gd")
|
||||
const DEFAULT_BRUSH: String = "circle0.exr"
|
||||
const BRUSH_PATH: String = "res://addons/terrain_3d/editor/brushes"
|
||||
const PICKER_ICON: String = "res://addons/terrain_3d/icons/icon_picker.svg"
|
||||
|
||||
const NONE: int = 0x0
|
||||
const ALLOW_LARGER: int = 0x1
|
||||
const ALLOW_SMALLER: int = 0x2
|
||||
const ALLOW_OUT_OF_BOUNDS: int = 0x3
|
||||
|
||||
var brush_preview_material: ShaderMaterial
|
||||
|
||||
var list: HBoxContainer
|
||||
var advanced_list: VBoxContainer
|
||||
var settings: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
list = HBoxContainer.new()
|
||||
add_child(list, true)
|
||||
|
||||
add_brushes(list)
|
||||
|
||||
add_setting(SettingType.SLIDER, "size", 50, list, "m", 4, 200, 1, ALLOW_LARGER)
|
||||
add_setting(SettingType.SLIDER, "opacity", 10, list, "%", 1, 100)
|
||||
add_setting(SettingType.CHECKBOX, "enable", true, list)
|
||||
|
||||
add_setting(SettingType.COLOR_SELECT, "color", Color.WHITE, list)
|
||||
add_setting(SettingType.PICKER, "color picker", Terrain3DEditor.COLOR, list)
|
||||
|
||||
add_setting(SettingType.SLIDER, "roughness", 0, list, "%", -100, 100, 1)
|
||||
add_setting(SettingType.PICKER, "roughness picker", Terrain3DEditor.ROUGHNESS, list)
|
||||
|
||||
add_setting(SettingType.SLIDER, "height", 50, list, "m", -500, 500, 0.1, ALLOW_OUT_OF_BOUNDS)
|
||||
add_setting(SettingType.PICKER, "height picker", Terrain3DEditor.HEIGHT, list)
|
||||
add_setting(SettingType.DOUBLE_SLIDER, "slope", 0, list, "°", 0, 180, 1)
|
||||
|
||||
add_setting(SettingType.POINT_PICKER, "gradient_points", Terrain3DEditor.HEIGHT, list)
|
||||
add_setting(SettingType.CHECKBOX, "drawable", false, list)
|
||||
|
||||
settings["drawable"].toggled.connect(_on_drawable_toggled)
|
||||
|
||||
var spacer: Control = Control.new()
|
||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
list.add_child(spacer, true)
|
||||
|
||||
## Advanced Settings Menu
|
||||
advanced_list = create_submenu(list, "Advanced", Layout.VERTICAL)
|
||||
add_setting(SettingType.CHECKBOX, "automatic_regions", true, advanced_list)
|
||||
add_setting(SettingType.CHECKBOX, "align_to_view", true, advanced_list)
|
||||
add_setting(SettingType.CHECKBOX, "show_cursor_while_painting", true, advanced_list)
|
||||
advanced_list.add_child(HSeparator.new(), true)
|
||||
add_setting(SettingType.SLIDER, "gamma", 1.0, advanced_list, "γ", 0.1, 2.0, 0.01)
|
||||
add_setting(SettingType.SLIDER, "jitter", 50, advanced_list, "%", 0, 100)
|
||||
|
||||
|
||||
func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout) -> Container:
|
||||
var menu_button: Button = Button.new()
|
||||
menu_button.set_text(p_button_name)
|
||||
menu_button.set_toggle_mode(true)
|
||||
menu_button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
menu_button.connect("toggled", _on_show_submenu.bind(menu_button))
|
||||
|
||||
var submenu: PopupPanel = PopupPanel.new()
|
||||
submenu.connect("popup_hide", menu_button.set_pressed_no_signal.bind(false))
|
||||
submenu.set("theme_override_styles/panel", get_theme_stylebox("panel", "PopupMenu"))
|
||||
|
||||
var sublist: Container
|
||||
match(p_layout):
|
||||
Layout.GRID:
|
||||
sublist = GridContainer.new()
|
||||
Layout.VERTICAL:
|
||||
sublist = VBoxContainer.new()
|
||||
Layout.HORIZONTAL, _:
|
||||
sublist = HBoxContainer.new()
|
||||
|
||||
p_parent.add_child(menu_button, true)
|
||||
menu_button.add_child(submenu, true)
|
||||
submenu.add_child(sublist, true)
|
||||
|
||||
return sublist
|
||||
|
||||
|
||||
func _on_show_submenu(p_toggled: bool, p_button: Button) -> void:
|
||||
var popup: PopupPanel = p_button.get_child(0)
|
||||
var popup_pos: Vector2 = p_button.get_screen_transform().origin
|
||||
popup.set_visible(p_toggled)
|
||||
popup_pos.y -= popup.get_size().y
|
||||
popup.set_position(popup_pos)
|
||||
|
||||
|
||||
func add_brushes(p_parent: Control) -> void:
|
||||
var brush_list: GridContainer = create_submenu(p_parent, "Brush", Layout.GRID)
|
||||
brush_list.name = "BrushList"
|
||||
|
||||
var brush_button_group: ButtonGroup = ButtonGroup.new()
|
||||
brush_button_group.connect("pressed", _on_setting_changed)
|
||||
var default_brush_btn: Button
|
||||
|
||||
var dir: DirAccess = DirAccess.open(BRUSH_PATH)
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
if !dir.current_is_dir() and file_name.ends_with(".exr"):
|
||||
var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
|
||||
_black_to_alpha(img)
|
||||
var tex: ImageTexture = ImageTexture.create_from_image(img)
|
||||
|
||||
var btn: Button = Button.new()
|
||||
btn.set_custom_minimum_size(Vector2.ONE * 100)
|
||||
btn.set_button_icon(tex)
|
||||
btn.set_meta("image", img)
|
||||
btn.set_expand_icon(true)
|
||||
btn.set_material(_get_brush_preview_material())
|
||||
btn.set_toggle_mode(true)
|
||||
btn.set_button_group(brush_button_group)
|
||||
btn.mouse_entered.connect(_on_brush_hover.bind(true, btn))
|
||||
btn.mouse_exited.connect(_on_brush_hover.bind(false, btn))
|
||||
brush_list.add_child(btn, true)
|
||||
if file_name == DEFAULT_BRUSH:
|
||||
default_brush_btn = btn
|
||||
|
||||
var lbl: Label = Label.new()
|
||||
btn.add_child(lbl, true)
|
||||
lbl.text = file_name.get_basename()
|
||||
lbl.visible = false
|
||||
lbl.position.y = 70
|
||||
lbl.add_theme_color_override("font_shadow_color", Color.BLACK)
|
||||
lbl.add_theme_constant_override("shadow_offset_x", 1)
|
||||
lbl.add_theme_constant_override("shadow_offset_y", 1)
|
||||
lbl.add_theme_font_size_override("font_size", 16)
|
||||
|
||||
file_name = dir.get_next()
|
||||
|
||||
brush_list.columns = sqrt(brush_list.get_child_count()) + 2
|
||||
|
||||
if not default_brush_btn:
|
||||
default_brush_btn = brush_button_group.get_buttons()[0]
|
||||
default_brush_btn.set_pressed(true)
|
||||
|
||||
settings["brush"] = brush_button_group
|
||||
|
||||
# Optionally erase the main brush button text and replace it with the texture
|
||||
# var select_brush_btn: Button = brush_list.get_parent().get_parent()
|
||||
# select_brush_btn.set_button_icon(default_brush_btn.get_button_icon())
|
||||
# select_brush_btn.set_custom_minimum_size(Vector2.ONE * 36)
|
||||
# select_brush_btn.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
# select_brush_btn.set_expand_icon(true)
|
||||
|
||||
|
||||
func _on_brush_hover(p_hovering: bool, p_button: Button) -> void:
|
||||
if p_button.get_child_count() > 0:
|
||||
var child = p_button.get_child(0)
|
||||
if child is Label:
|
||||
if p_hovering:
|
||||
child.visible = true
|
||||
else:
|
||||
child.visible = false
|
||||
|
||||
|
||||
func _on_pick(p_type: Terrain3DEditor.Tool) -> void:
|
||||
emit_signal("picking", p_type, _on_picked)
|
||||
|
||||
|
||||
func _on_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3) -> void:
|
||||
match p_type:
|
||||
Terrain3DEditor.HEIGHT:
|
||||
settings["height"].value = p_color.r
|
||||
Terrain3DEditor.COLOR:
|
||||
settings["color"].color = p_color
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
# 200... -.5 converts 0,1 to -100,100
|
||||
settings["roughness"].value = round(200 * (p_color.a - 0.5))
|
||||
_on_setting_changed()
|
||||
|
||||
|
||||
func _on_point_pick(p_type: Terrain3DEditor.Tool, p_name: String) -> void:
|
||||
assert(p_type == Terrain3DEditor.HEIGHT)
|
||||
emit_signal("picking", p_type, _on_point_picked.bind(p_name))
|
||||
|
||||
|
||||
func _on_point_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3, p_name: String) -> void:
|
||||
assert(p_type == Terrain3DEditor.HEIGHT)
|
||||
|
||||
var point: Vector3 = p_global_position
|
||||
point.y = p_color.r
|
||||
settings[p_name].add_point(point)
|
||||
_on_setting_changed()
|
||||
|
||||
|
||||
func add_setting(p_type: SettingType, p_name: StringName, p_value: Variant, p_parent: Control,
|
||||
p_suffix: String = "", p_min_value: float = 0.0, p_max_value: float = 0.0, p_step: float = 1.0,
|
||||
p_flags: int = NONE) -> void:
|
||||
|
||||
var container: HBoxContainer = HBoxContainer.new()
|
||||
var label: Label = Label.new()
|
||||
var control: Control
|
||||
|
||||
container.set_v_size_flags(SIZE_EXPAND_FILL)
|
||||
|
||||
match p_type:
|
||||
SettingType.SLIDER, SettingType.DOUBLE_SLIDER:
|
||||
label.set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER)
|
||||
label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
label.set_custom_minimum_size(Vector2(32, 0))
|
||||
label.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
label.set_text(p_name.capitalize() + ": ")
|
||||
container.add_child(label, true)
|
||||
|
||||
var slider: Control
|
||||
if p_type == SettingType.SLIDER:
|
||||
control = EditorSpinSlider.new()
|
||||
control.set_flat(true)
|
||||
control.set_hide_slider(true)
|
||||
control.connect("value_changed", _on_setting_changed)
|
||||
control.set_max(p_max_value)
|
||||
control.set_min(p_min_value)
|
||||
control.set_step(p_step)
|
||||
control.set_value(p_value)
|
||||
control.set_suffix(p_suffix)
|
||||
control.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
|
||||
slider = HSlider.new()
|
||||
slider.share(control)
|
||||
if p_flags & ALLOW_LARGER:
|
||||
slider.set_allow_greater(true)
|
||||
if p_flags & ALLOW_SMALLER:
|
||||
slider.set_allow_lesser(true)
|
||||
|
||||
else:
|
||||
control = Label.new()
|
||||
control.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
control.set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER)
|
||||
slider = DoubleSlider.new()
|
||||
slider.label = control
|
||||
slider.suffix = p_suffix
|
||||
slider.connect("value_changed", _on_setting_changed)
|
||||
|
||||
control.set_custom_minimum_size(Vector2(75, 0))
|
||||
slider.set_max(p_max_value)
|
||||
slider.set_min(p_min_value)
|
||||
slider.set_step(p_step)
|
||||
slider.set_value(p_value)
|
||||
slider.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
slider.set_h_size_flags(SIZE_SHRINK_END | SIZE_EXPAND)
|
||||
slider.set_custom_minimum_size(Vector2(100, 10))
|
||||
|
||||
container.add_child(slider, true)
|
||||
|
||||
SettingType.CHECKBOX:
|
||||
control = CheckBox.new()
|
||||
control.set_text(p_name.capitalize())
|
||||
control.set_pressed_no_signal(p_value)
|
||||
control.connect("pressed", _on_setting_changed)
|
||||
|
||||
SettingType.COLOR_SELECT:
|
||||
control = ColorPickerButton.new()
|
||||
control.set_custom_minimum_size(Vector2(100, 10))
|
||||
control.color = Color.WHITE
|
||||
control.edit_alpha = false
|
||||
control.get_picker().set_color_mode(ColorPicker.MODE_HSV)
|
||||
control.connect("color_changed", _on_setting_changed)
|
||||
|
||||
SettingType.PICKER:
|
||||
control = Button.new()
|
||||
control.icon = load(PICKER_ICON)
|
||||
control.tooltip_text = "Pick value from the Terrain"
|
||||
control.connect("pressed", _on_pick.bind(p_value))
|
||||
|
||||
SettingType.POINT_PICKER:
|
||||
control = PointPicker.new()
|
||||
control.connect("pressed", _on_point_pick.bind(p_value, p_name))
|
||||
control.connect("value_changed", _on_setting_changed)
|
||||
|
||||
container.add_child(control, true)
|
||||
p_parent.add_child(container, true)
|
||||
|
||||
settings[p_name] = control
|
||||
|
||||
|
||||
func get_setting(p_setting: String) -> Variant:
|
||||
var object: Object = settings[p_setting]
|
||||
var value: Variant
|
||||
if object is Range:
|
||||
value = object.get_value()
|
||||
elif object is DoubleSlider:
|
||||
value = [object.get_min_value(), object.get_max_value()]
|
||||
elif object is ButtonGroup:
|
||||
var img: Image = object.get_pressed_button().get_meta("image")
|
||||
var tex: Texture2D = object.get_pressed_button().get_button_icon()
|
||||
value = [ img, tex ]
|
||||
elif object is CheckBox:
|
||||
value = object.is_pressed()
|
||||
elif object is ColorPickerButton:
|
||||
value = object.color
|
||||
elif object is PointPicker:
|
||||
value = object.get_points()
|
||||
return value
|
||||
|
||||
|
||||
func hide_settings(p_settings: PackedStringArray) -> void:
|
||||
for setting in settings.keys():
|
||||
var object: Object = settings[setting]
|
||||
if object is Control:
|
||||
object.get_parent().show()
|
||||
|
||||
for setting in p_settings:
|
||||
if settings.has(setting):
|
||||
var object: Object = settings[setting]
|
||||
if object is Control:
|
||||
object.get_parent().hide()
|
||||
|
||||
|
||||
func _on_setting_changed(p_data: Variant = null) -> void:
|
||||
# If a button was clicked on a submenu
|
||||
if p_data is Button and p_data.get_parent().get_parent() is PopupPanel:
|
||||
if p_data.get_parent().name == "BrushList":
|
||||
# Optionally Set selected brush texture in main brush button
|
||||
# p_data.get_parent().get_parent().get_parent().set_button_icon(p_data.get_button_icon())
|
||||
# Hide popup
|
||||
p_data.get_parent().get_parent().set_visible(false)
|
||||
# Hide label
|
||||
if p_data.get_child_count() > 0:
|
||||
p_data.get_child(0).visible = false
|
||||
|
||||
emit_signal("setting_changed")
|
||||
|
||||
|
||||
func _on_drawable_toggled(p_button_pressed: bool) -> void:
|
||||
if not p_button_pressed:
|
||||
settings["gradient_points"].clear()
|
||||
|
||||
|
||||
func _get_brush_preview_material() -> ShaderMaterial:
|
||||
if !brush_preview_material:
|
||||
brush_preview_material = ShaderMaterial.new()
|
||||
|
||||
var shader: Shader = Shader.new()
|
||||
var code: String = "shader_type canvas_item;\n"
|
||||
|
||||
code += "varying vec4 v_vertex_color;\n"
|
||||
code += "void vertex() {\n"
|
||||
code += " v_vertex_color = COLOR;\n"
|
||||
code += "}\n"
|
||||
code += "void fragment(){\n"
|
||||
code += " vec4 tex = texture(TEXTURE, UV);\n"
|
||||
code += " COLOR.a *= pow(tex.r, 0.666);\n"
|
||||
code += " COLOR.rgb = v_vertex_color.rgb;\n"
|
||||
code += "}\n"
|
||||
|
||||
shader.set_code(code)
|
||||
|
||||
brush_preview_material.set_shader(shader)
|
||||
|
||||
return brush_preview_material
|
||||
|
||||
|
||||
func _black_to_alpha(p_image: Image) -> void:
|
||||
if p_image.get_format() != Image.FORMAT_RGBAF:
|
||||
p_image.convert(Image.FORMAT_RGBAF)
|
||||
|
||||
for y in p_image.get_height():
|
||||
for x in p_image.get_width():
|
||||
var color: Color = p_image.get_pixel(x,y)
|
||||
color.a = color.get_luminance()
|
||||
p_image.set_pixel(x, y, color)
|
||||
|
||||
|
||||
#### Sub Class DoubleSlider
|
||||
|
||||
class DoubleSlider extends Range:
|
||||
|
||||
var label: Label
|
||||
var suffix: String
|
||||
var grabbed: bool = false
|
||||
var _max_value: float
|
||||
|
||||
|
||||
func _gui_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton:
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
grabbed = p_event.is_pressed()
|
||||
set_min_max(p_event.get_position().x)
|
||||
|
||||
if p_event is InputEventMouseMotion:
|
||||
if grabbed:
|
||||
set_min_max(p_event.get_position().x)
|
||||
|
||||
|
||||
func _notification(p_what: int) -> void:
|
||||
if p_what == NOTIFICATION_RESIZED:
|
||||
pass
|
||||
if p_what == NOTIFICATION_DRAW:
|
||||
var bg: StyleBox = get_theme_stylebox("slider", "HSlider")
|
||||
var bg_height: float = bg.get_minimum_size().y
|
||||
draw_style_box(bg, Rect2(Vector2(0, (size.y - bg_height) / 2), Vector2(size.x, bg_height)))
|
||||
|
||||
var grabber: Texture2D = get_theme_icon("grabber", "HSlider")
|
||||
var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
|
||||
var h: float = size.y / 2 - grabber.get_size().y / 2
|
||||
|
||||
var minpos: Vector2 = Vector2((min_value / _max_value) * size.x - grabber.get_size().x / 2, h)
|
||||
var maxpos: Vector2 = Vector2((max_value / _max_value) * size.x - grabber.get_size().x / 2, h)
|
||||
|
||||
draw_style_box(area, Rect2(Vector2(minpos.x + grabber.get_size().x / 2, (size.y - bg_height) / 2), Vector2(maxpos.x - minpos.x, bg_height)))
|
||||
|
||||
draw_texture(grabber, minpos)
|
||||
draw_texture(grabber, maxpos)
|
||||
|
||||
|
||||
func set_max(p_value: float) -> void:
|
||||
max_value = p_value
|
||||
if _max_value == 0:
|
||||
_max_value = max_value
|
||||
update_label()
|
||||
|
||||
|
||||
func set_min_max(p_xpos: float) -> void:
|
||||
var mid_value_normalized: float = ((max_value + min_value) / 2.0) / _max_value
|
||||
var mid_value: float = size.x * mid_value_normalized
|
||||
var min_active: bool = p_xpos < mid_value
|
||||
var xpos_ranged: float = snappedf((p_xpos / size.x) * _max_value, step)
|
||||
|
||||
if min_active:
|
||||
min_value = xpos_ranged
|
||||
else:
|
||||
max_value = xpos_ranged
|
||||
|
||||
min_value = clamp(min_value, 0, max_value - 10)
|
||||
max_value = clamp(max_value, min_value + 10, _max_value)
|
||||
|
||||
update_label()
|
||||
emit_signal("setting_changed", value)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func update_label() -> void:
|
||||
if label:
|
||||
label.set_text(str(min_value) + suffix + "/" + str(max_value) + suffix)
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
extends VBoxContainer
|
||||
|
||||
|
||||
signal tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation)
|
||||
|
||||
const ICON_REGION_ADD: String = "res://addons/terrain_3d/icons/icon_map_add.svg"
|
||||
const ICON_REGION_REMOVE: String = "res://addons/terrain_3d/icons/icon_map_remove.svg"
|
||||
const ICON_HEIGHT_ADD: String = "res://addons/terrain_3d/icons/icon_height_add.svg"
|
||||
const ICON_HEIGHT_SUB: String = "res://addons/terrain_3d/icons/icon_height_sub.svg"
|
||||
const ICON_HEIGHT_MUL: String = "res://addons/terrain_3d/icons/icon_height_mul.svg"
|
||||
const ICON_HEIGHT_DIV: String = "res://addons/terrain_3d/icons/icon_height_div.svg"
|
||||
const ICON_HEIGHT_FLAT: String = "res://addons/terrain_3d/icons/icon_height_flat.svg"
|
||||
const ICON_HEIGHT_SLOPE: String = "res://addons/terrain_3d/icons/icon_height_slope.svg"
|
||||
const ICON_HEIGHT_SMOOTH: String = "res://addons/terrain_3d/icons/icon_height_smooth.svg"
|
||||
const ICON_PAINT_TEXTURE: String = "res://addons/terrain_3d/icons/icon_brush.svg"
|
||||
const ICON_SPRAY_TEXTURE: String = "res://addons/terrain_3d/icons/icon_spray.svg"
|
||||
const ICON_COLOR: String = "res://addons/terrain_3d/icons/icon_color.svg"
|
||||
const ICON_WETNESS: String = "res://addons/terrain_3d/icons/icon_wetness.svg"
|
||||
const ICON_AUTOSHADER: String = "res://addons/terrain_3d/icons/icon_terrain_material.svg"
|
||||
const ICON_HOLES: String = "res://addons/terrain_3d/icons/icon_holes.svg"
|
||||
const ICON_NAVIGATION: String = "res://addons/terrain_3d/icons/icon_navigation.svg"
|
||||
|
||||
var tool_group: ButtonGroup = ButtonGroup.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
set_custom_minimum_size(Vector2(32, 0))
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
tool_group.connect("pressed", _on_tool_selected)
|
||||
|
||||
add_tool_button(Terrain3DEditor.REGION, Terrain3DEditor.ADD, "Add Region", load(ICON_REGION_ADD), tool_group)
|
||||
add_tool_button(Terrain3DEditor.REGION, Terrain3DEditor.SUBTRACT, "Delete Region", load(ICON_REGION_REMOVE), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.ADD, "Raise", load(ICON_HEIGHT_ADD), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.SUBTRACT, "Lower", load(ICON_HEIGHT_SUB), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.MULTIPLY, "Expand (Away from 0)", load(ICON_HEIGHT_MUL), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.DIVIDE, "Reduce (Towards 0)", load(ICON_HEIGHT_DIV), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.REPLACE, "Flatten", load(ICON_HEIGHT_FLAT), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.GRADIENT, "Slope", load(ICON_HEIGHT_SLOPE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.AVERAGE, "Smooth", load(ICON_HEIGHT_SMOOTH), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.TEXTURE, Terrain3DEditor.REPLACE, "Paint Base Texture", load(ICON_PAINT_TEXTURE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.TEXTURE, Terrain3DEditor.ADD, "Spray Overlay Texture", load(ICON_SPRAY_TEXTURE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.AUTOSHADER, Terrain3DEditor.REPLACE, "Autoshader", load(ICON_AUTOSHADER), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.COLOR, Terrain3DEditor.REPLACE, "Paint Color", load(ICON_COLOR), tool_group)
|
||||
add_tool_button(Terrain3DEditor.ROUGHNESS, Terrain3DEditor.REPLACE, "Paint Wetness", load(ICON_WETNESS), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.HOLES, Terrain3DEditor.REPLACE, "Create Holes", load(ICON_HOLES), tool_group)
|
||||
add_tool_button(Terrain3DEditor.NAVIGATION, Terrain3DEditor.REPLACE, "Paint Navigable Area", load(ICON_NAVIGATION), tool_group)
|
||||
|
||||
var buttons: Array[BaseButton] = tool_group.get_buttons()
|
||||
buttons[0].set_pressed(true)
|
||||
|
||||
|
||||
func add_tool_button(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation,
|
||||
p_tip: String, p_icon: Texture2D, p_group: ButtonGroup) -> void:
|
||||
|
||||
var button: Button = Button.new()
|
||||
button.set_meta("Tool", p_tool)
|
||||
button.set_meta("Operation", p_operation)
|
||||
button.set_tooltip_text(p_tip)
|
||||
button.set_button_icon(p_icon)
|
||||
button.set_button_group(p_group)
|
||||
button.set_flat(true)
|
||||
button.set_toggle_mode(true)
|
||||
button.set_h_size_flags(SIZE_SHRINK_END)
|
||||
add_child(button)
|
||||
|
||||
|
||||
func _on_tool_selected(p_button: BaseButton) -> void:
|
||||
emit_signal("tool_changed", p_button.get_meta("Tool", -1), p_button.get_meta("Operation", -1))
|
||||
|
|
@ -1,379 +0,0 @@
|
|||
extends Node
|
||||
#class_name Terrain3DUI Cannot be named until Godot #75388
|
||||
|
||||
|
||||
# Includes
|
||||
const Toolbar: Script = preload("res://addons/terrain_3d/editor/components/toolbar.gd")
|
||||
const ToolSettings: Script = preload("res://addons/terrain_3d/editor/components/tool_settings.gd")
|
||||
const TerrainTools: Script = preload("res://addons/terrain_3d/editor/components/terrain_tools.gd")
|
||||
const OperationBuilder: Script = preload("res://addons/terrain_3d/editor/components/operation_builder.gd")
|
||||
const GradientOperationBuilder: Script = preload("res://addons/terrain_3d/editor/components/gradient_operation_builder.gd")
|
||||
const RING1: String = "res://addons/terrain_3d/editor/brushes/ring1.exr"
|
||||
const COLOR_RAISE := Color.WHITE
|
||||
const COLOR_LOWER := Color.BLACK
|
||||
const COLOR_SMOOTH := Color(0.5, 0, .1)
|
||||
const COLOR_EXPAND := Color.ORANGE
|
||||
const COLOR_REDUCE := Color.BLUE_VIOLET
|
||||
const COLOR_FLATTEN := Color(0., 0.32, .4)
|
||||
const COLOR_SLOPE := Color.YELLOW
|
||||
const COLOR_PAINT := Color.FOREST_GREEN
|
||||
const COLOR_SPRAY := Color.SEA_GREEN
|
||||
const COLOR_ROUGHNESS := Color.ROYAL_BLUE
|
||||
const COLOR_AUTOSHADER := Color.DODGER_BLUE
|
||||
const COLOR_HOLES := Color.BLACK
|
||||
const COLOR_NAVIGATION := Color.REBECCA_PURPLE
|
||||
const COLOR_PICK_COLOR := Color.WHITE
|
||||
const COLOR_PICK_HEIGHT := Color.DARK_RED
|
||||
const COLOR_PICK_ROUGH := Color.ROYAL_BLUE
|
||||
|
||||
|
||||
var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
|
||||
var toolbar: Toolbar
|
||||
var toolbar_settings: ToolSettings
|
||||
var terrain_tools: TerrainTools
|
||||
var setting_has_changed: bool = false
|
||||
var visible: bool = false
|
||||
var picking: int = Terrain3DEditor.TOOL_MAX
|
||||
var picking_callback: Callable
|
||||
var decal: Decal
|
||||
var decal_timer: Timer
|
||||
var gradient_decals: Array[Decal]
|
||||
var brush_data: Dictionary
|
||||
var operation_builder: OperationBuilder
|
||||
@onready var picker_texture: ImageTexture = ImageTexture.create_from_image(Image.load_from_file(RING1))
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
toolbar = Toolbar.new()
|
||||
toolbar.hide()
|
||||
toolbar.connect("tool_changed", _on_tool_changed)
|
||||
|
||||
toolbar_settings = ToolSettings.new()
|
||||
toolbar_settings.connect("setting_changed", _on_setting_changed)
|
||||
toolbar_settings.connect("picking", _on_picking)
|
||||
toolbar_settings.hide()
|
||||
|
||||
terrain_tools = TerrainTools.new()
|
||||
terrain_tools.plugin = plugin
|
||||
terrain_tools.hide()
|
||||
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, toolbar_settings)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_tools)
|
||||
|
||||
decal = Decal.new()
|
||||
add_child(decal)
|
||||
decal_timer = Timer.new()
|
||||
decal_timer.wait_time = .5
|
||||
decal_timer.one_shot = true
|
||||
decal_timer.timeout.connect(Callable(func(node):
|
||||
if node:
|
||||
get_tree().create_tween().tween_property(node, "albedo_mix", 0.0, 0.15)).bind(decal))
|
||||
add_child(decal_timer)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, toolbar_settings)
|
||||
toolbar.queue_free()
|
||||
toolbar_settings.queue_free()
|
||||
terrain_tools.queue_free()
|
||||
decal.queue_free()
|
||||
decal_timer.queue_free()
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.queue_free()
|
||||
gradient_decals.clear()
|
||||
|
||||
|
||||
func set_visible(p_visible: bool) -> void:
|
||||
visible = p_visible
|
||||
toolbar.set_visible(p_visible and plugin.terrain)
|
||||
terrain_tools.set_visible(p_visible)
|
||||
|
||||
if p_visible and plugin.terrain:
|
||||
p_visible = plugin.editor.get_tool() != Terrain3DEditor.REGION
|
||||
toolbar_settings.set_visible(p_visible and plugin.terrain)
|
||||
update_decal()
|
||||
|
||||
|
||||
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
||||
clear_picking()
|
||||
|
||||
if not visible or not plugin.terrain:
|
||||
return
|
||||
|
||||
if plugin.editor:
|
||||
plugin.editor.set_tool(p_tool)
|
||||
plugin.editor.set_operation(p_operation)
|
||||
|
||||
if p_tool != Terrain3DEditor.REGION:
|
||||
# Select which settings to hide. Options:
|
||||
# size, opactiy, height, slope, color, roughness, (height|color|roughness) picker
|
||||
var to_hide: PackedStringArray = []
|
||||
|
||||
if p_tool == Terrain3DEditor.HEIGHT:
|
||||
to_hide.push_back("color")
|
||||
to_hide.push_back("color picker")
|
||||
to_hide.push_back("roughness")
|
||||
to_hide.push_back("roughness picker")
|
||||
to_hide.push_back("slope")
|
||||
to_hide.push_back("enable")
|
||||
if p_operation != Terrain3DEditor.REPLACE:
|
||||
to_hide.push_back("height")
|
||||
to_hide.push_back("height picker")
|
||||
if p_operation != Terrain3DEditor.GRADIENT:
|
||||
to_hide.push_back("gradient_points")
|
||||
to_hide.push_back("drawable")
|
||||
|
||||
elif p_tool == Terrain3DEditor.TEXTURE:
|
||||
to_hide.push_back("height")
|
||||
to_hide.push_back("height picker")
|
||||
to_hide.push_back("gradient_points")
|
||||
to_hide.push_back("drawable")
|
||||
to_hide.push_back("color")
|
||||
to_hide.push_back("color picker")
|
||||
to_hide.push_back("roughness")
|
||||
to_hide.push_back("roughness picker")
|
||||
to_hide.push_back("slope")
|
||||
to_hide.push_back("enable")
|
||||
if p_operation == Terrain3DEditor.REPLACE:
|
||||
to_hide.push_back("opacity")
|
||||
|
||||
elif p_tool == Terrain3DEditor.COLOR:
|
||||
to_hide.push_back("height")
|
||||
to_hide.push_back("height picker")
|
||||
to_hide.push_back("gradient_points")
|
||||
to_hide.push_back("drawable")
|
||||
to_hide.push_back("roughness")
|
||||
to_hide.push_back("roughness picker")
|
||||
to_hide.push_back("slope")
|
||||
to_hide.push_back("enable")
|
||||
|
||||
elif p_tool == Terrain3DEditor.ROUGHNESS:
|
||||
to_hide.push_back("height")
|
||||
to_hide.push_back("height picker")
|
||||
to_hide.push_back("gradient_points")
|
||||
to_hide.push_back("drawable")
|
||||
to_hide.push_back("color")
|
||||
to_hide.push_back("color picker")
|
||||
to_hide.push_back("slope")
|
||||
to_hide.push_back("enable")
|
||||
|
||||
elif p_tool in [ Terrain3DEditor.AUTOSHADER, Terrain3DEditor.HOLES, Terrain3DEditor.NAVIGATION ]:
|
||||
to_hide.push_back("height")
|
||||
to_hide.push_back("height picker")
|
||||
to_hide.push_back("gradient_points")
|
||||
to_hide.push_back("drawable")
|
||||
to_hide.push_back("color")
|
||||
to_hide.push_back("color picker")
|
||||
to_hide.push_back("roughness")
|
||||
to_hide.push_back("roughness picker")
|
||||
to_hide.push_back("slope")
|
||||
to_hide.push_back("opacity")
|
||||
|
||||
toolbar_settings.hide_settings(to_hide)
|
||||
|
||||
toolbar_settings.set_visible(p_tool != Terrain3DEditor.REGION)
|
||||
|
||||
operation_builder = null
|
||||
if p_operation == Terrain3DEditor.GRADIENT:
|
||||
operation_builder = GradientOperationBuilder.new()
|
||||
operation_builder.tool_settings = toolbar_settings
|
||||
|
||||
_on_setting_changed()
|
||||
plugin.update_region_grid()
|
||||
|
||||
|
||||
|
||||
func _on_setting_changed() -> void:
|
||||
if not visible or not plugin.terrain:
|
||||
return
|
||||
brush_data = {
|
||||
"size": int(toolbar_settings.get_setting("size")),
|
||||
"opacity": toolbar_settings.get_setting("opacity") / 100.0,
|
||||
"height": toolbar_settings.get_setting("height"),
|
||||
"texture_index": plugin.texture_dock.get_selected_index(),
|
||||
"color": toolbar_settings.get_setting("color"),
|
||||
"roughness": toolbar_settings.get_setting("roughness"),
|
||||
"gradient_points": toolbar_settings.get_setting("gradient_points"),
|
||||
"enable": toolbar_settings.get_setting("enable"),
|
||||
"automatic_regions": toolbar_settings.get_setting("automatic_regions"),
|
||||
"align_to_view": toolbar_settings.get_setting("align_to_view"),
|
||||
"show_cursor_while_painting": toolbar_settings.get_setting("show_cursor_while_painting"),
|
||||
"gamma": toolbar_settings.get_setting("gamma"),
|
||||
"jitter": toolbar_settings.get_setting("jitter"),
|
||||
}
|
||||
var brush_imgs: Array = toolbar_settings.get_setting("brush")
|
||||
brush_data["image"] = brush_imgs[0]
|
||||
brush_data["texture"] = brush_imgs[1]
|
||||
|
||||
update_decal()
|
||||
plugin.editor.set_brush_data(brush_data)
|
||||
|
||||
|
||||
func update_decal() -> void:
|
||||
var mouse_buttons: int = Input.get_mouse_button_mask()
|
||||
if not visible or \
|
||||
not plugin.terrain or \
|
||||
brush_data.is_empty() or \
|
||||
mouse_buttons & MOUSE_BUTTON_RIGHT or \
|
||||
(mouse_buttons & MOUSE_BUTTON_LEFT and not brush_data["show_cursor_while_painting"]) or \
|
||||
plugin.editor.get_tool() == Terrain3DEditor.REGION:
|
||||
decal.visible = false
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
return
|
||||
else:
|
||||
# Wait for cursor to recenter after right-click before revealing
|
||||
# See https://github.com/godotengine/godot/issues/70098
|
||||
await get_tree().create_timer(.05).timeout
|
||||
decal.visible = true
|
||||
|
||||
decal.size = Vector3.ONE * brush_data["size"]
|
||||
if brush_data["align_to_view"]:
|
||||
var cam: Camera3D = plugin.terrain.get_camera();
|
||||
if (cam):
|
||||
decal.rotation.y = cam.rotation.y
|
||||
else:
|
||||
decal.rotation.y = 0
|
||||
|
||||
# Set texture and color
|
||||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
decal.texture_albedo = picker_texture
|
||||
decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
|
||||
match picking:
|
||||
Terrain3DEditor.HEIGHT:
|
||||
decal.modulate = COLOR_PICK_HEIGHT
|
||||
Terrain3DEditor.COLOR:
|
||||
decal.modulate = COLOR_PICK_COLOR
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
decal.modulate = COLOR_PICK_ROUGH
|
||||
decal.modulate.a = 1.0
|
||||
else:
|
||||
decal.texture_albedo = brush_data["texture"]
|
||||
match plugin.editor.get_tool():
|
||||
Terrain3DEditor.HEIGHT:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.ADD:
|
||||
decal.modulate = COLOR_RAISE
|
||||
Terrain3DEditor.SUBTRACT:
|
||||
decal.modulate = COLOR_LOWER
|
||||
Terrain3DEditor.MULTIPLY:
|
||||
decal.modulate = COLOR_EXPAND
|
||||
Terrain3DEditor.DIVIDE:
|
||||
decal.modulate = COLOR_REDUCE
|
||||
Terrain3DEditor.REPLACE:
|
||||
decal.modulate = COLOR_FLATTEN
|
||||
Terrain3DEditor.AVERAGE:
|
||||
decal.modulate = COLOR_SMOOTH
|
||||
Terrain3DEditor.GRADIENT:
|
||||
decal.modulate = COLOR_SLOPE
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = max(.3, brush_data["opacity"])
|
||||
Terrain3DEditor.TEXTURE:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.REPLACE:
|
||||
decal.modulate = COLOR_PAINT
|
||||
decal.modulate.a = 1.0
|
||||
Terrain3DEditor.ADD:
|
||||
decal.modulate = COLOR_SPRAY
|
||||
decal.modulate.a = max(.3, brush_data["opacity"])
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
Terrain3DEditor.COLOR:
|
||||
decal.modulate = brush_data["color"].srgb_to_linear()*.5
|
||||
decal.modulate.a = max(.3, brush_data["opacity"])
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
decal.modulate = COLOR_ROUGHNESS
|
||||
decal.modulate.a = max(.3, brush_data["opacity"])
|
||||
Terrain3DEditor.AUTOSHADER:
|
||||
decal.modulate = COLOR_AUTOSHADER
|
||||
decal.modulate.a = 1.0
|
||||
Terrain3DEditor.HOLES:
|
||||
decal.modulate = COLOR_HOLES
|
||||
decal.modulate.a = 1.0
|
||||
Terrain3DEditor.NAVIGATION:
|
||||
decal.modulate = COLOR_NAVIGATION
|
||||
decal.modulate.a = 1.0
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = max(.3, brush_data["opacity"])
|
||||
decal.size.y = max(1000, decal.size.y)
|
||||
decal.albedo_mix = 1.0
|
||||
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
|
||||
decal_timer.start()
|
||||
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
|
||||
if plugin.editor.get_operation() == Terrain3DEditor.GRADIENT:
|
||||
var index := 0
|
||||
for point in brush_data["gradient_points"]:
|
||||
if point != Vector3.ZERO:
|
||||
var point_decal: Decal = _get_gradient_decal(index)
|
||||
point_decal.visible = true
|
||||
point_decal.position = point
|
||||
index += 1
|
||||
|
||||
|
||||
func _get_gradient_decal(index: int) -> Decal:
|
||||
if gradient_decals.size() > index:
|
||||
return gradient_decals[index]
|
||||
|
||||
var gradient_decal := Decal.new()
|
||||
gradient_decal = Decal.new()
|
||||
gradient_decal.texture_albedo = picker_texture
|
||||
gradient_decal.modulate = COLOR_SLOPE
|
||||
gradient_decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
|
||||
gradient_decal.size.y = 1000.
|
||||
gradient_decal.cull_mask = decal.cull_mask
|
||||
add_child(gradient_decal)
|
||||
|
||||
gradient_decals.push_back(gradient_decal)
|
||||
return gradient_decal
|
||||
|
||||
|
||||
func set_decal_rotation(p_rot: float) -> void:
|
||||
decal.rotation.y = p_rot
|
||||
|
||||
|
||||
func _on_picking(p_type: int, p_callback: Callable) -> void:
|
||||
picking = p_type
|
||||
picking_callback = p_callback
|
||||
update_decal()
|
||||
|
||||
|
||||
func clear_picking() -> void:
|
||||
picking = Terrain3DEditor.TOOL_MAX
|
||||
|
||||
|
||||
func is_picking() -> bool:
|
||||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
return true
|
||||
|
||||
if operation_builder and operation_builder.is_picking():
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func pick(p_global_position: Vector3) -> void:
|
||||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
var color: Color
|
||||
match picking:
|
||||
Terrain3DEditor.HEIGHT:
|
||||
color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_HEIGHT, p_global_position)
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_COLOR, p_global_position)
|
||||
Terrain3DEditor.COLOR:
|
||||
color = plugin.terrain.get_storage().get_color(p_global_position)
|
||||
_:
|
||||
push_error("Unsupported picking type: ", picking)
|
||||
return
|
||||
picking_callback.call(picking, color, p_global_position)
|
||||
picking = Terrain3DEditor.TOOL_MAX
|
||||
|
||||
elif operation_builder and operation_builder.is_picking():
|
||||
operation_builder.pick(p_global_position, plugin.terrain)
|
||||
|
||||
|
|
@ -1,282 +0,0 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
#class_name Terrain3DEditorPlugin Cannot be named until Godot #75388
|
||||
|
||||
|
||||
# Includes
|
||||
const UI: Script = preload("res://addons/terrain_3d/editor/components/ui.gd")
|
||||
const RegionGizmo: Script = preload("res://addons/terrain_3d/editor/components/region_gizmo.gd")
|
||||
const TextureDock: Script = preload("res://addons/terrain_3d/editor/components/texture_dock.gd")
|
||||
|
||||
var terrain: Terrain3D
|
||||
var nav_region: NavigationRegion3D
|
||||
|
||||
var editor: Terrain3DEditor
|
||||
var ui: Node # Terrain3DUI see Godot #75388
|
||||
var texture_dock: TextureDock
|
||||
var texture_dock_container: CustomControlContainer = CONTAINER_INSPECTOR_BOTTOM
|
||||
|
||||
var visible: bool
|
||||
var region_gizmo: RegionGizmo
|
||||
var current_region_position: Vector2
|
||||
var mouse_global_position: Vector3 = Vector3.ZERO
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
editor = Terrain3DEditor.new()
|
||||
ui = UI.new()
|
||||
ui.plugin = self
|
||||
add_child(ui)
|
||||
|
||||
texture_dock = TextureDock.new()
|
||||
texture_dock.hide()
|
||||
texture_dock.resource_changed.connect(_on_texture_dock_resource_changed)
|
||||
texture_dock.resource_inspected.connect(_on_texture_dock_resource_selected)
|
||||
texture_dock.resource_selected.connect(ui._on_setting_changed)
|
||||
|
||||
region_gizmo = RegionGizmo.new()
|
||||
|
||||
add_control_to_container(texture_dock_container, texture_dock)
|
||||
texture_dock.get_parent().visibility_changed.connect(_on_texture_dock_visibility_changed)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_control_from_container(texture_dock_container, texture_dock)
|
||||
texture_dock.queue_free()
|
||||
ui.queue_free()
|
||||
editor.free()
|
||||
|
||||
|
||||
func _handles(p_object: Object) -> bool:
|
||||
return p_object is Terrain3D or p_object is NavigationRegion3D
|
||||
|
||||
|
||||
func _edit(p_object: Object) -> void:
|
||||
if !p_object:
|
||||
_clear()
|
||||
|
||||
if p_object is Terrain3D:
|
||||
if p_object == terrain:
|
||||
return
|
||||
terrain = p_object
|
||||
editor.set_terrain(terrain)
|
||||
region_gizmo.set_node_3d(terrain)
|
||||
terrain.add_gizmo(region_gizmo)
|
||||
terrain.set_plugin(self)
|
||||
|
||||
if not terrain.texture_list_changed.is_connected(_load_textures):
|
||||
terrain.texture_list_changed.connect(_load_textures)
|
||||
_load_textures()
|
||||
if not terrain.storage_changed.is_connected(_load_storage):
|
||||
terrain.storage_changed.connect(_load_storage)
|
||||
_load_storage()
|
||||
else:
|
||||
terrain = null
|
||||
|
||||
if p_object is NavigationRegion3D:
|
||||
nav_region = p_object
|
||||
else:
|
||||
nav_region = null
|
||||
|
||||
_update_visibility()
|
||||
|
||||
|
||||
func _make_visible(p_visible: bool) -> void:
|
||||
visible = p_visible
|
||||
_update_visibility()
|
||||
|
||||
|
||||
func _update_visibility() -> void:
|
||||
ui.set_visible(visible)
|
||||
texture_dock.set_visible(visible and terrain)
|
||||
if terrain:
|
||||
update_region_grid()
|
||||
region_gizmo.set_hidden(not visible or not terrain)
|
||||
|
||||
|
||||
func _clear() -> void:
|
||||
if is_terrain_valid():
|
||||
terrain.storage_changed.disconnect(_load_storage)
|
||||
|
||||
terrain.clear_gizmos()
|
||||
terrain = null
|
||||
editor.set_terrain(null)
|
||||
|
||||
ui.clear_picking()
|
||||
|
||||
region_gizmo.clear()
|
||||
|
||||
|
||||
func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> int:
|
||||
if not is_terrain_valid():
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
## Handle mouse movement
|
||||
if p_event is InputEventMouseMotion:
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
## Get mouse location on terrain
|
||||
|
||||
# Snap terrain to current camera
|
||||
terrain.set_camera(p_viewport_camera)
|
||||
|
||||
# Detect if viewport is set to half_resolution
|
||||
# Structure is: Node3DEditorViewportContainer/Node3DEditorViewport/SubViewportContainer/SubViewport/Camera3D
|
||||
var editor_vpc: SubViewportContainer = p_viewport_camera.get_parent().get_parent()
|
||||
var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true
|
||||
|
||||
# Project 2D mouse position to 3D position and direction
|
||||
var mouse_pos: Vector2 = p_event.position if full_resolution else p_event.position/2
|
||||
var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos)
|
||||
var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos)
|
||||
|
||||
# If region tool, grab mouse position without considering height
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir)
|
||||
mouse_global_position = (camera_pos + t * camera_dir)
|
||||
else:
|
||||
# Else look for intersection with terrain
|
||||
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir)
|
||||
if intersection_point.z > 3.4e38: # double max
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
mouse_global_position = intersection_point
|
||||
|
||||
## Update decal
|
||||
ui.decal.global_position = mouse_global_position
|
||||
ui.decal.albedo_mix = 1.0
|
||||
if ui.decal_timer.is_stopped():
|
||||
ui.update_decal()
|
||||
else:
|
||||
ui.decal_timer.start()
|
||||
|
||||
## Update region highlight
|
||||
var region_size = terrain.get_storage().get_region_size()
|
||||
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
|
||||
/ (region_size * terrain.get_mesh_vertex_spacing()) ).floor()
|
||||
if current_region_position != region_position:
|
||||
current_region_position = region_position
|
||||
update_region_grid()
|
||||
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and editor.is_operating():
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
elif p_event is InputEventMouseButton:
|
||||
ui.update_decal()
|
||||
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
if p_event.is_pressed():
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If picking
|
||||
if ui.is_picking():
|
||||
ui.pick(mouse_global_position)
|
||||
if not ui.operation_builder or not ui.operation_builder.is_ready():
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If adjusting regions
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
# Skip regions that already exist or don't
|
||||
var has_region: bool = terrain.get_storage().has_region(mouse_global_position)
|
||||
var op: int = editor.get_operation()
|
||||
if ( has_region and op == Terrain3DEditor.ADD) or \
|
||||
( not has_region and op == Terrain3DEditor.SUBTRACT ):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If an automatic operation is ready to go (e.g. gradient)
|
||||
if ui.operation_builder and ui.operation_builder.is_ready():
|
||||
ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# Mouse clicked, start editing
|
||||
editor.start_operation(mouse_global_position)
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
elif editor.is_operating():
|
||||
# Mouse released, save undo data
|
||||
editor.stop_operation()
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
|
||||
func is_terrain_valid() -> bool:
|
||||
var valid: bool = false
|
||||
if is_instance_valid(terrain):
|
||||
valid = terrain.get_storage() != null
|
||||
return valid
|
||||
|
||||
|
||||
func update_texture_dock(p_args: Array) -> void:
|
||||
texture_dock.clear()
|
||||
|
||||
if is_terrain_valid() and terrain.texture_list:
|
||||
var texture_count: int = terrain.texture_list.get_texture_count()
|
||||
for i in texture_count:
|
||||
var texture: Terrain3DTexture = terrain.texture_list.get_texture(i)
|
||||
texture_dock.add_item(texture)
|
||||
|
||||
if texture_count < Terrain3DTextureList.MAX_TEXTURES:
|
||||
texture_dock.add_item()
|
||||
|
||||
|
||||
func update_region_grid() -> void:
|
||||
if !region_gizmo.get_node_3d():
|
||||
return
|
||||
|
||||
if is_terrain_valid():
|
||||
region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
|
||||
region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
|
||||
region_gizmo.region_position = current_region_position
|
||||
region_gizmo.region_size = terrain.get_storage().get_region_size() * terrain.get_mesh_vertex_spacing()
|
||||
region_gizmo.grid = terrain.get_storage().get_region_offsets()
|
||||
|
||||
terrain.update_gizmos()
|
||||
return
|
||||
|
||||
region_gizmo.show_rect = false
|
||||
region_gizmo.region_size = 1024
|
||||
region_gizmo.grid = [Vector2i.ZERO]
|
||||
|
||||
|
||||
# Signal handlers
|
||||
|
||||
|
||||
func _load_textures() -> void:
|
||||
if terrain and terrain.texture_list:
|
||||
if not terrain.texture_list.textures_changed.is_connected(update_texture_dock):
|
||||
terrain.texture_list.textures_changed.connect(update_texture_dock)
|
||||
update_texture_dock(Array())
|
||||
|
||||
|
||||
func _load_storage() -> void:
|
||||
if terrain:
|
||||
update_region_grid()
|
||||
|
||||
|
||||
func _on_texture_dock_resource_changed(texture: Resource, index: int) -> void:
|
||||
if is_terrain_valid():
|
||||
# If removing last entry and its selected, clear inspector
|
||||
if not texture and index == texture_dock.get_selected_index() and \
|
||||
texture_dock.get_selected_index() == texture_dock.entries.size() - 2:
|
||||
get_editor_interface().inspect_object(null)
|
||||
terrain.get_texture_list().set_texture(index, texture)
|
||||
call_deferred("_load_storage")
|
||||
|
||||
|
||||
func _on_texture_dock_resource_selected(texture) -> void:
|
||||
get_editor_interface().inspect_object(texture, "", true)
|
||||
|
||||
|
||||
func _on_texture_dock_visibility_changed() -> void:
|
||||
if texture_dock.get_parent() != null:
|
||||
remove_control_from_container(texture_dock_container, texture_dock)
|
||||
|
||||
if texture_dock.get_parent() == null:
|
||||
texture_dock_container = CONTAINER_INSPECTOR_BOTTOM
|
||||
if get_editor_interface().is_distraction_free_mode_enabled():
|
||||
texture_dock_container = CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT
|
||||
add_control_to_container(texture_dock_container, texture_dock)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue