Level Setup
Integrated with Trenchbroom
15
demo/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
|
||||
# Godot-specific ignores
|
||||
.import/
|
||||
export.cfg
|
||||
export_presets.cfg
|
||||
|
||||
# Imported translations (automatically generated from CSV files)
|
||||
*.translation
|
||||
|
||||
# Mono-specific ignores
|
||||
.mono/
|
||||
data_*/
|
||||
mono_crash.*.json
|
||||
@@ -1,21 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 DmitriySalnikov
|
||||
Copyright (c) 2023 func-godot
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the Software), to deal
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, andor sell
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
55
demo/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
<p align="center"><img src="https://github.com/func-godot/.github/assets/44485952/53bdc589-33e8-4a39-8707-01a5f850d155" alt="FuncGodotRanger" width="15%" />
|
||||
<img src="https://github.com/func-godot/.github/assets/44485952/b7c19218-2089-4319-a2bd-6ce4b354c1ce" alt="FuncGodot" width="80%"/></p>
|
||||
|
||||
***FuncGodot*** is a plugin for [Godot 4](https://godotengine.org/) that allows users to generate Godot scenes using the [Quake MAP file format](https://quakewiki.org/wiki/Quake_Map_Format) and [Valve Map Format](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)). Map files can be made in a variety of editors, the most commonly recommended one being [TrenchBroom](https://trenchbroom.github.io/). It is a reworking and rewrite of the [Qodot](https://github.com/QodotPlugin/Qodot) plugin for Godot 3 and 4.
|
||||
|
||||
## [Download FuncGodot!](https://github.com/func-godot/func_godot_plugin)
|
||||
|
||||
[Full documentation is available online](https://func-godot.github.io/func_godot_docs/) as well as off. [Download a standalone copy of the manual here](https://github.com/func-godot/func_godot_docs/releases/).
|
||||
|
||||
For more help or to contribute to the community, join us on the [**Official FuncGodot Discord**](https://discord.gg/eBQ7EfNZSZ)!
|
||||
|
||||
<p align="center"><img src="https://github.com/func-godot/.github/assets/44485952/0a4d2436-884e-4cee-94a8-220df3813627" alt="TrenchBroom" width="45%" />
|
||||
<img src="https://github.com/func-godot/.github/assets/44485952/25e96e49-3482-40cf-ade9-99e83c3eca7d" alt="Godot FuncGodotMap Built" width="45%"/></p>
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Godot Scene Generation
|
||||
- Supports Quake `map` and Hammer `vmf`
|
||||
- Supports Quake WAD2, Half-Life WAD3, and `lmp` palette formats
|
||||
- Meshes from `map` brush geometry
|
||||
- Materials and UVs from map texture definitions
|
||||
- Convex and concave collision shapes
|
||||
- Entity Definition Support
|
||||
- Fully customizable entities that can be defined for map editors and generated in Godot
|
||||
- Leverage the map format's classname and key value pair systems
|
||||
- Define the visual and collision properties of brush entities on a per-classname basis
|
||||
- Retrieve easy to access mesh metadata for per face material information
|
||||
- Define point entities that can be generated from node class name and script or from packed scenes
|
||||
- Generate GLB display models with correct orientation and scale for point entities in map editors with GLTF support
|
||||
- FGD (Forge Game Data) export
|
||||
- TrenchBroom Integration
|
||||
- GameConfig export
|
||||
- Brush and Face Tags
|
||||
- `model` keyword and scale expression
|
||||
- NetRadiant Custom Integration
|
||||
- Gamepack Export
|
||||
- Shader definitions
|
||||
- Customizable build options
|
||||
|
||||
## Confirmed Compatible Map Editors
|
||||
- TrenchBroom
|
||||
- Hammer
|
||||
- J.A.C.K.
|
||||
- NetRadiant Custom
|
||||
|
||||
Help us add to this list by testing out your preferred map editor and helping us come up with compatibility solutions!
|
||||
|
||||
## Credits
|
||||
|
||||
FuncGodot was created by [Hannah "EMBYR" Crawford](https://embyr.sh/), [Emberlynn Bland](https://github.com/deertears/), [Tim "RhapsodyInGeek" Maccabe](https://github.com/RhapsodyInGeek), and [Vera "sinewavey" Lux](https://github.com/sinewavey), reworked from the [Godot 4 port of Qodot](https://github.com/QodotPlugin/Qodot/tree/main) by Embyr, with contributions from members of the FuncGodot, Qodot, Godot, and Quake Mapping Communities.
|
||||
|
||||
Both plugins are based on the original [Qodot for Godot 3.5](https://github.com/QodotPlugin/qodot-plugin/) created by [Josh "Shifty" Palmer](https://twitter.com/ShiftyAxel).
|
||||
|
||||
<p align="center"><img src="https://github.com/func-godot/.github/assets/44485952/9ff9cd96-024b-4202-b4a2-611741b81609" alt="Godambler" /></p>
|
||||
@@ -1,157 +0,0 @@
|
||||

|
||||
|
||||
# Debug drawing utility for Godot
|
||||
|
||||
This is an add-on for debug drawing in 3D and for some 2D overlays, which is written in `C++` and can be used with `GDScript` or `C#`.
|
||||
|
||||
Based on my previous addon, which was developed [only for C#](https://github.com/DmitriySalnikov/godot_debug_draw_cs), and which was inspired by [Zylann's GDScript addon](https://github.com/Zylann/godot_debug_draw)
|
||||
|
||||
## [Documentation](https://dd3d.dmitriysalnikov.ru/docs/)
|
||||
|
||||
## [Godot 3 version](https://github.com/DmitriySalnikov/godot_debug_draw_3d/tree/godot_3)
|
||||
|
||||
## Support me
|
||||
|
||||
Your support adds motivation to develop my public projects.
|
||||
|
||||
<a href="https://boosty.to/dmitriysalnikov/donate"><img src="/docs/images/boosty.png" alt="Boosty" width=150px/></a>
|
||||
|
||||
<a href="#"><img src="/docs/images/USDT-TRC20.png" alt="USDT-TRC20" width=150px/></a>
|
||||
|
||||
<b>USDT-TRC20 TEw934PrsffHsAn5M63SoHYRuZo984EF6v</b>
|
||||
|
||||
## Features
|
||||
|
||||
3D:
|
||||
|
||||
* Arrow
|
||||
* Billboard opaque square
|
||||
* Box
|
||||
* Camera Frustum
|
||||
* Cylinder
|
||||
* Gizmo
|
||||
* Grid
|
||||
* Line
|
||||
* Line Path
|
||||
* Line with Arrow
|
||||
* Plane
|
||||
* Points
|
||||
* Position 3D (3 crossing axes)
|
||||
* Sphere
|
||||
* 3D Text
|
||||
|
||||
2D:
|
||||
|
||||
* **[Work in progress]**
|
||||
|
||||
Overlay:
|
||||
|
||||
* Text (with grouping and coloring)
|
||||
|
||||
Precompiled for:
|
||||
|
||||
* Windows
|
||||
* Linux (built on Ubuntu 22.04)
|
||||
* macOS (10.15+)
|
||||
* Android (5.0+)
|
||||
* iOS
|
||||
* Web (Firefox is supported by Godot 4.3+)
|
||||
|
||||
This addon supports working with several World3D and different Viewports.
|
||||
There is also a no depth test mode and other settings that can be changed for each instance.
|
||||
|
||||
This library supports double-precision builds, for more information, [see the documentation](https://dd3d.dmitriysalnikov.ru/docs/?page=md_docs_2DoublePrecision.html).
|
||||
|
||||
## [Interactive Web Demo](https://dd3d.dmitriysalnikov.ru/demo/)
|
||||
|
||||
[](https://dd3d.dmitriysalnikov.ru/demo/)
|
||||
|
||||
## Download
|
||||
|
||||
To download, use the [Godot Asset Library](https://godotengine.org/asset-library/asset/1766) or use one of the stable versions from the [GitHub Releases](https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases) page.
|
||||
|
||||
For versions prior to `1.4.5`, just download one of the `source codes` in the assets. For newer versions, download `debug-draw-3d_[version].zip`.
|
||||
|
||||
### Installation
|
||||
|
||||
* Close editor
|
||||
* Copy `addons/debug_draw_3d` to your `addons` folder, create it if the folder doesn't exist
|
||||
* Launch editor
|
||||
|
||||
## Examples
|
||||
|
||||
More examples can be found in the `examples_dd3d/` folder.
|
||||
|
||||
Simple test:
|
||||
|
||||
```gdscript
|
||||
func _process(delta: float) -> void:
|
||||
var _time = Time.get_ticks_msec() / 1000.0
|
||||
var box_pos = Vector3(0, sin(_time * 4), 0)
|
||||
var line_begin = Vector3(-1, sin(_time * 4), 0)
|
||||
var line_end = Vector3(1, cos(_time * 4), 0)
|
||||
|
||||
DebugDraw3D.draw_box(box_pos, Quaternion.IDENTITY, Vector3(1, 2, 1), Color(0, 1, 0))
|
||||
DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0))
|
||||
DebugDraw2D.set_text("Time", _time)
|
||||
DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn())
|
||||
DebugDraw2D.set_text("FPS", Engine.get_frames_per_second())
|
||||
DebugDraw2D.set_text("delta", delta)
|
||||
```
|
||||
|
||||

|
||||
|
||||
An example of using scoped configs:
|
||||
|
||||
```gdscript
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
func _ready():
|
||||
# Set the base scoped_config.
|
||||
# Each frame will be reset to these scoped values.
|
||||
DebugDraw3D.scoped_config().set_thickness(0.1).set_center_brightness(0.6)
|
||||
|
||||
func _process(delta):
|
||||
# Draw using the base scoped config.
|
||||
DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE * 2, Color.CORNFLOWER_BLUE)
|
||||
if true:
|
||||
# Create a scoped config that will exist until exiting this if.
|
||||
var _s = DebugDraw3D.new_scoped_config().set_thickness(0).set_center_brightness(0.1)
|
||||
# Draw with a thickness of 0
|
||||
DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE, Color.RED)
|
||||
# If necessary, the values inside this scope can be changed
|
||||
# even before each call to draw_*.
|
||||
_s.set_thickness(0.05)
|
||||
DebugDraw3D.draw_box(Vector3(1,0,1), Quaternion.IDENTITY, Vector3.ONE * 1, Color.BLUE_VIOLET)
|
||||
```
|
||||
|
||||

|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> If you want to use a non-standard Viewport for rendering a 3d scene, then do not forget to specify it in the scoped config!
|
||||
|
||||
## API
|
||||
|
||||
This project has a separate [documentation](https://dd3d.dmitriysalnikov.ru/docs/) page.
|
||||
|
||||
Also, a list of all functions is available in the documentation inside the editor (see `DebugDraw3D` and `DebugDraw2D`).
|
||||
|
||||

|
||||
|
||||
## Known issues and limitations
|
||||
|
||||
The text in the keys and values of a text group cannot contain multi-line strings.
|
||||
|
||||
The entire text overlay can only be placed in one corner.
|
||||
|
||||
[Frustum of Camera3D does not take into account the window size from ProjectSettings](https://github.com/godotengine/godot/issues/70362).
|
||||
|
||||
## More screenshots
|
||||
|
||||
`DebugDrawDemoScene.tscn` in editor
|
||||

|
||||
|
||||
`DebugDrawDemoScene.tscn` in play mode
|
||||

|
||||
@@ -1,153 +0,0 @@
|
||||
[configuration]
|
||||
|
||||
entry_symbol = "debug_draw_3d_library_init"
|
||||
compatibility_minimum = "4.2.2"
|
||||
reloadable = false
|
||||
|
||||
[dependencies]
|
||||
|
||||
; example.x86_64 = { "relative or absolute path to the dependency" : "the path relative to the exported project", }
|
||||
; -------------------------------------
|
||||
; debug
|
||||
|
||||
macos = { }
|
||||
windows.x86_64 = { }
|
||||
linux.x86_64 = { }
|
||||
|
||||
; by default godot is using threads
|
||||
web.wasm32.nothreads = {}
|
||||
web.wasm32 = {}
|
||||
|
||||
android.arm32 = { }
|
||||
android.arm64 = { }
|
||||
android.x86_32 = { }
|
||||
android.x86_64 = { }
|
||||
|
||||
ios = {}
|
||||
|
||||
; -------------------------------------
|
||||
; release no debug draw
|
||||
|
||||
macos.template_release = { }
|
||||
windows.template_release.x86_64 = { }
|
||||
linux.template_release.x86_64 = { }
|
||||
|
||||
web.template_release.wasm32.nothreads = { }
|
||||
web.template_release.wasm32 = { }
|
||||
|
||||
android.template_release.arm32 = { }
|
||||
android.template_release.arm64 = { }
|
||||
android.template_release.x86_32 = { }
|
||||
android.template_release.x86_64 = { }
|
||||
|
||||
ios.template_release = {}
|
||||
|
||||
; -------------------------------------
|
||||
; release forced debug draw
|
||||
|
||||
macos.template_release.forced_dd3d = { }
|
||||
windows.template_release.x86_64.forced_dd3d = { }
|
||||
linux.template_release.x86_64.forced_dd3d = { }
|
||||
|
||||
web.template_release.wasm32.nothreads.forced_dd3d = { }
|
||||
web.template_release.wasm32.forced_dd3d = { }
|
||||
|
||||
ios.template_release.forced_dd3d = {}
|
||||
|
||||
[libraries]
|
||||
|
||||
; -------------------------------------
|
||||
; debug
|
||||
|
||||
macos = "libs/libdd3d.macos.editor.universal.framework"
|
||||
windows.x86_64 = "libs/libdd3d.windows.editor.x86_64.dll"
|
||||
linux.x86_64 = "libs/libdd3d.linux.editor.x86_64.so"
|
||||
|
||||
web.wasm32.nothreads = "libs/libdd3d.web.template_debug.wasm32.wasm"
|
||||
web.wasm32 = "libs/libdd3d.web.template_debug.wasm32.threads.wasm"
|
||||
|
||||
android.arm32 = "libs/libdd3d.android.template_debug.arm32.so"
|
||||
android.arm64 = "libs/libdd3d.android.template_debug.arm64.so"
|
||||
android.x86_32 = "libs/libdd3d.android.template_debug.x86_32.so"
|
||||
android.x86_64 = "libs/libdd3d.android.template_debug.x86_64.so"
|
||||
|
||||
ios = "libs/libdd3d.ios.template_debug.universal.dylib"
|
||||
|
||||
; -------------------------------------
|
||||
; release no debug draw
|
||||
|
||||
macos.template_release = "libs/libdd3d.macos.template_release.universal.framework"
|
||||
windows.template_release.x86_64 = "libs/libdd3d.windows.template_release.x86_64.dll"
|
||||
linux.template_release.x86_64 = "libs/libdd3d.linux.template_release.x86_64.so"
|
||||
|
||||
web.template_release.wasm32.nothreads = "libs/libdd3d.web.template_release.wasm32.wasm"
|
||||
web.template_release.wasm32 = "libs/libdd3d.web.template_release.wasm32.threads.wasm"
|
||||
|
||||
android.template_release.arm32 = "libs/libdd3d.android.template_release.arm32.so"
|
||||
android.template_release.arm64 = "libs/libdd3d.android.template_release.arm64.so"
|
||||
android.template_release.x86_32 = "libs/libdd3d.android.template_release.x86_32.so"
|
||||
android.template_release.x86_64 = "libs/libdd3d.android.template_release.x86_64.so"
|
||||
|
||||
ios.template_release = "libs/libdd3d.ios.template_release.universal.dylib"
|
||||
|
||||
; -------------------------------------
|
||||
; release forced debug draw
|
||||
|
||||
macos.template_release.forced_dd3d = "libs/libdd3d.macos.template_release.universal.enabled.framework"
|
||||
windows.template_release.x86_64.forced_dd3d = "libs/libdd3d.windows.template_release.x86_64.enabled.dll"
|
||||
linux.template_release.x86_64.forced_dd3d = "libs/libdd3d.linux.template_release.x86_64.enabled.so"
|
||||
|
||||
web.template_release.wasm32.nothreads.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.enabled.wasm"
|
||||
web.template_release.wasm32.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm"
|
||||
|
||||
ios.template_release.forced_dd3d = "libs/libdd3d.ios.template_release.universal.enabled.dylib"
|
||||
|
||||
; -------------------------------------
|
||||
; DOUBLE PRECISION
|
||||
; -------------------------------------
|
||||
|
||||
; -------------------------------------
|
||||
; debug
|
||||
|
||||
macos.double = "libs/libdd3d.macos.editor.universal.double.framework"
|
||||
windows.x86_64.double = "libs/libdd3d.windows.editor.x86_64.double.dll"
|
||||
linux.x86_64.double = "libs/libdd3d.linux.editor.x86_64.double.so"
|
||||
|
||||
web.wasm32.nothreads.double = "libs/libdd3d.web.template_debug.wasm32.double.wasm"
|
||||
web.wasm32.double = "libs/libdd3d.web.template_debug.wasm32.threads.double.wasm"
|
||||
|
||||
android.arm32.double = "libs/libdd3d.android.template_debug.arm32.double.so"
|
||||
android.arm64.double = "libs/libdd3d.android.template_debug.arm64.double.so"
|
||||
android.x86_32.double = "libs/libdd3d.android.template_debug.x86_32.double.so"
|
||||
android.x86_64.double = "libs/libdd3d.android.template_debug.x86_64.double.so"
|
||||
|
||||
ios.double = "libs/libdd3d.ios.template_debug.universal.dylib"
|
||||
|
||||
; -------------------------------------
|
||||
; release no debug draw
|
||||
|
||||
macos.template_release.double = "libs/libdd3d.macos.template_release.universal.double.framework"
|
||||
windows.template_release.x86_64.double = "libs/libdd3d.windows.template_release.x86_64.double.dll"
|
||||
linux.template_release.x86_64.double = "libs/libdd3d.linux.template_release.x86_64.double.so"
|
||||
|
||||
web.template_release.wasm32.nothreads.double = "libs/libdd3d.web.template_release.wasm32.double.wasm"
|
||||
web.template_release.wasm32.double = "libs/libdd3d.web.template_release.wasm32.threads.double.wasm"
|
||||
|
||||
android.template_release.arm32.double = "libs/libdd3d.android.template_release.arm32.double.so"
|
||||
android.template_release.arm64.double = "libs/libdd3d.android.template_release.arm64.double.so"
|
||||
android.template_release.x86_32.double = "libs/libdd3d.android.template_release.x86_32.double.so"
|
||||
android.template_release.x86_64.double = "libs/libdd3d.android.template_release.x86_64.double.so"
|
||||
|
||||
ios.template_release.double = "libs/libdd3d.ios.template_release.universal.double.dylib"
|
||||
|
||||
; -------------------------------------
|
||||
; release forced debug draw
|
||||
|
||||
macos.template_release.forced_dd3d.double = "libs/libdd3d.macos.template_release.universal.enabled.double.framework"
|
||||
windows.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.windows.template_release.x86_64.enabled.double.dll"
|
||||
linux.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.linux.template_release.x86_64.enabled.double.so"
|
||||
|
||||
web.template_release.wasm32.nothreads.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.enabled.double.wasm"
|
||||
web.template_release.wasm32.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.threads.enabled.double.wasm"
|
||||
|
||||
ios.template_release.forced_dd3d.double = "libs/libdd3d.ios.template_release.universal.enabled.double.dylib"
|
||||
@@ -1 +0,0 @@
|
||||
uid://svqaxfp5kyrl
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libdd3d.macos.editor.universal.dylib</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>ru.dmitriysalnikov.dd3d</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Dmitriy Salnikov.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>macosx</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libdd3d.macos.template_release.universal.enabled.dylib</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>ru.dmitriysalnikov.dd3d</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Dmitriy Salnikov.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>macosx</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libdd3d.macos.template_release.universal.dylib</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Debug Draw 3D</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>ru.dmitriysalnikov.dd3d</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Dmitriy Salnikov.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>DTPlatformName</key>
|
||||
<string>macosx</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
39
demo/addons/func_godot/fgd/func_detail.tres
Normal file
@@ -0,0 +1,39 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://cxy7jnh6d7msn"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="1_0fsmp"]
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_c3bns"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_c03gr"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_0fsmp")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = false
|
||||
render_layers = 1
|
||||
collision_shape_type = 2
|
||||
collision_layer = 1
|
||||
collision_mask = 0
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_detail"
|
||||
description = "Static collidable geometry. Builds a StaticBody3D with a MeshInstance3D and a single concave CollisionShape3D. Does not occlude other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_c3bns"), ExtResource("2_c03gr")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
node_class = "StaticBody3D"
|
||||
name_property = ""
|
||||
39
demo/addons/func_godot/fgd/func_detail_illusionary.tres
Normal file
@@ -0,0 +1,39 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://ch3e0dix85uhb"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_ar63x"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_j7vgq"]
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="2_lhb87"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_lhb87")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = false
|
||||
render_layers = 1
|
||||
collision_shape_type = 0
|
||||
collision_layer = 1
|
||||
collision_mask = 1
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_detail_illusionary"
|
||||
description = "Static geometry with no collision. Builds a Node3D with a MeshInstance3D. Does not occlude other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_ar63x"), ExtResource("2_j7vgq")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
node_class = "Node3D"
|
||||
name_property = ""
|
||||
39
demo/addons/func_godot/fgd/func_geo.tres
Normal file
@@ -0,0 +1,39 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://b70vf4t5dc70t"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_5mwee"]
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="2_8o081"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_bp8pb"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_8o081")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = true
|
||||
render_layers = 1
|
||||
collision_shape_type = 2
|
||||
collision_layer = 1
|
||||
collision_mask = 0
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_geo"
|
||||
description = "Static collidable geometry. Builds a StaticBody3D with a MeshInstance3D, a single concave CollisionShape3D, and an OccluderInstance3D."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_5mwee"), ExtResource("2_bp8pb")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
node_class = "StaticBody3D"
|
||||
name_property = ""
|
||||
17
demo/addons/func_godot/fgd/func_godot_fgd.tres
Normal file
@@ -0,0 +1,17 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDFile" load_steps=9 format=3 uid="uid://crgpdahjaj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://drlmgulwbjwqu" path="res://addons/func_godot/src/fgd/func_godot_fgd_file.gd" id="1_axt3h"]
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_ehab8"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_7jebp"]
|
||||
[ext_resource type="Resource" uid="uid://bdji3873bg32h" path="res://addons/func_godot/fgd/worldspawn.tres" id="2_ri2rx"]
|
||||
[ext_resource type="Resource" uid="uid://b70vf4t5dc70t" path="res://addons/func_godot/fgd/func_geo.tres" id="3_7jigp"]
|
||||
[ext_resource type="Resource" uid="uid://cxy7jnh6d7msn" path="res://addons/func_godot/fgd/func_detail.tres" id="3_fqfww"]
|
||||
[ext_resource type="Resource" uid="uid://dg5x44cc7flew" path="res://addons/func_godot/fgd/func_illusionary.tres" id="4_c4ucw"]
|
||||
[ext_resource type="Resource" uid="uid://ch3e0dix85uhb" path="res://addons/func_godot/fgd/func_detail_illusionary.tres" id="5_b2q3p"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_axt3h")
|
||||
target_map_editor = 1
|
||||
fgd_name = "FuncGodot"
|
||||
base_fgd_files = Array[Resource]([])
|
||||
entity_definitions = Array[Resource]([ExtResource("1_ehab8"), ExtResource("2_7jebp"), ExtResource("2_ri2rx"), ExtResource("3_7jigp"), ExtResource("3_fqfww"), ExtResource("5_b2q3p"), ExtResource("4_c4ucw")])
|
||||
39
demo/addons/func_godot/fgd/func_illusionary.tres
Normal file
@@ -0,0 +1,39 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=4 format=3 uid="uid://dg5x44cc7flew"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://nayxb8n7see2" path="res://addons/func_godot/fgd/phong_base.tres" id="1_kv0mq"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="2_hovr4"]
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="2_uffhi"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_uffhi")
|
||||
spawn_type = 2
|
||||
origin_type = 3
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = true
|
||||
render_layers = 1
|
||||
collision_shape_type = 0
|
||||
collision_layer = 1
|
||||
collision_mask = 1
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "func_illusionary"
|
||||
description = "Static geometry with no collision. Builds a Node3D with a MeshInstance3D and an Occluder3D to aid in render culling of other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_kv0mq"), ExtResource("2_hovr4")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
node_class = "Node3D"
|
||||
name_property = ""
|
||||
28
demo/addons/func_godot/fgd/phong_base.tres
Normal file
@@ -0,0 +1,28 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDBaseClass" load_steps=2 format=3 uid="uid://nayxb8n7see2"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ck575aqs1sbrb" path="res://addons/func_godot/src/fgd/func_godot_fgd_base_class.gd" id="1_04y3n"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_04y3n")
|
||||
classname = "Phong"
|
||||
description = "Phong shading options for SolidClass geometry."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([])
|
||||
class_properties = {
|
||||
"_phong": {
|
||||
"Disabled": 0,
|
||||
"Smooth shading": 1
|
||||
},
|
||||
"_phong_angle": 89.0
|
||||
}
|
||||
class_property_descriptions = {
|
||||
"_phong": ["Phong shading", 0],
|
||||
"_phong_angle": "Phong smoothing angle"
|
||||
}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1),
|
||||
"size": AABB(-8, -8, -8, 8, 8, 8)
|
||||
}
|
||||
node_class = ""
|
||||
name_property = ""
|
||||
24
demo/addons/func_godot/fgd/vertex_merge_distance_base.tres
Normal file
@@ -0,0 +1,24 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDBaseClass" load_steps=2 format=3 uid="uid://doo4ly322b4jc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ck575aqs1sbrb" path="res://addons/func_godot/src/fgd/func_godot_fgd_base_class.gd" id="1_h3atm"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_h3atm")
|
||||
classname = "VertexMergeDistance"
|
||||
description = "Adjustable value to snap vertices to on map build. This can reduce instances of seams between polygons."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([])
|
||||
class_properties = {
|
||||
"_vertex_merge_distance": 0.008
|
||||
}
|
||||
class_property_descriptions = {
|
||||
"_vertex_merge_distance": "Adjustable value to snap vertices to on map build. This can reduce instances of seams between polygons."
|
||||
}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1),
|
||||
"size": AABB(-8, -8, -8, 8, 8, 8)
|
||||
}
|
||||
node_class = ""
|
||||
name_property = ""
|
||||
metadata/_custom_type_script = "uid://ck575aqs1sbrb"
|
||||
38
demo/addons/func_godot/fgd/worldspawn.tres
Normal file
@@ -0,0 +1,38 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotFGDSolidClass" load_steps=3 format=3 uid="uid://bdji3873bg32h"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5cow84q03m6a" path="res://addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd" id="1_62t8m"]
|
||||
[ext_resource type="Resource" uid="uid://doo4ly322b4jc" path="res://addons/func_godot/fgd/vertex_merge_distance_base.tres" id="1_h1046"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_62t8m")
|
||||
spawn_type = 0
|
||||
origin_type = 1
|
||||
build_visuals = true
|
||||
global_illumination_mode = 1
|
||||
use_in_baked_light = true
|
||||
shadow_casting_setting = 1
|
||||
build_occlusion = false
|
||||
render_layers = 1
|
||||
collision_shape_type = 1
|
||||
collision_layer = 1
|
||||
collision_mask = 0
|
||||
collision_priority = 1.0
|
||||
collision_shape_margin = 0.04
|
||||
add_textures_metadata = false
|
||||
add_vertex_metadata = false
|
||||
add_face_position_metadata = false
|
||||
add_face_normal_metadata = false
|
||||
add_collision_shape_to_face_indices_metadata = false
|
||||
add_collision_shape_face_range_metadata = false
|
||||
classname = "worldspawn"
|
||||
description = "Default static world geometry. Builds a StaticBody3D with a single MeshInstance3D and a single convex CollisionShape3D shape. Also builds Occluder3D to aid in render culling of other VisualInstance3D nodes."
|
||||
func_godot_internal = false
|
||||
base_classes = Array[Resource]([ExtResource("1_h1046")])
|
||||
class_properties = {}
|
||||
class_property_descriptions = {}
|
||||
auto_apply_to_matching_node_properties = false
|
||||
meta_properties = {
|
||||
"color": Color(0.8, 0.8, 0.8, 1)
|
||||
}
|
||||
node_class = "StaticBody3D"
|
||||
name_property = ""
|
||||
35
demo/addons/func_godot/func_godot_default_map_settings.tres
Normal file
@@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotMapSettings" load_steps=5 format=3 uid="uid://bkhxcqsquw1yg"]
|
||||
|
||||
[ext_resource type="Material" uid="uid://cvex6toty8yn7" path="res://addons/func_godot/textures/default_material.tres" id="1_8l5wm"]
|
||||
[ext_resource type="Script" uid="uid://38q6k0ctahjn" path="res://addons/func_godot/src/map/func_godot_map_settings.gd" id="1_dlf23"]
|
||||
[ext_resource type="Resource" uid="uid://crgpdahjaj" path="res://addons/func_godot/fgd/func_godot_fgd.tres" id="2_hf4oi"]
|
||||
[ext_resource type="Script" uid="uid://cij36hpqc46c" path="res://addons/func_godot/src/import/quake_wad_file.gd" id="4_576s4"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_dlf23")
|
||||
inverse_scale_factor = 32.0
|
||||
entity_fgd = ExtResource("2_hf4oi")
|
||||
entity_name_property = ""
|
||||
entity_smoothing_property = "_phong"
|
||||
entity_smoothing_angle_property = "_phong_angle"
|
||||
use_groups_hierarchy = false
|
||||
base_texture_dir = "res://textures"
|
||||
texture_file_extensions = Array[String](["png", "jpg", "jpeg", "bmp", "tga", "webp"])
|
||||
clip_texture = "special/clip"
|
||||
skip_texture = "special/skip"
|
||||
origin_texture = "special/origin"
|
||||
texture_wads = Array[ExtResource("4_576s4")]([])
|
||||
base_material_dir = ""
|
||||
material_file_extension = "tres"
|
||||
default_material = ExtResource("1_8l5wm")
|
||||
default_material_albedo_uniform = ""
|
||||
albedo_map_pattern = "%s_albedo.%s"
|
||||
normal_map_pattern = "%s_normal.%s"
|
||||
metallic_map_pattern = "%s_metallic.%s"
|
||||
roughness_map_pattern = "%s_roughness.%s"
|
||||
emission_map_pattern = "%s_emission.%s"
|
||||
ao_map_pattern = "%s_ao.%s"
|
||||
height_map_pattern = "%s_height.%s"
|
||||
orm_map_pattern = "%s_orm.%s"
|
||||
save_generated_materials = true
|
||||
uv_unwrap_texel_size = 2.0
|
||||
6
demo/addons/func_godot/func_godot_local_config.tres
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_resource type="Resource" script_class="FuncGodotLocalConfig" load_steps=2 format=3 uid="uid://bqjt7nyekxgog"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://xsjnhahhyein" path="res://addons/func_godot/src/util/func_godot_local_config.gd" id="1_g8kqj"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_g8kqj")
|
||||
@@ -0,0 +1,24 @@
|
||||
[gd_resource type="Resource" script_class="NetRadiantCustomGamePackConfig" load_steps=6 format=3 uid="uid://cv1k2e85fo2ax"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://crgpdahjaj" path="res://addons/func_godot/fgd/func_godot_fgd.tres" id="1_gct4v"]
|
||||
[ext_resource type="Script" uid="uid://dfhj3me2g5j0l" path="res://addons/func_godot/src/netradiant_custom/netradiant_custom_gamepack_config.gd" id="2_en8ro"]
|
||||
[ext_resource type="Resource" uid="uid://f5erfnvbg6b7" path="res://addons/func_godot/game_config/netradiant_custom/netradiant_custom_shader_clip.tres" id="2_w7psh"]
|
||||
[ext_resource type="Resource" uid="uid://cfhg30jclb4lw" path="res://addons/func_godot/game_config/netradiant_custom/netradiant_custom_shader_skip.tres" id="3_6gpk8"]
|
||||
[ext_resource type="Resource" uid="uid://bpnj14oaufdpt" path="res://addons/func_godot/game_config/netradiant_custom/netradiant_custom_shader_origin.tres" id="4_8rl60"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_en8ro")
|
||||
gamepack_name = "func_godot"
|
||||
game_name = "FuncGodot"
|
||||
base_game_path = ""
|
||||
fgd_file = ExtResource("1_gct4v")
|
||||
netradiant_custom_shaders = Array[Resource]([ExtResource("2_w7psh"), ExtResource("3_6gpk8"), ExtResource("4_8rl60")])
|
||||
texture_types = PackedStringArray("png", "jpg", "jpeg", "bmp", "tga")
|
||||
model_types = PackedStringArray("glb", "gltf", "obj")
|
||||
sound_types = PackedStringArray("wav", "ogg")
|
||||
default_scale = "1.0"
|
||||
clip_texture = "textures/special/clip"
|
||||
skip_texture = "textures/special/skip"
|
||||
map_type = 1
|
||||
default_build_menu_variables = {}
|
||||
default_build_menu_commands = {}
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_resource type="Resource" script_class="NetRadiantCustomShader" load_steps=2 format=3 uid="uid://f5erfnvbg6b7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dn86acprv4e86" path="res://addons/func_godot/src/netradiant_custom/netradiant_custom_shader.gd" id="1_cuylw"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_cuylw")
|
||||
texture_path = "textures/special/clip"
|
||||
shader_attributes = Array[String](["qer_trans 0.4"])
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_resource type="Resource" script_class="NetRadiantCustomShader" load_steps=2 format=3 uid="uid://bpnj14oaufdpt"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dn86acprv4e86" path="res://addons/func_godot/src/netradiant_custom/netradiant_custom_shader.gd" id="1_ah2cp"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_ah2cp")
|
||||
texture_path = "textures/special/origin"
|
||||
shader_attributes = Array[String](["qer_trans 0.4"])
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_resource type="Resource" script_class="NetRadiantCustomShader" load_steps=2 format=3 uid="uid://cfhg30jclb4lw"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dn86acprv4e86" path="res://addons/func_godot/src/netradiant_custom/netradiant_custom_shader.gd" id="1_4ja6h"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_4ja6h")
|
||||
texture_path = "textures/special/skip"
|
||||
shader_attributes = Array[String](["qer_trans 0.4"])
|
||||
@@ -0,0 +1,34 @@
|
||||
[gd_resource type="Resource" script_class="TrenchBroomGameConfig" load_steps=7 format=3 uid="uid://b44ah5b2000wa"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://crgpdahjaj" path="res://addons/func_godot/fgd/func_godot_fgd.tres" id="1_8u1vq"]
|
||||
[ext_resource type="Resource" uid="uid://b4xhdj0e16lop" path="res://addons/func_godot/game_config/trenchbroom/tb_face_tag_clip.tres" id="1_rsp20"]
|
||||
[ext_resource type="Resource" uid="uid://ca7377sfgj074" path="res://addons/func_godot/game_config/trenchbroom/tb_face_tag_skip.tres" id="2_166i2"]
|
||||
[ext_resource type="Script" uid="uid://cx44c4vnq8bt5" path="res://addons/func_godot/src/trenchbroom/trenchbroom_game_config.gd" id="2_ns6ah"]
|
||||
[ext_resource type="Resource" uid="uid://bkjxc54mmdhbo" path="res://addons/func_godot/game_config/trenchbroom/tb_face_tag_origin.tres" id="3_stisi"]
|
||||
[ext_resource type="Texture2D" uid="uid://decwujsyhj0qy" path="res://addons/func_godot/icon32.png" id="6_tex5j"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_ns6ah")
|
||||
game_name = "FuncGodot"
|
||||
icon = ExtResource("6_tex5j")
|
||||
map_formats = Array[Dictionary]([{
|
||||
"format": "Valve",
|
||||
"initialmap": "initial_valve.map"
|
||||
}, {
|
||||
"format": "Standard",
|
||||
"initialmap": "initial_standard.map"
|
||||
}, {
|
||||
"format": "Quake2",
|
||||
"initialmap": "initial_quake2.map"
|
||||
}, {
|
||||
"format": "Quake3"
|
||||
}])
|
||||
textures_root_folder = "textures"
|
||||
texture_exclusion_patterns = Array[String](["*_albedo", "*_ao", "*_emission", "*_height", "*_metallic", "*_normal", "*_orm", "*_roughness", "*_sss"])
|
||||
palette_path = "textures/palette.lmp"
|
||||
fgd_file = ExtResource("1_8u1vq")
|
||||
entity_scale = "32"
|
||||
brush_tags = Array[Resource]([])
|
||||
brushface_tags = Array[Resource]([ExtResource("1_rsp20"), ExtResource("2_166i2"), ExtResource("3_stisi")])
|
||||
default_uv_scale = Vector2(1, 1)
|
||||
game_config_version = 0
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="TrenchBroomTag" load_steps=2 format=3 uid="uid://37iduqf7tpxq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b66qdknwqpfup" path="res://addons/func_godot/src/trenchbroom/trenchbroom_tag.gd" id="1_rn13a"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_rn13a")
|
||||
tag_name = "Func"
|
||||
tag_attributes = Array[String]([])
|
||||
tag_match_type = 1
|
||||
tag_pattern = "func*"
|
||||
texture_name = ""
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="TrenchBroomTag" load_steps=2 format=3 uid="uid://co2sb1ng7cw4i"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b66qdknwqpfup" path="res://addons/func_godot/src/trenchbroom/trenchbroom_tag.gd" id="1_msqpk"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_msqpk")
|
||||
tag_name = "Trigger"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 1
|
||||
tag_pattern = "trigger*"
|
||||
texture_name = "special/trigger"
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="TrenchBroomTag" load_steps=2 format=3 uid="uid://b4xhdj0e16lop"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b66qdknwqpfup" path="res://addons/func_godot/src/trenchbroom/trenchbroom_tag.gd" id="1_7td58"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_7td58")
|
||||
tag_name = "Clip"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 0
|
||||
tag_pattern = "clip"
|
||||
texture_name = ""
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="TrenchBroomTag" load_steps=2 format=3 uid="uid://bkjxc54mmdhbo"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b66qdknwqpfup" path="res://addons/func_godot/src/trenchbroom/trenchbroom_tag.gd" id="1_enkfc"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_enkfc")
|
||||
tag_name = "Origin"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 0
|
||||
tag_pattern = "origin"
|
||||
texture_name = ""
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="TrenchBroomTag" load_steps=2 format=3 uid="uid://ca7377sfgj074"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b66qdknwqpfup" path="res://addons/func_godot/src/trenchbroom/trenchbroom_tag.gd" id="1_2teqe"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_2teqe")
|
||||
tag_name = "Skip"
|
||||
tag_attributes = Array[String](["transparent"])
|
||||
tag_match_type = 0
|
||||
tag_pattern = "skip"
|
||||
texture_name = ""
|
||||
BIN
demo/addons/func_godot/icon.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
40
demo/addons/func_godot/icon.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cp2as6ujvknyu"
|
||||
path="res://.godot/imported/icon.png-6db43b6a52df1ce3744a82f15cbdbbea.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-6db43b6a52df1ce3744a82f15cbdbbea.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
13
demo/addons/func_godot/icon.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
43
demo/addons/func_godot/icon.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bx5buvf1ydm7q"
|
||||
path="res://.godot/imported/icon.svg-99f2c56e0c1ce867c819715c68d9c120.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-99f2c56e0c1ce867c819715c68d9c120.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
BIN
demo/addons/func_godot/icon32.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
40
demo/addons/func_godot/icon32.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://decwujsyhj0qy"
|
||||
path="res://.godot/imported/icon32.png-7025e2d95a64a3066b7947e1900b4daf.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icon32.png"
|
||||
dest_files=["res://.godot/imported/icon32.png-7025e2d95a64a3066b7947e1900b4daf.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
13
demo/addons/func_godot/icons/icon_godambler.svg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
43
demo/addons/func_godot/icons/icon_godambler.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bm2kwpq18quv0"
|
||||
path="res://.godot/imported/icon_godambler.svg-a6dbba375ab2a45be046a1875b8d41e6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icons/icon_godambler.svg"
|
||||
dest_files=["res://.godot/imported/icon_godambler.svg-a6dbba375ab2a45be046a1875b8d41e6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
34
demo/addons/func_godot/icons/icon_godambler3d.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
43
demo/addons/func_godot/icons/icon_godambler3d.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dieefivfbkovw"
|
||||
path="res://.godot/imported/icon_godambler3d.svg-f7df9bfe58320474198644aa06a8f3f6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icons/icon_godambler3d.svg"
|
||||
dest_files=["res://.godot/imported/icon_godambler3d.svg-f7df9bfe58320474198644aa06a8f3f6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
13
demo/addons/func_godot/icons/icon_godot_ranger.svg
Normal file
|
After Width: | Height: | Size: 19 KiB |
43
demo/addons/func_godot/icons/icon_godot_ranger.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cfxlhjsefleff"
|
||||
path="res://.godot/imported/icon_godot_ranger.svg-8572582518f54de6403b767a923b5a92.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icons/icon_godot_ranger.svg"
|
||||
dest_files=["res://.godot/imported/icon_godot_ranger.svg-8572582518f54de6403b767a923b5a92.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
13
demo/addons/func_godot/icons/icon_godot_ranger3d.svg
Normal file
|
After Width: | Height: | Size: 19 KiB |
43
demo/addons/func_godot/icons/icon_godot_ranger3d.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://brm515f5ivx8m"
|
||||
path="res://.godot/imported/icon_godot_ranger3d.svg-a9a2c9bcf2e8b1e07a0a941a16264e98.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icons/icon_godot_ranger3d.svg"
|
||||
dest_files=["res://.godot/imported/icon_godot_ranger3d.svg-a9a2c9bcf2e8b1e07a0a941a16264e98.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
85
demo/addons/func_godot/icons/icon_quake_file.svg
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
inkscape:export-filename="/home/djrm/Projects/godot/tools/editor/icons/icon_node.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90"
|
||||
sodipodi:docname="icon_qodot_node.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="31.999999"
|
||||
inkscape:cx="9.2742768"
|
||||
inkscape:cy="5.9794586"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
inkscape:window-width="1849"
|
||||
inkscape:window-height="942"
|
||||
inkscape:window-x="2365"
|
||||
inkscape:window-y="478"
|
||||
inkscape:window-maximized="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3336"
|
||||
empspacing="4" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1036.3622)">
|
||||
<g
|
||||
transform="matrix(0.00898883,0,0,0.00898883,2.84873,1036.9616)"
|
||||
id="g8"
|
||||
style="fill:#e0e0e0;fill-opacity:0.99607843">
|
||||
<path
|
||||
sodipodi:type="inkscape:offset"
|
||||
inkscape:radius="32.247818"
|
||||
inkscape:original="M 805.49219 48.865234 L 805.49219 52 L 805.49219 70.470703 C 908.63676 125.4219 990.75756 219.02053 1030.5371 328.76562 C 1072.5343 442.84389 1069.0608 572.88678 1019.4355 684.0293 C 962.84256 813.15825 847.75434 914.65851 712.625 954.68555 C 687.95498 962.05941 662.7661 967.4194 637.26562 970.68359 C 637.37347 905.40191 636.93661 840.04476 637.35156 774.81055 C 644.27173 753.89399 667.02241 744.70589 686.79883 740.29102 C 696.25394 738.22707 705.90322 736.98982 715.58203 736.76953 C 714.41755 731.86565 718.02354 722.1913 713.58203 720.56445 L 439 720.56445 C 440.15426 725.49354 436.59893 734.98734 440.9375 736.83008 C 464.02438 738.2287 488.57642 742.59448 506.58789 758.30469 C 513.72262 764.95503 519.23908 774.34645 517.41992 784.43945 L 517.41992 970.64062 C 392.2186 952.28136 280.08237 877.51602 203.18555 778.49805 C 146.20758 705.03793 105.92922 616.34944 98.074219 523.1582 C 90.131888 410.66597 124.62307 296.10332 192.38281 205.89258 C 233.9121 150.23193 287.72076 103.61704 348.8125 70.462891 C 348.8205 63.264976 348.82683 56.067058 348.83203 48.869141 C 226.43763 103.66249 125.42782 204.32365 70.367188 326.61133 C 10.634796 457.1454 4.9961598 611.01761 54.242188 745.77539 C 100.18371 873.54309 194.5137 982.99051 313.99414 1047.4492 C 376.81007 1081.5901 446.33117 1103.3409 517.41992 1110.9785 C 517.56059 1169.4421 517.13902 1227.9564 517.62891 1286.3887 C 536.50698 1390.7196 555.38596 1495.0499 574.26367 1599.3809 C 595.24637 1494.6025 616.32947 1389.8338 637.24609 1285.0488 C 637.24709 1227.0293 637.249 1169.0098 637.25 1110.9902 C 753.86537 1099.5192 866.94739 1050.9022 953.70898 971.83203 C 1036.6427 897.08153 1094.4372 795.63679 1118.3398 686.71289 C 1142.8039 576.4452 1136.0436 458.67677 1094.3887 353.33398 C 1044.4835 224.74362 944.8252 116.42175 820.77539 56.089844 C 815.72077 53.598792 810.60447 51.234804 805.49219 48.865234 z "
|
||||
style="fill:#e0e0e0;fill-opacity:0.99607843;stroke:none;stroke-width:5;stroke-miterlimit:10;stroke-opacity:1"
|
||||
id="path6"
|
||||
d="m 347.90625,16.636719 a 32.251043,32.251043 0 0 0 -12.25,2.798828 C 205.88469,77.531491 99.412299,183.61822 41.001953,313.29102 -22.348317,451.80048 -28.265288,613.88282 23.927734,756.76758 72.647329,892.19719 172.02966,1007.4904 298.65234,1075.8125 c 58.00997,31.5205 121.31762,53.0204 186.51172,63.377 -0.002,48.987 -0.19484,98.1365 0.21875,147.4687 a 32.251043,32.251043 0 0 0 0.51367,5.4727 c 18.87811,104.3311 37.75711,208.6614 56.63477,312.9921 a 32.251043,32.251043 0 0 0 63.35156,0.5899 c 20.97874,-104.7586 42.06315,-209.5337 62.98633,-314.3516 a 32.251043,32.251043 0 0 0 0.625,-6.3125 c 8.4e-4,-48.5498 0.003,-97.1 0.004,-145.6504 112.94142,-16.8214 220.92348,-66.2925 305.84961,-143.65817 88.14404,-79.4662 149.14894,-186.66163 174.48434,-302.08984 25.7675,-116.18424 18.7794,-240.2345 -25.416,-352.06836 C 1071.4778,205.24135 966.47079,91.107321 834.92773,27.115234 829.42805,24.407001 824.10868,21.95088 819.05273,19.607422 A 32.251043,32.251043 0 0 0 773.24414,48.865234 V 52 70.470703 a 32.251043,32.251043 0 0 0 17.08594,28.460938 c 96.0021,51.145979 172.95277,138.924249 209.88872,240.824219 a 32.251043,32.251043 0 0 0 0.057,0.15039 c 39.0531,106.08119 35.7505,227.87147 -10.28517,330.97461 a 32.251043,32.251043 0 0 0 -0.0898,0.20312 C 937.26564,791.18147 829.14264,886.53883 703.4668,923.76562 a 32.251043,32.251043 0 0 0 -0.0762,0.0234 c -11.19816,3.34713 -22.50938,6.2295 -33.90429,8.67578 -0.0393,-50.21706 -0.16903,-100.33931 0.10156,-150.23828 0.86271,-0.9191 2.16446,-1.94384 4.36719,-3.27734 4.48441,-2.7148 12.01568,-5.42153 19.80273,-7.16602 7.58488,-1.65065 15.16509,-2.60516 22.5586,-2.77343 a 32.251043,32.251043 0 0 0 30.64062,-39.69141 c 2.24778,9.46588 0.68077,5.89079 1.03906,0.11719 0.17915,-2.88681 0.72749,-6.86388 -1.11718,-14.20313 -1.84468,-7.33925 -10.07914,-20.50769 -22.20508,-24.94922 a 32.251043,32.251043 0 0 0 -11.0918,-1.96679 H 439 a 32.251043,32.251043 0 0 0 -31.40234,39.58203 c -2.13044,-9.10803 -0.65038,-5.75562 -1.00782,-0.0645 -0.17902,2.85053 -0.68774,6.79348 1.02344,13.91016 1.71118,7.11668 9.00286,19.79227 20.7168,24.76758 a 32.251043,32.251043 0 0 0 10.6582,2.50781 c 20.32647,1.23139 36.93313,5.44255 46.25977,13.48242 a 32.251043,32.251043 0 0 0 -0.0762,1.9375 V 931.33789 C 384.2764,906.36086 293.26933,841.91584 228.66602,758.73438 l -0.0117,-0.0156 C 174.92035,689.43588 137.4999,606.28431 130.23242,520.66211 122.92233,416.31754 155.17118,309.12943 218.16797,225.25977 a 32.251043,32.251043 0 0 0 0.0606,-0.082 c 38.7059,-51.87653 89.03359,-95.47481 145.96484,-126.371089 a 32.251043,32.251043 0 0 0 16.86719,-28.308594 c 0.008,-7.202903 0.0143,-14.404177 0.0195,-21.605469 A 32.251043,32.251043 0 0 0 347.90625,16.636719 Z M 484.59961,781.89453 c 0.6695,0.62405 0.56402,0.47412 0.65039,0.54102 a 32.251043,32.251043 0 0 0 0,0.0449 z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
43
demo/addons/func_godot/icons/icon_quake_file.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c0464gp8lby0w"
|
||||
path="res://.godot/imported/icon_quake_file.svg-1718b9a2b5e0b124f6d72bb4c72d2ee6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icons/icon_quake_file.svg"
|
||||
dest_files=["res://.godot/imported/icon_quake_file.svg-1718b9a2b5e0b124f6d72bb4c72d2ee6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
13
demo/addons/func_godot/icons/icon_slipgate.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<!-- Created using Krita: https://krita.org -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:krita="http://krita.org/namespaces/svg/krita"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="23.04pt"
|
||||
height="23.04pt"
|
||||
viewBox="0 0 23.04 23.04">
|
||||
<defs/>
|
||||
<path id="shape0" transform="translate(3.32859367052032, 1.70718625021606)" fill="#ffffff" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 2.17125L5.76 4.3425L11.52 2.17125L5.76 0Z" sodipodi:nodetypes="ccccc"/><path id="shape1" transform="translate(3.49734362007209, 4.23843624987037)" fill="#ffffff" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 0L0 12.96L5.4 15.12L5.49 2.16Z" sodipodi:nodetypes="ccccc"/><path id="shape01" transform="translate(9.22078101093241, 4.42406124416546)" fill="#ffffff" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 1.8L5.0175 3.78563L10.4906 1.98563L5.085 0Z" sodipodi:nodetypes="ccccc"/><path id="shape11" transform="translate(9.38953096048418, 6.58406124416546)" fill="#ffffff" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 0L0.03375 1.97438L4.69125 3.94875L4.6575 1.97438Z" sodipodi:nodetypes="ccccc"/><path id="shape011" transform="matrix(-1 0 0 1 19.705781131254 6.76968624399261)" fill="#ffffff" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0.03375 0L0 1.97438L5.39438 3.76313L5.42813 1.78875Z" sodipodi:nodetypes="ccccc"/><path id="shape02" transform="translate(8.855151086652, 15.2240587499568)" fill="#ffffff" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 1.8L5.0175 3.78563L10.4906 1.98563L5.085 0Z" sodipodi:nodetypes="ccccc"/><path id="shape12" transform="translate(9.02390103620378, 17.3840587499568)" fill="#ffffff" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 0L0.03375 1.97438L4.69125 3.94875L4.6575 1.97438Z" sodipodi:nodetypes="ccccc"/><path id="shape012" transform="matrix(-1 0 0 1 19.3401512069735 17.5696837497839)" fill="#ffffff" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0.03375 0L0 1.97438L5.39438 3.76313L5.42813 1.78875Z" sodipodi:nodetypes="ccccc"/><path id="shape2" transform="translate(9.05484388076874, 8.91843624987036)" fill="#ffffff" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 7.92L0 0L5.90625 2.16L5.805 5.76Z" sodipodi:nodetypes="ccccc"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
43
demo/addons/func_godot/icons/icon_slipgate.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bw74kacajcaxb"
|
||||
path="res://.godot/imported/icon_slipgate.svg-f42668b28b92f93c031f56d95dfcf5a6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icons/icon_slipgate.svg"
|
||||
dest_files=["res://.godot/imported/icon_slipgate.svg-f42668b28b92f93c031f56d95dfcf5a6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
13
demo/addons/func_godot/icons/icon_slipgate3d.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<!-- Created using Krita: https://krita.org -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:krita="http://krita.org/namespaces/svg/krita"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="23.04pt"
|
||||
height="23.04pt"
|
||||
viewBox="0 0 23.04 23.04">
|
||||
<defs/>
|
||||
<path id="shape0" transform="translate(3.32859367052032, 1.70718625021606)" fill="#fc7f7f" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 2.17125L5.76 4.3425L11.52 2.17125L5.76 0Z" sodipodi:nodetypes="ccccc"/><path id="shape1" transform="translate(3.49734362007209, 4.23843624987037)" fill="#fc7f7f" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 0L0 12.96L5.4 15.12L5.49 2.16Z" sodipodi:nodetypes="ccccc"/><path id="shape01" transform="translate(9.22078101093241, 4.42406124416546)" fill="#fc7f7f" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 1.8L5.0175 3.78563L10.4906 1.98563L5.085 0Z" sodipodi:nodetypes="ccccc"/><path id="shape11" transform="translate(9.38953096048418, 6.58406124416546)" fill="#fc7f7f" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 0L0.03375 1.97438L4.69125 3.94875L4.6575 1.97438Z" sodipodi:nodetypes="ccccc"/><path id="shape011" transform="matrix(-1 0 0 1 19.705781131254 6.76968624399261)" fill="#fc7f7f" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0.03375 0L0 1.97438L5.39438 3.76313L5.42813 1.78875Z" sodipodi:nodetypes="ccccc"/><path id="shape02" transform="translate(8.855151086652, 15.2240587499568)" fill="#fc7f7f" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 1.8L5.0175 3.78563L10.4906 1.98563L5.085 0Z" sodipodi:nodetypes="ccccc"/><path id="shape12" transform="translate(9.02390103620378, 17.3840587499568)" fill="#fc7f7f" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 0L0.03375 1.97438L4.69125 3.94875L4.6575 1.97438Z" sodipodi:nodetypes="ccccc"/><path id="shape012" transform="matrix(-1 0 0 1 19.3401512069735 17.5696837497839)" fill="#fc7f7f" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0.03375 0L0 1.97438L5.39438 3.76313L5.42813 1.78875Z" sodipodi:nodetypes="ccccc"/><path id="shape2" transform="translate(9.05484388076874, 8.91843624987036)" fill="#fc7f7f" fill-rule="evenodd" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0 7.92L0 0L5.90625 2.16L5.805 5.76Z" sodipodi:nodetypes="ccccc"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
43
demo/addons/func_godot/icons/icon_slipgate3d.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cfvririkaa4tv"
|
||||
path="res://.godot/imported/icon_slipgate3d.svg-f125bef6ff5aa79b5fe3f232a083425e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/icons/icon_slipgate3d.svg"
|
||||
dest_files=["res://.godot/imported/icon_slipgate3d.svg-f125bef6ff5aa79b5fe3f232a083425e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
BIN
demo/addons/func_godot/palette.lmp
Normal file
14
demo/addons/func_godot/palette.lmp.import
Normal file
@@ -0,0 +1,14 @@
|
||||
[remap]
|
||||
|
||||
importer="func_godot.palette"
|
||||
type="Resource"
|
||||
uid="uid://drgnc41yfybr"
|
||||
path="res://.godot/imported/palette.lmp-138c33f2ac0cab3ad6373e7c0425cf00.tres"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/func_godot/palette.lmp"
|
||||
dest_files=["res://.godot/imported/palette.lmp-138c33f2ac0cab3ad6373e7c0425cf00.tres"]
|
||||
|
||||
[params]
|
||||
|
||||
7
demo/addons/func_godot/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="FuncGodot"
|
||||
description="Quake .map and Half-Life .vmf file support for Godot."
|
||||
author="Josh Palmer, Hannah Crawford, Emberlynn Bland, Tim Maccabe, Vera Lux"
|
||||
version="2025.9"
|
||||
script="src/func_godot_plugin.gd"
|
||||
190
demo/addons/func_godot/src/core/data.gd
Normal file
@@ -0,0 +1,190 @@
|
||||
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
||||
class_name FuncGodotData
|
||||
## Container that holds various data structs to be used in the [FuncGodotMap] build process.
|
||||
##
|
||||
## FuncGodot utilizes multiple custom data structs to hold information parsed from the map file
|
||||
## and read and modified by the other core build classes.
|
||||
## All data structs extend from [RefCounted], therefore all data is passed by reference.
|
||||
## [br][br]
|
||||
## [FuncGodotData.FaceData][br]
|
||||
## [FuncGodotData.BrushData][br]
|
||||
## [FuncGodotData.PatchData][br]
|
||||
## [FuncGodotData.GroupData][br]
|
||||
## [FuncGodotData.EntityData][br]
|
||||
|
||||
## Data struct representing both a single map plane and a mesh face. Generated during parsing by plane definitions in the map file,
|
||||
## it is further modified and utilized during the geo generation stage to create the final entity meshes.
|
||||
class FaceData extends RefCounted:
|
||||
## Vertex array for the face. Only populated in combination with other faces, as a result of planar intersections.
|
||||
var vertices: PackedVector3Array = []
|
||||
## Index array for the face. Used in ArrayMesh creation.
|
||||
var indices: PackedInt32Array = []
|
||||
## Vertex normal array for the face.
|
||||
## By default, set to the planar normal, which results in flat shading. May be modified to adjust shading.
|
||||
var normals: PackedVector3Array = []
|
||||
## Tangent data for the face.
|
||||
var tangents: PackedFloat32Array = []
|
||||
## Local path to the texture without the extension, relative to the FuncGodotMap node's settings' base texture directory.
|
||||
var texture: String
|
||||
## UV transform data generated during the parsing stage. Used for both Standard and Valve 220 UV formats,
|
||||
## though rotation is not applied to the transform when using Valve 220.
|
||||
var uv: Transform2D
|
||||
## Raw vector data provided by the Valve 220 format during parsing. It is used to calculate rotations.
|
||||
## The presence of this data determines how face UVs and tangents are calculated.
|
||||
var uv_axes: PackedVector3Array = []
|
||||
## Raw plane data parsed from the map file using the id Tech coordinate system.
|
||||
var plane: Plane
|
||||
|
||||
## Returns the average position of all vertices in the face. Only valid when the face has at least one vertex.
|
||||
func get_centroid() -> Vector3:
|
||||
return FuncGodotUtil.op_vec3_avg(vertices)
|
||||
|
||||
## Returns an arbitrary coplanar direction to use for winding the face.
|
||||
## Only valid when the face has at least two vertices.
|
||||
func get_basis() -> Vector3:
|
||||
if vertices.size() < 2:
|
||||
push_error("Cannot get winding basis without at least 2 vertices!")
|
||||
return Vector3.ZERO
|
||||
return (vertices[1] - vertices[0]).normalized()
|
||||
|
||||
## Prepares the face for OpenGL triangle winding order.
|
||||
## Sorts the vertex array in-place by angle from the centroid.
|
||||
func wind() -> void:
|
||||
var centroid: Vector3 = get_centroid()
|
||||
var u_axis: Vector3 = get_basis()
|
||||
var v_axis: Vector3 = u_axis.cross(plane.normal).normalized()
|
||||
var cmp_winding_angle: Callable = (
|
||||
func(a: Vector3, b: Vector3) -> bool:
|
||||
var dir_a: Vector3 = a - centroid
|
||||
var dir_b: Vector3 = b - centroid
|
||||
var angle_a: float = atan2(dir_a.dot(v_axis), dir_a.dot(u_axis))
|
||||
var angle_b: float = atan2(dir_b.dot(v_axis), dir_b.dot(u_axis))
|
||||
return angle_a < angle_b
|
||||
)
|
||||
|
||||
var _vertices: Array[Vector3]
|
||||
_vertices.assign(vertices)
|
||||
_vertices.sort_custom(cmp_winding_angle)
|
||||
vertices = _vertices
|
||||
|
||||
## Repopulate the [member indices] array to create a triangle fan.
|
||||
## The face must be properly wound for the resulting indices to be valid.
|
||||
func index_vertices() -> void:
|
||||
var tri_count: int = vertices.size() - 2
|
||||
indices.resize(tri_count * 3)
|
||||
var index: int = 0
|
||||
for i in tri_count:
|
||||
indices[index] = 0
|
||||
indices[index + 1] = i + 1
|
||||
indices[index + 2] = i + 2
|
||||
index += 3
|
||||
|
||||
## Data struct representing a single map format brush. It is largely meant as a container for [FuncGodotData.FaceData] data.
|
||||
class BrushData extends RefCounted:
|
||||
## Raw plane data parsed from the map file using the id Tech coordinate system.
|
||||
var planes: Array[Plane]
|
||||
## Collection of [FuncGodotData.FaceData].
|
||||
var faces: Array[FaceData]
|
||||
## [code]true[/code] if this brush is completely covered in the [i]Origin[/i] texture defined in [FuncGodotMapSettings].
|
||||
## Determined during [FuncGodotParser] and utilized during [FuncGodotGeometryGenerator].
|
||||
var origin: bool = false
|
||||
|
||||
## Data struct representing a patch def entity.
|
||||
class PatchData extends RefCounted:
|
||||
## Local path to the texture without the extension, relative to the FuncGodotMap node's settings' base texture directory.
|
||||
var texture: String
|
||||
var size: PackedInt32Array
|
||||
var points: PackedVector3Array
|
||||
var uvs: PackedVector2Array
|
||||
|
||||
## Data struct representing a TrenchBroom Group, TrenchBroom Layer, or Valve VisGroup.
|
||||
## Generated during the parsing stage and utilized during both parsing and entity assembly stages.
|
||||
class GroupData extends RefCounted:
|
||||
enum GroupType { GROUP, LAYER, }
|
||||
## Defines whether the group is a Group or a Layer. Currently only determines the name of the group.
|
||||
var type: GroupType = GroupType.GROUP
|
||||
## Group ID retrieved from the map file. Utilized during the parsing and entity assembly stages to determine
|
||||
## which entities belong to which groups as well as which groups are children of other groups.
|
||||
var id: int
|
||||
## Generated during the parsing stage using the format of type_id_name, eg: group_2_Arkham.
|
||||
var name: String
|
||||
## ID of the parent group data, used to determine which group data is this group's parent.
|
||||
var parent_id: int = -1
|
||||
## Pointer to another group data that this group is a child of.
|
||||
var parent: GroupData = null
|
||||
## Pointer to generated Node3D representing this group in the SceneTree.
|
||||
var node: Node3D = null
|
||||
## If true, erases all entities assigned to this group and then the group itself at the end of the parsing stage, preventing those entities from being generated into nodes.
|
||||
## Can be set in TrenchBroom on layers using the "omit layer" option.
|
||||
var omit: bool = false
|
||||
|
||||
## Data struct representing a map format entity.
|
||||
class EntityData extends RefCounted:
|
||||
## All of the entity's key value pairs from the map file, retrieved during parsing.
|
||||
## The func_godot_properties dictionary generated at the end of entity assembly is derived from this.
|
||||
var properties: Dictionary = {}
|
||||
## The entity's brush data collected during the parsing stage. If the entity's FGD resource cannot be found,
|
||||
## the presence of a single brush determines this entity to be a Solid Entity.
|
||||
var brushes: Array[BrushData] = []
|
||||
## The entity's patch def data collected during the parsing stage. If the entity's FGD resource cannot be found,
|
||||
## the presence of a single patch def determines this entity to be a Solid Entity.
|
||||
var patches: Array[PatchData] = []
|
||||
## Pointer to the group data this entity belongs to.
|
||||
var group: GroupData = null
|
||||
## The entity's FGD resource, determined by matching the classname properties of each.
|
||||
## This can only be a [FuncGodotFGDSolidClass], [FuncGodotFGDPointClass], or [FuncGodotFGDModelPointClass].
|
||||
var definition: FuncGodotFGDEntityClass = null
|
||||
## Mesh resource generated during the geometry generation stage and applied during the entity assembly stage.
|
||||
var mesh: ArrayMesh = null
|
||||
## MeshInstance3D node generated during the entity assembly stage.
|
||||
var mesh_instance: MeshInstance3D = null
|
||||
## Optional mesh metadata compiled during the geometry generation stage, used to determine face information from collision.
|
||||
var mesh_metadata: Dictionary = {}
|
||||
## A collection of collision shape resources generated during the geometry generation stage and applied during the entity assembly stage.
|
||||
var shapes: Array[Shape3D] = []
|
||||
## A collection of [CollisionShape3D] nodes generated during the entity assembly stage. Each node corresponds to a shape in the [member shapes] array.
|
||||
var collision_shapes: Array[CollisionShape3D] = []
|
||||
## [OccluderInstance3D] node generated during the entity assembly stage using the [member mesh] resource.
|
||||
var occluder_instance: OccluderInstance3D = null
|
||||
## True global position of the entity's generated node that the mesh's vertices are offset by during the geometry generation stage.
|
||||
var origin: Vector3 = Vector3.ZERO
|
||||
|
||||
## Checks the entity's FGD resource definition, returning whether the Solid Class has a [MeshInstance3D] built for it.
|
||||
func is_visual() -> bool:
|
||||
return (definition
|
||||
and definition is FuncGodotFGDSolidClass
|
||||
and definition.build_visuals)
|
||||
|
||||
## Checks the entity's FGD resource definition, returning whether the Solid Class CollisionShapeType is set to Convex.
|
||||
func is_collision_convex() -> bool:
|
||||
return (definition
|
||||
and definition is FuncGodotFGDSolidClass
|
||||
and definition.collision_shape_type == FuncGodotFGDSolidClass.CollisionShapeType.CONVEX
|
||||
)
|
||||
|
||||
## Checks the entity's FGD resource definition, returning whether the Solid Class CollisionShapeType is set to Concave.
|
||||
func is_collision_concave() -> bool:
|
||||
return (definition
|
||||
and definition is FuncGodotFGDSolidClass
|
||||
and definition.collision_shape_type == FuncGodotFGDSolidClass.CollisionShapeType.CONCAVE
|
||||
)
|
||||
|
||||
## Determines if the entity's mesh should be processed for normal smoothing.
|
||||
## The smoothing property can be retrieved from [member FuncGodotMapSettings.entity_smoothing_property].
|
||||
func is_smooth_shaded(smoothing_property: String = "_phong") -> bool:
|
||||
return properties.get(smoothing_property, "0").to_int()
|
||||
|
||||
## Retrieves the entity's smoothing angle to determine if the face should be smoothed.
|
||||
## The smoothing angle property can be retrieved from [member FuncGodotMapSettings.entity_smoothing_angle_property].
|
||||
func get_smoothing_angle(smoothing_angle_property: String = "_phong_angle") -> float:
|
||||
return properties.get(smoothing_angle_property, "89.0").to_float()
|
||||
|
||||
class VertexGroupData:
|
||||
## Faces this vertex appears in.
|
||||
var faces: Array[FaceData]
|
||||
## Index within the associated face for this vertex.
|
||||
var face_indices: PackedInt32Array
|
||||
|
||||
class ParseData:
|
||||
var entities: Array[EntityData] = []
|
||||
var groups: Array[GroupData] = []
|
||||
1
demo/addons/func_godot/src/core/data.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cqye8dehq4c7q
|
||||
451
demo/addons/func_godot/src/core/entity_assembler.gd
Normal file
@@ -0,0 +1,451 @@
|
||||
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
||||
class_name FuncGodotEntityAssembler extends RefCounted
|
||||
## Entity assembly class that is instantiated by a [FuncGodotMap] node.
|
||||
|
||||
const _SIGNATURE: String = "[ENT]"
|
||||
|
||||
# Namespacing
|
||||
const _GroupData := FuncGodotData.GroupData
|
||||
const _EntityData := FuncGodotData.EntityData
|
||||
|
||||
# Class members
|
||||
## [FuncGodotMapSettings] provided by the [FuncGodotMap] during the build process.
|
||||
var map_settings: FuncGodotMapSettings = null
|
||||
## [enum FuncGodotMap.BuildFlags] that may affect the build process provided by the [FuncGodotMap].
|
||||
var build_flags: int = 0
|
||||
|
||||
# Signals
|
||||
## Emitted when a step in the entity assembly process is completed.
|
||||
## It is connected to [method FuncGodotUtil.print_profile_info] method if [member FuncGodotMap.build_flags] SHOW_PROFILE_INFO flag is set.
|
||||
signal declare_step(step: String)
|
||||
|
||||
func _init(settings: FuncGodotMapSettings) -> void:
|
||||
map_settings = settings
|
||||
|
||||
## Attempts to retrieve a [Script] via class name, to allow for [GDScript] class instantiation.
|
||||
static func get_script_by_class_name(name_of_class: String) -> Script:
|
||||
if ResourceLoader.exists(name_of_class, "Script"):
|
||||
return load(name_of_class) as Script
|
||||
for global_class in ProjectSettings.get_global_class_list():
|
||||
var found_name_of_class : String = global_class["class"]
|
||||
var found_path : String = global_class["path"]
|
||||
if found_name_of_class == name_of_class:
|
||||
return load(found_path) as Script
|
||||
return null
|
||||
|
||||
## Generates a [Node3D] for a group's [SceneTree] representation and links the new [Node3D] to that group.
|
||||
func generate_group_node(group_data: _GroupData) -> Node3D:
|
||||
var group_node := Node3D.new()
|
||||
group_node.name = group_data.name
|
||||
group_data.node = group_node
|
||||
return group_node
|
||||
|
||||
## Generates and assembles a new [Node] based upon processed [FuncGodotData.EntityData]. Depending upon provided data,
|
||||
## additional [MeshInstance3D], [CollisionShape3D], and [OccluderInstance3D] nodes may also be generated.
|
||||
func generate_solid_entity_node(node: Node, node_name: String, data: _EntityData, definition: FuncGodotFGDSolidClass) -> Node:
|
||||
if definition.spawn_type == FuncGodotFGDSolidClass.SpawnType.MERGE_WORLDSPAWN:
|
||||
return null
|
||||
|
||||
if definition.node_class != "":
|
||||
if ClassDB.class_exists(definition.node_class):
|
||||
node = ClassDB.instantiate(definition.node_class)
|
||||
else:
|
||||
var script: Script = get_script_by_class_name(definition.node_class)
|
||||
if script is GDScript:
|
||||
node = (script as GDScript).new()
|
||||
else:
|
||||
node = Node3D.new()
|
||||
|
||||
node.name = node_name
|
||||
node_name = node_name.trim_suffix(definition.classname).trim_suffix("_")
|
||||
var properties: Dictionary = data.properties
|
||||
|
||||
# Mesh Instance generation
|
||||
if data.mesh:
|
||||
var mesh_instance := MeshInstance3D.new()
|
||||
mesh_instance.name = node_name + "_mesh_instance"
|
||||
mesh_instance.mesh = data.mesh
|
||||
mesh_instance.gi_mode = GeometryInstance3D.GI_MODE_DISABLED
|
||||
if definition.global_illumination_mode:
|
||||
mesh_instance.gi_mode = definition.global_illumination_mode
|
||||
mesh_instance.cast_shadow = definition.shadow_casting_setting
|
||||
mesh_instance.layers = definition.render_layers
|
||||
node.add_child(mesh_instance)
|
||||
data.mesh_instance = mesh_instance
|
||||
|
||||
# Occluder generation
|
||||
if definition.build_occlusion and data.mesh:
|
||||
var verts: PackedVector3Array = []
|
||||
var indices: PackedInt32Array = []
|
||||
var index: int = 0
|
||||
for surf_idx in range(data.mesh.get_surface_count()):
|
||||
var vert_count: int = verts.size()
|
||||
var surf_array: Array = data.mesh.surface_get_arrays(surf_idx)
|
||||
verts.append_array(surf_array[Mesh.ARRAY_VERTEX])
|
||||
indices.resize(indices.size() + surf_array[Mesh.ARRAY_INDEX].size())
|
||||
for new_index in surf_array[Mesh.ARRAY_INDEX]:
|
||||
indices[index] = (new_index + vert_count)
|
||||
index += 1
|
||||
|
||||
var occluder := ArrayOccluder3D.new()
|
||||
occluder.set_arrays(verts, indices)
|
||||
var occluder_instance := OccluderInstance3D.new()
|
||||
occluder_instance.name = node_name + "_occluder_instance"
|
||||
occluder_instance.occluder = occluder
|
||||
node.add_child(occluder_instance)
|
||||
data.occluder_instance = occluder_instance
|
||||
|
||||
if not (build_flags & FuncGodotMap.BuildFlags.DISABLE_SMOOTHING) and data.is_smooth_shaded(map_settings.entity_smoothing_property):
|
||||
mesh_instance.mesh = FuncGodotUtil.smooth_mesh_by_angle(data.mesh, data.get_smoothing_angle(map_settings.entity_smoothing_angle_property))
|
||||
|
||||
# Collision generation
|
||||
if data.shapes.size() and node is CollisionObject3D:
|
||||
node.collision_layer = definition.collision_layer
|
||||
node.collision_mask = definition.collision_mask
|
||||
node.collision_priority = definition.collision_priority
|
||||
|
||||
var shape_to_face_array : Array[PackedInt32Array] = []
|
||||
if data.mesh_metadata.has('shape_to_face_array'):
|
||||
shape_to_face_array = data.mesh_metadata['shape_to_face_array']
|
||||
data.mesh_metadata.erase('shape_to_face_array')
|
||||
|
||||
# Generate CollisionShape3D nodes and apply shapes
|
||||
var face_index_metadata : Dictionary[String, PackedInt32Array] = {}
|
||||
for i in data.shapes.size():
|
||||
var shape := data.shapes[i]
|
||||
var collision_shape := CollisionShape3D.new()
|
||||
if definition.collision_shape_type == FuncGodotFGDSolidClass.CollisionShapeType.CONCAVE:
|
||||
collision_shape.name = node_name + "_collision_shape"
|
||||
else:
|
||||
collision_shape.name = node_name + "_brush_%s_collision_shape" % i
|
||||
collision_shape.shape = shape
|
||||
collision_shape.shape.margin = definition.collision_shape_margin
|
||||
collision_shape.owner = node.owner
|
||||
node.add_child(collision_shape)
|
||||
data.collision_shapes.append(collision_shape)
|
||||
if shape_to_face_array.size() > i:
|
||||
face_index_metadata[collision_shape.name] = shape_to_face_array[i]
|
||||
|
||||
if definition.add_collision_shape_to_face_indices_metadata:
|
||||
data.mesh_metadata['collision_shape_to_face_indices_map'] = face_index_metadata
|
||||
|
||||
if "position" in node:
|
||||
if node.position is Vector3:
|
||||
node.position = FuncGodotUtil.id_to_opengl(data.origin) * map_settings.scale_factor
|
||||
|
||||
if not data.mesh_metadata.is_empty():
|
||||
node.set_meta("func_godot_mesh_data", data.mesh_metadata)
|
||||
|
||||
return node
|
||||
|
||||
## Generates and assembles a new [Node] or [PackedScene] based upon processed [FuncGodotData.EntityData].
|
||||
func generate_point_entity_node(node: Node, node_name: String, properties: Dictionary, definition: FuncGodotFGDPointClass) -> Node:
|
||||
var classname: String = properties["classname"]
|
||||
|
||||
if definition.scene_file:
|
||||
var flag: PackedScene.GenEditState = PackedScene.GEN_EDIT_STATE_DISABLED
|
||||
if Engine.is_editor_hint():
|
||||
flag = PackedScene.GEN_EDIT_STATE_INSTANCE
|
||||
node = definition.scene_file.instantiate(flag)
|
||||
elif definition.node_class != "":
|
||||
if ClassDB.class_exists(definition.node_class):
|
||||
node = ClassDB.instantiate(definition.node_class)
|
||||
else:
|
||||
var script: Script = get_script_by_class_name(definition.node_class)
|
||||
if script is GDScript:
|
||||
node = (script as GDScript).new()
|
||||
else:
|
||||
node = Node3D.new()
|
||||
|
||||
node.name = node_name
|
||||
|
||||
if "rotation_degrees" in node and definition.apply_rotation_on_map_build:
|
||||
var angles := Vector3.ZERO
|
||||
if "angles" in properties or "mangle" in properties:
|
||||
var key := "angles" if "angles" in properties else "mangle"
|
||||
var angles_raw = properties[key]
|
||||
if not angles_raw is Vector3:
|
||||
angles_raw = angles_raw.split_floats(' ')
|
||||
if angles_raw.size() > 2:
|
||||
angles = Vector3(-angles_raw[0], angles_raw[1], -angles_raw[2])
|
||||
if key == "mangle":
|
||||
if definition.classname.begins_with("light"):
|
||||
angles = Vector3(angles_raw[1], angles_raw[0], -angles_raw[2])
|
||||
elif definition.classname == "info_intermission":
|
||||
angles = Vector3(angles_raw[0], angles_raw[1], -angles_raw[2])
|
||||
else:
|
||||
push_error("Invalid vector format for \"" + key + "\" in entity \"" + classname + "\"")
|
||||
elif "angle" in properties:
|
||||
var angle = properties["angle"]
|
||||
if not angle is float:
|
||||
angle = float(angle)
|
||||
angles.y += angle
|
||||
angles.y += 180
|
||||
node.rotation_degrees = angles
|
||||
|
||||
if "scale" in node and definition.apply_scale_on_map_build:
|
||||
if "scale" in properties:
|
||||
var scale_prop: Variant = properties["scale"]
|
||||
if typeof(scale_prop) == TYPE_STRING:
|
||||
var scale_arr: PackedStringArray = (scale_prop as String).split(" ")
|
||||
match scale_arr.size():
|
||||
1: scale_prop = scale_arr[0].to_float()
|
||||
3: scale_prop = Vector3(scale_arr[1].to_float(), scale_arr[2].to_float(), scale_arr[0].to_float())
|
||||
2: scale_prop = Vector2(scale_arr[0].to_float(), scale_arr[0].to_float())
|
||||
if typeof(scale_prop) == TYPE_FLOAT or typeof(scale_prop) == TYPE_INT:
|
||||
node.scale *= scale_prop as float
|
||||
elif node.scale is Vector3:
|
||||
if typeof(scale_prop) == TYPE_VECTOR3 or typeof(scale_prop) == TYPE_VECTOR3I:
|
||||
node.scale *= scale_prop as Vector3
|
||||
elif node.scale is Vector2:
|
||||
if typeof(scale_prop) == TYPE_VECTOR2 or typeof(scale_prop) == TYPE_VECTOR2I:
|
||||
node.scale *= scale_prop as Vector2
|
||||
|
||||
if "origin" in properties:
|
||||
var origin_vec: Vector3 = Vector3.ZERO
|
||||
var origin_comps: PackedFloat64Array = properties['origin'].split_floats(' ')
|
||||
if origin_comps.size() > 2:
|
||||
origin_vec = Vector3(origin_comps[1], origin_comps[2], origin_comps[0])
|
||||
else:
|
||||
push_error("Invalid vector format for \"origin\" in " + node_name)
|
||||
if "position" in node:
|
||||
if node.position is Vector3:
|
||||
node.position = origin_vec * map_settings.scale_factor
|
||||
elif node.position is Vector2:
|
||||
node.position = Vector2(origin_vec.z, -origin_vec.y)
|
||||
|
||||
return node
|
||||
|
||||
## Converts the [String] values of the entity data's [code]properties[/code] [Dictionary] to various [Variant] formats
|
||||
## based upon the [FuncGodotFGDEntity]'s class properties, then attempts to send those properties to a [code]func_godot_properties[/code] [Dictionary]
|
||||
## and an [code]_func_godot_apply_properties(properties: Dictionary)[/code] method on the node. A deferred call to [code]_func_godot_build_complete()[/code] is also made.
|
||||
func apply_entity_properties(node: Node, data: _EntityData) -> void:
|
||||
var properties: Dictionary = data.properties
|
||||
|
||||
if data.definition:
|
||||
var def := data.definition
|
||||
for property in properties:
|
||||
var prop_string = properties[property]
|
||||
if property in def.class_properties:
|
||||
var prop_default: Variant = def.class_properties[property]
|
||||
|
||||
match typeof(prop_default):
|
||||
TYPE_INT:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_FLOAT:
|
||||
properties[property] = prop_string.to_float()
|
||||
TYPE_BOOL:
|
||||
properties[property] = bool(prop_string.to_int())
|
||||
TYPE_VECTOR3:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 2:
|
||||
properties[property] = Vector3(prop_comps[0], prop_comps[1], prop_comps[2])
|
||||
else:
|
||||
push_error("Invalid Vector3 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR3I:
|
||||
var prop_vec: Vector3i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 2:
|
||||
for i in 3:
|
||||
prop_vec[i] = prop_comps[i].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector3i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_COLOR:
|
||||
var prop_color: Color = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 2:
|
||||
prop_color.r8 = prop_comps[0].to_int()
|
||||
prop_color.g8 = prop_comps[1].to_int()
|
||||
prop_color.b8 = prop_comps[2].to_int()
|
||||
prop_color.a = 1.0
|
||||
else:
|
||||
push_error("Invalid Color format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_color
|
||||
TYPE_DICTIONARY:
|
||||
var prop_desc = def.class_property_descriptions[property]
|
||||
if prop_desc is Array and prop_desc.size() > 1 and prop_desc[1] is int:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_ARRAY:
|
||||
properties[property] = prop_string.to_int()
|
||||
TYPE_VECTOR2:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 1:
|
||||
properties[property] = Vector2(prop_comps[0], prop_comps[1])
|
||||
else:
|
||||
push_error("Invalid Vector2 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR2I:
|
||||
var prop_vec: Vector2i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 1:
|
||||
for i in 2:
|
||||
prop_vec[i] = prop_comps[i].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector2i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_VECTOR4:
|
||||
var prop_comps: PackedFloat64Array = prop_string.split_floats(" ")
|
||||
if prop_comps.size() > 3:
|
||||
properties[property] = Vector4(prop_comps[0], prop_comps[1], prop_comps[2], prop_comps[3])
|
||||
else:
|
||||
push_error("Invalid Vector4 format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_default
|
||||
TYPE_VECTOR4I:
|
||||
var prop_vec: Vector4i = prop_default
|
||||
var prop_comps: PackedStringArray = prop_string.split(" ")
|
||||
if prop_comps.size() > 3:
|
||||
for i in 4:
|
||||
prop_vec[i] = prop_comps[i].to_int()
|
||||
else:
|
||||
push_error("Invalid Vector4i format for \'" + property + "\' in entity \'" + def.classname + "\': " + prop_string)
|
||||
properties[property] = prop_vec
|
||||
TYPE_STRING_NAME:
|
||||
properties[property] = StringName(prop_string)
|
||||
TYPE_NODE_PATH:
|
||||
properties[property] = prop_string
|
||||
TYPE_OBJECT:
|
||||
properties[property] = prop_string
|
||||
|
||||
# Assign properties not defined with defaults from the entity definition
|
||||
for property in def.class_properties:
|
||||
if not property in properties:
|
||||
var prop_default: Variant = def.class_properties[property]
|
||||
# Flags
|
||||
if prop_default is Array:
|
||||
var prop_flags_sum := 0
|
||||
for prop_flag in prop_default:
|
||||
if prop_flag is Array and prop_flag.size() > 2:
|
||||
if prop_flag[2] and prop_flag[1] is int:
|
||||
prop_flags_sum += prop_flag[1]
|
||||
properties[property] = prop_flags_sum
|
||||
# Choices
|
||||
elif prop_default is Dictionary:
|
||||
var prop_desc = def.class_property_descriptions.get(property, "")
|
||||
if prop_desc is Array and prop_desc.size() > 1 and (prop_desc[1] is int or prop_desc[1] is String):
|
||||
properties[property] = prop_desc[1]
|
||||
elif prop_default.size():
|
||||
properties[property] = prop_default[prop_default.keys().front()]
|
||||
else:
|
||||
properties[property] = 0
|
||||
# Materials, Shaders, and Sounds
|
||||
elif prop_default is Resource:
|
||||
properties[property] = prop_default.resource_path
|
||||
# Target Destination and Target Source
|
||||
elif prop_default is NodePath or prop_default is Object or prop_default == null:
|
||||
properties[property] = ""
|
||||
# Everything else
|
||||
else:
|
||||
properties[property] = prop_default
|
||||
|
||||
if def.auto_apply_to_matching_node_properties:
|
||||
for property in properties:
|
||||
if property in node:
|
||||
if typeof(node.get(property)) == typeof(properties[property]):
|
||||
node.set(property, properties[property])
|
||||
else:
|
||||
push_error("Entity %s property \'%s\' type mismatch with matching generated node property." % [node.name, property])
|
||||
|
||||
if "func_godot_properties" in node:
|
||||
node.func_godot_properties = properties
|
||||
|
||||
if node.has_method("_func_godot_apply_properties"):
|
||||
node.call("_func_godot_apply_properties", properties)
|
||||
|
||||
if node.has_method("_func_godot_build_complete"):
|
||||
node.call_deferred("_func_godot_build_complete")
|
||||
|
||||
## Generate a [Node] from [FuncGodotData.EntityData]. The returned node value can be [code]null[/code],
|
||||
## in the case of [FuncGodotFGDSolidClass] entities with no [FuncGodotData.BrushData] entries.
|
||||
func generate_entity_node(entity_data: _EntityData, entity_index: int) -> Node:
|
||||
var node: Node = null
|
||||
var node_name: String = "entity_%s" % entity_index
|
||||
var properties: Dictionary = entity_data.properties
|
||||
var entity_def: FuncGodotFGDEntityClass = entity_data.definition
|
||||
|
||||
if "classname" in entity_data.properties:
|
||||
var classname: String = properties["classname"]
|
||||
|
||||
node_name += "_" + properties["classname"]
|
||||
var default_point_def := FuncGodotFGDPointClass.new()
|
||||
var default_solid_def := FuncGodotFGDSolidClass.new()
|
||||
default_solid_def.collision_shape_type = FuncGodotFGDSolidClass.CollisionShapeType.NONE
|
||||
|
||||
if entity_def:
|
||||
var name_prop: String
|
||||
if entity_def.name_property in properties:
|
||||
name_prop = str(properties[entity_def.name_property])
|
||||
elif map_settings.entity_name_property in properties:
|
||||
name_prop = str(properties[map_settings.entity_name_property])
|
||||
if not name_prop.is_empty():
|
||||
node_name = "entity_" + name_prop
|
||||
|
||||
if entity_def is FuncGodotFGDSolidClass:
|
||||
node = generate_solid_entity_node(node, node_name, entity_data, entity_def)
|
||||
elif entity_def is FuncGodotFGDPointClass:
|
||||
node = generate_point_entity_node(node, node_name, properties, entity_def)
|
||||
else:
|
||||
push_error("Invalid entity definition for \"" + node_name + "\". Entity definition must be Solid Class or Point Class.")
|
||||
node = generate_point_entity_node(node, node_name, properties, default_point_def)
|
||||
|
||||
if node and entity_def.script_class:
|
||||
node.set_script(entity_def.script_class)
|
||||
else:
|
||||
push_error("No entity definition found for \"" + node_name + "\"")
|
||||
if entity_data.brushes.size():
|
||||
node = generate_solid_entity_node(node, node_name, entity_data, default_solid_def)
|
||||
else:
|
||||
node = generate_point_entity_node(node, node_name, properties, default_point_def)
|
||||
|
||||
return node
|
||||
|
||||
## Main entity assembly process called by [FuncGodotMap]. Generates and sorts group nodes in the [SceneTree] first,
|
||||
## then generates and assembles [Node]s based upon the provided [FuncGodotData.EntityData] and adds them to the [SceneTree].
|
||||
func build(map_node: FuncGodotMap, entities: Array[_EntityData], groups: Array[_GroupData]) -> void:
|
||||
var scene_root := map_node.get_tree().edited_scene_root if map_node.get_tree() else map_node
|
||||
build_flags = map_node.build_flags
|
||||
|
||||
if map_settings.use_groups_hierarchy:
|
||||
declare_step.emit("Generating %s groups" % groups.size())
|
||||
# Generate group nodes
|
||||
for group in groups:
|
||||
group.node = generate_group_node(group)
|
||||
# Sort hierarchy and add them to the map
|
||||
for group in groups:
|
||||
if group.parent_id < 0:
|
||||
map_node.add_child(group.node)
|
||||
group.node.owner = scene_root
|
||||
else:
|
||||
for parent in groups:
|
||||
if group.parent_id == parent.id:
|
||||
parent.node.add_child(group.node)
|
||||
group.node.owner = scene_root
|
||||
declare_step.emit("Groups generation and sorting complete")
|
||||
|
||||
declare_step.emit("Assembling %s entities" % entities.size())
|
||||
var entity_node: Node = null
|
||||
for entity_index in entities.size():
|
||||
var entity_data : _EntityData = entities[entity_index]
|
||||
entity_node = generate_entity_node(entity_data, entity_index)
|
||||
if entity_node:
|
||||
if not map_settings.use_groups_hierarchy or not entity_data.group:
|
||||
map_node.add_child(entity_node)
|
||||
if entity_index == 0:
|
||||
map_node.move_child(entity_node, 0)
|
||||
elif map_settings.use_groups_hierarchy:
|
||||
for group in groups:
|
||||
if entity_data.group.id == group.id:
|
||||
group.node.add_child(entity_node)
|
||||
|
||||
entity_node.owner = scene_root
|
||||
if entity_data.mesh_instance:
|
||||
entity_data.mesh_instance.owner = scene_root
|
||||
for shape in entity_data.collision_shapes:
|
||||
if shape:
|
||||
shape.owner = scene_root
|
||||
if entity_data.occluder_instance:
|
||||
entity_data.occluder_instance.owner = scene_root
|
||||
|
||||
apply_entity_properties(entity_node, entity_data)
|
||||
declare_step.emit("Entity assembly and property application complete")
|
||||
1
demo/addons/func_godot/src/core/entity_assembler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dh73tfvwp7kr6
|
||||
567
demo/addons/func_godot/src/core/geometry_generator.gd
Normal file
@@ -0,0 +1,567 @@
|
||||
@icon("res://addons/func_godot/icons/icon_slipgate.svg")
|
||||
class_name FuncGodotGeometryGenerator extends RefCounted
|
||||
## Geometry generation class that is instantiated by a [FuncGodotMap] node.
|
||||
|
||||
const _SIGNATURE: String = "[GEO]"
|
||||
|
||||
# Namespacing
|
||||
const _VERTEX_EPSILON := FuncGodotUtil._VERTEX_EPSILON
|
||||
const _VERTEX_EPSILON2 := _VERTEX_EPSILON * _VERTEX_EPSILON
|
||||
|
||||
const _HYPERPLANE_SIZE := 65355.0
|
||||
|
||||
const _OriginType := FuncGodotFGDSolidClass.OriginType
|
||||
|
||||
const _GroupData := FuncGodotData.GroupData
|
||||
const _EntityData := FuncGodotData.EntityData
|
||||
const _BrushData := FuncGodotData.BrushData
|
||||
const _PatchData := FuncGodotData.PatchData
|
||||
const _FaceData := FuncGodotData.FaceData
|
||||
const _VertexGroupData := FuncGodotData.VertexGroupData
|
||||
|
||||
# Class members
|
||||
var map_settings: FuncGodotMapSettings = null
|
||||
var entity_data: Array[_EntityData]
|
||||
var texture_materials: Dictionary[String, Material]
|
||||
var texture_sizes: Dictionary[String, Vector2]
|
||||
|
||||
# Signals
|
||||
|
||||
## Emitted when beginning a new step of the generation process.
|
||||
signal declare_step(step: String)
|
||||
|
||||
func _init(settings: FuncGodotMapSettings = null) -> void:
|
||||
map_settings = settings
|
||||
|
||||
#region TOOLS
|
||||
func is_skip(face: _FaceData) -> bool:
|
||||
return FuncGodotUtil.is_skip(face.texture, map_settings)
|
||||
|
||||
func is_clip(face: _FaceData) -> bool:
|
||||
return FuncGodotUtil.is_clip(face.texture, map_settings)
|
||||
|
||||
func is_origin(face: _FaceData) -> bool:
|
||||
return FuncGodotUtil.is_origin(face.texture, map_settings)
|
||||
|
||||
#endregion
|
||||
|
||||
#region PATCHES
|
||||
func sample_bezier_curve(controls: Array[Vector3], t: float) -> Vector3:
|
||||
var points: Array[Vector3] = controls.duplicate()
|
||||
for i in controls.size():
|
||||
for j in controls.size() - 1 - i:
|
||||
points[j] = points[j].lerp(points[j + 1], t)
|
||||
return points[0]
|
||||
|
||||
func sample_bezier_surface(controls: Array[Vector3], width: int, height: int, u: float, v: float) -> Vector3:
|
||||
var curve: Array[Vector3] = []
|
||||
for x in range(width):
|
||||
var col: Array[Vector3] = []
|
||||
for y in range(height):
|
||||
var idx := y * width + x
|
||||
col.append(controls[idx])
|
||||
curve.append(sample_bezier_curve(col, v))
|
||||
return sample_bezier_curve(curve, u)
|
||||
|
||||
# Generate patch triangle indices
|
||||
func get_triangle_indices(width: int, height: int) -> Array[int]:
|
||||
var indices: Array[int] = []
|
||||
if width < 2 or height < 2:
|
||||
return indices
|
||||
|
||||
for row in range(height - 1):
|
||||
for col in range(width - 1):
|
||||
## First triangle of the square; top left, top right, bottom left
|
||||
indices.append(col + row * width)
|
||||
indices.append((col + 1) + row * width)
|
||||
indices.append(col + (row + 1) * width)
|
||||
|
||||
## Second triangle of the square; top right, bottom right, bottom left
|
||||
indices.append((col + 1) + row * width)
|
||||
indices.append((col + 1) + (row + 1) * width)
|
||||
indices.append(col + (row + 1) * width)
|
||||
return indices
|
||||
|
||||
func create_patch_mesh(data: Array[_PatchData], mesh: Mesh):
|
||||
return
|
||||
|
||||
#endregion
|
||||
|
||||
#region BRUSHES
|
||||
func generate_base_winding(plane: Plane) -> PackedVector3Array:
|
||||
var up := Vector3.UP
|
||||
if abs(plane.normal.dot(up)) > 0.9:
|
||||
up = Vector3.RIGHT
|
||||
|
||||
var right: Vector3 = plane.normal.cross(up).normalized()
|
||||
var forward: Vector3 = right.cross(plane.normal).normalized()
|
||||
var centroid: Vector3 = plane.get_center()
|
||||
|
||||
# construct oversized square on the plane to clip against
|
||||
var winding := PackedVector3Array()
|
||||
winding.append(centroid + (right * _HYPERPLANE_SIZE) + (forward * _HYPERPLANE_SIZE))
|
||||
winding.append(centroid + (right * -_HYPERPLANE_SIZE) + (forward * _HYPERPLANE_SIZE))
|
||||
winding.append(centroid + (right * -_HYPERPLANE_SIZE) + (forward * -_HYPERPLANE_SIZE))
|
||||
winding.append(centroid + (right * _HYPERPLANE_SIZE) + (forward * -_HYPERPLANE_SIZE))
|
||||
return winding
|
||||
|
||||
func generate_face_vertices(brush: _BrushData, face_index: int, vertex_merge_distance: float = 0.0) -> PackedVector3Array:
|
||||
var plane: Plane = brush.faces[face_index].plane
|
||||
|
||||
# Generate initial square polygon to clip other planes against
|
||||
var winding: PackedVector3Array = generate_base_winding(plane)
|
||||
|
||||
for other_face_index in brush.faces.size():
|
||||
if other_face_index == face_index:
|
||||
continue
|
||||
|
||||
# NOTE: This may need to be recentered to the origin, then moved back to the correct face position
|
||||
# This problem may arise from floating point inaccuracy, given a large enough initial brush
|
||||
winding = Geometry3D.clip_polygon(winding, brush.faces[other_face_index].plane)
|
||||
if winding.is_empty():
|
||||
break
|
||||
|
||||
# Reduce seams between vertices
|
||||
for i in winding.size():
|
||||
winding.set(i, winding.get(i).snappedf(vertex_merge_distance))
|
||||
|
||||
return winding
|
||||
|
||||
func generate_brush_vertices(entity_index: int, brush_index: int) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
var brush: _BrushData = entity.brushes[brush_index]
|
||||
var vertex_merge_distance: float = entity.properties.get(map_settings.vertex_merge_distance_property, 0.0) as float
|
||||
|
||||
for face_index in brush.faces.size():
|
||||
var face: _FaceData = brush.faces[face_index]
|
||||
face.vertices = generate_face_vertices(brush, face_index, vertex_merge_distance)
|
||||
|
||||
face.normals.resize(face.vertices.size())
|
||||
face.normals.fill(face.plane.normal)
|
||||
|
||||
var tangent: PackedFloat32Array = FuncGodotUtil.get_face_tangent(face)
|
||||
|
||||
# convert into OpenGL coordinates
|
||||
for i in face.vertices.size():
|
||||
face.tangents.append(tangent[1]) # Y
|
||||
face.tangents.append(tangent[2]) # Z
|
||||
face.tangents.append(tangent[0]) # X
|
||||
face.tangents.append(tangent[3]) # W
|
||||
return
|
||||
|
||||
func generate_entity_vertices(entity_index: int) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
for brush_index in entity.brushes.size():
|
||||
generate_brush_vertices(entity_index, brush_index)
|
||||
|
||||
func determine_entity_origins(entity_index: int) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
var origin_type := _OriginType.BRUSH
|
||||
|
||||
if entity.definition is not FuncGodotFGDSolidClass:
|
||||
if entity.brushes.is_empty():
|
||||
return
|
||||
else:
|
||||
origin_type = entity.definition.origin_type
|
||||
|
||||
if entity_index == 0:
|
||||
entity.origin = Vector3.ZERO
|
||||
return
|
||||
|
||||
var entity_mins: Vector3 = Vector3.INF
|
||||
var entity_maxs: Vector3 = Vector3.INF
|
||||
var origin_mins: Vector3 = Vector3.INF
|
||||
var origin_maxs: Vector3 = -Vector3.INF
|
||||
|
||||
for brush in entity.brushes:
|
||||
for face in brush.faces:
|
||||
for vertex in face.vertices:
|
||||
if entity_mins != Vector3.INF:
|
||||
entity_mins = entity_mins.min(vertex)
|
||||
else:
|
||||
entity_mins = vertex
|
||||
if entity_maxs != Vector3.INF:
|
||||
entity_maxs = entity_maxs.max(vertex)
|
||||
else:
|
||||
entity_maxs = vertex
|
||||
|
||||
if brush.origin:
|
||||
if origin_mins != Vector3.INF:
|
||||
origin_mins = origin_mins.min(vertex)
|
||||
else:
|
||||
origin_mins = vertex
|
||||
if origin_maxs != Vector3.INF:
|
||||
origin_maxs = origin_maxs.max(vertex)
|
||||
else:
|
||||
origin_maxs = vertex
|
||||
|
||||
# Default origin type is BOUNDS_CENTER
|
||||
if entity_maxs != Vector3.INF and entity_mins != Vector3.INF:
|
||||
entity.origin = entity_maxs - ((entity_maxs - entity_mins) * 0.5)
|
||||
|
||||
if origin_type != _OriginType.BOUNDS_CENTER and entity.brushes.size() > 0:
|
||||
match origin_type:
|
||||
_OriginType.ABSOLUTE, _OriginType.RELATIVE:
|
||||
if "origin" in entity.properties:
|
||||
var origin_comps: PackedFloat64Array = entity.properties["origin"].split_floats(" ")
|
||||
if origin_comps.size() > 2:
|
||||
if entity.origin_type == _OriginType.ABSOLUTE:
|
||||
entity.origin = Vector3(origin_comps[0], origin_comps[1], origin_comps[2])
|
||||
else: # _OriginType.RELATIVE
|
||||
entity.origin += Vector3(origin_comps[0], origin_comps[1], origin_comps[2])
|
||||
|
||||
_OriginType.BRUSH:
|
||||
if origin_mins != Vector3.INF:
|
||||
entity.origin = origin_maxs - ((origin_maxs - origin_mins) * 0.5)
|
||||
|
||||
_OriginType.BOUNDS_MINS:
|
||||
entity.origin = entity_mins
|
||||
|
||||
_OriginType.BOUNDS_MAXS:
|
||||
entity.origin = entity_maxs
|
||||
|
||||
_OriginType.AVERAGED:
|
||||
entity.origin = Vector3.ZERO
|
||||
var vertices: PackedVector3Array
|
||||
for brush in entity.brushes:
|
||||
for face in brush.faces:
|
||||
vertices.append_array(face.vertices)
|
||||
entity.origin = FuncGodotUtil.op_vec3_avg(vertices)
|
||||
|
||||
func wind_entity_faces(entity_index: int) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
for brush in entity.brushes:
|
||||
for face in brush.faces:
|
||||
# Faces should already be wound from the new generation process, but this should be tested further first.
|
||||
face.wind()
|
||||
face.index_vertices()
|
||||
|
||||
func smooth_entity_vertices(entity_index: int) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
if not entity.is_smooth_shaded(map_settings.entity_smoothing_property):
|
||||
return
|
||||
|
||||
var smoothing_angle: float = deg_to_rad(entity.get_smoothing_angle(map_settings.entity_smoothing_angle_property))
|
||||
var vertex_map: Dictionary[Vector3, _VertexGroupData] = {}
|
||||
|
||||
# Group vertices by position and build map. NOTE: Vector3 keys can suffer from floating point precision.
|
||||
# However, the vertex position should have already been snapped to _VERTEX_EPSILON.
|
||||
for brush in entity.brushes:
|
||||
for face in brush.faces:
|
||||
for i in face.vertices.size():
|
||||
var pos := face.vertices[i].snappedf(_VERTEX_EPSILON)
|
||||
|
||||
if not vertex_map.has(pos):
|
||||
vertex_map[pos] = _VertexGroupData.new()
|
||||
|
||||
var data := vertex_map[pos]
|
||||
data.faces.append(face)
|
||||
data.face_indices.append(i)
|
||||
|
||||
var smoothed_normals: PackedVector3Array
|
||||
|
||||
for vertex_group in vertex_map.values():
|
||||
if vertex_group.faces.size() <= 1:
|
||||
continue
|
||||
|
||||
# Collect final normals in a temporary arrays
|
||||
# These cannot be applied until all original normals have been checked.
|
||||
smoothed_normals = []
|
||||
|
||||
for i in vertex_group.faces.size():
|
||||
var this_face: _FaceData = vertex_group.faces[i]
|
||||
var this_index: int = vertex_group.face_indices[i]
|
||||
var this_normal: Vector3 = this_face.normals[this_index]
|
||||
var average_normal: Vector3 = this_normal
|
||||
|
||||
for j in vertex_group.faces.size():
|
||||
# Skip this face
|
||||
if i == j:
|
||||
continue
|
||||
|
||||
var other_face: _FaceData = vertex_group.faces[j]
|
||||
var other_index: int = vertex_group.face_indices[j]
|
||||
var other_normal: Vector3 = other_face.normals[other_index]
|
||||
|
||||
if this_normal.angle_to(other_normal) <= smoothing_angle:
|
||||
average_normal += other_normal
|
||||
|
||||
# Store the averaged normal
|
||||
smoothed_normals.append(average_normal.normalized())
|
||||
|
||||
# Apply smoothed normals back to face data
|
||||
for i in vertex_group.faces.size():
|
||||
var face: _FaceData = vertex_group.faces[i]
|
||||
var index: int = vertex_group.face_indices[i]
|
||||
face.normals[index] = smoothed_normals[i]
|
||||
return
|
||||
|
||||
#endregion
|
||||
|
||||
func generate_entity_surfaces(entity_index: int) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
|
||||
# Don't build for non-solid classes or solids without any brushes.
|
||||
if not entity or entity.brushes.is_empty():
|
||||
return
|
||||
|
||||
var def: FuncGodotFGDSolidClass
|
||||
if entity.definition is not FuncGodotFGDSolidClass:
|
||||
def = FuncGodotFGDSolidClass.new()
|
||||
else:
|
||||
def = entity.definition
|
||||
|
||||
var op_entity_ogl_xf: Callable = func(v: Vector3) -> Vector3:
|
||||
return (FuncGodotUtil.id_to_opengl(v - entity.origin) * map_settings.scale_factor)
|
||||
|
||||
# Surface groupings <texture_name, Array[Face]>
|
||||
var surfaces: Dictionary[String, Array] = {}
|
||||
|
||||
# Metadata
|
||||
var current_metadata_index: int = 0
|
||||
var texture_names_metadata: Array[StringName] = []
|
||||
var textures_metadata: PackedInt32Array = []
|
||||
var vertices_metadata: PackedVector3Array = []
|
||||
var normals_metadata: PackedVector3Array = []
|
||||
var positions_metadata: PackedVector3Array = []
|
||||
var shape_to_face_metadata: Array[PackedInt32Array] = []
|
||||
var face_index_metadata_map: Dictionary[_FaceData, PackedInt32Array] = {}
|
||||
|
||||
# Arrange faces by surface texture
|
||||
for brush in entity.brushes:
|
||||
for face in brush.faces:
|
||||
if is_skip(face) or is_origin(face):
|
||||
continue
|
||||
|
||||
if not surfaces.has(face.texture):
|
||||
surfaces[face.texture] = []
|
||||
surfaces[face.texture].append(face)
|
||||
|
||||
# Cache order for consistency when rebuilding
|
||||
var textures: Array[String] = surfaces.keys()
|
||||
|
||||
# Output mesh data
|
||||
var mesh := ArrayMesh.new()
|
||||
var mesh_arrays: Array[Array] = []
|
||||
var build_concave: bool = entity.is_collision_concave()
|
||||
var concave_vertices: PackedVector3Array
|
||||
|
||||
# Iteration variables
|
||||
var arrays: Array
|
||||
var faces: Array
|
||||
|
||||
# MULTISURFACE SCOPE BEGIN
|
||||
for texture_name in textures:
|
||||
# SURFACE SCOPE BEGIN
|
||||
faces = surfaces[texture_name]
|
||||
|
||||
# Get texture index for metadata
|
||||
var tex_index: int = texture_names_metadata.size()
|
||||
if def.add_textures_metadata:
|
||||
texture_names_metadata.append(texture_name)
|
||||
|
||||
# Prepare new array
|
||||
arrays = Array()
|
||||
arrays.resize(ArrayMesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array()
|
||||
arrays[Mesh.ARRAY_NORMAL] = PackedVector3Array()
|
||||
arrays[Mesh.ARRAY_TANGENT] = PackedFloat32Array()
|
||||
arrays[Mesh.ARRAY_TEX_UV] = PackedVector2Array()
|
||||
arrays[Mesh.ARRAY_INDEX] = PackedInt32Array()
|
||||
|
||||
# Begin fresh index offset for this subarray
|
||||
var index_offset: int = 0
|
||||
|
||||
for face in faces:
|
||||
# FACE SCOPE BEGIN
|
||||
|
||||
# Reject invalid faces
|
||||
if face.vertices.size() < 3 or is_skip(face) or is_origin(face):
|
||||
continue
|
||||
|
||||
# Create trimesh points regardless of texture
|
||||
if build_concave:
|
||||
var tris: PackedVector3Array
|
||||
tris.resize(face.indices.size())
|
||||
|
||||
# Add triangles from face indices directly
|
||||
# TODO: This can possibly be merged with the below loop in a clever way
|
||||
for i in face.indices.size():
|
||||
tris[i] = op_entity_ogl_xf.call(face.vertices[face.indices[i]])
|
||||
|
||||
concave_vertices.append_array(tris)
|
||||
|
||||
# Do not generate visuals for clip textures
|
||||
if is_clip(face):
|
||||
continue
|
||||
|
||||
# Handle metadata for this face
|
||||
# Add metadata per triangle rather than per face to keep consistent metadata
|
||||
var num_tris = face.indices.size() / 3
|
||||
if def.add_textures_metadata:
|
||||
var tex_array: Array[int] = []
|
||||
tex_array.resize(num_tris)
|
||||
tex_array.fill(tex_index)
|
||||
textures_metadata.append_array(tex_array)
|
||||
if def.add_face_normal_metadata:
|
||||
var normal_array: Array[Vector3] = []
|
||||
normal_array.resize(num_tris)
|
||||
normal_array.fill(FuncGodotUtil.id_to_opengl(face.plane.normal))
|
||||
normals_metadata.append_array(normal_array)
|
||||
if def.add_face_position_metadata:
|
||||
for i in num_tris:
|
||||
var triangle_indices: Array[int] = []
|
||||
var triangle_vertices: Array[Vector3] = []
|
||||
triangle_indices.assign(face.indices.slice(i * 3, i * 3 + 3))
|
||||
triangle_vertices.assign(triangle_indices.map(func(idx : int) -> Vector3: return face.vertices[idx]))
|
||||
var position := FuncGodotUtil.op_vec3_avg(triangle_vertices)
|
||||
positions_metadata.append(op_entity_ogl_xf.call(position))
|
||||
if def.add_vertex_metadata:
|
||||
for i in face.indices:
|
||||
vertices_metadata.append(op_entity_ogl_xf.call(face.vertices[i]))
|
||||
if def.add_collision_shape_to_face_indices_metadata:
|
||||
face_index_metadata_map[face] = PackedInt32Array(range(current_metadata_index, current_metadata_index + num_tris))
|
||||
current_metadata_index += num_tris
|
||||
|
||||
# Append face data to surface array
|
||||
for i in face.vertices.size():
|
||||
# TODO: Mesh metadata may be generated here.
|
||||
var v: Vector3 = face.vertices[i]
|
||||
arrays[ArrayMesh.ARRAY_VERTEX].append(op_entity_ogl_xf.call(v))
|
||||
arrays[ArrayMesh.ARRAY_NORMAL].append(FuncGodotUtil.id_to_opengl(face.normals[i]))
|
||||
var tx_sz: Vector2 = texture_sizes.get(face.texture, Vector2.ONE * map_settings.inverse_scale_factor)
|
||||
arrays[ArrayMesh.ARRAY_TEX_UV].append(FuncGodotUtil.get_face_vertex_uv(v, face, tx_sz))
|
||||
|
||||
for j in 4:
|
||||
arrays[ArrayMesh.ARRAY_TANGENT].append(face.tangents[(i * 4) + j])
|
||||
|
||||
# Create offset indices for the visual mesh
|
||||
var op_shift_index: Callable = (func(a: int) -> int: return a + index_offset)
|
||||
arrays[ArrayMesh.ARRAY_INDEX].append_array(Array(face.indices).map(op_shift_index))
|
||||
|
||||
index_offset += face.vertices.size()
|
||||
|
||||
# FACE SCOPE END
|
||||
|
||||
if FuncGodotUtil.filter_face(texture_name, map_settings):
|
||||
continue
|
||||
|
||||
mesh_arrays.append(arrays)
|
||||
|
||||
# SURFACE SCOPE END
|
||||
|
||||
# MULTISURFACE SCOPE END
|
||||
textures.erase(map_settings.clip_texture)
|
||||
|
||||
if def.build_visuals:
|
||||
# Build mesh
|
||||
for array_index in mesh_arrays.size():
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_arrays[array_index])
|
||||
mesh.surface_set_name(array_index, textures[array_index])
|
||||
mesh.surface_set_material(array_index, texture_materials[textures[array_index]])
|
||||
|
||||
# Apply mesh metadata
|
||||
if def.add_textures_metadata:
|
||||
entity.mesh_metadata["texture_names"] = texture_names_metadata
|
||||
entity.mesh_metadata["textures"] = textures_metadata
|
||||
if def.add_vertex_metadata:
|
||||
entity.mesh_metadata["vertices"] = vertices_metadata
|
||||
if def.add_face_normal_metadata:
|
||||
entity.mesh_metadata["normals"] = normals_metadata
|
||||
if def.add_face_position_metadata:
|
||||
entity.mesh_metadata["positions"] = positions_metadata
|
||||
|
||||
entity.mesh = mesh
|
||||
|
||||
# Clear up unusued memory
|
||||
arrays = []
|
||||
surfaces = {}
|
||||
|
||||
if entity.is_collision_convex():
|
||||
var sh: ConvexPolygonShape3D
|
||||
for b in entity.brushes:
|
||||
if b.planes.is_empty() or b.origin:
|
||||
continue
|
||||
|
||||
var points := Array(Geometry3D.compute_convex_mesh_points(b.planes)).map(op_entity_ogl_xf)
|
||||
if points.is_empty():
|
||||
continue
|
||||
|
||||
sh = ConvexPolygonShape3D.new()
|
||||
sh.points = points
|
||||
entity.shapes.append(sh)
|
||||
|
||||
if def.add_collision_shape_to_face_indices_metadata:
|
||||
# convex collision has one shape per brush, so collect the
|
||||
# indices for this brush's faces
|
||||
var face_indices_array : PackedInt32Array = []
|
||||
for face in b.faces:
|
||||
if face_index_metadata_map.has(face):
|
||||
face_indices_array.append_array(face_index_metadata_map[face])
|
||||
shape_to_face_metadata.append(face_indices_array)
|
||||
|
||||
elif build_concave and concave_vertices.size():
|
||||
var sh := ConcavePolygonShape3D.new()
|
||||
sh.set_faces(concave_vertices)
|
||||
entity.shapes.append(sh)
|
||||
|
||||
if def.add_collision_shape_to_face_indices_metadata:
|
||||
# for concave collision the shape will always represent every face
|
||||
# in the entity, so just add every face here
|
||||
var face_indices_array : PackedInt32Array = []
|
||||
for fm in face_index_metadata_map.values():
|
||||
face_indices_array.append_array(fm)
|
||||
shape_to_face_metadata.append(face_indices_array)
|
||||
|
||||
if def.add_collision_shape_to_face_indices_metadata:
|
||||
# this metadata will be mapped to the actual shape node names during entity assembly
|
||||
entity.mesh_metadata["shape_to_face_array"] = shape_to_face_metadata
|
||||
|
||||
func unwrap_uv2s(entity_index: int, texel_size: float) -> void:
|
||||
var entity: _EntityData = entity_data[entity_index]
|
||||
if entity.mesh:
|
||||
if (entity.definition as FuncGodotFGDSolidClass).global_illumination_mode:
|
||||
entity.mesh.lightmap_unwrap(Transform3D.IDENTITY, texel_size)
|
||||
|
||||
# Main build process
|
||||
func build(build_flags: int, entities: Array[_EntityData]) -> Error:
|
||||
var entity_count: int = entities.size()
|
||||
declare_step.emit("Preparing %s %s" % [entity_count, "entity" if entity_count == 1 else "entities"])
|
||||
entity_data = entities
|
||||
|
||||
declare_step.emit("Gathering materials")
|
||||
var texture_map: Array[Dictionary] = FuncGodotUtil.build_texture_map(entity_data, map_settings)
|
||||
texture_materials = texture_map[0]
|
||||
texture_sizes = texture_map[1]
|
||||
|
||||
var task_id: int
|
||||
declare_step.emit("Generating brush vertices")
|
||||
task_id = WorkerThreadPool.add_group_task(generate_entity_vertices, entity_count, -1, false, "Generate Brush Vertices")
|
||||
WorkerThreadPool.wait_for_group_task_completion(task_id)
|
||||
|
||||
declare_step.emit("Determining solid entity origins")
|
||||
task_id = WorkerThreadPool.add_group_task(determine_entity_origins, entity_count, -1, false, "Determine Entity Origins")
|
||||
WorkerThreadPool.wait_for_group_task_completion(task_id)
|
||||
|
||||
declare_step.emit("Winding faces")
|
||||
task_id = WorkerThreadPool.add_group_task(wind_entity_faces, entity_count, -1, false, "Wind Brush Faces")
|
||||
WorkerThreadPool.wait_for_group_task_completion(task_id)
|
||||
|
||||
# TODO: Reimplement after solving issues
|
||||
#if not (build_flags & FuncGodotMap.BuildFlags.DISABLE_SMOOTHING):
|
||||
# declare_step.emit("Smoothing entity faces")
|
||||
# task_id = WorkerThreadPool.add_group_task(smooth_entity_vertices, entity_count, -1, false, "Smooth Entities")
|
||||
# WorkerThreadPool.wait_for_group_task_completion(task_id)
|
||||
|
||||
declare_step.emit("Generating surfaces")
|
||||
task_id = WorkerThreadPool.add_group_task(generate_entity_surfaces, entity_count, -1, false, "Generate Surfaces")
|
||||
WorkerThreadPool.wait_for_group_task_completion(task_id)
|
||||
|
||||
if build_flags & FuncGodotMap.BuildFlags.UNWRAP_UV2:
|
||||
declare_step.emit("Unwrapping UV2s")
|
||||
var texel_size: float = map_settings.uv_unwrap_texel_size * map_settings.scale_factor
|
||||
for entity_index in entity_count:
|
||||
unwrap_uv2s(entity_index, texel_size)
|
||||
|
||||
declare_step.emit("Geometry generation complete")
|
||||
return OK
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1yg28xbyno7v
|
||||
439
demo/addons/func_godot/src/core/parser.gd
Normal file
@@ -0,0 +1,439 @@
|
||||
@icon("res://addons/func_godot/icons/icon_godambler.svg")
|
||||
class_name FuncGodotParser extends RefCounted
|
||||
## MAP and VMF parser class that is instantiated by a [FuncGodotMap] node during the build process.
|
||||
##
|
||||
## @tutorial(Quake Wiki Map Format Article): https://quakewiki.org/wiki/Quake_Map_Format
|
||||
## @tutorial(Valve Developer Wiki VMF Article): https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)
|
||||
|
||||
const _SIGNATURE: String = "[PRS]"
|
||||
|
||||
const _GroupData := FuncGodotData.GroupData
|
||||
const _EntityData := FuncGodotData.EntityData
|
||||
const _BrushData := FuncGodotData.BrushData
|
||||
const _PatchData := FuncGodotData.PatchData
|
||||
const _FaceData := FuncGodotData.FaceData
|
||||
const _ParseData := FuncGodotData.ParseData
|
||||
|
||||
## Emitted when a step in the parsing process is completed.
|
||||
## It is connected to [method FuncGodotUtil.print_profile_info] method if [member FuncGodotMap.build_flags] SHOW_PROFILE_INFO flag is set.
|
||||
signal declare_step(step: String)
|
||||
|
||||
## Parses the map file, generating entity and group data and sub-data, then returns the generated data as an array of arrays.
|
||||
## The first array is Array[FuncGodotData.EntityData], while the second array is Array[FuncGodotData.GroupData].
|
||||
func parse_map_data(map_file: String, map_settings: FuncGodotMapSettings) -> _ParseData:
|
||||
var map_data: PackedStringArray = []
|
||||
var parse_data := _ParseData.new()
|
||||
declare_step.emit("Loading map file %s" % map_file)
|
||||
|
||||
# Retrieve real path if needed
|
||||
if map_file.begins_with("uid://"):
|
||||
var uid := ResourceUID.text_to_id(map_file)
|
||||
if not ResourceUID.has_id(uid):
|
||||
printerr("Error: failed to retrieve path for UID (%s)" % map_file)
|
||||
return parse_data
|
||||
map_file = ResourceUID.get_id_path(uid)
|
||||
|
||||
# Open the map file
|
||||
var file: FileAccess = FileAccess.open(map_file, FileAccess.READ)
|
||||
if not file:
|
||||
file = FileAccess.open(map_file + ".import", FileAccess.READ)
|
||||
if file:
|
||||
map_file += ".import"
|
||||
else:
|
||||
printerr("Error: Failed to open map file (" + map_file + ")")
|
||||
return parse_data
|
||||
|
||||
# Packed map file resources need to be accessed differently in exported projects.
|
||||
if map_file.ends_with(".import"):
|
||||
while not file.eof_reached():
|
||||
var line: String = file.get_line()
|
||||
if line.begins_with("path"):
|
||||
file.close()
|
||||
line = line.replace("path=", "")
|
||||
line = line.replace('"', '')
|
||||
var data: String = (load(line) as QuakeMapFile).map_data
|
||||
if data.is_empty():
|
||||
printerr("Error: Failed to open map file (" + line + ")")
|
||||
return parse_data
|
||||
map_data = data.split("\n")
|
||||
break
|
||||
else:
|
||||
while not file.eof_reached():
|
||||
map_data.append(file.get_line())
|
||||
|
||||
# Determine map type and parse data
|
||||
if map_file.to_lower().contains(".map"):
|
||||
declare_step.emit("Parsing as Quake MAP")
|
||||
parse_data = _parse_quake_map(map_data, map_settings, parse_data)
|
||||
elif map_file.to_lower().contains(".vmf"):
|
||||
declare_step.emit("Parsing as Source VMF")
|
||||
parse_data = _parse_vmf(map_data, map_settings, parse_data)
|
||||
|
||||
# Determine group hierarchy
|
||||
declare_step.emit("Determining groups hierarchy")
|
||||
var groups_data: Array[_GroupData] = parse_data.groups
|
||||
for g in groups_data:
|
||||
if g.parent_id != -1:
|
||||
for p in groups_data:
|
||||
if p.id == g.parent_id:
|
||||
g.parent = p
|
||||
break
|
||||
|
||||
var entities_data: Array[_EntityData] = parse_data.entities
|
||||
var entity_defs: Dictionary[String, FuncGodotFGDEntityClass] = map_settings.entity_fgd.get_entity_definitions()
|
||||
|
||||
declare_step.emit("Checking entity omission and definition status")
|
||||
|
||||
for i in range(entities_data.size() - 1, -1, -1):
|
||||
var entity: _EntityData = entities_data[i]
|
||||
|
||||
# Delete entities from omitted groups
|
||||
if entity.group != null and entity.group.omit == true:
|
||||
entities_data.remove_at(i)
|
||||
continue
|
||||
|
||||
# Provide entity definition to entity data. This gets used in both
|
||||
# geo generation and entity assembly.
|
||||
if "classname" in entity.properties:
|
||||
var classname: String = entity.properties["classname"]
|
||||
if classname in entity_defs:
|
||||
entity.definition = entity_defs[classname]
|
||||
|
||||
# Delete omitted groups
|
||||
declare_step.emit("Removing omitted layers and groups")
|
||||
for i in range(groups_data.size() - 1, -1, -1):
|
||||
if groups_data[i].omit == true:
|
||||
groups_data.remove_at(i)
|
||||
|
||||
declare_step.emit("Map parsing complete")
|
||||
return parse_data
|
||||
|
||||
## Parser subroutine called by [method parse_map_data], specializing in the Quake MAP format.
|
||||
func _parse_quake_map(map_data: PackedStringArray, map_settings: FuncGodotMapSettings, parse_data: _ParseData) -> _ParseData:
|
||||
var entities_data: Array[_EntityData] = parse_data.entities
|
||||
var groups_data: Array[_GroupData] = parse_data.groups
|
||||
var ent: _EntityData = null
|
||||
var brush: _BrushData = null
|
||||
var patch: _PatchData = null
|
||||
var scope: int = 0 # Scope level, to keep track of where we are in PatchDef parsing
|
||||
|
||||
for line in map_data:
|
||||
line = line.replace("\t", "")
|
||||
|
||||
#region START DATA
|
||||
# Start entity, brush, or patchdef
|
||||
if line.begins_with("{"):
|
||||
if not ent:
|
||||
ent = _EntityData.new()
|
||||
else:
|
||||
if not patch:
|
||||
brush = _BrushData.new()
|
||||
else:
|
||||
scope += 1
|
||||
continue
|
||||
#endregion
|
||||
|
||||
#region COMMIT DATA
|
||||
# Commit entity or brush
|
||||
if line.begins_with("}"):
|
||||
if brush:
|
||||
ent.brushes.append(brush)
|
||||
brush = null
|
||||
elif patch:
|
||||
if scope:
|
||||
scope -= 1
|
||||
else:
|
||||
ent.patches.append(patch)
|
||||
patch = null
|
||||
else:
|
||||
# TrenchBroom layers and groups
|
||||
if ent.properties["classname"] == "func_group" and ent.properties.has("_tb_type"):
|
||||
# Merge TB Group / Layer structural brushes with worldspawn
|
||||
if entities_data.size():
|
||||
entities_data[0].brushes.append_array(ent.brushes)
|
||||
|
||||
# Create group data
|
||||
var group: _GroupData = _GroupData.new()
|
||||
var props: Dictionary = ent.properties
|
||||
group.id = props["_tb_id"] as int
|
||||
if props["_tb_type"] == "_tb_layer":
|
||||
group.type = _GroupData.GroupType.GROUP
|
||||
group.name = "layer_"
|
||||
else:
|
||||
group.name = "group_"
|
||||
group.name = group.name + str(group.id)
|
||||
if props["_tb_name"] != "Unnamed":
|
||||
group.name = group.name + "_" + (props["_tb_name"] as String).replace(" ", "_")
|
||||
if props.has("_tb_layer"):
|
||||
group.parent_id = props["_tb_layer"] as int
|
||||
if props.has("_tb_group"):
|
||||
group.parent_id = props["_tb_group"] as int
|
||||
if props.has("_tb_layer_omit_from_export"):
|
||||
group.omit = true
|
||||
|
||||
# Commit group
|
||||
groups_data.append(group)
|
||||
|
||||
# Commit entity
|
||||
else:
|
||||
entities_data.append(ent)
|
||||
ent = null
|
||||
continue
|
||||
#endregion
|
||||
|
||||
#region PROPERTY DATA
|
||||
# Retrieve key value pairs
|
||||
if line.begins_with("\""):
|
||||
var tokens: PackedStringArray = line.split("\" \"")
|
||||
if tokens.size() < 2:
|
||||
tokens = line.split("\"\"")
|
||||
var key: String = tokens[0].trim_prefix("\"")
|
||||
var value: String = tokens[1].trim_suffix("\"")
|
||||
ent.properties[key] = value
|
||||
#endregion
|
||||
|
||||
#region BRUSH DATA
|
||||
if brush and line.begins_with("("):
|
||||
line = line.replace("(","")
|
||||
var tokens: PackedStringArray = line.split(" ) ")
|
||||
|
||||
# Retrieve plane data
|
||||
var points: PackedVector3Array
|
||||
points.resize(3)
|
||||
for i in 3:
|
||||
tokens[i] = tokens[i].trim_prefix("(")
|
||||
var pts: PackedFloat64Array = tokens[i].split_floats(" ", false)
|
||||
var point := Vector3(pts[0], pts[1], pts[2])
|
||||
points[i] = point
|
||||
|
||||
var plane := Plane(points[0], points[1], points[2])
|
||||
brush.planes.append(plane)
|
||||
|
||||
var face: _FaceData = _FaceData.new()
|
||||
face.plane = plane
|
||||
|
||||
# Retrieve texture data
|
||||
var tex: String = String()
|
||||
if tokens[3].begins_with("\""): # textures with spaces get surrounded by double quotes
|
||||
var last_quote := tokens[3].rfind("\"")
|
||||
tex = tokens[3].substr(1, last_quote - 1)
|
||||
tokens = tokens[3].substr(last_quote + 2).split(" ] ")
|
||||
else:
|
||||
tex = tokens[3].get_slice(" ", 0)
|
||||
tokens = tokens[3].trim_prefix(tex + " ").split(" ] ")
|
||||
face.texture = tex
|
||||
|
||||
# Check for origin brushes. Brushes must be completely textured with origin to be valid.
|
||||
if brush.faces.is_empty():
|
||||
if tex == map_settings.origin_texture:
|
||||
brush.origin = true
|
||||
elif brush.origin == true:
|
||||
if tex != map_settings.origin_texture:
|
||||
brush.origin = false
|
||||
|
||||
# Retrieve UV data
|
||||
var uv: Transform2D = Transform2D.IDENTITY
|
||||
|
||||
# Valve 220: texname [ ux uy ux offsetX ] [vx vy vz offsetY] rotation scaleX scaleY
|
||||
if tokens.size() > 1:
|
||||
var coords: PackedFloat64Array
|
||||
for i in 2:
|
||||
coords = tokens[i].trim_prefix("[ ").split_floats(" ", false)
|
||||
face.uv_axes.append(Vector3(coords[0], coords[1], coords[2])) # Save axis vectors separately
|
||||
face.uv.origin[i] = coords[3] # UV offset stored as transform origin
|
||||
|
||||
coords = tokens[2].split_floats(" ", false)
|
||||
# UV scale factor stored in basis
|
||||
face.uv.x = Vector2(coords[1], 0.0)
|
||||
face.uv.y = Vector2(0.0, coords[2])
|
||||
|
||||
# Quake Standard: texname offsetX offsetY rotation scaleX scaleY
|
||||
else:
|
||||
var coords: PackedFloat64Array = tokens[0].split_floats(" ", false)
|
||||
face.uv.origin = Vector2(coords[0], coords[1])
|
||||
|
||||
var r: float = deg_to_rad(coords[2])
|
||||
face.uv.x = Vector2(cos(r), -sin(r)) * coords[3]
|
||||
face.uv.y = Vector2(sin(r), cos(r)) * coords[4]
|
||||
|
||||
brush.faces.append(face)
|
||||
continue
|
||||
#endregion
|
||||
|
||||
#region PATCH DATA
|
||||
if patch:
|
||||
if line.begins_with("("):
|
||||
line = line.replace("( ","")
|
||||
# Retrieve patch control points
|
||||
if patch.size:
|
||||
var tokens: PackedStringArray = line.replace("(", "").split(" )", false)
|
||||
for i in tokens.size():
|
||||
var subtokens: PackedFloat64Array = tokens[i].split_floats(" ", false)
|
||||
patch.points.append(Vector3(subtokens[0], subtokens[1], subtokens[2]))
|
||||
patch.uvs.append(Vector2(subtokens[3], subtokens[4]))
|
||||
# Retrieve patch size
|
||||
else:
|
||||
var tokens: PackedStringArray = line.replace(")","").split(" ", false)
|
||||
patch.size.resize(tokens.size())
|
||||
for i in tokens.size():
|
||||
patch.size[i] = tokens[i].to_int()
|
||||
# Retrieve patch texture
|
||||
elif not line.begins_with(")"):
|
||||
patch.texture = line.replace("\"","")
|
||||
|
||||
if line.begins_with("patchDef"):
|
||||
brush = null
|
||||
patch = _PatchData.new()
|
||||
continue
|
||||
#endregion
|
||||
|
||||
#region ASSIGN GROUPS
|
||||
for e in entities_data:
|
||||
var group_id: int = -1
|
||||
if e.properties.has("_tb_layer"):
|
||||
group_id = e.properties["_tb_layer"] as int
|
||||
elif e.properties.has("_tb_group"):
|
||||
group_id = e.properties["_tb_group"] as int
|
||||
if group_id != -1:
|
||||
for g in groups_data:
|
||||
if g.id == group_id:
|
||||
e.group = g
|
||||
break
|
||||
#endregion
|
||||
|
||||
return parse_data
|
||||
|
||||
## Parser subroutine called by [method parse_map_data], specializing in the Valve Map Format used by Hammer based editors.
|
||||
func _parse_vmf(map_data: PackedStringArray, map_settings: FuncGodotMapSettings, parse_data: _ParseData) -> _ParseData:
|
||||
var entities_data: Array[_EntityData] = parse_data.entities
|
||||
var groups_data: Array[_GroupData] = parse_data.groups
|
||||
var ent: _EntityData = null
|
||||
var brush: _BrushData = null
|
||||
var group: _GroupData = null
|
||||
var group_parent_hierarchy: Array[_GroupData] = []
|
||||
var scope: int = 0
|
||||
|
||||
for line in map_data:
|
||||
line = line.replace("\t", "")
|
||||
|
||||
#region START DATA
|
||||
if line.begins_with("entity") or line.begins_with("world"):
|
||||
ent = _EntityData.new()
|
||||
continue
|
||||
if line.begins_with("solid"):
|
||||
brush = _BrushData.new()
|
||||
continue
|
||||
if brush and line.begins_with("{"):
|
||||
scope += 1
|
||||
continue
|
||||
if line == "visgroup":
|
||||
if group != null:
|
||||
groups_data.append(group)
|
||||
group_parent_hierarchy.append(group)
|
||||
group = _GroupData.new()
|
||||
if group_parent_hierarchy.size():
|
||||
group.parent = group_parent_hierarchy.back()
|
||||
group.parent_id = group.parent.id
|
||||
continue
|
||||
#endregion
|
||||
|
||||
#region COMMIT DATA
|
||||
if line.begins_with("}"):
|
||||
if scope > 0:
|
||||
scope -= 1
|
||||
if not scope:
|
||||
if brush:
|
||||
if brush.faces.size():
|
||||
ent.brushes.append(brush)
|
||||
brush = null
|
||||
elif ent:
|
||||
entities_data.append(ent)
|
||||
ent = null
|
||||
elif group:
|
||||
groups_data.append(group)
|
||||
group = null
|
||||
elif group_parent_hierarchy.size():
|
||||
group_parent_hierarchy.pop_back()
|
||||
continue
|
||||
#endregion
|
||||
|
||||
# Retrieve key value pairs
|
||||
if (ent or group) and line.begins_with("\""):
|
||||
var tokens: PackedStringArray = line.split("\" \"")
|
||||
var key: String = tokens[0].trim_prefix("\"")
|
||||
var value: String = tokens[1].trim_suffix("\"")
|
||||
|
||||
#region BRUSH DATA
|
||||
if brush:
|
||||
if scope > 1:
|
||||
match key:
|
||||
"plane":
|
||||
tokens = value.replace("(", "").split(")", false)
|
||||
var points: PackedVector3Array
|
||||
points.resize(3)
|
||||
for i in 3:
|
||||
tokens[i] = tokens[i].trim_prefix("(")
|
||||
var pts: PackedFloat64Array = tokens[i].split_floats(" ", false)
|
||||
var point: Vector3 = Vector3(pts[0], pts[1], pts[2])
|
||||
points[i] = point
|
||||
brush.planes.append(Plane(points[0], points[1], points[2]))
|
||||
brush.faces.append(_FaceData.new())
|
||||
brush.faces[-1].plane = brush.planes[-1]
|
||||
continue
|
||||
"material":
|
||||
if brush.faces.size():
|
||||
brush.faces[-1].texture = value
|
||||
# Origin brush needs to be completely set to origin, otherwise it's invalid
|
||||
if brush.faces.size() < 2:
|
||||
if value == map_settings.origin_texture:
|
||||
brush.origin = true
|
||||
elif brush.origin == true:
|
||||
if value != map_settings.origin_texture:
|
||||
brush.origin = false
|
||||
continue
|
||||
"uaxis", "vaxis":
|
||||
if brush.faces.size():
|
||||
value = value.replace("[", "")
|
||||
var vals: PackedFloat64Array = value.replace("]", "").split_floats(" ", false)
|
||||
var face: _FaceData = brush.faces[-1]
|
||||
face.uv_axes.append(Vector3(vals[0], vals[1], vals[2]))
|
||||
if key.begins_with("u"):
|
||||
face.uv.origin.x = vals[3] # Offset
|
||||
face.uv.x *= vals[4] # Scale
|
||||
else:
|
||||
face.uv.origin.y = vals[3] # Offset
|
||||
face.uv.y *= vals[4] # Scale
|
||||
continue
|
||||
"rotation":
|
||||
# Rotation isn't used in Valve 220 mapping and VMFs are 220 exclusive
|
||||
continue
|
||||
"visgroupid":
|
||||
# Don't put worldspawn into a group
|
||||
if entities_data.size():
|
||||
# Only nodes can be organized into groups in the SceneTree, so only use the first brush's group
|
||||
if not ent.properties.has(key):
|
||||
ent.properties[key] = value
|
||||
#endregion
|
||||
elif ent:
|
||||
ent.properties[key] = value
|
||||
continue
|
||||
elif group:
|
||||
if key == "name":
|
||||
group.name = "group_%s_" + value
|
||||
elif key == "visgroupid":
|
||||
group.id = value.to_int()
|
||||
group.name = group.name % value
|
||||
group.name = group.name.replace(" ", "_")
|
||||
continue
|
||||
|
||||
#region ASSIGN GROUPS
|
||||
for e in entities_data:
|
||||
if e.properties.has("visgroupid"):
|
||||
var group_id: int = e.properties["visgroupid"] as int
|
||||
for g in groups_data:
|
||||
if g.id == group_id:
|
||||
e.group = g
|
||||
break
|
||||
#endregion
|
||||
|
||||
return parse_data
|
||||
1
demo/addons/func_godot/src/core/parser.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dflet6p5hbqts
|
||||
14
demo/addons/func_godot/src/fgd/func_godot_fgd_base_class.gd
Normal file
@@ -0,0 +1,14 @@
|
||||
@tool
|
||||
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
||||
class_name FuncGodotFGDBaseClass extends FuncGodotFGDEntityClass
|
||||
## Special inheritance class for [FuncGodotFGDSolidClass] and [FuncGodotFGDPointClass] entity definitions.
|
||||
##
|
||||
## Inheritance class for [FuncGodotFGDSolidClass] and [FuncGodotFGDPointClass] entities,
|
||||
## used to shared or common properties and descriptions across different definitions.
|
||||
##
|
||||
## @tutorial(Quake Wiki Entity Article): https://quakewiki.org/wiki/Entity
|
||||
## @tutorial(Level Design Book: Entity Types and Settings): https://book.leveldesignbook.com/appendix/resources/formats/fgd#entity-types-and-settings-basic
|
||||
## @tutorial(Valve Developer Wiki FGD Article): https://developer.valvesoftware.com/wiki/FGD#Class_Types_and_Properties
|
||||
|
||||
func _init() -> void:
|
||||
prefix = "@BaseClass"
|
||||
@@ -0,0 +1 @@
|
||||
uid://ck575aqs1sbrb
|
||||
233
demo/addons/func_godot/src/fgd/func_godot_fgd_entity_class.gd
Normal file
@@ -0,0 +1,233 @@
|
||||
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
||||
class_name FuncGodotFGDEntityClass extends Resource
|
||||
## Entity definition template. WARNING! Not to be used directly! Use [FuncGodotFGDBaseClass], [FuncGodotFGDSolidClass], or [FuncGodotFGDPointClass] instead.
|
||||
##
|
||||
## Entity definition template. It holds all of the common entity class properties shared between [FuncGodotFGDBaseClass], [FuncGodotFGDSolidClass], or [FuncGodotFGDPointClass].
|
||||
## Not to be used directly, use one of the aforementioned FGD class types instead.
|
||||
##
|
||||
## @tutorial(Quake Wiki Entity Article): https://quakewiki.org/wiki/Entity
|
||||
## @tutorial(Level Design Book: Entity Types and Settings): https://book.leveldesignbook.com/appendix/resources/formats/fgd#entity-types-and-settings-basic
|
||||
## @tutorial(Valve Developer Wiki FGD Article): https://developer.valvesoftware.com/wiki/FGD#Class_Types_and_Properties
|
||||
## @tutorial(Valve Developer Wiki Entity Descriptions): https://developer.valvesoftware.com/wiki/FGD#Entity_Description
|
||||
|
||||
var prefix: String = ""
|
||||
|
||||
@export_group("Entity Definition")
|
||||
|
||||
## Entity classname. [b][i]This is a required field in all entity types[/i][/b] as it is parsed by both the map editor and by FuncGodot on map build.
|
||||
@export var classname : String = ""
|
||||
|
||||
## Entity description that appears in the map editor. Not required.
|
||||
@export_multiline var description : String = ""
|
||||
|
||||
## Entity does not get written to the exported FGD. Entity is only used for [FuncGodotMap] build process.
|
||||
@export var func_godot_internal : bool = false
|
||||
|
||||
## [FuncGodotFGDBaseClass] resources to inherit [member class_properties] and [member class_descriptions] from.
|
||||
@export var base_classes: Array[Resource] = []
|
||||
|
||||
## Key value pair properties that will appear in the map editor. After building the [FuncGodotMap] in Godot, these properties will be added to a [Dictionary]
|
||||
## that gets applied to the generated node, as long as that node is a tool script with an exported `func_godot_properties` Dictionary.
|
||||
@export var class_properties : Dictionary = {}
|
||||
|
||||
## Map editor descriptions for previously defined key value pair properties. Optional but recommended.
|
||||
@export var class_property_descriptions : Dictionary = {}
|
||||
|
||||
## Automatically applies entity class properties to matching properties in the generated node.
|
||||
## When using this feature, class properties need to be the correct type or you may run into errors on map build.
|
||||
@export var auto_apply_to_matching_node_properties : bool = false
|
||||
|
||||
## Appearance properties for the map editor. See the Valve Developer Wiki and TrenchBroom documentation for more information.
|
||||
@export var meta_properties : Dictionary = {
|
||||
"size": AABB(Vector3(-8, -8, -8), Vector3(8, 8, 8)),
|
||||
"color": Color(0.8, 0.8, 0.8)
|
||||
}
|
||||
|
||||
@export_group("Node Generation")
|
||||
|
||||
## Node to generate on map build. This can be a built-in Godot class, a GDScript class, or a GDExtension class.
|
||||
## For Point Class entities that use Scene File instantiation leave this blank.
|
||||
@export var node_class := ""
|
||||
|
||||
## Optional class property to use in naming the generated node. Overrides [member FuncGodotMapSettings.name_property].
|
||||
## Naming occurs before adding to the [SceneTree] and applying properties.
|
||||
## Nodes will be named `"entity_" + name_property`. An entity's name should be unique, otherwise you may run into unexpected behavior.
|
||||
@export var name_property := ""
|
||||
|
||||
## Parses the definition and outputs it into the FGD format.
|
||||
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
||||
# Class prefix
|
||||
var res : String = prefix
|
||||
|
||||
# Meta properties
|
||||
var base_str = ""
|
||||
var meta_props = meta_properties.duplicate()
|
||||
|
||||
for base_class in base_classes:
|
||||
if not 'classname' in base_class:
|
||||
continue
|
||||
|
||||
base_str += base_class.classname
|
||||
|
||||
if base_class != base_classes.back():
|
||||
base_str += ", "
|
||||
|
||||
if base_str != "":
|
||||
meta_props['base'] = base_str
|
||||
|
||||
for prop in meta_props:
|
||||
if prefix == '@SolidClass':
|
||||
if prop == "size" or prop == "model":
|
||||
continue
|
||||
|
||||
if prop == 'model' and target_editor != FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM:
|
||||
continue
|
||||
|
||||
var value = meta_props[prop]
|
||||
res += " " + prop + "("
|
||||
|
||||
if value is AABB:
|
||||
res += "%s %s %s, %s %s %s" % [
|
||||
value.position.x,
|
||||
value.position.y,
|
||||
value.position.z,
|
||||
value.size.x,
|
||||
value.size.y,
|
||||
value.size.z
|
||||
]
|
||||
elif value is Color:
|
||||
res += "%s %s %s" % [
|
||||
value.r8,
|
||||
value.g8,
|
||||
value.b8
|
||||
]
|
||||
elif value is String:
|
||||
res += value
|
||||
elif value is Dictionary and target_editor == FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM:
|
||||
res += JSON.stringify(value)
|
||||
|
||||
res += ")"
|
||||
|
||||
res += " = " + classname
|
||||
|
||||
if prefix != "@BaseClass": # having a description in BaseClasses crashes some editors
|
||||
var normalized_description = description.replace("\"", "\'")
|
||||
if normalized_description != "":
|
||||
res += " : \"%s\" " % [normalized_description]
|
||||
else: # Having no description crashes some editors
|
||||
res += " : \"" + classname + "\" "
|
||||
|
||||
if class_properties.size() > 0:
|
||||
res += FuncGodotUtil.newline() + "[" + FuncGodotUtil.newline()
|
||||
else:
|
||||
res += "["
|
||||
|
||||
# Class properties
|
||||
for prop in class_properties:
|
||||
var value = class_properties[prop]
|
||||
var prop_val = null
|
||||
var prop_type := ""
|
||||
var prop_description: String
|
||||
if prop in class_property_descriptions:
|
||||
# Optional default value for Choices can be set up as [String, int]
|
||||
if value is Dictionary and class_property_descriptions[prop] is Array:
|
||||
var prop_arr: Array = class_property_descriptions[prop]
|
||||
if prop_arr.size() > 1 and (prop_arr[1] is int or prop_arr[1] is String):
|
||||
var value_str : String = str(prop_arr[1]) if prop_arr[1] is int else "\"" + prop_arr[1] + "\""
|
||||
prop_description = "\"" + prop_arr[0] + "\" : " + value_str
|
||||
else:
|
||||
prop_description = "\"\" : 0"
|
||||
printerr(str(prop) + " has incorrect description format. Should be [String description, int / String default value].")
|
||||
else:
|
||||
prop_description = "\"" + class_property_descriptions[prop] + "\""
|
||||
else:
|
||||
prop_description = "\"\""
|
||||
|
||||
match typeof(value):
|
||||
TYPE_INT:
|
||||
prop_type = "integer"
|
||||
prop_val = str(value)
|
||||
TYPE_FLOAT:
|
||||
prop_type = "float"
|
||||
prop_val = "\"" + str(value) + "\""
|
||||
TYPE_STRING:
|
||||
prop_type = "string"
|
||||
prop_val = "\"" + value + "\""
|
||||
TYPE_BOOL:
|
||||
prop_type = "choices"
|
||||
prop_val = FuncGodotUtil.newline() + "\t[" + FuncGodotUtil.newline()
|
||||
prop_val += "\t\t" + str(0) + " : \"No\"" + FuncGodotUtil.newline()
|
||||
prop_val += "\t\t" + str(1) + " : \"Yes\"" + FuncGodotUtil.newline()
|
||||
prop_val += "\t]"
|
||||
TYPE_VECTOR2, TYPE_VECTOR2I:
|
||||
prop_type = "string"
|
||||
prop_val = "\"%s %s\"" % [value.x, value.y]
|
||||
TYPE_VECTOR3, TYPE_VECTOR3I:
|
||||
prop_type = "string"
|
||||
prop_val = "\"%s %s %s\"" % [value.x, value.y, value.z]
|
||||
TYPE_VECTOR4, TYPE_VECTOR4I:
|
||||
prop_type = "string"
|
||||
prop_val = "\"%s %s %s %s\"" % [value[0], value[1], value[2], value[3]]
|
||||
TYPE_COLOR:
|
||||
prop_type = "color255"
|
||||
prop_val = "\"%s %s %s\"" % [value.r8, value.g8, value.b8]
|
||||
TYPE_DICTIONARY:
|
||||
prop_type = "choices"
|
||||
prop_val = FuncGodotUtil.newline() + "\t[" + FuncGodotUtil.newline()
|
||||
for choice in value:
|
||||
var choice_val = value[choice]
|
||||
if typeof(choice_val) == TYPE_STRING:
|
||||
if not (choice_val as String).begins_with("\""):
|
||||
choice_val = "\"" + choice_val + "\""
|
||||
prop_val += "\t\t" + str(choice_val) + " : \"" + choice + "\"" + FuncGodotUtil.newline()
|
||||
prop_val += "\t]"
|
||||
TYPE_ARRAY:
|
||||
prop_type = "flags"
|
||||
prop_val = FuncGodotUtil.newline() + "\t[" + FuncGodotUtil.newline()
|
||||
for arr_val in value:
|
||||
prop_val += "\t\t" + str(arr_val[1]) + " : \"" + str(arr_val[0]) + "\" : " + ("1" if arr_val[2] else "0") + FuncGodotUtil.newline()
|
||||
prop_val += "\t]"
|
||||
TYPE_NODE_PATH:
|
||||
prop_type = "target_destination"
|
||||
prop_val = "\"\""
|
||||
TYPE_OBJECT:
|
||||
if value is Resource:
|
||||
prop_val = "\"" + value.resource_path + "\""
|
||||
if value is Material:
|
||||
if target_editor != FuncGodotFGDFile.FuncGodotTargetMapEditors.JACK:
|
||||
prop_type = "material"
|
||||
else:
|
||||
prop_type = "shader"
|
||||
elif value is Texture2D:
|
||||
prop_type = "decal"
|
||||
elif value is AudioStream:
|
||||
prop_type = "sound"
|
||||
else:
|
||||
prop_type = "target_source"
|
||||
prop_val = "\"\""
|
||||
|
||||
if prop_val:
|
||||
res += "\t"
|
||||
res += prop
|
||||
res += "("
|
||||
res += prop_type
|
||||
res += ")"
|
||||
|
||||
if not value is Array:
|
||||
if not value is Dictionary or prop_description != "":
|
||||
res += " : "
|
||||
res += prop_description
|
||||
|
||||
if value is bool:
|
||||
res += " : 1 = " if value else " : 0 = "
|
||||
elif value is Dictionary or value is Array:
|
||||
res += " = "
|
||||
else:
|
||||
res += " : "
|
||||
|
||||
res += prop_val
|
||||
res += FuncGodotUtil.newline()
|
||||
|
||||
res += "]" + FuncGodotUtil.newline()
|
||||
|
||||
return res
|
||||
@@ -0,0 +1 @@
|
||||
uid://cgkrrgcimlr8y
|
||||
177
demo/addons/func_godot/src/fgd/func_godot_fgd_file.gd
Normal file
@@ -0,0 +1,177 @@
|
||||
@tool
|
||||
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
||||
class_name FuncGodotFGDFile extends Resource
|
||||
## [Resource] file used to express a set of [FuncGodotFGDEntity] definitions.
|
||||
##
|
||||
## Can be exported as an FGD file for use with a Quake or Hammer-based map editor. Used in conjunction with [FuncGodotMapSetting] to generate nodes in a [FuncGodotMap] node.
|
||||
##
|
||||
## @tutorial(Level Design Book FGD Chapter): https://book.leveldesignbook.com/appendix/resources/formats/fgd
|
||||
## @tutorial(Valve Developer Wiki FGD Article): https://developer.valvesoftware.com/wiki/FGD
|
||||
|
||||
## Supported map editors enum, used in conjunction with [member target_map_editor].
|
||||
enum FuncGodotTargetMapEditors {
|
||||
OTHER,
|
||||
TRENCHBROOM,
|
||||
JACK,
|
||||
NET_RADIANT_CUSTOM,
|
||||
}
|
||||
|
||||
## Builds and exports the FGD file.
|
||||
@export_tool_button("Export FGD") var export_file := export_button
|
||||
|
||||
func export_button() -> void:
|
||||
do_export_file(target_map_editor)
|
||||
|
||||
func do_export_file(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM, fgd_output_folder: String = "") -> void:
|
||||
if not Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if fgd_output_folder.is_empty():
|
||||
fgd_output_folder = FuncGodotLocalConfig.get_setting(FuncGodotLocalConfig.PROPERTY.FGD_OUTPUT_FOLDER) as String
|
||||
if fgd_output_folder.is_empty():
|
||||
print("Skipping export: No game config folder")
|
||||
return
|
||||
|
||||
if fgd_name == "":
|
||||
print("Skipping export: Empty FGD name")
|
||||
|
||||
if not DirAccess.dir_exists_absolute(fgd_output_folder):
|
||||
if DirAccess.make_dir_recursive_absolute(fgd_output_folder) != OK:
|
||||
print("Skipping export: Failed to create directory")
|
||||
return
|
||||
|
||||
var fgd_file = fgd_output_folder.path_join(fgd_name + ".fgd")
|
||||
|
||||
var file_obj := FileAccess.open(fgd_file, FileAccess.WRITE)
|
||||
if not file_obj:
|
||||
print("Failed to open file for writing: ", fgd_file)
|
||||
return
|
||||
|
||||
print("Exporting FGD to ", fgd_file)
|
||||
file_obj.store_string(build_class_text(target_editor))
|
||||
file_obj.close()
|
||||
|
||||
@export_group("Map Editor")
|
||||
|
||||
## Some map editors do not support the features found in others
|
||||
## (ex: TrenchBroom supports the "model" key word while others require "studio",
|
||||
## J.A.C.K. uses the "shader" key word while others use "material", etc...).
|
||||
## If you get errors in your map editor, try changing this setting and re-exporting.
|
||||
## This setting is overridden when the FGD is built via the Game Config resource.
|
||||
@export var target_map_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM
|
||||
|
||||
# Some map editors do not support the "model" key word and require the "studio" key word instead.
|
||||
# If you get errors in your map editor, try changing this setting.
|
||||
# This setting is overridden when the FGD is built via the Game Config resource.
|
||||
#@export var model_key_word_supported: bool = true
|
||||
|
||||
@export_group("FGD")
|
||||
|
||||
## FGD output filename without the extension.
|
||||
@export var fgd_name: String = "FuncGodot"
|
||||
|
||||
## Array of [FuncGodotFGDFile] resources to include in FGD file output. All of the entities included with these FuncGodotFGDFile resources will be prepended to the outputted FGD file.
|
||||
@export var base_fgd_files: Array[Resource] = []
|
||||
|
||||
## Array of resources that inherit from [FuncGodotFGDEntityClass]. This array defines the entities that will be added to the exported FGD file and the nodes that will be generated in a [FuncGodotMap].
|
||||
@export var entity_definitions: Array[Resource] = []
|
||||
|
||||
func build_class_text(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
||||
var res : String = ""
|
||||
|
||||
for base_fgd in base_fgd_files:
|
||||
if base_fgd is FuncGodotFGDFile:
|
||||
res += base_fgd.build_class_text(target_editor)
|
||||
else:
|
||||
printerr("Base Fgd Files contains incorrect resource type! Should only be type FuncGodotFGDFile.")
|
||||
|
||||
var entities = get_fgd_classes()
|
||||
for ent in entities:
|
||||
if not ent is FuncGodotFGDEntityClass:
|
||||
continue
|
||||
if ent.func_godot_internal:
|
||||
continue
|
||||
|
||||
var ent_text = ent.build_def_text(target_editor)
|
||||
res += ent_text
|
||||
if ent != entities[-1]:
|
||||
res += "\n"
|
||||
return res
|
||||
|
||||
## This getter does a little bit of validation. Providing only an array of non-null uniquely-named entity definitions
|
||||
func get_fgd_classes() -> Array:
|
||||
var res : Array = []
|
||||
for cur_ent_def_ind in range(entity_definitions.size()):
|
||||
var cur_ent_def = entity_definitions[cur_ent_def_ind]
|
||||
if cur_ent_def == null:
|
||||
continue
|
||||
elif not (cur_ent_def is FuncGodotFGDEntityClass):
|
||||
printerr("Bad value in entity definition set at position %s! Not an entity defintion." % cur_ent_def_ind)
|
||||
continue
|
||||
res.append(cur_ent_def)
|
||||
return res
|
||||
|
||||
func get_entity_definitions() -> Dictionary[String, FuncGodotFGDEntityClass]:
|
||||
var res: Dictionary[String, FuncGodotFGDEntityClass] = {}
|
||||
|
||||
for base_fgd in base_fgd_files:
|
||||
var fgd_res = base_fgd.get_entity_definitions()
|
||||
for key in fgd_res:
|
||||
res[key] = fgd_res[key]
|
||||
|
||||
for ent in get_fgd_classes():
|
||||
# Skip entities without classnames
|
||||
if ent.classname.replace(" ","") == "":
|
||||
printerr("Skipping " + ent.get_path() + ": Empty classname")
|
||||
continue
|
||||
|
||||
if ent is FuncGodotFGDPointClass or ent is FuncGodotFGDSolidClass:
|
||||
var entity_def = ent.duplicate()
|
||||
var meta_properties := {}
|
||||
var class_properties := {}
|
||||
var class_property_descriptions := {}
|
||||
|
||||
for base_class in _generate_base_class_list(entity_def):
|
||||
for meta_property in base_class.meta_properties:
|
||||
meta_properties[meta_property] = base_class.meta_properties[meta_property]
|
||||
|
||||
for class_property in base_class.class_properties:
|
||||
class_properties[class_property] = base_class.class_properties[class_property]
|
||||
|
||||
for class_property_desc in base_class.class_property_descriptions:
|
||||
class_property_descriptions[class_property_desc] = base_class.class_property_descriptions[class_property_desc]
|
||||
|
||||
for meta_property in entity_def.meta_properties:
|
||||
meta_properties[meta_property] = entity_def.meta_properties[meta_property]
|
||||
|
||||
for class_property in entity_def.class_properties:
|
||||
class_properties[class_property] = entity_def.class_properties[class_property]
|
||||
|
||||
for class_property_desc in entity_def.class_property_descriptions:
|
||||
class_property_descriptions[class_property_desc] = entity_def.class_property_descriptions[class_property_desc]
|
||||
|
||||
entity_def.meta_properties = meta_properties
|
||||
entity_def.class_properties = class_properties
|
||||
entity_def.class_property_descriptions = class_property_descriptions
|
||||
|
||||
res[ent.classname] = entity_def
|
||||
return res
|
||||
|
||||
func _generate_base_class_list(entity_def : Resource, visited_base_classes = []) -> Array:
|
||||
var base_classes : Array = []
|
||||
|
||||
visited_base_classes.append(entity_def.classname)
|
||||
|
||||
# End recursive search if no more base_classes
|
||||
if len(entity_def.base_classes) == 0:
|
||||
return base_classes
|
||||
|
||||
# Traverse up to the next level of hierarchy, if not already visited
|
||||
for base_class in entity_def.base_classes:
|
||||
if not base_class.classname in visited_base_classes:
|
||||
base_classes.append(base_class)
|
||||
base_classes += _generate_base_class_list(base_class, visited_base_classes)
|
||||
else:
|
||||
printerr(str("Entity '", entity_def.classname,"' contains cycle/duplicate to Entity '", base_class.classname, "'"))
|
||||
|
||||
return base_classes
|
||||
@@ -0,0 +1 @@
|
||||
uid://drlmgulwbjwqu
|
||||
@@ -0,0 +1,190 @@
|
||||
@tool
|
||||
@icon("res://addons/func_godot/icons/icon_godambler3d.svg")
|
||||
class_name FuncGodotFGDModelPointClass extends FuncGodotFGDPointClass
|
||||
## A special type of [FuncGodotFGDPointClass] entity that automatically generates a special simplified GLB model file for the map editor display.
|
||||
## Only supported in map editors that support GLTF or GLB.
|
||||
##
|
||||
## @tutorial(Quake Wiki Entity Article): https://quakewiki.org/wiki/Entity
|
||||
## @tutorial(Level Design Book: Entity Types and Settings): https://book.leveldesignbook.com/appendix/resources/formats/fgd#entity-types-and-settings-basic
|
||||
## @tutorial(Valve Developer Wiki FGD Article): https://developer.valvesoftware.com/wiki/FGD#Class_Types_and_Properties
|
||||
## @tutorial(dumptruck_ds' Quake Mapping Entities Tutorial): https://www.youtube.com/watch?v=gtL9f6_N2WM
|
||||
## @tutorial(Level Design Book: Display Models for Entities): https://book.leveldesignbook.com/appendix/resources/formats/fgd#display-models-for-entities
|
||||
## @tutorial(Valve Developer Wiki FGD Article: Entity Description Section): https://developer.valvesoftware.com/wiki/FGD#Entity_Description
|
||||
## @tutorial(TrenchBroom Manual: Display Models for Entities): https://trenchbroom.github.io/manual/latest/#display-models-for-entities
|
||||
|
||||
enum TargetMapEditor {
|
||||
GENERIC, ## Entity definition uses the [b]@studio[/b] key word. [member scale_expression] is ignored. Supported by all map editors.
|
||||
TRENCHBROOM ## Entity definition uses the [b]@model[/b] key word. [member scale_expression] is applied if set.
|
||||
}
|
||||
|
||||
@export var target_map_editor: TargetMapEditor = TargetMapEditor.GENERIC
|
||||
## Display model export folder relative to [member ProjectSettings.func_godot/model_point_class_save_path].
|
||||
@export var models_sub_folder : String = ""
|
||||
## Scale expression applied to model. Only used by TrenchBroom. If left empty, uses [member ProjectSettings.func_godot/default_inverse_scale_factor]. [br][br]Read the TrenchBroom Manual for more information on the "scale expression" feature.
|
||||
@export var scale_expression : String = ""
|
||||
## Model Point Class can override the 'size' meta property by auto-generating a value from the meshes' [AABB]. Proper generation requires [member scale_expression] set to a float or vector. [br][br][color=orange]WARNING:[/color] Generated size property unlikely to align cleanly to grid!
|
||||
@export var generate_size_property : bool = false
|
||||
## Degrees to rotate model prior to export. Different editors may handle GLTF transformations differently. If your model isn't oriented correctly, try modifying this property.
|
||||
@export var rotation_offset: Vector3 = Vector3(0.0, 0.0, 0.0)
|
||||
## Creates a .gdignore file in the model export folder to prevent Godot importing the display models. Only needs to be generated once.
|
||||
@export_tool_button("Generate GD Ignore File", "FileAccess") var generate_gd_ignore_file : Callable = _generate_gd_ignore_file
|
||||
|
||||
func _generate_gd_ignore_file() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
var path: String = _get_game_path().path_join(_get_model_folder())
|
||||
var error: Error = DirAccess.make_dir_recursive_absolute(path)
|
||||
if error != Error.OK:
|
||||
printerr("Failed creating dir for GDIgnore file", error)
|
||||
return
|
||||
path = path.path_join('.gdignore')
|
||||
if FileAccess.file_exists(path):
|
||||
return
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
file.store_string('')
|
||||
file.close()
|
||||
|
||||
## Builds and saves the display model into the specified destination, then parses the definition and outputs it into the FGD format.
|
||||
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
||||
_generate_model()
|
||||
return super()
|
||||
|
||||
func _generate_model() -> void:
|
||||
if not scene_file:
|
||||
return
|
||||
|
||||
var gltf_state := GLTFState.new()
|
||||
var path: String = _get_export_dir()
|
||||
var node: Node3D = _get_node()
|
||||
if not node:
|
||||
return
|
||||
if not _create_gltf_file(gltf_state, path, node):
|
||||
printerr("could not create gltf file")
|
||||
return
|
||||
node.queue_free()
|
||||
|
||||
if target_map_editor == TargetMapEditor.TRENCHBROOM:
|
||||
const model_key: String = "model"
|
||||
if scale_expression.is_empty():
|
||||
meta_properties[model_key] = '"%s"' % _get_local_path()
|
||||
else:
|
||||
meta_properties[model_key] = '{"path": "%s", "scale": %s }' % [
|
||||
_get_local_path(),
|
||||
scale_expression
|
||||
]
|
||||
else:
|
||||
meta_properties["studio"] = '"%s"' % _get_local_path()
|
||||
|
||||
if generate_size_property:
|
||||
meta_properties["size"] = _generate_size_from_aabb(gltf_state.meshes, gltf_state.get_nodes())
|
||||
|
||||
func _get_node() -> Node3D:
|
||||
var node := scene_file.instantiate()
|
||||
if node is Node3D:
|
||||
return node as Node3D
|
||||
node.queue_free()
|
||||
printerr("Scene is not of type 'Node3D'")
|
||||
return null
|
||||
|
||||
func _get_export_dir() -> String:
|
||||
var work_dir: String = _get_game_path()
|
||||
var model_dir: String = _get_model_folder()
|
||||
return work_dir.path_join(model_dir).path_join('%s.glb' % classname)
|
||||
|
||||
func _get_local_path() -> String:
|
||||
return _get_model_folder().path_join('%s.glb' % classname)
|
||||
|
||||
func _get_model_folder() -> String:
|
||||
var model_dir: String = ProjectSettings.get_setting("func_godot/model_point_class_save_path", "") as String
|
||||
if not models_sub_folder.is_empty():
|
||||
model_dir = model_dir.path_join(models_sub_folder)
|
||||
return model_dir
|
||||
|
||||
func _get_game_path() -> String:
|
||||
return FuncGodotLocalConfig.get_setting(FuncGodotLocalConfig.PROPERTY.MAP_EDITOR_GAME_PATH) as String
|
||||
|
||||
func _create_gltf_file(gltf_state: GLTFState, path: String, node: Node3D) -> bool:
|
||||
var global_export_path = path
|
||||
var gltf_document := GLTFDocument.new()
|
||||
gltf_state.create_animations = false
|
||||
|
||||
node.rotate_x(deg_to_rad(rotation_offset.x))
|
||||
node.rotate_y(deg_to_rad(rotation_offset.y))
|
||||
node.rotate_z(deg_to_rad(rotation_offset.z))
|
||||
|
||||
# With TrenchBroom we can specify a scale expression, but for other editors we need to scale our models manually.
|
||||
if target_map_editor != TargetMapEditor.TRENCHBROOM:
|
||||
var scale_factor: Vector3 = Vector3.ONE
|
||||
if scale_expression.is_empty():
|
||||
scale_factor *= ProjectSettings.get_setting("func_godot/default_inverse_scale_factor", 32.0) as float
|
||||
else:
|
||||
if scale_expression.begins_with('\''):
|
||||
var scale_arr := scale_expression.split_floats(' ', false)
|
||||
if scale_arr.size() == 3:
|
||||
scale_factor *= Vector3(scale_arr[0], scale_arr[1], scale_arr[2])
|
||||
elif scale_expression.to_float() > 0:
|
||||
scale_factor *= scale_expression.to_float()
|
||||
if scale_factor.length() == 0:
|
||||
scale_factor = Vector3.ONE # Don't let the node scale into oblivion!
|
||||
node.scale *= scale_factor
|
||||
|
||||
var error: Error = gltf_document.append_from_scene(node, gltf_state)
|
||||
if error != Error.OK:
|
||||
printerr("Failed appending to gltf document", error)
|
||||
return false
|
||||
|
||||
call_deferred("_save_to_file_system", gltf_document, gltf_state, global_export_path)
|
||||
return true
|
||||
|
||||
func _save_to_file_system(gltf_document: GLTFDocument, gltf_state: GLTFState, path: String) -> void:
|
||||
var error: Error = DirAccess.make_dir_recursive_absolute(path.get_base_dir())
|
||||
if error != Error.OK:
|
||||
printerr("Failed creating dir", error)
|
||||
return
|
||||
|
||||
error = gltf_document.write_to_filesystem(gltf_state, path)
|
||||
if error != Error.OK:
|
||||
printerr("Failed writing to file system", error)
|
||||
return
|
||||
print('Exported model to ', path)
|
||||
|
||||
func _generate_size_from_aabb(meshes: Array[GLTFMesh], nodes: Array[GLTFNode]) -> AABB:
|
||||
var aabb := AABB()
|
||||
for mesh in meshes:
|
||||
aabb = aabb.merge(mesh.mesh.get_mesh().get_aabb())
|
||||
var pos_ofs := Vector3.ZERO
|
||||
if not nodes.is_empty():
|
||||
var ct: int = 0
|
||||
for node in nodes:
|
||||
if node.parent == 0:
|
||||
pos_ofs += node.position
|
||||
ct += 1
|
||||
pos_ofs /= maxi(ct, 1)
|
||||
aabb.position += pos_ofs
|
||||
|
||||
# Reorient the AABB so it matches TrenchBroom's coordinate system
|
||||
var size_prop := AABB()
|
||||
size_prop.position = Vector3(aabb.position.z, aabb.position.x, aabb.position.y)
|
||||
size_prop.size = Vector3(aabb.size.z, aabb.size.x, aabb.size.y)
|
||||
|
||||
# Scale the size bounds to our scale factor
|
||||
# Scale factor will need to be set if we decide to auto-generate our bounds
|
||||
var scale_factor: Vector3 = Vector3.ONE
|
||||
if target_map_editor == TargetMapEditor.TRENCHBROOM:
|
||||
if scale_expression.is_empty():
|
||||
scale_factor *= ProjectSettings.get_setting("func_godot/default_inverse_scale_factor", 32.0) as float
|
||||
else:
|
||||
if scale_expression.begins_with('\''):
|
||||
var scale_arr := scale_expression.split_floats(' ', false)
|
||||
if scale_arr.size() == 3:
|
||||
scale_factor *= Vector3(scale_arr[0], scale_arr[1], scale_arr[2])
|
||||
elif scale_expression.to_float() > 0:
|
||||
scale_factor *= scale_expression.to_float()
|
||||
|
||||
size_prop.position *= scale_factor
|
||||
size_prop.size *= scale_factor
|
||||
size_prop.size += size_prop.position
|
||||
# Round the size so it can stay on grid level 1 at least
|
||||
for i in 3:
|
||||
size_prop.position[i] = round(size_prop.position[i])
|
||||
size_prop.size[i] = round(size_prop.size[i])
|
||||
return size_prop
|
||||
@@ -0,0 +1 @@
|
||||
uid://ldfqjtq0br35
|
||||
35
demo/addons/func_godot/src/fgd/func_godot_fgd_point_class.gd
Normal file
@@ -0,0 +1,35 @@
|
||||
@tool
|
||||
@icon("res://addons/func_godot/icons/icon_godambler3d.svg")
|
||||
class_name FuncGodotFGDPointClass extends FuncGodotFGDEntityClass
|
||||
## FGD PointClass entity definition.
|
||||
##
|
||||
## A resource used to define an FGD PointClass entity. PointClass entities can use either the [member FuncGodotFGDEntityClass.node_class]
|
||||
## or the [member scene_file] property to tell [FuncGodotMap] what to generate on map build.
|
||||
##
|
||||
## @tutorial(Quake Wiki Entity Article): https://quakewiki.org/wiki/Entity
|
||||
## @tutorial(Level Design Book: Entity Types and Settings): https://book.leveldesignbook.com/appendix/resources/formats/fgd#entity-types-and-settings-basic
|
||||
## @tutorial(Valve Developer Wiki FGD Article): https://developer.valvesoftware.com/wiki/FGD#Class_Types_and_Properties
|
||||
## @tutorial(dumptruck_ds' Quake Mapping Entities Tutorial): https://www.youtube.com/watch?v=gtL9f6_N2WM
|
||||
## @tutorial(Level Design Book: Display Models for Entities): https://book.leveldesignbook.com/appendix/resources/formats/fgd#display-models-for-entities
|
||||
## @tutorial(Valve Developer Wiki FGD Article: Entity Description Section): https://developer.valvesoftware.com/wiki/FGD#Entity_Description
|
||||
## @tutorial(TrenchBroom Manual: Display Models for Entities): https://trenchbroom.github.io/manual/latest/#display-models-for-entities
|
||||
|
||||
func _init() -> void:
|
||||
prefix = "@PointClass"
|
||||
|
||||
@export_group ("Scene")
|
||||
## An optional [PackedScene] file to instantiate on map build. Overrides [member FuncGodotFGDEntityClass.node_class] and [member script_class].
|
||||
@export var scene_file: PackedScene
|
||||
|
||||
@export_group ("Scripting")
|
||||
## An optional [Script] resource to attach to the node generated on map build. Ignored if [member scene_file] is specified.
|
||||
@export var script_class: Script
|
||||
|
||||
@export_group("Build")
|
||||
## Toggles whether entity will use `angles`, `mangle`, or `angle` to determine rotations on [FuncGodotMap] build, prioritizing the key value pairs in that order.
|
||||
## Set to [code]false[/code] if you would like to define how the generated node is rotated yourself.
|
||||
@export var apply_rotation_on_map_build : bool = true
|
||||
|
||||
## Toggles whether entity will use `scale` to determine the generated node or scene's scale. This is performed on the top level node.
|
||||
## The property can be a [float], [Vector3], or [Vector2]. Set to [code]false[/code] if you would like to define how the generated node is scaled yourself.
|
||||
@export var apply_scale_on_map_build: bool = true
|
||||
@@ -0,0 +1 @@
|
||||
uid://cxsqwtsqd8w33
|
||||
106
demo/addons/func_godot/src/fgd/func_godot_fgd_solid_class.gd
Normal file
@@ -0,0 +1,106 @@
|
||||
@tool
|
||||
@icon("res://addons/func_godot/icons/icon_slipgate3d.svg")
|
||||
class_name FuncGodotFGDSolidClass extends FuncGodotFGDEntityClass
|
||||
## FGD SolidClass entity definition that generates a mesh from [FuncGodotData.BrushData].
|
||||
##
|
||||
## A [MeshInstance3D] will be generated by [FuncGodotMap] according to this definition's Visual Build settings.
|
||||
## If [member FuncGodotFGDEntityClass.node_class] inherits [CollisionObject3D]
|
||||
## then one or more [CollisionShape3D] nodes will be generated according to Collision Build settings.
|
||||
##
|
||||
## @tutorial(Quake Wiki Entity Article): https://quakewiki.org/wiki/Entity
|
||||
## @tutorial(Level Design Book: Entity Types and Settings): https://book.leveldesignbook.com/appendix/resources/formats/fgd#entity-types-and-settings-basic
|
||||
## @tutorial(Valve Developer Wiki FGD Article): https://developer.valvesoftware.com/wiki/FGD#Class_Types_and_Properties
|
||||
## @tutorial(dumptruck_ds' Quake Mapping Entities Tutorial): https://www.youtube.com/watch?v=gtL9f6_N2WM
|
||||
|
||||
enum SpawnType {
|
||||
WORLDSPAWN = 0, ## Builds the geometry of this entity relative to the FuncGodotMap position.
|
||||
MERGE_WORLDSPAWN = 1, ## This entity's geometry is merged with the [b]worldspawn[/b] entity and this entity is removed. Behavior mimics [b]func_group[/b] in modern Quake compilers.
|
||||
ENTITY = 2, ## This entity is built as its own object. It finds the origin of the entity based on [member origin_type].
|
||||
}
|
||||
|
||||
enum OriginType {
|
||||
AVERAGED = 0, ## Use averaged brush vertices for center position. This is the old Qodot behavior.
|
||||
ABSOLUTE = 1, ## Use [code]origin[/code] class property in global coordinates as the center position.
|
||||
RELATIVE = 2, ## Calculate center position using [code]origin[/code] class property as an offset to the entity's bounding box center.
|
||||
BRUSH = 3, ## Calculate center position based on the bounding box center of all brushes using the 'origin' texture specified in the [FuncGodotMapSettings]. If no Origin Brush is found, fall back to BOUNDS_CENTER. This is the default option and recommended for most entities.
|
||||
BOUNDS_CENTER = 4, ## Use the center of the entity's bounding box for center position.
|
||||
BOUNDS_MINS = 5, ## Use the lowest bounding box coordinates for center position. This is standard Quake and Half-Life brush entity behavior.
|
||||
BOUNDS_MAXS = 6, ## Use the highest bounding box coordinates for center position.
|
||||
}
|
||||
|
||||
enum CollisionShapeType {
|
||||
NONE, ## No collision shape is built. Useful for decorative geometry like vines, hanging wires, grass, etc...
|
||||
CONVEX, ## Will build a Convex CollisionShape3D for each brush used to make this Solid Class. Required for non-[StaticBody3D] nodes like [Area3D].
|
||||
CONCAVE ## Should have a concave collision shape
|
||||
}
|
||||
|
||||
## Controls whether this Solid Class is the worldspawn, is combined with the worldspawn, or is spawned as its own free-standing entity.
|
||||
@export var spawn_type: SpawnType = SpawnType.ENTITY
|
||||
## Controls how this Solid Class determines its center position. Only valid if [member spawn_type] is set to ENTITY.
|
||||
@export var origin_type: OriginType = OriginType.BRUSH
|
||||
|
||||
@export_group("Visual Build")
|
||||
## Controls whether a [MeshInstance3D] is built for this Solid Class.
|
||||
@export var build_visuals : bool = true
|
||||
## Global illumination mode for the generated [MeshInstance3D]. Setting to [b]GI_MODE_STATIC[/b] will unwrap the mesh's UV2 during build.
|
||||
@export var global_illumination_mode : GeometryInstance3D.GIMode = GeometryInstance3D.GI_MODE_STATIC
|
||||
## @deprecated: Use [member global_illumination_mode] instead. [br]Sets generated [MeshInstance3D] to be available for UV2 unwrapping after [FuncGodotMap] build. Utilized in baked lightmapping.
|
||||
@export var use_in_baked_light : bool = true
|
||||
## Shadow casting setting allows for further lightmapping customization.
|
||||
@export var shadow_casting_setting : GeometryInstance3D.ShadowCastingSetting = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
||||
## Automatically build [OccluderInstance3D] for this entity.
|
||||
@export var build_occlusion : bool = false
|
||||
## This Solid Class' [MeshInstance3D] will only be visible for [Camera3D]s whose cull mask includes any of these render layers.
|
||||
@export_flags_3d_render var render_layers: int = 1
|
||||
|
||||
@export_group("Collision Build")
|
||||
## Controls how collisions are built for this Solid Class.
|
||||
@export var collision_shape_type: CollisionShapeType = CollisionShapeType.CONVEX
|
||||
## The physics layers this Solid Class can be detected in.
|
||||
@export_flags_3d_physics var collision_layer: int = 1
|
||||
## The physics layers this Solid Class scans.
|
||||
@export_flags_3d_physics var collision_mask: int = 1
|
||||
## The priority used to solve colliding when penetration occurs. The higher the priority is, the lower the penetration into the Solid Class will be. This can for example be used to prevent the player from breaking through the boundaries of a level.
|
||||
@export var collision_priority: float = 1.0
|
||||
## The collision margin for the Solid Class' collision shapes. Not used in Godot Physics. See [Shape3D] for details.
|
||||
@export var collision_shape_margin: float = 0.04
|
||||
|
||||
## The following properties tell FuncGodot to add a [i]"func_godot_mesh_data"[/i] Dictionary to the metadata of the generated node upon build.
|
||||
## This data is parallelized, so that each element of the array is ordered to reference the same face in the mesh.
|
||||
@export_group("Mesh Metadata")
|
||||
## Add a texture lookup table to the generated node's metadata on build.[br][br]
|
||||
## The data is split between an [Array] of [StringName] called [i]"texture_names"[/i] containing all currently used texture materials
|
||||
## and a [PackedInt32Array] called [i]"textures"[/i] where each element is an index corresponding to the [i]"texture_names"[/i] entries.
|
||||
@export var add_textures_metadata: bool = false
|
||||
## Add a [PackedVector3Array] called [i]"vertices"[/i] to the generated node's metadata on build.[br][br]
|
||||
## This is a list of every vertex in the generated node's [MeshInstance3D]. Every 3 vertices represent a single face.
|
||||
@export var add_vertex_metadata: bool = false
|
||||
## Add a [PackedVector3Array] called [i]"positions"[/i] to the generated node's metadata on build.[br][br]
|
||||
## This is a list of positions for each face, local to the generated node, calculated by averaging the vertices to find the face's center.
|
||||
@export var add_face_position_metadata: bool = false
|
||||
## Add a [PackedVector3Array] called [i]"normals"[/i] to the generated node's metadata on build.[br][br]
|
||||
## Contains a list of each face's normal.
|
||||
@export var add_face_normal_metadata: bool = false
|
||||
## Add a [Dictionary] called [i]"collision_shape_to_face_indices_map"[/i] in the generated node's metadata on build.[br][br]
|
||||
## Contains keys of strings, which are the names of child [CollisionShape3D] nodes, and values of
|
||||
## [PackedInt32Array], containing indices of that child's faces.[br][br]
|
||||
## For example, an element of [br][br][code]{ "entity_1_brush_0_collision_shape" : [0, 1, 3] }[/code][br][br]
|
||||
## shows that this solid class has been generated with one child collision shape named
|
||||
## [i]entity_1_brush_0_collision_shape[/i] which handles 3 faces of the mesh with collision, at indices 0, 1, and 3.
|
||||
@export var add_collision_shape_to_face_indices_metadata : bool = false
|
||||
## [s]Add a [Dictionary] called [i]"collision_shape_to_face_range_map"[/i] in the generated node's metadata on build.[br][br]
|
||||
## Contains keys of strings, which are the names of child [CollisionShape3D] nodes, and values of
|
||||
## [Vector2i], where [i]X[/i] represents the starting index of that child's faces and [i]Y[/i] represents the
|
||||
## ending index.[br][br]
|
||||
## For example, an element of [br][br][code]{ "entity_1_brush_0_collision_shape" : Vector2i(0, 15) }[/code][br][br]
|
||||
## shows that this solid class has been generated with one child collision shape named
|
||||
## [i]entity_1_brush_0_collision_shape[/i] which handles the first 15 faces of the parts of the mesh with collision.[/s]
|
||||
## @deprecated: No longer supported or planned as of 2025.7, but retained in case a contributor provides an appropriate solution in the future.
|
||||
@export var add_collision_shape_face_range_metadata: bool = false
|
||||
|
||||
@export_group("Scripting")
|
||||
## An optional [Script] file to attach to the node generated on map build.
|
||||
@export var script_class: Script
|
||||
|
||||
func _init():
|
||||
prefix = "@SolidClass"
|
||||
@@ -0,0 +1 @@
|
||||
uid://5cow84q03m6a
|
||||
131
demo/addons/func_godot/src/func_godot_plugin.gd
Normal file
@@ -0,0 +1,131 @@
|
||||
@tool
|
||||
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
||||
class_name FuncGodotPlugin extends EditorPlugin
|
||||
|
||||
var map_import_plugin : QuakeMapImportPlugin = null
|
||||
var palette_import_plugin : QuakePaletteImportPlugin = null
|
||||
var wad_import_plugin: QuakeWadImportPlugin = null
|
||||
|
||||
#var func_godot_map_progress_bar: Control = null
|
||||
var edited_object_ref: WeakRef = weakref(null)
|
||||
|
||||
func _get_plugin_name() -> String:
|
||||
return "FuncGodot"
|
||||
|
||||
func _handles(object: Object) -> bool:
|
||||
return object is FuncGodotMap
|
||||
|
||||
func _edit(object: Object) -> void:
|
||||
edited_object_ref = weakref(object)
|
||||
|
||||
#func _make_visible(visible: bool) -> void:
|
||||
#if func_godot_map_progress_bar:
|
||||
#func_godot_map_progress_bar.set_visible(visible)
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# Import plugins
|
||||
map_import_plugin = QuakeMapImportPlugin.new()
|
||||
palette_import_plugin = QuakePaletteImportPlugin.new()
|
||||
wad_import_plugin = QuakeWadImportPlugin.new()
|
||||
|
||||
add_import_plugin(map_import_plugin)
|
||||
add_import_plugin(palette_import_plugin)
|
||||
add_import_plugin(wad_import_plugin)
|
||||
|
||||
#func_godot_map_progress_bar = create_func_godot_map_progress_bar()
|
||||
#func_godot_map_progress_bar.set_visible(false)
|
||||
#add_control_to_container(EditorPlugin.CONTAINER_INSPECTOR_BOTTOM, func_godot_map_progress_bar)
|
||||
|
||||
add_custom_type("FuncGodotMap", "Node3D", preload("res://addons/func_godot/src/map/func_godot_map.gd"), null)
|
||||
|
||||
# Default Map Settings
|
||||
if not ProjectSettings.has_setting("func_godot/default_map_settings"):
|
||||
ProjectSettings.set_setting("func_godot/default_map_settings", "res://addons/func_godot/func_godot_default_map_settings.tres")
|
||||
var property_info = {
|
||||
"name": "func_godot/default_map_settings",
|
||||
"type": TYPE_STRING,
|
||||
"hint": PROPERTY_HINT_FILE,
|
||||
"hint_string": "*.tres"
|
||||
}
|
||||
ProjectSettings.add_property_info(property_info)
|
||||
ProjectSettings.set_as_basic("func_godot/default_map_settings", true)
|
||||
ProjectSettings.set_initial_value("func_godot/default_map_settings", "res://addons/func_godot/func_godot_default_map_settings.tres")
|
||||
|
||||
# Default Inverse Scale Factor
|
||||
if not ProjectSettings.has_setting("func_godot/default_inverse_scale_factor"):
|
||||
ProjectSettings.set_setting("func_godot/default_inverse_scale_factor", 32.0)
|
||||
var property_info = {
|
||||
"name": "func_godot/default_inverse_scale_factor",
|
||||
"type": TYPE_FLOAT
|
||||
}
|
||||
ProjectSettings.add_property_info(property_info)
|
||||
ProjectSettings.set_as_basic("func_godot/default_inverse_scale_factor", true)
|
||||
ProjectSettings.set_initial_value("func_godot/default_inverse_scale_factor", 32.0)
|
||||
|
||||
# Model Point Class Default Path
|
||||
if not ProjectSettings.has_setting("func_godot/model_point_class_save_path"):
|
||||
ProjectSettings.set_setting("func_godot/model_point_class_save_path", "")
|
||||
var property_info = {
|
||||
"name": "func_godot/model_point_class_save_path",
|
||||
"type": TYPE_STRING
|
||||
}
|
||||
ProjectSettings.add_property_info(property_info)
|
||||
ProjectSettings.set_as_basic("func_godot/model_point_class_save_path", true)
|
||||
ProjectSettings.set_initial_value("func_godot/model_point_class_save_path", "")
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_custom_type("FuncGodotMap")
|
||||
remove_import_plugin(map_import_plugin)
|
||||
remove_import_plugin(palette_import_plugin)
|
||||
if wad_import_plugin:
|
||||
remove_import_plugin(wad_import_plugin)
|
||||
|
||||
map_import_plugin = null
|
||||
palette_import_plugin = null
|
||||
wad_import_plugin = null
|
||||
|
||||
#if func_godot_map_progress_bar:
|
||||
#remove_control_from_container(EditorPlugin.CONTAINER_INSPECTOR_BOTTOM, func_godot_map_progress_bar)
|
||||
#func_godot_map_progress_bar.queue_free()
|
||||
#func_godot_map_progress_bar = null
|
||||
|
||||
# Create a progress bar for building a [FuncGodotMap]
|
||||
#func create_func_godot_map_progress_bar() -> Control:
|
||||
#var progress_label = Label.new()
|
||||
#progress_label.name = "ProgressLabel"
|
||||
#progress_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
#progress_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
#
|
||||
#var progress_bar := ProgressBar.new()
|
||||
#progress_bar.name = "ProgressBar"
|
||||
#progress_bar.show_percentage = false
|
||||
#progress_bar.min_value = 0.0
|
||||
#progress_bar.max_value = 1.0
|
||||
#progress_bar.custom_minimum_size.y = 30
|
||||
#progress_bar.set_anchors_and_offsets_preset(Control.PRESET_LEFT_WIDE)
|
||||
#progress_bar.add_child(progress_label)
|
||||
#progress_label.set_anchors_and_offsets_preset(Control.PRESET_LEFT_WIDE)
|
||||
#progress_label.offset_top = -9
|
||||
#progress_label.offset_left = 3
|
||||
#
|
||||
#return progress_bar
|
||||
|
||||
# Update the build progress bar (see: [method create_func_godot_map_progress_bar]) to display the current step and progress (0-1)
|
||||
#func func_godot_map_build_progress(step: String, progress: float) -> void:
|
||||
#var progress_label = func_godot_map_progress_bar.get_node("ProgressLabel")
|
||||
#func_godot_map_progress_bar.value = progress
|
||||
#progress_label.text = step.capitalize()
|
||||
|
||||
## Callback for when the build process for a [FuncGodotMap] is finished.
|
||||
func func_godot_map_build_complete(func_godot_map: FuncGodotMap) -> void:
|
||||
#var progress_label = func_godot_map_progress_bar.get_node("ProgressLabel")
|
||||
#progress_label.text = "Build Complete"
|
||||
|
||||
#if func_godot_map.is_connected("build_progress",Callable(self,"func_godot_map_build_progress")):
|
||||
#func_godot_map.disconnect("build_progress",Callable(self,"func_godot_map_build_progress"))
|
||||
|
||||
if func_godot_map.is_connected("build_complete",Callable(self,"func_godot_map_build_complete")):
|
||||
func_godot_map.disconnect("build_complete",Callable(self,"func_godot_map_build_complete"))
|
||||
|
||||
if func_godot_map.is_connected("build_failed",Callable(self,"func_godot_map_build_complete")):
|
||||
func_godot_map.disconnect("build_failed",Callable(self,"func_godot_map_build_complete"))
|
||||
1
demo/addons/func_godot/src/func_godot_plugin.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bqy3tr83l7di
|
||||