diff --git a/Scenes/main.tscn b/Scenes/main.tscn
index a4504a9..ca0a049 100644
--- a/Scenes/main.tscn
+++ b/Scenes/main.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=11 format=3 uid="uid://bj7y7q2qkpnci"]
+[gd_scene load_steps=18 format=3 uid="uid://bj7y7q2qkpnci"]
[ext_resource type="ArrayMesh" uid="uid://622ethh2pdfa" path="res://Blends/road/road.obj" id="4_jjvhh"]
[ext_resource type="PackedScene" uid="uid://dim2geqhn6d35" path="res://Scenes/Prefabs/cart.tscn" id="6_21xkr"]
@@ -36,6 +36,69 @@ size = Vector2(0.5, 0.5)
[sub_resource type="BoxShape3D" id="BoxShape3D_21xkr"]
+[sub_resource type="Gradient" id="Gradient_jjvhh"]
+offsets = PackedFloat32Array(0.2, 1)
+colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_kry3j"]
+noise_type = 2
+frequency = 0.03
+cellular_jitter = 3.0
+cellular_return_type = 0
+domain_warp_enabled = true
+domain_warp_type = 1
+domain_warp_amplitude = 50.0
+domain_warp_fractal_type = 2
+domain_warp_fractal_lacunarity = 1.5
+domain_warp_fractal_gain = 1.0
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_21xkr"]
+seamless = true
+color_ramp = SubResource("Gradient_jjvhh")
+noise = SubResource("FastNoiseLite_kry3j")
+
+[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_6bp64"]
+_shader_parameters = {
+&"bias_distance": 512.0,
+&"blend_sharpness": 0.5,
+&"depth_blur": 0.0,
+&"enable_macro_variation": true,
+&"enable_projection": true,
+&"flat_terrain_normals": false,
+&"macro_variation1": Color(1, 1, 1, 1),
+&"macro_variation2": Color(1, 1, 1, 1),
+&"macro_variation_slope": 0.333,
+&"mipmap_bias": 1.0,
+&"noise1_angle": 0.0,
+&"noise1_offset": Vector2(0.5, 0.5),
+&"noise1_scale": 0.04,
+&"noise2_scale": 0.076,
+&"noise_texture": SubResource("NoiseTexture2D_21xkr"),
+&"projection_threshold": 0.8
+}
+show_checkered = true
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_344ge"]
+transparency = 4
+cull_mode = 2
+vertex_color_use_as_albedo = true
+backlight_enabled = true
+backlight = Color(0.5, 0.5, 0.5, 1)
+distance_fade_mode = 1
+distance_fade_min_distance = 128.0
+distance_fade_max_distance = 96.0
+
+[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_ynf5e"]
+generated_type = 1
+height_offset = 0.5
+material_override = SubResource("StandardMaterial3D_344ge")
+last_lod = 0
+last_shadow_lod = 0
+lod0_range = 128.0
+
+[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_hptm8"]
+mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_ynf5e")])
+
[node name="Main" type="Node3D"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
@@ -111,3 +174,11 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 20.3213, 36.9694, -59.4263)
[node name="ground2" parent="." instance=ExtResource("6_344ge")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 180.354, 36.969, -59.426)
+
+[node name="Terrain3D" type="Terrain3D" parent="."]
+data_directory = "res://Terrain_data"
+material = SubResource("Terrain3DMaterial_6bp64")
+assets = SubResource("Terrain3DAssets_hptm8")
+show_checkered = true
+top_level = true
+metadata/_edit_lock_ = true
diff --git a/addons/terrain_3d/LICENSE.txt b/addons/terrain_3d/LICENSE.txt
new file mode 100644
index 0000000..368567b
--- /dev/null
+++ b/addons/terrain_3d/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+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:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/addons/terrain_3d/README.md b/addons/terrain_3d/README.md
new file mode 100644
index 0000000..1260ae0
--- /dev/null
+++ b/addons/terrain_3d/README.md
@@ -0,0 +1,52 @@
+
+
+# Terrain3D
+A high performance, editable terrain system for Godot 4.
+
+
+## Features
+* Written in C++ as a GDExtension addon, which works with official builds of Godot Engine
+* [Can be accessed](https://terrain3d.readthedocs.io/en/stable/docs/programming_languages.html) by GDScript, C#, and any language Godot supports
+* Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in non-contiguous and variable sized regions
+* Up to 32 textures
+* Up to 10 levels of detail for the terrain mesh
+* Foliage instancing, with up to 10 levels of detail, and a shadow impostor
+* Sculpting, holes, texture painting, texture detiling, painting colors and wetness
+* Imports heightmaps from [HTerrain](https://github.com/Zylann/godot_heightmap_plugin/), Gaea, World Creator, World Machine, Unity, Unreal and any tool that can export a heightmap. See [heightmaps](https://terrain3d.readthedocs.io/en/stable/docs/heightmaps.html)
+
+
+## Games Using Terrain3D
+
+Please see the [featured games using Terrain3D](https://terrain3d.readthedocs.io/en/latest/docs/games.html) for examples of what it can do.
+
+
+## Getting Started
+
+1. Read the [Introduction](https://terrain3d.readthedocs.io/en/stable/docs/introduction.html) to understand how this terrain system works.
+
+2. Read the [Installation & Upgrade](https://terrain3d.readthedocs.io/en/stable/docs/installation.html) instructions.
+
+3. Watch the [tutorial videos](https://terrain3d.readthedocs.io/en/stable/docs/tutorial_videos.html) and read through the documentation.
+
+4. For support, read [Getting Help](https://terrain3d.readthedocs.io/en/stable/docs/getting_help.html) and join our [Discord server](https://tokisan.com/discord).
+
+
+## Credit
+Developed for the Godot community by:
+
+|||
+|--|--|
+| **Cory Petkovsek, Tokisan Games** | [
](https://twitter.com/TokisanGames) [
](https://github.com/TokisanGames) [
](https://tokisan.com/) [
](https://tokisan.com/discord) [
](https://www.youtube.com/@TokisanGames)|
+| **Roope Palmroos, Outobugi Games** | [
](https://twitter.com/outobugi) [
](https://github.com/outobugi) [
](https://outobugi.com/) [
](https://www.youtube.com/@outobugi)|
+
+And the contribution team in [AUTHORS.md](https://terrain3d.readthedocs.io/en/stable/docs/authors.html) and on the right of the github page.
+
+
+## Contributing
+
+Please see [CONTRIBUTING.md](https://github.com/TokisanGames/Terrain3D/blob/main/CONTRIBUTING.md) if you would like to help make Terrain3D the best terrain system for Godot.
+
+
+## License
+
+This addon has been released under the [MIT License](https://github.com/TokisanGames/Terrain3D/blob/main/LICENSE.txt).
diff --git a/addons/terrain_3d/bin/libterrain.android.debug.arm32.so b/addons/terrain_3d/bin/libterrain.android.debug.arm32.so
new file mode 100644
index 0000000..73873d2
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.android.debug.arm32.so differ
diff --git a/addons/terrain_3d/bin/libterrain.android.debug.arm64.so b/addons/terrain_3d/bin/libterrain.android.debug.arm64.so
new file mode 100644
index 0000000..12a2dda
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.android.debug.arm64.so differ
diff --git a/addons/terrain_3d/bin/libterrain.android.release.arm32.so b/addons/terrain_3d/bin/libterrain.android.release.arm32.so
new file mode 100644
index 0000000..0820e70
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.android.release.arm32.so differ
diff --git a/addons/terrain_3d/bin/libterrain.android.release.arm64.so b/addons/terrain_3d/bin/libterrain.android.release.arm64.so
new file mode 100644
index 0000000..5824276
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.android.release.arm64.so differ
diff --git a/addons/terrain_3d/bin/libterrain.ios.debug.universal.dylib b/addons/terrain_3d/bin/libterrain.ios.debug.universal.dylib
new file mode 100644
index 0000000..1268f1e
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.ios.debug.universal.dylib differ
diff --git a/addons/terrain_3d/bin/libterrain.ios.release.universal.dylib b/addons/terrain_3d/bin/libterrain.ios.release.universal.dylib
new file mode 100644
index 0000000..433e104
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.ios.release.universal.dylib differ
diff --git a/addons/terrain_3d/bin/libterrain.linux.debug.x86_64.so b/addons/terrain_3d/bin/libterrain.linux.debug.x86_64.so
new file mode 100644
index 0000000..67f9efc
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.linux.debug.x86_64.so differ
diff --git a/addons/terrain_3d/bin/libterrain.linux.release.x86_64.so b/addons/terrain_3d/bin/libterrain.linux.release.x86_64.so
new file mode 100644
index 0000000..55aad2f
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.linux.release.x86_64.so differ
diff --git a/addons/terrain_3d/bin/libterrain.macos.debug.framework/libterrain.macos.debug b/addons/terrain_3d/bin/libterrain.macos.debug.framework/libterrain.macos.debug
new file mode 100644
index 0000000..ae20905
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.macos.debug.framework/libterrain.macos.debug differ
diff --git a/addons/terrain_3d/bin/libterrain.macos.release.framework/libterrain.macos.release b/addons/terrain_3d/bin/libterrain.macos.release.framework/libterrain.macos.release
new file mode 100644
index 0000000..3253c76
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.macos.release.framework/libterrain.macos.release differ
diff --git a/addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm b/addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm
new file mode 100644
index 0000000..8517ee3
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm differ
diff --git a/addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm b/addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm
new file mode 100644
index 0000000..45ac636
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm differ
diff --git a/addons/terrain_3d/bin/libterrain.windows.debug.x86_64.dll b/addons/terrain_3d/bin/libterrain.windows.debug.x86_64.dll
new file mode 100644
index 0000000..78f7c45
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.windows.debug.x86_64.dll differ
diff --git a/addons/terrain_3d/bin/libterrain.windows.release.x86_64.dll b/addons/terrain_3d/bin/libterrain.windows.release.x86_64.dll
new file mode 100644
index 0000000..8aa37c5
Binary files /dev/null and b/addons/terrain_3d/bin/libterrain.windows.release.x86_64.dll differ
diff --git a/addons/terrain_3d/brushes/.gdignore b/addons/terrain_3d/brushes/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/addons/terrain_3d/brushes/acrylic1.exr b/addons/terrain_3d/brushes/acrylic1.exr
new file mode 100644
index 0000000..14b66d6
Binary files /dev/null and b/addons/terrain_3d/brushes/acrylic1.exr differ
diff --git a/addons/terrain_3d/brushes/circle0.exr b/addons/terrain_3d/brushes/circle0.exr
new file mode 100644
index 0000000..78937f1
Binary files /dev/null and b/addons/terrain_3d/brushes/circle0.exr differ
diff --git a/addons/terrain_3d/brushes/circle1.exr b/addons/terrain_3d/brushes/circle1.exr
new file mode 100644
index 0000000..e91d97e
Binary files /dev/null and b/addons/terrain_3d/brushes/circle1.exr differ
diff --git a/addons/terrain_3d/brushes/circle2.exr b/addons/terrain_3d/brushes/circle2.exr
new file mode 100644
index 0000000..f6931ba
Binary files /dev/null and b/addons/terrain_3d/brushes/circle2.exr differ
diff --git a/addons/terrain_3d/brushes/circle3.exr b/addons/terrain_3d/brushes/circle3.exr
new file mode 100644
index 0000000..477ab7e
Binary files /dev/null and b/addons/terrain_3d/brushes/circle3.exr differ
diff --git a/addons/terrain_3d/brushes/circle4.exr b/addons/terrain_3d/brushes/circle4.exr
new file mode 100644
index 0000000..b466f92
Binary files /dev/null and b/addons/terrain_3d/brushes/circle4.exr differ
diff --git a/addons/terrain_3d/brushes/hill1.exr b/addons/terrain_3d/brushes/hill1.exr
new file mode 100644
index 0000000..a668ab6
Binary files /dev/null and b/addons/terrain_3d/brushes/hill1.exr differ
diff --git a/addons/terrain_3d/brushes/hill2.exr b/addons/terrain_3d/brushes/hill2.exr
new file mode 100644
index 0000000..068e22b
Binary files /dev/null and b/addons/terrain_3d/brushes/hill2.exr differ
diff --git a/addons/terrain_3d/brushes/mountain1.exr b/addons/terrain_3d/brushes/mountain1.exr
new file mode 100644
index 0000000..be71748
Binary files /dev/null and b/addons/terrain_3d/brushes/mountain1.exr differ
diff --git a/addons/terrain_3d/brushes/mountain2.exr b/addons/terrain_3d/brushes/mountain2.exr
new file mode 100644
index 0000000..ca30373
Binary files /dev/null and b/addons/terrain_3d/brushes/mountain2.exr differ
diff --git a/addons/terrain_3d/brushes/mountain3.exr b/addons/terrain_3d/brushes/mountain3.exr
new file mode 100644
index 0000000..ca07a1e
Binary files /dev/null and b/addons/terrain_3d/brushes/mountain3.exr differ
diff --git a/addons/terrain_3d/brushes/mountain4.exr b/addons/terrain_3d/brushes/mountain4.exr
new file mode 100644
index 0000000..f8197fe
Binary files /dev/null and b/addons/terrain_3d/brushes/mountain4.exr differ
diff --git a/addons/terrain_3d/brushes/peak1.exr b/addons/terrain_3d/brushes/peak1.exr
new file mode 100644
index 0000000..49d341e
Binary files /dev/null and b/addons/terrain_3d/brushes/peak1.exr differ
diff --git a/addons/terrain_3d/brushes/peak2.exr b/addons/terrain_3d/brushes/peak2.exr
new file mode 100644
index 0000000..db74297
Binary files /dev/null and b/addons/terrain_3d/brushes/peak2.exr differ
diff --git a/addons/terrain_3d/brushes/peak3.exr b/addons/terrain_3d/brushes/peak3.exr
new file mode 100644
index 0000000..9383681
Binary files /dev/null and b/addons/terrain_3d/brushes/peak3.exr differ
diff --git a/addons/terrain_3d/brushes/ring1.exr b/addons/terrain_3d/brushes/ring1.exr
new file mode 100644
index 0000000..6396804
Binary files /dev/null and b/addons/terrain_3d/brushes/ring1.exr differ
diff --git a/addons/terrain_3d/brushes/smoke.exr b/addons/terrain_3d/brushes/smoke.exr
new file mode 100644
index 0000000..021947b
Binary files /dev/null and b/addons/terrain_3d/brushes/smoke.exr differ
diff --git a/addons/terrain_3d/brushes/square1.exr b/addons/terrain_3d/brushes/square1.exr
new file mode 100644
index 0000000..3aff9cd
Binary files /dev/null and b/addons/terrain_3d/brushes/square1.exr differ
diff --git a/addons/terrain_3d/brushes/square2.exr b/addons/terrain_3d/brushes/square2.exr
new file mode 100644
index 0000000..230113c
Binary files /dev/null and b/addons/terrain_3d/brushes/square2.exr differ
diff --git a/addons/terrain_3d/brushes/square3.exr b/addons/terrain_3d/brushes/square3.exr
new file mode 100644
index 0000000..6da88b8
Binary files /dev/null and b/addons/terrain_3d/brushes/square3.exr differ
diff --git a/addons/terrain_3d/brushes/square4.exr b/addons/terrain_3d/brushes/square4.exr
new file mode 100644
index 0000000..350cd7d
Binary files /dev/null and b/addons/terrain_3d/brushes/square4.exr differ
diff --git a/addons/terrain_3d/brushes/square5.exr b/addons/terrain_3d/brushes/square5.exr
new file mode 100644
index 0000000..f0832e0
Binary files /dev/null and b/addons/terrain_3d/brushes/square5.exr differ
diff --git a/addons/terrain_3d/brushes/stones.exr b/addons/terrain_3d/brushes/stones.exr
new file mode 100644
index 0000000..7ef5977
Binary files /dev/null and b/addons/terrain_3d/brushes/stones.exr differ
diff --git a/addons/terrain_3d/brushes/terrain1.exr b/addons/terrain_3d/brushes/terrain1.exr
new file mode 100644
index 0000000..8366b52
Binary files /dev/null and b/addons/terrain_3d/brushes/terrain1.exr differ
diff --git a/addons/terrain_3d/brushes/terrain2.exr b/addons/terrain_3d/brushes/terrain2.exr
new file mode 100644
index 0000000..d11a069
Binary files /dev/null and b/addons/terrain_3d/brushes/terrain2.exr differ
diff --git a/addons/terrain_3d/brushes/terrain3.exr b/addons/terrain_3d/brushes/terrain3.exr
new file mode 100644
index 0000000..61bfe0f
Binary files /dev/null and b/addons/terrain_3d/brushes/terrain3.exr differ
diff --git a/addons/terrain_3d/brushes/terrain4.exr b/addons/terrain_3d/brushes/terrain4.exr
new file mode 100644
index 0000000..e8f4ce4
Binary files /dev/null and b/addons/terrain_3d/brushes/terrain4.exr differ
diff --git a/addons/terrain_3d/brushes/terrain5.exr b/addons/terrain_3d/brushes/terrain5.exr
new file mode 100644
index 0000000..ad3fe5b
Binary files /dev/null and b/addons/terrain_3d/brushes/terrain5.exr differ
diff --git a/addons/terrain_3d/brushes/terrain6.exr b/addons/terrain_3d/brushes/terrain6.exr
new file mode 100644
index 0000000..0a15419
Binary files /dev/null and b/addons/terrain_3d/brushes/terrain6.exr differ
diff --git a/addons/terrain_3d/brushes/texture1.exr b/addons/terrain_3d/brushes/texture1.exr
new file mode 100644
index 0000000..f456b77
Binary files /dev/null and b/addons/terrain_3d/brushes/texture1.exr differ
diff --git a/addons/terrain_3d/brushes/texture2.exr b/addons/terrain_3d/brushes/texture2.exr
new file mode 100644
index 0000000..2624d3b
Binary files /dev/null and b/addons/terrain_3d/brushes/texture2.exr differ
diff --git a/addons/terrain_3d/brushes/texture3.exr b/addons/terrain_3d/brushes/texture3.exr
new file mode 100644
index 0000000..690fe5e
Binary files /dev/null and b/addons/terrain_3d/brushes/texture3.exr differ
diff --git a/addons/terrain_3d/brushes/texture4.exr b/addons/terrain_3d/brushes/texture4.exr
new file mode 100644
index 0000000..a2d96aa
Binary files /dev/null and b/addons/terrain_3d/brushes/texture4.exr differ
diff --git a/addons/terrain_3d/brushes/texture5.exr b/addons/terrain_3d/brushes/texture5.exr
new file mode 100644
index 0000000..62aad60
Binary files /dev/null and b/addons/terrain_3d/brushes/texture5.exr differ
diff --git a/addons/terrain_3d/brushes/vegetation1.exr b/addons/terrain_3d/brushes/vegetation1.exr
new file mode 100644
index 0000000..d65bc6e
Binary files /dev/null and b/addons/terrain_3d/brushes/vegetation1.exr differ
diff --git a/addons/terrain_3d/extras/3rd_party/import_sgt.gd b/addons/terrain_3d/extras/3rd_party/import_sgt.gd
new file mode 100644
index 0000000..44bb685
--- /dev/null
+++ b/addons/terrain_3d/extras/3rd_party/import_sgt.gd
@@ -0,0 +1,42 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Import From SimpleGrassTextured
+#
+# This script demonstrates how to import transforms from SimpleGrassTextured. To use it:
+#
+# 1. Setup the mesh asset you wish to use in the asset dock.
+# 1. Select your Terrain3D node.
+# 1. In the inspector, click Script (very bottom) and Quick Load import_sgt.gd.
+# 1. At the very top, assign your SimpleGrassTextured node.
+# 1. Input the desired mesh asset ID.
+# 1. Click import. The output window and console will report when finished.
+# 1. Clear the script from your Terrain3D node, and save your scene.
+#
+# The instance transforms are now stored in your region files.
+#
+# Use clear_instances to erase all instances that match the assign_mesh_id.
+#
+# The add_transforms function (called by add_multimesh) applies the height_offset specified in the
+# Terrain3DMeshAsset.
+# Once the transforms are imported, you can reassign any mesh you like into this mesh slot.
+
+@tool
+extends Terrain3D
+
+@export var simple_grass_textured: MultiMeshInstance3D
+@export var assign_mesh_id: int
+@export var import: bool = false : set = import_sgt
+@export var clear_instances: bool = false : set = clear_multimeshes
+
+
+func clear_multimeshes(value: bool) -> void:
+ get_instancer().clear_by_mesh(assign_mesh_id)
+
+
+func import_sgt(value: bool) -> void:
+ var sgt_mm: MultiMesh = simple_grass_textured.multimesh
+ var global_xform: Transform3D = simple_grass_textured.global_transform
+ print("Starting to import %d instances from SimpleGrassTextured using mesh id %d" % [ sgt_mm.instance_count, assign_mesh_id])
+ var time: int = Time.get_ticks_msec()
+ get_instancer().add_multimesh(assign_mesh_id, sgt_mm, simple_grass_textured.global_transform)
+ print("Import complete in %.2f seconds" % [ float(Time.get_ticks_msec() - time)/1000. ])
+
diff --git a/addons/terrain_3d/extras/3rd_party/import_sgt.gd.uid b/addons/terrain_3d/extras/3rd_party/import_sgt.gd.uid
new file mode 100644
index 0000000..00b3868
--- /dev/null
+++ b/addons/terrain_3d/extras/3rd_party/import_sgt.gd.uid
@@ -0,0 +1 @@
+uid://bllcuwetve45k
diff --git a/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd b/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd
new file mode 100644
index 0000000..860014e
--- /dev/null
+++ b/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd
@@ -0,0 +1,137 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter
+# It provides a `Project on Terrain3D` modifier, which allows Scatter
+# to detect the terrain height from Terrain3D without using collision.
+#
+# Copy this file into /addons/proton_scatter/src/modifiers
+# Then uncomment everything below (select, press CTRL+K)
+# In the editor, add this modifier to Scatter, then set your Terrain3D node
+
+#@tool
+#extends "base_modifier.gd"
+#
+#
+#signal projection_completed
+#
+#
+#@export var terrain_node : NodePath
+#@export var align_with_collision_normal : bool = false
+#@export_range(0.0, 90.0, 0.1) var max_slope : float = 90.0
+#@export var enable_texture_filtering : bool = false
+#@export_range(0, 31) var target_texture_id : int = 0
+#@export var not_target_texture : bool = false
+#@export_range(0.0, 1.0, 0.01) var texture_threshold : float = 0.8
+#
+#var _terrain: Terrain3D
+#
+#
+#func _init() -> void:
+ #display_name = "Project On Terrain3D"
+ #category = "Edit"
+ #can_restrict_height = false
+ #global_reference_frame_available = true
+ #local_reference_frame_available = true
+ #individual_instances_reference_frame_available = true
+ #use_global_space_by_default()
+#
+ #documentation.add_paragraph(
+ #"This is a modified version of `Project on Colliders` that queries Terrain3D
+ #for heights without using collision. It constrains placement by slope or texture.
+#
+ #This modifier must have terrain_node set to a Terrain3D node.")
+#
+ #var p := documentation.add_parameter("Terrain Node")
+ #p.set_type("NodePath")
+ #p.set_description("Set your Terrain3D node.")
+ #
+ #p = documentation.add_parameter("Align with collision normal")
+ #p.set_type("bool")
+ #p.set_description(
+ #"Rotate the transform to align it with the collision normal in case
+ #the ray cast hit a collider.")
+ #
+ #p = documentation.add_parameter("Enable Texture Filtering")
+ #p.set_type("bool")
+ #p.set_description(
+ #"If enabled, objects will only be placed based on the ground texture specified.")
+ #
+ #p = documentation.add_parameter("Target Texture ID")
+ #p.set_type("int")
+ #p.set_description(
+ #"The ID of the texture to place objects on (0-31). Objects will only be placed on this texture.")
+ #
+ #p = documentation.add_parameter("Not Target Texture")
+ #p.set_type("bool")
+ #p.set_description(
+ #"If true, objects will be placed on all textures EXCEPT the target texture.")
+ #
+ #p = documentation.add_parameter("Texture Threshold")
+ #p.set_type("float")
+ #p.set_description("The blend value required for placement on the texture.")
+#
+#
+#func _process_transforms(transforms, domain, _seed) -> void:
+ #if transforms.is_empty():
+ #return
+#
+ #if terrain_node:
+ #_terrain = domain.get_root().get_node_or_null(terrain_node)
+#
+ #if not _terrain:
+ #warning += """No Terrain3D node found"""
+ #return
+#
+ #if not _terrain.data:
+ #warning += """Terrain3DData is not initialized"""
+ #return
+#
+ ## Review transforms
+ #var gt: Transform3D = domain.get_global_transform()
+ #var gt_inverse := gt.affine_inverse()
+ #var new_transforms_array: Array[Transform3D] = []
+ #var remapped_max_slope: float = remap(max_slope, 0.0, 90.0, 0.0, 1.0)
+ #for i in transforms.list.size():
+ #var t: Transform3D = transforms.list[i]
+ #
+ #var location: Vector3 = (gt * t).origin
+ #var height: float = _terrain.data.get_height(location)
+ #if is_nan(height):
+ #continue
+ #
+ #var normal: Vector3 = _terrain.data.get_normal(location)
+ #if not abs(Vector3.UP.dot(normal)) >= (1.0 - remapped_max_slope):
+ #continue
+ #
+ #if enable_texture_filtering:
+ #var texture_info: Vector3 = _terrain.data.get_texture_id(location)
+ #var base_id: int = int(texture_info.x)
+ #var overlay_id: int = int(texture_info.y)
+ #var blend_value: float = texture_info.z
+ ## Skip if overlay or blend != target texture, unless inverted
+ #if ((overlay_id != target_texture_id or blend_value < texture_threshold) and \
+ #(base_id != target_texture_id or blend_value >= texture_threshold)) != not_target_texture:
+ #continue
+#
+ #if align_with_collision_normal and not is_nan(normal.x):
+ #t.basis.y = normal
+ #t.basis.x = -t.basis.z.cross(normal)
+ #t.basis = t.basis.orthonormalized()
+#
+ #t.origin.y = height - gt.origin.y
+ #new_transforms_array.push_back(t)
+#
+ #transforms.list.clear()
+ #transforms.list.append_array(new_transforms_array)
+#
+ #if transforms.is_empty():
+ #warning += """All transforms have been removed. Possible reasons include: \n"""
+ #if enable_texture_filtering:
+ #warning += """+ No matching texture found at any position.
+ #+ Texture threshold may be too high.
+ #"""
+ #warning += """+ No collider is close enough to the shapes.
+ #+ Ray length is too short.
+ #+ Ray direction is incorrect.
+ #+ Collision mask is not set properly.
+ #+ Max slope is too low.
+ #"""
diff --git a/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd.uid b/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd.uid
new file mode 100644
index 0000000..6911309
--- /dev/null
+++ b/addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd.uid
@@ -0,0 +1 @@
+uid://g3opjh3m3iww
diff --git a/addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn b/addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn
new file mode 100644
index 0000000..0b5e615
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn
@@ -0,0 +1,25 @@
+[gd_scene load_steps=5 format=3 uid="uid://d3sr0a7dxfkr8"]
+
+[ext_resource type="Script" uid="uid://bp7r4ppgq1m0g" path="res://addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd" id="1_gl3qg"]
+[ext_resource type="Material" uid="uid://el5y10hnh13g" path="res://addons/terrain_3d/extras/particle_example/process_material.tres" id="2_2gon1"]
+[ext_resource type="Material" uid="uid://ljo1wt61kbkq" path="res://addons/terrain_3d/extras/particle_example/grass_material.tres" id="3_qyjnw"]
+
+[sub_resource type="RibbonTrailMesh" id="RibbonTrailMesh_fwrtk"]
+shape = 0
+section_length = 0.18
+section_segments = 1
+
+[node name="Terrain3DParticles" type="Node3D"]
+script = ExtResource("1_gl3qg")
+instance_spacing = 0.25
+cell_width = 24.0
+grid_width = 5
+rows = 96
+amount = 9216
+process_material = ExtResource("2_2gon1")
+mesh = SubResource("RibbonTrailMesh_fwrtk")
+shadow_mode = 0
+mesh_material_override = ExtResource("3_qyjnw")
+min_draw_distance = 60.0
+particle_count = 230400
+metadata/_edit_lock_ = true
diff --git a/addons/terrain_3d/extras/particle_example/grass.gdshader b/addons/terrain_3d/extras/particle_example/grass.gdshader
new file mode 100644
index 0000000..a149fd6
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/grass.gdshader
@@ -0,0 +1,69 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+shader_type spatial;
+
+render_mode skip_vertex_transform, cull_disabled, blend_mix;
+
+uniform vec2 wind_direction = vec2(1.0, 1.0);
+
+varying vec4 data;
+
+// A lot of hard coded things here atm.
+void vertex() {
+ // Wind effect from model data, in this case no vertex colors,
+ // so just use vertex Y component, including mesh offset
+ data[2] = (VERTEX.y + 0.55);
+ data[2] *= data[2]; // make non-linear
+
+ // Ribbon used as a grass mesh.. so pinch the top.
+ VERTEX.xz *= (1.0 - data[2]);
+
+ // Brighten tips
+ COLOR = mix(COLOR, vec4(1.0), smoothstep(0.9, 1.0, data[2]));
+ // Darken base, skip is scale is less than threshold, as this means "grow in" is occuring.
+ COLOR *= INSTANCE_CUSTOM[3] < 0.35 ? 1. : mix(1.0, 0.75, smoothstep(0.35, 0.0, data[2]));
+ // Save red/green shift for fragment
+ data.rg = INSTANCE_CUSTOM.rg;
+
+ // World space vertex
+ vec3 w_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
+ // Get wind force and scale from process()
+ float scale = pow(INSTANCE_CUSTOM[3] * INSTANCE_CUSTOM[3], 0.707);
+ float force = INSTANCE_CUSTOM[2] * data[2] * scale;
+ // Add some cheap jitter at high wind values
+ force -= fract(force * 256.0) * force * 0.05;
+ // Curve the result
+ force = pow(force, 0.707);
+
+ // These 2 combined result in a decent bend without resorting to matrices or pivot data.
+ // Lateral move and wobble
+ float lateral_wobble = sin(TIME * 2.0 * (1.0 + data.r + data.g)) * 0.25 * (1.0 - INSTANCE_CUSTOM[2]);
+ vec2 direction = normalize(wind_direction);
+ w_vertex.xz -= (vec2(-direction.y, direction.x) * lateral_wobble + direction) * force;
+ // Flatten
+ w_vertex.y -= INSTANCE_CUSTOM[2] * force * data[2];
+
+ // Save final wind force value for fragment.
+ data[3] = force;
+
+ VERTEX = (VIEW_MATRIX * vec4(w_vertex, 1.0)).xyz;
+ NORMAL = MODELVIEW_NORMAL_MATRIX * NORMAL;
+ BINORMAL = MODELVIEW_NORMAL_MATRIX * BINORMAL;
+ TANGENT = MODELVIEW_NORMAL_MATRIX * TANGENT;
+}
+
+void fragment() {
+ // Hard coded color.
+ ALBEDO = vec3(0.20, 0.22, 0.05) * (data[2] * 0.5 + 0.5);
+ ALBEDO.rg *= (data.rg * 0.3 + 0.9);
+ ALBEDO *= pow(COLOR.rgb, vec3(2.2));
+ // Modify roughness / specular based on wind force for added detail
+ float spec_rough = clamp(max(data[2], data[3]), 0., 1.);
+ ROUGHNESS = 1. - spec_rough;
+ SPECULAR = clamp(spec_rough * 0.25, 0., .15);
+
+ BACKLIGHT = vec3(0.33);
+ #if CURRENT_RENDERER == RENDERER_COMPATIBILITY
+ ALBEDO = pow(ALBEDO, vec3(0.4));
+ #endif
+}
diff --git a/addons/terrain_3d/extras/particle_example/grass.gdshader.uid b/addons/terrain_3d/extras/particle_example/grass.gdshader.uid
new file mode 100644
index 0000000..94b0237
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/grass.gdshader.uid
@@ -0,0 +1 @@
+uid://dq3lfyp3u5oxt
diff --git a/addons/terrain_3d/extras/particle_example/grass_material.tres b/addons/terrain_3d/extras/particle_example/grass_material.tres
new file mode 100644
index 0000000..2fd2fe8
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/grass_material.tres
@@ -0,0 +1,8 @@
+[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://ljo1wt61kbkq"]
+
+[ext_resource type="Shader" uid="uid://dq3lfyp3u5oxt" path="res://addons/terrain_3d/extras/particle_example/grass.gdshader" id="1_nkru0"]
+
+[resource]
+render_priority = 0
+shader = ExtResource("1_nkru0")
+shader_parameter/wind_direction = Vector2(1, 1)
diff --git a/addons/terrain_3d/extras/particle_example/particles.gdshader b/addons/terrain_3d/extras/particle_example/particles.gdshader
new file mode 100644
index 0000000..300198c
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/particles.gdshader
@@ -0,0 +1,275 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+// This is an example particle shader designed to procedurally place
+// particles like grass, small rocks, and other ground effects, on the terrain
+// surface by reading the Terrain3D data maps. It works in tandem with the
+// provided GDScript.
+
+shader_type particles;
+render_mode disable_velocity, disable_force;
+
+group_uniforms options;
+uniform sampler2D main_noise;
+uniform float main_noise_scale = 0.01;
+uniform vec3 position_offset = vec3(0.);
+uniform bool align_to_normal = true;
+uniform float normal_strength : hint_range(0.01, 1.0, 0.01) = 0.3;
+uniform bool random_rotation = true;
+uniform float random_spacing : hint_range(0.0, 1.0, 0.01) = 0.5;
+uniform vec3 min_scale = vec3(1.0);
+uniform vec3 max_scale = vec3(1.0);
+
+group_uniforms wind;
+uniform float noise_scale = 0.0041;
+uniform float wind_speed = 0.025;
+uniform float wind_strength : hint_range(0.0, 1.0, 0.01) = 1.0;
+uniform float wind_dithering = 4.0;
+uniform vec2 wind_direction = vec2(1.0,1.0);
+
+group_uniforms shapeing;
+uniform float clod_scale_boost = 3.0;
+uniform float clod_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.2;
+uniform float clod_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.5;
+uniform float patch_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.025;
+uniform float patch_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.2;
+
+group_uniforms filtering;
+uniform float condition_dither_range : hint_range(0.0, 1.0, 0.01) = 0.15;
+uniform float surface_slope_min : hint_range(0.0, 1.0, 0.01) = 0.87;
+uniform float distance_fade_ammount : hint_range(0.0, 1.0, 0.01) = 0.5;
+
+group_uniforms private;
+uniform float max_dist = 1.;
+uniform vec3 camera_position = vec3(0.);
+uniform uint instance_rows = 1;
+uniform float instance_spacing = 0.5;
+uniform uint _background_mode = 0u;
+uniform float _vertex_spacing = 1.0;
+uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
+uniform float _region_size = 1024.0;
+uniform float _region_texel_size = 0.0009765625; // = 1/REGION_SIZE
+uniform int _region_map_size = 32;
+uniform int _region_map[1024];
+uniform vec2 _region_locations[1024];
+uniform highp sampler2DArray _height_maps : repeat_disable;
+uniform highp sampler2DArray _control_maps : repeat_disable;
+uniform highp sampler2DArray _color_maps : repeat_disable;
+
+// Defined Constants
+#define SKIP_PASS 0
+#define VERTEX_PASS 1
+#define FRAGMENT_PASS 2
+
+// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
+// Returns ivec3 with:
+// XY: (0 to _region_size - 1) coordinates within a region
+// Z: layer index used for texturearrays, -1 if not in a region
+ivec3 get_index_coord(const vec2 uv, const int search) {
+ vec2 r_uv = round(uv);
+ vec2 o_uv = mod(r_uv,_region_size);
+ ivec2 pos;
+ int bounds, layer_index = -1;
+ for (int i = -1; i < 0; i++) {
+ if ((layer_index == -1 && _background_mode == 0u) || i < 0) {
+ r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
+ pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
+ bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
+ }
+ }
+ return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
+}
+
+#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
+ #define fma(a, b, c) ((a) * (b) + (c))
+#endif
+float random(vec2 v) {
+ return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x)))));
+}
+
+mat3 rotation_matrix(vec3 axis, float angle) {
+ float c = cos(angle);
+ float s = sin(angle);
+ float t = 1.0 - c;
+ vec3 n = normalize(axis);
+ float x = n.x;
+ float y = n.y;
+ float z = n.z;
+
+ return mat3(
+ vec3(t * x * x + c, t * x * y - z * s, t * x * z + y * s),
+ vec3(t * x * y + z * s, t * y * y + c, t * y * z - x * s),
+ vec3(t * x * z - y * s, t * y * z + x * s, t * z * z + c));
+}
+
+mat3 align_to_vector(vec3 normal) {
+ vec3 up = vec3(0.0, 1.0, 0.0);
+ if (abs(dot(normal, up)) > 0.9999) { // Avoid singularity
+ up = vec3(1.0, 0.0, 0.0);
+ }
+ vec3 tangent = normalize(cross(up, normal));
+ vec3 bitangent = normalize(cross(tangent, normal));
+ return mat3(tangent, normal, bitangent);
+}
+
+void start() {
+ // Create centered a grid
+ vec3 pos = vec3(float(INDEX % instance_rows), 0.0, float(INDEX / instance_rows)) - float(instance_rows >> 1u);
+
+ // Apply spcaing
+ pos *= instance_spacing;
+ // Move the grid to the emitter, snapping is handled CPU side
+ pos.xz += EMISSION_TRANSFORM[3].xz;
+
+ // Create random values per-instance, incorporating the seed, mask bits to avoid NAN/INF
+ float seed = fract(uintBitsToFloat(RANDOM_SEED & 0x7EFFFFFFu));
+ vec3 r = fract(vec3(random(pos.xz), random(pos.xz + vec2(0.5)), random(pos.xz - vec2(0.5))) + seed);
+ // Randomize instance spacing
+ pos.x += ((r.x * 2.0) - 1.0) * random_spacing * instance_spacing;
+ pos.z += ((r.z * 2.0) - 1.0) * random_spacing * instance_spacing;
+
+ // Lookup offsets, ID and blend weight
+ const vec3 offsets = vec3(0, 1, 2);
+ vec2 index_id = floor(pos.xz * _vertex_density);
+ vec2 weight = fract(pos.xz * _vertex_density);
+ vec2 invert = 1.0 - weight;
+ vec4 weights = vec4(
+ invert.x * weight.y, // 0
+ weight.x * weight.y, // 1
+ weight.x * invert.y, // 2
+ invert.x * invert.y // 3
+ );
+
+ ivec3 index[4];
+ // Map lookups
+ index[0] = get_index_coord(index_id + offsets.xy, VERTEX_PASS);
+ index[1] = get_index_coord(index_id + offsets.yy, VERTEX_PASS);
+ index[2] = get_index_coord(index_id + offsets.yx, VERTEX_PASS);
+ index[3] = get_index_coord(index_id + offsets.xx, VERTEX_PASS);
+
+ highp float h[8];
+ h[0] = texelFetch(_height_maps, index[0], 0).r; // 0 (0,1)
+ h[1] = texelFetch(_height_maps, index[1], 0).r; // 1 (1,1)
+ h[2] = texelFetch(_height_maps, index[2], 0).r; // 2 (1,0)
+ h[3] = texelFetch(_height_maps, index[3], 0).r; // 3 (0,0)
+ h[4] = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, VERTEX_PASS), 0).r; // 4 (1,2)
+ h[5] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, VERTEX_PASS), 0).r; // 5 (2,1)
+ h[6] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, VERTEX_PASS), 0).r; // 6 (2,0)
+ h[7] = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, VERTEX_PASS), 0).r; // 7 (0,2)
+ vec3 index_normal[4];
+ index_normal[0] = vec3(h[0] - h[1], _vertex_spacing, h[0] - h[7]);
+ index_normal[1] = vec3(h[1] - h[5], _vertex_spacing, h[1] - h[4]);
+ index_normal[2] = vec3(h[2] - h[6], _vertex_spacing, h[2] - h[1]);
+ index_normal[3] = vec3(h[3] - h[2], _vertex_spacing, h[3] - h[0]);
+ vec3 w_normal = normalize(
+ index_normal[0] * weights[0] +
+ index_normal[1] * weights[1] +
+ index_normal[2] * weights[2] +
+ index_normal[3] * weights[3]);
+
+ // Set the height according to the heightmap data
+ pos.y =
+ h[0] * weights[0] +
+ h[1] * weights[1] +
+ h[2] * weights[2] +
+ h[3] * weights[3] ;
+
+ // Offset, Rotation, Alignment.
+ TRANSFORM = mat4(1.0);
+ vec3 orientation = vec3(0., 1., 0.);
+
+ vec2 uv = (pos.xz) * main_noise_scale;
+ float noise = textureLod(main_noise, uv, 0.0).r;
+ float clods = smoothstep(clod_min_threshold, clod_max_threshold, noise) * clod_scale_boost;
+ float patch = smoothstep(patch_min_threshold, patch_max_threshold, noise);
+ float width_modifier = 1.0 + 3.0 * smoothstep(0., max_dist, length(camera_position - pos));
+
+ // Calculate scale
+ vec3 scale = vec3(
+ mix(min_scale.x, max_scale.x, r.x) * width_modifier,
+ mix(min_scale.y, max_scale.y, r.y) + clods,
+ mix(min_scale.z, max_scale.z, r.z) * width_modifier) * patch;
+
+ // Apply scale to offset
+ vec3 offset = position_offset * scale;
+
+ // Apply normal orientation
+ if (align_to_normal) {
+ orientation = mix(orientation, w_normal, normal_strength);
+ mat3 alignment = align_to_vector(orientation);
+ offset = alignment * offset;
+ TRANSFORM = mat4(alignment);
+ }
+
+ // Apply rotation around orientation
+ if (random_rotation) {
+ mat3 rotation = rotation_matrix(orientation, r.x * TAU);
+ TRANSFORM = mat4(rotation) * TRANSFORM;
+ }
+
+ // Filtering - Causes some particles to be rendered as degenerate triangles
+ // via 0./0. - Particles filtered this way are still processed by the GPU.
+ // For compatibility it seems we must shift as well.
+ // Surface slope filtering
+ if (surface_slope_min > w_normal.y + (r.y - 0.5) * condition_dither_range) {
+ pos.y = 0. / 0.;
+ pos.xz = vec2(100000.0);
+ }
+
+ // Read color map
+ highp vec4 c[4];
+ #define COLOR_MAP vec4(1., 1., 1., 0.5)
+ c[0] = index[0].z >= 0 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP; // 0 (0,1)
+ c[1] = index[1].z >= 0 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP; // 1 (1,1)
+ c[2] = index[2].z >= 0 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP; // 2 (1,0)
+ c[3] = index[3].z >= 0 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP; // 3 (0,0)
+ vec4 color_map =
+ c[0] * weights[0] +
+ c[1] * weights[1] +
+ c[2] * weights[2] +
+ c[3] * weights[3] ;
+
+ COLOR = color_map;
+
+ // Read control map
+ uint control = floatBitsToUint(texelFetch(_control_maps, index[3], 0).r);
+ bool auto = bool(control & 0x1u);
+ int base = int(control >>27u & 0x1Fu);
+ int over = int(control >> 22u & 0x1Fu);
+ float blend = float(control >> 14u & 0xFFu) * 0.003921568627450; // 1. / 255.
+
+ // Hardcoded example, hand painted texture id 0 is filtered out.
+ if (!auto && ((base == 0 && blend < 0.7) || (over == 0 && blend >= 0.3))) {
+ pos.y = 0. / 0.;
+ pos.xz = vec2(100000.0);
+ }
+
+ if (length(camera_position - pos) > max_dist) {
+ pos.y = 0. / 0.;
+ pos.xz = vec2(100000.0);
+ } else {
+ float fade_factor = 1.0 - smoothstep(max_dist * distance_fade_ammount, max_dist + 0.0001, length(camera_position - pos)) + 0.001;
+ scale.y *= fade_factor;
+ offset *= fade_factor;
+ }
+
+ // Apply scale
+ TRANSFORM[0] *= scale.x;
+ TRANSFORM[1] *= scale.y;
+ TRANSFORM[2] *= scale.z;
+
+ // Apply the position
+ TRANSFORM[3].xyz = pos.xyz + offset;
+
+ // Save Fixed 2 Random values for Reg/Green color randomness
+ CUSTOM.rg = r.rg;
+ // Save Y component scale pre-rotation
+ CUSTOM[3] = scale.y;
+}
+
+void process() {
+ // Extract world space UV from Transform Matrix
+ vec2 uv = (TRANSFORM[3].xz + CUSTOM.rg * wind_dithering) * noise_scale;
+ // Scaled wind noise, updated per instance, at process FPS. Passed to Vertex()
+ CUSTOM[2] = textureLod(main_noise, uv + TIME * wind_speed * normalize(wind_direction), 0.0).r * wind_strength;
+}
diff --git a/addons/terrain_3d/extras/particle_example/particles.gdshader.uid b/addons/terrain_3d/extras/particle_example/particles.gdshader.uid
new file mode 100644
index 0000000..595a815
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/particles.gdshader.uid
@@ -0,0 +1 @@
+uid://dce675i014xcn
diff --git a/addons/terrain_3d/extras/particle_example/process_material.tres b/addons/terrain_3d/extras/particle_example/process_material.tres
new file mode 100644
index 0000000..ad7e259
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/process_material.tres
@@ -0,0 +1,49 @@
+[gd_resource type="ShaderMaterial" load_steps=4 format=3 uid="uid://el5y10hnh13g"]
+
+[ext_resource type="Shader" uid="uid://dce675i014xcn" path="res://addons/terrain_3d/extras/particle_example/particles.gdshader" id="1_t55me"]
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_544iv"]
+noise_type = 2
+frequency = 0.0145
+cellular_return_type = 4
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_544iv"]
+seamless = true
+noise = SubResource("FastNoiseLite_544iv")
+
+[resource]
+shader = ExtResource("1_t55me")
+shader_parameter/main_noise = SubResource("NoiseTexture2D_544iv")
+shader_parameter/main_noise_scale = 0.01
+shader_parameter/position_offset = Vector3(0, 0.45, 0)
+shader_parameter/align_to_normal = true
+shader_parameter/normal_strength = 0.3
+shader_parameter/random_rotation = true
+shader_parameter/random_spacing = 0.5
+shader_parameter/min_scale = Vector3(0.125, 0.5, 0.125)
+shader_parameter/max_scale = Vector3(0.125, 1, 0.125)
+shader_parameter/noise_scale = 0.0041
+shader_parameter/wind_speed = 0.025
+shader_parameter/wind_strength = 1.0
+shader_parameter/wind_dithering = 4.0
+shader_parameter/wind_direction = Vector2(1, 1)
+shader_parameter/clod_scale_boost = 2.0
+shader_parameter/clod_min_threshold = 0.2
+shader_parameter/clod_max_threshold = 0.8
+shader_parameter/patch_min_threshold = 0.025
+shader_parameter/patch_max_threshold = 0.2
+shader_parameter/condition_dither_range = 0.15
+shader_parameter/surface_slope_min = 0.87
+shader_parameter/distance_fade_ammount = 0.66
+shader_parameter/max_dist = 1.0
+shader_parameter/camera_position = Vector3(0, 0, 0)
+shader_parameter/instance_rows = 1
+shader_parameter/instance_spacing = 0.5
+shader_parameter/_background_mode = 0
+shader_parameter/_vertex_spacing = 1.0
+shader_parameter/_vertex_density = 1.0
+shader_parameter/_region_size = 1024.0
+shader_parameter/_region_texel_size = 0.000976563
+shader_parameter/_region_map_size = 32
+shader_parameter/_region_map = PackedInt32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+shader_parameter/_region_locations = PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
diff --git a/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd b/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd
new file mode 100644
index 0000000..46f7572
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd
@@ -0,0 +1,237 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+#
+# This is an example of using a particle shader with Terrain3D.
+# To use it, add `Terrain3DParticles.tscn` to your scene and assign the terrain.
+# Then customize the settings, materials and shader to extend it and make it your own.
+
+@tool
+extends Node3D
+
+
+#region settings
+## Auto set if attached as a child of a Terrain3D node
+@export var terrain: Terrain3D:
+ set(value):
+ terrain = value
+ _create_grid()
+
+
+## Distance between instances
+@export_range(0.125, 2.0, 0.015625) var instance_spacing: float = 0.5:
+ set(value):
+ instance_spacing = clamp(round(value * 64.0) * 0.015625, 0.125, 2.0)
+ rows = maxi(int(cell_width / instance_spacing), 1)
+ amount = rows * rows
+ _set_offsets()
+
+
+## Width of an individual cell of the grid
+@export_range(8.0, 256.0, 1.0) var cell_width: float = 32.0:
+ set(value):
+ cell_width = clamp(value, 8.0, 256.0)
+ rows = maxi(int(cell_width / instance_spacing), 1)
+ amount = rows * rows
+ min_draw_distance = 1.0
+ # Have to update aabb
+ if terrain and terrain.data:
+ var height_range: Vector2 = terrain.data.get_height_range()
+ var height: float = height_range[0] - height_range[1]
+ var aabb: AABB = AABB()
+ aabb.size = Vector3(cell_width, height, cell_width)
+ aabb.position = aabb.size * -0.5
+ aabb.position.y = height_range[1]
+ for p in particle_nodes:
+ p.custom_aabb = aabb
+ _set_offsets()
+
+
+## Grid width. Must be odd.
+## Higher values cull slightly better, draw further out.
+@export_range(1, 15, 2) var grid_width: int = 9:
+ set(value):
+ grid_width = value
+ particle_count = 1
+ min_draw_distance = 1.0
+ _create_grid()
+
+
+@export_storage var rows: int = 1
+
+@export_storage var amount: int = 1:
+ set(value):
+ amount = value
+ particle_count = value
+ last_pos = Vector3.ZERO
+ for p in particle_nodes:
+ p.amount = amount
+
+
+@export_range(1, 256, 1) var process_fixed_fps: int = 30:
+ set(value):
+ process_fixed_fps = maxi(value, 1)
+ for p in particle_nodes:
+ p.fixed_fps = process_fixed_fps
+ p.preprocess = 1.0 / float(process_fixed_fps)
+
+
+## Access to process material parameters
+@export var process_material: ShaderMaterial
+
+## The mesh that each particle will render
+@export var mesh: Mesh
+
+@export var shadow_mode: GeometryInstance3D.ShadowCastingSetting = (
+ GeometryInstance3D.ShadowCastingSetting.SHADOW_CASTING_SETTING_ON):
+ set(value):
+ shadow_mode = value
+ for p in particle_nodes:
+ p.cast_shadow = value
+
+
+## Override material for the particle mesh
+@export_custom(
+ PROPERTY_HINT_RESOURCE_TYPE,
+ "BaseMaterial3D,ShaderMaterial") var mesh_material_override: Material:
+ set(value):
+ mesh_material_override = value
+ for p in particle_nodes:
+ p.material_override = mesh_material_override
+
+
+@export_group("Info")
+## The minimum distance that particles will be drawn upto
+## If using fade out effects like pixel alpha this is the limit to use.
+@export var min_draw_distance: float = 1.0:
+ set(value):
+ min_draw_distance = float(cell_width * grid_width) * 0.5
+
+
+## Displays current total particle count based on Cell Width and Instance Spacing
+@export var particle_count: int = 1:
+ set(value):
+ particle_count = amount * grid_width * grid_width
+
+#endregion
+
+
+var offsets: Array[Vector3]
+var last_pos: Vector3 = Vector3.ZERO
+var particle_nodes: Array[GPUParticles3D]
+
+
+func _ready() -> void:
+ if not terrain:
+ var parent: Node = get_parent()
+ if parent is Terrain3D:
+ terrain = parent
+ _create_grid()
+
+
+func _notification(what: int) -> void:
+ if what == NOTIFICATION_PREDELETE:
+ _destroy_grid()
+
+
+func _physics_process(delta: float) -> void:
+ if terrain:
+ var camera: Camera3D = terrain.get_camera()
+ if camera:
+ if last_pos.distance_squared_to(camera.global_position) > 1.0:
+ var pos: Vector3 = camera.global_position.snapped(Vector3.ONE)
+ _position_grid(pos)
+ RenderingServer.material_set_param(process_material.get_rid(), "camera_position", pos )
+ last_pos = camera.global_position
+ _update_process_parameters()
+ else:
+ set_physics_process(false)
+
+
+func _create_grid() -> void:
+ _destroy_grid()
+ if not terrain:
+ return
+ set_physics_process(true)
+ _set_offsets()
+ var hr: Vector2 = terrain.data.get_height_range()
+ var height: float = hr.x - hr.y
+ var aabb: AABB = AABB()
+ aabb.size = Vector3(cell_width, height, cell_width)
+ aabb.position = aabb.size * -0.5
+ aabb.position.y = hr.y
+ var half_grid: int = grid_width / 2
+ # Iterating the array like this allows identifying grid position, in case setting
+ # different mesh or materials is desired for LODs etc.
+ for x in range(-half_grid, half_grid + 1):
+ for z in range(-half_grid, half_grid + 1):
+ #var ring: int = maxi(maxi(absi(x), absi(z)), 0)
+ var particle_node = GPUParticles3D.new()
+ particle_node.lifetime = 600.0
+ particle_node.amount = amount
+ particle_node.explosiveness = 1.0
+ particle_node.amount_ratio = 1.0
+ particle_node.process_material = process_material
+ particle_node.draw_pass_1 = mesh
+ particle_node.speed_scale = 1.0
+ particle_node.custom_aabb = aabb
+ particle_node.cast_shadow = shadow_mode
+ particle_node.fixed_fps = process_fixed_fps
+ # This prevent minor grid alignment errors when the camera is moving very fast
+ particle_node.preprocess = 1.0 / float(process_fixed_fps)
+ if mesh_material_override:
+ particle_node.material_override = mesh_material_override
+ particle_node.use_fixed_seed = true
+ if (x > -half_grid and z > -half_grid): # Use the same seed across all nodes
+ particle_node.seed = particle_nodes[0].seed
+ self.add_child(particle_node)
+ particle_node.emitting = true
+ particle_nodes.push_back(particle_node)
+ last_pos = Vector3.ZERO
+
+
+func _set_offsets() -> void:
+ var half_grid: int = grid_width / 2
+ offsets.clear()
+ for x in range(-half_grid, half_grid + 1):
+ for z in range(-half_grid, half_grid + 1):
+ var offset := Vector3(
+ float(x * rows) * instance_spacing,
+ 0.0,
+ float(z * rows) * instance_spacing
+ )
+ offsets.append(offset)
+
+
+func _destroy_grid() -> void:
+ for node: GPUParticles3D in particle_nodes:
+ if is_instance_valid(node):
+ node.queue_free()
+ particle_nodes.clear()
+
+
+func _position_grid(pos: Vector3) -> void:
+ for i in particle_nodes.size():
+ var node: GPUParticles3D = particle_nodes[i]
+ var snap = Vector3(pos.x, 0, pos.z).snapped(Vector3.ONE) + offsets[i]
+ node.global_position = (snap / instance_spacing).round() * instance_spacing
+ node.reset_physics_interpolation()
+ node.restart(true) # keep the same seed.
+
+
+func _update_process_parameters() -> void:
+ if process_material:
+ var process_rid: RID = process_material.get_rid()
+ if terrain and process_rid.is_valid():
+ RenderingServer.material_set_param(process_rid, "_background_mode", terrain.material.world_background)
+ RenderingServer.material_set_param(process_rid, "_vertex_spacing", terrain.vertex_spacing)
+ RenderingServer.material_set_param(process_rid, "_vertex_density", 1.0 / terrain.vertex_spacing)
+ RenderingServer.material_set_param(process_rid, "_region_size", terrain.region_size)
+ RenderingServer.material_set_param(process_rid, "_region_texel_size", 1.0 / terrain.region_size)
+ RenderingServer.material_set_param(process_rid, "_region_map_size", 32)
+ RenderingServer.material_set_param(process_rid, "_region_map", terrain.data.get_region_map())
+ RenderingServer.material_set_param(process_rid, "_region_locations", terrain.data.get_region_locations())
+ RenderingServer.material_set_param(process_rid, "_height_maps", terrain.data.get_height_maps_rid())
+ RenderingServer.material_set_param(process_rid, "_control_maps", terrain.data.get_control_maps_rid())
+ RenderingServer.material_set_param(process_rid, "_color_maps", terrain.data.get_color_maps_rid())
+ RenderingServer.material_set_param(process_rid, "instance_spacing", instance_spacing)
+ RenderingServer.material_set_param(process_rid, "instance_rows", rows)
+ RenderingServer.material_set_param(process_rid, "max_dist", min_draw_distance)
diff --git a/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd.uid b/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd.uid
new file mode 100644
index 0000000..b365859
--- /dev/null
+++ b/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd.uid
@@ -0,0 +1 @@
+uid://bp7r4ppgq1m0g
diff --git a/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc b/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc
new file mode 100644
index 0000000..2163989
--- /dev/null
+++ b/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc
@@ -0,0 +1,67 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+// This shader snippet draws a hex grid
+
+// To use it, add this line to the top of your shader:
+// #include "res://addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc"
+
+// And this line at the bottom of your shader:
+// draw_hex_grid(uv2, _region_texel_size, w_normal, ALBEDO);
+
+mat2 rotate2d(float _angle) {
+ return mat2(vec2(cos(_angle),-sin(_angle)), vec2(sin(_angle), cos(_angle)));
+}
+
+void draw_hex_grid(vec2 uv, float texel_size, vec3 normal, inout vec3 albedo) {
+ float hex_size = 0.02;
+ float line_thickness = 0.04;
+
+ vec2 guv = (uv - vec2(0.5 * texel_size)) / hex_size;
+
+ // Convert UV to axial hex coordinates
+ float q = (sqrt(3.0) / 3.0 * guv.x - 1.0 / 3.0 * guv.y);
+ float r = (2.0 / 3.0 * guv.y);
+
+ // Cube coordinates for the hex (q, r, -q-r)
+ float x = q;
+ float z = r;
+ float y = -x - z;
+
+ // Round to the nearest hex center
+ vec3 rounded = round(vec3(x, y, z));
+ vec3 diff = abs(vec3(x, y, z) - rounded);
+
+ // Fix rounding errors
+ if (diff.x > diff.y && diff.x > diff.z) {
+ rounded.x = -rounded.y - rounded.z;
+ } else if (diff.y > diff.z) {
+ rounded.y = -rounded.x - rounded.z;
+ } else {
+ rounded.z = -rounded.x - rounded.y;
+ }
+
+ // Find the hex center in UV space
+ vec2 hex_center = vec2(
+ sqrt(3.0) * rounded.x + sqrt(3.0) / 2.0 * rounded.z,
+ 3.0 / 2.0 * rounded.z
+ );
+
+ // Relative position within the hex
+ vec2 local_pos = guv - hex_center;
+ vec2 lines_uv = local_pos;
+ float line = 1.0;
+
+ for (int i = 0; i < 6; i++) {
+ vec2 luv = lines_uv * rotate2d(radians(60.0 * float(i) + 30.0));
+ float dist = abs(dot(luv + vec2(0.90), vec2(0.0, 1.0)));
+ line = min(line, dist);
+ }
+
+ // Filter lines by slope
+ float slope = 4.; // Can also assign to (auto_slope * 4.) to match grass placement
+ float slope_factor = clamp(dot(vec3(0., 1., 0.), slope * (normal - 1.) + 1.), 0., 1.);
+
+ // Draw hex grid
+ albedo = mix(albedo, vec3(1.0), smoothstep(line_thickness + 0.02, line_thickness, line) * slope_factor);
+ // Draw Hex center dot
+ albedo = mix(albedo, vec3(0.0, 0.5, 0.5), smoothstep(0.11, 0.10, length(local_pos)) * slope_factor);
+}
\ No newline at end of file
diff --git a/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc.uid b/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc.uid
new file mode 100644
index 0000000..feee7f6
--- /dev/null
+++ b/addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc.uid
@@ -0,0 +1 @@
+uid://mri8pfoj2mfk
diff --git a/addons/terrain_3d/extras/shaders/lightweight.gdshader b/addons/terrain_3d/extras/shaders/lightweight.gdshader
new file mode 100644
index 0000000..7a4087c
--- /dev/null
+++ b/addons/terrain_3d/extras/shaders/lightweight.gdshader
@@ -0,0 +1,367 @@
+shader_type spatial;
+render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
+
+/* This is an example stripped down shader with maximum performance in mind.
+ * Only Autoshader/Base/Over/Blend/Holes/Colormap are supported.
+ * All terrain normal calculations take place in vetex().
+ *
+ * Control map indices are processed such that each ID only requires reading ONCE.
+ * The following features: projection, detiling, and paintable rotation / scale
+ * cannot work with this method, without the additional samples required for blending
+ * between same ID textures with different values across indices.
+ */
+
+// Defined Constants
+#define SKIP_PASS 0
+#define VERTEX_PASS 1
+#define FRAGMENT_PASS 2
+#define COLOR_MAP vec4(1.0, 1.0, 1.0, 0.5)
+#define DIV_255 0.003921568627450 // 1. / 255.
+
+// Inline Functions
+#define DECODE_BLEND(control) float(control >> 14u & 0xFFu) * DIV_255
+#define DECODE_AUTO(control) bool(control & 0x1u)
+#define DECODE_BASE(control) int(control >> 27u & 0x1Fu)
+#define DECODE_OVER(control) int(control >> 22u & 0x1Fu)
+#define DECODE_HOLE(control) bool(control >>2u & 0x1u)
+
+#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
+ #define fma(a, b, c) ((a) * (b) + (c))
+ #define dFdxCoarse(a) dFdx(a)
+ #define dFdyCoarse(a) dFdy(a)
+#endif
+
+// Private uniforms
+uniform vec3 _camera_pos = vec3(0.f);
+uniform float _mesh_size = 48.f;
+uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
+uniform uint _mouse_layer = 0x80000000u; // Layer 32
+uniform float _vertex_spacing = 1.0;
+uniform float _vertex_density = 1.0; // = 1./_vertex_spacing
+uniform float _region_size = 1024.0;
+uniform float _region_texel_size = 0.0009765625; // = 1./region_size
+uniform int _region_map_size = 32;
+uniform int _region_map[1024];
+uniform vec2 _region_locations[1024];
+uniform float _texture_normal_depth_array[32];
+uniform float _texture_ao_strength_array[32];
+uniform float _texture_roughness_mod_array[32];
+uniform float _texture_uv_scale_array[32];
+uniform vec4 _texture_color_array[32];
+uniform highp sampler2DArray _height_maps : repeat_disable;
+uniform highp sampler2DArray _control_maps : repeat_disable;
+uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
+uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
+uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
+
+// Public uniforms
+uniform bool enable_texturing = true;
+uniform float blend_sharpness : hint_range(0, 1) = 0.5;
+uniform bool flat_terrain_normals = false;
+// Autoshader
+uniform float auto_slope : hint_range(0, 10) = 1.0;
+uniform float auto_height_reduction : hint_range(0, 1) = 0.1;
+uniform int auto_base_texture : hint_range(0, 31) = 0;
+uniform int auto_overlay_texture : hint_range(0, 31) = 1;
+// Macro Variation
+uniform bool enable_macro_variation = true;
+uniform vec3 macro_variation1 : source_color = vec3(1.);
+uniform vec3 macro_variation2 : source_color = vec3(1.);
+uniform float macro_variation_slope : hint_range(0., 1.) = 0.333;
+uniform highp sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
+uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x
+uniform float noise1_angle : hint_range(0, 6.283) = 0.;
+uniform vec2 noise1_offset = vec2(0.5);
+uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x
+
+// Varyings & Types
+varying vec3 v_normal;
+varying vec3 v_vertex;
+varying mat3 TBN;
+
+////////////////////////
+// Vertex
+////////////////////////
+
+// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
+// Returns ivec3 with:
+// XY: (0 to _region_size - 1) coordinates within a region
+// Z: layer index used for texturearrays, -1 if not in a region
+ivec3 get_index_coord(const vec2 uv, const int search) {
+ vec2 r_uv = round(uv);
+ vec2 o_uv = mod(r_uv,_region_size);
+ ivec2 pos;
+ int bounds, layer_index = -1;
+ for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
+ if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
+ r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
+ pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
+ bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
+ }
+ }
+ return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
+}
+
+// Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with:
+// XY: (0. to 1.) coordinates within a region
+// Z: layer index used for texturearrays, -1 if not in a region
+vec3 get_index_uv(const vec2 uv2) {
+ ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2);
+ int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
+ return vec3(uv2 - _region_locations[layer_index], float(layer_index));
+}
+
+void vertex() {
+ // Get vertex of flat plane in world coordinates and set world UV
+ v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
+
+ // Camera distance to vertex on flat plane
+ float v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
+
+ // Geomorph vertex, set end and start for linear height interpolate
+ float scale = MODEL_MATRIX[0][0];
+ float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
+ vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
+ // For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
+ vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
+ // Shift from regular to symetric
+ mix(v_fract, vec2(v_fract.x, -v_fract.y),
+ round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
+ round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
+ ) :
+ // Symetric shift
+ v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
+ vec2 start_pos = v_vertex.xz * _vertex_density;
+ vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
+ v_vertex.xz -= shift * scale * vertex_lerp;
+
+ // UV coordinates in world space. Values are 0 to _region_size within regions
+ UV = v_vertex.xz * _vertex_density;
+
+ // UV coordinates in region space + texel offset. Values are 0 to 1 within regions
+ UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
+
+ // Discard vertices for Holes. 1 lookup
+ ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
+ uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
+ bool hole = DECODE_HOLE(control);
+
+ // Show holes to all cameras except mouse camera (on exactly 1 layer)
+ if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
+ (hole || (_background_mode == 0u && v_region.z == -1))) {
+ v_vertex.x = 0. / 0.;
+ } else {
+ // Set final vertex height & calculate vertex normals. 3 lookups
+ ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
+ ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
+ float h = mix(texelFetch(_height_maps, uv_a, 0).r,texelFetch(_height_maps, uv_b, 0).r,vertex_lerp);
+ v_vertex.y = h;
+
+ // Vertex normals
+ float u = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(1,0), VERTEX_PASS), 0).r,
+ texelFetch(_height_maps, get_index_coord(end_pos + vec2(1,0), VERTEX_PASS), 0).r, vertex_lerp);
+ float v = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(0,1), VERTEX_PASS), 0).r,
+ texelFetch(_height_maps, get_index_coord(end_pos + vec2(0,1), VERTEX_PASS), 0).r, vertex_lerp);
+
+ v_normal = normalize(vec3(h - u, _vertex_spacing, h - v));
+ }
+
+ // Convert model space to view space w/ skip_vertex_transform render mode
+ VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
+
+ // Apply terrain normals
+ vec3 w_tangent = normalize(cross(v_normal, vec3(0.0, 0.0, 1.0)));
+ vec3 w_binormal = normalize(cross(v_normal, w_tangent));
+ TBN = mat3(w_tangent, w_binormal, v_normal);
+
+ NORMAL = normalize((VIEW_MATRIX * vec4(v_normal, 0.0)).xyz);
+ BINORMAL = normalize((VIEW_MATRIX * vec4(w_binormal, 0.0)).xyz);
+ TANGENT = normalize((VIEW_MATRIX * vec4(w_tangent, 0.0)).xyz);
+}
+
+////////////////////////
+// Fragment
+////////////////////////
+
+mat2 rotate_plane(float angle) {
+ float c = cos(angle), s = sin(angle);
+ return mat2(vec2(c, s), vec2(-s, c));
+}
+
+void fragment() {
+ // Recover UVs
+ vec2 uv = UV;
+ vec2 uv2 = UV2;
+
+ // Lookup offsets, ID and blend weight
+ vec3 region_uv = get_index_uv(uv2);
+ const vec3 offsets = vec3(0, 1, 2);
+ vec2 index_id = floor(uv);
+ vec2 weight = fract(uv);
+ vec2 invert = 1.0 - weight;
+ vec4 weights = vec4(
+ invert.x * weight.y, // 0
+ weight.x * weight.y, // 1
+ weight.x * invert.y, // 2
+ invert.x * invert.y // 3
+ );
+
+ ivec3 index[4];
+ // control map lookups, used for some normal lookups as well
+ index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
+ index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
+ index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
+ index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
+
+ vec3 base_ddx = dFdxCoarse(v_vertex);
+ vec3 base_ddy = dFdyCoarse(v_vertex);
+ vec4 base_dd = vec4(base_ddx.xz, base_ddy.xz);
+ // Calculate the effective mipmap for regionspace
+ float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
+
+ // Color map
+ vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP;
+
+ if (flat_terrain_normals) {
+ NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX)));
+ TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz));
+ BINORMAL = normalize(cross(NORMAL, TANGENT));
+ }
+
+ // defaults
+ vec4 normal_rough = vec4(0., 1., 0., 0.7);
+ vec4 albedo_height = vec4(1.);
+ float normal_map_depth = 1.;
+ float ao_strength = 0.;
+
+ if (enable_texturing) {
+ // set to zero before accumulation
+ albedo_height = vec4(0.);
+ normal_rough = vec4(0.);
+ normal_map_depth = 0.;
+ ao_strength = 0.;
+ float total_weight = 0.;
+ float sharpness = fma(56., blend_sharpness, 8.);
+
+ // Get index control data
+ // 1 - 4 lookups
+ uvec4 control = floatBitsToUint(vec4(
+ texelFetch(_control_maps, index[0], 0).r,
+ texelFetch(_control_maps, index[1], 0).r,
+ texelFetch(_control_maps, index[2], 0).r,
+ texelFetch(_control_maps, index[3], 0).r));
+
+ {
+ // Auto blend calculation
+ float auto_blend = clamp(fma(auto_slope * 2.0, (v_normal.y - 1.0), 1.0)
+ - auto_height_reduction * 0.01 * v_vertex.y, 0.0, 1.0);
+ // Enable Autoshader if outside regions or painted in regions, otherwise manual painted
+ uvec4 is_auto = (control & uvec4(0x1u)) | uvec4(uint(region_uv.z < 0.0));
+ uint u_auto =
+ ((uint(auto_base_texture) & 0x1Fu) << 27u) |
+ ((uint(auto_overlay_texture) & 0x1Fu) << 22u) |
+ ((uint(fma(auto_blend, 255.0 , 0.5)) & 0xFFu) << 14u);
+ control = control * (1u - is_auto) + u_auto * is_auto;
+ }
+
+
+ // Texture weights
+ // Vectorised Deocode of all texture IDs, then swizzle to per index mapping.
+ ivec4 t_id[2] = {ivec4(control >> uvec4(27u) & uvec4(0x1Fu)),
+ ivec4(control >> uvec4(22u) & uvec4(0x1Fu))};
+ ivec2 texture_ids[4] = ivec2[4](
+ ivec2(t_id[0].x, t_id[1].x),
+ ivec2(t_id[0].y, t_id[1].y),
+ ivec2(t_id[0].z, t_id[1].z),
+ ivec2(t_id[0].w, t_id[1].w));
+
+ // interpolated weights.
+ vec4 weights_id_1 = vec4(control >> uvec4(14u) & uvec4(0xFFu)) * DIV_255 * weights;
+ vec4 weights_id_0 = weights - weights_id_1;
+ vec2 t_weights[4] = {vec2(0), vec2(0), vec2(0), vec2(0)};
+ for (int i = 0; i < 4; i++) {
+ vec2 w_0 = vec2(weights_id_0[i]);
+ vec2 w_1 = vec2(weights_id_1[i]);
+ ivec2 id_0 = texture_ids[i].xx;
+ ivec2 id_1 = texture_ids[i].yy;
+ t_weights[0] += fma(w_0, vec2(equal(texture_ids[0], id_0)), w_1 * vec2(equal(texture_ids[0], id_1)));
+ t_weights[1] += fma(w_0, vec2(equal(texture_ids[1], id_0)), w_1 * vec2(equal(texture_ids[1], id_1)));
+ t_weights[2] += fma(w_0, vec2(equal(texture_ids[2], id_0)), w_1 * vec2(equal(texture_ids[2], id_1)));
+ t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1)));
+ }
+
+
+ // Process control data to determine each texture ID present, so that only
+ // a single sample will be needed later, as all id are contiguous when features
+ // like detiling, scale, rotation, and projection are not present.
+ // 2 to 16 lookups
+ uint id_read = 0u; // 1 bit per possible ID
+ // world normal adjustment requires acess to previous id during next iteration
+ vec4 nrm = vec4(0.0, 1.0, 0.0, 1.0);
+ // adjust uv scale to account for vertex spacing
+ uv *= _vertex_spacing;
+ for (int i = 0; i < 4; i++) {
+ for (int t = 0; t < 2; t++) {
+ int id = texture_ids[i][t];
+ uint mask = 1u << uint(id);
+ if ((id_read & mask) == 0u) {
+ // Set this id bit
+ id_read |= mask;
+ float id_w = t_weights[i][t];
+ float id_scale = _texture_uv_scale_array[id] * 0.5;
+ vec2 id_uv = fma(uv, vec2(id_scale), vec2(0.5));
+ vec4 i_dd = base_dd * id_scale;
+ vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw);
+ float world_normal = clamp(fma(TBN[0], vec3(nrm.x), fma(TBN[1], vec3(nrm.z), v_normal * vec3(nrm.y))).y, 0., 1.);
+ nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw);
+ alb.rgb *= _texture_color_array[id].rgb;
+ nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.);
+ // Unpack normal map for blending.
+ nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0));
+ // height weight modifier.
+ float id_weight = exp2(sharpness * log2(id_w + alb.a * world_normal));
+ albedo_height += alb * id_weight;
+ normal_rough += nrm * id_weight;
+ normal_map_depth += _texture_normal_depth_array[id] * id_weight;
+ ao_strength += _texture_ao_strength_array[id] * id_weight;
+ total_weight += id_weight;
+ }
+ }
+ }
+ // normalize accumulated values back to 0.0 - 1.0 range.
+ float weight_inv = 1.0 / total_weight;
+ albedo_height *= weight_inv;
+ normal_rough *= weight_inv;
+ normal_map_depth *= weight_inv;
+ ao_strength *= weight_inv;
+ }
+
+ // Macro variation. 2 lookups
+ vec3 macrov = vec3(1.);
+ if (enable_macro_variation) {
+ float noise1 = texture(noise_texture, (uv * noise1_scale * .1 + noise1_offset) * rotate_plane(noise1_angle)).r;
+ float noise2 = texture(noise_texture, uv * noise2_scale * .1).r;
+ macrov = mix(macro_variation1, vec3(1.), noise1);
+ macrov *= mix(macro_variation2, vec3(1.), noise2);
+ macrov = mix(vec3(1.0), macrov, clamp(v_normal.y + macro_variation_slope, 0., 1.));
+ }
+
+ // Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range, clamped to Godot roughness values
+ float roughness = clamp(fma(color_map.a - 0.5, 2.0, normal_rough.a), 0., 1.);
+
+ // Apply PBR
+ ALBEDO = albedo_height.rgb * color_map.rgb * macrov;
+ ROUGHNESS = roughness;
+ SPECULAR = 1. - normal_rough.a;
+ // Repack final normal map value.
+ NORMAL_MAP = fma(normalize(normal_rough.xzy), vec3(0.5), vec3(0.5));
+ NORMAL_MAP_DEPTH = normal_map_depth;
+
+ // Higher and/or facing up, less occluded.
+ float ao = (1. - (albedo_height.a * log(2.1 - ao_strength))) * (1. - normal_rough.y);
+ AO = clamp(1. - ao * ao_strength, albedo_height.a, 1.0);
+ AO_LIGHT_AFFECT = (1.0 - albedo_height.a) * clamp(normal_rough.y, 0., 1.);
+
+}
diff --git a/addons/terrain_3d/extras/shaders/lightweight.gdshader.uid b/addons/terrain_3d/extras/shaders/lightweight.gdshader.uid
new file mode 100644
index 0000000..b4cd1db
--- /dev/null
+++ b/addons/terrain_3d/extras/shaders/lightweight.gdshader.uid
@@ -0,0 +1 @@
+uid://bbx2xhanpq5l3
diff --git a/addons/terrain_3d/extras/shaders/minimum.gdshader b/addons/terrain_3d/extras/shaders/minimum.gdshader
new file mode 100644
index 0000000..b7a95e7
--- /dev/null
+++ b/addons/terrain_3d/extras/shaders/minimum.gdshader
@@ -0,0 +1,217 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+// This shader is the minimum needed to allow the terrain to function, without any texturing.
+
+shader_type spatial;
+render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
+
+// Defined Constants
+#define SKIP_PASS 0
+#define VERTEX_PASS 1
+#define FRAGMENT_PASS 2
+
+#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
+ #define fma(a, b, c) ((a) * (b) + (c))
+ #define dFdxCoarse(a) dFdx(a)
+ #define dFdyCoarse(a) dFdy(a)
+#endif
+
+// Private uniforms
+// Commented uniforms aren't needed for this shader, but are available for your own needs.
+uniform vec3 _camera_pos = vec3(0.f);
+uniform float _mesh_size = 48.f;
+uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
+uniform uint _mouse_layer = 0x80000000u; // Layer 32
+uniform float _vertex_spacing = 1.0;
+uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
+uniform float _region_size = 1024.0;
+uniform float _region_texel_size = 0.0009765625; // = 1/1024
+uniform int _region_map_size = 32;
+uniform int _region_map[1024];
+//uniform vec2 _region_locations[1024];
+//uniform float _texture_uv_scale_array[32];
+//uniform float _texture_detile_array[32];
+//uniform vec4 _texture_color_array[32];
+uniform highp sampler2DArray _height_maps : repeat_disable;
+uniform highp sampler2DArray _control_maps : repeat_disable;
+//uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
+//uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
+//uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
+
+// Public Uniforms
+uniform bool flat_terrain_normals = false;
+
+// Varyings & Types
+// Some are required for editor functions
+varying float v_vertex_xz_dist;
+varying vec3 v_vertex;
+
+////////////////////////
+// Vertex
+////////////////////////
+
+// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
+// Returns ivec3 with:
+// XY: (0 to _region_size - 1) coordinates within a region
+// Z: layer index used for texturearrays, -1 if not in a region
+ivec3 get_index_coord(const vec2 uv, const int search) {
+ vec2 r_uv = round(uv);
+ vec2 o_uv = mod(r_uv,_region_size);
+ ivec2 pos;
+ int bounds, layer_index = -1;
+ for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
+ if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
+ r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
+ pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
+ bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
+ }
+ }
+ return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
+}
+
+void vertex() {
+ // Get vertex of flat plane in world coordinates and set world UV
+ v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
+
+ // Camera distance to vertex on flat plane
+ v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
+
+ // Geomorph vertex, set end and start for linear height interpolate
+ float scale = MODEL_MATRIX[0][0];
+ float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
+ vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
+ // For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
+ vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
+ // Shift from regular to symetric
+ mix(v_fract, vec2(v_fract.x, -v_fract.y),
+ round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
+ round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
+ ) :
+ // Symetric shift
+ v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
+ vec2 start_pos = v_vertex.xz * _vertex_density;
+ vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
+ v_vertex.xz -= shift * scale * vertex_lerp;
+
+ // UV coordinates in world space. Values are 0 to _region_size within regions
+ UV = v_vertex.xz * _vertex_density;
+
+ // UV coordinates in region space + texel offset. Values are 0 to 1 within regions
+ UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
+
+ // Discard vertices for Holes. 1 lookup
+ ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
+ uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
+ bool hole = bool(control >>2u & 0x1u);
+
+ // Show holes to all cameras except mouse camera (on exactly 1 layer)
+ if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
+ (hole || (_background_mode == 0u && v_region.z < 0))) {
+ v_vertex.x = 0. / 0.;
+ } else {
+ // Interpolate Geomorph Start & End, set height. 2 Lookups.
+ ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
+ ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
+ float h = mix(texelFetch(_height_maps, uv_a, 0).r, texelFetch(_height_maps, uv_b, 0).r, vertex_lerp);
+ v_vertex.y = h;
+ }
+
+ // Convert model space to view space w/ skip_vertex_transform render mode
+ VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
+ NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
+ BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
+ TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
+}
+
+////////////////////////
+// Fragment
+////////////////////////
+
+void fragment() {
+ // Recover UVs
+ vec2 uv = UV;
+ //vec2 uv2 = UV2;
+
+ // Lookup offsets, ID and blend weight
+ const vec3 offsets = vec3(0, 1, 2);
+ vec2 index_id = floor(uv);
+ vec2 weight = fract(uv);
+ vec2 invert = 1.0 - weight;
+ vec4 weights = vec4(
+ invert.x * weight.y, // 0
+ weight.x * weight.y, // 1
+ weight.x * invert.y, // 2
+ invert.x * invert.y // 3
+ );
+
+ vec3 base_ddx = dFdxCoarse(v_vertex);
+ vec3 base_ddy = dFdyCoarse(v_vertex);
+ //vec4 base_derivatives = vec4(base_ddx.xz, base_ddy.xz);
+ // Calculate the effective mipmap for regionspace, and if less than 0,
+ // skip all extra lookups required for bilinear blend.
+ float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
+ bool bilerp = region_mip < 0.0;
+
+ ivec3 index[4];
+ // control map lookups, used for some normal lookups as well
+ index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
+ index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
+ index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
+ index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
+
+ // Terrain normals
+ vec3 index_normal[4];
+ float h[4];
+ // allows additional derivatives, eg world noise, brush previews etc
+ float u = 0.0;
+ float v = 0.0;
+
+ // Re-use index[] for the first lookups, skipping some math. 3 lookups
+ h[3] = texelFetch(_height_maps, index[3], 0).r; // 0 (0,0)
+ h[2] = texelFetch(_height_maps, index[2], 0).r; // 1 (1,0)
+ h[0] = texelFetch(_height_maps, index[0], 0).r; // 2 (0,1)
+ index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v));
+
+ // Set flat world normal - overriden if bilerp is true
+ vec3 w_normal = index_normal[3];
+
+ // Branching smooth normals must be done seperatley for correct normals at all 4 index ids
+ if (bilerp) {
+ // 5 lookups
+ // Fetch the additional required height values for smooth normals
+ h[1] = texelFetch(_height_maps, index[1], 0).r; // 3 (1,1)
+ float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1,2)
+ float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2,1)
+ float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2,0)
+ float h_7 = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, FRAGMENT_PASS), 0).r; // 7 (0,2)
+
+ // Calculate the normal for the remaining index ids.
+ index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v));
+ index_normal[1] = normalize(vec3(h[1] - h_5 + u, _vertex_spacing, h[1] - h_4 + v));
+ index_normal[2] = normalize(vec3(h[2] - h_6 + u, _vertex_spacing, h[2] - h[1] + v));
+
+ // Set interpolated world normal
+ w_normal =
+ index_normal[0] * weights[0] +
+ index_normal[1] * weights[1] +
+ index_normal[2] * weights[2] +
+ index_normal[3] * weights[3] ;
+ }
+
+ // Apply terrain normals
+ vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0)));
+ vec3 w_binormal = normalize(cross(w_normal, w_tangent));
+ NORMAL = mat3(VIEW_MATRIX) * w_normal;
+ TANGENT = mat3(VIEW_MATRIX) * w_tangent;
+ BINORMAL = mat3(VIEW_MATRIX) * w_binormal;
+
+ if (flat_terrain_normals) {
+ NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX)));
+ TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz));
+ BINORMAL = normalize(cross(NORMAL, TANGENT));
+ }
+
+ // Apply PBR
+ ALBEDO = vec3(.2);
+ ROUGHNESS = .7;
+}
diff --git a/addons/terrain_3d/extras/shaders/minimum.gdshader.uid b/addons/terrain_3d/extras/shaders/minimum.gdshader.uid
new file mode 100644
index 0000000..8d390b2
--- /dev/null
+++ b/addons/terrain_3d/extras/shaders/minimum.gdshader.uid
@@ -0,0 +1 @@
+uid://01qauauvd8aa
diff --git a/addons/terrain_3d/icons/autoshader.svg b/addons/terrain_3d/icons/autoshader.svg
new file mode 100644
index 0000000..5e6ee1a
--- /dev/null
+++ b/addons/terrain_3d/icons/autoshader.svg
@@ -0,0 +1,194 @@
+
+
diff --git a/addons/terrain_3d/icons/autoshader.svg.import b/addons/terrain_3d/icons/autoshader.svg.import
new file mode 100644
index 0000000..1551553
--- /dev/null
+++ b/addons/terrain_3d/icons/autoshader.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdwolwswwy8wr"
+path="res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/autoshader.svg"
+dest_files=["res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/color_paint.svg b/addons/terrain_3d/icons/color_paint.svg
new file mode 100644
index 0000000..317b742
--- /dev/null
+++ b/addons/terrain_3d/icons/color_paint.svg
@@ -0,0 +1,175 @@
+
+
diff --git a/addons/terrain_3d/icons/color_paint.svg.import b/addons/terrain_3d/icons/color_paint.svg.import
new file mode 100644
index 0000000..56d0d5d
--- /dev/null
+++ b/addons/terrain_3d/icons/color_paint.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://krrmpalen8xu"
+path="res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/color_paint.svg"
+dest_files=["res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/height_add.svg b/addons/terrain_3d/icons/height_add.svg
new file mode 100644
index 0000000..299a1b8
--- /dev/null
+++ b/addons/terrain_3d/icons/height_add.svg
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/height_add.svg.import b/addons/terrain_3d/icons/height_add.svg.import
new file mode 100644
index 0000000..e1bbbad
--- /dev/null
+++ b/addons/terrain_3d/icons/height_add.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bcmbqryggekg1"
+path="res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/height_add.svg"
+dest_files=["res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/height_div.svg b/addons/terrain_3d/icons/height_div.svg
new file mode 100644
index 0000000..79a6111
--- /dev/null
+++ b/addons/terrain_3d/icons/height_div.svg
@@ -0,0 +1,196 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/height_div.svg.import b/addons/terrain_3d/icons/height_div.svg.import
new file mode 100644
index 0000000..c894274
--- /dev/null
+++ b/addons/terrain_3d/icons/height_div.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://danh7tb2v6rx7"
+path="res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/height_div.svg"
+dest_files=["res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/height_flat.svg b/addons/terrain_3d/icons/height_flat.svg
new file mode 100644
index 0000000..98973fe
--- /dev/null
+++ b/addons/terrain_3d/icons/height_flat.svg
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/height_flat.svg.import b/addons/terrain_3d/icons/height_flat.svg.import
new file mode 100644
index 0000000..fd0e685
--- /dev/null
+++ b/addons/terrain_3d/icons/height_flat.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://crj0xfyiyr45u"
+path="res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/height_flat.svg"
+dest_files=["res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/height_mul.svg b/addons/terrain_3d/icons/height_mul.svg
new file mode 100644
index 0000000..ef4f92e
--- /dev/null
+++ b/addons/terrain_3d/icons/height_mul.svg
@@ -0,0 +1,201 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/height_mul.svg.import b/addons/terrain_3d/icons/height_mul.svg.import
new file mode 100644
index 0000000..b20aadc
--- /dev/null
+++ b/addons/terrain_3d/icons/height_mul.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bu3q0645kb3el"
+path="res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/height_mul.svg"
+dest_files=["res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/height_slope.svg b/addons/terrain_3d/icons/height_slope.svg
new file mode 100644
index 0000000..fe2903d
--- /dev/null
+++ b/addons/terrain_3d/icons/height_slope.svg
@@ -0,0 +1,105 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/height_slope.svg.import b/addons/terrain_3d/icons/height_slope.svg.import
new file mode 100644
index 0000000..ed55b14
--- /dev/null
+++ b/addons/terrain_3d/icons/height_slope.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0cd7so4kw7da"
+path="res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/height_slope.svg"
+dest_files=["res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/height_smooth.svg b/addons/terrain_3d/icons/height_smooth.svg
new file mode 100644
index 0000000..81154c3
--- /dev/null
+++ b/addons/terrain_3d/icons/height_smooth.svg
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/height_smooth.svg.import b/addons/terrain_3d/icons/height_smooth.svg.import
new file mode 100644
index 0000000..eef9e53
--- /dev/null
+++ b/addons/terrain_3d/icons/height_smooth.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://chrbx4xnxyiel"
+path="res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/height_smooth.svg"
+dest_files=["res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/height_sub.svg b/addons/terrain_3d/icons/height_sub.svg
new file mode 100644
index 0000000..bb0d2a7
--- /dev/null
+++ b/addons/terrain_3d/icons/height_sub.svg
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/height_sub.svg.import b/addons/terrain_3d/icons/height_sub.svg.import
new file mode 100644
index 0000000..4dc17eb
--- /dev/null
+++ b/addons/terrain_3d/icons/height_sub.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://mo3hnbk3ffjs"
+path="res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/height_sub.svg"
+dest_files=["res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/holes.svg b/addons/terrain_3d/icons/holes.svg
new file mode 100644
index 0000000..da639d9
--- /dev/null
+++ b/addons/terrain_3d/icons/holes.svg
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/holes.svg.import b/addons/terrain_3d/icons/holes.svg.import
new file mode 100644
index 0000000..8117195
--- /dev/null
+++ b/addons/terrain_3d/icons/holes.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bsmaxekrmnuy2"
+path="res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/holes.svg"
+dest_files=["res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/layers.svg b/addons/terrain_3d/icons/layers.svg
new file mode 100644
index 0000000..5c9c4c9
--- /dev/null
+++ b/addons/terrain_3d/icons/layers.svg
@@ -0,0 +1,102 @@
+
+
diff --git a/addons/terrain_3d/icons/layers.svg.import b/addons/terrain_3d/icons/layers.svg.import
new file mode 100644
index 0000000..559cd21
--- /dev/null
+++ b/addons/terrain_3d/icons/layers.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cs1la1mashf2e"
+path="res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/layers.svg"
+dest_files=["res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/multimesh.svg b/addons/terrain_3d/icons/multimesh.svg
new file mode 100644
index 0000000..a62e5f9
--- /dev/null
+++ b/addons/terrain_3d/icons/multimesh.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/addons/terrain_3d/icons/multimesh.svg.import b/addons/terrain_3d/icons/multimesh.svg.import
new file mode 100644
index 0000000..1493feb
--- /dev/null
+++ b/addons/terrain_3d/icons/multimesh.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cjlcl5lf20ve0"
+path="res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/multimesh.svg"
+dest_files=["res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/navigation.svg b/addons/terrain_3d/icons/navigation.svg
new file mode 100644
index 0000000..1056202
--- /dev/null
+++ b/addons/terrain_3d/icons/navigation.svg
@@ -0,0 +1,42 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/navigation.svg.import b/addons/terrain_3d/icons/navigation.svg.import
new file mode 100644
index 0000000..d8ac263
--- /dev/null
+++ b/addons/terrain_3d/icons/navigation.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://f3po5pogkv2b"
+path="res://.godot/imported/navigation.svg-1e4cf210c589be8d2911c522d4a17d78.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/navigation.svg"
+dest_files=["res://.godot/imported/navigation.svg-1e4cf210c589be8d2911c522d4a17d78.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/picker_checked.svg b/addons/terrain_3d/icons/picker_checked.svg
new file mode 100644
index 0000000..653d57e
--- /dev/null
+++ b/addons/terrain_3d/icons/picker_checked.svg
@@ -0,0 +1,43 @@
+
+
diff --git a/addons/terrain_3d/icons/picker_checked.svg.import b/addons/terrain_3d/icons/picker_checked.svg.import
new file mode 100644
index 0000000..ff53b15
--- /dev/null
+++ b/addons/terrain_3d/icons/picker_checked.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bg8x6o32ggt88"
+path="res://.godot/imported/picker_checked.svg-81f35b6ae38bccc8aa9e7ae22b530168.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/picker_checked.svg"
+dest_files=["res://.godot/imported/picker_checked.svg-81f35b6ae38bccc8aa9e7ae22b530168.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/region_add.svg b/addons/terrain_3d/icons/region_add.svg
new file mode 100644
index 0000000..56d7c92
--- /dev/null
+++ b/addons/terrain_3d/icons/region_add.svg
@@ -0,0 +1,95 @@
+
+
diff --git a/addons/terrain_3d/icons/region_add.svg.import b/addons/terrain_3d/icons/region_add.svg.import
new file mode 100644
index 0000000..e50c30b
--- /dev/null
+++ b/addons/terrain_3d/icons/region_add.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c0tn453fsckv5"
+path="res://.godot/imported/region_add.svg-a05dc161a452dd3e024f9835a737d9f0.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/region_add.svg"
+dest_files=["res://.godot/imported/region_add.svg-a05dc161a452dd3e024f9835a737d9f0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/region_remove.svg b/addons/terrain_3d/icons/region_remove.svg
new file mode 100644
index 0000000..315657d
--- /dev/null
+++ b/addons/terrain_3d/icons/region_remove.svg
@@ -0,0 +1,102 @@
+
+
diff --git a/addons/terrain_3d/icons/region_remove.svg.import b/addons/terrain_3d/icons/region_remove.svg.import
new file mode 100644
index 0000000..6ded856
--- /dev/null
+++ b/addons/terrain_3d/icons/region_remove.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cbpo5eamf3bx2"
+path="res://.godot/imported/region_remove.svg-5710e8aeb34f1eaa06e637634f4a7d16.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/region_remove.svg"
+dest_files=["res://.godot/imported/region_remove.svg-5710e8aeb34f1eaa06e637634f4a7d16.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/terrain3d.svg b/addons/terrain_3d/icons/terrain3d.svg
new file mode 100644
index 0000000..ca8c661
--- /dev/null
+++ b/addons/terrain_3d/icons/terrain3d.svg
@@ -0,0 +1,153 @@
+
+
diff --git a/addons/terrain_3d/icons/terrain3d.svg.import b/addons/terrain_3d/icons/terrain3d.svg.import
new file mode 100644
index 0000000..c077de1
--- /dev/null
+++ b/addons/terrain_3d/icons/terrain3d.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bnsydn4jkyeyn"
+path="res://.godot/imported/terrain3d.svg-eb45756f1a003759fda81eaa1db10769.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/terrain3d.svg"
+dest_files=["res://.godot/imported/terrain3d.svg-eb45756f1a003759fda81eaa1db10769.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/texture_paint.svg b/addons/terrain_3d/icons/texture_paint.svg
new file mode 100644
index 0000000..908d3f4
--- /dev/null
+++ b/addons/terrain_3d/icons/texture_paint.svg
@@ -0,0 +1,151 @@
+
+
diff --git a/addons/terrain_3d/icons/texture_paint.svg.import b/addons/terrain_3d/icons/texture_paint.svg.import
new file mode 100644
index 0000000..8a09d92
--- /dev/null
+++ b/addons/terrain_3d/icons/texture_paint.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://duo8valena3a2"
+path="res://.godot/imported/texture_paint.svg-72da4fd2096377e625a8fe09cdacb0e4.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/texture_paint.svg"
+dest_files=["res://.godot/imported/texture_paint.svg-72da4fd2096377e625a8fe09cdacb0e4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/texture_spray.svg b/addons/terrain_3d/icons/texture_spray.svg
new file mode 100644
index 0000000..bfc3e82
--- /dev/null
+++ b/addons/terrain_3d/icons/texture_spray.svg
@@ -0,0 +1,165 @@
+
+
diff --git a/addons/terrain_3d/icons/texture_spray.svg.import b/addons/terrain_3d/icons/texture_spray.svg.import
new file mode 100644
index 0000000..c4acc6f
--- /dev/null
+++ b/addons/terrain_3d/icons/texture_spray.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://16yfxe7xe703"
+path="res://.godot/imported/texture_spray.svg-326fee11cf418653e621bc222a470861.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/texture_spray.svg"
+dest_files=["res://.godot/imported/texture_spray.svg-326fee11cf418653e621bc222a470861.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/icons/wetness.svg b/addons/terrain_3d/icons/wetness.svg
new file mode 100644
index 0000000..90c3d03
--- /dev/null
+++ b/addons/terrain_3d/icons/wetness.svg
@@ -0,0 +1,41 @@
+
+
+
+
diff --git a/addons/terrain_3d/icons/wetness.svg.import b/addons/terrain_3d/icons/wetness.svg.import
new file mode 100644
index 0000000..9379330
--- /dev/null
+++ b/addons/terrain_3d/icons/wetness.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bcgg0srmqsh3n"
+path="res://.godot/imported/wetness.svg-9b2ddec096ab7734492b77b20c75c82b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/terrain_3d/icons/wetness.svg"
+dest_files=["res://.godot/imported/wetness.svg-9b2ddec096ab7734492b77b20c75c82b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=true
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/terrain_3d/menu/bake_lod_dialog.gd b/addons/terrain_3d/menu/bake_lod_dialog.gd
new file mode 100644
index 0000000..ada4fb2
--- /dev/null
+++ b/addons/terrain_3d/menu/bake_lod_dialog.gd
@@ -0,0 +1,30 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Bake LOD Dialog for Terrain3D
+@tool
+extends ConfirmationDialog
+
+var lod: int = 0
+var description: String = ""
+
+
+func _ready() -> void:
+ set_unparent_when_invisible(true)
+ about_to_popup.connect(_on_about_to_popup)
+ visibility_changed.connect(_on_visibility_changed)
+ %LodBox.value_changed.connect(_on_lod_box_value_changed)
+
+
+func _on_about_to_popup() -> void:
+ lod = %LodBox.value
+
+
+func _on_visibility_changed() -> void:
+ # Change text on the autowrap label only when the popup is visible.
+ # Works around Godot issue #47005:
+ # https://github.com/godotengine/godot/issues/47005
+ if visible:
+ %DescriptionLabel.text = description
+
+
+func _on_lod_box_value_changed(p_value: float) -> void:
+ lod = %LodBox.value
diff --git a/addons/terrain_3d/menu/bake_lod_dialog.gd.uid b/addons/terrain_3d/menu/bake_lod_dialog.gd.uid
new file mode 100644
index 0000000..08c194f
--- /dev/null
+++ b/addons/terrain_3d/menu/bake_lod_dialog.gd.uid
@@ -0,0 +1 @@
+uid://cqmt8f5x5c2ad
diff --git a/addons/terrain_3d/menu/bake_lod_dialog.tscn b/addons/terrain_3d/menu/bake_lod_dialog.tscn
new file mode 100644
index 0000000..bad26f1
--- /dev/null
+++ b/addons/terrain_3d/menu/bake_lod_dialog.tscn
@@ -0,0 +1,43 @@
+[gd_scene load_steps=2 format=3 uid="uid://bhvrrmb8bk1bt"]
+
+[ext_resource type="Script" path="res://addons/terrain_3d/menu/bake_lod_dialog.gd" id="1_sf76d"]
+
+[node name="bake_lod_dialog" type="ConfirmationDialog"]
+title = "Bake Terrain3D Mesh"
+position = Vector2i(0, 36)
+size = Vector2i(400, 155)
+visible = true
+script = ExtResource("1_sf76d")
+
+[node name="MarginContainer" type="MarginContainer" parent="."]
+offset_left = 8.0
+offset_top = 8.0
+offset_right = 392.0
+offset_bottom = 106.0
+theme_override_constants/margin_left = 10
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 10
+theme_override_constants/margin_bottom = 10
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 20
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "LOD:"
+
+[node name="LodBox" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+max_value = 8.0
+value = 4.0
+
+[node name="DescriptionLabel" type="Label" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+autowrap_mode = 2
diff --git a/addons/terrain_3d/menu/baker.gd b/addons/terrain_3d/menu/baker.gd
new file mode 100644
index 0000000..08064b6
--- /dev/null
+++ b/addons/terrain_3d/menu/baker.gd
@@ -0,0 +1,398 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Baker for Terrain3D
+extends Node
+
+const BakeLodDialog: PackedScene = preload("res://addons/terrain_3d/menu/bake_lod_dialog.tscn")
+const BAKE_MESH_DESCRIPTION: String = "This will create a child MeshInstance3D. LOD4+ is recommended. LOD0 is slow and dense with vertices every 1 unit. It is not an optimal mesh."
+const BAKE_OCCLUDER_DESCRIPTION: String = "This will create a child OccluderInstance3D. LOD4+ is recommended and will take 5+ seconds per region to generate. LOD0 is unnecessarily dense and slow."
+const SET_UP_NAVIGATION_DESCRIPTION: String = "This operation will:
+
+- Create a NavigationRegion3D node,
+- Assign it a blank NavigationMesh resource,
+- Move the Terrain3D node to be a child of the new node,
+- And bake the nav mesh.
+
+Once setup is complete, you can modify the settings on your nav mesh, and rebake
+without having to run through the setup again.
+
+If preferred, this setup can be canceled and the steps performed manually. For
+the best results, adjust the settings on the NavigationMesh resource to match
+the settings of your navigation agents and collisions."
+
+var plugin: EditorPlugin
+var bake_method: Callable
+var bake_lod_dialog: ConfirmationDialog
+var confirm_dialog: ConfirmationDialog
+
+
+func _enter_tree() -> void:
+ bake_lod_dialog = BakeLodDialog.instantiate()
+ bake_lod_dialog.hide()
+ bake_lod_dialog.confirmed.connect(func(): bake_method.call())
+ bake_lod_dialog.set_unparent_when_invisible(true)
+
+ confirm_dialog = ConfirmationDialog.new()
+ confirm_dialog.hide()
+ confirm_dialog.confirmed.connect(func(): bake_method.call())
+ confirm_dialog.set_unparent_when_invisible(true)
+
+
+func _exit_tree() -> void:
+ bake_lod_dialog.queue_free()
+ confirm_dialog.queue_free()
+
+
+func bake_mesh_popup() -> void:
+ if plugin.terrain:
+ bake_method = _bake_mesh
+ bake_lod_dialog.description = BAKE_MESH_DESCRIPTION
+ EditorInterface.popup_dialog_centered(bake_lod_dialog)
+
+
+func _bake_mesh() -> void:
+ if plugin.terrain.data.get_region_count() == 0:
+ push_error("Terrain3D has no active regions to bake")
+ return
+ var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_NEAREST)
+ if !mesh:
+ push_error("Failed to bake mesh from Terrain3D")
+ return
+
+ var undo: EditorUndoRedoManager = plugin.get_undo_redo()
+ undo.create_action("Terrain3D Bake ArrayMesh")
+
+ var mesh_instance := plugin.terrain.get_node_or_null(^"MeshInstance3D") as MeshInstance3D
+ if !mesh_instance:
+ mesh_instance = MeshInstance3D.new()
+ mesh_instance.name = &"MeshInstance3D"
+ mesh_instance.set_skeleton_path(NodePath())
+ mesh_instance.mesh = mesh
+
+ undo.add_do_method(plugin.terrain, &"add_child", mesh_instance, true)
+ undo.add_undo_method(plugin.terrain, &"remove_child", mesh_instance)
+ undo.add_do_property(mesh_instance, &"owner", EditorInterface.get_edited_scene_root())
+ undo.add_do_reference(mesh_instance)
+
+ else:
+ undo.add_do_property(mesh_instance, &"mesh", mesh)
+ undo.add_undo_property(mesh_instance, &"mesh", mesh_instance.mesh)
+
+ if mesh_instance.mesh.resource_path:
+ var path := mesh_instance.mesh.resource_path
+ undo.add_do_method(mesh, &"take_over_path", path)
+ undo.add_undo_method(mesh_instance.mesh, &"take_over_path", path)
+ undo.add_do_method(ResourceSaver, &"save", mesh)
+ undo.add_undo_method(ResourceSaver, &"save", mesh_instance.mesh)
+
+ undo.commit_action()
+
+
+func bake_occluder_popup() -> void:
+ if plugin.terrain:
+ bake_method = _bake_occluder
+ bake_lod_dialog.description = BAKE_OCCLUDER_DESCRIPTION
+ EditorInterface.popup_dialog_centered(bake_lod_dialog)
+
+
+func _bake_occluder() -> void:
+ if plugin.terrain.data.get_region_count() == 0:
+ push_error("Terrain3D has no active regions to bake")
+ return
+ var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_MINIMUM)
+ if !mesh:
+ push_error("Failed to bake mesh from Terrain3D")
+ return
+ assert(mesh.get_surface_count() == 1)
+
+ var undo: EditorUndoRedoManager = plugin.get_undo_redo()
+ undo.create_action("Terrain3D Bake Occluder3D")
+
+ var occluder := ArrayOccluder3D.new()
+ var arrays: Array = mesh.surface_get_arrays(0)
+ assert(arrays.size() > Mesh.ARRAY_INDEX)
+ assert(arrays[Mesh.ARRAY_INDEX] != null)
+ occluder.set_arrays(arrays[Mesh.ARRAY_VERTEX], arrays[Mesh.ARRAY_INDEX])
+
+ var occluder_instance := plugin.terrain.get_node_or_null(^"OccluderInstance3D") as OccluderInstance3D
+ if !occluder_instance:
+ occluder_instance = OccluderInstance3D.new()
+ occluder_instance.name = &"OccluderInstance3D"
+ occluder_instance.occluder = occluder
+
+ undo.add_do_method(plugin.terrain, &"add_child", occluder_instance, true)
+ undo.add_undo_method(plugin.terrain, &"remove_child", occluder_instance)
+ undo.add_do_property(occluder_instance, &"owner", EditorInterface.get_edited_scene_root())
+ undo.add_do_reference(occluder_instance)
+
+ else:
+ undo.add_do_property(occluder_instance, &"occluder", occluder)
+ undo.add_undo_property(occluder_instance, &"occluder", occluder_instance.occluder)
+
+ if occluder_instance.occluder.resource_path:
+ var path := occluder_instance.occluder.resource_path
+ undo.add_do_method(occluder, &"take_over_path", path)
+ undo.add_undo_method(occluder_instance.occluder, &"take_over_path", path)
+ undo.add_do_method(ResourceSaver, &"save", occluder)
+ undo.add_undo_method(ResourceSaver, &"save", occluder_instance.occluder)
+
+ undo.commit_action()
+
+
+func find_nav_region_terrains(p_nav_region: NavigationRegion3D) -> Array[Terrain3D]:
+ var result: Array[Terrain3D] = []
+ if not p_nav_region.navigation_mesh:
+ return result
+
+ var source_mode: NavigationMesh.SourceGeometryMode
+ source_mode = p_nav_region.navigation_mesh.geometry_source_geometry_mode
+ if source_mode == NavigationMesh.SOURCE_GEOMETRY_ROOT_NODE_CHILDREN:
+ result.append_array(p_nav_region.find_children("", "Terrain3D", true, true))
+ return result
+
+ var group_nodes: Array = p_nav_region.get_tree().get_nodes_in_group(p_nav_region.navigation_mesh.geometry_source_group_name)
+ for node in group_nodes:
+ if node is Terrain3D:
+ result.push_back(node)
+ if source_mode == NavigationMesh.SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN:
+ result.append_array(node.find_children("", "Terrain3D", true, true))
+
+ return result
+
+
+func find_terrain_nav_regions(p_terrain: Terrain3D) -> Array[NavigationRegion3D]:
+ var result: Array[NavigationRegion3D] = []
+ var root: Node = EditorInterface.get_edited_scene_root()
+ if not root:
+ return result
+ for nav_region in root.find_children("", "NavigationRegion3D", true, true):
+ if find_nav_region_terrains(nav_region).has(p_terrain):
+ result.push_back(nav_region)
+ return result
+
+
+func bake_nav_mesh() -> void:
+ if plugin.nav_region:
+ # A NavigationRegion3D is selected. We only need to bake that one navmesh.
+ _bake_nav_region_nav_mesh(plugin.nav_region)
+ print("Terrain3DNavigation: Finished baking 1 NavigationMesh.")
+
+ elif plugin.terrain:
+ if plugin.terrain.data.get_region_count() == 0:
+ push_error("Terrain3D has no active regions to bake")
+ return
+ # A Terrain3D is selected. There are potentially multiple navmeshes to bake and we need to
+ # find them all. (The multiple navmesh use-case is likely on very large scenes with lots of
+ # geometry. Each navmesh in this case would define its own, non-overlapping, baking AABB, to
+ # cut down on the amount of geometry to bake. In a large open-world RPG, for instance, there
+ # could be a navmesh for each town.)
+ var nav_regions: Array[NavigationRegion3D] = find_terrain_nav_regions(plugin.terrain)
+ for nav_region in nav_regions:
+ _bake_nav_region_nav_mesh(nav_region)
+ print("Terrain3DNavigation: Finished baking %d NavigationMesh(es)." % nav_regions.size())
+
+
+func _bake_nav_region_nav_mesh(p_nav_region: NavigationRegion3D) -> void:
+ var nav_mesh: NavigationMesh = p_nav_region.navigation_mesh
+ assert(nav_mesh != null)
+
+ var source_geometry_data := NavigationMeshSourceGeometryData3D.new()
+ NavigationServer3D.parse_source_geometry_data(nav_mesh, source_geometry_data, p_nav_region)
+
+ for terrain in find_nav_region_terrains(p_nav_region):
+ var aabb: AABB = nav_mesh.filter_baking_aabb
+ aabb.position += nav_mesh.filter_baking_aabb_offset
+ aabb = p_nav_region.global_transform * aabb
+ var faces: PackedVector3Array = terrain.generate_nav_mesh_source_geometry(aabb)
+ if not faces.is_empty():
+ source_geometry_data.add_faces(faces, Transform3D.IDENTITY)
+
+ NavigationServer3D.bake_from_source_geometry_data(nav_mesh, source_geometry_data)
+
+ _postprocess_nav_mesh(nav_mesh)
+
+ # Assign null first to force the debug display to actually update:
+ p_nav_region.set_navigation_mesh(null)
+ p_nav_region.set_navigation_mesh(nav_mesh)
+
+ # Trigger save to disk if it is saved as an external file
+ if not nav_mesh.get_path().is_empty():
+ ResourceSaver.save(nav_mesh, nav_mesh.get_path(), ResourceSaver.FLAG_COMPRESS)
+
+ # Let other editor plugins and tool scripts know the nav mesh was just baked:
+ p_nav_region.bake_finished.emit()
+
+
+func _postprocess_nav_mesh(p_nav_mesh: NavigationMesh) -> void:
+ # Post-process the nav mesh to work around Godot issue #85548
+
+ # Round all the vertices in the nav_mesh to the nearest cell_size/cell_height so that it doesn't
+ # contain any edges shorter than cell_size/cell_height (one cause of #85548).
+ var vertices: PackedVector3Array = _postprocess_nav_mesh_round_vertices(p_nav_mesh)
+
+ # Rounding vertices can collapse some edges to 0 length. We remove these edges, and any polygons
+ # that have been reduced to 0 area.
+ var polygons: Array[PackedInt32Array] = _postprocess_nav_mesh_remove_empty_polygons(p_nav_mesh, vertices)
+
+ # Another cause of #85548 is baking producing overlapping polygons. We remove these.
+ _postprocess_nav_mesh_remove_overlapping_polygons(p_nav_mesh, vertices, polygons)
+
+ p_nav_mesh.clear_polygons()
+ p_nav_mesh.set_vertices(vertices)
+ for polygon in polygons:
+ p_nav_mesh.add_polygon(polygon)
+
+
+func _postprocess_nav_mesh_round_vertices(p_nav_mesh: NavigationMesh) -> PackedVector3Array:
+ assert(p_nav_mesh != null)
+ assert(p_nav_mesh.cell_size > 0.0)
+ assert(p_nav_mesh.cell_height > 0.0)
+
+ var cell_size: Vector3 = Vector3(p_nav_mesh.cell_size, p_nav_mesh.cell_height, p_nav_mesh.cell_size)
+
+ # Round a little harder to avoid rounding errors with non-power-of-two cell_size/cell_height
+ # causing the navigation map to put two non-matching edges in the same cell:
+ var round_factor := cell_size * 1.001
+
+ var vertices: PackedVector3Array = p_nav_mesh.get_vertices()
+ for i in range(vertices.size()):
+ vertices[i] = (vertices[i] / round_factor).floor() * round_factor
+ return vertices
+
+
+func _postprocess_nav_mesh_remove_empty_polygons(p_nav_mesh: NavigationMesh, p_vertices: PackedVector3Array) -> Array[PackedInt32Array]:
+ var polygons: Array[PackedInt32Array] = []
+
+ for i in range(p_nav_mesh.get_polygon_count()):
+ var old_polygon: PackedInt32Array = p_nav_mesh.get_polygon(i)
+ var new_polygon: PackedInt32Array = []
+
+ # Remove duplicate vertices (introduced by rounding) from the polygon:
+ var polygon_vertices: PackedVector3Array = []
+ for index in old_polygon:
+ var vertex: Vector3 = p_vertices[index]
+ if polygon_vertices.has(vertex):
+ continue
+ polygon_vertices.push_back(vertex)
+ new_polygon.push_back(index)
+
+ # If we removed some vertices, we might be able to remove the polygon too:
+ if new_polygon.size() <= 2:
+ continue
+ polygons.push_back(new_polygon)
+
+ return polygons
+
+
+func _postprocess_nav_mesh_remove_overlapping_polygons(p_nav_mesh: NavigationMesh, p_vertices: PackedVector3Array, p_polygons: Array[PackedInt32Array]) -> void:
+ # Occasionally, a baked nav mesh comes out with overlapping polygons:
+ # https://github.com/godotengine/godot/issues/85548#issuecomment-1839341071
+ # Until the bug is fixed in the engine, this function attempts to detect and remove overlapping
+ # polygons.
+
+ # This function has to make a choice of which polygon to remove when an overlap is detected,
+ # because in this case the nav mesh is ambiguous. To do this it uses a heuristic:
+ # (1) an 'overlap' is defined as an edge that is shared by 3 or more polygons.
+ # (2) a 'bad polygon' is defined as a polygon that contains 2 or more 'overlaps'.
+ # The function removes the 'bad polygons', which in practice seems to be enough to remove all
+ # overlaps without creating holes in the nav mesh.
+
+ var cell_size: Vector3 = Vector3(p_nav_mesh.cell_size, p_nav_mesh.cell_height, p_nav_mesh.cell_size)
+
+ # `edges` is going to map edges (vertex pairs) to arrays of polygons that contain that edge.
+ var edges: Dictionary = {}
+
+ for polygon_index in range(p_polygons.size()):
+ var polygon: PackedInt32Array = p_polygons[polygon_index]
+ for j in range(polygon.size()):
+ var vertex: Vector3 = p_vertices[polygon[j]]
+ var next_vertex: Vector3 = p_vertices[polygon[(j + 1) % polygon.size()]]
+
+ # edge_key is a key we can use in the edges dictionary that uniquely identifies the
+ # edge. We use cell coordinates here (Vector3i) because with a non-power-of-two
+ # cell_size, rounding errors can cause Vector3 vertices to not be equal.
+ # Array.sort IS defined for vector types - see the Godot docs. It's necessary here
+ # because polygons that share an edge can have their vertices in a different order.
+ var edge_key: Array = [Vector3i(vertex / cell_size), Vector3i(next_vertex / cell_size)]
+ edge_key.sort()
+
+ if !edges.has(edge_key):
+ edges[edge_key] = []
+ edges[edge_key].push_back(polygon_index)
+
+ var overlap_count: Dictionary = {}
+ for connections in edges.values():
+ if connections.size() <= 2:
+ continue
+ for polygon_index in connections:
+ overlap_count[polygon_index] = overlap_count.get(polygon_index, 0) + 1
+
+ var bad_polygons: Array = []
+ for polygon_index in overlap_count.keys():
+ if overlap_count[polygon_index] >= 2:
+ bad_polygons.push_back(polygon_index)
+
+ bad_polygons.sort()
+ for i in range(bad_polygons.size() - 1, -1, -1):
+ p_polygons.remove_at(bad_polygons[i])
+
+
+func set_up_navigation_popup() -> void:
+ if plugin.terrain:
+ bake_method = _set_up_navigation
+ confirm_dialog.dialog_text = SET_UP_NAVIGATION_DESCRIPTION
+ EditorInterface.popup_dialog_centered(confirm_dialog)
+
+
+func _set_up_navigation() -> void:
+ assert(plugin.terrain)
+ if plugin.terrain == EditorInterface.get_edited_scene_root():
+ push_error("Terrain3D Navigation setup not possible if Terrain3D node is scene root")
+ return
+ if plugin.terrain.data.get_region_count() == 0:
+ push_error("Terrain3D has no active regions")
+ return
+ var terrain: Terrain3D = plugin.terrain
+
+ var nav_region := NavigationRegion3D.new()
+ nav_region.name = &"NavigationRegion3D"
+ nav_region.navigation_mesh = NavigationMesh.new()
+
+ var undo_redo: EditorUndoRedoManager = plugin.get_undo_redo()
+
+ undo_redo.create_action("Terrain3D Set up Navigation")
+ undo_redo.add_do_method(self, &"_do_set_up_navigation", nav_region, terrain)
+ undo_redo.add_undo_method(self, &"_undo_set_up_navigation", nav_region, terrain)
+ undo_redo.add_do_reference(nav_region)
+ undo_redo.commit_action()
+
+ EditorInterface.inspect_object(nav_region)
+ assert(plugin.nav_region == nav_region)
+
+ bake_nav_mesh()
+
+
+func _do_set_up_navigation(p_nav_region: NavigationRegion3D, p_terrain: Terrain3D) -> void:
+ var parent: Node = p_terrain.get_parent()
+ var index: int = p_terrain.get_index()
+ var t_owner: Node = p_terrain.owner
+
+ parent.add_child(p_nav_region, true)
+ p_terrain.reparent(p_nav_region)
+ parent.move_child(p_nav_region, index)
+
+ p_nav_region.owner = t_owner
+ p_terrain.owner = t_owner
+
+
+func _undo_set_up_navigation(p_nav_region: NavigationRegion3D, p_terrain: Terrain3D) -> void:
+ assert(p_terrain.get_parent() == p_nav_region)
+
+ var parent: Node = p_nav_region.get_parent()
+ var index: int = p_nav_region.get_index()
+ var t_owner: Node = p_nav_region.get_owner()
+
+ p_terrain.reparent(parent)
+ parent.remove_child(p_nav_region)
+ parent.move_child(p_terrain, index)
+
+ p_terrain.owner = t_owner
diff --git a/addons/terrain_3d/menu/baker.gd.uid b/addons/terrain_3d/menu/baker.gd.uid
new file mode 100644
index 0000000..0f2ed94
--- /dev/null
+++ b/addons/terrain_3d/menu/baker.gd.uid
@@ -0,0 +1 @@
+uid://4ulaeevj5jvi
diff --git a/addons/terrain_3d/menu/channel_packer.gd b/addons/terrain_3d/menu/channel_packer.gd
new file mode 100644
index 0000000..24fb5a8
--- /dev/null
+++ b/addons/terrain_3d/menu/channel_packer.gd
@@ -0,0 +1,465 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Channel Packer for Terrain3D
+extends RefCounted
+
+const WINDOW_SCENE: String = "res://addons/terrain_3d/menu/channel_packer.tscn"
+const TEMPLATE_PATH: String = "res://addons/terrain_3d/menu/channel_packer_import_template.txt"
+const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/menu/channel_packer_dragdrop.gd"
+enum {
+ INFO,
+ WARN,
+ ERROR,
+}
+
+enum {
+ IMAGE_ALBEDO,
+ IMAGE_HEIGHT,
+ IMAGE_NORMAL,
+ IMAGE_ROUGHNESS
+}
+
+var plugin: EditorPlugin
+var window: Window
+var save_file_dialog: EditorFileDialog
+var open_file_dialog: EditorFileDialog
+var invert_green_checkbox: CheckBox
+var invert_smooth_checkbox: CheckBox
+var invert_height_checkbox: CheckBox
+var normalize_height_checkbox: CheckBox
+var lumin_height_button: Button
+var generate_mipmaps_checkbox: CheckBox
+var high_quality_checkbox: CheckBox
+var align_normals_checkbox: CheckBox
+var resize_toggle_checkbox: CheckBox
+var resize_option_box: SpinBox
+var height_channel: Array[Button]
+var height_channel_selected: int = 0
+var roughness_channel: Array[Button]
+var roughness_channel_selected: int = 0
+var last_opened_directory: String
+var last_saved_directory: String
+var packing_albedo: bool = false
+var queue_pack_normal_roughness: bool = false
+var images: Array[Image] = [null, null, null, null]
+var status_label: Label
+var no_op: Callable = func(): pass
+var last_file_selected_fn: Callable = no_op
+var normal_vector: Vector3
+
+
+func pack_textures_popup() -> void:
+ if window != null:
+ window.show()
+ window.grab_focus()
+ window.move_to_center()
+ return
+ window = (load(WINDOW_SCENE) as PackedScene).instantiate()
+ window.close_requested.connect(_on_close_requested)
+ window.window_input.connect(func(event:InputEvent):
+ if event is InputEventKey:
+ if event.pressed and event.keycode == KEY_ESCAPE:
+ _on_close_requested()
+ )
+ window.find_child("CloseButton").pressed.connect(_on_close_requested)
+
+ status_label = window.find_child("StatusLabel") as Label
+ invert_green_checkbox = window.find_child("InvertGreenChannelCheckBox") as CheckBox
+ invert_smooth_checkbox = window.find_child("InvertSmoothCheckBox") as CheckBox
+ invert_height_checkbox = window.find_child("ConvertDepthToHeight") as CheckBox
+ normalize_height_checkbox = window.find_child("NormalizeHeight") as CheckBox
+ lumin_height_button = window.find_child("LuminanceAsHeightButton") as Button
+ generate_mipmaps_checkbox = window.find_child("GenerateMipmapsCheckBox") as CheckBox
+ high_quality_checkbox = window.find_child("HighQualityCheckBox") as CheckBox
+ align_normals_checkbox = window.find_child("AlignNormalsCheckBox") as CheckBox
+ resize_toggle_checkbox = window.find_child("ResizeToggle") as CheckBox
+ resize_option_box = window.find_child("ResizeOptionButton") as SpinBox
+ height_channel = [
+ window.find_child("HeightChannelR") as Button,
+ window.find_child("HeightChannelG") as Button,
+ window.find_child("HeightChannelB") as Button,
+ window.find_child("HeightChannelA") as Button
+ ]
+ roughness_channel = [
+ window.find_child("RoughnessChannelR") as Button,
+ window.find_child("RoughnessChannelG") as Button,
+ window.find_child("RoughnessChannelB") as Button,
+ window.find_child("RoughnessChannelA") as Button
+ ]
+
+ height_channel[0].pressed.connect(func() -> void: height_channel_selected = 0)
+ height_channel[1].pressed.connect(func() -> void: height_channel_selected = 1)
+ height_channel[2].pressed.connect(func() -> void: height_channel_selected = 2)
+ height_channel[3].pressed.connect(func() -> void: height_channel_selected = 3)
+
+ roughness_channel[0].pressed.connect(func() -> void: roughness_channel_selected = 0)
+ roughness_channel[1].pressed.connect(func() -> void: roughness_channel_selected = 1)
+ roughness_channel[2].pressed.connect(func() -> void: roughness_channel_selected = 2)
+ roughness_channel[3].pressed.connect(func() -> void: roughness_channel_selected = 3)
+
+ plugin.add_child(window)
+ _init_file_dialogs()
+
+ # the dialog disables the parent window "on top" so, restore it after 1 frame to alow the dialog to clear.
+ var set_on_top_fn: Callable = func(_file: String = "") -> void:
+ await RenderingServer.frame_post_draw
+ window.always_on_top = true
+ save_file_dialog.file_selected.connect(set_on_top_fn)
+ save_file_dialog.canceled.connect(set_on_top_fn)
+ open_file_dialog.file_selected.connect(set_on_top_fn)
+ open_file_dialog.canceled.connect(set_on_top_fn)
+
+ _init_texture_picker(window.find_child("AlbedoVBox"), IMAGE_ALBEDO)
+ _init_texture_picker(window.find_child("HeightVBox"), IMAGE_HEIGHT)
+ _init_texture_picker(window.find_child("NormalVBox"), IMAGE_NORMAL)
+ _init_texture_picker(window.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
+
+ (window.find_child("PackButton") as Button).pressed.connect(_on_pack_button_pressed)
+
+
+func _on_close_requested() -> void:
+ last_file_selected_fn = no_op
+ images = [null, null, null, null]
+ window.queue_free()
+ window = null
+
+
+func _init_file_dialogs() -> void:
+ save_file_dialog = EditorFileDialog.new()
+ save_file_dialog.set_filters(PackedStringArray(["*.png"]))
+ save_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE)
+ save_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
+ save_file_dialog.file_selected.connect(_on_save_file_selected)
+ save_file_dialog.ok_button_text = "Save"
+ save_file_dialog.size = Vector2i(550, 550)
+ #save_file_dialog.transient = false
+ #save_file_dialog.exclusive = false
+ #save_file_dialog.popup_window = true
+
+ open_file_dialog = EditorFileDialog.new()
+ open_file_dialog.set_filters(PackedStringArray(
+ ["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", "*.ktx", "*.dds"]))
+ open_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_OPEN_FILE)
+ open_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
+ open_file_dialog.ok_button_text = "Open"
+ open_file_dialog.size = Vector2i(550, 550)
+ #open_file_dialog.transient = false
+ #open_file_dialog.exclusive = false
+ #open_file_dialog.popup_window = true
+
+ window.add_child(save_file_dialog)
+ window.add_child(open_file_dialog)
+
+
+func _init_texture_picker(p_parent: Node, p_image_index: int) -> void:
+ var line_edit: LineEdit = p_parent.find_child("LineEdit") as LineEdit
+ var file_pick_button: Button = p_parent.find_child("PickButton") as Button
+ var clear_button: Button = p_parent.find_child("ClearButton") as Button
+ var texture_rect: TextureRect = p_parent.find_child("TextureRect") as TextureRect
+ var texture_button: Button = p_parent.find_child("TextureButton") as Button
+ texture_button.set_script(load(DRAG_DROP_SCRIPT) as GDScript)
+
+ var set_channel_fn: Callable = func(used_channels: int) -> void:
+ var channel_count: int = 4
+ # enum Image.UsedChannels
+ match used_channels:
+ Image.USED_CHANNELS_L, Image.USED_CHANNELS_R: channel_count = 1
+ Image.USED_CHANNELS_LA, Image.USED_CHANNELS_RG: channel_count = 2
+ Image.USED_CHANNELS_RGB: channel_count = 3
+ Image.USED_CHANNELS_RGBA: channel_count = 4
+ if p_image_index == IMAGE_HEIGHT:
+ for i in 4:
+ height_channel[i].visible = i < channel_count
+ height_channel[0].button_pressed = true
+ height_channel[0].pressed.emit()
+ elif p_image_index == IMAGE_ROUGHNESS:
+ for i in 4:
+ roughness_channel[i].visible = i < channel_count
+ roughness_channel[0].button_pressed = true
+ roughness_channel[0].pressed.emit()
+
+ var load_image_fn: Callable = func(path: String):
+ var image: Image = Image.new()
+ var error: int = OK
+ # Special case for dds files
+ if path.get_extension() == "dds":
+ image = ResourceLoader.load(path).get_image()
+ if not image.is_empty():
+ # if the dds file is loaded, we must clear any mipmaps and
+ # decompress if needed in order to do per pixel operations.
+ image.clear_mipmaps()
+ image.decompress()
+ else:
+ error = FAILED
+ else:
+ error = image.load(path)
+ if error != OK:
+ _show_message(ERROR, "Failed to load texture '" + path + "'")
+ texture_rect.texture = null
+ images[p_image_index] = null
+ else:
+ _show_message(INFO, "Loaded texture '" + path + "'")
+ texture_rect.texture = ImageTexture.create_from_image(image)
+ images[p_image_index] = image
+ _set_wh_labels(p_image_index, image.get_width(), image.get_height())
+ if p_image_index == IMAGE_NORMAL:
+ _set_normal_vector(image)
+ if p_image_index == IMAGE_HEIGHT or p_image_index == IMAGE_ROUGHNESS:
+ set_channel_fn.call(image.detect_used_channels())
+
+ var os_drop_fn: Callable = func(files: PackedStringArray) -> void:
+ # OS drag drop holds mouse focus until released,
+ # Get mouse pos and check directly if inside texture_rect
+ var rect = texture_button.get_global_rect()
+ var mouse_position = texture_button.get_global_mouse_position()
+ if rect.has_point(mouse_position):
+ if files.size() != 1:
+ _show_message(ERROR, "Cannot load multiple files")
+ else:
+ line_edit.text = files[0]
+ load_image_fn.call(files[0])
+
+ var godot_drop_fn: Callable = func(path: String) -> void:
+ path = ProjectSettings.globalize_path(path)
+ line_edit.text = path
+ load_image_fn.call(path)
+
+ var open_fn: Callable = func() -> void:
+ open_file_dialog.current_path = last_opened_directory
+ if last_file_selected_fn != no_op:
+ open_file_dialog.file_selected.disconnect(last_file_selected_fn)
+ last_file_selected_fn = func(path: String) -> void:
+ line_edit.text = path
+ load_image_fn.call(path)
+ open_file_dialog.file_selected.connect(last_file_selected_fn)
+ open_file_dialog.popup_centered_ratio()
+
+ var line_edit_submit_fn: Callable = func(path: String) -> void:
+ line_edit.text = path
+ load_image_fn.call(path)
+
+ var clear_fn: Callable = func() -> void:
+ line_edit.text = ""
+ texture_rect.texture = null
+ images[p_image_index] = null
+ _set_wh_labels(p_image_index, -1, -1)
+
+ line_edit.text_submitted.connect(line_edit_submit_fn)
+ file_pick_button.pressed.connect(open_fn)
+ texture_button.pressed.connect(open_fn)
+ clear_button.pressed.connect(clear_fn)
+ texture_button.dropped.connect(godot_drop_fn)
+ window.files_dropped.connect(os_drop_fn)
+
+ if p_image_index == IMAGE_HEIGHT:
+ var lumin_fn: Callable = func() -> void:
+ if !images[IMAGE_ALBEDO]:
+ _show_message(ERROR, "Albedo Image Required for Operation")
+ else:
+ line_edit.text = "Generated Height"
+ var height_texture: Image = Terrain3DUtil.luminance_to_height(images[IMAGE_ALBEDO])
+ if height_texture.is_empty():
+ _show_message(ERROR, "Height Texture Generation error")
+ # blur the image by resizing down and back..
+ var w: int = height_texture.get_width()
+ var h: int = height_texture.get_height()
+ height_texture.resize(w / 4, h / 4)
+ height_texture.resize(w, h, Image.INTERPOLATE_CUBIC)
+ # "Load" the height texture
+ images[IMAGE_HEIGHT] = height_texture
+ texture_rect.texture = ImageTexture.create_from_image(images[IMAGE_HEIGHT])
+ _set_wh_labels(IMAGE_HEIGHT, height_texture.get_width(), height_texture.get_height())
+ set_channel_fn.call(Image.USED_CHANNELS_R)
+ _show_message(INFO, "Height Texture generated sucsessfully")
+ lumin_height_button.pressed.connect(lumin_fn)
+ plugin.ui.set_button_editor_icon(file_pick_button, "Folder")
+ plugin.ui.set_button_editor_icon(clear_button, "Remove")
+
+
+func _set_wh_labels(p_image_index: int, width: int, height: int) -> void:
+ var w: String = ""
+ var h: String = ""
+ if width > 0 and height > 0:
+ w = "w: " + str(width)
+ h = "h: " + str(height)
+ match p_image_index:
+ 0:
+ window.find_child("AlbedoW").text = w
+ window.find_child("AlbedoH").text = h
+ 1:
+ window.find_child("HeightW").text = w
+ window.find_child("HeightH").text = h
+ 2:
+ window.find_child("NormalW").text = w
+ window.find_child("NormalH").text = h
+ 3:
+ window.find_child("RoughnessW").text = w
+ window.find_child("RoughnessH").text = h
+
+
+func _show_message(p_level: int, p_text: String) -> void:
+ status_label.text = p_text
+ match p_level:
+ INFO:
+ print("Terrain3DChannelPacker: " + p_text)
+ status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
+ WARN:
+ push_warning("Terrain3DChannelPacker: " + p_text)
+ status_label.add_theme_color_override("font_color", Color(0.9, 0.9, 0))
+ ERROR,_:
+ push_error("Terrain3DChannelPacker: " + p_text)
+ status_label.add_theme_color_override("font_color", Color(0.9, 0, 0))
+
+
+func _create_import_file(png_path: String) -> void:
+ var dst_import_path: String = png_path + ".import"
+ var file: FileAccess = FileAccess.open(TEMPLATE_PATH, FileAccess.READ)
+ var template_content: String = file.get_as_text()
+ file.close()
+ template_content = template_content.replace(
+ "$SOURCE_FILE", png_path).replace(
+ "$HIGH_QUALITY", str(high_quality_checkbox.button_pressed)).replace(
+ "$GENERATE_MIPMAPS", str(generate_mipmaps_checkbox.button_pressed)
+ )
+ var import_content: String = template_content
+ file = FileAccess.open(dst_import_path, FileAccess.WRITE)
+ file.store_string(import_content)
+ file.close()
+
+
+func _on_pack_button_pressed() -> void:
+ packing_albedo = images[IMAGE_ALBEDO] != null and images[IMAGE_HEIGHT] != null
+ var packing_normal_roughness: bool = images[IMAGE_NORMAL] != null and images[IMAGE_ROUGHNESS] != null
+
+ if not packing_albedo and not packing_normal_roughness:
+ _show_message(WARN, "Please select an albedo and height texture or a normal and roughness texture")
+ return
+ if packing_albedo:
+ save_file_dialog.current_path = last_saved_directory + "packed_albedo_height"
+ save_file_dialog.title = "Save Packed Albedo/Height Texture"
+ save_file_dialog.popup_centered_ratio()
+ if packing_normal_roughness:
+ queue_pack_normal_roughness = true
+ return
+ if packing_normal_roughness:
+ save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
+ save_file_dialog.title = "Save Packed Normal/Roughness Texture"
+ save_file_dialog.popup_centered_ratio()
+
+
+func _on_save_file_selected(p_dst_path) -> void:
+ last_saved_directory = p_dst_path.get_base_dir() + "/"
+ var error: int
+ if packing_albedo:
+ error = _pack_textures(images[IMAGE_ALBEDO], images[IMAGE_HEIGHT], p_dst_path, false,
+ invert_height_checkbox.button_pressed, false, normalize_height_checkbox.button_pressed, height_channel_selected)
+ else:
+ error = _pack_textures(images[IMAGE_NORMAL], images[IMAGE_ROUGHNESS], p_dst_path,
+ invert_green_checkbox.button_pressed, invert_smooth_checkbox.button_pressed,
+ align_normals_checkbox.button_pressed, false, roughness_channel_selected)
+
+ if error == OK:
+ EditorInterface.get_resource_filesystem().scan()
+ if window.visible:
+ window.hide()
+ await EditorInterface.get_resource_filesystem().resources_reimported
+ # wait 1 extra frame, to ensure the UI is responsive.
+ await RenderingServer.frame_post_draw
+ window.show()
+
+ if queue_pack_normal_roughness:
+ queue_pack_normal_roughness = false
+ packing_albedo = false
+ save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
+ save_file_dialog.title = "Save Packed Normal/Roughness Texture"
+
+ save_file_dialog.call_deferred("popup_centered_ratio")
+ save_file_dialog.call_deferred("grab_focus")
+
+
+func _alignment_basis(normal: Vector3) -> Basis:
+ var up: Vector3 = Vector3(0, 0, 1)
+ var v: Vector3 = normal.cross(up)
+ var c: float = normal.dot(up)
+ var k: float = 1.0 / (1.0 + c)
+
+ var vxy: float = v.x * v.y * k
+ var vxz: float = v.x * v.z * k
+ var vyz: float = v.y * v.z * k
+
+ return Basis(Vector3(v.x * v.x * k + c, vxy - v.z, vxz + v.y),
+ Vector3(vxy + v.z, v.y * v.y * k + c, vyz - v.x),
+ Vector3(vxz - v.y, vyz + v.x, v.z * v.z * k + c)
+ )
+
+
+func _set_normal_vector(source: Image, quiet: bool = false) -> void:
+ # Calculate texture normal sum direction
+ var normal: Image = source
+ var sum: Color = Color(0.0, 0.0, 0.0, 0.0)
+ for x in normal.get_width():
+ for y in normal.get_height():
+ sum += normal.get_pixel(x, y)
+ var div: float = normal.get_height() * normal.get_width()
+ sum /= Color(div, div, div)
+ sum *= 2.0
+ sum -= Color(1.0, 1.0, 1.0)
+ normal_vector = Vector3(sum.r, sum.g, sum.b).normalized()
+ if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && !quiet:
+ _show_message(WARN, "Normal Texture Not Orthoganol to UV plane.\nFor Compatability with Detiling and Rotation, Select Orthoganolize Normals")
+
+
+func _align_normals(source: Image, iteration: int = 0) -> void:
+ # generate matrix to re-align the normalmap
+ var mat3: Basis = _alignment_basis(normal_vector)
+ # re-align the normal map pixels
+ for x in source.get_width():
+ for y in source.get_height():
+ var old_pixel: Color = source.get_pixel(x, y)
+ var vector_pixel: Vector3 = Vector3(old_pixel.r, old_pixel.g, old_pixel.b)
+ vector_pixel *= 2.0
+ vector_pixel -= Vector3.ONE
+ vector_pixel = vector_pixel.normalized()
+ vector_pixel = vector_pixel * mat3
+ vector_pixel += Vector3.ONE
+ vector_pixel *= 0.5
+ var new_pixel: Color = Color(vector_pixel.x, vector_pixel.y, vector_pixel.z, old_pixel.a)
+ source.set_pixel(x, y, new_pixel)
+ _set_normal_vector(source, true)
+ if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && iteration < 3:
+ ++iteration
+ _align_normals(source, iteration)
+
+
+func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_invert_green: bool,
+ p_invert_smooth: bool, p_align_normals: bool, p_normalize_height: bool, p_alpha_channel: int) -> Error:
+ if p_rgb_image and p_a_image:
+ if p_rgb_image.get_size() != p_a_image.get_size() and !resize_toggle_checkbox.button_pressed:
+ _show_message(ERROR, "Textures must be the same size.\nEnable resize to override image dimensions")
+ return FAILED
+
+ if resize_toggle_checkbox.button_pressed:
+ var size: int = max(128, resize_option_box.value)
+ p_rgb_image.resize(size, size, Image.INTERPOLATE_CUBIC)
+ p_a_image.resize(size, size, Image.INTERPOLATE_CUBIC)
+
+ if p_align_normals and normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999:
+ _align_normals(p_rgb_image)
+ elif p_align_normals:
+ _show_message(INFO, "Alignment OK, skipping Normal Orthogonalization")
+
+ var output_image: Image = Terrain3DUtil.pack_image(p_rgb_image, p_a_image,
+ p_invert_green, p_invert_smooth, p_normalize_height, p_alpha_channel)
+
+ if not output_image:
+ _show_message(ERROR, "Failed to pack textures")
+ return FAILED
+ if output_image.detect_alpha() != Image.ALPHA_BLEND:
+ _show_message(WARN, "Warning, Alpha channel empty")
+
+ output_image.save_png(p_dst_path)
+ _create_import_file(p_dst_path)
+ _show_message(INFO, "Packed to " + p_dst_path + ".")
+ return OK
+ else:
+ _show_message(ERROR, "Failed to load one or more textures")
+ return FAILED
diff --git a/addons/terrain_3d/menu/channel_packer.gd.uid b/addons/terrain_3d/menu/channel_packer.gd.uid
new file mode 100644
index 0000000..564c78d
--- /dev/null
+++ b/addons/terrain_3d/menu/channel_packer.gd.uid
@@ -0,0 +1 @@
+uid://bwldx4itd58o7
diff --git a/addons/terrain_3d/menu/channel_packer.tscn b/addons/terrain_3d/menu/channel_packer.tscn
new file mode 100644
index 0000000..fe0f80a
--- /dev/null
+++ b/addons/terrain_3d/menu/channel_packer.tscn
@@ -0,0 +1,539 @@
+[gd_scene load_steps=7 format=3 uid="uid://nud6dwjcnj5v"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ysabf"]
+bg_color = Color(0.211765, 0.239216, 0.290196, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lcvna"]
+bg_color = Color(0.168627, 0.211765, 0.266667, 1)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+border_color = Color(0.270588, 0.435294, 0.580392, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cb0xf"]
+bg_color = Color(0.137255, 0.137255, 0.137255, 1)
+draw_center = false
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+border_color = Color(0.784314, 0.784314, 0.784314, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7qdas"]
+
+[sub_resource type="ButtonGroup" id="ButtonGroup_wnxik"]
+
+[sub_resource type="ButtonGroup" id="ButtonGroup_bs6ki"]
+
+[node name="Window" type="Window"]
+title = "Terrain3D Channel Packer"
+initial_position = 1
+size = Vector2i(583, 891)
+wrap_controls = true
+always_on_top = true
+
+[node name="PanelContainer" type="PanelContainer" parent="."]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_ysabf")
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 5
+theme_override_constants/margin_top = 5
+theme_override_constants/margin_right = 5
+theme_override_constants/margin_bottom = 5
+
+[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="AlbedoHeightPanel" type="PanelContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel"]
+layout_mode = 2
+theme_override_constants/margin_left = 10
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 10
+theme_override_constants/margin_bottom = 10
+
+[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer"]
+layout_mode = 2
+
+[node name="AlbedoVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="AlbedoLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
+layout_mode = 2
+text = "Albedo texture"
+
+[node name="AlbedoHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
+layout_mode = 2
+
+[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
+layout_mode = 2
+
+[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
+layout_mode = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
+layout_mode = 2
+size_flags_vertical = 4
+theme_override_constants/margin_top = 10
+
+[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer"]
+custom_minimum_size = Vector2(110, 110)
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
+
+[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -50.0
+offset_top = -50.0
+offset_right = 50.0
+offset_bottom = 50.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+
+[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
+
+[node name="AlbedoWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="AlbedoW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="AlbedoH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="LuminanceAsHeightButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/HBoxContainer2"]
+layout_mode = 2
+text = " Generate Height from Luminance"
+icon_alignment = 2
+
+[node name="HeightVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="HeightLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
+layout_mode = 2
+text = "Height texture"
+
+[node name="HeightHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
+layout_mode = 2
+
+[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
+layout_mode = 2
+
+[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
+layout_mode = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
+layout_mode = 2
+size_flags_vertical = 4
+theme_override_constants/margin_top = 10
+
+[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer"]
+custom_minimum_size = Vector2(110, 110)
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
+
+[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -50.0
+offset_top = -50.0
+offset_right = 50.0
+offset_bottom = 50.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+
+[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
+
+[node name="HeightWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="HeightW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="HeightH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="ConvertDepthToHeight" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer2"]
+layout_mode = 2
+text = " Convert Depth to Height"
+icon_alignment = 2
+
+[node name="HBoxContainer3" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="NormalizeHeight" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer3"]
+layout_mode = 2
+text = "Normalize Height"
+icon_alignment = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="HeightChannelLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
+layout_mode = 2
+text = " Source Channel: "
+horizontal_alignment = 2
+
+[node name="HeightChannelR" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_pressed = true
+button_group = SubResource("ButtonGroup_wnxik")
+text = "R"
+
+[node name="HeightChannelB" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_group = SubResource("ButtonGroup_wnxik")
+text = "G"
+
+[node name="HeightChannelG" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_group = SubResource("ButtonGroup_wnxik")
+text = "B"
+
+[node name="HeightChannelA" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_group = SubResource("ButtonGroup_wnxik")
+text = "A"
+
+[node name="NormalRoughnessPanel" type="PanelContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel"]
+layout_mode = 2
+theme_override_constants/margin_left = 10
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 10
+theme_override_constants/margin_bottom = 10
+
+[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer"]
+layout_mode = 2
+
+[node name="NormalVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="NormalLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
+layout_mode = 2
+text = "Normal texture"
+
+[node name="NormalHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
+layout_mode = 2
+
+[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
+layout_mode = 2
+
+[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
+layout_mode = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
+layout_mode = 2
+size_flags_vertical = 4
+theme_override_constants/margin_top = 10
+
+[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer"]
+custom_minimum_size = Vector2(110, 110)
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
+
+[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -50.0
+offset_top = -50.0
+offset_right = 50.0
+offset_bottom = 50.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+
+[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
+
+[node name="NormalWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="NormalW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="NormalH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="InvertGreenChannelCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer"]
+layout_mode = 2
+text = " Convert DirectX to OpenGL"
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="AlignNormalsCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer2"]
+layout_mode = 2
+text = " Orthoganolise Normals"
+
+[node name="RoughnessVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="RoughnessLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
+layout_mode = 2
+text = "Roughness texture"
+
+[node name="RoughnessHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
+layout_mode = 2
+
+[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
+layout_mode = 2
+
+[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
+layout_mode = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
+layout_mode = 2
+size_flags_vertical = 4
+theme_override_constants/margin_top = 10
+
+[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer"]
+custom_minimum_size = Vector2(110, 110)
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
+
+[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -50.0
+offset_top = -50.0
+offset_right = 50.0
+offset_bottom = 50.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+
+[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer/Panel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
+
+[node name="RoughnessWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="RoughnessW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="RoughnessH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="InvertSmoothCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer2"]
+layout_mode = 2
+text = " Convert Smoothness to Roughness"
+
+[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="RoughnessChannelLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
+layout_mode = 2
+text = " Source Channel: "
+horizontal_alignment = 2
+
+[node name="RoughnessChannelR" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_pressed = true
+button_group = SubResource("ButtonGroup_bs6ki")
+text = "R"
+
+[node name="RoughnessChannelG" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_group = SubResource("ButtonGroup_bs6ki")
+text = "G"
+
+[node name="RoughnessChannelB" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_group = SubResource("ButtonGroup_bs6ki")
+text = "B"
+
+[node name="RoughnessChannelA" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
+layout_mode = 2
+toggle_mode = true
+button_group = SubResource("ButtonGroup_bs6ki")
+text = "A"
+
+[node name="GeneralOptionsLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "General Options"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="GeneralOptionsHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+alignment = 1
+
+[node name="ResizeToggle" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
+layout_mode = 2
+text = " Resize Packed Image"
+
+[node name="ResizeOptionButton" type="SpinBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
+visible = false
+layout_mode = 2
+tooltip_text = "A value of 0 disables resizing."
+min_value = 128.0
+max_value = 4096.0
+step = 128.0
+value = 1024.0
+
+[node name="VSeparator" type="VSeparator" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
+layout_mode = 2
+
+[node name="GenerateMipmapsCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
+layout_mode = 2
+button_pressed = true
+text = "Generate Mipmaps"
+
+[node name="HighQualityCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
+layout_mode = 2
+text = "Import High Quality"
+
+[node name="PackButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Pack textures as..."
+
+[node name="StatusLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer"]
+custom_minimum_size = Vector2(0, 60)
+layout_mode = 2
+horizontal_alignment = 1
+autowrap_mode = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+alignment = 1
+
+[node name="CloseButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Close"
+
+[connection signal="toggled" from="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeToggle" to="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeOptionButton" method="set_visible"]
diff --git a/addons/terrain_3d/menu/channel_packer_dragdrop.gd b/addons/terrain_3d/menu/channel_packer_dragdrop.gd
new file mode 100644
index 0000000..154c8ad
--- /dev/null
+++ b/addons/terrain_3d/menu/channel_packer_dragdrop.gd
@@ -0,0 +1,17 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Channel Packer Dragdropper for Terrain3D
+@tool
+extends Button
+
+signal dropped
+
+func _can_drop_data(p_position, p_data) -> bool:
+ if typeof(p_data) == TYPE_DICTIONARY:
+ if p_data.files.size() == 1:
+ match p_data.files[0].get_extension():
+ "png", "bmp", "exr", "hdr", "jpg", "jpeg", "tga", "svg", "webp", "ktx", "dds":
+ return true
+ return false
+
+func _drop_data(p_position, p_data) -> void:
+ dropped.emit(p_data.files[0])
diff --git a/addons/terrain_3d/menu/channel_packer_dragdrop.gd.uid b/addons/terrain_3d/menu/channel_packer_dragdrop.gd.uid
new file mode 100644
index 0000000..d9a59e3
--- /dev/null
+++ b/addons/terrain_3d/menu/channel_packer_dragdrop.gd.uid
@@ -0,0 +1 @@
+uid://br45krrqbw8bg
diff --git a/addons/terrain_3d/menu/channel_packer_import_template.txt b/addons/terrain_3d/menu/channel_packer_import_template.txt
new file mode 100644
index 0000000..55003e3
--- /dev/null
+++ b/addons/terrain_3d/menu/channel_packer_import_template.txt
@@ -0,0 +1,32 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="$SOURCE_FILE"
+
+[params]
+
+compress/mode=2
+compress/high_quality=$HIGH_QUALITY
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=2
+compress/channel_pack=0
+mipmaps/generate=$GENERATE_MIPMAPS
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+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
diff --git a/addons/terrain_3d/menu/directory_setup.gd b/addons/terrain_3d/menu/directory_setup.gd
new file mode 100644
index 0000000..b851747
--- /dev/null
+++ b/addons/terrain_3d/menu/directory_setup.gd
@@ -0,0 +1,86 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Directory Setup for Terrain3D
+extends Node
+
+const DIRECTORY_SETUP: String = "res://addons/terrain_3d/menu/directory_setup.tscn"
+
+var plugin: EditorPlugin
+var dialog: ConfirmationDialog
+var select_dir_btn: Button
+var selected_dir_le: LineEdit
+var editor_file_dialog: EditorFileDialog
+
+
+func _init() -> void:
+ editor_file_dialog = EditorFileDialog.new()
+ editor_file_dialog.set_filters(PackedStringArray(["*.res"]))
+ editor_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE)
+ editor_file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
+ editor_file_dialog.ok_button_text = "Open"
+ editor_file_dialog.title = "Open a folder or file"
+ editor_file_dialog.dir_selected.connect(_on_dir_selected)
+ editor_file_dialog.size = Vector2i(850, 550)
+ editor_file_dialog.transient = false
+ editor_file_dialog.exclusive = false
+ editor_file_dialog.popup_window = true
+ add_child(editor_file_dialog)
+
+
+func directory_setup_popup() -> void:
+ dialog = load(DIRECTORY_SETUP).instantiate()
+ dialog.hide()
+
+ # Nodes
+ select_dir_btn = dialog.get_node("Margin/VBox/DirHBox/SelectDir")
+ selected_dir_le = dialog.get_node("Margin/VBox/DirHBox/LineEdit")
+
+ if plugin.terrain.data_directory:
+ selected_dir_le.text = plugin.terrain.data_directory
+
+ # Icons
+ plugin.ui.set_button_editor_icon(select_dir_btn, "Folder")
+
+ #Signals
+ select_dir_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_DIR))
+ dialog.confirmed.connect(_on_close_requested)
+ dialog.canceled.connect(_on_close_requested)
+ dialog.get_ok_button().pressed.connect(_on_ok_pressed)
+
+ # Popup
+ EditorInterface.popup_dialog_centered(dialog)
+
+
+func _on_close_requested() -> void:
+ dialog.queue_free()
+ dialog = null
+
+
+func _on_select_file_pressed(file_mode: EditorFileDialog.FileMode) -> void:
+ editor_file_dialog.file_mode = file_mode
+ editor_file_dialog.popup_centered()
+
+
+func _on_dir_selected(path: String) -> void:
+ selected_dir_le.text = path
+
+
+func _on_ok_pressed() -> void:
+ if not plugin.terrain:
+ push_error("Not connected terrain. Click the Terrain3D node first")
+ return
+ if selected_dir_le.text.is_empty():
+ push_error("No data directory specified")
+ return
+ if not DirAccess.dir_exists_absolute(selected_dir_le.text):
+ push_error("Directory doesn't exist: ", selected_dir_le.text)
+ return
+ # Check if directory empty of terrain files
+ var data_found: bool = false
+ var files: Array = DirAccess.get_files_at(selected_dir_le.text)
+ for file in files:
+ if file.begins_with("terrain3d") || file.ends_with(".res"):
+ data_found = true
+ break
+
+ print("Setting terrain directory: ", selected_dir_le.text)
+ plugin.terrain.data_directory = selected_dir_le.text
diff --git a/addons/terrain_3d/menu/directory_setup.gd.uid b/addons/terrain_3d/menu/directory_setup.gd.uid
new file mode 100644
index 0000000..de0b12f
--- /dev/null
+++ b/addons/terrain_3d/menu/directory_setup.gd.uid
@@ -0,0 +1 @@
+uid://0034ukv2mngn
diff --git a/addons/terrain_3d/menu/directory_setup.tscn b/addons/terrain_3d/menu/directory_setup.tscn
new file mode 100644
index 0000000..d5dabc5
--- /dev/null
+++ b/addons/terrain_3d/menu/directory_setup.tscn
@@ -0,0 +1,48 @@
+[gd_scene format=3 uid="uid://by3kr2nqbqr67"]
+
+[node name="DirectorySetup" type="ConfirmationDialog"]
+title = "Terrain3D Data Directory Setup"
+position = Vector2i(0, 36)
+size = Vector2i(750, 330)
+visible = true
+
+[node name="Margin" type="MarginContainer" parent="."]
+offset_left = 8.0
+offset_top = 8.0
+offset_right = 742.0
+offset_bottom = 281.0
+theme_override_constants/margin_left = 20
+theme_override_constants/margin_top = 20
+theme_override_constants/margin_right = 20
+theme_override_constants/margin_bottom = 20
+
+[node name="VBox" type="VBoxContainer" parent="Margin"]
+layout_mode = 2
+
+[node name="Instructions" type="Label" parent="Margin/VBox"]
+custom_minimum_size = Vector2(400, 0)
+layout_mode = 2
+text = "Terrain3D now stores data in a directory instead of a single file. Each region is stored in a separate file named `terrain3d[-_]##[-_]##.res`. For instance, the region at location (-1, 1) would be named `terrain3d-01_01.res`. Enable Terrain3D / Debug / Show Region Labels for a visual display."
+autowrap_mode = 3
+
+[node name="DirectoryLabel" type="Label" parent="Margin/VBox"]
+custom_minimum_size = Vector2(400, 0)
+layout_mode = 2
+text = "
+Specify the directory to store your data. Any existing region files will be loaded."
+autowrap_mode = 3
+
+[node name="DirHBox" type="HBoxContainer" parent="Margin/VBox"]
+layout_mode = 2
+
+[node name="LineEdit" type="LineEdit" parent="Margin/VBox/DirHBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Data directory"
+
+[node name="SelectDir" type="Button" parent="Margin/VBox/DirHBox"]
+layout_mode = 2
+
+[node name="Spacer" type="Control" parent="Margin/VBox"]
+custom_minimum_size = Vector2(0, 40)
+layout_mode = 2
diff --git a/addons/terrain_3d/menu/terrain_menu.gd b/addons/terrain_3d/menu/terrain_menu.gd
new file mode 100644
index 0000000..c53656f
--- /dev/null
+++ b/addons/terrain_3d/menu/terrain_menu.gd
@@ -0,0 +1,83 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Menu for Terrain3D
+extends HBoxContainer
+
+
+const DirectoryWizard: Script = preload("res://addons/terrain_3d/menu/directory_setup.gd")
+const Packer: Script = preload("res://addons/terrain_3d/menu/channel_packer.gd")
+const Baker: Script = preload("res://addons/terrain_3d/menu/baker.gd")
+
+var plugin: EditorPlugin
+var menu_button: MenuButton = MenuButton.new()
+var directory_setup: DirectoryWizard = DirectoryWizard.new()
+var packer: Packer = Packer.new()
+var baker: Baker = Baker.new()
+
+# These are IDs and order must be consistent with add_item and set_disabled IDs
+enum {
+ MENU_DIRECTORY_SETUP,
+ MENU_PACK_TEXTURES,
+ MENU_SEPARATOR,
+ MENU_BAKE_ARRAY_MESH,
+ MENU_BAKE_OCCLUDER,
+ MENU_SEPARATOR2,
+ MENU_SET_UP_NAVIGATION,
+ MENU_BAKE_NAV_MESH,
+}
+
+
+func _enter_tree() -> void:
+ directory_setup.plugin = plugin
+ packer.plugin = plugin
+ baker.plugin = plugin
+ add_child(directory_setup)
+ add_child(baker)
+
+ menu_button.text = "Terrain3D"
+ menu_button.get_popup().add_item("Directory Setup...", MENU_DIRECTORY_SETUP)
+ menu_button.get_popup().add_item("Pack Textures...", MENU_PACK_TEXTURES)
+ menu_button.get_popup().add_separator("", MENU_SEPARATOR)
+ menu_button.get_popup().add_item("Bake ArrayMesh...", MENU_BAKE_ARRAY_MESH)
+ menu_button.get_popup().add_item("Bake Occluder3D...", MENU_BAKE_OCCLUDER)
+ menu_button.get_popup().add_separator("", MENU_SEPARATOR2)
+ menu_button.get_popup().add_item("Set up Navigation...", MENU_SET_UP_NAVIGATION)
+ menu_button.get_popup().add_item("Bake NavMesh...", MENU_BAKE_NAV_MESH)
+
+ menu_button.get_popup().id_pressed.connect(_on_menu_pressed)
+ menu_button.about_to_popup.connect(_on_menu_about_to_popup)
+ add_child(menu_button)
+
+
+func _on_menu_pressed(p_id: int) -> void:
+ match p_id:
+ MENU_DIRECTORY_SETUP:
+ directory_setup.directory_setup_popup()
+ MENU_PACK_TEXTURES:
+ packer.pack_textures_popup()
+ MENU_BAKE_ARRAY_MESH:
+ baker.bake_mesh_popup()
+ MENU_BAKE_OCCLUDER:
+ baker.bake_occluder_popup()
+ MENU_SET_UP_NAVIGATION:
+ baker.set_up_navigation_popup()
+ MENU_BAKE_NAV_MESH:
+ baker.bake_nav_mesh()
+
+
+func _on_menu_about_to_popup() -> void:
+ menu_button.get_popup().set_item_disabled(MENU_DIRECTORY_SETUP, not plugin.terrain)
+ menu_button.get_popup().set_item_disabled(MENU_PACK_TEXTURES, not plugin.terrain)
+ menu_button.get_popup().set_item_disabled(MENU_BAKE_ARRAY_MESH, not plugin.terrain)
+ menu_button.get_popup().set_item_disabled(MENU_BAKE_OCCLUDER, not plugin.terrain)
+
+ if plugin.terrain:
+ var nav_regions: Array[NavigationRegion3D] = baker.find_terrain_nav_regions(plugin.terrain)
+ menu_button.get_popup().set_item_disabled(MENU_BAKE_NAV_MESH, nav_regions.size() == 0)
+ menu_button.get_popup().set_item_disabled(MENU_SET_UP_NAVIGATION, nav_regions.size() != 0)
+ elif plugin.nav_region:
+ var terrains: Array[Terrain3D] = baker.find_nav_region_terrains(plugin.nav_region)
+ menu_button.get_popup().set_item_disabled(MENU_BAKE_NAV_MESH, terrains.size() == 0)
+ menu_button.get_popup().set_item_disabled(MENU_SET_UP_NAVIGATION, true)
+ else:
+ menu_button.get_popup().set_item_disabled(MENU_BAKE_NAV_MESH, true)
+ menu_button.get_popup().set_item_disabled(MENU_SET_UP_NAVIGATION, true)
diff --git a/addons/terrain_3d/menu/terrain_menu.gd.uid b/addons/terrain_3d/menu/terrain_menu.gd.uid
new file mode 100644
index 0000000..b1b1cea
--- /dev/null
+++ b/addons/terrain_3d/menu/terrain_menu.gd.uid
@@ -0,0 +1 @@
+uid://3gxvahogxa10
diff --git a/addons/terrain_3d/plugin.cfg b/addons/terrain_3d/plugin.cfg
new file mode 100644
index 0000000..6171d9c
--- /dev/null
+++ b/addons/terrain_3d/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Terrain3D"
+description="A high performance, editable terrain system for Godot 4."
+author="Cory Petkovsek & Roope Palmroos"
+version="1.0.1"
+script="src/editor_plugin.gd"
diff --git a/addons/terrain_3d/src/asset_dock.gd b/addons/terrain_3d/src/asset_dock.gd
new file mode 100644
index 0000000..58c67c1
--- /dev/null
+++ b/addons/terrain_3d/src/asset_dock.gd
@@ -0,0 +1,883 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Asset Dock for Terrain3D
+@tool
+extends PanelContainer
+
+signal confirmation_closed
+signal confirmation_confirmed
+signal confirmation_canceled
+
+const ES_DOCK_SLOT: String = "terrain3d/dock/slot"
+const ES_DOCK_TILE_SIZE: String = "terrain3d/dock/tile_size"
+const ES_DOCK_FLOATING: String = "terrain3d/dock/floating"
+const ES_DOCK_PINNED: String = "terrain3d/dock/always_on_top"
+const ES_DOCK_WINDOW_POSITION: String = "terrain3d/dock/window_position"
+const ES_DOCK_WINDOW_SIZE: String = "terrain3d/dock/window_size"
+const ES_DOCK_TAB: String = "terrain3d/dock/tab"
+
+var texture_list: ListContainer
+var mesh_list: ListContainer
+var _current_list: ListContainer
+var _last_thumb_update_time: int = 0
+const MAX_UPDATE_TIME: int = 1000
+
+var placement_opt: OptionButton
+var floating_btn: Button
+var pinned_btn: Button
+var size_slider: HSlider
+var box: BoxContainer
+var buttons: BoxContainer
+var textures_btn: Button
+var meshes_btn: Button
+var asset_container: ScrollContainer
+var confirm_dialog: ConfirmationDialog
+var _confirmed: bool = false
+
+# Used only for editor, so change to single visible/hiddden
+enum {
+ HIDDEN = -1,
+ SIDEBAR = 0,
+ BOTTOM = 1,
+ WINDOWED = 2,
+}
+var state: int = HIDDEN
+
+enum {
+ POS_LEFT_UL = 0,
+ POS_LEFT_BL = 1,
+ POS_LEFT_UR = 2,
+ POS_LEFT_BR = 3,
+ POS_RIGHT_UL = 4,
+ POS_RIGHT_BL = 5,
+ POS_RIGHT_UR = 6,
+ POS_RIGHT_BR = 7,
+ POS_BOTTOM = 8,
+ POS_MAX = 9,
+}
+var slot: int = POS_RIGHT_BR
+var _initialized: bool = false
+var plugin: EditorPlugin
+var window: Window
+var _godot_last_state: Window.Mode = Window.MODE_FULLSCREEN
+
+
+func initialize(p_plugin: EditorPlugin) -> void:
+ if p_plugin:
+ plugin = p_plugin
+
+ _godot_last_state = plugin.godot_editor_window.mode
+ placement_opt = $Box/Buttons/PlacementOpt
+ pinned_btn = $Box/Buttons/Pinned
+ floating_btn = $Box/Buttons/Floating
+ floating_btn.owner = null
+ size_slider = $Box/Buttons/SizeSlider
+ size_slider.owner = null
+ box = $Box
+ buttons = $Box/Buttons
+ textures_btn = $Box/Buttons/TexturesBtn
+ meshes_btn = $Box/Buttons/MeshesBtn
+ asset_container = $Box/ScrollContainer
+
+ texture_list = ListContainer.new()
+ texture_list.plugin = plugin
+ texture_list.type = Terrain3DAssets.TYPE_TEXTURE
+ asset_container.add_child(texture_list)
+ mesh_list = ListContainer.new()
+ mesh_list.plugin = plugin
+ mesh_list.type = Terrain3DAssets.TYPE_MESH
+ mesh_list.visible = false
+ asset_container.add_child(mesh_list)
+ _current_list = texture_list
+
+ load_editor_settings()
+
+ # Connect signals
+ resized.connect(update_layout)
+ textures_btn.pressed.connect(_on_textures_pressed)
+ meshes_btn.pressed.connect(_on_meshes_pressed)
+ placement_opt.item_selected.connect(set_slot)
+ floating_btn.pressed.connect(make_dock_float)
+ pinned_btn.toggled.connect(_on_pin_changed)
+ pinned_btn.visible = ( window != null )
+ size_slider.value_changed.connect(_on_slider_changed)
+ plugin.ui.toolbar.tool_changed.connect(_on_tool_changed)
+
+ meshes_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale())
+ textures_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale())
+
+ _initialized = true
+ update_dock()
+ update_layout()
+
+
+func _ready() -> void:
+ if not _initialized:
+ return
+
+ # Setup styles
+ set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel"))
+ # Avoid saving icon resources in tscn when editing w/ a tool script
+ if EditorInterface.get_edited_scene_root() != self:
+ pinned_btn.icon = get_theme_icon("Pin", "EditorIcons")
+ pinned_btn.text = ""
+ floating_btn.icon = get_theme_icon("MakeFloating", "EditorIcons")
+ floating_btn.text = ""
+
+ update_thumbnails()
+ confirm_dialog = ConfirmationDialog.new()
+ add_child(confirm_dialog)
+ confirm_dialog.hide()
+ confirm_dialog.confirmed.connect(func(): _confirmed = true; \
+ emit_signal("confirmation_closed"); \
+ emit_signal("confirmation_confirmed") )
+ confirm_dialog.canceled.connect(func(): _confirmed = false; \
+ emit_signal("confirmation_closed"); \
+ emit_signal("confirmation_canceled") )
+
+
+func get_current_list() -> ListContainer:
+ return _current_list
+
+
+## Dock placement
+
+func set_slot(p_slot: int) -> void:
+ p_slot = clamp(p_slot, 0, POS_MAX-1)
+
+ if slot != p_slot:
+ slot = p_slot
+ placement_opt.selected = slot
+ save_editor_settings()
+ plugin.select_terrain()
+ update_dock()
+
+
+func remove_dock(p_force: bool = false) -> void:
+ if state == SIDEBAR:
+ plugin.remove_control_from_docks(self)
+ state = HIDDEN
+
+ elif state == BOTTOM:
+ plugin.remove_control_from_bottom_panel(self)
+ state = HIDDEN
+
+ # If windowed and destination is not window or final exit, otherwise leave
+ elif state == WINDOWED and p_force and window:
+ var parent: Node = get_parent()
+ if parent:
+ parent.remove_child(self)
+ plugin.godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
+ plugin.godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
+ plugin.godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited)
+ window.hide()
+ window.queue_free()
+ window = null
+ floating_btn.button_pressed = false
+ floating_btn.visible = true
+ pinned_btn.visible = false
+ placement_opt.visible = true
+ state = HIDDEN
+ update_dock() # return window to side/bottom
+
+
+func update_dock() -> void:
+ if not _initialized or window:
+ return
+
+ update_assets()
+
+ # Move dock to new destination
+ remove_dock()
+ # Sidebar
+ if slot < POS_BOTTOM:
+ state = SIDEBAR
+ plugin.add_control_to_dock(slot, self)
+ # Bottom
+ elif slot == POS_BOTTOM:
+ state = BOTTOM
+ plugin.add_control_to_bottom_panel(self, "Terrain3D")
+ plugin.make_bottom_panel_item_visible(self)
+
+
+func update_layout() -> void:
+ if not _initialized:
+ return
+
+ # Detect if we have a new window from Make floating, grab it so we can free it properly
+ if not window and get_parent() and get_parent().get_parent() is Window:
+ window = get_parent().get_parent()
+ make_dock_float()
+ return # Will call this function again upon display
+
+ var size_parent: Control = size_slider.get_parent()
+ # Vertical layout in window / sidebar
+ if window or slot < POS_BOTTOM:
+ box.vertical = true
+ buttons.vertical = false
+
+ if size.x >= 500 and size_parent != buttons:
+ size_slider.reparent(buttons)
+ buttons.move_child(size_slider, 3)
+ elif size.x < 500 and size_parent != box:
+ size_slider.reparent(box)
+ box.move_child(size_slider, 1)
+ floating_btn.reparent(buttons)
+ buttons.move_child(floating_btn, 4)
+
+ # Wide layout on bottom bar
+ else:
+ size_slider.reparent(buttons)
+ buttons.move_child(size_slider, 3)
+ floating_btn.reparent(box)
+ box.vertical = false
+ buttons.vertical = true
+
+ save_editor_settings()
+
+
+func update_thumbnails() -> void:
+ if not is_instance_valid(plugin.terrain):
+ return
+ if _current_list.type == Terrain3DAssets.TYPE_MESH and \
+ Time.get_ticks_msec() - _last_thumb_update_time > MAX_UPDATE_TIME:
+ plugin.terrain.assets.create_mesh_thumbnails()
+ _last_thumb_update_time = Time.get_ticks_msec()
+ for mesh_asset in mesh_list.entries:
+ mesh_asset.queue_redraw()
+
+
+## Dock Button handlers
+
+
+func _on_pin_changed(toggled: bool) -> void:
+ if window:
+ window.always_on_top = pinned_btn.button_pressed
+ save_editor_settings()
+
+
+func _on_slider_changed(value: float) -> void:
+ if texture_list:
+ texture_list.set_entry_width(value)
+ if mesh_list:
+ mesh_list.set_entry_width(value)
+ save_editor_settings()
+
+
+func _on_textures_pressed() -> void:
+ _current_list = texture_list
+ texture_list.update_asset_list()
+ texture_list.visible = true
+ mesh_list.visible = false
+ textures_btn.button_pressed = true
+ meshes_btn.button_pressed = false
+ texture_list.set_selected_id(texture_list.selected_id)
+ if plugin.is_terrain_valid():
+ EditorInterface.edit_node(plugin.terrain)
+ save_editor_settings()
+
+
+func _on_meshes_pressed() -> void:
+ _current_list = mesh_list
+ mesh_list.update_asset_list()
+ mesh_list.visible = true
+ texture_list.visible = false
+ meshes_btn.button_pressed = true
+ textures_btn.button_pressed = false
+ mesh_list.set_selected_id(mesh_list.selected_id)
+ if plugin.is_terrain_valid():
+ EditorInterface.edit_node(plugin.terrain)
+ update_thumbnails()
+ save_editor_settings()
+
+
+func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
+ if p_tool == Terrain3DEditor.INSTANCER:
+ _on_meshes_pressed()
+ elif p_tool in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
+ _on_textures_pressed()
+
+
+## Update Dock Contents
+
+
+func update_assets() -> void:
+ if not _initialized:
+ return
+
+ # Verify signals to individual lists
+ if plugin.is_terrain_valid() and plugin.terrain.assets:
+ if not plugin.terrain.assets.textures_changed.is_connected(texture_list.update_asset_list):
+ plugin.terrain.assets.textures_changed.connect(texture_list.update_asset_list)
+ if not plugin.terrain.assets.meshes_changed.is_connected(mesh_list.update_asset_list):
+ plugin.terrain.assets.meshes_changed.connect(mesh_list.update_asset_list)
+
+ _current_list.update_asset_list()
+
+
+## Window Management
+
+
+func make_dock_float() -> void:
+ # If not already created (eg from editor panel 'Make Floating' button)
+ if not window:
+ remove_dock()
+ create_window()
+
+ state = WINDOWED
+ visible = true # Asset dock contents are hidden when popping out of the bottom!
+ pinned_btn.visible = true
+ floating_btn.visible = false
+ placement_opt.visible = false
+ window.title = "Terrain3D Asset Dock"
+ window.always_on_top = pinned_btn.button_pressed
+ window.close_requested.connect(remove_dock.bind(true))
+ window.window_input.connect(_on_window_input)
+ window.focus_exited.connect(save_editor_settings)
+ window.mouse_exited.connect(save_editor_settings)
+ window.size_changed.connect(save_editor_settings)
+ plugin.godot_editor_window.mouse_entered.connect(_on_godot_window_entered)
+ plugin.godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
+ plugin.godot_editor_window.focus_exited.connect(_on_godot_focus_exited)
+ plugin.godot_editor_window.grab_focus()
+ update_assets()
+ save_editor_settings()
+
+
+func create_window() -> void:
+ window = Window.new()
+ window.wrap_controls = true
+ var mc := MarginContainer.new()
+ mc.set_anchors_preset(PRESET_FULL_RECT, false)
+ mc.add_child(self)
+ window.add_child(mc)
+ window.set_transient(false)
+ window.set_size(plugin.get_setting(ES_DOCK_WINDOW_SIZE, Vector2i(512, 512)))
+ window.set_position(plugin.get_setting(ES_DOCK_WINDOW_POSITION, Vector2i(704, 284)))
+ plugin.add_child(window)
+ window.show()
+
+
+func clamp_window_position() -> void:
+ if window and window.visible:
+ var bounds: Vector2i
+ if EditorInterface.get_editor_settings().get_setting("interface/editor/single_window_mode"):
+ bounds = EditorInterface.get_base_control().size
+ else:
+ bounds = DisplayServer.screen_get_position(window.current_screen)
+ bounds += DisplayServer.screen_get_size(window.current_screen)
+ var margin: int = 40
+ window.position.x = clamp(window.position.x, -window.size.x + 2*margin, bounds.x - margin)
+ window.position.y = clamp(window.position.y, 25, bounds.y - margin)
+
+
+func _on_window_input(event: InputEvent) -> void:
+ # Capture CTRL+S when doc focused to save scene
+ if event is InputEventKey and event.keycode == KEY_S and event.pressed and event.is_command_or_control_pressed():
+ save_editor_settings()
+ EditorInterface.save_scene()
+
+
+func _on_godot_window_entered() -> void:
+ if is_instance_valid(window) and window.has_focus():
+ plugin.godot_editor_window.grab_focus()
+
+
+func _on_godot_focus_entered() -> void:
+ # If asset dock is windowed, and Godot was minimized, and now is not, restore asset dock window
+ if is_instance_valid(window):
+ if _godot_last_state == Window.MODE_MINIMIZED and plugin.godot_editor_window.mode != Window.MODE_MINIMIZED:
+ window.show()
+ _godot_last_state = plugin.godot_editor_window.mode
+ plugin.godot_editor_window.grab_focus()
+
+
+func _on_godot_focus_exited() -> void:
+ if is_instance_valid(window) and plugin.godot_editor_window.mode == Window.MODE_MINIMIZED:
+ window.hide()
+ _godot_last_state = plugin.godot_editor_window.mode
+
+
+## Manage Editor Settings
+
+func load_editor_settings() -> void:
+ floating_btn.button_pressed = plugin.get_setting(ES_DOCK_FLOATING, false)
+ pinned_btn.button_pressed = plugin.get_setting(ES_DOCK_PINNED, true)
+ size_slider.value = plugin.get_setting(ES_DOCK_TILE_SIZE, 83)
+ _on_slider_changed(size_slider.value)
+ set_slot(plugin.get_setting(ES_DOCK_SLOT, POS_BOTTOM))
+ if floating_btn.button_pressed:
+ make_dock_float()
+ # TODO Don't save tab until thumbnail generation more reliable
+ #if plugin.get_setting(ES_DOCK_TAB, 0) == 1:
+ # _on_meshes_pressed()
+
+
+func save_editor_settings() -> void:
+ if not _initialized:
+ return
+ clamp_window_position()
+ plugin.set_setting(ES_DOCK_SLOT, slot)
+ plugin.set_setting(ES_DOCK_TILE_SIZE, size_slider.value)
+ plugin.set_setting(ES_DOCK_FLOATING, floating_btn.button_pressed)
+ plugin.set_setting(ES_DOCK_PINNED, pinned_btn.button_pressed)
+ # TODO Don't save tab until thumbnail generation more reliable
+ # plugin.set_setting(ES_DOCK_TAB, 0 if _current_list == texture_list else 1)
+ if window:
+ plugin.set_setting(ES_DOCK_WINDOW_SIZE, window.size)
+ plugin.set_setting(ES_DOCK_WINDOW_POSITION, window.position)
+
+
+##############################################################
+## class ListContainer
+##############################################################
+
+
+class ListContainer extends Container:
+ var plugin: EditorPlugin
+ var type := Terrain3DAssets.TYPE_TEXTURE
+ var entries: Array[ListEntry]
+ var selected_id: int = 0
+ var height: float = 0
+ var width: float = 83
+ var focus_style: StyleBox
+
+
+ func _ready() -> void:
+ set_v_size_flags(SIZE_EXPAND_FILL)
+ set_h_size_flags(SIZE_EXPAND_FILL)
+
+
+ func clear() -> void:
+ for e in entries:
+ e.get_parent().remove_child(e)
+ e.queue_free()
+ entries.clear()
+
+
+ func update_asset_list() -> void:
+ clear()
+
+ # Grab terrain
+ var t: Terrain3D
+ if plugin.is_terrain_valid():
+ t = plugin.terrain
+ elif is_instance_valid(plugin._last_terrain) and plugin.is_terrain_valid(plugin._last_terrain):
+ t = plugin._last_terrain
+ else:
+ return
+
+ if not t.assets:
+ return
+
+ if type == Terrain3DAssets.TYPE_TEXTURE:
+ var texture_count: int = t.assets.get_texture_count()
+ for i in texture_count:
+ var texture: Terrain3DTextureAsset = t.assets.get_texture(i)
+ add_item(texture)
+ if texture_count < Terrain3DAssets.MAX_TEXTURES:
+ add_item()
+ else:
+ var mesh_count: int = t.assets.get_mesh_count()
+ for i in mesh_count:
+ var mesh: Terrain3DMeshAsset = t.assets.get_mesh_asset(i)
+ add_item(mesh, t.assets)
+ if mesh_count < Terrain3DAssets.MAX_MESHES:
+ add_item()
+ if selected_id >= mesh_count or selected_id < 0:
+ set_selected_id(0)
+
+
+ func add_item(p_resource: Resource = null, p_assets: Terrain3DAssets = null) -> void:
+ var entry: ListEntry = ListEntry.new()
+ entry.focus_style = focus_style
+ var id: int = entries.size()
+
+ entry.set_edited_resource(p_resource)
+ entry.hovered.connect(_on_resource_hovered.bind(id))
+ entry.selected.connect(set_selected_id.bind(id))
+ entry.inspected.connect(_on_resource_inspected)
+ entry.changed.connect(_on_resource_changed.bind(id))
+ entry.type = type
+ entry.asset_list = p_assets
+ add_child(entry)
+ entries.push_back(entry)
+
+ if p_resource:
+ entry.set_selected(id == selected_id)
+ if not p_resource.id_changed.is_connected(set_selected_after_swap):
+ p_resource.id_changed.connect(set_selected_after_swap)
+
+
+ func _on_resource_hovered(p_id: int):
+ if type == Terrain3DAssets.TYPE_MESH:
+ if plugin.terrain:
+ plugin.terrain.assets.create_mesh_thumbnails(p_id)
+
+
+ func set_selected_after_swap(p_type: Terrain3DAssets.AssetType, p_old_id: int, p_new_id: int) -> void:
+ set_selected_id(clamp(p_new_id, 0, entries.size() - 2))
+
+
+ func set_selected_id(p_id: int) -> void:
+ selected_id = p_id
+
+ for i in entries.size():
+ var entry: ListEntry = entries[i]
+ entry.set_selected(i == selected_id)
+
+ plugin.select_terrain()
+
+ # Select Paint tool if clicking a texture
+ if type == Terrain3DAssets.TYPE_TEXTURE and \
+ not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
+ var paint_btn: Button = plugin.ui.toolbar.get_node_or_null("PaintTexture")
+ if paint_btn:
+ paint_btn.set_pressed(true)
+ plugin.ui._on_tool_changed(Terrain3DEditor.TEXTURE, Terrain3DEditor.REPLACE)
+
+ elif type == Terrain3DAssets.TYPE_MESH and plugin.editor.get_tool() != Terrain3DEditor.INSTANCER:
+ var instancer_btn: Button = plugin.ui.toolbar.get_node_or_null("InstanceMeshes")
+ if instancer_btn:
+ instancer_btn.set_pressed(true)
+ plugin.ui._on_tool_changed(Terrain3DEditor.INSTANCER, Terrain3DEditor.ADD)
+
+ # Update editor with selected brush
+ plugin.ui._on_setting_changed()
+
+
+ func _on_resource_inspected(p_resource: Resource) -> void:
+ await get_tree().create_timer(.01).timeout
+ EditorInterface.edit_resource(p_resource)
+
+
+ func _on_resource_changed(p_resource: Resource, p_id: int) -> void:
+ if not p_resource:
+ var asset_dock: Control = get_parent().get_parent().get_parent()
+ if type == Terrain3DAssets.TYPE_TEXTURE:
+ asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this texture?"
+ else:
+ asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this mesh and delete all instances?"
+ asset_dock.confirm_dialog.popup_centered()
+ await asset_dock.confirmation_closed
+ if not asset_dock._confirmed:
+ update_asset_list()
+ return
+
+ if not plugin.is_terrain_valid():
+ plugin.select_terrain()
+ await get_tree().create_timer(.01).timeout
+
+ if plugin.is_terrain_valid():
+ if type == Terrain3DAssets.TYPE_TEXTURE:
+ plugin.terrain.get_assets().set_texture(p_id, p_resource)
+ else:
+ plugin.terrain.get_assets().set_mesh_asset(p_id, p_resource)
+ await get_tree().create_timer(.01).timeout
+ plugin.terrain.assets.create_mesh_thumbnails(p_id)
+
+ # If removing an entry, clear inspector
+ if not p_resource:
+ EditorInterface.inspect_object(null)
+
+ # If null resource, remove last
+ if not p_resource:
+ var last_offset: int = 2
+ if p_id == entries.size()-2:
+ last_offset = 3
+ set_selected_id(clamp(selected_id, 0, entries.size() - last_offset))
+
+
+ func get_selected_id() -> int:
+ return selected_id
+
+
+ func set_entry_width(value: float) -> void:
+ width = clamp(value, 66, 230)
+ redraw()
+
+
+ func get_entry_width() -> float:
+ return width
+
+
+ func redraw() -> void:
+ height = 0
+ var id: int = 0
+ var separation: float = 4
+ var columns: int = 3
+ columns = clamp(size.x / width, 1, 100)
+
+ for c in get_children():
+ if is_instance_valid(c):
+ c.size = Vector2(width, width) - Vector2(separation, separation)
+ c.position = Vector2(id % columns, id / columns) * width + \
+ Vector2(separation / columns, separation / columns)
+ height = max(height, c.position.y + width)
+ id += 1
+
+
+ # Needed to enable ScrollContainer scroll bar
+ func _get_minimum_size() -> Vector2:
+ return Vector2(0, height)
+
+
+ func _notification(p_what) -> void:
+ if p_what == NOTIFICATION_SORT_CHILDREN:
+ redraw()
+
+
+##############################################################
+## class ListEntry
+##############################################################
+
+
+class ListEntry extends VBoxContainer:
+ signal hovered()
+ signal selected()
+ signal changed(resource: Resource)
+ signal inspected(resource: Resource)
+
+ var resource: Resource
+ var type := Terrain3DAssets.TYPE_TEXTURE
+ var _thumbnail: Texture2D
+ var drop_data: bool = false
+ var is_hovered: bool = false
+ var is_selected: bool = false
+ var asset_list: Terrain3DAssets
+
+ @onready var button_row := HBoxContainer.new()
+ @onready var button_clear := TextureButton.new()
+ @onready var button_edit := TextureButton.new()
+ @onready var spacer := Control.new()
+ @onready var button_enabled := TextureButton.new()
+ @onready var clear_icon: Texture2D = get_theme_icon("Close", "EditorIcons")
+ @onready var edit_icon: Texture2D = get_theme_icon("Edit", "EditorIcons")
+ @onready var enabled_icon: Texture2D = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
+ @onready var disabled_icon: Texture2D = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
+
+ var name_label: Label
+ @onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons")
+ @onready var background: StyleBox = get_theme_stylebox("pressed", "Button")
+ @onready var focus_style: StyleBox = get_theme_stylebox("focus", "Button").duplicate()
+
+
+ func _ready() -> void:
+ setup_buttons()
+ setup_label()
+ focus_style.set_border_width_all(2)
+ focus_style.set_border_color(Color(1, 1, 1, .67))
+
+
+ func setup_buttons() -> void:
+ var icon_size: Vector2 = Vector2(12, 12)
+ var margin_container := MarginContainer.new()
+ margin_container.mouse_filter = Control.MOUSE_FILTER_PASS
+ margin_container.add_theme_constant_override("margin_top", 5)
+ margin_container.add_theme_constant_override("margin_left", 5)
+ margin_container.add_theme_constant_override("margin_right", 5)
+ add_child(margin_container)
+
+ button_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ button_row.alignment = BoxContainer.ALIGNMENT_CENTER
+ button_row.mouse_filter = Control.MOUSE_FILTER_PASS
+ margin_container.add_child(button_row)
+
+ if type == Terrain3DAssets.TYPE_MESH:
+ button_enabled.set_texture_normal(enabled_icon)
+ button_enabled.set_texture_pressed(disabled_icon)
+ button_enabled.set_custom_minimum_size(icon_size)
+ button_enabled.set_h_size_flags(Control.SIZE_SHRINK_END)
+ button_enabled.set_visible(resource != null)
+ button_enabled.toggle_mode = true
+ button_enabled.mouse_filter = Control.MOUSE_FILTER_PASS
+ button_enabled.pressed.connect(enable)
+ button_row.add_child(button_enabled)
+
+ button_edit.set_texture_normal(edit_icon)
+ button_edit.set_custom_minimum_size(icon_size)
+ button_edit.set_h_size_flags(Control.SIZE_SHRINK_END)
+ button_edit.set_visible(resource != null)
+ button_edit.mouse_filter = Control.MOUSE_FILTER_PASS
+ button_edit.pressed.connect(edit)
+ button_row.add_child(button_edit)
+
+ spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ spacer.mouse_filter = Control.MOUSE_FILTER_PASS
+ button_row.add_child(spacer)
+
+ button_clear.set_texture_normal(clear_icon)
+ button_clear.set_custom_minimum_size(icon_size)
+ button_clear.set_h_size_flags(Control.SIZE_SHRINK_END)
+ button_clear.set_visible(resource != null)
+ button_clear.mouse_filter = Control.MOUSE_FILTER_PASS
+ button_clear.pressed.connect(clear)
+ button_row.add_child(button_clear)
+
+
+ func setup_label() -> void:
+ name_label = Label.new()
+ add_child(name_label, true)
+ name_label.visible = false
+ name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ name_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
+ name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ name_label.add_theme_color_override("font_color", Color.WHITE)
+ name_label.add_theme_color_override("font_shadow_color", Color.BLACK)
+ name_label.add_theme_constant_override("shadow_offset_x", 1.)
+ name_label.add_theme_constant_override("shadow_offset_y", 1.)
+ name_label.add_theme_font_size_override("font_size", 15)
+ name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
+ name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
+ if type == Terrain3DAssets.TYPE_TEXTURE:
+ name_label.text = "Add Texture"
+ else:
+ name_label.text = "Add Mesh"
+
+
+ func _notification(p_what) -> void:
+ match p_what:
+ NOTIFICATION_DRAW:
+ # Hide spacer if icons are crowding small textures
+ spacer.visible = size.x > 70 or type == Terrain3DAssets.TYPE_TEXTURE
+
+ var rect: Rect2 = Rect2(Vector2.ZERO, get_size())
+ if !resource:
+ draw_style_box(background, rect)
+ draw_texture(add_icon, (get_size() / 2) - (add_icon.get_size() / 2))
+ else:
+ if type == Terrain3DAssets.TYPE_TEXTURE:
+ name_label.text = (resource as Terrain3DTextureAsset).get_name()
+ self_modulate = resource.get_albedo_color()
+ _thumbnail = resource.get_albedo_texture()
+ if _thumbnail:
+ draw_texture_rect(_thumbnail, rect, false)
+ texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST_WITH_MIPMAPS
+ else:
+ name_label.text = (resource as Terrain3DMeshAsset).get_name()
+ var id: int = (resource as Terrain3DMeshAsset).get_id()
+ _thumbnail = resource.get_thumbnail()
+ if _thumbnail:
+ draw_texture_rect(_thumbnail, rect, false)
+ texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
+ else:
+ draw_rect(rect, Color(.15, .15, .15, 1.))
+ button_enabled.set_pressed_no_signal(!resource.is_enabled())
+ name_label.add_theme_font_size_override("font_size", 4 + rect.size.x/10)
+ if drop_data:
+ draw_style_box(focus_style, rect)
+ if is_hovered:
+ draw_rect(rect, Color(1, 1, 1, 0.2))
+ if is_selected:
+ draw_style_box(focus_style, rect)
+ NOTIFICATION_MOUSE_ENTER:
+ is_hovered = true
+ name_label.visible = true
+ emit_signal("hovered")
+ queue_redraw()
+ NOTIFICATION_MOUSE_EXIT:
+ is_hovered = false
+ name_label.visible = false
+ drop_data = false
+ queue_redraw()
+
+
+ func _gui_input(p_event: InputEvent) -> void:
+ if p_event is InputEventMouseButton:
+ if p_event.is_pressed():
+ match p_event.get_button_index():
+ MOUSE_BUTTON_LEFT:
+ # If `Add new` is clicked
+ if !resource:
+ if type == Terrain3DAssets.TYPE_TEXTURE:
+ set_edited_resource(Terrain3DTextureAsset.new(), false)
+ else:
+ set_edited_resource(Terrain3DMeshAsset.new(), false)
+ edit()
+ else:
+ emit_signal("selected")
+ MOUSE_BUTTON_RIGHT:
+ if resource:
+ edit()
+ MOUSE_BUTTON_MIDDLE:
+ if resource:
+ clear()
+
+
+ func _can_drop_data(p_at_position: Vector2, p_data: Variant) -> bool:
+ drop_data = false
+ if typeof(p_data) == TYPE_DICTIONARY:
+ if p_data.files.size() == 1:
+ queue_redraw()
+ drop_data = true
+ return drop_data
+
+
+ func _drop_data(p_at_position: Vector2, p_data: Variant) -> void:
+ if typeof(p_data) == TYPE_DICTIONARY:
+ var res: Resource = load(p_data.files[0])
+ if res is Texture2D and type == Terrain3DAssets.TYPE_TEXTURE:
+ var ta := Terrain3DTextureAsset.new()
+ if resource is Terrain3DTextureAsset:
+ ta.id = resource.id
+ ta.set_albedo_texture(res)
+ set_edited_resource(ta, false)
+ resource = ta
+ elif res is Terrain3DTextureAsset and type == Terrain3DAssets.TYPE_TEXTURE:
+ if resource is Terrain3DTextureAsset:
+ res.id = resource.id
+ set_edited_resource(res, false)
+ elif res is PackedScene and type == Terrain3DAssets.TYPE_MESH:
+ var ma := Terrain3DMeshAsset.new()
+ if resource is Terrain3DMeshAsset:
+ ma.id = resource.id
+ set_edited_resource(ma, false)
+ ma.set_scene_file(res)
+ resource = ma
+ elif res is Terrain3DMeshAsset and type == Terrain3DAssets.TYPE_MESH:
+ if resource is Terrain3DMeshAsset:
+ res.id = resource.id
+ set_edited_resource(res, false)
+ emit_signal("selected")
+ emit_signal("inspected", resource)
+
+
+
+ func set_edited_resource(p_res: Resource, p_no_signal: bool = true) -> void:
+ resource = p_res
+ if resource:
+ resource.setting_changed.connect(_on_resource_changed)
+ resource.file_changed.connect(_on_resource_changed)
+ if resource is Terrain3DMeshAsset:
+ resource.instancer_setting_changed.connect(_on_resource_changed)
+
+ if button_clear:
+ button_clear.set_visible(resource != null)
+
+ queue_redraw()
+ if !p_no_signal:
+ emit_signal("changed", resource)
+
+
+ func _on_resource_changed() -> void:
+ queue_redraw()
+ emit_signal("changed", resource)
+
+
+ func set_selected(value: bool) -> void:
+ is_selected = value
+ queue_redraw()
+
+
+ func clear() -> void:
+ if resource:
+ set_edited_resource(null, false)
+
+
+ func edit() -> void:
+ emit_signal("selected")
+ emit_signal("inspected", resource)
+
+
+ func enable() -> void:
+ if resource is Terrain3DMeshAsset:
+ resource.set_enabled(!resource.is_enabled())
diff --git a/addons/terrain_3d/src/asset_dock.gd.uid b/addons/terrain_3d/src/asset_dock.gd.uid
new file mode 100644
index 0000000..8c39f29
--- /dev/null
+++ b/addons/terrain_3d/src/asset_dock.gd.uid
@@ -0,0 +1 @@
+uid://bgoifepft1hjw
diff --git a/addons/terrain_3d/src/asset_dock.tscn b/addons/terrain_3d/src/asset_dock.tscn
new file mode 100644
index 0000000..f88c8c0
--- /dev/null
+++ b/addons/terrain_3d/src/asset_dock.tscn
@@ -0,0 +1,92 @@
+[gd_scene load_steps=2 format=3 uid="uid://dkb6hii5e48m2"]
+
+[ext_resource type="Script" path="res://addons/terrain_3d/src/asset_dock.gd" id="1_e23pg"]
+
+[node name="Terrain3D" type="PanelContainer"]
+custom_minimum_size = Vector2(256, 95)
+offset_right = 766.0
+offset_bottom = 100.0
+script = ExtResource("1_e23pg")
+
+[node name="Box" type="BoxContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 3
+vertical = true
+
+[node name="Buttons" type="BoxContainer" parent="Box"]
+layout_mode = 2
+
+[node name="TexturesBtn" type="Button" parent="Box/Buttons"]
+custom_minimum_size = Vector2(80, 30)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+theme_override_font_sizes/font_size = 16
+toggle_mode = true
+button_pressed = true
+text = "Textures"
+
+[node name="MeshesBtn" type="Button" parent="Box/Buttons"]
+custom_minimum_size = Vector2(80, 30)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+theme_override_font_sizes/font_size = 16
+toggle_mode = true
+text = "Meshes"
+
+[node name="PlacementOpt" type="OptionButton" parent="Box/Buttons"]
+custom_minimum_size = Vector2(80, 30)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+selected = 7
+item_count = 9
+popup/item_0/text = "Left_UL"
+popup/item_1/text = "Left_BL"
+popup/item_1/id = 1
+popup/item_2/text = "Left_UR"
+popup/item_2/id = 2
+popup/item_3/text = "Left_BR"
+popup/item_3/id = 3
+popup/item_4/text = "Right_UL"
+popup/item_4/id = 4
+popup/item_5/text = "Right_BL "
+popup/item_5/id = 5
+popup/item_6/text = "Right_UR"
+popup/item_6/id = 6
+popup/item_7/text = "Right_BR"
+popup/item_7/id = 7
+popup/item_8/text = "Bottom"
+popup/item_8/id = 8
+
+[node name="SizeSlider" type="HSlider" parent="Box/Buttons"]
+custom_minimum_size = Vector2(80, 10)
+layout_mode = 2
+size_flags_horizontal = 3
+min_value = 66.0
+max_value = 230.0
+value = 83.0
+
+[node name="Floating" type="Button" parent="Box/Buttons"]
+layout_mode = 2
+size_flags_horizontal = 0
+size_flags_vertical = 0
+tooltip_text = "Pop this dock out to a floating window."
+toggle_mode = true
+text = "F"
+flat = true
+
+[node name="Pinned" type="Button" parent="Box/Buttons"]
+layout_mode = 2
+size_flags_horizontal = 0
+size_flags_vertical = 0
+tooltip_text = "Make this window \"Always on top\"."
+toggle_mode = true
+text = "P"
+flat = true
+
+[node name="ScrollContainer" type="ScrollContainer" parent="Box"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
diff --git a/addons/terrain_3d/src/double_slider.gd b/addons/terrain_3d/src/double_slider.gd
new file mode 100644
index 0000000..8119456
--- /dev/null
+++ b/addons/terrain_3d/src/double_slider.gd
@@ -0,0 +1,164 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# DoubleSlider for Terrain3D
+# Should work for other UIs
+@tool
+class_name DoubleSlider
+extends Control
+
+signal value_changed(Vector2)
+var label: Label
+var suffix: String
+var grabbed_handle: int = 0 # -1 left, 0 none, 1 right
+var min_value: float = 0.0
+var max_value: float = 100.0
+var step: float = 1.0
+var range := Vector2(0, 100)
+var display_scale: float = 1.
+var position_x: float = 0.
+var minimum_x: float = 60.
+
+
+func _ready() -> void:
+ # Setup Display Scale
+ # 0 auto, 1 75%, 2 100%, 3 125%, 4 150%, 5 175%, 6 200%, 7 custom
+ var es: EditorSettings = EditorInterface.get_editor_settings()
+ var ds: int = es.get_setting("interface/editor/display_scale")
+ if ds == 0:
+ ds = 2
+ elif ds == 7:
+ display_scale = es.get_setting("interface/editor/custom_display_scale")
+ else:
+ display_scale = float(ds + 2) * .25
+
+ update_label()
+
+
+func set_min(p_value: float) -> void:
+ min_value = p_value
+ if range.x <= min_value:
+ range.x = min_value
+ set_value(range)
+ update_label()
+
+
+func get_min() -> float:
+ return min_value
+
+
+func set_max(p_value: float) -> void:
+ max_value = p_value
+ if range.y == 0 or range.y >= max_value:
+ range.y = max_value
+ set_value(range)
+ update_label()
+
+
+func get_max() -> float:
+ return max_value
+
+
+func set_step(p_step: float) -> void:
+ step = p_step
+
+
+func get_step() -> float:
+ return step
+
+
+func set_value(p_range: Vector2) -> void:
+ range.x = clamp(p_range.x, min_value, max_value)
+ range.y = clamp(p_range.y, min_value, max_value)
+ if range.y < range.x:
+ var tmp: float = range.x
+ range.x = range.y
+ range.y = tmp
+
+ update_label()
+ emit_signal("value_changed", Vector2(range.x, range.y))
+ queue_redraw()
+
+
+func get_value() -> Vector2:
+ return range
+
+
+func update_label() -> void:
+ if label:
+ label.set_text(str(range.x) + suffix + "/" + str(range.y) + suffix)
+ if position_x == 0:
+ position_x = label.position.x
+ else:
+ label.position.x = position_x + 5 * display_scale
+ label.custom_minimum_size.x = minimum_x + 5 * display_scale
+
+
+func _get_handle() -> int:
+ return 1
+
+
+func _gui_input(p_event: InputEvent) -> void:
+ if p_event is InputEventMouseButton:
+ var button: int = p_event.get_button_index()
+ if button in [ MOUSE_BUTTON_LEFT, MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN ]:
+ if p_event.is_pressed():
+ var mid_point = (range.x + range.y) / 2.0
+ var xpos: float = p_event.get_position().x * 2.0
+ if xpos >= mid_point:
+ grabbed_handle = 1
+ else:
+ grabbed_handle = -1
+ match button:
+ MOUSE_BUTTON_LEFT:
+ set_slider(p_event.get_position().x)
+ MOUSE_BUTTON_WHEEL_DOWN:
+ set_slider(-1., true)
+ MOUSE_BUTTON_WHEEL_UP:
+ set_slider(1., true)
+ else:
+ grabbed_handle = 0
+
+ if p_event is InputEventMouseMotion:
+ if grabbed_handle != 0:
+ set_slider(p_event.get_position().x)
+
+
+func set_slider(p_xpos: float, p_relative: bool = false) -> void:
+ if grabbed_handle == 0:
+ return
+ var xpos_step: float = clamp(snappedf((p_xpos / size.x) * max_value, step), min_value, max_value)
+ if(grabbed_handle < 0):
+ if p_relative:
+ range.x += p_xpos
+ else:
+ range.x = xpos_step
+ else:
+ if p_relative:
+ range.y += p_xpos
+ else:
+ range.y = xpos_step
+ set_value(range)
+
+
+func _notification(p_what: int) -> void:
+ if p_what == NOTIFICATION_DRAW:
+ # Draw background bar
+ var bg: StyleBox = get_theme_stylebox("slider", "HSlider")
+ var bg_height: float = bg.get_minimum_size().y
+ var mid_y: float = (size.y - bg_height) / 2.0
+ draw_style_box(bg, Rect2(Vector2(0, mid_y), Vector2(size.x, bg_height)))
+
+ # Draw foreground bar
+ var handle: Texture2D = get_theme_icon("grabber", "HSlider")
+ var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
+ var startx: float = (range.x / max_value) * size.x
+ var endx: float = (range.y / max_value) * size.x
+ draw_style_box(area, Rect2(Vector2(startx, mid_y), Vector2(endx - startx, bg_height)))
+
+ # Draw handles, slightly in so they don't get on the outside edges
+ var handle_pos: Vector2
+ handle_pos.x = clamp(startx - handle.get_size().x/2, -10, size.x)
+ handle_pos.y = clamp(endx - handle.get_size().x/2, 0, size.x - 10)
+ draw_texture(handle, Vector2(handle_pos.x, -mid_y - 10 * (display_scale - 1.)))
+ draw_texture(handle, Vector2(handle_pos.y, -mid_y - 10 * (display_scale - 1.)))
+
+ update_label()
diff --git a/addons/terrain_3d/src/double_slider.gd.uid b/addons/terrain_3d/src/double_slider.gd.uid
new file mode 100644
index 0000000..1e73752
--- /dev/null
+++ b/addons/terrain_3d/src/double_slider.gd.uid
@@ -0,0 +1 @@
+uid://stro0p1oawfb
diff --git a/addons/terrain_3d/src/editor_plugin.gd b/addons/terrain_3d/src/editor_plugin.gd
new file mode 100644
index 0000000..e3e732e
--- /dev/null
+++ b/addons/terrain_3d/src/editor_plugin.gd
@@ -0,0 +1,458 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Editor Plugin for Terrain3D
+@tool
+extends EditorPlugin
+
+
+# Includes
+const UI: Script = preload("res://addons/terrain_3d/src/ui.gd")
+const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd")
+const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn"
+
+var modifier_ctrl: bool
+var modifier_alt: bool
+var modifier_shift: bool
+var _last_modifiers: int = 0
+var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating
+var rmb_release_time: int = 0
+var _use_meta: bool = false
+
+var terrain: Terrain3D
+var _last_terrain: Terrain3D
+var nav_region: NavigationRegion3D
+
+var editor: Terrain3DEditor
+var editor_settings: EditorSettings
+var ui: Node # Terrain3DUI see Godot #75388
+var asset_dock: PanelContainer
+var region_gizmo: RegionGizmo
+var current_region_position: Vector2
+var mouse_global_position: Vector3 = Vector3.ZERO
+var godot_editor_window: Window # The Godot Editor window
+
+
+func _init() -> void:
+ if OS.get_name() == "macOS":
+ _use_meta = true
+
+ # Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control
+ godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent()
+ godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
+
+
+func _enter_tree() -> void:
+ editor = Terrain3DEditor.new()
+ setup_editor_settings()
+ ui = UI.new()
+ ui.plugin = self
+ add_child(ui)
+
+ region_gizmo = RegionGizmo.new()
+
+ scene_changed.connect(_on_scene_changed)
+
+ asset_dock = load(ASSET_DOCK).instantiate()
+ asset_dock.initialize(self)
+
+
+func _exit_tree() -> void:
+ asset_dock.remove_dock(true)
+ asset_dock.queue_free()
+ ui.queue_free()
+ editor.free()
+
+ scene_changed.disconnect(_on_scene_changed)
+ godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
+
+
+func _on_godot_focus_entered() -> void:
+ _read_input()
+ ui.update_decal()
+
+
+## EditorPlugin selection function call chain isn't consistent. Here's the map of calls:
+## Assume we handle Terrain3D and NavigationRegion3D
+# Click Terrain3D: _handles(Terrain3D), _make_visible(true), _edit(Terrain3D)
+# Deselect: _make_visible(false), _edit(null)
+# Click other node: _handles(OtherNode)
+# Click NavRegion3D: _handles(NavReg3D), _make_visible(true), _edit(NavReg3D)
+# Click NavRegion3D, Terrain3D: _handles(Terrain3D), _edit(Terrain3D)
+# Click Terrain3D, NavRegion3D: _handles(NavReg3D), _edit(NavReg3D)
+func _handles(p_object: Object) -> bool:
+ if p_object is Terrain3D:
+ return true
+ elif p_object is NavigationRegion3D and is_instance_valid(_last_terrain):
+ return true
+
+ # Terrain3DObjects requires access to EditorUndoRedoManager. The only way to make sure it
+ # always has it, is to pass it in here. _edit is NOT called if the node is cut and pasted.
+ elif p_object is Terrain3DObjects:
+ p_object.editor_setup(self)
+ elif p_object is Node3D and p_object.get_parent() is Terrain3DObjects:
+ p_object.get_parent().editor_setup(self)
+
+ return false
+
+
+func _make_visible(p_visible: bool, p_redraw: bool = false) -> void:
+ if p_visible and is_selected():
+ ui.set_visible(true)
+ asset_dock.update_dock()
+ else:
+ ui.set_visible(false)
+
+
+func _edit(p_object: Object) -> void:
+ if !p_object:
+ _clear()
+
+ if p_object is Terrain3D:
+ if p_object == terrain:
+ return
+ terrain = p_object
+ _last_terrain = terrain
+ terrain.set_plugin(self)
+ terrain.set_editor(editor)
+ editor.set_terrain(terrain)
+ region_gizmo.set_node_3d(terrain)
+ terrain.add_gizmo(region_gizmo)
+ ui.set_visible(true)
+ terrain.set_meta("_edit_lock_", true)
+
+ # Get alerted when a new asset list is loaded
+ if not terrain.assets_changed.is_connected(asset_dock.update_assets):
+ terrain.assets_changed.connect(asset_dock.update_assets)
+ asset_dock.update_assets()
+ # Get alerted when the region map changes
+ if not terrain.data.region_map_changed.is_connected(update_region_grid):
+ terrain.data.region_map_changed.connect(update_region_grid)
+ update_region_grid()
+ else:
+ _clear()
+
+ if is_terrain_valid(_last_terrain):
+ if p_object is NavigationRegion3D:
+ ui.set_visible(true, true)
+ nav_region = p_object
+ else:
+ nav_region = null
+
+
+func _clear() -> void:
+ if is_terrain_valid():
+ if terrain.data.region_map_changed.is_connected(update_region_grid):
+ terrain.data.region_map_changed.disconnect(update_region_grid)
+
+ terrain.clear_gizmos()
+ terrain = null
+ editor.set_terrain(null)
+
+ ui.clear_picking()
+
+ region_gizmo.clear()
+
+
+func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> AfterGUIInput:
+ if not is_terrain_valid():
+ return AFTER_GUI_INPUT_PASS
+
+ var continue_input: AfterGUIInput = _read_input(p_event)
+ if continue_input != AFTER_GUI_INPUT_CUSTOM:
+ return continue_input
+ ui.update_decal()
+
+ ## Setup active camera & viewport
+ # Always update this for all inputs, as the mouse position can move without
+ # necessarily being a InputEventMouseMotion object. get_intersection() also
+ # returns the last frame position, and should be updated more frequently.
+
+ # Snap terrain to current camera
+ terrain.set_camera(p_viewport_camera)
+
+ # Detect if viewport is set to half_resolution
+ # Structure is: Node3DEditorViewportContainer/Node3DEditorViewport(4)/SubViewportContainer/SubViewport/Camera3D
+ var editor_vpc: SubViewportContainer = p_viewport_camera.get_parent().get_parent()
+ var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true
+
+ ## Get mouse location on terrain
+ # Project 2D mouse position to 3D position and direction
+ var vp_mouse_pos: Vector2 = editor_vpc.get_local_mouse_position()
+ var mouse_pos: Vector2 = vp_mouse_pos if full_resolution else vp_mouse_pos / 2
+ var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos)
+ var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos)
+
+ # If region tool, grab mouse position without considering height
+ if editor.get_tool() == Terrain3DEditor.REGION:
+ var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir)
+ mouse_global_position = (camera_pos + t * camera_dir)
+ else:
+ #Else look for intersection with terrain
+ var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir, true)
+ if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan
+ return AFTER_GUI_INPUT_PASS
+ mouse_global_position = intersection_point
+
+ ## Handle mouse movement
+ if p_event is InputEventMouseMotion:
+
+ if _input_mode != -1: # Not cam rotation
+ ## Update region highlight
+ var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
+ / (terrain.get_region_size() * terrain.get_vertex_spacing()) ).floor()
+ if current_region_position != region_position:
+ current_region_position = region_position
+ update_region_grid()
+
+ if _input_mode > 0 and editor.is_operating():
+ # Inject pressure - Relies on C++ set_brush_data() using same dictionary instance
+ ui.brush_data["mouse_pressure"] = p_event.pressure
+
+ editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
+ return AFTER_GUI_INPUT_STOP
+
+ return AFTER_GUI_INPUT_PASS
+
+ if p_event is InputEventMouseButton and _input_mode > 0:
+ if p_event.is_pressed():
+ # If picking
+ if ui.is_picking():
+ ui.pick(mouse_global_position)
+ if not ui.operation_builder or not ui.operation_builder.is_ready():
+ return AFTER_GUI_INPUT_STOP
+
+ if modifier_ctrl and editor.get_tool() == Terrain3DEditor.HEIGHT:
+ var height: float = terrain.data.get_height(mouse_global_position)
+ ui.brush_data["height"] = height
+ ui.tool_settings.set_setting("height", height)
+
+ # If adjusting regions
+ if editor.get_tool() == Terrain3DEditor.REGION:
+ # Skip regions that already exist or don't
+ var has_region: bool = terrain.data.has_regionp(mouse_global_position)
+ var op: int = editor.get_operation()
+ if ( has_region and op == Terrain3DEditor.ADD) or \
+ ( not has_region and op == Terrain3DEditor.SUBTRACT ):
+ return AFTER_GUI_INPUT_STOP
+
+ # If an automatic operation is ready to go (e.g. gradient)
+ if ui.operation_builder and ui.operation_builder.is_ready():
+ ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y)
+ return AFTER_GUI_INPUT_STOP
+
+ # Mouse clicked, start editing
+ editor.start_operation(mouse_global_position)
+ editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
+ return AFTER_GUI_INPUT_STOP
+
+ # _input_apply released, save undo data
+ elif editor.is_operating():
+ editor.stop_operation()
+ return AFTER_GUI_INPUT_STOP
+
+ return AFTER_GUI_INPUT_PASS
+
+
+func _read_input(p_event: InputEvent = null) -> AfterGUIInput:
+ ## Determine if user is moving camera or applying
+ if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) or \
+ p_event is InputEventMouseButton and p_event.is_released() and \
+ p_event.get_button_index() == MOUSE_BUTTON_LEFT:
+ _input_mode = 1
+ else:
+ _input_mode = 0
+
+ match get_setting("editors/3d/navigation/navigation_scheme", 0):
+ 2, 1: # Modo, Maya
+ if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
+ ( Input.is_key_pressed(KEY_ALT) and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) ):
+ _input_mode = -1
+ if p_event is InputEventMouseButton and p_event.is_released() and \
+ ( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
+ ( Input.is_key_pressed(KEY_ALT) and p_event.get_button_index() == MOUSE_BUTTON_LEFT )):
+ rmb_release_time = Time.get_ticks_msec()
+ 0, _: # Godot
+ if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
+ Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE):
+ _input_mode = -1
+ if p_event is InputEventMouseButton and p_event.is_released() and \
+ ( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
+ p_event.get_button_index() == MOUSE_BUTTON_MIDDLE ):
+ rmb_release_time = Time.get_ticks_msec()
+ if _input_mode < 0:
+ # Camera is moving, skip input
+ return AFTER_GUI_INPUT_PASS
+
+ ## Determine modifiers pressed
+ modifier_shift = Input.is_key_pressed(KEY_SHIFT)
+
+ # Editor responds to modifier_ctrl so we must register touchscreen Invert
+ if _use_meta:
+ modifier_ctrl = Input.is_key_pressed(KEY_META) || ui.inverted_input
+ else:
+ modifier_ctrl = Input.is_key_pressed(KEY_CTRL) || ui.inverted_input
+
+ # Keybind enum: Alt,Space,Meta,Capslock
+ var alt_key: int
+ match get_setting("terrain3d/config/alt_key_bind", 0):
+ 3: alt_key = KEY_CAPSLOCK
+ 2: alt_key = KEY_META
+ 1: alt_key = KEY_SPACE
+ 0, _: alt_key = KEY_ALT
+ modifier_alt = Input.is_key_pressed(alt_key)
+ var current_mods: int = int(modifier_shift) | int(modifier_ctrl) << 1 | int(modifier_alt) << 2
+
+ ## Process Hotkeys
+ if p_event is InputEventKey and \
+ current_mods == 0 and \
+ p_event.is_pressed() and \
+ not p_event.is_echo() and \
+ consume_hotkey(p_event.keycode):
+ # Hotkey found, consume event, and stop input processing
+ EditorInterface.get_editor_viewport_3d().set_input_as_handled()
+ return AFTER_GUI_INPUT_STOP
+
+ # Brush data is cleared on set_tool, or clicking textures in the asset dock
+ # Update modifiers if changed or missing
+ if _last_modifiers != current_mods or not ui.brush_data.has("modifier_shift"):
+ _last_modifiers = current_mods
+ ui.brush_data["modifier_shift"] = modifier_shift
+ ui.brush_data["modifier_ctrl"] = modifier_ctrl
+ ui.brush_data["modifier_alt"] = modifier_alt
+ ui.set_active_operation()
+
+ ## Continue processing input
+ return AFTER_GUI_INPUT_CUSTOM
+
+
+# Returns true if hotkey matches and operation triggered
+func consume_hotkey(keycode: int) -> bool:
+ match keycode:
+ KEY_1:
+ terrain.material.set_show_region_grid(!terrain.material.get_show_region_grid())
+ KEY_2:
+ terrain.material.set_show_instancer_grid(!terrain.material.get_show_instancer_grid())
+ KEY_3:
+ terrain.material.set_show_vertex_grid(!terrain.material.get_show_vertex_grid())
+ KEY_4:
+ terrain.material.set_show_contours(!terrain.material.get_show_contours())
+ KEY_E:
+ ui.toolbar.get_button("AddRegion").set_pressed(true)
+ KEY_R:
+ ui.toolbar.get_button("Raise").set_pressed(true)
+ KEY_H:
+ ui.toolbar.get_button("Height").set_pressed(true)
+ KEY_S:
+ ui.toolbar.get_button("Slope").set_pressed(true)
+ KEY_C:
+ ui.toolbar.get_button("PaintColor").set_pressed(true)
+ KEY_N:
+ ui.toolbar.get_button("PaintNavigableArea").set_pressed(true)
+ KEY_I:
+ ui.toolbar.get_button("InstanceMeshes").set_pressed(true)
+ KEY_X:
+ ui.toolbar.get_button("AddHoles").set_pressed(true)
+ KEY_W:
+ ui.toolbar.get_button("PaintWetness").set_pressed(true)
+ KEY_B:
+ ui.toolbar.get_button("PaintTexture").set_pressed(true)
+ KEY_V:
+ ui.toolbar.get_button("SprayTexture").set_pressed(true)
+ KEY_A:
+ ui.toolbar.get_button("PaintAutoshader").set_pressed(true)
+ _:
+ return false
+ return true
+
+
+func update_region_grid() -> void:
+ if not region_gizmo:
+ return
+ region_gizmo.set_hidden(not ui.visible)
+
+ if is_terrain_valid():
+ region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
+ region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
+ region_gizmo.region_position = current_region_position
+ region_gizmo.region_size = terrain.get_region_size() * terrain.get_vertex_spacing()
+ region_gizmo.grid = terrain.get_data().get_region_locations()
+
+ terrain.update_gizmos()
+ return
+
+ region_gizmo.show_rect = false
+ region_gizmo.region_size = 1024
+ region_gizmo.grid = [Vector2i.ZERO]
+
+
+func _on_scene_changed(scene_root: Node) -> void:
+ if not scene_root:
+ return
+
+ for node in scene_root.find_children("", "Terrain3DObjects"):
+ node.editor_setup(self)
+
+ asset_dock.update_assets()
+ await get_tree().create_timer(2).timeout
+ asset_dock.update_thumbnails()
+
+
+func is_terrain_valid(p_terrain: Terrain3D = null) -> bool:
+ var t: Terrain3D
+ if p_terrain:
+ t = p_terrain
+ else:
+ t = terrain
+ if is_instance_valid(t) and t.is_inside_tree() and t.data:
+ return true
+ return false
+
+
+func is_selected() -> bool:
+ var selected: Array[Node] = EditorInterface.get_selection().get_selected_nodes()
+ for node in selected:
+ if ( is_instance_valid(_last_terrain) and node.get_instance_id() == _last_terrain.get_instance_id() ) or \
+ node is Terrain3D:
+ return true
+ return false
+
+
+func select_terrain() -> void:
+ if is_instance_valid(_last_terrain) and is_terrain_valid(_last_terrain) and not is_selected():
+ var es: EditorSelection = EditorInterface.get_selection()
+ es.clear()
+ es.add_node(_last_terrain)
+
+
+## Editor Settings
+
+
+func setup_editor_settings() -> void:
+ editor_settings = EditorInterface.get_editor_settings()
+ if not editor_settings.has_setting("terrain3d/config/alt_key_bind"):
+ editor_settings.set("terrain3d/config/alt_key_bind", 0)
+ var property_info = {
+ "name": "terrain3d/config/alt_key_bind",
+ "type": TYPE_INT,
+ "hint": PROPERTY_HINT_ENUM,
+ "hint_string": "Alt,Space,Meta,Capslock"
+ }
+ editor_settings.add_property_info(property_info)
+
+
+func set_setting(p_str: String, p_value: Variant) -> void:
+ editor_settings.set_setting(p_str, p_value)
+
+
+func get_setting(p_str: String, p_default: Variant) -> Variant:
+ if editor_settings.has_setting(p_str):
+ return editor_settings.get_setting(p_str)
+ else:
+ return p_default
+
+
+func has_setting(p_str: String) -> bool:
+ return editor_settings.has_setting(p_str)
+
+
+func erase_setting(p_str: String) -> void:
+ editor_settings.erase(p_str)
diff --git a/addons/terrain_3d/src/editor_plugin.gd.uid b/addons/terrain_3d/src/editor_plugin.gd.uid
new file mode 100644
index 0000000..fb3e1ea
--- /dev/null
+++ b/addons/terrain_3d/src/editor_plugin.gd.uid
@@ -0,0 +1 @@
+uid://bsgxo1qywjdf3
diff --git a/addons/terrain_3d/src/gradient_operation_builder.gd b/addons/terrain_3d/src/gradient_operation_builder.gd
new file mode 100644
index 0000000..8bbafd4
--- /dev/null
+++ b/addons/terrain_3d/src/gradient_operation_builder.gd
@@ -0,0 +1,56 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Gradient Operation Builder for Terrain3D
+extends "res://addons/terrain_3d/src/operation_builder.gd"
+
+
+const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd")
+
+
+func _get_point_picker() -> MultiPicker:
+ return tool_settings.settings["gradient_points"]
+
+
+func _get_brush_size() -> float:
+ return tool_settings.get_setting("size")
+
+
+func _is_drawable() -> bool:
+ return tool_settings.get_setting("drawable")
+
+
+func is_picking() -> bool:
+ return not _get_point_picker().all_points_selected()
+
+
+func pick(p_global_position: Vector3, p_terrain: Terrain3D) -> void:
+ if not _get_point_picker().all_points_selected():
+ _get_point_picker().add_point(p_global_position)
+
+
+func is_ready() -> bool:
+ return _get_point_picker().all_points_selected() and not _is_drawable()
+
+
+func apply_operation(p_editor: Terrain3DEditor, p_global_position: Vector3, p_camera_direction: float) -> void:
+ var points: PackedVector3Array = _get_point_picker().get_points()
+ assert(points.size() == 2)
+ assert(not _is_drawable())
+
+ var brush_size: float = _get_brush_size()
+ assert(brush_size > 0.0)
+
+ var start: Vector3 = points[0]
+ var end: Vector3 = points[1]
+
+ p_editor.start_operation(start)
+
+ var dir: Vector3 = (end - start).normalized()
+
+ var pos: Vector3 = start
+ while dir.dot(end - pos) > 0.0:
+ p_editor.operate(pos, p_camera_direction)
+ pos += dir * brush_size * 0.2
+
+ p_editor.stop_operation()
+
+ _get_point_picker().clear()
diff --git a/addons/terrain_3d/src/gradient_operation_builder.gd.uid b/addons/terrain_3d/src/gradient_operation_builder.gd.uid
new file mode 100644
index 0000000..e747d25
--- /dev/null
+++ b/addons/terrain_3d/src/gradient_operation_builder.gd.uid
@@ -0,0 +1 @@
+uid://def7sych6dp8b
diff --git a/addons/terrain_3d/src/multi_picker.gd b/addons/terrain_3d/src/multi_picker.gd
new file mode 100644
index 0000000..717ca7b
--- /dev/null
+++ b/addons/terrain_3d/src/multi_picker.gd
@@ -0,0 +1,88 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Multipicker for Terrain3D
+extends HBoxContainer
+
+
+signal pressed
+signal value_changed
+
+
+const ICON_PICKER_CHECKED: String = "res://addons/terrain_3d/icons/picker_checked.svg"
+const MAX_POINTS: int = 2
+
+
+var icon_picker: Texture2D
+var icon_picker_checked: Texture2D
+var points: PackedVector3Array
+var picking_index: int = -1
+
+
+func _enter_tree() -> void:
+ icon_picker = get_theme_icon("ColorPick", "EditorIcons")
+ icon_picker_checked = load(ICON_PICKER_CHECKED)
+
+ points.resize(MAX_POINTS)
+
+ for i in range(MAX_POINTS):
+ var button := Button.new()
+ button.icon = icon_picker
+ button.tooltip_text = "Pick point on the Terrain"
+ button.set_meta(&"point_index", i)
+ button.pressed.connect(_on_button_pressed.bind(i))
+ add_child(button)
+
+ _update_buttons()
+
+
+func _on_button_pressed(button_index: int) -> void:
+ points[button_index] = Vector3.ZERO
+ picking_index = button_index
+ _update_buttons()
+ pressed.emit()
+
+
+func _update_buttons() -> void:
+ for child in get_children():
+ if child is Button:
+ _update_button(child)
+
+
+func _update_button(button: Button) -> void:
+ var index: int = button.get_meta(&"point_index")
+
+ if points[index] != Vector3.ZERO:
+ button.icon = icon_picker_checked
+ else:
+ button.icon = icon_picker
+
+
+func clear() -> void:
+ points.fill(Vector3.ZERO)
+ _update_buttons()
+ value_changed.emit()
+
+
+func all_points_selected() -> bool:
+ return points.count(Vector3.ZERO) == 0
+
+
+func add_point(p_value: Vector3) -> void:
+ if points.has(p_value):
+ return
+
+ # If manually selecting a point individually
+ if picking_index != -1:
+ points[picking_index] = p_value
+ picking_index = -1
+ else:
+ # Else picking a sequence of points (non-drawable)
+ for i in range(MAX_POINTS):
+ if points[i] == Vector3.ZERO:
+ points[i] = p_value
+ break
+ _update_buttons()
+ value_changed.emit()
+
+
+func get_points() -> PackedVector3Array:
+ return points
diff --git a/addons/terrain_3d/src/multi_picker.gd.uid b/addons/terrain_3d/src/multi_picker.gd.uid
new file mode 100644
index 0000000..6db5cb7
--- /dev/null
+++ b/addons/terrain_3d/src/multi_picker.gd.uid
@@ -0,0 +1 @@
+uid://dvdtoa32h6xdn
diff --git a/addons/terrain_3d/src/operation_builder.gd b/addons/terrain_3d/src/operation_builder.gd
new file mode 100644
index 0000000..2a558be
--- /dev/null
+++ b/addons/terrain_3d/src/operation_builder.gd
@@ -0,0 +1,25 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Operation Builder for Terrain3D
+extends RefCounted
+
+
+const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd")
+
+
+var tool_settings: ToolSettings
+
+
+func is_picking() -> bool:
+ return false
+
+
+func pick(p_global_position: Vector3, p_terrain: Terrain3D) -> void:
+ pass
+
+
+func is_ready() -> bool:
+ return false
+
+
+func apply_operation(editor: Terrain3DEditor, p_global_position: Vector3, p_camera_direction: float) -> void:
+ pass
diff --git a/addons/terrain_3d/src/operation_builder.gd.uid b/addons/terrain_3d/src/operation_builder.gd.uid
new file mode 100644
index 0000000..f14e1fe
--- /dev/null
+++ b/addons/terrain_3d/src/operation_builder.gd.uid
@@ -0,0 +1 @@
+uid://bu5cm0eh052rm
diff --git a/addons/terrain_3d/src/region_gizmo.gd b/addons/terrain_3d/src/region_gizmo.gd
new file mode 100644
index 0000000..c74c8f5
--- /dev/null
+++ b/addons/terrain_3d/src/region_gizmo.gd
@@ -0,0 +1,68 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Editor Region Gizmos for Terrain3D
+extends EditorNode3DGizmo
+
+var material: StandardMaterial3D
+var selection_material: StandardMaterial3D
+var region_position: Vector2
+var region_size: float
+var grid: Array[Vector2i]
+var use_secondary_color: bool = false
+var show_rect: bool = true
+
+var main_color: Color = Color.GREEN_YELLOW
+var secondary_color: Color = Color.RED
+var grid_color: Color = Color.WHITE
+var border_color: Color = Color.BLUE
+
+
+func _init() -> void:
+ material = StandardMaterial3D.new()
+ material.set_flag(BaseMaterial3D.FLAG_DISABLE_DEPTH_TEST, true)
+ material.set_flag(BaseMaterial3D.FLAG_ALBEDO_FROM_VERTEX_COLOR, true)
+ material.set_shading_mode(BaseMaterial3D.SHADING_MODE_UNSHADED)
+ material.set_albedo(Color.WHITE)
+
+ selection_material = material.duplicate()
+ selection_material.set_render_priority(0)
+
+
+func _redraw() -> void:
+ clear()
+
+ var rect_position = region_position * region_size
+
+ if show_rect:
+ var modulate: Color = main_color if !use_secondary_color else secondary_color
+ if abs(region_position.x) > Terrain3DData.REGION_MAP_SIZE*.5 or abs(region_position.y) > Terrain3DData.REGION_MAP_SIZE*.5:
+ modulate = Color.GRAY
+ draw_rect(Vector2(region_size,region_size)*.5 + rect_position, region_size, selection_material, modulate)
+
+ for pos in grid:
+ var grid_tile_position = Vector2(pos) * region_size
+ if show_rect and grid_tile_position == rect_position:
+ # Skip this one, otherwise focused region borders are not always visible due to draw order
+ continue
+
+ draw_rect(Vector2(region_size,region_size)*.5 + grid_tile_position, region_size, material, grid_color)
+
+ draw_rect(Vector2.ZERO, region_size * Terrain3DData.REGION_MAP_SIZE, material, border_color)
+
+
+func draw_rect(p_pos: Vector2, p_size: float, p_material: StandardMaterial3D, p_modulate: Color) -> void:
+ var lines: PackedVector3Array = [
+ Vector3(-1, 0, -1),
+ Vector3(-1, 0, 1),
+ Vector3(1, 0, 1),
+ Vector3(1, 0, -1),
+ Vector3(-1, 0, 1),
+ Vector3(1, 0, 1),
+ Vector3(1, 0, -1),
+ Vector3(-1, 0, -1),
+ ]
+
+ for i in lines.size():
+ lines[i] = ((lines[i] / 2.0) * p_size) + Vector3(p_pos.x, 0, p_pos.y)
+
+ add_lines(lines, p_material, false, p_modulate)
+
diff --git a/addons/terrain_3d/src/region_gizmo.gd.uid b/addons/terrain_3d/src/region_gizmo.gd.uid
new file mode 100644
index 0000000..346a404
--- /dev/null
+++ b/addons/terrain_3d/src/region_gizmo.gd.uid
@@ -0,0 +1 @@
+uid://bh6qwe1ok4cx3
diff --git a/addons/terrain_3d/src/tool_settings.gd b/addons/terrain_3d/src/tool_settings.gd
new file mode 100644
index 0000000..ea2d246
--- /dev/null
+++ b/addons/terrain_3d/src/tool_settings.gd
@@ -0,0 +1,693 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Tool settings bar for Terrain3D
+extends PanelContainer
+
+signal picking(type: Terrain3DEditor.Tool, callback: Callable)
+signal setting_changed(setting: Variant)
+
+enum Layout {
+ HORIZONTAL,
+ VERTICAL,
+ GRID,
+}
+
+enum SettingType {
+ CHECKBOX,
+ COLOR_SELECT,
+ DOUBLE_SLIDER,
+ OPTION,
+ PICKER,
+ MULTI_PICKER,
+ SLIDER,
+ LABEL,
+ TYPE_MAX,
+}
+
+const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd")
+const DEFAULT_BRUSH: String = "circle0.exr"
+const BRUSH_PATH: String = "res://addons/terrain_3d/brushes"
+const ES_TOOL_SETTINGS: String = "terrain3d/tool_settings/"
+
+# Add settings flags
+const NONE: int = 0x0
+const ALLOW_LARGER: int = 0x1
+const ALLOW_SMALLER: int = 0x2
+const ALLOW_OUT_OF_BOUNDS: int = 0x3 # LARGER|SMALLER
+const NO_LABEL: int = 0x4
+const ADD_SEPARATOR: int = 0x8 # Add a vertical line before this entry
+const ADD_SPACER: int = 0x10 # Add a space before this entry
+const NO_SAVE: int = 0x20 # Don't save this in EditorSettings
+
+var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
+var brush_preview_material: ShaderMaterial
+var select_brush_button: Button
+var selected_brush_imgs: Array
+var main_list: HFlowContainer
+var advanced_list: VBoxContainer
+var height_list: VBoxContainer
+var scale_list: VBoxContainer
+var rotation_list: VBoxContainer
+var color_list: VBoxContainer
+var settings: Dictionary = {}
+
+
+func _ready() -> void:
+ # Remove old editor settings
+ for setting in ["lift_floor", "flatten_peaks", "lift_flatten", "automatic_regions"]:
+ plugin.erase_setting(ES_TOOL_SETTINGS + setting)
+
+ # Setup buttons
+ main_list = HFlowContainer.new()
+ add_child(main_list, true)
+
+ add_brushes(main_list)
+
+ add_setting({ "name":"instructions", "label":"Click the terrain to add a region. CTRL+Click to remove. Or select another tool on the left.",
+ "type":SettingType.LABEL, "list":main_list, "flags":NO_LABEL|NO_SAVE })
+
+ add_setting({ "name":"size", "type":SettingType.SLIDER, "list":main_list, "default":20, "unit":"m",
+ "range":Vector3(0.1, 200, 1), "flags":ALLOW_LARGER|ADD_SPACER })
+
+ add_setting({ "name":"strength", "type":SettingType.SLIDER, "list":main_list, "default":33,
+ "unit":"%", "range":Vector3(1, 100, 1), "flags":ALLOW_LARGER })
+
+ add_setting({ "name":"height", "type":SettingType.SLIDER, "list":main_list, "default":20,
+ "unit":"m", "range":Vector3(-500, 500, 0.1), "flags":ALLOW_OUT_OF_BOUNDS })
+ add_setting({ "name":"height_picker", "type":SettingType.PICKER, "list":main_list,
+ "default":Terrain3DEditor.HEIGHT, "flags":NO_LABEL })
+
+ add_setting({ "name":"color", "type":SettingType.COLOR_SELECT, "list":main_list,
+ "default":Color.WHITE, "flags":ADD_SEPARATOR })
+ add_setting({ "name":"color_picker", "type":SettingType.PICKER, "list":main_list,
+ "default":Terrain3DEditor.COLOR, "flags":NO_LABEL })
+
+ add_setting({ "name":"roughness", "type":SettingType.SLIDER, "list":main_list, "default":-65,
+ "unit":"%", "range":Vector3(-100, 100, 1), "flags":ADD_SEPARATOR })
+ add_setting({ "name":"roughness_picker", "type":SettingType.PICKER, "list":main_list,
+ "default":Terrain3DEditor.ROUGHNESS, "flags":NO_LABEL })
+
+ add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX,
+ "list":main_list, "default":true, "flags":ADD_SEPARATOR })
+
+ add_setting({ "name":"texture_filter", "label":"Texture Filter", "type":SettingType.CHECKBOX,
+ "list":main_list, "default":false, "flags":ADD_SEPARATOR })
+
+ add_setting({ "name":"margin", "type":SettingType.SLIDER, "list":main_list, "default":0,
+ "unit":"", "range":Vector3(-50, 50, 1), "flags":ALLOW_OUT_OF_BOUNDS })
+
+ # Slope painting filter
+ add_setting({ "name":"slope", "type":SettingType.DOUBLE_SLIDER, "list":main_list, "default":Vector2(0, 90),
+ "unit":"°", "range":Vector3(0, 90, 1), "flags":ADD_SEPARATOR })
+
+ add_setting({ "name":"enable_angle", "label":"Angle", "type":SettingType.CHECKBOX,
+ "list":main_list, "default":true, "flags":ADD_SEPARATOR })
+ add_setting({ "name":"angle", "type":SettingType.SLIDER, "list":main_list, "default":0,
+ "unit":"%", "range":Vector3(0, 337.5, 22.5), "flags":NO_LABEL })
+ add_setting({ "name":"angle_picker", "type":SettingType.PICKER, "list":main_list,
+ "default":Terrain3DEditor.ANGLE, "flags":NO_LABEL })
+ add_setting({ "name":"dynamic_angle", "label":"Dynamic", "type":SettingType.CHECKBOX,
+ "list":main_list, "default":false, "flags":ADD_SPACER })
+
+ add_setting({ "name":"enable_scale", "label":"Scale", "type":SettingType.CHECKBOX,
+ "list":main_list, "default":true, "flags":ADD_SEPARATOR })
+ add_setting({ "name":"scale", "label":"±", "type":SettingType.SLIDER, "list":main_list, "default":0,
+ "unit":"%", "range":Vector3(-60, 80, 20), "flags":NO_LABEL })
+ add_setting({ "name":"scale_picker", "type":SettingType.PICKER, "list":main_list,
+ "default":Terrain3DEditor.SCALE, "flags":NO_LABEL })
+
+ ## Slope sculpting brush
+ add_setting({ "name":"gradient_points", "type":SettingType.MULTI_PICKER, "label":"Points",
+ "list":main_list, "default":Terrain3DEditor.SCULPT, "flags":ADD_SEPARATOR })
+ add_setting({ "name":"drawable", "type":SettingType.CHECKBOX, "list":main_list, "default":false,
+ "flags":ADD_SEPARATOR })
+ settings["drawable"].toggled.connect(_on_drawable_toggled)
+
+ ## Instancer
+ height_list = create_submenu(main_list, "Height", Layout.VERTICAL)
+ add_setting({ "name":"height_offset", "type":SettingType.SLIDER, "list":height_list, "default":0,
+ "unit":"m", "range":Vector3(-10, 10, 0.05), "flags":ALLOW_OUT_OF_BOUNDS })
+ add_setting({ "name":"random_height", "label":"Random Height ±", "type":SettingType.SLIDER,
+ "list":height_list, "default":0, "unit":"m", "range":Vector3(0, 10, 0.05),
+ "flags":ALLOW_OUT_OF_BOUNDS })
+
+ scale_list = create_submenu(main_list, "Scale", Layout.VERTICAL)
+ add_setting({ "name":"fixed_scale", "type":SettingType.SLIDER, "list":scale_list, "default":100,
+ "unit":"%", "range":Vector3(1, 1000, 1), "flags":ALLOW_OUT_OF_BOUNDS })
+ add_setting({ "name":"random_scale", "label":"Random Scale ±", "type":SettingType.SLIDER, "list":scale_list,
+ "default":20, "unit":"%", "range":Vector3(0, 99, 1), "flags":ALLOW_OUT_OF_BOUNDS })
+
+ rotation_list = create_submenu(main_list, "Rotation", Layout.VERTICAL)
+ add_setting({ "name":"fixed_spin", "label":"Fixed Spin (Around Y)", "type":SettingType.SLIDER, "list":rotation_list,
+ "default":0, "unit":"°", "range":Vector3(0, 360, 1) })
+ add_setting({ "name":"random_spin", "type":SettingType.SLIDER, "list":rotation_list, "default":360,
+ "unit":"°", "range":Vector3(0, 360, 1) })
+ add_setting({ "name":"fixed_tilt", "label":"Fixed Tilt", "type":SettingType.SLIDER, "list":rotation_list,
+ "default":0, "unit":"°", "range":Vector3(-85, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS })
+ add_setting({ "name":"random_tilt", "label":"Random Tilt ±", "type":SettingType.SLIDER, "list":rotation_list,
+ "default":10, "unit":"°", "range":Vector3(0, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS })
+ add_setting({ "name":"align_to_normal", "type":SettingType.CHECKBOX, "list":rotation_list, "default":false })
+
+ color_list = create_submenu(main_list, "Color", Layout.VERTICAL)
+ add_setting({ "name":"vertex_color", "type":SettingType.COLOR_SELECT, "list":color_list,
+ "default":Color.WHITE })
+ add_setting({ "name":"random_hue", "label":"Random Hue Shift ±", "type":SettingType.SLIDER,
+ "list":color_list, "default":0, "unit":"°", "range":Vector3(0, 360, 1) })
+ add_setting({ "name":"random_darken", "type":SettingType.SLIDER, "list":color_list, "default":50,
+ "unit":"%", "range":Vector3(0, 100, 1) })
+ #add_setting({ "name":"blend_mode", "type":SettingType.OPTION, "list":color_list, "default":0,
+ #"range":Vector3(0, 3, 1) })
+
+ if DisplayServer.is_touchscreen_available():
+ add_setting({ "name":"invert", "label":"Invert", "type":SettingType.CHECKBOX, "list":main_list, "default":false, "flags":ADD_SEPARATOR })
+
+ var spacer: Control = Control.new()
+ spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ main_list.add_child(spacer, true)
+
+ ## Advanced Settings Menu
+ advanced_list = create_submenu(main_list, "", Layout.VERTICAL, false)
+ add_setting({ "name":"auto_regions", "label":"Add regions while sculpting", "type":SettingType.CHECKBOX,
+ "list":advanced_list, "default":true })
+ add_setting({ "name":"align_to_view", "type":SettingType.CHECKBOX, "list":advanced_list,
+ "default":true })
+ add_setting({ "name":"show_cursor_while_painting", "type":SettingType.CHECKBOX, "list":advanced_list,
+ "default":true })
+ advanced_list.add_child(HSeparator.new(), true)
+ add_setting({ "name":"gamma", "type":SettingType.SLIDER, "list":advanced_list, "default":1.0,
+ "unit":"γ", "range":Vector3(0.1, 2.0, 0.01) })
+ add_setting({ "name":"jitter", "type":SettingType.SLIDER, "list":advanced_list, "default":50,
+ "unit":"%", "range":Vector3(0, 100, 1) })
+ add_setting({ "name":"crosshair_threshold", "type":SettingType.SLIDER, "list":advanced_list, "default":16.,
+ "unit":"m", "range":Vector3(0, 200, 1) })
+
+
+func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout, p_hover_pop: bool = true) -> Container:
+ var menu_button: Button = Button.new()
+ if p_button_name.is_empty():
+ menu_button.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons")
+ else:
+ menu_button.set_text(p_button_name)
+ menu_button.set_toggle_mode(true)
+ menu_button.set_v_size_flags(SIZE_SHRINK_CENTER)
+ menu_button.toggled.connect(_on_show_submenu.bind(menu_button))
+
+ var submenu: PopupPanel = PopupPanel.new()
+ submenu.popup_hide.connect(menu_button.set_pressed.bind(false))
+ var panel_style: StyleBox = get_theme_stylebox("panel", "PopupMenu").duplicate()
+ panel_style.set_content_margin_all(10)
+ submenu.set("theme_override_styles/panel", panel_style)
+ submenu.add_to_group("terrain3d_submenus")
+
+ # Pop up menu on hover, hide on exit
+ if p_hover_pop:
+ menu_button.mouse_entered.connect(_on_show_submenu.bind(true, menu_button))
+
+ submenu.mouse_entered.connect(func(): submenu.set_meta("mouse_entered", true))
+
+ submenu.mouse_exited.connect(func():
+ # On mouse_exit, hide popup unless LineEdit focused
+ var focused_element: Control = submenu.gui_get_focus_owner()
+ if not focused_element is LineEdit:
+ _on_show_submenu(false, menu_button)
+ submenu.set_meta("mouse_entered", false)
+ return
+
+ focused_element.focus_exited.connect(func():
+ # Close submenu once lineedit loses focus
+ if not submenu.get_meta("mouse_entered"):
+ _on_show_submenu(false, menu_button)
+ submenu.set_meta("mouse_entered", false)
+ )
+ )
+
+ var sublist: Container
+ match(p_layout):
+ Layout.GRID:
+ sublist = GridContainer.new()
+ Layout.VERTICAL:
+ sublist = VBoxContainer.new()
+ Layout.HORIZONTAL, _:
+ sublist = HBoxContainer.new()
+
+ p_parent.add_child(menu_button, true)
+ menu_button.add_child(submenu, true)
+ submenu.add_child(sublist, true)
+
+ return sublist
+
+
+func _on_show_submenu(p_toggled: bool, p_button: Button) -> void:
+ # Don't show if mouse already down (from painting)
+ if p_toggled and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
+ return
+
+ # Hide menu if mouse is not in button or panel
+ var button_rect: Rect2 = Rect2(p_button.get_screen_transform().origin, p_button.get_global_rect().size)
+ var in_button: bool = button_rect.has_point(DisplayServer.mouse_get_position())
+ var popup: PopupPanel = p_button.get_child(0)
+ var popup_rect: Rect2 = Rect2(popup.position, popup.size)
+ var in_popup: bool = popup_rect.has_point(DisplayServer.mouse_get_position())
+ if not p_toggled and ( in_button or in_popup ):
+ return
+
+ # Hide all submenus before possibly enabling the current one
+ get_tree().call_group("terrain3d_submenus", "set_visible", false)
+ popup.set_visible(p_toggled)
+ var popup_pos: Vector2 = p_button.get_screen_transform().origin
+ popup_pos.y -= popup.size.y
+ if popup.get_child_count()>0 and popup.get_child(0) == advanced_list:
+ popup_pos.x -= popup.size.x - p_button.size.x
+ popup.set_position(popup_pos)
+
+
+func add_brushes(p_parent: Control) -> void:
+ var brush_list: GridContainer = create_submenu(p_parent, "Brush", Layout.GRID)
+ brush_list.name = "BrushList"
+
+ var brush_button_group: ButtonGroup = ButtonGroup.new()
+ brush_button_group.pressed.connect(_on_setting_changed)
+ var default_brush_btn: Button
+
+ var dir: DirAccess = DirAccess.open(BRUSH_PATH)
+ if dir:
+ dir.list_dir_begin()
+ var file_name = dir.get_next()
+ while file_name != "":
+ if !dir.current_is_dir() and file_name.ends_with(".exr"):
+ var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
+ var thumbimg: Image = img.duplicate()
+ img.convert(Image.FORMAT_RF)
+
+ if thumbimg.get_width() != 100 and thumbimg.get_height() != 100:
+ thumbimg.resize(100, 100, Image.INTERPOLATE_CUBIC)
+ thumbimg = Terrain3DUtil.black_to_alpha(thumbimg)
+ thumbimg.convert(Image.FORMAT_LA8)
+ var thumbtex: ImageTexture = ImageTexture.create_from_image(thumbimg)
+
+ var brush_btn: Button = Button.new()
+ brush_btn.set_custom_minimum_size(Vector2.ONE * 100)
+ brush_btn.set_button_icon(thumbtex)
+ brush_btn.set_meta("image", img)
+ brush_btn.set_expand_icon(true)
+ brush_btn.set_material(_get_brush_preview_material())
+ brush_btn.set_toggle_mode(true)
+ brush_btn.set_button_group(brush_button_group)
+ brush_btn.mouse_entered.connect(_on_brush_hover.bind(true, brush_btn))
+ brush_btn.mouse_exited.connect(_on_brush_hover.bind(false, brush_btn))
+ brush_list.add_child(brush_btn, true)
+ if file_name == DEFAULT_BRUSH:
+ default_brush_btn = brush_btn
+
+ var lbl: Label = Label.new()
+ brush_btn.name = file_name.get_basename().to_pascal_case()
+ brush_btn.add_child(lbl, true)
+ lbl.text = brush_btn.name
+ lbl.visible = false
+ lbl.position.y = 70
+ lbl.add_theme_color_override("font_shadow_color", Color.BLACK)
+ lbl.add_theme_constant_override("shadow_offset_x", 1)
+ lbl.add_theme_constant_override("shadow_offset_y", 1)
+ lbl.add_theme_font_size_override("font_size", 16)
+
+ file_name = dir.get_next()
+
+ brush_list.columns = sqrt(brush_list.get_child_count()) + 2
+
+ if not default_brush_btn:
+ default_brush_btn = brush_button_group.get_buttons()[0]
+ default_brush_btn.set_pressed(true)
+ _generate_brush_texture(default_brush_btn)
+
+ settings["brush"] = brush_button_group
+
+ select_brush_button = brush_list.get_parent().get_parent()
+ # Optionally erase the main brush button text and replace it with the texture
+ select_brush_button.set_text("")
+ select_brush_button.set_button_icon(default_brush_btn.get_button_icon())
+ select_brush_button.set_custom_minimum_size(Vector2.ONE * 36)
+ select_brush_button.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER)
+ select_brush_button.set_expand_icon(true)
+
+
+func _on_brush_hover(p_hovering: bool, p_button: Button) -> void:
+ if p_button.get_child_count() > 0:
+ var child = p_button.get_child(0)
+ if child is Label:
+ if p_hovering:
+ child.visible = true
+ else:
+ child.visible = false
+
+
+func _on_pick(p_type: Terrain3DEditor.Tool) -> void:
+ emit_signal("picking", p_type, _on_picked)
+
+
+func _on_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3) -> void:
+ match p_type:
+ Terrain3DEditor.HEIGHT:
+ settings["height"].value = p_color.r if not is_nan(p_color.r) else 0.
+ Terrain3DEditor.COLOR:
+ settings["color"].color = p_color if not is_nan(p_color.r) else Color.WHITE
+ Terrain3DEditor.ROUGHNESS:
+ # This converts 0,1 to -100,100
+ # It also quantizes explicitly so picked values matches painted values
+ settings["roughness"].value = round(200. * float(int(p_color.a * 255.) / 255. - .5)) if not is_nan(p_color.r) else 0.
+ Terrain3DEditor.ANGLE:
+ settings["angle"].value = p_color.r
+ Terrain3DEditor.SCALE:
+ settings["scale"].value = p_color.r
+ _on_setting_changed()
+
+
+func _on_point_pick(p_type: Terrain3DEditor.Tool, p_name: String) -> void:
+ assert(p_type == Terrain3DEditor.SCULPT)
+ emit_signal("picking", p_type, _on_point_picked.bind(p_name))
+
+
+func _on_point_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3, p_name: String) -> void:
+ assert(p_type == Terrain3DEditor.SCULPT)
+ var point: Vector3 = p_global_position
+ point.y = p_color.r
+ settings[p_name].add_point(point)
+ _on_setting_changed()
+
+
+func add_setting(p_args: Dictionary) -> void:
+ var p_name: StringName = p_args.get("name", "")
+ var p_label: String = p_args.get("label", "") # Optional replacement for name
+ var p_type: SettingType = p_args.get("type", SettingType.TYPE_MAX)
+ var p_list: Control = p_args.get("list")
+ var p_default: Variant = p_args.get("default")
+ var p_suffix: String = p_args.get("unit", "")
+ var p_range: Vector3 = p_args.get("range", Vector3(0, 0, 1))
+ var p_minimum: float = p_range.x
+ var p_maximum: float = p_range.y
+ var p_step: float = p_range.z
+ var p_flags: int = p_args.get("flags", NONE)
+
+ if p_name.is_empty() or p_type == SettingType.TYPE_MAX:
+ return
+
+ var container: HBoxContainer = HBoxContainer.new()
+ container.set_v_size_flags(SIZE_EXPAND_FILL)
+ var control: Control # Houses the setting to be saved
+ var pending_children: Array[Control]
+
+ match p_type:
+ SettingType.LABEL:
+ var label := Label.new()
+ label.set_text(p_label)
+ pending_children.push_back(label)
+ control = label
+
+ SettingType.CHECKBOX:
+ var checkbox := CheckBox.new()
+ if !(p_flags & NO_SAVE):
+ checkbox.set_pressed_no_signal(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
+ checkbox.toggled.connect( (
+ func(value, path):
+ plugin.set_setting(path, value)
+ ).bind(ES_TOOL_SETTINGS + p_name) )
+ else:
+ checkbox.set_pressed_no_signal(p_default)
+ checkbox.pressed.connect(_on_setting_changed)
+ pending_children.push_back(checkbox)
+ control = checkbox
+
+ SettingType.COLOR_SELECT:
+ var picker := ColorPickerButton.new()
+ picker.set_custom_minimum_size(Vector2(100, 25))
+ picker.edit_alpha = false
+ picker.get_picker().set_color_mode(ColorPicker.MODE_HSV)
+ if !(p_flags & NO_SAVE):
+ picker.set_pick_color(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
+ picker.color_changed.connect( (
+ func(value, path):
+ plugin.set_setting(path, value)
+ ).bind(ES_TOOL_SETTINGS + p_name) )
+ else:
+ picker.set_pick_color(p_default)
+ picker.color_changed.connect(_on_setting_changed)
+ pending_children.push_back(picker)
+ control = picker
+
+ SettingType.PICKER:
+ var button := Button.new()
+ button.set_v_size_flags(SIZE_SHRINK_CENTER)
+ button.icon = get_theme_icon("ColorPick", "EditorIcons")
+ button.tooltip_text = "Pick value from the Terrain"
+ button.pressed.connect(_on_pick.bind(p_default))
+ pending_children.push_back(button)
+ control = button
+
+ SettingType.MULTI_PICKER:
+ var multi_picker: HBoxContainer = MultiPicker.new()
+ multi_picker.pressed.connect(_on_point_pick.bind(p_default, p_name))
+ multi_picker.value_changed.connect(_on_setting_changed)
+ pending_children.push_back(multi_picker)
+ control = multi_picker
+
+ SettingType.OPTION:
+ var option := OptionButton.new()
+ for i in int(p_maximum):
+ option.add_item("a", i)
+ option.selected = p_minimum
+ option.item_selected.connect(_on_setting_changed)
+ pending_children.push_back(option)
+ control = option
+
+ SettingType.SLIDER, SettingType.DOUBLE_SLIDER:
+ var slider: Control
+ if p_type == SettingType.SLIDER:
+ # Create an editable value box
+ var spin_slider := EditorSpinSlider.new()
+ spin_slider.set_flat(false)
+ spin_slider.set_hide_slider(true)
+ spin_slider.value_changed.connect(_on_setting_changed)
+ spin_slider.set_max(p_maximum)
+ spin_slider.set_min(p_minimum)
+ spin_slider.set_step(p_step)
+ spin_slider.set_suffix(p_suffix)
+ spin_slider.set_v_size_flags(SIZE_SHRINK_CENTER)
+ spin_slider.set_custom_minimum_size(Vector2(65, 0))
+
+ # Create horizontal slider linked to the above box
+ slider = HSlider.new()
+ slider.share(spin_slider)
+ if p_flags & ALLOW_LARGER:
+ slider.set_allow_greater(true)
+ if p_flags & ALLOW_SMALLER:
+ slider.set_allow_lesser(true)
+
+ pending_children.push_back(slider)
+ pending_children.push_back(spin_slider)
+ control = spin_slider
+
+ else: # DOUBLE_SLIDER
+ var label := Label.new()
+ label.set_custom_minimum_size(Vector2(60, 0))
+ label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER)
+ slider = DoubleSlider.new()
+ slider.label = label
+ slider.suffix = p_suffix
+ slider.value_changed.connect(_on_setting_changed)
+ pending_children.push_back(slider)
+ pending_children.push_back(label)
+ control = slider
+
+ slider.set_min(p_minimum)
+ slider.set_max(p_maximum)
+ slider.set_step(p_step)
+ slider.set_value(p_default)
+ slider.set_v_size_flags(SIZE_SHRINK_CENTER)
+ slider.set_custom_minimum_size(Vector2(50, 10))
+
+ if !(p_flags & NO_SAVE):
+ slider.set_value(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
+ slider.value_changed.connect( (
+ func(value, path):
+ plugin.set_setting(path, value)
+ ).bind(ES_TOOL_SETTINGS + p_name) )
+ else:
+ slider.set_value(p_default)
+
+ control.name = p_name.to_pascal_case()
+ settings[p_name] = control
+
+ # Setup button labels
+ if not (p_flags & NO_LABEL):
+ # Labels are actually buttons styled to look like labels
+ var label := Button.new()
+ label.set("theme_override_styles/normal", get_theme_stylebox("normal", "Label"))
+ label.set("theme_override_styles/hover", get_theme_stylebox("normal", "Label"))
+ label.set("theme_override_styles/pressed", get_theme_stylebox("normal", "Label"))
+ label.set("theme_override_styles/focus", get_theme_stylebox("normal", "Label"))
+ label.pressed.connect(_on_label_pressed.bind(p_name, p_default))
+ if p_label.is_empty():
+ label.set_text(p_name.capitalize() + ": ")
+ else:
+ label.set_text(p_label.capitalize() + ": ")
+ pending_children.push_front(label)
+
+ # Add separators to front
+ if p_flags & ADD_SEPARATOR:
+ pending_children.push_front(VSeparator.new())
+ if p_flags & ADD_SPACER:
+ var spacer := Control.new()
+ spacer.set_custom_minimum_size(Vector2(5, 0))
+ pending_children.push_front(spacer)
+
+ # Add all children to container and list
+ for child in pending_children:
+ container.add_child(child, true)
+ p_list.add_child(container, true)
+
+
+# If label button is pressed, reset value to default or toggle checkbox
+func _on_label_pressed(p_name: String, p_default: Variant) -> void:
+ var control: Control = settings.get(p_name)
+ if not control:
+ return
+ if control is CheckBox:
+ set_setting(p_name, !control.button_pressed)
+ elif p_default != null:
+ set_setting(p_name, p_default)
+
+
+func get_settings() -> Dictionary:
+ var dict: Dictionary
+ for key in settings.keys():
+ dict[key] = get_setting(key)
+ return dict
+
+
+func get_setting(p_setting: String) -> Variant:
+ var object: Object = settings.get(p_setting)
+ var value: Variant
+ if object is Range:
+ value = object.get_value()
+ # Adjust widths of all sliders on update of values
+ var digits: float = count_digits(value)
+ var width: float = clamp( (1 + count_digits(value)) * 19., 50, 80) * clamp(EditorInterface.get_editor_scale(), .9, 2)
+ object.set_custom_minimum_size(Vector2(width, 0))
+ elif object is DoubleSlider:
+ value = object.get_value()
+ elif object is ButtonGroup: # "brush"
+ value = selected_brush_imgs
+ elif object is CheckBox:
+ value = object.is_pressed()
+ elif object is ColorPickerButton:
+ value = object.color
+ elif object is MultiPicker:
+ value = object.get_points()
+ if value == null:
+ value = 0
+ return value
+
+
+func set_setting(p_setting: String, p_value: Variant) -> void:
+ var object: Object = settings.get(p_setting)
+ if object is DoubleSlider: # Expects p_value is Vector2
+ object.set_value(p_value)
+ elif object is Range:
+ object.set_value(p_value)
+ elif object is ButtonGroup: # Expects p_value is Array [ "button name", boolean ]
+ if p_value is Array and p_value.size() == 2:
+ for button in object.get_buttons():
+ if button.name == p_value[0]:
+ button.button_pressed = p_value[1]
+ elif object is CheckBox:
+ object.button_pressed = p_value
+ elif object is ColorPickerButton:
+ object.color = p_value
+ plugin.set_setting(ES_TOOL_SETTINGS + p_setting, p_value) # Signal doesn't fire on CPB
+ elif object is MultiPicker: # Expects p_value is PackedVector3Array
+ object.points = p_value
+ _on_setting_changed(object)
+
+
+func show_settings(p_settings: PackedStringArray) -> void:
+ for setting in settings.keys():
+ var object: Object = settings[setting]
+ if object is Control:
+ if setting in p_settings:
+ object.get_parent().show()
+ else:
+ object.get_parent().hide()
+ if select_brush_button:
+ if not "brush" in p_settings:
+ select_brush_button.hide()
+ else:
+ select_brush_button.show()
+
+
+func _on_setting_changed(p_setting: Variant = null) -> void:
+ # If a brush was selected
+ if p_setting is Button and p_setting.get_parent().name == "BrushList":
+ _generate_brush_texture(p_setting)
+ # Optionally Set selected brush texture in main brush button
+ if select_brush_button:
+ select_brush_button.set_button_icon(p_setting.get_button_icon())
+ # Hide popup
+ p_setting.get_parent().get_parent().set_visible(false)
+ # Hide label
+ if p_setting.get_child_count() > 0:
+ p_setting.get_child(0).visible = false
+ emit_signal("setting_changed", p_setting)
+
+
+func _generate_brush_texture(p_btn: Button) -> void:
+ if p_btn is Button:
+ var img: Image = p_btn.get_meta("image")
+ if img.get_width() < 1024 and img.get_height() < 1024:
+ img = img.duplicate()
+ img.resize(1024, 1024, Image.INTERPOLATE_CUBIC)
+ var tex: ImageTexture = ImageTexture.create_from_image(img)
+ selected_brush_imgs = [ img, tex ]
+
+
+func _on_drawable_toggled(p_button_pressed: bool) -> void:
+ if not p_button_pressed:
+ settings["gradient_points"].clear()
+
+
+func _get_brush_preview_material() -> ShaderMaterial:
+ if !brush_preview_material:
+ brush_preview_material = ShaderMaterial.new()
+ var shader: Shader = Shader.new()
+ var code: String = "shader_type canvas_item;\n"
+ code += "varying vec4 v_vertex_color;\n"
+ code += "void vertex() {\n"
+ code += " v_vertex_color = COLOR;\n"
+ code += "}\n"
+ code += "void fragment(){\n"
+ code += " vec4 tex = texture(TEXTURE, UV);\n"
+ code += " COLOR.a *= pow(tex.r, 0.666);\n"
+ code += " COLOR.rgb = v_vertex_color.rgb;\n"
+ code += "}\n"
+ shader.set_code(code)
+ brush_preview_material.set_shader(shader)
+ return brush_preview_material
+
+
+# Counts digits of a number including negative sign, decimal points, and up to 3 decimals
+func count_digits(p_value: float) -> int:
+ var count: int = 1
+ for i in range(5, 0, -1):
+ if abs(p_value) >= pow(10, i):
+ count = i+1
+ break
+ if p_value - floor(p_value) >= .1:
+ count += 1 # For the decimal
+ if p_value*10 - floor(p_value*10.) >= .1:
+ count += 1
+ if p_value*100 - floor(p_value*100.) >= .1:
+ count += 1
+ if p_value*1000 - floor(p_value*1000.) >= .1:
+ count += 1
+ # Negative sign
+ if p_value < 0:
+ count += 1
+ return count
+
diff --git a/addons/terrain_3d/src/tool_settings.gd.uid b/addons/terrain_3d/src/tool_settings.gd.uid
new file mode 100644
index 0000000..b6c65fb
--- /dev/null
+++ b/addons/terrain_3d/src/tool_settings.gd.uid
@@ -0,0 +1 @@
+uid://ciskaaennrffu
diff --git a/addons/terrain_3d/src/toolbar.gd b/addons/terrain_3d/src/toolbar.gd
new file mode 100644
index 0000000..fc68b86
--- /dev/null
+++ b/addons/terrain_3d/src/toolbar.gd
@@ -0,0 +1,154 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Toolbar for Terrain3D
+extends VFlowContainer
+
+signal tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation)
+
+const ICON_REGION_ADD: String = "res://addons/terrain_3d/icons/region_add.svg"
+const ICON_REGION_REMOVE: String = "res://addons/terrain_3d/icons/region_remove.svg"
+const ICON_HEIGHT_ADD: String = "res://addons/terrain_3d/icons/height_add.svg"
+const ICON_HEIGHT_SUB: String = "res://addons/terrain_3d/icons/height_sub.svg"
+const ICON_HEIGHT_FLAT: String = "res://addons/terrain_3d/icons/height_flat.svg"
+const ICON_HEIGHT_SLOPE: String = "res://addons/terrain_3d/icons/height_slope.svg"
+const ICON_HEIGHT_SMOOTH: String = "res://addons/terrain_3d/icons/height_smooth.svg"
+const ICON_PAINT_TEXTURE: String = "res://addons/terrain_3d/icons/texture_paint.svg"
+const ICON_SPRAY_TEXTURE: String = "res://addons/terrain_3d/icons/texture_spray.svg"
+const ICON_COLOR: String = "res://addons/terrain_3d/icons/color_paint.svg"
+const ICON_WETNESS: String = "res://addons/terrain_3d/icons/wetness.svg"
+const ICON_AUTOSHADER: String = "res://addons/terrain_3d/icons/autoshader.svg"
+const ICON_HOLES: String = "res://addons/terrain_3d/icons/holes.svg"
+const ICON_NAVIGATION: String = "res://addons/terrain_3d/icons/navigation.svg"
+const ICON_INSTANCER: String = "res://addons/terrain_3d/icons/multimesh.svg"
+
+var add_tool_group: ButtonGroup = ButtonGroup.new()
+var sub_tool_group: ButtonGroup = ButtonGroup.new()
+var buttons: Dictionary
+
+
+func _init() -> void:
+ set_custom_minimum_size(Vector2(20, 0))
+
+
+func _ready() -> void:
+ add_tool_group.pressed.connect(_on_tool_selected)
+ sub_tool_group.pressed.connect(_on_tool_selected)
+
+ add_tool_button({ "tool":Terrain3DEditor.REGION,
+ "add_text":"Add Region (E)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_REGION_ADD,
+ "sub_text":"Remove Region", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_REGION_REMOVE })
+
+ add_child(HSeparator.new())
+
+ add_tool_button({ "tool":Terrain3DEditor.SCULPT,
+ "add_text":"Raise (R)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_ADD,
+ "sub_text":"Lower (R)", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_SUB })
+
+ add_tool_button({ "tool":Terrain3DEditor.SCULPT,
+ "add_text":"Smooth (Shift)", "add_op":Terrain3DEditor.AVERAGE, "add_icon":ICON_HEIGHT_SMOOTH })
+
+ add_tool_button({ "tool":Terrain3DEditor.HEIGHT,
+ "add_text":"Height (H)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_FLAT,
+ "sub_text":"Height (H)", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_FLAT })
+
+ add_tool_button({ "tool":Terrain3DEditor.SCULPT,
+ "add_text":"Slope (S)", "add_op":Terrain3DEditor.GRADIENT, "add_icon":ICON_HEIGHT_SLOPE })
+
+ add_child(HSeparator.new())
+
+ add_tool_button({ "tool":Terrain3DEditor.TEXTURE,
+ "add_text":"Paint Texture (B)", "add_op":Terrain3DEditor.REPLACE, "add_icon":ICON_PAINT_TEXTURE })
+
+ add_tool_button({ "tool":Terrain3DEditor.TEXTURE,
+ "add_text":"Spray Texture (V)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_SPRAY_TEXTURE })
+
+ add_tool_button({ "tool":Terrain3DEditor.AUTOSHADER,
+ "add_text":"Paint Autoshader (A)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_AUTOSHADER,
+ "sub_text":"Disable Autoshader (A)", "sub_op":Terrain3DEditor.SUBTRACT })
+
+ add_child(HSeparator.new())
+
+ add_tool_button({ "tool":Terrain3DEditor.COLOR,
+ "add_text":"Paint Color (C)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_COLOR,
+ "sub_text":"Remove Color (C)", "sub_op":Terrain3DEditor.SUBTRACT })
+
+ add_tool_button({ "tool":Terrain3DEditor.ROUGHNESS,
+ "add_text":"Paint Wetness (W)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_WETNESS,
+ "sub_text":"Remove Wetness (W)", "sub_op":Terrain3DEditor.SUBTRACT })
+
+ add_child(HSeparator.new())
+
+ add_tool_button({ "tool":Terrain3DEditor.HOLES,
+ "add_text":"Add Holes (X)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HOLES,
+ "sub_text":"Remove Holes (X)", "sub_op":Terrain3DEditor.SUBTRACT })
+
+ add_tool_button({ "tool":Terrain3DEditor.NAVIGATION,
+ "add_text":"Paint Navigable Area (N)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_NAVIGATION,
+ "sub_text":"Remove Navigable Area (N)", "sub_op":Terrain3DEditor.SUBTRACT })
+
+ add_tool_button({ "tool":Terrain3DEditor.INSTANCER,
+ "add_text":"Instance Meshes (I)", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_INSTANCER,
+ "sub_text":"Remove Meshes (I)", "sub_op":Terrain3DEditor.SUBTRACT })
+
+ # Select first button
+ var buttons: Array[BaseButton] = add_tool_group.get_buttons()
+ buttons[0].set_pressed(true)
+ show_add_buttons(true)
+
+
+func add_tool_button(p_params: Dictionary) -> void:
+ # Additive button
+ var button := Button.new()
+ var name_str: String = p_params.get("add_text", "blank").get_slice('(', 0).to_pascal_case()
+ button.set_name(name_str)
+ button.set_meta("Tool", p_params.get("tool", 0))
+ button.set_meta("Operation", p_params.get("add_op", 0))
+ button.set_meta("ID", add_tool_group.get_buttons().size() + 1)
+ button.set_tooltip_text(p_params.get("add_text", "blank"))
+ button.set_button_icon(load(p_params.get("add_icon")))
+ button.set_flat(true)
+ button.set_toggle_mode(true)
+ button.set_h_size_flags(SIZE_SHRINK_END)
+ button.set_button_group(p_params.get("group", add_tool_group))
+ add_child(button, true)
+ buttons[button.get_name()] = button
+
+ # Subtractive button
+ var button2: Button
+ if p_params.has("sub_text"):
+ button2 = Button.new()
+ name_str = p_params.get("sub_text", "blank").get_slice('(', 0).to_pascal_case()
+ button2.set_name(name_str)
+ button2.set_meta("Tool", p_params.get("tool", 0))
+ button2.set_meta("Operation", p_params.get("sub_op", 0))
+ button2.set_meta("ID", button.get_meta("ID"))
+ button2.set_tooltip_text(p_params.get("sub_text", "blank"))
+ button2.set_button_icon(load(p_params.get("sub_icon", p_params.get("add_icon"))))
+ button2.set_flat(true)
+ button2.set_toggle_mode(true)
+ button2.set_h_size_flags(SIZE_SHRINK_END)
+ else:
+ button2 = button.duplicate()
+ button2.set_button_group(p_params.get("group", sub_tool_group))
+ add_child(button2, true)
+ buttons[button2.get_name()] = button
+
+
+func get_button(p_name: String) -> Button:
+ return buttons.get(p_name, null)
+
+
+func show_add_buttons(p_enable: bool) -> void:
+ for button in add_tool_group.get_buttons():
+ button.visible = p_enable
+ for button in sub_tool_group.get_buttons():
+ button.visible = !p_enable
+
+
+func _on_tool_selected(p_button: BaseButton) -> void:
+ # Select same tool on negative bar
+ var group: ButtonGroup = p_button.get_button_group()
+ var change_group: ButtonGroup = add_tool_group if group == sub_tool_group else sub_tool_group
+ var id: int = p_button.get_meta("ID", -2)
+ for button in change_group.get_buttons():
+ button.set_pressed_no_signal(button.get_meta("ID", -1) == id)
+ emit_signal("tool_changed", p_button.get_meta("Tool", Terrain3DEditor.TOOL_MAX), p_button.get_meta("Operation", Terrain3DEditor.OP_MAX))
diff --git a/addons/terrain_3d/src/toolbar.gd.uid b/addons/terrain_3d/src/toolbar.gd.uid
new file mode 100644
index 0000000..6ba79d5
--- /dev/null
+++ b/addons/terrain_3d/src/toolbar.gd.uid
@@ -0,0 +1 @@
+uid://b1j37u6utjbom
diff --git a/addons/terrain_3d/src/ui.gd b/addons/terrain_3d/src/ui.gd
new file mode 100644
index 0000000..7c4d740
--- /dev/null
+++ b/addons/terrain_3d/src/ui.gd
@@ -0,0 +1,576 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# UI for Terrain3D
+extends Node
+
+
+# Includes
+const TerrainMenu: Script = preload("res://addons/terrain_3d/menu/terrain_menu.gd")
+const Toolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd")
+const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd")
+const OperationBuilder: Script = preload("res://addons/terrain_3d/src/operation_builder.gd")
+const GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd")
+const COLOR_RAISE := Color.WHITE
+const COLOR_LOWER := Color.BLACK
+const COLOR_SMOOTH := Color(0.5, 0, .2)
+const COLOR_LIFT := Color.ORANGE
+const COLOR_FLATTEN := Color.BLUE_VIOLET
+const COLOR_HEIGHT := Color(0., 0.32, .4)
+const COLOR_SLOPE := Color.YELLOW
+const COLOR_PAINT := Color.DARK_GREEN
+const COLOR_SPRAY := Color.PALE_GREEN
+const COLOR_ROUGHNESS := Color.ROYAL_BLUE
+const COLOR_AUTOSHADER := Color.DODGER_BLUE
+const COLOR_HOLES := Color.BLACK
+const COLOR_NAVIGATION := Color(.28, .0, .25)
+const COLOR_INSTANCER := Color.CRIMSON
+const COLOR_PICK_COLOR := Color.WHITE
+const COLOR_PICK_HEIGHT := Color.DARK_RED
+const COLOR_PICK_ROUGH := Color.ROYAL_BLUE
+
+const OP_NONE: int = 0x0
+const OP_POSITIVE_ONLY: int = 0x01
+const OP_NEGATIVE_ONLY: int = 0x02
+
+const RING1: String = "res://addons/terrain_3d/brushes/ring1.exr"
+var ring_texture : ImageTexture
+@onready var region_texture := ImageTexture.new() :
+ set(value):
+ var image: Image = Image.create_empty(1, 1, false, Image.FORMAT_R8)
+ image.fill(Color.WHITE)
+ value.create_from_image(image)
+ region_texture = value
+var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
+var toolbar: Toolbar
+var tool_settings: ToolSettings
+var terrain_menu: TerrainMenu
+var setting_has_changed: bool = false
+var visible: bool = false
+var picking: int = Terrain3DEditor.TOOL_MAX
+var picking_callback: Callable
+var brush_data: Dictionary
+var operation_builder: OperationBuilder
+var active_tool: Terrain3DEditor.Tool
+var _selected_tool: Terrain3DEditor.Tool
+var active_operation: Terrain3DEditor.Operation
+var _selected_operation: Terrain3DEditor.Operation
+var inverted_input: bool = false
+
+# Editor decals, indices; 0 = main brush, 1 = slope point A, 2 = slope point B
+var mat_rid: RID
+var editor_decal_position: Array[Vector2] = [Vector2(), Vector2(), Vector2()]
+var editor_decal_rotation: Array[float] = [float(), float(), float()]
+var editor_decal_size: Array[float] = [float(), float(), float()]
+var editor_decal_color: Array[Color] = [Color(), Color(), Color()]
+var editor_decal_visible: Array[bool] = [bool(), bool(), bool()]
+var editor_brush_texture_rid: RID = RID()
+var editor_decal_timer: Timer
+var editor_decal_fade: float :
+ set(value):
+ editor_decal_fade = value
+ if editor_decal_color.size() > 0:
+ editor_decal_color[0].a = value
+ if is_shader_valid():
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
+ if value < 0.001:
+ var r_map: PackedInt32Array = plugin.terrain.data.get_region_map()
+ RenderingServer.material_set_param(mat_rid, "_region_map", r_map)
+var editor_ring_texture_rid: RID
+
+
+func _enter_tree() -> void:
+ toolbar = Toolbar.new()
+ toolbar.hide()
+ toolbar.tool_changed.connect(_on_tool_changed)
+
+ tool_settings = ToolSettings.new()
+ tool_settings.setting_changed.connect(_on_setting_changed)
+ tool_settings.picking.connect(_on_picking)
+ tool_settings.plugin = plugin
+ tool_settings.hide()
+
+ terrain_menu = TerrainMenu.new()
+ terrain_menu.plugin = plugin
+ terrain_menu.hide()
+
+ plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
+ plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings)
+ plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_menu)
+
+ _on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD)
+
+ editor_decal_timer = Timer.new()
+ editor_decal_timer.wait_time = .5
+ editor_decal_timer.one_shot = true
+ editor_decal_timer.timeout.connect(func():
+ get_tree().create_tween().tween_property(self, "editor_decal_fade", 0.0, 0.15))
+ add_child(editor_decal_timer)
+
+
+func _ready() -> void:
+ var img: Image = Image.load_from_file(RING1)
+ img.convert(Image.FORMAT_R8)
+ ring_texture = ImageTexture.create_from_image(img)
+ editor_ring_texture_rid = ring_texture.get_rid()
+
+
+func _exit_tree() -> void:
+ plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
+ plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings)
+ toolbar.queue_free()
+ tool_settings.queue_free()
+ terrain_menu.queue_free()
+ editor_decal_timer.queue_free()
+
+
+func set_visible(p_visible: bool, p_menu_only: bool = false) -> void:
+ terrain_menu.set_visible(p_visible)
+
+ if p_menu_only:
+ toolbar.set_visible(false)
+ tool_settings.set_visible(false)
+ else:
+ visible = p_visible
+ toolbar.set_visible(p_visible)
+ tool_settings.set_visible(p_visible)
+ update_decal()
+
+ if plugin.editor:
+ if p_visible:
+ await get_tree().create_timer(.01).timeout # Won't work, otherwise
+ _on_tool_changed(_selected_tool, _selected_operation)
+ else:
+ plugin.editor.set_tool(Terrain3DEditor.TOOL_MAX)
+ plugin.editor.set_operation(Terrain3DEditor.OP_MAX)
+
+
+func set_menu_visibility(p_list: Control, p_visible: bool) -> void:
+ if p_list:
+ p_list.get_parent().get_parent().visible = p_visible
+
+
+func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
+ _selected_tool = p_tool
+ _selected_operation = p_operation
+ clear_picking()
+ set_menu_visibility(tool_settings.advanced_list, true)
+ set_menu_visibility(tool_settings.scale_list, false)
+ set_menu_visibility(tool_settings.rotation_list, false)
+ set_menu_visibility(tool_settings.height_list, false)
+ set_menu_visibility(tool_settings.color_list, false)
+
+ # Select which settings to show. Options in tool_settings.gd:_ready
+ var to_show: PackedStringArray = []
+
+ match _selected_tool:
+ Terrain3DEditor.REGION:
+ to_show.push_back("instructions")
+ to_show.push_back("invert")
+ set_menu_visibility(tool_settings.advanced_list, false)
+
+ Terrain3DEditor.SCULPT:
+ to_show.push_back("brush")
+ to_show.push_back("size")
+ to_show.push_back("strength")
+ if _selected_operation in [Terrain3DEditor.ADD, Terrain3DEditor.SUBTRACT]:
+ to_show.push_back("invert")
+ elif _selected_operation == Terrain3DEditor.GRADIENT:
+ to_show.push_back("gradient_points")
+ to_show.push_back("drawable")
+
+ Terrain3DEditor.HEIGHT:
+ to_show.push_back("brush")
+ to_show.push_back("size")
+ to_show.push_back("strength")
+ to_show.push_back("height")
+ to_show.push_back("height_picker")
+ to_show.push_back("invert")
+
+ Terrain3DEditor.TEXTURE:
+ to_show.push_back("brush")
+ to_show.push_back("size")
+ to_show.push_back("enable_texture")
+ if _selected_operation == Terrain3DEditor.ADD:
+ to_show.push_back("strength")
+ to_show.push_back("invert")
+ to_show.push_back("slope")
+ to_show.push_back("enable_angle")
+ to_show.push_back("angle")
+ to_show.push_back("angle_picker")
+ to_show.push_back("dynamic_angle")
+ to_show.push_back("enable_scale")
+ to_show.push_back("scale")
+ to_show.push_back("scale_picker")
+
+ Terrain3DEditor.COLOR:
+ to_show.push_back("brush")
+ to_show.push_back("size")
+ to_show.push_back("strength")
+ to_show.push_back("color")
+ to_show.push_back("color_picker")
+ to_show.push_back("slope")
+ to_show.push_back("texture_filter")
+ to_show.push_back("margin")
+ to_show.push_back("invert")
+
+ Terrain3DEditor.ROUGHNESS:
+ to_show.push_back("brush")
+ to_show.push_back("size")
+ to_show.push_back("strength")
+ to_show.push_back("roughness")
+ to_show.push_back("roughness_picker")
+ to_show.push_back("slope")
+ to_show.push_back("texture_filter")
+ to_show.push_back("margin")
+ to_show.push_back("invert")
+
+ Terrain3DEditor.AUTOSHADER, Terrain3DEditor.HOLES, Terrain3DEditor.NAVIGATION:
+ to_show.push_back("brush")
+ to_show.push_back("size")
+ to_show.push_back("invert")
+
+ Terrain3DEditor.INSTANCER:
+ to_show.push_back("size")
+ to_show.push_back("strength")
+ to_show.push_back("slope")
+ set_menu_visibility(tool_settings.height_list, true)
+ to_show.push_back("height_offset")
+ to_show.push_back("random_height")
+ set_menu_visibility(tool_settings.scale_list, true)
+ to_show.push_back("fixed_scale")
+ to_show.push_back("random_scale")
+ set_menu_visibility(tool_settings.rotation_list, true)
+ to_show.push_back("fixed_spin")
+ to_show.push_back("random_spin")
+ to_show.push_back("fixed_tilt")
+ to_show.push_back("random_tilt")
+ to_show.push_back("align_to_normal")
+ set_menu_visibility(tool_settings.color_list, true)
+ to_show.push_back("vertex_color")
+ to_show.push_back("random_darken")
+ to_show.push_back("random_hue")
+ to_show.push_back("invert")
+
+ _:
+ pass
+
+ # Advanced menu settings
+ to_show.push_back("auto_regions")
+ to_show.push_back("align_to_view")
+ to_show.push_back("show_cursor_while_painting")
+ to_show.push_back("gamma")
+ to_show.push_back("jitter")
+ to_show.push_back("crosshair_threshold")
+ tool_settings.show_settings(to_show)
+
+ operation_builder = null
+ if _selected_operation == Terrain3DEditor.GRADIENT:
+ operation_builder = GradientOperationBuilder.new()
+ operation_builder.tool_settings = tool_settings
+
+ _on_setting_changed()
+ plugin.update_region_grid()
+
+
+func _on_setting_changed(p_setting: Variant = null) -> void:
+ if not plugin.asset_dock: # Skip function if not _ready()
+ return
+ brush_data = tool_settings.get_settings()
+ brush_data["asset_id"] = plugin.asset_dock.get_current_list().get_selected_id()
+ if plugin.editor:
+ plugin.editor.set_brush_data(brush_data)
+ inverted_input = brush_data.get("invert", false)
+ if p_setting is CheckBox and p_setting.name == &"Invert":
+ plugin._read_input() # Revalidate keyboard input for modifier_ctrl
+ set_active_operation()
+ update_decal()
+
+
+# Change tool/operation based on modifiers. Called from:
+# * editor_plugin.gd:_read_input() - when a modifier key is pressed
+# * _on_tool_changed() via:
+# * _on_setting_changed() eg. Touchscreen Invert
+func set_active_operation() -> void:
+ var inverted: bool = plugin.modifier_ctrl || inverted_input
+
+ # Toggle toolbar buttons
+ toolbar.show_add_buttons(not inverted)
+
+ # If Shift, Smoothness
+ if plugin.modifier_shift and not inverted:
+ active_tool = Terrain3DEditor.SCULPT
+ active_operation = Terrain3DEditor.AVERAGE
+
+ # Else if Ctrl/Invert checked, opposite
+ elif _selected_operation == Terrain3DEditor.ADD and inverted:
+ active_tool = _selected_tool
+ active_operation = Terrain3DEditor.SUBTRACT
+ elif _selected_operation == Terrain3DEditor.SUBTRACT and not inverted:
+ active_tool = _selected_tool
+ active_operation = Terrain3DEditor.ADD
+
+ # Else use default and set
+ else:
+ active_tool = _selected_tool
+ active_operation = _selected_operation
+
+ if plugin.editor:
+ plugin.editor.set_tool(active_tool)
+ plugin.editor.set_operation(active_operation)
+
+
+func update_decal() -> void:
+ if not plugin.terrain or brush_data.size() <= 3:
+ return
+ mat_rid = plugin.terrain.material.get_material_rid()
+ editor_decal_timer.start()
+
+ # If not a state that should show the decal, hide everything and return
+ if not visible or \
+ plugin._input_mode < 0 or \
+ # After moving camera, wait for mouse cursor to update before revealing
+ # See https://github.com/godotengine/godot/issues/70098
+ Time.get_ticks_msec() - plugin.rmb_release_time <= 100 or \
+ (plugin._input_mode > 0 and not brush_data["show_cursor_while_painting"]):
+ hide_decal()
+ return
+
+ reset_decal_arrays()
+ editor_decal_position[0] = Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z)
+ editor_decal_visible[0] = true
+ # Set region size, and modify region map for none background mode.
+ var r_map: PackedInt32Array = plugin.terrain.data.get_region_map()
+ if plugin.editor.get_tool() == Terrain3DEditor.REGION:
+ var r_size: float = float(plugin.terrain.get_region_size()) * plugin.terrain.get_vertex_spacing()
+ var map_size: int = plugin.terrain.data.REGION_MAP_SIZE
+ var half_r_size: float = r_size * 0.5
+ var pos: Vector2 = (Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z) +
+ Vector2(half_r_size, half_r_size)).snappedf(r_size) - Vector2(half_r_size, half_r_size)
+ editor_brush_texture_rid = region_texture.get_rid()
+ editor_decal_position[0] = pos
+ editor_decal_size[0] = r_size
+ editor_decal_rotation[0] = 0.0
+
+ var loc: Vector2i = plugin.terrain.data.get_region_location(plugin.mouse_global_position)
+ loc += Vector2i(map_size / 2, map_size / 2)
+ if !(loc.x < 0 or loc.x > map_size - 1 or loc.y < 0 or loc.y > map_size - 1):
+ var index: int = clampi(loc.y * map_size + loc.x, 0, map_size * map_size - 1)
+ if plugin.terrain.material.get_world_background() == Terrain3DMaterial.WorldBackground.NONE:
+ if r_map[index] == 0 and active_operation == Terrain3DEditor.ADD:
+ r_map[index] = -index - 1
+ else:
+ r_map[index] = r_map[index]
+
+ match active_operation:
+ Terrain3DEditor.ADD:
+ if r_map[index] <= 0:
+ editor_decal_color[0] = Color.WHITE
+ editor_decal_color[0].a = 0.25
+ else:
+ hide_decal()
+
+ Terrain3DEditor.SUBTRACT:
+ if r_map[index] > 0:
+ editor_decal_color[0] = Color.WHITE * .15
+ editor_decal_color[0].a = 0.75
+ else:
+ hide_decal()
+ else:
+ hide_decal()
+ # Set texture and color
+ elif picking != Terrain3DEditor.TOOL_MAX:
+ editor_brush_texture_rid = ring_texture.get_rid()
+ editor_decal_size[0] = 10. * plugin.terrain.get_vertex_spacing()
+ match picking:
+ Terrain3DEditor.HEIGHT:
+ editor_decal_color[0] = COLOR_PICK_HEIGHT
+ Terrain3DEditor.COLOR:
+ editor_decal_color[0] = COLOR_PICK_COLOR
+ Terrain3DEditor.ROUGHNESS:
+ editor_decal_color[0] = COLOR_PICK_ROUGH
+ editor_decal_color[0].a = 1.0
+ else:
+ editor_brush_texture_rid = brush_data["brush"][1].get_rid()
+ editor_decal_size[0] = maxf(brush_data["size"], .5)
+ if brush_data["align_to_view"]:
+ var cam: Camera3D = plugin.terrain.get_camera();
+ if (cam):
+ editor_decal_rotation[0] = cam.rotation.y
+ else:
+ editor_decal_rotation[0] = 0.
+ match plugin.editor.get_tool():
+ Terrain3DEditor.SCULPT:
+ match active_operation:
+ Terrain3DEditor.ADD:
+ if plugin.modifier_alt:
+ editor_decal_color[0] = COLOR_LIFT
+ editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5)
+ else:
+ editor_decal_color[0] = COLOR_RAISE
+ editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5)
+ Terrain3DEditor.SUBTRACT:
+ if plugin.modifier_alt:
+ editor_decal_color[0] = COLOR_FLATTEN
+ editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5) + .1
+ else:
+ editor_decal_color[0] = COLOR_LOWER
+ editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
+ Terrain3DEditor.AVERAGE:
+ editor_decal_color[0] = COLOR_SMOOTH
+ editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
+ Terrain3DEditor.GRADIENT:
+ editor_decal_color[0] = COLOR_SLOPE
+ editor_decal_color[0].a = clamp(brush_data["strength"], .2, .4)
+ Terrain3DEditor.HEIGHT:
+ editor_decal_color[0] = COLOR_HEIGHT
+ editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
+ Terrain3DEditor.TEXTURE:
+ match active_operation:
+ Terrain3DEditor.REPLACE:
+ editor_decal_color[0] = COLOR_PAINT
+ editor_decal_color[0].a = .6
+ Terrain3DEditor.SUBTRACT:
+ editor_decal_color[0] = COLOR_PAINT
+ editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1
+ Terrain3DEditor.ADD:
+ editor_decal_color[0] = COLOR_SPRAY
+ editor_decal_color[0].a = clamp(brush_data["strength"], .15, .4)
+ Terrain3DEditor.COLOR:
+ editor_decal_color[0] = brush_data["color"].srgb_to_linear()
+ editor_decal_color[0].a *= clamp(brush_data["strength"], .2, .5)
+ Terrain3DEditor.ROUGHNESS:
+ editor_decal_color[0] = COLOR_ROUGHNESS
+ editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1
+ Terrain3DEditor.AUTOSHADER:
+ editor_decal_color[0] = COLOR_AUTOSHADER
+ editor_decal_color[0].a = .6
+ Terrain3DEditor.HOLES:
+ editor_decal_color[0] = COLOR_HOLES
+ editor_decal_color[0].a = .75
+ Terrain3DEditor.NAVIGATION:
+ editor_decal_color[0] = COLOR_NAVIGATION
+ editor_decal_color[0].a = .80
+ Terrain3DEditor.INSTANCER:
+ editor_brush_texture_rid = ring_texture.get_rid()
+ editor_decal_color[0] = COLOR_INSTANCER
+ editor_decal_color[0].a = .75
+
+ editor_decal_visible[1] = false
+ editor_decal_visible[2] = false
+
+ if active_operation == Terrain3DEditor.GRADIENT:
+ var point1: Vector3 = brush_data["gradient_points"][0]
+ if point1 != Vector3.ZERO:
+ editor_decal_color[1] = COLOR_SLOPE
+ editor_decal_size[1] = 10. * plugin.terrain.get_vertex_spacing()
+ editor_decal_visible[1] = true
+ editor_decal_position[1] = Vector2(point1.x, point1.z)
+ var point2: Vector3 = brush_data["gradient_points"][1]
+ if point2 != Vector3.ZERO:
+ editor_decal_color[2] = COLOR_SLOPE
+ editor_decal_size[2] = 10. * plugin.terrain.get_vertex_spacing()
+ editor_decal_visible[2] = true
+ editor_decal_position[2] = Vector2(point2.x, point2.z)
+
+ if RenderingServer.get_current_rendering_method().contains("gl_compatibility"):
+ for i in editor_decal_color.size():
+ editor_decal_color[i].a = maxf(0.1, editor_decal_color[i].a - .25)
+
+ editor_decal_fade = editor_decal_color[0].a
+ # Update Shader params
+ if is_shader_valid():
+ RenderingServer.material_set_param(mat_rid, "_editor_brush_texture", editor_brush_texture_rid)
+ RenderingServer.material_set_param(mat_rid, "_editor_ring_texture", editor_ring_texture_rid)
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position)
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size)
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
+ RenderingServer.material_set_param(mat_rid, "_editor_crosshair_threshold", brush_data["crosshair_threshold"] + 0.1)
+ RenderingServer.material_set_param(mat_rid, "_region_map", r_map)
+
+
+func is_shader_valid() -> bool:
+ # As long as the compiled shader contains at least 1 uniform, we can use it to check
+ # if the shader compilation has failed, as this will then return an empty dictionary.
+ if not plugin.terrain:
+ return false
+ var params = RenderingServer.get_shader_parameter_list(plugin.terrain.material.get_shader_rid())
+ if params.is_empty():
+ return false
+ else:
+ return true
+
+
+func hide_decal() -> void:
+ editor_decal_visible = [false, false, false]
+ if is_shader_valid():
+ var r_map: PackedInt32Array = plugin.terrain.data.get_region_map()
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
+ RenderingServer.material_set_param(mat_rid, "_region_map", r_map)
+
+
+# These array sizes are reset to 0 when closing scenes for some unknown reason, so check and reset
+func reset_decal_arrays() -> void:
+ if editor_decal_color.size() < 3:
+ editor_decal_position = [Vector2(), Vector2(), Vector2()]
+ editor_decal_rotation = [float(), float(), float()]
+ editor_decal_size = [float(), float(), float()]
+ editor_decal_color = [Color(), Color(), Color()]
+ editor_decal_visible = [false, false, false]
+ editor_brush_texture_rid = RID()
+
+
+func set_decal_rotation(p_rot: float) -> void:
+ editor_decal_rotation[0] = p_rot
+ if is_shader_valid():
+ RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
+
+
+func _on_picking(p_type: Terrain3DEditor.Tool, p_callback: Callable) -> void:
+ picking = p_type
+ picking_callback = p_callback
+ update_decal()
+
+
+func clear_picking() -> void:
+ picking = Terrain3DEditor.TOOL_MAX
+
+
+func is_picking() -> bool:
+ if picking != Terrain3DEditor.TOOL_MAX:
+ return true
+
+ if operation_builder and operation_builder.is_picking():
+ return true
+
+ return false
+
+
+func pick(p_global_position: Vector3) -> void:
+ if picking != Terrain3DEditor.TOOL_MAX:
+ var color: Color
+ match picking:
+ Terrain3DEditor.HEIGHT, Terrain3DEditor.SCULPT:
+ color = Color(plugin.terrain.data.get_height(p_global_position), 0., 0., 1.)
+ Terrain3DEditor.ROUGHNESS:
+ color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position)
+ Terrain3DEditor.COLOR:
+ color = plugin.terrain.data.get_color(p_global_position)
+ Terrain3DEditor.ANGLE:
+ color = Color(plugin.terrain.data.get_control_angle(p_global_position), 0., 0., 1.)
+ Terrain3DEditor.SCALE:
+ color = Color(plugin.terrain.data.get_control_scale(p_global_position), 0., 0., 1.)
+ _:
+ push_error("Unsupported picking type: ", picking)
+ return
+ if picking_callback.is_valid():
+ picking_callback.call(picking, color, p_global_position)
+ picking_callback = Callable()
+ picking = Terrain3DEditor.TOOL_MAX
+
+ elif operation_builder and operation_builder.is_picking():
+ operation_builder.pick(p_global_position, plugin.terrain)
+
+
+func set_button_editor_icon(p_button: Button, p_icon_name: String) -> void:
+ p_button.icon = EditorInterface.get_base_control().get_theme_icon(p_icon_name, "EditorIcons")
diff --git a/addons/terrain_3d/src/ui.gd.uid b/addons/terrain_3d/src/ui.gd.uid
new file mode 100644
index 0000000..bc20eee
--- /dev/null
+++ b/addons/terrain_3d/src/ui.gd.uid
@@ -0,0 +1 @@
+uid://bpad72s36mwkx
diff --git a/addons/terrain_3d/terrain.gdextension b/addons/terrain_3d/terrain.gdextension
new file mode 100644
index 0000000..000d085
--- /dev/null
+++ b/addons/terrain_3d/terrain.gdextension
@@ -0,0 +1,32 @@
+[configuration]
+
+entry_symbol = "terrain_3d_init"
+compatibility_minimum = 4.4
+
+[icons]
+
+Terrain3D = "res://addons/terrain_3d/icons/terrain3d.svg"
+
+[libraries]
+
+windows.debug.x86_64 = "res://addons/terrain_3d/bin/libterrain.windows.debug.x86_64.dll"
+windows.release.x86_64 = "res://addons/terrain_3d/bin/libterrain.windows.release.x86_64.dll"
+
+linux.debug.x86_64 = "res://addons/terrain_3d/bin/libterrain.linux.debug.x86_64.so"
+linux.release.x86_64 = "res://addons/terrain_3d/bin/libterrain.linux.release.x86_64.so"
+linux.debug.arm64 = "res://addons/terrain_3d/bin/libterrain.linux.debug.arm64.so"
+linux.release.arm64 = "res://addons/terrain_3d/bin/libterrain.linux.release.arm64.so"
+linux.debug.rv64 = "res://addons/terrain_3d/bin/libterrain.linux.debug.rv64.so"
+linux.release.rv64 = "res://addons/terrain_3d/bin/libterrain.linux.release.rv64.so"
+
+macos.debug = "res://addons/terrain_3d/bin/libterrain.macos.debug.framework"
+macos.release = "res://addons/terrain_3d/bin/libterrain.macos.release.framework"
+
+android.debug.arm64 = "res://addons/terrain_3d/bin/libterrain.android.debug.arm64.so"
+android.release.arm64 = "res://addons/terrain_3d/bin/libterrain.android.release.arm64.so"
+
+ios.debug = "res://addons/terrain_3d/bin/libterrain.ios.debug.universal.dylib"
+ios.release = "res://addons/terrain_3d/bin/libterrain.ios.release.universal.dylib"
+
+web.debug = "res://addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm"
+web.release = "res://addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm"
diff --git a/addons/terrain_3d/terrain.gdextension.uid b/addons/terrain_3d/terrain.gdextension.uid
new file mode 100644
index 0000000..ef58a55
--- /dev/null
+++ b/addons/terrain_3d/terrain.gdextension.uid
@@ -0,0 +1 @@
+uid://bdn2ygldx56a8
diff --git a/addons/terrain_3d/tools/importer.gd b/addons/terrain_3d/tools/importer.gd
new file mode 100644
index 0000000..661099b
--- /dev/null
+++ b/addons/terrain_3d/tools/importer.gd
@@ -0,0 +1,108 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Importer for Terrain3D
+@tool
+extends Terrain3D
+
+
+@export var clear_all: bool = false : set = reset_settings
+@export var clear_terrain: bool = false : set = reset_terrain
+@export var update_height_range: bool = false : set = update_heights
+
+
+func reset_settings(p_value) -> void:
+ if p_value:
+ height_file_name = ""
+ control_file_name = ""
+ color_file_name = ""
+ destination_directory = ""
+ import_position = Vector2i.ZERO
+ height_offset = 0.0
+ import_scale = 1.0
+ r16_range = Vector2(0, 1)
+ r16_size = Vector2i(1024, 1024)
+ material = null
+ assets = null
+ reset_terrain(true)
+
+
+func reset_terrain(p_value) -> void:
+ data_directory = ""
+ for region:Terrain3DRegion in data.get_regions_active():
+ data.remove_region(region, false)
+ data.update_maps(Terrain3DRegion.TYPE_MAX, true, false)
+
+
+## Recalculates min and max heights for all regions.
+func update_heights(p_value) -> void:
+ if p_value and data:
+ data.calc_height_range(true)
+
+
+@export_group("Import File")
+@export_global_file var height_file_name: String = ""
+@export_global_file var control_file_name: String = ""
+@export_global_file var color_file_name: String = ""
+@export var import_position: Vector2i = Vector2i(0, 0) : set = set_import_position
+@export var import_scale: float = 1.0
+@export var height_offset: float = 0.0
+@export var r16_range: Vector2 = Vector2(0, 1)
+@export var r16_size: Vector2i = Vector2i(1024, 1024) : set = set_r16_size
+@export var run_import: bool = false : set = start_import
+
+@export_dir var destination_directory: String = ""
+@export var save_to_disk: bool = false : set = save_data
+
+
+func set_import_position(p_value: Vector2i) -> void:
+ import_position.x = clamp(p_value.x, -8192, 8192)
+ import_position.y = clamp(p_value.y, -8192, 8192)
+
+
+func set_r16_size(p_value: Vector2i) -> void:
+ r16_size.x = clamp(p_value.x, 0, 16384)
+ r16_size.y = clamp(p_value.y, 0, 16384)
+
+
+func start_import(p_value: bool) -> void:
+ if p_value:
+ print("Terrain3DImporter: Importing files:\n\t%s\n\t%s\n\t%s" % [ height_file_name, control_file_name, color_file_name])
+
+ var imported_images: Array[Image]
+ imported_images.resize(Terrain3DRegion.TYPE_MAX)
+ var min_max := Vector2(0, 1)
+ var img: Image
+ if height_file_name:
+ img = Terrain3DUtil.load_image(height_file_name, ResourceLoader.CACHE_MODE_IGNORE, r16_range, r16_size)
+ min_max = Terrain3DUtil.get_min_max(img)
+ imported_images[Terrain3DRegion.TYPE_HEIGHT] = img
+ if control_file_name:
+ img = Terrain3DUtil.load_image(control_file_name, ResourceLoader.CACHE_MODE_IGNORE)
+ imported_images[Terrain3DRegion.TYPE_CONTROL] = img
+ if color_file_name:
+ img = Terrain3DUtil.load_image(color_file_name, ResourceLoader.CACHE_MODE_IGNORE)
+ imported_images[Terrain3DRegion.TYPE_COLOR] = img
+ if assets.get_texture_count() == 0:
+ material.show_checkered = false
+ material.show_colormap = true
+ var pos := Vector3(import_position.x, 0, import_position.y)
+ data.import_images(imported_images, pos, height_offset, import_scale)
+ print("Terrain3DImporter: Import finished")
+
+
+func save_data(p_value: bool) -> void:
+ if destination_directory.is_empty():
+ push_error("Set destination directory first")
+ return
+ data.save_directory(destination_directory)
+
+
+@export_group("Export File")
+enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR }
+@export_enum("Height:0", "Control:1", "Color:2") var map_type: int = TYPE_HEIGHT
+@export var file_name_out: String = ""
+@export var run_export: bool = false : set = start_export
+
+func start_export(p_value: bool) -> void:
+ var err: int = data.export_image(file_name_out, map_type)
+ print("Terrain3DImporter: Export error status: ", err, " ", error_string(err))
+
diff --git a/addons/terrain_3d/tools/importer.gd.uid b/addons/terrain_3d/tools/importer.gd.uid
new file mode 100644
index 0000000..7a59538
--- /dev/null
+++ b/addons/terrain_3d/tools/importer.gd.uid
@@ -0,0 +1 @@
+uid://cib2rig7vup10
diff --git a/addons/terrain_3d/tools/importer.tscn b/addons/terrain_3d/tools/importer.tscn
new file mode 100644
index 0000000..4bc43c3
--- /dev/null
+++ b/addons/terrain_3d/tools/importer.tscn
@@ -0,0 +1,63 @@
+[gd_scene load_steps=9 format=3 uid="uid://blaieaqp413k7"]
+
+[ext_resource type="Script" path="res://addons/terrain_3d/tools/importer.gd" id="1_60b8f"]
+
+[sub_resource type="Gradient" id="Gradient_88f3t"]
+offsets = PackedFloat32Array(0.2, 1)
+colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_muvel"]
+noise_type = 2
+frequency = 0.03
+cellular_jitter = 3.0
+cellular_return_type = 0
+domain_warp_enabled = true
+domain_warp_type = 1
+domain_warp_amplitude = 50.0
+domain_warp_fractal_type = 2
+domain_warp_fractal_lacunarity = 1.5
+domain_warp_fractal_gain = 1.0
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_ve0yk"]
+seamless = true
+color_ramp = SubResource("Gradient_88f3t")
+noise = SubResource("FastNoiseLite_muvel")
+
+[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_p55u0"]
+_shader_parameters = {
+"blend_sharpness": 0.87,
+"height_blending": true,
+"macro_variation1": Color(1, 1, 1, 1),
+"macro_variation2": Color(1, 1, 1, 1),
+"noise1_angle": 0.0,
+"noise1_offset": Vector2(0.5, 0.5),
+"noise1_scale": 0.04,
+"noise2_scale": 0.076,
+"noise3_scale": 0.225,
+"noise_texture": SubResource("NoiseTexture2D_ve0yk"),
+"vertex_normals_distance": 128.0
+}
+show_checkered = true
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8rvqy"]
+cull_mode = 2
+vertex_color_use_as_albedo = true
+backlight_enabled = true
+backlight = Color(0.5, 0.5, 0.5, 1)
+
+[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_7je72"]
+height_offset = 0.5
+density = 10.0
+material_override = SubResource("StandardMaterial3D_8rvqy")
+generated_type = 1
+
+[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_op32e"]
+mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_7je72")])
+
+[node name="Importer" type="Terrain3D"]
+material = SubResource("Terrain3DMaterial_p55u0")
+assets = SubResource("Terrain3DAssets_op32e")
+mesh_lods = 8
+top_level = true
+script = ExtResource("1_60b8f")
+metadata/_edit_lock_ = true
diff --git a/addons/terrain_3d/tools/region_mover.gd b/addons/terrain_3d/tools/region_mover.gd
new file mode 100644
index 0000000..78dcbd8
--- /dev/null
+++ b/addons/terrain_3d/tools/region_mover.gd
@@ -0,0 +1,52 @@
+# This script can be used to move your regions by an offset.
+# Eventually this tool will find its way into a built in UI
+#
+# Attach it to your Terrain3D node
+# Save and reload your scene
+# Select your Terrain3D node
+# Enter a valid `offset` where all regions will be within -16, +15
+# Run it
+# It should unload the regions, rename files, and reload them
+# Clear the script and resave your scene
+
+
+@tool
+extends Terrain3D
+
+
+@export var offset: Vector2i
+@export var run: bool = false : set = start_rename
+
+
+func start_rename(val: bool = false) -> void:
+ if val == false or offset == Vector2i.ZERO:
+ return
+
+ var dir_name: String = data_directory
+ data_directory = ""
+ var dir := DirAccess.open(dir_name)
+ if not dir:
+ print("An error occurred when trying to access the path: ", data_directory)
+ return
+
+ var affected_files: PackedStringArray
+ var files: PackedStringArray = dir.get_files()
+ for file_name in files:
+ if file_name.match("terrain3d*.res") and not dir.current_is_dir():
+ var region_loc: Vector2i = Terrain3DUtil.filename_to_location(file_name)
+ var new_loc: Vector2i = region_loc + offset
+ if new_loc.x < -16 or new_loc.x > 15 or new_loc.y < -16 or new_loc.y > 15:
+ push_error("New location %.0v out of bounds for region %.0v. Aborting" % [ new_loc, region_loc ])
+ return
+ var new_name: String = "tmp_" + Terrain3DUtil.location_to_filename(new_loc)
+ dir.rename(file_name, new_name)
+ affected_files.push_back(new_name)
+ print("File: %s renamed to: %s" % [ file_name, new_name ])
+
+ for file_name in affected_files:
+ var new_name: String = file_name.trim_prefix("tmp_")
+ dir.rename(file_name, new_name)
+ print("File: %s renamed to: %s" % [ file_name, new_name ])
+
+ data_directory = dir_name
+ EditorInterface.get_resource_filesystem().scan()
diff --git a/addons/terrain_3d/tools/region_mover.gd.uid b/addons/terrain_3d/tools/region_mover.gd.uid
new file mode 100644
index 0000000..0c9b4e5
--- /dev/null
+++ b/addons/terrain_3d/tools/region_mover.gd.uid
@@ -0,0 +1 @@
+uid://bngnvtbm6ifkk
diff --git a/addons/terrain_3d/utils/terrain_3d_objects.gd b/addons/terrain_3d/utils/terrain_3d_objects.gd
new file mode 100644
index 0000000..3a19f6d
--- /dev/null
+++ b/addons/terrain_3d/utils/terrain_3d_objects.gd
@@ -0,0 +1,191 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Objects parent for Terrain3D
+# Children nodes get transform updates on sculpting
+@tool
+extends Node3D
+class_name Terrain3DObjects
+
+const TransformChangedNotifier: Script = preload("res://addons/terrain_3d/utils/transform_changed_notifier.gd")
+
+const CHILD_HELPER_NAME: StringName = &"TransformChangedSignaller"
+const CHILD_HELPER_PATH: NodePath = ^"TransformChangedSignaller"
+
+var _undo_redo = null
+var _terrain_id: int
+var _offsets: Dictionary # Object ID -> Vector3(X, Y offset relative to terrain height, Z)
+var _ignore_transform_change: bool = false
+
+
+func _enter_tree() -> void:
+ if not Engine.is_editor_hint():
+ return
+
+ for child in get_children():
+ _on_child_entered_tree(child)
+
+ child_entered_tree.connect(_on_child_entered_tree)
+ child_exiting_tree.connect(_on_child_exiting_tree)
+
+
+func _exit_tree() -> void:
+ if not Engine.is_editor_hint():
+ return
+
+ child_entered_tree.disconnect(_on_child_entered_tree)
+ child_exiting_tree.disconnect(_on_child_exiting_tree)
+
+ for child in get_children():
+ _on_child_exiting_tree(child)
+
+
+func editor_setup(p_plugin) -> void:
+ _undo_redo = p_plugin.get_undo_redo()
+
+
+func get_terrain() -> Terrain3D:
+ var terrain := instance_from_id(_terrain_id) as Terrain3D
+ if not terrain or terrain.is_queued_for_deletion() or not terrain.is_inside_tree():
+ var terrains: Array[Node] = Engine.get_singleton(&"EditorInterface").get_edited_scene_root().find_children("", "Terrain3D")
+ if terrains.size() > 0:
+ terrain = terrains[0]
+ _terrain_id = terrain.get_instance_id() if terrain else 0
+
+ if terrain and terrain.data and not terrain.data.maps_edited.is_connected(_on_maps_edited):
+ terrain.data.maps_edited.connect(_on_maps_edited)
+
+ return terrain
+
+
+func _get_terrain_height(p_global_position: Vector3) -> float:
+ var terrain: Terrain3D = get_terrain()
+ if not terrain or not terrain.data:
+ return 0.0
+ var height: float = terrain.data.get_height(p_global_position)
+ if is_nan(height):
+ return 0.0
+ return height
+
+
+func _on_child_entered_tree(p_node: Node) -> void:
+ if not (p_node is Node3D):
+ return
+
+ assert(p_node.get_parent() == self)
+
+ var helper: TransformChangedNotifier = p_node.get_node_or_null(CHILD_HELPER_PATH)
+ if not helper:
+ helper = TransformChangedNotifier.new()
+ helper.name = CHILD_HELPER_NAME
+ p_node.add_child(helper, true, INTERNAL_MODE_BACK)
+ assert(p_node.has_node(CHILD_HELPER_PATH))
+
+ # When reparenting a Node3D, Godot changes its transform _after_ reparenting it. So here,
+ # we must use call_deferred, to avoid receiving transform_changed as a result of reparenting.
+ _setup_child_signal.call_deferred(p_node, helper)
+
+
+func _setup_child_signal(p_node: Node, helper: TransformChangedNotifier) -> void:
+ if not p_node.is_inside_tree():
+ return
+ if helper.transform_changed.is_connected(_on_child_transform_changed):
+ return
+
+ helper.transform_changed.connect(_on_child_transform_changed.bind(p_node))
+ _update_child_offset(p_node)
+
+
+func _on_child_exiting_tree(p_node: Node) -> void:
+ if not (p_node is Node3D) or not p_node.has_node(CHILD_HELPER_PATH):
+ return
+
+ var helper: TransformChangedNotifier = p_node.get_node_or_null(CHILD_HELPER_PATH)
+ if helper:
+ if helper.transform_changed.is_connected(_on_child_transform_changed):
+ helper.transform_changed.disconnect(_on_child_transform_changed)
+ p_node.remove_child(helper)
+ helper.queue_free()
+
+ _offsets.erase(p_node.get_instance_id())
+
+
+func _is_node_selected(p_node: Node) -> bool:
+ var editor_sel = Engine.get_singleton(&"EditorInterface").get_selection()
+ return editor_sel.get_transformable_selected_nodes().has(p_node)
+
+
+func _on_child_transform_changed(p_node: Node3D) -> void:
+ if _ignore_transform_change:
+ return
+
+ var lmb_down := Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
+ if lmb_down and (_is_node_selected(p_node) or _is_node_selected(self)):
+ # The user may be moving the node using gizmos.
+ # We should wait until they're done before updating otherwise gizmos + this node conflict.
+ return
+
+ if not _offsets.has(p_node.get_instance_id()):
+ return
+
+ var old_offset: Vector3 = _offsets[p_node.get_instance_id()]
+ var old_h: float = _get_terrain_height(old_offset)
+ var old_position: Vector3 = old_offset + Vector3(0, old_h, 0)
+ var new_position: Vector3 = p_node.global_position
+ if old_position.is_equal_approx(new_position):
+ return
+ var new_h: float = _get_terrain_height(new_position)
+ var new_offset: Vector3 = new_position - Vector3(0, new_h, 0)
+
+ var translate_without_reposition: bool = Input.is_key_pressed(KEY_SHIFT)
+ var y_changed: bool = not is_equal_approx(old_position.y, p_node.global_position.y)
+ if not y_changed and not translate_without_reposition:
+ new_offset.y = old_offset.y
+ new_position = new_offset + Vector3(0, new_h, 0)
+
+ # Make sure that when the user undo's the translation, the offset change gets undone too!
+ _undo_redo.create_action("Translate", UndoRedo.MERGE_ALL)
+ _undo_redo.add_do_method(self, &"_set_offset_and_position", p_node.get_instance_id(), new_offset, new_position)
+ _undo_redo.add_undo_method(self, &"_set_offset_and_position", p_node.get_instance_id(), old_offset, old_position)
+ _undo_redo.commit_action()
+
+
+func _set_offset_and_position(p_id: int, p_offset: Vector3, p_position: Vector3) -> void:
+ var node := instance_from_id(p_id) as Node
+ if not is_instance_valid(node):
+ return
+
+ _ignore_transform_change = true
+ node.global_position = p_position
+ _offsets[p_id] = p_offset
+ _ignore_transform_change = false
+
+
+# Overwrite current offset stored for node with its current Y position relative to the terrain
+func _update_child_offset(p_node: Node3D) -> void:
+ var position: Vector3 = global_transform * p_node.position
+ var h: float = _get_terrain_height(position)
+ var offset: Vector3 = position - Vector3(0, h, 0)
+ _offsets[p_node.get_instance_id()] = offset
+
+
+# Overwrite node's current position with terrain height + stored offset for this node
+func _update_child_position(p_node: Node3D) -> void:
+ if not _offsets.has(p_node.get_instance_id()):
+ return
+
+ var position: Vector3 = global_transform * p_node.position
+ var h: float = _get_terrain_height(position)
+ var offset: Vector3 = _offsets[p_node.get_instance_id()]
+ var new_position: Vector3 = global_transform.inverse() * (offset + Vector3(0, h, 0))
+ if not p_node.position.is_equal_approx(new_position):
+ p_node.position = new_position
+
+
+func _on_maps_edited(p_edited_aabb: AABB) -> void:
+ var edited_area: AABB = p_edited_aabb.grow(1)
+ edited_area.position.y = -INF
+ edited_area.end.y = INF
+
+ for child in get_children():
+ var node := child as Node3D
+ if node && edited_area.has_point(node.global_position):
+ _update_child_position(node)
diff --git a/addons/terrain_3d/utils/terrain_3d_objects.gd.uid b/addons/terrain_3d/utils/terrain_3d_objects.gd.uid
new file mode 100644
index 0000000..3a1e873
--- /dev/null
+++ b/addons/terrain_3d/utils/terrain_3d_objects.gd.uid
@@ -0,0 +1 @@
+uid://dbndw8p05yam7
diff --git a/addons/terrain_3d/utils/transform_changed_notifier.gd b/addons/terrain_3d/utils/transform_changed_notifier.gd
new file mode 100644
index 0000000..5feaae8
--- /dev/null
+++ b/addons/terrain_3d/utils/transform_changed_notifier.gd
@@ -0,0 +1,16 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Transform Changed Notifier for Terrain3D
+@tool
+extends Node3D
+
+signal transform_changed
+
+
+func _ready() -> void:
+ assert(Engine.is_editor_hint())
+ set_notify_transform(true)
+
+
+func _notification(what: int) -> void:
+ if what == NOTIFICATION_TRANSFORM_CHANGED:
+ transform_changed.emit()
diff --git a/addons/terrain_3d/utils/transform_changed_notifier.gd.uid b/addons/terrain_3d/utils/transform_changed_notifier.gd.uid
new file mode 100644
index 0000000..fc43825
--- /dev/null
+++ b/addons/terrain_3d/utils/transform_changed_notifier.gd.uid
@@ -0,0 +1 @@
+uid://claxtgppe8keq
diff --git a/demo/CodeGeneratedDemo.tscn b/demo/CodeGeneratedDemo.tscn
new file mode 100644
index 0000000..8c7b5e4
--- /dev/null
+++ b/demo/CodeGeneratedDemo.tscn
@@ -0,0 +1,42 @@
+[gd_scene load_steps=8 format=3 uid="uid://cofnhdcclon1w"]
+
+[ext_resource type="Script" path="res://demo/src/CodeGenerated.gd" id="1_h7vyv"]
+[ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="2_3v2uf"]
+[ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_71ikj"]
+[ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="4_p8qry"]
+[ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="4_x5ge4"]
+[ext_resource type="Script" path="res://demo/src/RuntimeNavigationBaker.gd" id="5_445ur"]
+
+[sub_resource type="NavigationMesh" id="NavigationMesh_vs6am"]
+geometry_parsed_geometry_type = 1
+agent_height = 2.0
+agent_max_slope = 30.0
+
+[node name="CodeGenerated" type="Node"]
+script = ExtResource("1_h7vyv")
+
+[node name="Environment" parent="." instance=ExtResource("3_71ikj")]
+
+[node name="Player" parent="." instance=ExtResource("2_3v2uf")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, 0)
+
+[node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("4_p8qry")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 50, -10)
+target = NodePath("../Player")
+
+[node name="UI" parent="." instance=ExtResource("4_x5ge4")]
+
+[node name="RuntimeNavigationBaker" type="Node" parent="." node_paths=PackedStringArray("player")]
+script = ExtResource("5_445ur")
+enabled = false
+template = SubResource("NavigationMesh_vs6am")
+player = NodePath("../Player")
+
+[node name="RunThisSceneLabel3D" type="Label3D" parent="."]
+pixel_size = 0.001
+billboard = 1
+no_depth_test = true
+fixed_size = true
+text = "Run This Scene"
+font_size = 64
+outline_size = 10
diff --git a/demo/Demo.tscn b/demo/Demo.tscn
new file mode 100644
index 0000000..c11e4ec
--- /dev/null
+++ b/demo/Demo.tscn
@@ -0,0 +1,99 @@
+[gd_scene load_steps=13 format=3 uid="uid://chol2xlfbq7cu"]
+
+[ext_resource type="Script" uid="uid://chstoagn42gbr" path="res://demo/src/DemoScene.gd" id="1_k7qca"]
+[ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="2_nqak5"]
+[ext_resource type="PackedScene" uid="uid://dwnhqfjq7v1pq" path="res://demo/components/Borders.tscn" id="3_cw38j"]
+[ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="3_ht63y"]
+[ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="3_kdh0b"]
+[ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_yqldq"]
+[ext_resource type="PackedScene" uid="uid://d3sr0a7dxfkr8" path="res://addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn" id="8_fwrtk"]
+[ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="8_g2of2"]
+
+[sub_resource type="Gradient" id="Gradient_vr1m7"]
+offsets = PackedFloat32Array(0.2, 1)
+colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_d8lcj"]
+noise_type = 2
+frequency = 0.03
+cellular_jitter = 3.0
+cellular_return_type = 0
+domain_warp_enabled = true
+domain_warp_type = 1
+domain_warp_amplitude = 50.0
+domain_warp_fractal_type = 2
+domain_warp_fractal_lacunarity = 1.5
+domain_warp_fractal_gain = 1.0
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_bov7h"]
+seamless = true
+color_ramp = SubResource("Gradient_vr1m7")
+noise = SubResource("FastNoiseLite_d8lcj")
+
+[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jrc01"]
+_shader_parameters = {
+"_mouse_layer": 2147483648,
+"auto_base_texture": 0,
+"auto_height_reduction": 0.05,
+"auto_overlay_texture": 1,
+"auto_slope": 0.45,
+"bias_distance": 512.0,
+"blend_sharpness": 0.5,
+"depth_blur": 0.0,
+"dual_scale_far": 170.0,
+"dual_scale_near": 100.0,
+"dual_scale_reduction": 0.3,
+"dual_scale_texture": 0,
+"enable_macro_variation": true,
+"enable_projection": true,
+&"flat_terrain_normals": false,
+"macro_variation1": Color(0.855, 0.8625, 0.9, 1),
+"macro_variation2": Color(0.9, 0.885, 0.81, 1),
+"macro_variation_slope": 0.333,
+"mipmap_bias": 1.0,
+"noise1_angle": 0.1,
+"noise1_offset": Vector2(0.5, 0.5),
+"noise1_scale": 0.04,
+"noise2_scale": 0.076,
+"noise_texture": SubResource("NoiseTexture2D_bov7h"),
+"projection_threshold": 0.87,
+"tri_scale_reduction": 0.3,
+"world_noise_fragment_normals": false,
+"world_noise_height": 34.0,
+"world_noise_lod_distance": 7500.0,
+"world_noise_max_octaves": 4,
+"world_noise_min_octaves": 2,
+"world_noise_offset": Vector3(2.17, -1.225, 1.9),
+"world_noise_region_blend": 0.33,
+"world_noise_scale": 9.85
+}
+world_background = 2
+auto_shader = true
+dual_scaling = true
+
+[node name="Demo" type="Node"]
+script = ExtResource("1_k7qca")
+
+[node name="UI" parent="." instance=ExtResource("2_nqak5")]
+
+[node name="Environment" parent="." instance=ExtResource("3_yqldq")]
+
+[node name="Borders" parent="." instance=ExtResource("3_cw38j")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 512, -3, 512)
+collision_mask = 3
+
+[node name="Tunnel" parent="." instance=ExtResource("3_kdh0b")]
+
+[node name="Player" parent="." instance=ExtResource("3_ht63y")]
+transform = Transform3D(0.176947, 0, -0.98422, 0, 1, 0, 0.98422, 0, 0.176947, 223.143, 105.348, -1833.08)
+
+[node name="Terrain3D" type="Terrain3D" parent="."]
+data_directory = "res://demo/data"
+material = SubResource("Terrain3DMaterial_jrc01")
+assets = ExtResource("8_g2of2")
+collision_mask = 3
+top_level = true
+metadata/_edit_lock_ = true
+
+[node name="Terrain3DParticles" parent="Terrain3D" node_paths=PackedStringArray("terrain") instance=ExtResource("8_fwrtk")]
+terrain = NodePath("..")
diff --git a/demo/NavigationDemo.tscn b/demo/NavigationDemo.tscn
new file mode 100644
index 0000000..694a1dc
--- /dev/null
+++ b/demo/NavigationDemo.tscn
@@ -0,0 +1,98 @@
+[gd_scene load_steps=14 format=3 uid="uid://x34e4v60vdmy"]
+
+[ext_resource type="Script" path="res://demo/src/DemoScene.gd" id="1_po4vw"]
+[ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="2_i74wg"]
+[ext_resource type="PackedScene" uid="uid://dwnhqfjq7v1pq" path="res://demo/components/Borders.tscn" id="3_3upu0"]
+[ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="4_fk3ul"]
+[ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="5_2lvlr"]
+[ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="5_rc7e2"]
+[ext_resource type="NavigationMesh" uid="uid://yf3fu23666k8" path="res://demo/data/nav_mesh.res" id="8_6jfdr"]
+[ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="10_qmhh8"]
+[ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="12_b2xl8"]
+
+[sub_resource type="Gradient" id="Gradient_vr1m7"]
+offsets = PackedFloat32Array(0.2, 1)
+colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_d8lcj"]
+noise_type = 2
+frequency = 0.03
+cellular_jitter = 3.0
+cellular_return_type = 0
+domain_warp_enabled = true
+domain_warp_type = 1
+domain_warp_amplitude = 50.0
+domain_warp_fractal_type = 2
+domain_warp_fractal_lacunarity = 1.5
+domain_warp_fractal_gain = 1.0
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_bov7h"]
+seamless = true
+color_ramp = SubResource("Gradient_vr1m7")
+noise = SubResource("FastNoiseLite_d8lcj")
+
+[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_s5mq5"]
+_shader_parameters = {
+"_mouse_layer": 2147483648,
+"auto_base_texture": 0,
+"auto_height_reduction": 0.1,
+"auto_overlay_texture": 1,
+"auto_slope": 1.0,
+"bias_distance": 512.0,
+"blend_sharpness": 0.87,
+"depth_blur": 0.0,
+"dual_scale_far": 170.0,
+"dual_scale_near": 100.0,
+"dual_scale_reduction": 0.3,
+"dual_scale_texture": 0,
+"enable_macro_variation": true,
+"enable_projection": true,
+"height_blending": true,
+"macro_variation1": Color(0.878431, 0.862745, 0.901961, 1),
+"macro_variation2": Color(0.898039, 0.898039, 0.803922, 1),
+"macro_variation_slope": 0.333,
+"mipmap_bias": 1.0,
+"noise1_angle": 0.1,
+"noise1_offset": Vector2(0.5, 0.5),
+"noise1_scale": 0.04,
+"noise2_scale": 0.076,
+"noise3_scale": 0.225,
+"noise_texture": SubResource("NoiseTexture2D_bov7h"),
+"projection_angular_division": 1.436,
+"projection_threshold": 0.8,
+"tri_scale_reduction": 0.3
+}
+world_background = 0
+auto_shader = true
+dual_scaling = true
+
+[node name="Demo" type="Node"]
+script = ExtResource("1_po4vw")
+
+[node name="UI" parent="." instance=ExtResource("5_rc7e2")]
+
+[node name="Environment" parent="." instance=ExtResource("2_i74wg")]
+
+[node name="Borders" parent="." instance=ExtResource("3_3upu0")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 512, -2.5, 512)
+
+[node name="Tunnel" parent="." instance=ExtResource("5_2lvlr")]
+
+[node name="Player" parent="." instance=ExtResource("4_fk3ul")]
+transform = Transform3D(0.0774673, 0, -0.996995, 0, 1, 0, 0.996995, 0, 0.0774673, 229.418, 104.956, -1838.16)
+
+[node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("12_b2xl8")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 192.727, 105.171, -1787.81)
+target = NodePath("../Player")
+
+[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
+navigation_mesh = ExtResource("8_6jfdr")
+
+[node name="Terrain3D" type="Terrain3D" parent="NavigationRegion3D"]
+data_directory = "res://demo/data"
+material = SubResource("Terrain3DMaterial_s5mq5")
+assets = ExtResource("10_qmhh8")
+collision_mask = 3
+mesh_size = 64
+top_level = true
+metadata/_edit_lock_ = true
diff --git a/demo/assets/materials/M_crystal_blue.tres b/demo/assets/materials/M_crystal_blue.tres
new file mode 100644
index 0000000..6d55603
--- /dev/null
+++ b/demo/assets/materials/M_crystal_blue.tres
@@ -0,0 +1,25 @@
+[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://fkhkir6t1hml"]
+
+[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_3is54"]
+
+[resource]
+transparency = 1
+vertex_color_use_as_albedo = true
+albedo_color = Color(0.4, 1, 1, 0.647059)
+metallic_specular = 1.0
+roughness = 0.2
+roughness_texture = ExtResource("2_3is54")
+roughness_texture_channel = 3
+emission_enabled = true
+emission = Color(0, 0.145098, 0.776471, 1)
+emission_energy_multiplier = 1.4
+normal_enabled = true
+normal_scale = 0.4
+normal_texture = ExtResource("2_3is54")
+rim_enabled = true
+rim = 0.25
+clearcoat_roughness = 0.0
+ao_light_affect = 1.0
+ao_texture_channel = 3
+refraction_enabled = true
+refraction_scale = -0.1
diff --git a/demo/assets/materials/M_crystal_purple.tres b/demo/assets/materials/M_crystal_purple.tres
new file mode 100644
index 0000000..a7905c6
--- /dev/null
+++ b/demo/assets/materials/M_crystal_purple.tres
@@ -0,0 +1,24 @@
+[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://cso4f2iyuxpmc"]
+
+[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_1t610"]
+
+[resource]
+transparency = 1
+vertex_color_use_as_albedo = true
+albedo_color = Color(0.4, 0.4, 1, 0.556863)
+metallic_specular = 1.0
+roughness = 0.2
+roughness_texture = ExtResource("2_1t610")
+roughness_texture_channel = 3
+emission_enabled = true
+emission = Color(0.235294, 0.145098, 0.776471, 1)
+normal_enabled = true
+normal_scale = 0.4
+normal_texture = ExtResource("2_1t610")
+rim_enabled = true
+rim = 0.25
+clearcoat_roughness = 0.0
+ao_light_affect = 1.0
+ao_texture_channel = 3
+refraction_enabled = true
+refraction_scale = -0.1
diff --git a/demo/assets/materials/M_crystal_red.tres b/demo/assets/materials/M_crystal_red.tres
new file mode 100644
index 0000000..dd8108c
--- /dev/null
+++ b/demo/assets/materials/M_crystal_red.tres
@@ -0,0 +1,24 @@
+[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://ickkffutwcvo"]
+
+[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_k2yhv"]
+
+[resource]
+transparency = 1
+albedo_color = Color(0.670588, 0.0588235, 0.384314, 0.509804)
+metallic_specular = 1.0
+roughness = 0.2
+roughness_texture = ExtResource("2_k2yhv")
+roughness_texture_channel = 3
+emission_enabled = true
+emission = Color(0.258824, 0.0823529, 0.25098, 1)
+normal_enabled = true
+normal_scale = 0.4
+normal_texture = ExtResource("2_k2yhv")
+rim_enabled = true
+rim = 0.25
+clearcoat_roughness = 0.0
+ao_enabled = true
+ao_light_affect = 1.0
+ao_texture_channel = 3
+refraction_enabled = true
+refraction_scale = -0.1
diff --git a/demo/assets/materials/M_rock23_black_tp.tres b/demo/assets/materials/M_rock23_black_tp.tres
new file mode 100644
index 0000000..57bde2c
--- /dev/null
+++ b/demo/assets/materials/M_rock23_black_tp.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://d0hyi5n6ng25w"]
+
+[ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="1_1js8i"]
+[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_snl3x"]
+
+[resource]
+albedo_color = Color(0.501406, 0.501407, 0.501406, 1)
+albedo_texture = ExtResource("1_1js8i")
+roughness = 0.95
+roughness_texture = ExtResource("2_snl3x")
+roughness_texture_channel = 3
+normal_enabled = true
+normal_texture = ExtResource("2_snl3x")
+uv1_scale = Vector3(0.4, 0.4, 0.4)
+uv1_triplanar = true
+uv1_world_triplanar = true
+texture_filter = 5
diff --git a/demo/assets/materials/M_rock23_tp.tres b/demo/assets/materials/M_rock23_tp.tres
new file mode 100644
index 0000000..268aae1
--- /dev/null
+++ b/demo/assets/materials/M_rock23_tp.tres
@@ -0,0 +1,16 @@
+[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://nbbdrx8vma80"]
+
+[ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="1_mek0c"]
+[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_pp13c"]
+
+[resource]
+vertex_color_use_as_albedo = true
+albedo_color = Color(0.83, 0.83, 0.83, 1)
+albedo_texture = ExtResource("1_mek0c")
+normal_enabled = true
+normal_texture = ExtResource("2_pp13c")
+uv1_scale = Vector3(0.02, 0.02, 0.02)
+uv1_triplanar = true
+uv1_triplanar_sharpness = 6.06286
+uv1_world_triplanar = true
+texture_filter = 5
diff --git a/demo/assets/models/CrystalC.tscn b/demo/assets/models/CrystalC.tscn
new file mode 100644
index 0000000..ede436b
--- /dev/null
+++ b/demo/assets/models/CrystalC.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=3 uid="uid://cribhhvg03u8g"]
+
+[ext_resource type="PackedScene" uid="uid://c8cx4xjwluvxw" path="res://demo/assets/models/RockC.glb" id="1_1vww1"]
+[ext_resource type="Material" uid="uid://fkhkir6t1hml" path="res://demo/assets/materials/M_crystal_blue.tres" id="2_fclne"]
+
+[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_ycsjC"]
+data = PackedVector3Array(1.3907, -4.3898, 0.264, 0.3707, -6.5026, 0.2358, 0.9197, -5.2139, 0.5803, 1.3907, -4.3898, 0.264, 1.5973, -4.4989, -0.523, 0.3707, -6.5026, 0.2358, 2.7182, -1.9153, 1.6333, 2.6862, -0.2733, 1.344, 2.791, -2.0828, 0.6971, -0.6892, -7.3327, -0.0456, -1.2211, -6.6432, -1.3733, -1.8677, -6.7222, 0.0053, 0.1584, -6.6788, -0.712, 0.8528, -5.1659, -1.2387, -0.129, -6.0155, -1.6627, 0.3707, -6.5026, 0.2358, 0.8249, -4.8431, 1.2373, 0.9197, -5.2139, 0.5803, 0.3707, -6.5026, 0.2358, -0.1504, -6.0468, 1.6347, 0.8249, -4.8431, 1.2373, 2.7182, -1.9153, 1.6333, 2.5729, -0.0463, 2.323, 2.6862, -0.2733, 1.344, -1.5434, -2.0626, 2.9798, -0.0319, -0.907, 3.3884, -0.6296, -2.1168, 3.0274, -1.5434, -2.0626, 2.9798, -2.2391, -1.0013, 3.2378, -0.0319, -0.907, 3.3884, -3.2503, -1.9438, -0.0444, -3.1045, -0.0443, -0.8667, -2.9895, -0.0523, 0.8233, -0.5191, -2.1018, -2.9939, -2.0489, -0.9812, -3.014, -1.4897, -2.0239, -2.8933, -0.5191, -2.1018, -2.9939, 0.0444, -0.8451, -3.3989, -2.0489, -0.9812, -3.014, 3.4189, -1.5098, -2.1519, 3.3947, 0.8197, -1.419, 2.9911, 0.177, -2.5055, -0.9484, -5.3598, 2.2535, -0.8975, -3.7031, 2.6018, -0.1302, -3.9225, 2.4564, -2.8602, -5.2902, -0.0034, -3.2063, -3.9919, -0.0118, -3.0246, -4.4027, 0.8468, -0.9423, -5.2303, -2.2174, -1.6189, -3.1121, -2.5666, -2.3373, -3.6333, -2.2624, -0.9423, -5.2303, -2.2174, -0.9004, -3.6647, -2.5803, -1.6189, -3.1121, -2.5666, 2.4159, -3.3908, 0.0683, 2.6374, -3.7219, -1.0723, 1.5973, -4.4989, -0.523, 0.0817, 0.7564, 3.232, 1.3747, 1.7762, 2.618, 1.9677, 0.877, 2.8315, 0.0817, 0.7564, 3.232, 0.5064, 1.8556, 2.7003, 1.3747, 1.7762, 2.618, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 0.6919, 3.2703, 2.2188, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -2.0691, 2.5964, 1.5185, -1.2285, 2.4662, -2.4325, -1.544, 3.8438, -1.471, -2.3777, 2.1645, -2.0413, 2.3543, 3.9299, -1.7328, 1.4379, 5.2295, -1.8152, 1.1494, 2.3687, -2.734, 1.4341, 6.0081, 1.2447, 0.9325, 6.924, 0.4365, 2.3528, 6.0999, 0.4838, -1.8677, -6.7222, 0.0053, -2.3434, -5.4566, 1.3758, -1.2352, -6.7568, 1.4079, 3.0694, 0.3768, 0.2917, 3.6836, 2.3906, 0.1427, 3.39, -0.1345, -0.5433, 1.5686, 6.857, -0.4389, 0.7824, 7.1818, -0.4789, 0.9515, 6.8097, -1.0683, 2.4165, 5.7335, -0.6508, 1.4379, 5.2295, -1.8152, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 1.1494, 2.3687, -2.734, 0.0399, 0.8971, -3.3453, -0.1803, 5.4522, -1.6664, -0.2609, 6.3493, -0.8464, -1.544, 3.8438, -1.471, -3.1045, -0.0443, -0.8667, -2.9635, 2.0631, -0.0469, -2.9895, -0.0523, 0.8233, -2.8218, -0.0908, -2.3316, -2.8949, 1.7465, -1.4628, -3.1045, -0.0443, -0.8667, -1.137, 5.0901, -0.2048, -0.2548, 6.3653, 0.3115, -0.7059, 4.7856, 0.8682, -1.8973, 4.0438, -0.0231, -1.6818, 3.3981, 1.0198, -2.3926, 3.3478, 0.3255, -2.9895, -0.0523, 0.8233, -2.7222, 1.5834, 1.3251, -2.9289, -0.1232, 2.5316, -0.7059, 4.7856, 0.8682, -0.2548, 6.3653, 0.3115, 0.4487, 4.8708, 1.6514, -0.7059, 4.7856, 0.8682, 0.4487, 4.8708, 1.6514, -0.2637, 3.5029, 1.8153, 3.3947, 0.8197, -1.419, 3.6143, 2.6541, -0.7787, 3.0814, 2.2747, -1.5878, 3.3017, 4.3689, -0.5397, 2.4165, 5.7335, -0.6508, 2.3543, 3.9299, -1.7328, -2.1314, 0.7603, 3.0046, -1.0522, 1.986, 2.2721, 0.0817, 0.7564, 3.232, 0.0399, 0.8971, -3.3453, 1.1494, 2.3687, -2.734, -1.2285, 2.4662, -2.4325, 0.549, 3.3793, -2.5003, 0.4374, 4.5707, -2.224, -0.1459, 3.4745, -2.3857, 1.4341, 6.0081, 1.2447, 2.3528, 6.0999, 0.4838, 2.8818, 4.7444, 1.3483, -3.1045, -0.0443, -0.8667, -2.8949, 1.7465, -1.4628, -2.9635, 2.0631, -0.0469, -2.7186, 2.9035, -1.3821, -2.3162, 3.6228, -0.9721, -2.7435, 3.293, -0.5137, -0.1803, 5.4522, -1.6664, -1.544, 3.8438, -1.471, -1.2285, 2.4662, -2.4325, -2.1314, 0.7603, 3.0046, -2.0691, 2.5964, 1.5185, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -1.6818, 3.3981, 1.0198, -1.0522, 1.986, 2.2721, 0.0399, 0.8971, -3.3453, -1.2285, 2.4662, -2.4325, -2.0395, 0.8474, -2.9039, 1.9677, 0.877, 2.8315, 1.5121, 2.8416, 2.3106, 2.2766, 3.3307, 2.1367, 1.9677, 0.877, 2.8315, 1.3747, 1.7762, 2.618, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 2.2766, 3.3307, 2.1367, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 0.4487, 4.8708, 1.6514, 2.2766, 3.3307, 2.1367, 3.3947, 0.8197, -1.419, 3.0814, 2.2747, -1.5878, 2.9911, 0.177, -2.5055, 2.8818, 4.7444, 1.3483, 2.3528, 6.0999, 0.4838, 3.481, 4.5391, 0.4718, 2.0681, 1.0413, -2.9849, 2.3543, 3.9299, -1.7328, 1.1494, 2.3687, -2.734, 2.5729, -0.0463, 2.323, 2.8449, 1.8063, 1.8788, 2.6862, -0.2733, 1.344, -1.4897, -2.0239, -2.8933, -2.3373, -3.6333, -2.2624, -1.6189, -3.1121, -2.5666, -1.4897, -2.0239, -2.8933, -2.0489, -0.9812, -3.014, -2.3373, -3.6333, -2.2624, -2.0395, 0.8474, -2.9039, -1.2285, 2.4662, -2.4325, -2.3777, 2.1645, -2.0413, 2.1308, -0.8122, -3.0291, 0.0444, -0.8451, -3.3989, 1.2123, -2.2074, -2.6786, -3.2503, -1.9438, -0.0444, -2.9895, -0.0523, 0.8233, -3.048, -2.4163, 1.5053, -2.9895, -0.0523, 0.8233, -2.9635, 2.0631, -0.0469, -2.7222, 1.5834, 1.3251, -2.8218, -0.0908, -2.3316, -3.1045, -0.0443, -0.8667, -3.1353, -2.4738, -1.533, -0.6296, -2.1168, 3.0274, -0.0319, -0.907, 3.3884, 0.6607, -2.1929, 2.7904, 0.0817, 0.7564, 3.232, -1.0522, 1.986, 2.2721, 0.5064, 1.8556, 2.7003, 3.0694, 0.3768, 0.2917, 3.39, -0.1345, -0.5433, 3.0799, -1.877, -0.2485, 2.332, -3.2513, 1.4491, 1.8096, -3.8731, 0.517, 1.7532, -3.8773, 1.1862, 2.332, -3.2513, 1.4491, 2.4159, -3.3908, 0.0683, 1.8096, -3.8731, 0.517, 3.0694, 0.3768, 0.2917, 3.2927, 2.696, 1.0915, 3.6836, 2.3906, 0.1427, 0.8528, -5.1659, -1.2387, 0.5989, -4.5033, -1.836, -0.129, -6.0155, -1.6627, 1.6723, -4.1113, -1.5062, 2.2303, -3.7001, -1.8804, 1.5835, -3.611, -1.9401, 2.6485, -2.4319, -2.56, 2.1308, -0.8122, -3.0291, 1.2123, -2.2074, -2.6786, -1.2211, -6.6432, -1.3733, -2.3738, -5.4707, -1.3892, -1.8677, -6.7222, 0.0053, 1.2123, -2.2074, -2.6786, 0.0444, -0.8451, -3.3989, -0.5191, -2.1018, -2.9939, -0.9423, -5.2303, -2.2174, -0.0313, -3.8492, -2.3572, -0.9004, -3.6647, -2.5803, -2.9895, -0.0523, 0.8233, -2.9289, -0.1232, 2.5316, -3.048, -2.4163, 1.5053, -3.1353, -2.4738, -1.533, -3.1045, -0.0443, -0.8667, -3.2503, -1.9438, -0.0444, -2.8602, -5.2902, -0.0034, -3.0733, -4.4498, -0.8737, -3.2063, -3.9919, -0.0118, -0.1504, -6.0468, 1.6347, 0.6052, -4.4095, 1.8566, 0.8249, -4.8431, 1.2373, -2.3213, -3.5933, 2.2613, -1.5434, -2.0626, 2.9798, -1.6095, -3.1051, 2.5592, -2.3213, -3.5933, 2.2613, -2.2391, -1.0013, 3.2378, -1.5434, -2.0626, 2.9798, 3.0799, -1.877, -0.2485, 3.39, -0.1345, -0.5433, 3.4106, -2.2767, -1.2285, -0.0319, -0.907, 3.3884, 1.8364, -0.871, 2.91, 0.6607, -2.1929, 2.7904, 1.7532, -3.8773, 1.1862, 1.5121, -3.4321, 1.801, 2.332, -3.2513, 1.4491, -0.9484, -5.3598, 2.2535, -1.6095, -3.1051, 2.5592, -0.8975, -3.7031, 2.6018, -0.9484, -5.3598, 2.2535, -2.3213, -3.5933, 2.2613, -1.6095, -3.1051, 2.5592, 2.4159, -3.3908, 0.0683, 1.3907, -4.3898, 0.264, 1.8096, -3.8731, 0.517, 2.4159, -3.3908, 0.0683, 1.5973, -4.4989, -0.523, 1.3907, -4.3898, 0.264, 0.6607, -2.1929, 2.7904, 1.8364, -0.871, 2.91, 2.1556, -2.2057, 2.3132, -0.6892, -7.3327, -0.0456, 0.3707, -6.5026, 0.2358, 0.1584, -6.6788, -0.712, 2.7182, -1.9153, 1.6333, 2.332, -3.2513, 1.4491, 2.1556, -2.2057, 2.3132, -0.8975, -3.7031, 2.6018, -1.5434, -2.0626, 2.9798, -0.6296, -2.1168, 3.0274, -0.8975, -3.7031, 2.6018, -1.6095, -3.1051, 2.5592, -1.5434, -2.0626, 2.9798, -0.5191, -2.1018, -2.9939, -1.6189, -3.1121, -2.5666, -0.9004, -3.6647, -2.5803, -0.5191, -2.1018, -2.9939, -1.4897, -2.0239, -2.8933, -1.6189, -3.1121, -2.5666, 3.4106, -2.2767, -1.2285, 2.2303, -3.7001, -1.8804, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 3.4189, -1.5098, -2.1519, 2.6485, -2.4319, -2.56, 2.2303, -3.7001, -1.8804, 3.4106, -2.2767, -1.2285, 3.4189, -1.5098, -2.1519, 0.6919, 3.2703, 2.2188, 1.3747, 1.7762, 2.618, 0.5064, 1.8556, 2.7003, 0.6919, 3.2703, 2.2188, 1.5121, 2.8416, 2.3106, 1.3747, 1.7762, 2.618, -2.7186, 2.9035, -1.3821, -2.8949, 1.7465, -1.4628, -2.3777, 2.1645, -2.0413, 3.6143, 2.6541, -0.7787, 3.481, 4.5391, 0.4718, 3.3017, 4.3689, -0.5397, 3.6143, 2.6541, -0.7787, 3.6836, 2.3906, 0.1427, 3.481, 4.5391, 0.4718, 0.7824, 7.1818, -0.4789, -0.2548, 6.3653, 0.3115, -0.2609, 6.3493, -0.8464, 0.7824, 7.1818, -0.4789, 0.9325, 6.924, 0.4365, -0.2548, 6.3653, 0.3115, -0.1504, -6.0468, 1.6347, -1.2352, -6.7568, 1.4079, -0.9484, -5.3598, 2.2535, 1.8096, -3.8731, 0.517, 0.8249, -4.8431, 1.2373, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 1.3907, -4.3898, 0.264, 0.9197, -5.2139, 0.5803, 0.8249, -4.8431, 1.2373, 1.8096, -3.8731, 0.517, 1.3907, -4.3898, 0.264, -0.1302, -3.9225, 2.4564, 1.5121, -3.4321, 1.801, 0.6052, -4.4095, 1.8566, -0.1302, -3.9225, 2.4564, 0.6607, -2.1929, 2.7904, 1.5121, -3.4321, 1.801, 2.4159, -3.3908, 0.0683, 2.791, -2.0828, 0.6971, 3.0799, -1.877, -0.2485, 1.5973, -4.4989, -0.523, 1.6723, -4.1113, -1.5062, 0.8528, -5.1659, -1.2387, -3.0246, -4.4027, 0.8468, -2.3213, -3.5933, 2.2613, -2.3434, -5.4566, 1.3758, -3.0246, -4.4027, 0.8468, -3.048, -2.4163, 1.5053, -2.3213, -3.5933, 2.2613, -1.2211, -6.6432, -1.3733, -0.129, -6.0155, -1.6627, -0.9423, -5.2303, -2.2174, -3.0733, -4.4498, -0.8737, -2.3373, -3.6333, -2.2624, -3.1353, -2.4738, -1.533, -3.0733, -4.4498, -0.8737, -2.3738, -5.4707, -1.3892, -2.3373, -3.6333, -2.2624, 1.5835, -3.611, -1.9401, -0.0313, -3.8492, -2.3572, 0.5989, -4.5033, -1.836, 1.5835, -3.611, -1.9401, 1.2123, -2.2074, -2.6786, -0.0313, -3.8492, -2.3572, 2.5729, -0.0463, 2.323, 1.8364, -0.871, 2.91, 1.9677, 0.877, 2.8315, -2.9289, -0.1232, 2.5316, -2.1314, 0.7603, 3.0046, -2.2391, -1.0013, 3.2378, -2.0395, 0.8474, -2.9039, -2.8218, -0.0908, -2.3316, -2.0489, -0.9812, -3.014, 2.1308, -0.8122, -3.0291, 2.9911, 0.177, -2.5055, 2.0681, 1.0413, -2.9849, 3.2927, 2.696, 1.0915, 2.2766, 3.3307, 2.1367, 2.8818, 4.7444, 1.3483, 3.2927, 2.696, 1.0915, 2.8449, 1.8063, 1.8788, 2.2766, 3.3307, 2.1367, -2.9635, 2.0631, -0.0469, -2.7435, 3.293, -0.5137, -2.3926, 3.3478, 0.3255, 2.3528, 6.0999, 0.4838, 1.5686, 6.857, -0.4389, 2.4165, 5.7335, -0.6508, -2.3162, 3.6228, -0.9721, -1.137, 5.0901, -0.2048, -1.8973, 4.0438, -0.0231, -2.3162, 3.6228, -0.9721, -1.544, 3.8438, -1.471, -1.137, 5.0901, -0.2048, 0.4374, 4.5707, -2.224, 0.9515, 6.8097, -1.0683, -0.1803, 5.4522, -1.6664, 0.4374, 4.5707, -2.224, 1.4379, 5.2295, -1.8152, 0.9515, 6.8097, -1.0683, -1.2352, -6.7568, 1.4079, 0.3707, -6.5026, 0.2358, -0.6892, -7.3327, -0.0456, -1.2352, -6.7568, 1.4079, -0.1504, -6.0468, 1.6347, 0.3707, -6.5026, 0.2358, 0.6607, -2.1929, 2.7904, -0.8975, -3.7031, 2.6018, -0.6296, -2.1168, 3.0274, 0.6607, -2.1929, 2.7904, -0.1302, -3.9225, 2.4564, -0.8975, -3.7031, 2.6018, 2.791, -2.0828, 0.6971, 2.332, -3.2513, 1.4491, 2.7182, -1.9153, 1.6333, 2.791, -2.0828, 0.6971, 2.4159, -3.3908, 0.0683, 2.332, -3.2513, 1.4491, 1.6723, -4.1113, -1.5062, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 1.6723, -4.1113, -1.5062, 1.5973, -4.4989, -0.523, 2.6374, -3.7219, -1.0723, -3.048, -2.4163, 1.5053, -3.2063, -3.9919, -0.0118, -3.2503, -1.9438, -0.0444, -3.048, -2.4163, 1.5053, -3.0246, -4.4027, 0.8468, -3.2063, -3.9919, -0.0118, -0.129, -6.0155, -1.6627, -0.6892, -7.3327, -0.0456, 0.1584, -6.6788, -0.712, -0.129, -6.0155, -1.6627, -1.2211, -6.6432, -1.3733, -0.6892, -7.3327, -0.0456, 1.2123, -2.2074, -2.6786, 2.2303, -3.7001, -1.8804, 2.6485, -2.4319, -2.56, 1.2123, -2.2074, -2.6786, 1.5835, -3.611, -1.9401, 2.2303, -3.7001, -1.8804, 3.3947, 0.8197, -1.419, 3.6836, 2.3906, 0.1427, 3.6143, 2.6541, -0.7787, 3.3947, 0.8197, -1.419, 3.39, -0.1345, -0.5433, 3.6836, 2.3906, 0.1427, -2.1314, 0.7603, 3.0046, -2.7222, 1.5834, 1.3251, -2.0691, 2.5964, 1.5185, -2.1314, 0.7603, 3.0046, -2.9289, -0.1232, 2.5316, -2.7222, 1.5834, 1.3251, 2.9911, 0.177, -2.5055, 2.6485, -2.4319, -2.56, 3.4189, -1.5098, -2.1519, 2.9911, 0.177, -2.5055, 2.1308, -0.8122, -3.0291, 2.6485, -2.4319, -2.56, -2.7435, 3.293, -0.5137, -2.8949, 1.7465, -1.4628, -2.7186, 2.9035, -1.3821, -2.7435, 3.293, -0.5137, -2.9635, 2.0631, -0.0469, -2.8949, 1.7465, -1.4628, -0.1459, 3.4745, -2.3857, 1.1494, 2.3687, -2.734, 0.549, 3.3793, -2.5003, -0.1459, 3.4745, -2.3857, -1.2285, 2.4662, -2.4325, 1.1494, 2.3687, -2.734, 2.3543, 3.9299, -1.7328, 3.6143, 2.6541, -0.7787, 3.3017, 4.3689, -0.5397, 2.3543, 3.9299, -1.7328, 3.0814, 2.2747, -1.5878, 3.6143, 2.6541, -0.7787, 1.5686, 6.857, -0.4389, 0.9325, 6.924, 0.4365, 0.7824, 7.1818, -0.4789, 1.5686, 6.857, -0.4389, 2.3528, 6.0999, 0.4838, 0.9325, 6.924, 0.4365, -1.544, 3.8438, -1.471, -2.7186, 2.9035, -1.3821, -2.3777, 2.1645, -2.0413, -1.544, 3.8438, -1.471, -2.3162, 3.6228, -0.9721, -2.7186, 2.9035, -1.3821, 1.4379, 5.2295, -1.8152, 0.549, 3.3793, -2.5003, 1.1494, 2.3687, -2.734, 1.4379, 5.2295, -1.8152, 0.4374, 4.5707, -2.224, 0.549, 3.3793, -2.5003, 2.1556, -2.2057, 2.3132, 1.5121, -3.4321, 1.801, 0.6607, -2.1929, 2.7904, 2.1556, -2.2057, 2.3132, 2.332, -3.2513, 1.4491, 1.5121, -3.4321, 1.801, 3.4106, -2.2767, -1.2285, 2.4159, -3.3908, 0.0683, 3.0799, -1.877, -0.2485, 3.4106, -2.2767, -1.2285, 2.6374, -3.7219, -1.0723, 2.4159, -3.3908, 0.0683, 0.1584, -6.6788, -0.712, 1.5973, -4.4989, -0.523, 0.8528, -5.1659, -1.2387, 0.1584, -6.6788, -0.712, 0.3707, -6.5026, 0.2358, 1.5973, -4.4989, -0.523, -3.2503, -1.9438, -0.0444, -3.0733, -4.4498, -0.8737, -3.1353, -2.4738, -1.533, -3.2503, -1.9438, -0.0444, -3.2063, -3.9919, -0.0118, -3.0733, -4.4498, -0.8737, -0.5191, -2.1018, -2.9939, -0.0313, -3.8492, -2.3572, 1.2123, -2.2074, -2.6786, -0.5191, -2.1018, -2.9939, -0.9004, -3.6647, -2.5803, -0.0313, -3.8492, -2.3572, 3.4189, -1.5098, -2.1519, 3.39, -0.1345, -0.5433, 3.3947, 0.8197, -1.419, 3.4189, -1.5098, -2.1519, 3.4106, -2.2767, -1.2285, 3.39, -0.1345, -0.5433, 2.7182, -1.9153, 1.6333, 1.8364, -0.871, 2.91, 2.5729, -0.0463, 2.323, 2.7182, -1.9153, 1.6333, 2.1556, -2.2057, 2.3132, 1.8364, -0.871, 2.91, -2.3777, 2.1645, -2.0413, -2.8218, -0.0908, -2.3316, -2.0395, 0.8474, -2.9039, -2.3777, 2.1645, -2.0413, -2.8949, 1.7465, -1.4628, -2.8218, -0.0908, -2.3316, 3.481, 4.5391, 0.4718, 3.2927, 2.696, 1.0915, 2.8818, 4.7444, 1.3483, 3.481, 4.5391, 0.4718, 3.6836, 2.3906, 0.1427, 3.2927, 2.696, 1.0915, 0.6919, 3.2703, 2.2188, -1.0522, 1.986, 2.2721, -0.2637, 3.5029, 1.8153, 0.6919, 3.2703, 2.2188, 0.5064, 1.8556, 2.7003, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -2.9635, 2.0631, -0.0469, -2.3926, 3.3478, 0.3255, -2.0691, 2.5964, 1.5185, -2.7222, 1.5834, 1.3251, -2.9635, 2.0631, -0.0469, -0.2548, 6.3653, 0.3115, 1.4341, 6.0081, 1.2447, 0.4487, 4.8708, 1.6514, -0.2548, 6.3653, 0.3115, 0.9325, 6.924, 0.4365, 1.4341, 6.0081, 1.2447, 3.3017, 4.3689, -0.5397, 2.3528, 6.0999, 0.4838, 2.4165, 5.7335, -0.6508, 3.3017, 4.3689, -0.5397, 3.481, 4.5391, 0.4718, 2.3528, 6.0999, 0.4838, -0.2609, 6.3493, -0.8464, -1.137, 5.0901, -0.2048, -1.544, 3.8438, -1.471, -0.2609, 6.3493, -0.8464, -0.2548, 6.3653, 0.3115, -1.137, 5.0901, -0.2048, 0.7824, 7.1818, -0.4789, -0.1803, 5.4522, -1.6664, 0.9515, 6.8097, -1.0683, 0.7824, 7.1818, -0.4789, -0.2609, 6.3493, -0.8464, -0.1803, 5.4522, -1.6664, 2.4165, 5.7335, -0.6508, 0.9515, 6.8097, -1.0683, 1.4379, 5.2295, -1.8152, 2.4165, 5.7335, -0.6508, 1.5686, 6.857, -0.4389, 0.9515, 6.8097, -1.0683, 0.4374, 4.5707, -2.224, -1.2285, 2.4662, -2.4325, -0.1459, 3.4745, -2.3857, 0.4374, 4.5707, -2.224, -0.1803, 5.4522, -1.6664, -1.2285, 2.4662, -2.4325, -1.8973, 4.0438, -0.0231, -0.7059, 4.7856, 0.8682, -1.6818, 3.3981, 1.0198, -1.8973, 4.0438, -0.0231, -1.137, 5.0901, -0.2048, -0.7059, 4.7856, 0.8682, -2.3162, 3.6228, -0.9721, -2.3926, 3.3478, 0.3255, -2.7435, 3.293, -0.5137, -2.3162, 3.6228, -0.9721, -1.8973, 4.0438, -0.0231, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -0.2637, 3.5029, 1.8153, -1.0522, 1.986, 2.2721, -1.6818, 3.3981, 1.0198, -0.7059, 4.7856, 0.8682, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 2.8818, 4.7444, 1.3483, 2.2766, 3.3307, 2.1367, 0.4487, 4.8708, 1.6514, 1.4341, 6.0081, 1.2447, 2.8818, 4.7444, 1.3483, 3.0814, 2.2747, -1.5878, 2.0681, 1.0413, -2.9849, 2.9911, 0.177, -2.5055, 3.0814, 2.2747, -1.5878, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 0.0444, -0.8451, -3.3989, -2.0395, 0.8474, -2.9039, -2.0489, -0.9812, -3.014, 0.0444, -0.8451, -3.3989, 0.0399, 0.8971, -3.3453, -2.0395, 0.8474, -2.9039, -2.2391, -1.0013, 3.2378, 0.0817, 0.7564, 3.232, -0.0319, -0.907, 3.3884, -2.2391, -1.0013, 3.2378, -2.1314, 0.7603, 3.0046, 0.0817, 0.7564, 3.232, 2.5729, -0.0463, 2.323, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 2.5729, -0.0463, 2.323, 1.9677, 0.877, 2.8315, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 3.0694, 0.3768, 0.2917, 2.6862, -0.2733, 1.344, 2.8449, 1.8063, 1.8788, 3.2927, 2.696, 1.0915, 3.0694, 0.3768, 0.2917, 2.1308, -0.8122, -3.0291, 0.0399, 0.8971, -3.3453, 0.0444, -0.8451, -3.3989, 2.1308, -0.8122, -3.0291, 2.0681, 1.0413, -2.9849, 0.0399, 0.8971, -3.3453, -2.0489, -0.9812, -3.014, -3.1353, -2.4738, -1.533, -2.3373, -3.6333, -2.2624, -2.0489, -0.9812, -3.014, -2.8218, -0.0908, -2.3316, -3.1353, -2.4738, -1.533, -2.9289, -0.1232, 2.5316, -2.3213, -3.5933, 2.2613, -3.048, -2.4163, 1.5053, -2.9289, -0.1232, 2.5316, -2.2391, -1.0013, 3.2378, -2.3213, -3.5933, 2.2613, -0.0319, -0.907, 3.3884, 1.9677, 0.877, 2.8315, 1.8364, -0.871, 2.91, -0.0319, -0.907, 3.3884, 0.0817, 0.7564, 3.232, 1.9677, 0.877, 2.8315, 2.6862, -0.2733, 1.344, 3.0799, -1.877, -0.2485, 2.791, -2.0828, 0.6971, 2.6862, -0.2733, 1.344, 3.0694, 0.3768, 0.2917, 3.0799, -1.877, -0.2485, 0.8528, -5.1659, -1.2387, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, 0.8528, -5.1659, -1.2387, 1.6723, -4.1113, -1.5062, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, -0.9423, -5.2303, -2.2174, -0.129, -6.0155, -1.6627, 0.5989, -4.5033, -1.836, -0.0313, -3.8492, -2.3572, -0.9423, -5.2303, -2.2174, -1.2211, -6.6432, -1.3733, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -1.2211, -6.6432, -1.3733, -0.9423, -5.2303, -2.2174, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -2.3738, -5.4707, -1.3892, -3.0733, -4.4498, -0.8737, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -1.8677, -6.7222, 0.0053, -2.8602, -5.2902, -0.0034, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -0.9484, -5.3598, 2.2535, -1.2352, -6.7568, 1.4079, -2.3434, -5.4566, 1.3758, -2.3213, -3.5933, 2.2613, -0.9484, -5.3598, 2.2535, -0.1504, -6.0468, 1.6347, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, -0.1504, -6.0468, 1.6347, -0.9484, -5.3598, 2.2535, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 0.6052, -4.4095, 1.8566, 1.5121, -3.4321, 1.801, 1.7532, -3.8773, 1.1862, -0.6892, -7.3327, -0.0456, -1.8677, -6.7222, 0.0053, -1.2352, -6.7568, 1.4079)
+
+[node name="CrystalC" instance=ExtResource("1_1vww1")]
+collision_mask = 3
+
+[node name="Rock3" parent="." index="0"]
+surface_material_override/0 = ExtResource("2_fclne")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"]
+shape = SubResource("ConcavePolygonShape3D_ycsjC")
diff --git a/demo/assets/models/LODExample.tscn b/demo/assets/models/LODExample.tscn
new file mode 100644
index 0000000..31d95ee
--- /dev/null
+++ b/demo/assets/models/LODExample.tscn
@@ -0,0 +1,56 @@
+[gd_scene load_steps=9 format=3 uid="uid://bn5nf4esciwex"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_788j8"]
+albedo_color = Color(1, 0, 0, 1)
+
+[sub_resource type="SphereMesh" id="SphereMesh_u4ac2"]
+material = SubResource("StandardMaterial3D_788j8")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3i52q"]
+albedo_color = Color(1, 0.540167, 0.11, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_xyuxq"]
+material = SubResource("StandardMaterial3D_3i52q")
+size = Vector3(0.85, 0.85, 0.85)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p0ha4"]
+albedo_color = Color(0.48, 1, 0.497333, 1)
+
+[sub_resource type="PrismMesh" id="PrismMesh_xnm4h"]
+material = SubResource("StandardMaterial3D_p0ha4")
+size = Vector3(1, 1, 0.855)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_4koei"]
+albedo_color = Color(0.45, 0.596667, 1, 1)
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_dp7xj"]
+material = SubResource("StandardMaterial3D_4koei")
+top_radius = 0.0
+bottom_radius = 0.34
+height = 1.54
+radial_segments = 4
+
+[node name="TestMultimesh" type="Node3D"]
+
+[node name="Node3D" type="Node3D" parent="."]
+
+[node name="MeshInstance3D1" type="MeshInstance3D" parent="Node3D"]
+mesh = SubResource("SphereMesh_u4ac2")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Node3D"]
+visible = false
+mesh = SubResource("BoxMesh_xyuxq")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.118133, 0)
+visible = false
+mesh = SubResource("PrismMesh_xnm4h")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D4" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
+visible = false
+mesh = SubResource("CylinderMesh_dp7xj")
+skeleton = NodePath("../..")
diff --git a/demo/assets/models/LODExample10.tscn b/demo/assets/models/LODExample10.tscn
new file mode 100644
index 0000000..1a6e4c3
--- /dev/null
+++ b/demo/assets/models/LODExample10.tscn
@@ -0,0 +1,139 @@
+[gd_scene load_steps=21 format=3 uid="uid://dqwgv1ahsvqio"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_788j8"]
+albedo_color = Color(1, 0, 0, 1)
+
+[sub_resource type="SphereMesh" id="SphereMesh_u4ac2"]
+material = SubResource("StandardMaterial3D_788j8")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0qa1y"]
+albedo_color = Color(1, 0.907, 0.38, 1)
+
+[sub_resource type="TorusMesh" id="TorusMesh_tjrnq"]
+material = SubResource("StandardMaterial3D_0qa1y")
+inner_radius = 0.141
+outer_radius = 0.601
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3i52q"]
+albedo_color = Color(1, 0.540167, 0.11, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_xyuxq"]
+material = SubResource("StandardMaterial3D_3i52q")
+size = Vector3(0.85, 0.85, 0.85)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p0ha4"]
+albedo_color = Color(0.48, 1, 0.497333, 1)
+
+[sub_resource type="PrismMesh" id="PrismMesh_xnm4h"]
+material = SubResource("StandardMaterial3D_p0ha4")
+size = Vector3(1, 1, 0.855)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jv6fb"]
+albedo_color = Color(0.3384, 0.538933, 0.94, 1)
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_i1yhp"]
+material = SubResource("StandardMaterial3D_jv6fb")
+top_radius = 0.0
+bottom_radius = 0.59
+height = 1.08
+radial_segments = 4
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0qk4s"]
+albedo_color = Color(0.62325, 0.495, 0.9, 1)
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_bvmel"]
+material = SubResource("StandardMaterial3D_0qk4s")
+top_radius = 0.765
+bottom_radius = 0.34
+height = 1.54
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_438mn"]
+albedo_color = Color(0.933333, 0.2, 1, 1)
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_x8oip"]
+material = SubResource("StandardMaterial3D_438mn")
+top_radius = 0.33
+bottom_radius = 0.34
+height = 1.0
+radial_segments = 7
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hqr78"]
+albedo_color = Color(0.51, 0.456705, 0.3417, 1)
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_dp7xj"]
+material = SubResource("StandardMaterial3D_hqr78")
+top_radius = 0.29
+bottom_radius = 0.59
+height = 0.65
+radial_segments = 4
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_aba40"]
+albedo_color = Color(0.1512, 0.36, 0.17208, 1)
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_8ctts"]
+material = SubResource("StandardMaterial3D_aba40")
+top_radius = 0.45
+bottom_radius = 0.495
+height = 1.54
+radial_segments = 4
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tl8dq"]
+albedo_color = Color(0.2279, 0.36888, 0.53, 1)
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_w2aj8"]
+material = SubResource("StandardMaterial3D_tl8dq")
+top_radius = 0.0
+bottom_radius = 0.77
+height = 1.54
+radial_segments = 4
+
+[node name="TestMultimesh" type="Node3D"]
+
+[node name="Node3D" type="Node3D" parent="."]
+
+[node name="MeshInstance3D0" type="MeshInstance3D" parent="Node3D"]
+mesh = SubResource("SphereMesh_u4ac2")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D1" type="MeshInstance3D" parent="Node3D"]
+mesh = SubResource("TorusMesh_tjrnq")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Node3D"]
+mesh = SubResource("BoxMesh_xyuxq")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.118133, 0)
+mesh = SubResource("PrismMesh_xnm4h")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D4" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
+mesh = SubResource("CylinderMesh_i1yhp")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D5" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
+mesh = SubResource("CylinderMesh_bvmel")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D6" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
+mesh = SubResource("CylinderMesh_x8oip")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D7" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
+mesh = SubResource("CylinderMesh_dp7xj")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D8" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
+mesh = SubResource("CylinderMesh_8ctts")
+skeleton = NodePath("../..")
+
+[node name="MeshInstance3D9" type="MeshInstance3D" parent="Node3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
+mesh = SubResource("CylinderMesh_w2aj8")
+skeleton = NodePath("../..")
diff --git a/demo/assets/models/RockA.glb b/demo/assets/models/RockA.glb
new file mode 100644
index 0000000..7ee3b6e
Binary files /dev/null and b/demo/assets/models/RockA.glb differ
diff --git a/demo/assets/models/RockA.glb.import b/demo/assets/models/RockA.glb.import
new file mode 100644
index 0000000..b54636b
--- /dev/null
+++ b/demo/assets/models/RockA.glb.import
@@ -0,0 +1,44 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://bxwiqxgwoh630"
+path="res://.godot/imported/RockA.glb-2e416adf16cd89c5c5afc8e204027147.scn"
+
+[deps]
+
+source_file="res://demo/assets/models/RockA.glb"
+dest_files=["res://.godot/imported/RockA.glb-2e416adf16cd89c5c5afc8e204027147.scn"]
+
+[params]
+
+nodes/root_type="StaticBody3D"
+nodes/root_name="Scene Root"
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+_subresources={
+"materials": {
+"@MATERIAL:0": {
+"use_external/enabled": true,
+"use_external/path": "res://demo/assets/materials/M_rock30.tres"
+}
+}
+}
+gltf/naming_version=0
+gltf/embedded_image_handling=1
diff --git a/demo/assets/models/RockA.tscn b/demo/assets/models/RockA.tscn
new file mode 100644
index 0000000..0beb068
--- /dev/null
+++ b/demo/assets/models/RockA.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=4 format=3 uid="uid://be6nrf0b8j4l0"]
+
+[ext_resource type="PackedScene" uid="uid://bxwiqxgwoh630" path="res://demo/assets/models/RockA.glb" id="1_nu743"]
+[ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_pmd5x"]
+
+[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_2s34t"]
+data = PackedVector3Array(0.8507, -2.8222, -2.0706, 0.5747, -2.1329, -3.01, 0.0002, -3.2065, -2.7359, 2.2873, -1.7649, -0.5811, 2.4378, -0.9615, -0.323, 2.038, -0.9707, -1.2311, -0.6382, -2.68, -2.9376, -1.1067, -1.2731, -2.7048, -1.5545, -2.3633, -2.567, -0.6382, -2.68, -2.9376, -0.2162, -1.825, -3.2439, -1.1067, -1.2731, -2.7048, 0.0002, -3.2065, -2.7359, 0.0411, -3.76, -1.9518, 0.8507, -2.8222, -2.0706, 2.425, -2.0284, 0.49, 2.4024, -1.5988, 1.5279, 2.5851, -1.2056, 0.7305, 0.0831, -2.9699, 1.939, -0.0859, -2.2445, 2.6136, 0.748, -2.001, 2.5127, -2.448, -1.0454, -1.0528, -2.5272, 0.3979, -0.8764, -2.399, -0.7585, 0.2313, -1.5104, 0.6775, -2.512, -1.3227, 1.7291, -2.1514, -2.0387, 1.5996, -2.2267, 0.1427, 2.3057, -2.4478, -0.4979, 2.1368, -2.1727, -0.4143, 1.2902, -3.2951, 0.1351, -3.9012, -0.5924, 0.2299, -3.4688, 0.9248, 0.5827, -3.159, 0.0985, -1.6255, -3.1162, -1.4188, -2.0226, -2.1644, -0.379, -1.2838, -3.0035, 0.2928, -1.1067, -1.2731, -2.7048, -1.5104, 0.6775, -2.512, -1.9542, -0.4605, -2.5215, 0.748, -2.001, 2.5127, 1.4775, -1.3103, 2.8259, 1.9116, -1.7142, 2.341, 1.8789, 1.1592, -0.8303, 1.3001, 2.6786, -0.7585, 0.8831, 2.5385, -1.8483, 0.5951, -0.2972, 3.54, 1.1272, 0.2617, 2.9365, 1.3352, -0.2905, 3.0366, 0.5951, -0.2972, 3.54, 0.7324, 0.4498, 3.1493, 1.1272, 0.2617, 2.9365, -1.3976, 0.6213, 1.8313, -0.8149, 0.6856, 2.232, -1.0617, -0.1771, 2.0331, -0.7449, 2.9351, -0.0395, -0.5981, 2.6867, 0.9116, -1.33, 2.7367, 0.359, 0.9156, 3.0784, 0.1405, 0.1518, 3.0337, 0.6723, 0.0726, 3.4231, -0.5764, 0.7064, 1.456, 2.7883, 1.3708, 1.9744, 2.321, 1.1174, 1.3846, 2.616, 0.7064, 1.456, 2.7883, 0.5198, 2.1281, 2.596, 1.3708, 1.9744, 2.321, -1.6255, -3.1162, -1.4188, -1.2838, -3.0035, 0.2928, -0.6766, -3.9798, -0.6577, 2.4717, 0.9465, 0.6202, 2.4515, -0.0586, -0.1403, 2.6117, 0.0377, 0.454, 2.4515, -0.0586, -0.1403, 2.1036, 1.6103, 0.0759, 1.8789, 1.1592, -0.8303, 2.4515, -0.0586, -0.1403, 2.4717, 0.9465, 0.6202, 2.1036, 1.6103, 0.0759, 1.0451, 2.7721, 1.514, 0.2361, 2.7435, 1.8554, 0.1518, 3.0337, 0.6723, 1.0451, 2.7721, 1.514, 0.1518, 3.0337, 0.6723, 0.9156, 3.0784, 0.1405, 0.1427, 2.3057, -2.4478, -0.1394, 3.1268, -1.5759, -0.4979, 2.1368, -2.1727, 0.1518, 3.0337, 0.6723, 0.2361, 2.7435, 1.8554, -0.5981, 2.6867, 0.9116, 0.1518, 3.0337, 0.6723, -0.5981, 2.6867, 0.9116, -0.7449, 2.9351, -0.0395, -2.4827, 1.6372, -1.6179, -2.4412, 1.9847, -0.6202, -2.5272, 0.3979, -0.8764, -0.7996, 2.0136, 1.7125, -0.1737, 2.1372, 2.2437, -0.5194, 1.6162, 2.2076, -0.7996, 2.0136, 1.7125, -0.5194, 1.6162, 2.2076, -1.313, 1.5855, 1.6731, -1.6866, -0.7727, 1.7598, -1.0617, -0.1771, 2.0331, -1.0177, -1.42, 2.2188, 0.2988, 1.5533, 2.7449, 0.5198, 2.1281, 2.596, 0.7064, 1.456, 2.7883, 0.3266, 0.5073, 3.1476, 0.7324, 0.4498, 3.1493, 0.5951, -0.2972, 3.54, 1.8789, 1.1592, -0.8303, 2.1036, 1.6103, 0.0759, 1.3001, 2.6786, -0.7585, 2.1338, 1.9216, 1.0603, 1.8537, 2.1593, 1.6508, 1.6716, 2.5645, 0.6123, -0.0859, -2.2445, 2.6136, 0.5411, -1.3085, 3.348, 0.748, -2.001, 2.5127, -0.4979, 2.1368, -2.1727, -0.1394, 3.1268, -1.5759, -0.6639, 2.8841, -1.1739, 0.0726, 3.4231, -0.5764, 0.1518, 3.0337, 0.6723, -0.7449, 2.9351, -0.0395, 1.1174, 1.3846, 2.616, 1.9058, 0.4561, 2.6704, 1.3631, 0.7103, 2.7557, 1.1174, 1.3846, 2.616, 1.3708, 1.9744, 2.321, 1.9058, 0.4561, 2.6704, -2.2917, 0.7985, 0.4167, -2.1878, 1.682, 0.5464, -1.9453, 1.0221, 1.1785, -1.533, 2.1485, 1.1174, -0.7996, 2.0136, 1.7125, -1.313, 1.5855, 1.6731, -1.8821, -1.8796, 1.075, -0.8935, -2.6081, 1.7932, -1.2838, -3.0035, 0.2928, -0.3641, -1.322, 2.8122, -0.3719, -0.4418, 2.6845, 0.0592, -0.6578, 3.3401, -0.3719, -0.4418, 2.6845, -0.2183, 0.1183, 2.8781, 0.0592, -0.6578, 3.3401, -2.2917, 0.7985, 0.4167, -1.9453, 1.0221, 1.1785, -2.1903, -0.0178, 1.0505, 1.9116, -1.7142, 2.341, 1.4775, -1.3103, 2.8259, 2.1165, -0.7174, 2.6008, 1.3352, -0.2905, 3.0366, 1.3631, 0.7103, 2.7557, 1.9058, 0.4561, 2.6704, 1.3352, -0.2905, 3.0366, 1.1272, 0.2617, 2.9365, 1.3631, 0.7103, 2.7557, 0.8304, 0.2995, -2.6326, 1.3709, 0.7135, -1.8767, 0.6753, 1.5794, -2.5902, 2.4418, 0.2925, 1.9718, 2.1338, 1.9216, 1.0603, 2.4414, 1.1989, 1.2948, 2.4418, 0.2925, 1.9718, 1.8537, 2.1593, 1.6508, 2.1338, 1.9216, 1.0603, 0.8831, 2.5385, -1.8483, 1.3001, 2.6786, -0.7585, 0.5065, 3.2659, -1.3942, 2.4024, -1.5988, 1.5279, 2.6677, -0.3337, 1.0018, 2.5851, -1.2056, 0.7305, 2.4024, -1.5988, 1.5279, 2.4418, 0.2925, 1.9718, 2.6677, -0.3337, 1.0018, -1.5104, 0.6775, -2.512, -2.0387, 1.5996, -2.2267, -1.9542, -0.4605, -2.5215, -2.0037, 2.5218, -1.5743, -1.3876, 2.8143, -0.9703, -1.9473, 2.7594, -0.6554, 0.8304, 0.2995, -2.6326, 0.6753, 1.5794, -2.5902, 0.0953, 0.6217, -3.4674, -2.0226, -2.1644, -0.379, -1.8821, -1.8796, 1.075, -1.2838, -3.0035, 0.2928, -1.6866, -0.7727, 1.7598, -1.3976, 0.6213, 1.8313, -1.0617, -0.1771, 2.0331, -1.2743, 2.358, -1.6626, -1.3876, 2.8143, -0.9703, -2.0037, 2.5218, -1.5743, 0.2299, -3.4688, 0.9248, 0.6312, -2.7374, 1.3685, 0.5827, -3.159, 0.0985, 0.748, -2.001, 2.5127, 0.5411, -1.3085, 3.348, 1.4775, -1.3103, 2.8259, 1.3964, -0.6655, -2.1748, 0.8304, 0.2995, -2.6326, 0.5493, -0.7511, -3.2244, 1.7659, -2.4495, -1.2002, 1.4806, -1.8538, -1.9812, 0.8507, -2.8222, -2.0706, 2.4418, 0.2925, 1.9718, 2.6117, 0.0377, 0.454, 2.6677, -0.3337, 1.0018, 2.6117, 0.0377, 0.454, 2.4414, 1.1989, 1.2948, 2.4717, 0.9465, 0.6202, 2.6117, 0.0377, 0.454, 2.4418, 0.2925, 1.9718, 2.4414, 1.1989, 1.2948, 0.5493, -0.7511, -3.2244, 0.8304, 0.2995, -2.6326, 0.0953, 0.6217, -3.4674, 2.4515, -0.0586, -0.1403, 2.038, -0.9707, -1.2311, 2.4378, -0.9615, -0.323, 2.4515, -0.0586, -0.1403, 1.8789, 1.1592, -0.8303, 2.038, -0.9707, -1.2311, -0.1975, -0.7247, -3.4884, -0.5122, 0.3139, -3.5925, -0.7053, -0.3406, -3.1144, -1.1067, -1.2731, -2.7048, -1.9542, -0.4605, -2.5215, -1.5545, -2.3633, -2.567, -0.4143, 1.2902, -3.2951, -0.4979, 2.1368, -2.1727, -0.8466, 1.1377, -2.7759, -0.7053, -0.3406, -3.1144, -0.5122, 0.3139, -3.5925, -0.9168, 0.4069, -2.985, -2.4827, 1.6372, -1.6179, -2.5272, 0.3979, -0.8764, -2.4716, -0.4239, -1.8891, -2.4716, -0.4239, -1.8891, -2.5272, 0.3979, -0.8764, -2.448, -1.0454, -1.0528, -2.2072, -1.9264, -1.9655, -2.4716, -0.4239, -1.8891, -2.448, -1.0454, -1.0528, 0.1351, -3.9012, -0.5924, 0.5827, -3.159, 0.0985, 0.6881, -3.2916, -0.9673, -1.2838, -3.0035, 0.2928, -0.8935, -2.6081, 1.7932, -0.3984, -3.542, 1.0253, 1.3964, -0.6655, -2.1748, 1.3709, 0.7135, -1.8767, 0.8304, 0.2995, -2.6326, 1.1446, -2.3077, 1.6179, 1.8479, -2.2708, 1.4206, 1.3113, -2.5503, 0.6426, 1.2555, -2.8919, -0.6692, 1.3113, -2.5503, 0.6426, 1.8655, -2.6668, 0.0778, -0.6766, -3.9798, -0.6577, -1.2838, -3.0035, 0.2928, -0.3984, -3.542, 1.0253, 1.4806, -1.8538, -1.9812, 0.5747, -2.1329, -3.01, 0.8507, -2.8222, -2.0706, 1.3113, -2.5503, 0.6426, 1.8479, -2.2708, 1.4206, 1.8655, -2.6668, 0.0778, -0.6836, -3.6027, -2.5845, 0.0002, -3.2065, -2.7359, -0.6382, -2.68, -2.9376, 1.7659, -2.4495, -1.2002, 2.425, -2.0284, 0.49, 2.2873, -1.7649, -0.5811, 1.7659, -2.4495, -1.2002, 1.8655, -2.6668, 0.0778, 2.425, -2.0284, 0.49, 0.0831, -2.9699, 1.939, 0.2299, -3.4688, 0.9248, -0.3984, -3.542, 1.0253, -0.9168, 0.4069, -2.985, -0.8466, 1.1377, -2.7759, -1.5104, 0.6775, -2.512, -1.9473, 2.7594, -0.6554, -2.1878, 1.682, 0.5464, -2.4412, 1.9847, -0.6202, -2.1878, 1.682, 0.5464, -1.33, 2.7367, 0.359, -1.533, 2.1485, 1.1174, -2.1878, 1.682, 0.5464, -1.9473, 2.7594, -0.6554, -1.33, 2.7367, 0.359, 0.0726, 3.4231, -0.5764, -0.1394, 3.1268, -1.5759, 0.5065, 3.2659, -1.3942, 2.1036, 1.6103, 0.0759, 2.4414, 1.1989, 1.2948, 2.1338, 1.9216, 1.0603, 2.1036, 1.6103, 0.0759, 2.4717, 0.9465, 0.6202, 2.4414, 1.1989, 1.2948, 0.2361, 2.7435, 1.8554, 0.5198, 2.1281, 2.596, -0.1737, 2.1372, 2.2437, 0.0411, -3.76, -1.9518, -0.6766, -3.9798, -0.6577, 0.1351, -3.9012, -0.5924, 0.0411, -3.76, -1.9518, -0.6585, -3.9829, -1.9964, -0.6766, -3.9798, -0.6577, 1.2555, -2.8919, -0.6692, 0.8507, -2.8222, -2.0706, 0.6881, -3.2916, -0.9673, 1.4806, -1.8538, -1.9812, 2.038, -0.9707, -1.2311, 1.3964, -0.6655, -2.1748, -0.2162, -1.825, -3.2439, 0.5493, -0.7511, -3.2244, -0.1975, -0.7247, -3.4884, -0.2162, -1.825, -3.2439, 0.5747, -2.1329, -3.01, 0.5493, -0.7511, -3.2244, -1.1679, -3.5644, -2.3336, -2.2072, -1.9264, -1.9655, -1.6255, -3.1162, -1.4188, -1.1679, -3.5644, -2.3336, -1.5545, -2.3633, -2.567, -2.2072, -1.9264, -1.9655, -0.4143, 1.2902, -3.2951, -0.5122, 0.3139, -3.5925, 0.0953, 0.6217, -3.4674, 2.6677, -0.3337, 1.0018, 2.4378, -0.9615, -0.323, 2.5851, -1.2056, 0.7305, 2.4378, -0.9615, -0.323, 2.6117, 0.0377, 0.454, 2.4515, -0.0586, -0.1403, 2.4378, -0.9615, -0.323, 2.6677, -0.3337, 1.0018, 2.6117, 0.0377, 0.454, 0.6312, -2.7374, 1.3685, 0.748, -2.001, 2.5127, 1.1446, -2.3077, 1.6179, 2.4024, -1.5988, 1.5279, 1.8479, -2.2708, 1.4206, 1.9116, -1.7142, 2.341, -1.8821, -1.8796, 1.075, -2.1903, -0.0178, 1.0505, -1.6866, -0.7727, 1.7598, -1.8821, -1.8796, 1.075, -2.399, -0.7585, 0.2313, -2.1903, -0.0178, 1.0505, -1.0177, -1.42, 2.2188, -0.0859, -2.2445, 2.6136, -0.8935, -2.6081, 1.7932, -1.0177, -1.42, 2.2188, -0.3641, -1.322, 2.8122, -0.0859, -2.2445, 2.6136, -2.0037, 2.5218, -1.5743, -2.4827, 1.6372, -1.6179, -2.0387, 1.5996, -2.2267, 0.8831, 2.5385, -1.8483, 0.1427, 2.3057, -2.4478, 0.6753, 1.5794, -2.5902, -1.3227, 1.7291, -2.1514, -0.4979, 2.1368, -2.1727, -1.2743, 2.358, -1.6626, 2.1165, -0.7174, 2.6008, 1.9058, 0.4561, 2.6704, 2.4418, 0.2925, 1.9718, 0.5951, -0.2972, 3.54, 0.5411, -1.3085, 3.348, 0.0592, -0.6578, 3.3401, -1.9453, 1.0221, 1.1785, -1.313, 1.5855, 1.6731, -1.3976, 0.6213, 1.8313, -0.6639, 2.8841, -1.1739, -0.7449, 2.9351, -0.0395, -1.3876, 2.8143, -0.9703, 1.3001, 2.6786, -0.7585, 1.6716, 2.5645, 0.6123, 0.9156, 3.0784, 0.1405, 1.1174, 1.3846, 2.616, 0.7324, 0.4498, 3.1493, 0.7064, 1.456, 2.7883, 0.7324, 0.4498, 3.1493, 1.3631, 0.7103, 2.7557, 1.1272, 0.2617, 2.9365, 0.7324, 0.4498, 3.1493, 1.1174, 1.3846, 2.616, 1.3631, 0.7103, 2.7557, 1.3708, 1.9744, 2.321, 1.0451, 2.7721, 1.514, 1.8537, 2.1593, 1.6508, -0.8149, 0.6856, 2.232, 0.3266, 0.5073, 3.1476, -0.2183, 0.1183, 2.8781, 0.3266, 0.5073, 3.1476, -0.5194, 1.6162, 2.2076, 0.2988, 1.5533, 2.7449, 0.3266, 0.5073, 3.1476, -0.8149, 0.6856, 2.232, -0.5194, 1.6162, 2.2076, -0.6585, -3.9829, -1.9964, 0.0002, -3.2065, -2.7359, -0.6836, -3.6027, -2.5845, -0.6585, -3.9829, -1.9964, 0.0411, -3.76, -1.9518, 0.0002, -3.2065, -2.7359, 0.8507, -2.8222, -2.0706, 1.8655, -2.6668, 0.0778, 1.7659, -2.4495, -1.2002, 0.8507, -2.8222, -2.0706, 1.2555, -2.8919, -0.6692, 1.8655, -2.6668, 0.0778, 2.038, -0.9707, -1.2311, 1.7659, -2.4495, -1.2002, 2.2873, -1.7649, -0.5811, 2.038, -0.9707, -1.2311, 1.4806, -1.8538, -1.9812, 1.7659, -2.4495, -1.2002, -1.5545, -2.3633, -2.567, -0.6836, -3.6027, -2.5845, -0.6382, -2.68, -2.9376, -1.5545, -2.3633, -2.567, -1.1679, -3.5644, -2.3336, -0.6836, -3.6027, -2.5845, 2.5851, -1.2056, 0.7305, 2.2873, -1.7649, -0.5811, 2.425, -2.0284, 0.49, 2.5851, -1.2056, 0.7305, 2.4378, -0.9615, -0.323, 2.2873, -1.7649, -0.5811, 0.748, -2.001, 2.5127, 0.2299, -3.4688, 0.9248, 0.0831, -2.9699, 1.939, 0.748, -2.001, 2.5127, 0.6312, -2.7374, 1.3685, 0.2299, -3.4688, 0.9248, -2.399, -0.7585, 0.2313, -2.0226, -2.1644, -0.379, -2.448, -1.0454, -1.0528, -2.399, -0.7585, 0.2313, -1.8821, -1.8796, 1.075, -2.0226, -2.1644, -0.379, -0.3641, -1.322, 2.8122, -1.0617, -0.1771, 2.0331, -0.3719, -0.4418, 2.6845, -0.3641, -1.322, 2.8122, -1.0177, -1.42, 2.2188, -1.0617, -0.1771, 2.0331, -2.2917, 0.7985, 0.4167, -2.4412, 1.9847, -0.6202, -2.1878, 1.682, 0.5464, -2.2917, 0.7985, 0.4167, -2.5272, 0.3979, -0.8764, -2.4412, 1.9847, -0.6202, 1.9058, 0.4561, 2.6704, 1.4775, -1.3103, 2.8259, 1.3352, -0.2905, 3.0366, 1.9058, 0.4561, 2.6704, 2.1165, -0.7174, 2.6008, 1.4775, -1.3103, 2.8259, -1.313, 1.5855, 1.6731, -2.1878, 1.682, 0.5464, -1.533, 2.1485, 1.1174, -1.313, 1.5855, 1.6731, -1.9453, 1.0221, 1.1785, -2.1878, 1.682, 0.5464, -0.7449, 2.9351, -0.0395, -0.1394, 3.1268, -1.5759, 0.0726, 3.4231, -0.5764, -0.7449, 2.9351, -0.0395, -0.6639, 2.8841, -1.1739, -0.1394, 3.1268, -1.5759, 1.6716, 2.5645, 0.6123, 2.1036, 1.6103, 0.0759, 2.1338, 1.9216, 1.0603, 1.6716, 2.5645, 0.6123, 1.3001, 2.6786, -0.7585, 2.1036, 1.6103, 0.0759, 1.0451, 2.7721, 1.514, 0.5198, 2.1281, 2.596, 0.2361, 2.7435, 1.8554, 1.0451, 2.7721, 1.514, 1.3708, 1.9744, 2.321, 0.5198, 2.1281, 2.596, -0.8149, 0.6856, 2.232, -0.3719, -0.4418, 2.6845, -1.0617, -0.1771, 2.0331, -0.8149, 0.6856, 2.232, -0.2183, 0.1183, 2.8781, -0.3719, -0.4418, 2.6845, -0.5981, 2.6867, 0.9116, -1.533, 2.1485, 1.1174, -1.33, 2.7367, 0.359, -0.5981, 2.6867, 0.9116, -0.7996, 2.0136, 1.7125, -1.533, 2.1485, 1.1174, -0.3984, -3.542, 1.0253, 0.1351, -3.9012, -0.5924, -0.6766, -3.9798, -0.6577, -0.3984, -3.542, 1.0253, 0.2299, -3.4688, 0.9248, 0.1351, -3.9012, -0.5924, -0.6382, -2.68, -2.9376, 0.5747, -2.1329, -3.01, -0.2162, -1.825, -3.2439, -0.6382, -2.68, -2.9376, 0.0002, -3.2065, -2.7359, 0.5747, -2.1329, -3.01, -2.448, -1.0454, -1.0528, -1.6255, -3.1162, -1.4188, -2.2072, -1.9264, -1.9655, -2.448, -1.0454, -1.0528, -2.0226, -2.1644, -0.379, -1.6255, -3.1162, -1.4188, -0.9168, 0.4069, -2.985, -1.1067, -1.2731, -2.7048, -0.7053, -0.3406, -3.1144, -0.9168, 0.4069, -2.985, -1.5104, 0.6775, -2.512, -1.1067, -1.2731, -2.7048, -0.8466, 1.1377, -2.7759, -0.5122, 0.3139, -3.5925, -0.4143, 1.2902, -3.2951, -0.8466, 1.1377, -2.7759, -0.9168, 0.4069, -2.985, -0.5122, 0.3139, -3.5925, 2.425, -2.0284, 0.49, 1.8479, -2.2708, 1.4206, 2.4024, -1.5988, 1.5279, 2.425, -2.0284, 0.49, 1.8655, -2.6668, 0.0778, 1.8479, -2.2708, 1.4206, 0.0831, -2.9699, 1.939, -0.8935, -2.6081, 1.7932, -0.0859, -2.2445, 2.6136, 0.0831, -2.9699, 1.939, -0.3984, -3.542, 1.0253, -0.8935, -2.6081, 1.7932, -1.9473, 2.7594, -0.6554, -2.4827, 1.6372, -1.6179, -2.0037, 2.5218, -1.5743, -1.9473, 2.7594, -0.6554, -2.4412, 1.9847, -0.6202, -2.4827, 1.6372, -1.6179, 0.5065, 3.2659, -1.3942, 0.1427, 2.3057, -2.4478, 0.8831, 2.5385, -1.8483, 0.5065, 3.2659, -1.3942, -0.1394, 3.1268, -1.5759, 0.1427, 2.3057, -2.4478, -1.5104, 0.6775, -2.512, -0.4979, 2.1368, -2.1727, -1.3227, 1.7291, -2.1514, -1.5104, 0.6775, -2.512, -0.8466, 1.1377, -2.7759, -0.4979, 2.1368, -2.1727, 1.3352, -0.2905, 3.0366, 0.5411, -1.3085, 3.348, 0.5951, -0.2972, 3.54, 1.3352, -0.2905, 3.0366, 1.4775, -1.3103, 2.8259, 0.5411, -1.3085, 3.348, -1.33, 2.7367, 0.359, -1.3876, 2.8143, -0.9703, -0.7449, 2.9351, -0.0395, -1.33, 2.7367, 0.359, -1.9473, 2.7594, -0.6554, -1.3876, 2.8143, -0.9703, 0.0726, 3.4231, -0.5764, 1.3001, 2.6786, -0.7585, 0.9156, 3.0784, 0.1405, 0.0726, 3.4231, -0.5764, 0.5065, 3.2659, -1.3942, 1.3001, 2.6786, -0.7585, -0.1737, 2.1372, 2.2437, 0.2988, 1.5533, 2.7449, -0.5194, 1.6162, 2.2076, -0.1737, 2.1372, 2.2437, 0.5198, 2.1281, 2.596, 0.2988, 1.5533, 2.7449, 0.2361, 2.7435, 1.8554, -0.7996, 2.0136, 1.7125, -0.5981, 2.6867, 0.9116, 0.2361, 2.7435, 1.8554, -0.1737, 2.1372, 2.2437, -0.7996, 2.0136, 1.7125, 1.8537, 2.1593, 1.6508, 0.9156, 3.0784, 0.1405, 1.6716, 2.5645, 0.6123, 1.8537, 2.1593, 1.6508, 1.0451, 2.7721, 1.514, 0.9156, 3.0784, 0.1405, -0.5194, 1.6162, 2.2076, -1.3976, 0.6213, 1.8313, -1.313, 1.5855, 1.6731, -0.5194, 1.6162, 2.2076, -0.8149, 0.6856, 2.232, -1.3976, 0.6213, 1.8313, 0.3266, 0.5073, 3.1476, 0.7064, 1.456, 2.7883, 0.7324, 0.4498, 3.1493, 0.3266, 0.5073, 3.1476, 0.2988, 1.5533, 2.7449, 0.7064, 1.456, 2.7883, -0.2183, 0.1183, 2.8781, 0.5951, -0.2972, 3.54, 0.0592, -0.6578, 3.3401, -0.2183, 0.1183, 2.8781, 0.3266, 0.5073, 3.1476, 0.5951, -0.2972, 3.54, 1.3708, 1.9744, 2.321, 2.4418, 0.2925, 1.9718, 1.9058, 0.4561, 2.6704, 1.3708, 1.9744, 2.321, 1.8537, 2.1593, 1.6508, 2.4418, 0.2925, 1.9718, 1.3709, 0.7135, -1.8767, 0.8831, 2.5385, -1.8483, 0.6753, 1.5794, -2.5902, 1.3709, 0.7135, -1.8767, 1.8789, 1.1592, -0.8303, 0.8831, 2.5385, -1.8483, -1.2743, 2.358, -1.6626, -0.6639, 2.8841, -1.1739, -1.3876, 2.8143, -0.9703, -1.2743, 2.358, -1.6626, -0.4979, 2.1368, -2.1727, -0.6639, 2.8841, -1.1739, -1.3227, 1.7291, -2.1514, -2.0037, 2.5218, -1.5743, -2.0387, 1.5996, -2.2267, -1.3227, 1.7291, -2.1514, -1.2743, 2.358, -1.6626, -2.0037, 2.5218, -1.5743, -2.5272, 0.3979, -0.8764, -2.1903, -0.0178, 1.0505, -2.399, -0.7585, 0.2313, -2.5272, 0.3979, -0.8764, -2.2917, 0.7985, 0.4167, -2.1903, -0.0178, 1.0505, -1.9453, 1.0221, 1.1785, -1.6866, -0.7727, 1.7598, -2.1903, -0.0178, 1.0505, -1.9453, 1.0221, 1.1785, -1.3976, 0.6213, 1.8313, -1.6866, -0.7727, 1.7598, -0.0859, -2.2445, 2.6136, 0.0592, -0.6578, 3.3401, 0.5411, -1.3085, 3.348, -0.0859, -2.2445, 2.6136, -0.3641, -1.322, 2.8122, 0.0592, -0.6578, 3.3401, 2.4024, -1.5988, 1.5279, 2.1165, -0.7174, 2.6008, 2.4418, 0.2925, 1.9718, 2.4024, -1.5988, 1.5279, 1.9116, -1.7142, 2.341, 2.1165, -0.7174, 2.6008, 0.6753, 1.5794, -2.5902, -0.4143, 1.2902, -3.2951, 0.0953, 0.6217, -3.4674, 0.6753, 1.5794, -2.5902, 0.1427, 2.3057, -2.4478, -0.4143, 1.2902, -3.2951, -2.0387, 1.5996, -2.2267, -2.4716, -0.4239, -1.8891, -1.9542, -0.4605, -2.5215, -2.0387, 1.5996, -2.2267, -2.4827, 1.6372, -1.6179, -2.4716, -0.4239, -1.8891, -1.8821, -1.8796, 1.075, -1.0177, -1.42, 2.2188, -0.8935, -2.6081, 1.7932, -1.8821, -1.8796, 1.075, -1.6866, -0.7727, 1.7598, -1.0177, -1.42, 2.2188, 1.1446, -2.3077, 1.6179, 1.9116, -1.7142, 2.341, 1.8479, -2.2708, 1.4206, 1.1446, -2.3077, 1.6179, 0.748, -2.001, 2.5127, 1.9116, -1.7142, 2.341, 0.6312, -2.7374, 1.3685, 1.3113, -2.5503, 0.6426, 0.5827, -3.159, 0.0985, 0.6312, -2.7374, 1.3685, 1.1446, -2.3077, 1.6179, 1.3113, -2.5503, 0.6426, 1.8789, 1.1592, -0.8303, 1.3964, -0.6655, -2.1748, 2.038, -0.9707, -1.2311, 1.8789, 1.1592, -0.8303, 1.3709, 0.7135, -1.8767, 1.3964, -0.6655, -2.1748, -0.1975, -0.7247, -3.4884, 0.0953, 0.6217, -3.4674, -0.5122, 0.3139, -3.5925, -0.1975, -0.7247, -3.4884, 0.5493, -0.7511, -3.2244, 0.0953, 0.6217, -3.4674, -0.2162, -1.825, -3.2439, -0.7053, -0.3406, -3.1144, -1.1067, -1.2731, -2.7048, -0.2162, -1.825, -3.2439, -0.1975, -0.7247, -3.4884, -0.7053, -0.3406, -3.1144, -1.9542, -0.4605, -2.5215, -2.2072, -1.9264, -1.9655, -1.5545, -2.3633, -2.567, -1.9542, -0.4605, -2.5215, -2.4716, -0.4239, -1.8891, -2.2072, -1.9264, -1.9655, -1.1679, -3.5644, -2.3336, -0.6766, -3.9798, -0.6577, -0.6585, -3.9829, -1.9964, -1.1679, -3.5644, -2.3336, -1.6255, -3.1162, -1.4188, -0.6766, -3.9798, -0.6577, 1.4806, -1.8538, -1.9812, 0.5493, -0.7511, -3.2244, 0.5747, -2.1329, -3.01, 1.4806, -1.8538, -1.9812, 1.3964, -0.6655, -2.1748, 0.5493, -0.7511, -3.2244, 0.0411, -3.76, -1.9518, 0.6881, -3.2916, -0.9673, 0.8507, -2.8222, -2.0706, 0.0411, -3.76, -1.9518, 0.1351, -3.9012, -0.5924, 0.6881, -3.2916, -0.9673, 0.5827, -3.159, 0.0985, 1.2555, -2.8919, -0.6692, 0.6881, -3.2916, -0.9673, 0.5827, -3.159, 0.0985, 1.3113, -2.5503, 0.6426, 1.2555, -2.8919, -0.6692, -0.6836, -3.6027, -2.5845, -1.1679, -3.5644, -2.3336, -0.6585, -3.9829, -1.9964)
+
+[node name="RockA" instance=ExtResource("1_nu743")]
+collision_mask = 3
+
+[node name="Rock1" parent="." index="0"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+surface_material_override/0 = ExtResource("2_pmd5x")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"]
+shape = SubResource("ConcavePolygonShape3D_2s34t")
diff --git a/demo/assets/models/RockB.glb b/demo/assets/models/RockB.glb
new file mode 100644
index 0000000..79c695e
Binary files /dev/null and b/demo/assets/models/RockB.glb differ
diff --git a/demo/assets/models/RockB.glb.import b/demo/assets/models/RockB.glb.import
new file mode 100644
index 0000000..4eba4f1
--- /dev/null
+++ b/demo/assets/models/RockB.glb.import
@@ -0,0 +1,44 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://nta3sef6c2el"
+path="res://.godot/imported/RockB.glb-d0df90244ab14da61106a961f4faa07f.scn"
+
+[deps]
+
+source_file="res://demo/assets/models/RockB.glb"
+dest_files=["res://.godot/imported/RockB.glb-d0df90244ab14da61106a961f4faa07f.scn"]
+
+[params]
+
+nodes/root_type="StaticBody3D"
+nodes/root_name="Scene Root"
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+_subresources={
+"materials": {
+"@MATERIAL:0": {
+"use_external/enabled": true,
+"use_external/path": "res://demo/assets/materials/M_rock30.tres"
+}
+}
+}
+gltf/naming_version=0
+gltf/embedded_image_handling=1
diff --git a/demo/assets/models/RockB.tscn b/demo/assets/models/RockB.tscn
new file mode 100644
index 0000000..97fa9de
--- /dev/null
+++ b/demo/assets/models/RockB.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=3 uid="uid://bwvtgwartxt0g"]
+
+[ext_resource type="PackedScene" uid="uid://nta3sef6c2el" path="res://demo/assets/models/RockB.glb" id="1_2nhli"]
+[ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_dbm2k"]
+
+[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_wfrmp"]
+data = PackedVector3Array(0.4584, -3.889, 0.8266, 0.6021, -4.1961, -0.0392, 0.169, -4.9369, 0.2441, 2.1498, -1.9824, 1.4097, 2.1074, -0.9551, 0.9434, 2.3387, -2.1373, 0.6494, -0.4293, -5.0774, 0.0003, -0.5108, -3.8408, -1.1476, -1.1208, -4.5065, 0.1953, 0.0671, -5.0216, -0.4515, 0.4756, -4.5162, -0.6549, 0.1398, -4.5397, -0.9338, 0.169, -4.9369, 0.2441, -0.0922, -4.5871, 1.3087, 0.4584, -3.889, 0.8266, 2.1498, -1.9824, 1.4097, 1.9393, -0.8033, 1.6608, 2.1074, -0.9551, 0.9434, -1.2288, -2.368, 2.4204, -1.4963, -0.9921, 2.5267, -0.7814, -0.8418, 2.7775, -2.6648, -1.8501, 0.307, -2.907, 0.041, -0.3088, -2.5202, -0.0318, 0.8043, -1.0077, -1.3716, -2.1657, -0.7692, -0.6389, -2.6654, -1.6842, -0.4729, -2.3712, 2.1195, -1.771, -1.5705, 2.5987, -0.8743, -1.1872, 1.9584, -0.8816, -2.0923, -0.2829, -3.5923, 2.1108, -0.2939, -2.4569, 2.4482, 0.5442, -2.0915, 2.2664, -1.1208, -4.5065, 0.1953, -2.179, -3.4152, 0.4259, -1.8849, -3.8097, 1.1593, -0.5108, -3.8408, -1.1476, -0.2695, -2.508, -2.0432, -1.1054, -2.8992, -1.0824, 2.1726, -2.8941, -0.5813, 1.87, -2.8735, -1.1419, 1.5863, -3.2514, -0.7541, 0.7315, 0.8274, 2.7196, 1.1136, 1.849, 2.4998, 1.409, 0.9631, 2.386, -0.4271, 2.5556, 2.4897, 0.5261, 4.2194, 1.9538, 0.5038, 3.0339, 2.4087, -1.9764, 3.1849, 0.2685, -1.0116, 3.5059, 0.8103, -1.5979, 2.7577, 1.2075, -0.9393, 2.872, -1.8539, -1.2485, 3.6723, -1.0264, -1.7216, 2.2099, -1.5001, 1.1827, 2.8613, -1.4341, 0.9077, 3.2903, -1.4616, 0.9177, 2.8679, -1.5387, 0.8594, 5.2909, 1.0912, 0.6378, 5.6752, 0.4017, 1.3249, 5.3073, 0.5102, -1.1208, -4.5065, 0.1953, -1.8849, -3.8097, 1.1593, -0.8729, -4.7982, 1.121, 2.4617, 0.7259, 0.0288, 2.5473, 1.387, -0.0726, 2.6196, 0.7185, -0.2944, 0.9445, 5.5277, -0.3666, 0.5746, 5.6649, -0.3989, 0.6884, 5.4332, -0.7011, 1.7744, 4.3299, -0.4393, 1.3226, 4.3217, -0.9655, 2.0312, 2.7158, -1.0135, 0.9877, 0.4313, -2.4389, 0.7444, 0.8429, -2.2856, 0.635, 0.4247, -2.5749, -0.1392, 4.5027, -1.2571, -0.2125, 5.045, -0.6732, -1.2485, 3.6723, -1.0264, -0.1392, 4.5027, -1.2571, -1.2485, 3.6723, -1.0264, -0.9393, 2.872, -1.8539, -1.7965, 0.899, -2.2295, -1.7216, 2.2099, -1.5001, -2.3834, 0.7782, -1.5631, -0.791, 4.3118, -0.0929, -0.2232, 5.1794, 0.1938, -0.5404, 4.4098, 0.4328, -1.3695, 3.5538, -0.0189, -1.0116, 3.5059, 0.8103, -1.9764, 3.1849, 0.2685, -2.5202, -0.0318, 0.8043, -2.1461, 1.5782, 1.2673, -2.0758, -0.0137, 1.8949, -0.5404, 4.4098, 0.4328, -0.2232, 5.1794, 0.1938, -0.0735, 4.938, 0.8308, -1.0116, 3.5059, 0.8103, -0.2982, 4.0929, 1.4648, -0.878, 3.135, 1.7811, 2.6003, 1.0473, -1.0133, 2.4031, 2.6779, -0.4122, 2.0312, 2.7158, -1.0135, 2.4031, 2.6779, -0.4122, 1.7744, 4.3299, -0.4393, 2.0312, 2.7158, -1.0135, -1.0159, 0.9756, 2.6776, -0.4271, 2.5556, 2.4897, -0.2195, 1.1266, 2.8293, -0.0823, 1.3076, -2.4751, 0.0456, 1.662, -2.2329, -0.2095, 1.8203, -2.3278, 0.1709, 2.9305, -1.8154, 0.1823, 3.4347, -1.7089, -0.0988, 3.0024, -1.9036, -2.907, 0.041, -0.3088, -2.575, 1.9843, 0.2578, -2.5202, -0.0318, 0.8043, -2.5502, 1.5321, -0.8246, -1.9974, 2.3952, -0.8364, -2.286, 2.7057, -0.3882, -1.9974, 2.3952, -0.8364, -1.7496, 3.0465, -0.6307, -2.286, 2.7057, -0.3882, 0.8594, 5.2909, 1.0912, 1.3249, 5.3073, 0.5102, 1.6148, 4.6612, 1.1746, -1.6176, 1.4128, 2.2491, -1.7058, 2.0492, 1.8707, -1.3001, 2.2705, 2.1959, -1.5979, 2.7577, 1.2075, -1.0116, 3.5059, 0.8103, -0.878, 3.135, 1.7811, -0.7261, 0.8081, -2.8158, -0.8202, 1.5078, -2.6285, -1.246, 0.719, -2.7492, 1.409, 0.9631, 2.386, 1.1136, 1.849, 2.4998, 1.6219, 2.267, 2.1861, 0.5038, 3.0339, 2.4087, 0.5261, 4.2194, 1.9538, 1.2634, 3.7734, 1.9919, 2.6003, 1.0473, -1.0133, 2.0312, 2.7158, -1.0135, 2.1515, 0.9506, -1.6595, 2.2544, 3.8319, 0.7934, 2.1174, 4.2399, 0.396, 2.3685, 3.5019, 0.414, 1.4453, 1.3219, -1.8647, 1.4442, 1.9, -1.6223, 1.2471, 1.631, -1.7857, 1.9293, 0.908, 1.6849, 2.1901, 2.3652, 1.3543, 2.1038, 1.0245, 0.9114, -1.0077, -1.3716, -2.1657, -1.6842, -0.4729, -2.3712, -1.6361, -1.5711, -1.5378, -1.7965, 0.899, -2.2295, -0.9393, 2.872, -1.8539, -1.7216, 2.2099, -1.5001, 1.1377, -0.8428, -2.7439, 0.7304, -0.9533, -2.8604, 0.9457, -1.6804, -2.7624, -2.6648, -1.8501, 0.307, -2.5202, -0.0318, 0.8043, -2.3406, -2.1709, 1.3672, -2.5202, -0.0318, 0.8043, -2.575, 1.9843, 0.2578, -2.1461, 1.5782, 1.2673, -1.6842, -0.4729, -2.3712, -2.3999, -0.6295, -1.5513, -1.6361, -1.5711, -1.5378, -0.2939, -2.4569, 2.4482, 0.256, -0.9397, 2.7009, 0.5442, -2.0915, 2.2664, -0.2195, 1.1266, 2.8293, -0.4271, 2.5556, 2.4897, 0.5038, 3.0339, 2.4087, 2.4385, -0.7713, 0.0524, 2.634, -0.7691, -0.3318, 2.5652, -1.538, -0.1083, 1.8065, -2.8661, 1.2715, 1.9869, -3.1191, 0.2426, 1.2817, -3.3103, 0.5621, 2.1038, 1.0245, 0.9114, 2.1901, 2.3652, 1.3543, 2.3678, 2.1513, 0.6399, 0.9342, -3.6502, -1.2505, 0.9178, -3.3811, -1.9588, 0.3423, -3.927, -1.4233, 0.9342, -3.6502, -1.2505, 1.4243, -3.0391, -1.5379, 0.9178, -3.3811, -1.9588, 2.1195, -1.771, -1.5705, 1.9584, -0.8816, -2.0923, 1.5426, -2.0265, -2.2438, -0.5108, -3.8408, -1.1476, -1.1054, -2.8992, -1.0824, -1.1208, -4.5065, 0.1953, 0.3194, -2.8417, -2.3407, 0.0974, -1.7758, -2.6186, -0.2695, -2.508, -2.0432, -0.5108, -3.8408, -1.1476, 0.3194, -2.8417, -2.3407, -0.2695, -2.508, -2.0432, -2.5202, -0.0318, 0.8043, -2.0758, -0.0137, 1.8949, -2.3406, -2.1709, 1.3672, -1.9059, -2.3487, -0.7483, -2.5883, -1.4508, -0.7517, -2.3688, -2.5627, -0.2622, -1.809, -3.263, -0.2744, -1.9059, -2.3487, -0.7483, -2.3688, -2.5627, -0.2622, -0.0922, -4.5871, 1.3087, 0.4197, -3.4404, 1.5487, 0.4584, -3.889, 0.8266, -1.7887, -3.0231, 1.9828, -1.4963, -0.9921, 2.5267, -1.2288, -2.368, 2.4204, 2.5358, -1.9021, -0.8254, 2.5987, -0.8743, -1.1872, 2.1195, -1.771, -1.5705, 0.256, -0.9397, 2.7009, 1.2883, -0.8696, 2.2885, 0.5442, -2.0915, 2.2664, 1.2817, -3.3103, 0.5621, 0.9704, -3.0065, 1.4497, 1.8065, -2.8661, 1.2715, -1.1122, -3.8993, 1.9946, -1.7887, -3.0231, 1.9828, -1.2288, -2.368, 2.4204, 1.9869, -3.1191, 0.2426, 1.267, -3.4909, -0.189, 1.2817, -3.3103, 0.5621, 0.5442, -2.0915, 2.2664, 1.2883, -0.8696, 2.2885, 1.6056, -1.9424, 1.9813, -0.4293, -5.0774, 0.0003, 0.169, -4.9369, 0.2441, 0.0671, -5.0216, -0.4515, 2.1498, -1.9824, 1.4097, 1.8065, -2.8661, 1.2715, 1.6056, -1.9424, 1.9813, -2.179, -3.4152, 0.4259, -2.3688, -2.5627, -0.2622, -2.6648, -1.8501, 0.307, 1.87, -2.8735, -1.1419, 2.1195, -1.771, -1.5705, 1.4243, -3.0391, -1.5379, -1.7058, 2.0492, 1.8707, -2.1461, 1.5782, 1.2673, -1.5979, 2.7577, 1.2075, 1.2471, 1.631, -1.7857, 0.0456, 1.662, -2.2329, 0.7444, 0.8429, -2.2856, 0.0456, 1.662, -2.2329, 0.9177, 2.8679, -1.5387, 0.1709, 2.9305, -1.8154, 0.0456, 1.662, -2.2329, 1.2471, 1.631, -1.7857, 0.9177, 2.8679, -1.5387, 2.5473, 1.387, -0.0726, 2.3685, 3.5019, 0.414, 2.4031, 2.6779, -0.4122, 2.5473, 1.387, -0.0726, 2.3678, 2.1513, 0.6399, 2.3685, 3.5019, 0.414, 0.5746, 5.6649, -0.3989, -0.2232, 5.1794, 0.1938, -0.2125, 5.045, -0.6732, 0.5746, 5.6649, -0.3989, 0.6378, 5.6752, 0.4017, -0.2232, 5.1794, 0.1938, -0.0922, -4.5871, 1.3087, -1.1122, -3.8993, 1.9946, -0.2829, -3.5923, 2.1108, -0.0922, -4.5871, 1.3087, -0.8729, -4.7982, 1.121, -1.1122, -3.8993, 1.9946, 0.9704, -3.0065, 1.4497, 0.4197, -3.4404, 1.5487, 0.5442, -2.0915, 2.2664, 2.3387, -2.1373, 0.6494, 2.1726, -2.8941, -0.5813, 1.9869, -3.1191, 0.2426, 2.1726, -2.8941, -0.5813, 2.5652, -1.538, -0.1083, 2.5358, -1.9021, -0.8254, 2.1726, -2.8941, -0.5813, 2.3387, -2.1373, 0.6494, 2.5652, -1.538, -0.1083, 0.9342, -3.6502, -1.2505, 1.267, -3.4909, -0.189, 1.5863, -3.2514, -0.7541, 1.267, -3.4909, -0.189, 0.4756, -4.5162, -0.6549, 0.6021, -4.1961, -0.0392, 1.267, -3.4909, -0.189, 0.9342, -3.6502, -1.2505, 0.4756, -4.5162, -0.6549, -1.8849, -3.8097, 1.1593, -2.3406, -2.1709, 1.3672, -1.7887, -3.0231, 1.9828, -0.5108, -3.8408, -1.1476, 0.1398, -4.5397, -0.9338, 0.3423, -3.927, -1.4233, -1.1054, -2.8992, -1.0824, -1.6361, -1.5711, -1.5378, -1.9059, -2.3487, -0.7483, 0.9178, -3.3811, -1.9588, 0.9457, -1.6804, -2.7624, 0.3194, -2.8417, -2.3407, 0.9178, -3.3811, -1.9588, 1.5426, -2.0265, -2.2438, 0.9457, -1.6804, -2.7624, 2.4617, 0.7259, 0.0288, 2.1074, -0.9551, 0.9434, 2.1038, 1.0245, 0.9114, 2.4617, 0.7259, 0.0288, 2.4385, -0.7713, 0.0524, 2.1074, -0.9551, 0.9434, 2.6196, 0.7185, -0.2944, 2.5987, -0.8743, -1.1872, 2.634, -0.7691, -0.3318, 2.6196, 0.7185, -0.2944, 2.6003, 1.0473, -1.0133, 2.5987, -0.8743, -1.1872, 0.256, -0.9397, 2.7009, -0.2195, 1.1266, 2.8293, 0.7315, 0.8274, 2.7196, 0.256, -0.9397, 2.7009, -0.7814, -0.8418, 2.7775, -0.2195, 1.1266, 2.8293, 1.9393, -0.8033, 1.6608, 1.409, 0.9631, 2.386, 1.9293, 0.908, 1.6849, 1.9393, -0.8033, 1.6608, 1.2883, -0.8696, 2.2885, 1.409, 0.9631, 2.386, -2.0758, -0.0137, 1.8949, -1.0159, 0.9756, 2.6776, -1.4963, -0.9921, 2.5267, -2.0758, -0.0137, 1.8949, -1.6176, 1.4128, 2.2491, -1.0159, 0.9756, 2.6776, -1.246, 0.719, -2.7492, -1.7965, 0.899, -2.2295, -1.6842, -0.4729, -2.3712, -2.5502, 1.5321, -0.8246, -2.3999, -0.6295, -1.5513, -2.3834, 0.7782, -1.5631, -2.3999, -0.6295, -1.5513, -2.907, 0.041, -0.3088, -2.5883, -1.4508, -0.7517, -2.3999, -0.6295, -1.5513, -2.5502, 1.5321, -0.8246, -2.907, 0.041, -0.3088, 1.1377, -0.8428, -2.7439, 1.4453, 1.3219, -1.8647, 0.9877, 0.4313, -2.4389, 1.4453, 1.3219, -1.8647, 1.9584, -0.8816, -2.0923, 2.1515, 0.9506, -1.6595, 1.4453, 1.3219, -1.8647, 1.1377, -0.8428, -2.7439, 1.9584, -0.8816, -2.0923, -0.0823, 1.3076, -2.4751, 0.7304, -0.9533, -2.8604, 0.635, 0.4247, -2.5749, 0.7304, -0.9533, -2.8604, -0.7692, -0.6389, -2.6654, 0.0974, -1.7758, -2.6186, -0.7692, -0.6389, -2.6654, -0.0823, 1.3076, -2.4751, -0.7261, 0.8081, -2.8158, 0.7304, -0.9533, -2.8604, -0.0823, 1.3076, -2.4751, -0.7692, -0.6389, -2.6654, 1.2634, 3.7734, 1.9919, 2.1901, 2.3652, 1.3543, 1.6219, 2.267, 2.1861, 2.1901, 2.3652, 1.3543, 1.6148, 4.6612, 1.1746, 2.2544, 3.8319, 0.7934, 2.1901, 2.3652, 1.3543, 1.2634, 3.7734, 1.9919, 1.6148, 4.6612, 1.1746, -1.3001, 2.2705, 2.1959, -0.878, 3.135, 1.7811, -0.4271, 2.5556, 2.4897, -1.9764, 3.1849, 0.2685, -2.575, 1.9843, 0.2578, -2.286, 2.7057, -0.3882, -0.2095, 1.8203, -2.3278, -0.9393, 2.872, -1.8539, -0.8202, 1.5078, -2.6285, -0.2095, 1.8203, -2.3278, -0.0988, 3.0024, -1.9036, -0.9393, 2.872, -1.8539, 1.4442, 1.9, -1.6223, 2.0312, 2.7158, -1.0135, 1.1827, 2.8613, -1.4341, -0.0735, 4.938, 0.8308, 0.5261, 4.2194, 1.9538, -0.2982, 4.0929, 1.4648, -0.0735, 4.938, 0.8308, 0.8594, 5.2909, 1.0912, 0.5261, 4.2194, 1.9538, 1.3249, 5.3073, 0.5102, 1.7744, 4.3299, -0.4393, 2.1174, 4.2399, 0.396, 1.3249, 5.3073, 0.5102, 0.9445, 5.5277, -0.3666, 1.7744, 4.3299, -0.4393, -1.7496, 3.0465, -0.6307, -0.791, 4.3118, -0.0929, -1.3695, 3.5538, -0.0189, -1.7496, 3.0465, -0.6307, -1.2485, 3.6723, -1.0264, -0.791, 4.3118, -0.0929, 0.9077, 3.2903, -1.4616, -0.1392, 4.5027, -1.2571, 0.1823, 3.4347, -1.7089, -0.1392, 4.5027, -1.2571, 1.3226, 4.3217, -0.9655, 0.6884, 5.4332, -0.7011, -0.1392, 4.5027, -1.2571, 0.9077, 3.2903, -1.4616, 1.3226, 4.3217, -0.9655, -0.8729, -4.7982, 1.121, 0.169, -4.9369, 0.2441, -0.4293, -5.0774, 0.0003, -0.8729, -4.7982, 1.121, -0.0922, -4.5871, 1.3087, 0.169, -4.9369, 0.2441, 2.3387, -2.1373, 0.6494, 1.8065, -2.8661, 1.2715, 2.1498, -1.9824, 1.4097, 2.3387, -2.1373, 0.6494, 1.9869, -3.1191, 0.2426, 1.8065, -2.8661, 1.2715, 0.9342, -3.6502, -1.2505, 1.87, -2.8735, -1.1419, 1.4243, -3.0391, -1.5379, 0.9342, -3.6502, -1.2505, 1.5863, -3.2514, -0.7541, 1.87, -2.8735, -1.1419, -2.3406, -2.1709, 1.3672, -2.179, -3.4152, 0.4259, -2.6648, -1.8501, 0.307, -2.3406, -2.1709, 1.3672, -1.8849, -3.8097, 1.1593, -2.179, -3.4152, 0.4259, 0.1398, -4.5397, -0.9338, -0.4293, -5.0774, 0.0003, 0.0671, -5.0216, -0.4515, 0.1398, -4.5397, -0.9338, -0.5108, -3.8408, -1.1476, -0.4293, -5.0774, 0.0003, -1.6361, -1.5711, -1.5378, -0.2695, -2.508, -2.0432, -1.0077, -1.3716, -2.1657, -1.6361, -1.5711, -1.5378, -1.1054, -2.8992, -1.0824, -0.2695, -2.508, -2.0432, 1.5426, -2.0265, -2.2438, 1.4243, -3.0391, -1.5379, 2.1195, -1.771, -1.5705, 1.5426, -2.0265, -2.2438, 0.9178, -3.3811, -1.9588, 1.4243, -3.0391, -1.5379, 2.6003, 1.0473, -1.0133, 2.5473, 1.387, -0.0726, 2.4031, 2.6779, -0.4122, 2.6003, 1.0473, -1.0133, 2.6196, 0.7185, -0.2944, 2.5473, 1.387, -0.0726, -0.7814, -0.8418, 2.7775, -0.2939, -2.4569, 2.4482, -1.2288, -2.368, 2.4204, -0.7814, -0.8418, 2.7775, 0.256, -0.9397, 2.7009, -0.2939, -2.4569, 2.4482, -1.6176, 1.4128, 2.2491, -2.1461, 1.5782, 1.2673, -1.7058, 2.0492, 1.8707, -1.6176, 1.4128, 2.2491, -2.0758, -0.0137, 1.8949, -2.1461, 1.5782, 1.2673, -2.5502, 1.5321, -0.8246, -1.7216, 2.2099, -1.5001, -1.9974, 2.3952, -0.8364, -2.5502, 1.5321, -0.8246, -2.3834, 0.7782, -1.5631, -1.7216, 2.2099, -1.5001, -0.0823, 1.3076, -2.4751, 0.7444, 0.8429, -2.2856, 0.0456, 1.662, -2.2329, -0.0823, 1.3076, -2.4751, 0.635, 0.4247, -2.5749, 0.7444, 0.8429, -2.2856, 1.2634, 3.7734, 1.9919, 1.1136, 1.849, 2.4998, 0.5038, 3.0339, 2.4087, 1.2634, 3.7734, 1.9919, 1.6219, 2.267, 2.1861, 1.1136, 1.849, 2.4998, -0.878, 3.135, 1.7811, -1.7058, 2.0492, 1.8707, -1.5979, 2.7577, 1.2075, -0.878, 3.135, 1.7811, -1.3001, 2.2705, 2.1959, -1.7058, 2.0492, 1.8707, -0.0988, 3.0024, -1.9036, 0.0456, 1.662, -2.2329, 0.1709, 2.9305, -1.8154, -0.0988, 3.0024, -1.9036, -0.2095, 1.8203, -2.3278, 0.0456, 1.662, -2.2329, 0.9445, 5.5277, -0.3666, 0.6378, 5.6752, 0.4017, 0.5746, 5.6649, -0.3989, 0.9445, 5.5277, -0.3666, 1.3249, 5.3073, 0.5102, 0.6378, 5.6752, 0.4017, -1.2485, 3.6723, -1.0264, -1.9974, 2.3952, -0.8364, -1.7216, 2.2099, -1.5001, -1.2485, 3.6723, -1.0264, -1.7496, 3.0465, -0.6307, -1.9974, 2.3952, -0.8364, 0.9077, 3.2903, -1.4616, 0.1709, 2.9305, -1.8154, 0.9177, 2.8679, -1.5387, 0.9077, 3.2903, -1.4616, 0.1823, 3.4347, -1.7089, 0.1709, 2.9305, -1.8154, -1.2288, -2.368, 2.4204, -0.2829, -3.5923, 2.1108, -1.1122, -3.8993, 1.9946, -1.2288, -2.368, 2.4204, -0.2939, -2.4569, 2.4482, -0.2829, -3.5923, 2.1108, 1.6056, -1.9424, 1.9813, 0.9704, -3.0065, 1.4497, 0.5442, -2.0915, 2.2664, 1.6056, -1.9424, 1.9813, 1.8065, -2.8661, 1.2715, 0.9704, -3.0065, 1.4497, 2.1195, -1.771, -1.5705, 2.1726, -2.8941, -0.5813, 2.5358, -1.9021, -0.8254, 2.1195, -1.771, -1.5705, 1.87, -2.8735, -1.1419, 2.1726, -2.8941, -0.5813, 0.0671, -5.0216, -0.4515, 0.6021, -4.1961, -0.0392, 0.4756, -4.5162, -0.6549, 0.0671, -5.0216, -0.4515, 0.169, -4.9369, 0.2441, 0.6021, -4.1961, -0.0392, -2.3688, -2.5627, -0.2622, -1.1208, -4.5065, 0.1953, -1.809, -3.263, -0.2744, -2.3688, -2.5627, -0.2622, -2.179, -3.4152, 0.4259, -1.1208, -4.5065, 0.1953, 2.3678, 2.1513, 0.6399, 2.4617, 0.7259, 0.0288, 2.1038, 1.0245, 0.9114, 2.3678, 2.1513, 0.6399, 2.5473, 1.387, -0.0726, 2.4617, 0.7259, 0.0288, 0.5038, 3.0339, 2.4087, 0.7315, 0.8274, 2.7196, -0.2195, 1.1266, 2.8293, 0.5038, 3.0339, 2.4087, 1.1136, 1.849, 2.4998, 0.7315, 0.8274, 2.7196, 2.1498, -1.9824, 1.4097, 1.2883, -0.8696, 2.2885, 1.9393, -0.8033, 1.6608, 2.1498, -1.9824, 1.4097, 1.6056, -1.9424, 1.9813, 1.2883, -0.8696, 2.2885, -2.6648, -1.8501, 0.307, -2.5883, -1.4508, -0.7517, -2.907, 0.041, -0.3088, -2.6648, -1.8501, 0.307, -2.3688, -2.5627, -0.2622, -2.5883, -1.4508, -0.7517, 1.2471, 1.631, -1.7857, 0.9877, 0.4313, -2.4389, 1.4453, 1.3219, -1.8647, 1.2471, 1.631, -1.7857, 0.7444, 0.8429, -2.2856, 0.9877, 0.4313, -2.4389, -1.0077, -1.3716, -2.1657, 0.0974, -1.7758, -2.6186, -0.7692, -0.6389, -2.6654, -1.0077, -1.3716, -2.1657, -0.2695, -2.508, -2.0432, 0.0974, -1.7758, -2.6186, 2.3685, 3.5019, 0.414, 2.1901, 2.3652, 1.3543, 2.2544, 3.8319, 0.7934, 2.3685, 3.5019, 0.414, 2.3678, 2.1513, 0.6399, 2.1901, 2.3652, 1.3543, -1.5979, 2.7577, 1.2075, -2.575, 1.9843, 0.2578, -1.9764, 3.1849, 0.2685, -1.5979, 2.7577, 1.2075, -2.1461, 1.5782, 1.2673, -2.575, 1.9843, 0.2578, 0.9177, 2.8679, -1.5387, 1.4442, 1.9, -1.6223, 1.1827, 2.8613, -1.4341, 0.9177, 2.8679, -1.5387, 1.2471, 1.631, -1.7857, 1.4442, 1.9, -1.6223, -0.2232, 5.1794, 0.1938, 0.8594, 5.2909, 1.0912, -0.0735, 4.938, 0.8308, -0.2232, 5.1794, 0.1938, 0.6378, 5.6752, 0.4017, 0.8594, 5.2909, 1.0912, 2.4031, 2.6779, -0.4122, 2.1174, 4.2399, 0.396, 1.7744, 4.3299, -0.4393, 2.4031, 2.6779, -0.4122, 2.3685, 3.5019, 0.414, 2.1174, 4.2399, 0.396, -0.2125, 5.045, -0.6732, -0.791, 4.3118, -0.0929, -1.2485, 3.6723, -1.0264, -0.2125, 5.045, -0.6732, -0.2232, 5.1794, 0.1938, -0.791, 4.3118, -0.0929, 0.5746, 5.6649, -0.3989, -0.1392, 4.5027, -1.2571, 0.6884, 5.4332, -0.7011, 0.5746, 5.6649, -0.3989, -0.2125, 5.045, -0.6732, -0.1392, 4.5027, -1.2571, 1.7744, 4.3299, -0.4393, 0.6884, 5.4332, -0.7011, 1.3226, 4.3217, -0.9655, 1.7744, 4.3299, -0.4393, 0.9445, 5.5277, -0.3666, 0.6884, 5.4332, -0.7011, 1.3226, 4.3217, -0.9655, 1.1827, 2.8613, -1.4341, 2.0312, 2.7158, -1.0135, 1.3226, 4.3217, -0.9655, 0.9077, 3.2903, -1.4616, 1.1827, 2.8613, -1.4341, 0.1823, 3.4347, -1.7089, -0.9393, 2.872, -1.8539, -0.0988, 3.0024, -1.9036, 0.1823, 3.4347, -1.7089, -0.1392, 4.5027, -1.2571, -0.9393, 2.872, -1.8539, -1.3695, 3.5538, -0.0189, -0.5404, 4.4098, 0.4328, -1.0116, 3.5059, 0.8103, -1.3695, 3.5538, -0.0189, -0.791, 4.3118, -0.0929, -0.5404, 4.4098, 0.4328, -1.7496, 3.0465, -0.6307, -1.9764, 3.1849, 0.2685, -2.286, 2.7057, -0.3882, -1.7496, 3.0465, -0.6307, -1.3695, 3.5538, -0.0189, -1.9764, 3.1849, 0.2685, -1.0116, 3.5059, 0.8103, -0.0735, 4.938, 0.8308, -0.2982, 4.0929, 1.4648, -1.0116, 3.5059, 0.8103, -0.5404, 4.4098, 0.4328, -0.0735, 4.938, 0.8308, -0.2982, 4.0929, 1.4648, -0.4271, 2.5556, 2.4897, -0.878, 3.135, 1.7811, -0.2982, 4.0929, 1.4648, 0.5261, 4.2194, 1.9538, -0.4271, 2.5556, 2.4897, 0.5261, 4.2194, 1.9538, 1.6148, 4.6612, 1.1746, 1.2634, 3.7734, 1.9919, 0.5261, 4.2194, 1.9538, 0.8594, 5.2909, 1.0912, 1.6148, 4.6612, 1.1746, 1.3249, 5.3073, 0.5102, 2.2544, 3.8319, 0.7934, 1.6148, 4.6612, 1.1746, 1.3249, 5.3073, 0.5102, 2.1174, 4.2399, 0.396, 2.2544, 3.8319, 0.7934, 2.5987, -0.8743, -1.1872, 2.1515, 0.9506, -1.6595, 1.9584, -0.8816, -2.0923, 2.5987, -0.8743, -1.1872, 2.6003, 1.0473, -1.0133, 2.1515, 0.9506, -1.6595, 2.0312, 2.7158, -1.0135, 1.4453, 1.3219, -1.8647, 2.1515, 0.9506, -1.6595, 2.0312, 2.7158, -1.0135, 1.4442, 1.9, -1.6223, 1.4453, 1.3219, -1.8647, -0.7261, 0.8081, -2.8158, -0.2095, 1.8203, -2.3278, -0.8202, 1.5078, -2.6285, -0.7261, 0.8081, -2.8158, -0.0823, 1.3076, -2.4751, -0.2095, 1.8203, -2.3278, -0.7692, -0.6389, -2.6654, -1.246, 0.719, -2.7492, -1.6842, -0.4729, -2.3712, -0.7692, -0.6389, -2.6654, -0.7261, 0.8081, -2.8158, -1.246, 0.719, -2.7492, -0.8202, 1.5078, -2.6285, -1.7965, 0.899, -2.2295, -1.246, 0.719, -2.7492, -0.8202, 1.5078, -2.6285, -0.9393, 2.872, -1.8539, -1.7965, 0.899, -2.2295, -2.907, 0.041, -0.3088, -2.286, 2.7057, -0.3882, -2.575, 1.9843, 0.2578, -2.907, 0.041, -0.3088, -2.5502, 1.5321, -0.8246, -2.286, 2.7057, -0.3882, -1.0159, 0.9756, 2.6776, -1.3001, 2.2705, 2.1959, -0.4271, 2.5556, 2.4897, -1.0159, 0.9756, 2.6776, -1.6176, 1.4128, 2.2491, -1.3001, 2.2705, 2.1959, -1.4963, -0.9921, 2.5267, -0.2195, 1.1266, 2.8293, -0.7814, -0.8418, 2.7775, -1.4963, -0.9921, 2.5267, -1.0159, 0.9756, 2.6776, -0.2195, 1.1266, 2.8293, 1.9293, 0.908, 1.6849, 1.6219, 2.267, 2.1861, 2.1901, 2.3652, 1.3543, 1.9293, 0.908, 1.6849, 1.409, 0.9631, 2.386, 1.6219, 2.267, 2.1861, 1.9393, -0.8033, 1.6608, 2.1038, 1.0245, 0.9114, 2.1074, -0.9551, 0.9434, 1.9393, -0.8033, 1.6608, 1.9293, 0.908, 1.6849, 2.1038, 1.0245, 0.9114, 1.1377, -0.8428, -2.7439, 0.635, 0.4247, -2.5749, 0.7304, -0.9533, -2.8604, 1.1377, -0.8428, -2.7439, 0.9877, 0.4313, -2.4389, 0.635, 0.4247, -2.5749, 1.9584, -0.8816, -2.0923, 0.9457, -1.6804, -2.7624, 1.5426, -2.0265, -2.2438, 1.9584, -0.8816, -2.0923, 1.1377, -0.8428, -2.7439, 0.9457, -1.6804, -2.7624, 0.7304, -0.9533, -2.8604, 0.3194, -2.8417, -2.3407, 0.9457, -1.6804, -2.7624, 0.7304, -0.9533, -2.8604, 0.0974, -1.7758, -2.6186, 0.3194, -2.8417, -2.3407, -1.6842, -0.4729, -2.3712, -2.3834, 0.7782, -1.5631, -2.3999, -0.6295, -1.5513, -1.6842, -0.4729, -2.3712, -1.7965, 0.899, -2.2295, -2.3834, 0.7782, -1.5631, -2.3999, -0.6295, -1.5513, -1.9059, -2.3487, -0.7483, -1.6361, -1.5711, -1.5378, -2.3999, -0.6295, -1.5513, -2.5883, -1.4508, -0.7517, -1.9059, -2.3487, -0.7483, -2.0758, -0.0137, 1.8949, -1.7887, -3.0231, 1.9828, -2.3406, -2.1709, 1.3672, -2.0758, -0.0137, 1.8949, -1.4963, -0.9921, 2.5267, -1.7887, -3.0231, 1.9828, 0.256, -0.9397, 2.7009, 1.409, 0.9631, 2.386, 1.2883, -0.8696, 2.2885, 0.256, -0.9397, 2.7009, 0.7315, 0.8274, 2.7196, 1.409, 0.9631, 2.386, 2.4385, -0.7713, 0.0524, 2.6196, 0.7185, -0.2944, 2.634, -0.7691, -0.3318, 2.4385, -0.7713, 0.0524, 2.4617, 0.7259, 0.0288, 2.6196, 0.7185, -0.2944, 2.1074, -0.9551, 0.9434, 2.5652, -1.538, -0.1083, 2.3387, -2.1373, 0.6494, 2.1074, -0.9551, 0.9434, 2.4385, -0.7713, 0.0524, 2.5652, -1.538, -0.1083, 2.634, -0.7691, -0.3318, 2.5358, -1.9021, -0.8254, 2.5652, -1.538, -0.1083, 2.634, -0.7691, -0.3318, 2.5987, -0.8743, -1.1872, 2.5358, -1.9021, -0.8254, 0.4756, -4.5162, -0.6549, 0.3423, -3.927, -1.4233, 0.1398, -4.5397, -0.9338, 0.4756, -4.5162, -0.6549, 0.9342, -3.6502, -1.2505, 0.3423, -3.927, -1.4233, 0.9178, -3.3811, -1.9588, -0.5108, -3.8408, -1.1476, 0.3423, -3.927, -1.4233, 0.9178, -3.3811, -1.9588, 0.3194, -2.8417, -2.3407, -0.5108, -3.8408, -1.1476, -1.1054, -2.8992, -1.0824, -1.809, -3.263, -0.2744, -1.1208, -4.5065, 0.1953, -1.1054, -2.8992, -1.0824, -1.9059, -2.3487, -0.7483, -1.809, -3.263, -0.2744, -1.8849, -3.8097, 1.1593, -1.1122, -3.8993, 1.9946, -0.8729, -4.7982, 1.121, -1.8849, -3.8097, 1.1593, -1.7887, -3.0231, 1.9828, -1.1122, -3.8993, 1.9946, 1.9869, -3.1191, 0.2426, 1.5863, -3.2514, -0.7541, 1.267, -3.4909, -0.189, 1.9869, -3.1191, 0.2426, 2.1726, -2.8941, -0.5813, 1.5863, -3.2514, -0.7541, 1.267, -3.4909, -0.189, 0.4584, -3.889, 0.8266, 1.2817, -3.3103, 0.5621, 1.267, -3.4909, -0.189, 0.6021, -4.1961, -0.0392, 0.4584, -3.889, 0.8266, -0.0922, -4.5871, 1.3087, 0.5442, -2.0915, 2.2664, 0.4197, -3.4404, 1.5487, -0.0922, -4.5871, 1.3087, -0.2829, -3.5923, 2.1108, 0.5442, -2.0915, 2.2664, 0.4197, -3.4404, 1.5487, 1.2817, -3.3103, 0.5621, 0.4584, -3.889, 0.8266, 0.4197, -3.4404, 1.5487, 0.9704, -3.0065, 1.4497, 1.2817, -3.3103, 0.5621, -0.4293, -5.0774, 0.0003, -1.1208, -4.5065, 0.1953, -0.8729, -4.7982, 1.121)
+
+[node name="RockB" instance=ExtResource("1_2nhli")]
+collision_mask = 3
+
+[node name="Rock2" parent="." index="0"]
+surface_material_override/0 = ExtResource("2_dbm2k")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"]
+shape = SubResource("ConcavePolygonShape3D_wfrmp")
diff --git a/demo/assets/models/RockC.glb b/demo/assets/models/RockC.glb
new file mode 100644
index 0000000..8d87cfd
Binary files /dev/null and b/demo/assets/models/RockC.glb differ
diff --git a/demo/assets/models/RockC.glb.import b/demo/assets/models/RockC.glb.import
new file mode 100644
index 0000000..1ca0672
--- /dev/null
+++ b/demo/assets/models/RockC.glb.import
@@ -0,0 +1,44 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://c8cx4xjwluvxw"
+path="res://.godot/imported/RockC.glb-5881ee1ff2072066a3de122ce4239bee.scn"
+
+[deps]
+
+source_file="res://demo/assets/models/RockC.glb"
+dest_files=["res://.godot/imported/RockC.glb-5881ee1ff2072066a3de122ce4239bee.scn"]
+
+[params]
+
+nodes/root_type="StaticBody3D"
+nodes/root_name="Scene Root"
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+_subresources={
+"materials": {
+"@MATERIAL:0": {
+"use_external/enabled": true,
+"use_external/path": "res://demo/assets/materials/M_rock30.tres"
+}
+}
+}
+gltf/naming_version=0
+gltf/embedded_image_handling=1
diff --git a/demo/assets/models/RockC.tscn b/demo/assets/models/RockC.tscn
new file mode 100644
index 0000000..e8588e2
--- /dev/null
+++ b/demo/assets/models/RockC.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=3 uid="uid://lsvs8a7urkca"]
+
+[ext_resource type="PackedScene" uid="uid://c8cx4xjwluvxw" path="res://demo/assets/models/RockC.glb" id="1_pb27A"]
+[ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_pcex8"]
+
+[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_ycsjC"]
+data = PackedVector3Array(1.3907, -4.3898, 0.264, 0.3707, -6.5026, 0.2358, 0.9197, -5.2139, 0.5803, 1.3907, -4.3898, 0.264, 1.5973, -4.4989, -0.523, 0.3707, -6.5026, 0.2358, 2.7182, -1.9153, 1.6333, 2.6862, -0.2733, 1.344, 2.791, -2.0828, 0.6971, -0.6892, -7.3327, -0.0456, -1.2211, -6.6432, -1.3733, -1.8677, -6.7222, 0.0053, 0.1584, -6.6788, -0.712, 0.8528, -5.1659, -1.2387, -0.129, -6.0155, -1.6627, 0.3707, -6.5026, 0.2358, 0.8249, -4.8431, 1.2373, 0.9197, -5.2139, 0.5803, 0.3707, -6.5026, 0.2358, -0.1504, -6.0468, 1.6347, 0.8249, -4.8431, 1.2373, 2.7182, -1.9153, 1.6333, 2.5729, -0.0463, 2.323, 2.6862, -0.2733, 1.344, -1.5434, -2.0626, 2.9798, -0.0319, -0.907, 3.3884, -0.6296, -2.1168, 3.0274, -1.5434, -2.0626, 2.9798, -2.2391, -1.0013, 3.2378, -0.0319, -0.907, 3.3884, -3.2503, -1.9438, -0.0444, -3.1045, -0.0443, -0.8667, -2.9895, -0.0523, 0.8233, -0.5191, -2.1018, -2.9939, -2.0489, -0.9812, -3.014, -1.4897, -2.0239, -2.8933, -0.5191, -2.1018, -2.9939, 0.0444, -0.8451, -3.3989, -2.0489, -0.9812, -3.014, 3.4189, -1.5098, -2.1519, 3.3947, 0.8197, -1.419, 2.9911, 0.177, -2.5055, -0.9484, -5.3598, 2.2535, -0.8975, -3.7031, 2.6018, -0.1302, -3.9225, 2.4564, -2.8602, -5.2902, -0.0034, -3.2063, -3.9919, -0.0118, -3.0246, -4.4027, 0.8468, -0.9423, -5.2303, -2.2174, -1.6189, -3.1121, -2.5666, -2.3373, -3.6333, -2.2624, -0.9423, -5.2303, -2.2174, -0.9004, -3.6647, -2.5803, -1.6189, -3.1121, -2.5666, 2.4159, -3.3908, 0.0683, 2.6374, -3.7219, -1.0723, 1.5973, -4.4989, -0.523, 0.0817, 0.7564, 3.232, 1.3747, 1.7762, 2.618, 1.9677, 0.877, 2.8315, 0.0817, 0.7564, 3.232, 0.5064, 1.8556, 2.7003, 1.3747, 1.7762, 2.618, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 0.6919, 3.2703, 2.2188, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -2.0691, 2.5964, 1.5185, -1.2285, 2.4662, -2.4325, -1.544, 3.8438, -1.471, -2.3777, 2.1645, -2.0413, 2.3543, 3.9299, -1.7328, 1.4379, 5.2295, -1.8152, 1.1494, 2.3687, -2.734, 1.4341, 6.0081, 1.2447, 0.9325, 6.924, 0.4365, 2.3528, 6.0999, 0.4838, -1.8677, -6.7222, 0.0053, -2.3434, -5.4566, 1.3758, -1.2352, -6.7568, 1.4079, 3.0694, 0.3768, 0.2917, 3.6836, 2.3906, 0.1427, 3.39, -0.1345, -0.5433, 1.5686, 6.857, -0.4389, 0.7824, 7.1818, -0.4789, 0.9515, 6.8097, -1.0683, 2.4165, 5.7335, -0.6508, 1.4379, 5.2295, -1.8152, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 1.1494, 2.3687, -2.734, 0.0399, 0.8971, -3.3453, -0.1803, 5.4522, -1.6664, -0.2609, 6.3493, -0.8464, -1.544, 3.8438, -1.471, -3.1045, -0.0443, -0.8667, -2.9635, 2.0631, -0.0469, -2.9895, -0.0523, 0.8233, -2.8218, -0.0908, -2.3316, -2.8949, 1.7465, -1.4628, -3.1045, -0.0443, -0.8667, -1.137, 5.0901, -0.2048, -0.2548, 6.3653, 0.3115, -0.7059, 4.7856, 0.8682, -1.8973, 4.0438, -0.0231, -1.6818, 3.3981, 1.0198, -2.3926, 3.3478, 0.3255, -2.9895, -0.0523, 0.8233, -2.7222, 1.5834, 1.3251, -2.9289, -0.1232, 2.5316, -0.7059, 4.7856, 0.8682, -0.2548, 6.3653, 0.3115, 0.4487, 4.8708, 1.6514, -0.7059, 4.7856, 0.8682, 0.4487, 4.8708, 1.6514, -0.2637, 3.5029, 1.8153, 3.3947, 0.8197, -1.419, 3.6143, 2.6541, -0.7787, 3.0814, 2.2747, -1.5878, 3.3017, 4.3689, -0.5397, 2.4165, 5.7335, -0.6508, 2.3543, 3.9299, -1.7328, -2.1314, 0.7603, 3.0046, -1.0522, 1.986, 2.2721, 0.0817, 0.7564, 3.232, 0.0399, 0.8971, -3.3453, 1.1494, 2.3687, -2.734, -1.2285, 2.4662, -2.4325, 0.549, 3.3793, -2.5003, 0.4374, 4.5707, -2.224, -0.1459, 3.4745, -2.3857, 1.4341, 6.0081, 1.2447, 2.3528, 6.0999, 0.4838, 2.8818, 4.7444, 1.3483, -3.1045, -0.0443, -0.8667, -2.8949, 1.7465, -1.4628, -2.9635, 2.0631, -0.0469, -2.7186, 2.9035, -1.3821, -2.3162, 3.6228, -0.9721, -2.7435, 3.293, -0.5137, -0.1803, 5.4522, -1.6664, -1.544, 3.8438, -1.471, -1.2285, 2.4662, -2.4325, -2.1314, 0.7603, 3.0046, -2.0691, 2.5964, 1.5185, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -1.6818, 3.3981, 1.0198, -1.0522, 1.986, 2.2721, 0.0399, 0.8971, -3.3453, -1.2285, 2.4662, -2.4325, -2.0395, 0.8474, -2.9039, 1.9677, 0.877, 2.8315, 1.5121, 2.8416, 2.3106, 2.2766, 3.3307, 2.1367, 1.9677, 0.877, 2.8315, 1.3747, 1.7762, 2.618, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 2.2766, 3.3307, 2.1367, 1.5121, 2.8416, 2.3106, 0.6919, 3.2703, 2.2188, 0.4487, 4.8708, 1.6514, 2.2766, 3.3307, 2.1367, 3.3947, 0.8197, -1.419, 3.0814, 2.2747, -1.5878, 2.9911, 0.177, -2.5055, 2.8818, 4.7444, 1.3483, 2.3528, 6.0999, 0.4838, 3.481, 4.5391, 0.4718, 2.0681, 1.0413, -2.9849, 2.3543, 3.9299, -1.7328, 1.1494, 2.3687, -2.734, 2.5729, -0.0463, 2.323, 2.8449, 1.8063, 1.8788, 2.6862, -0.2733, 1.344, -1.4897, -2.0239, -2.8933, -2.3373, -3.6333, -2.2624, -1.6189, -3.1121, -2.5666, -1.4897, -2.0239, -2.8933, -2.0489, -0.9812, -3.014, -2.3373, -3.6333, -2.2624, -2.0395, 0.8474, -2.9039, -1.2285, 2.4662, -2.4325, -2.3777, 2.1645, -2.0413, 2.1308, -0.8122, -3.0291, 0.0444, -0.8451, -3.3989, 1.2123, -2.2074, -2.6786, -3.2503, -1.9438, -0.0444, -2.9895, -0.0523, 0.8233, -3.048, -2.4163, 1.5053, -2.9895, -0.0523, 0.8233, -2.9635, 2.0631, -0.0469, -2.7222, 1.5834, 1.3251, -2.8218, -0.0908, -2.3316, -3.1045, -0.0443, -0.8667, -3.1353, -2.4738, -1.533, -0.6296, -2.1168, 3.0274, -0.0319, -0.907, 3.3884, 0.6607, -2.1929, 2.7904, 0.0817, 0.7564, 3.232, -1.0522, 1.986, 2.2721, 0.5064, 1.8556, 2.7003, 3.0694, 0.3768, 0.2917, 3.39, -0.1345, -0.5433, 3.0799, -1.877, -0.2485, 2.332, -3.2513, 1.4491, 1.8096, -3.8731, 0.517, 1.7532, -3.8773, 1.1862, 2.332, -3.2513, 1.4491, 2.4159, -3.3908, 0.0683, 1.8096, -3.8731, 0.517, 3.0694, 0.3768, 0.2917, 3.2927, 2.696, 1.0915, 3.6836, 2.3906, 0.1427, 0.8528, -5.1659, -1.2387, 0.5989, -4.5033, -1.836, -0.129, -6.0155, -1.6627, 1.6723, -4.1113, -1.5062, 2.2303, -3.7001, -1.8804, 1.5835, -3.611, -1.9401, 2.6485, -2.4319, -2.56, 2.1308, -0.8122, -3.0291, 1.2123, -2.2074, -2.6786, -1.2211, -6.6432, -1.3733, -2.3738, -5.4707, -1.3892, -1.8677, -6.7222, 0.0053, 1.2123, -2.2074, -2.6786, 0.0444, -0.8451, -3.3989, -0.5191, -2.1018, -2.9939, -0.9423, -5.2303, -2.2174, -0.0313, -3.8492, -2.3572, -0.9004, -3.6647, -2.5803, -2.9895, -0.0523, 0.8233, -2.9289, -0.1232, 2.5316, -3.048, -2.4163, 1.5053, -3.1353, -2.4738, -1.533, -3.1045, -0.0443, -0.8667, -3.2503, -1.9438, -0.0444, -2.8602, -5.2902, -0.0034, -3.0733, -4.4498, -0.8737, -3.2063, -3.9919, -0.0118, -0.1504, -6.0468, 1.6347, 0.6052, -4.4095, 1.8566, 0.8249, -4.8431, 1.2373, -2.3213, -3.5933, 2.2613, -1.5434, -2.0626, 2.9798, -1.6095, -3.1051, 2.5592, -2.3213, -3.5933, 2.2613, -2.2391, -1.0013, 3.2378, -1.5434, -2.0626, 2.9798, 3.0799, -1.877, -0.2485, 3.39, -0.1345, -0.5433, 3.4106, -2.2767, -1.2285, -0.0319, -0.907, 3.3884, 1.8364, -0.871, 2.91, 0.6607, -2.1929, 2.7904, 1.7532, -3.8773, 1.1862, 1.5121, -3.4321, 1.801, 2.332, -3.2513, 1.4491, -0.9484, -5.3598, 2.2535, -1.6095, -3.1051, 2.5592, -0.8975, -3.7031, 2.6018, -0.9484, -5.3598, 2.2535, -2.3213, -3.5933, 2.2613, -1.6095, -3.1051, 2.5592, 2.4159, -3.3908, 0.0683, 1.3907, -4.3898, 0.264, 1.8096, -3.8731, 0.517, 2.4159, -3.3908, 0.0683, 1.5973, -4.4989, -0.523, 1.3907, -4.3898, 0.264, 0.6607, -2.1929, 2.7904, 1.8364, -0.871, 2.91, 2.1556, -2.2057, 2.3132, -0.6892, -7.3327, -0.0456, 0.3707, -6.5026, 0.2358, 0.1584, -6.6788, -0.712, 2.7182, -1.9153, 1.6333, 2.332, -3.2513, 1.4491, 2.1556, -2.2057, 2.3132, -0.8975, -3.7031, 2.6018, -1.5434, -2.0626, 2.9798, -0.6296, -2.1168, 3.0274, -0.8975, -3.7031, 2.6018, -1.6095, -3.1051, 2.5592, -1.5434, -2.0626, 2.9798, -0.5191, -2.1018, -2.9939, -1.6189, -3.1121, -2.5666, -0.9004, -3.6647, -2.5803, -0.5191, -2.1018, -2.9939, -1.4897, -2.0239, -2.8933, -1.6189, -3.1121, -2.5666, 3.4106, -2.2767, -1.2285, 2.2303, -3.7001, -1.8804, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 3.4189, -1.5098, -2.1519, 2.6485, -2.4319, -2.56, 2.2303, -3.7001, -1.8804, 3.4106, -2.2767, -1.2285, 3.4189, -1.5098, -2.1519, 0.6919, 3.2703, 2.2188, 1.3747, 1.7762, 2.618, 0.5064, 1.8556, 2.7003, 0.6919, 3.2703, 2.2188, 1.5121, 2.8416, 2.3106, 1.3747, 1.7762, 2.618, -2.7186, 2.9035, -1.3821, -2.8949, 1.7465, -1.4628, -2.3777, 2.1645, -2.0413, 3.6143, 2.6541, -0.7787, 3.481, 4.5391, 0.4718, 3.3017, 4.3689, -0.5397, 3.6143, 2.6541, -0.7787, 3.6836, 2.3906, 0.1427, 3.481, 4.5391, 0.4718, 0.7824, 7.1818, -0.4789, -0.2548, 6.3653, 0.3115, -0.2609, 6.3493, -0.8464, 0.7824, 7.1818, -0.4789, 0.9325, 6.924, 0.4365, -0.2548, 6.3653, 0.3115, -0.1504, -6.0468, 1.6347, -1.2352, -6.7568, 1.4079, -0.9484, -5.3598, 2.2535, 1.8096, -3.8731, 0.517, 0.8249, -4.8431, 1.2373, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 1.3907, -4.3898, 0.264, 0.9197, -5.2139, 0.5803, 0.8249, -4.8431, 1.2373, 1.8096, -3.8731, 0.517, 1.3907, -4.3898, 0.264, -0.1302, -3.9225, 2.4564, 1.5121, -3.4321, 1.801, 0.6052, -4.4095, 1.8566, -0.1302, -3.9225, 2.4564, 0.6607, -2.1929, 2.7904, 1.5121, -3.4321, 1.801, 2.4159, -3.3908, 0.0683, 2.791, -2.0828, 0.6971, 3.0799, -1.877, -0.2485, 1.5973, -4.4989, -0.523, 1.6723, -4.1113, -1.5062, 0.8528, -5.1659, -1.2387, -3.0246, -4.4027, 0.8468, -2.3213, -3.5933, 2.2613, -2.3434, -5.4566, 1.3758, -3.0246, -4.4027, 0.8468, -3.048, -2.4163, 1.5053, -2.3213, -3.5933, 2.2613, -1.2211, -6.6432, -1.3733, -0.129, -6.0155, -1.6627, -0.9423, -5.2303, -2.2174, -3.0733, -4.4498, -0.8737, -2.3373, -3.6333, -2.2624, -3.1353, -2.4738, -1.533, -3.0733, -4.4498, -0.8737, -2.3738, -5.4707, -1.3892, -2.3373, -3.6333, -2.2624, 1.5835, -3.611, -1.9401, -0.0313, -3.8492, -2.3572, 0.5989, -4.5033, -1.836, 1.5835, -3.611, -1.9401, 1.2123, -2.2074, -2.6786, -0.0313, -3.8492, -2.3572, 2.5729, -0.0463, 2.323, 1.8364, -0.871, 2.91, 1.9677, 0.877, 2.8315, -2.9289, -0.1232, 2.5316, -2.1314, 0.7603, 3.0046, -2.2391, -1.0013, 3.2378, -2.0395, 0.8474, -2.9039, -2.8218, -0.0908, -2.3316, -2.0489, -0.9812, -3.014, 2.1308, -0.8122, -3.0291, 2.9911, 0.177, -2.5055, 2.0681, 1.0413, -2.9849, 3.2927, 2.696, 1.0915, 2.2766, 3.3307, 2.1367, 2.8818, 4.7444, 1.3483, 3.2927, 2.696, 1.0915, 2.8449, 1.8063, 1.8788, 2.2766, 3.3307, 2.1367, -2.9635, 2.0631, -0.0469, -2.7435, 3.293, -0.5137, -2.3926, 3.3478, 0.3255, 2.3528, 6.0999, 0.4838, 1.5686, 6.857, -0.4389, 2.4165, 5.7335, -0.6508, -2.3162, 3.6228, -0.9721, -1.137, 5.0901, -0.2048, -1.8973, 4.0438, -0.0231, -2.3162, 3.6228, -0.9721, -1.544, 3.8438, -1.471, -1.137, 5.0901, -0.2048, 0.4374, 4.5707, -2.224, 0.9515, 6.8097, -1.0683, -0.1803, 5.4522, -1.6664, 0.4374, 4.5707, -2.224, 1.4379, 5.2295, -1.8152, 0.9515, 6.8097, -1.0683, -1.2352, -6.7568, 1.4079, 0.3707, -6.5026, 0.2358, -0.6892, -7.3327, -0.0456, -1.2352, -6.7568, 1.4079, -0.1504, -6.0468, 1.6347, 0.3707, -6.5026, 0.2358, 0.6607, -2.1929, 2.7904, -0.8975, -3.7031, 2.6018, -0.6296, -2.1168, 3.0274, 0.6607, -2.1929, 2.7904, -0.1302, -3.9225, 2.4564, -0.8975, -3.7031, 2.6018, 2.791, -2.0828, 0.6971, 2.332, -3.2513, 1.4491, 2.7182, -1.9153, 1.6333, 2.791, -2.0828, 0.6971, 2.4159, -3.3908, 0.0683, 2.332, -3.2513, 1.4491, 1.6723, -4.1113, -1.5062, 2.6374, -3.7219, -1.0723, 2.2303, -3.7001, -1.8804, 1.6723, -4.1113, -1.5062, 1.5973, -4.4989, -0.523, 2.6374, -3.7219, -1.0723, -3.048, -2.4163, 1.5053, -3.2063, -3.9919, -0.0118, -3.2503, -1.9438, -0.0444, -3.048, -2.4163, 1.5053, -3.0246, -4.4027, 0.8468, -3.2063, -3.9919, -0.0118, -0.129, -6.0155, -1.6627, -0.6892, -7.3327, -0.0456, 0.1584, -6.6788, -0.712, -0.129, -6.0155, -1.6627, -1.2211, -6.6432, -1.3733, -0.6892, -7.3327, -0.0456, 1.2123, -2.2074, -2.6786, 2.2303, -3.7001, -1.8804, 2.6485, -2.4319, -2.56, 1.2123, -2.2074, -2.6786, 1.5835, -3.611, -1.9401, 2.2303, -3.7001, -1.8804, 3.3947, 0.8197, -1.419, 3.6836, 2.3906, 0.1427, 3.6143, 2.6541, -0.7787, 3.3947, 0.8197, -1.419, 3.39, -0.1345, -0.5433, 3.6836, 2.3906, 0.1427, -2.1314, 0.7603, 3.0046, -2.7222, 1.5834, 1.3251, -2.0691, 2.5964, 1.5185, -2.1314, 0.7603, 3.0046, -2.9289, -0.1232, 2.5316, -2.7222, 1.5834, 1.3251, 2.9911, 0.177, -2.5055, 2.6485, -2.4319, -2.56, 3.4189, -1.5098, -2.1519, 2.9911, 0.177, -2.5055, 2.1308, -0.8122, -3.0291, 2.6485, -2.4319, -2.56, -2.7435, 3.293, -0.5137, -2.8949, 1.7465, -1.4628, -2.7186, 2.9035, -1.3821, -2.7435, 3.293, -0.5137, -2.9635, 2.0631, -0.0469, -2.8949, 1.7465, -1.4628, -0.1459, 3.4745, -2.3857, 1.1494, 2.3687, -2.734, 0.549, 3.3793, -2.5003, -0.1459, 3.4745, -2.3857, -1.2285, 2.4662, -2.4325, 1.1494, 2.3687, -2.734, 2.3543, 3.9299, -1.7328, 3.6143, 2.6541, -0.7787, 3.3017, 4.3689, -0.5397, 2.3543, 3.9299, -1.7328, 3.0814, 2.2747, -1.5878, 3.6143, 2.6541, -0.7787, 1.5686, 6.857, -0.4389, 0.9325, 6.924, 0.4365, 0.7824, 7.1818, -0.4789, 1.5686, 6.857, -0.4389, 2.3528, 6.0999, 0.4838, 0.9325, 6.924, 0.4365, -1.544, 3.8438, -1.471, -2.7186, 2.9035, -1.3821, -2.3777, 2.1645, -2.0413, -1.544, 3.8438, -1.471, -2.3162, 3.6228, -0.9721, -2.7186, 2.9035, -1.3821, 1.4379, 5.2295, -1.8152, 0.549, 3.3793, -2.5003, 1.1494, 2.3687, -2.734, 1.4379, 5.2295, -1.8152, 0.4374, 4.5707, -2.224, 0.549, 3.3793, -2.5003, 2.1556, -2.2057, 2.3132, 1.5121, -3.4321, 1.801, 0.6607, -2.1929, 2.7904, 2.1556, -2.2057, 2.3132, 2.332, -3.2513, 1.4491, 1.5121, -3.4321, 1.801, 3.4106, -2.2767, -1.2285, 2.4159, -3.3908, 0.0683, 3.0799, -1.877, -0.2485, 3.4106, -2.2767, -1.2285, 2.6374, -3.7219, -1.0723, 2.4159, -3.3908, 0.0683, 0.1584, -6.6788, -0.712, 1.5973, -4.4989, -0.523, 0.8528, -5.1659, -1.2387, 0.1584, -6.6788, -0.712, 0.3707, -6.5026, 0.2358, 1.5973, -4.4989, -0.523, -3.2503, -1.9438, -0.0444, -3.0733, -4.4498, -0.8737, -3.1353, -2.4738, -1.533, -3.2503, -1.9438, -0.0444, -3.2063, -3.9919, -0.0118, -3.0733, -4.4498, -0.8737, -0.5191, -2.1018, -2.9939, -0.0313, -3.8492, -2.3572, 1.2123, -2.2074, -2.6786, -0.5191, -2.1018, -2.9939, -0.9004, -3.6647, -2.5803, -0.0313, -3.8492, -2.3572, 3.4189, -1.5098, -2.1519, 3.39, -0.1345, -0.5433, 3.3947, 0.8197, -1.419, 3.4189, -1.5098, -2.1519, 3.4106, -2.2767, -1.2285, 3.39, -0.1345, -0.5433, 2.7182, -1.9153, 1.6333, 1.8364, -0.871, 2.91, 2.5729, -0.0463, 2.323, 2.7182, -1.9153, 1.6333, 2.1556, -2.2057, 2.3132, 1.8364, -0.871, 2.91, -2.3777, 2.1645, -2.0413, -2.8218, -0.0908, -2.3316, -2.0395, 0.8474, -2.9039, -2.3777, 2.1645, -2.0413, -2.8949, 1.7465, -1.4628, -2.8218, -0.0908, -2.3316, 3.481, 4.5391, 0.4718, 3.2927, 2.696, 1.0915, 2.8818, 4.7444, 1.3483, 3.481, 4.5391, 0.4718, 3.6836, 2.3906, 0.1427, 3.2927, 2.696, 1.0915, 0.6919, 3.2703, 2.2188, -1.0522, 1.986, 2.2721, -0.2637, 3.5029, 1.8153, 0.6919, 3.2703, 2.2188, 0.5064, 1.8556, 2.7003, -1.0522, 1.986, 2.2721, -2.0691, 2.5964, 1.5185, -2.9635, 2.0631, -0.0469, -2.3926, 3.3478, 0.3255, -2.0691, 2.5964, 1.5185, -2.7222, 1.5834, 1.3251, -2.9635, 2.0631, -0.0469, -0.2548, 6.3653, 0.3115, 1.4341, 6.0081, 1.2447, 0.4487, 4.8708, 1.6514, -0.2548, 6.3653, 0.3115, 0.9325, 6.924, 0.4365, 1.4341, 6.0081, 1.2447, 3.3017, 4.3689, -0.5397, 2.3528, 6.0999, 0.4838, 2.4165, 5.7335, -0.6508, 3.3017, 4.3689, -0.5397, 3.481, 4.5391, 0.4718, 2.3528, 6.0999, 0.4838, -0.2609, 6.3493, -0.8464, -1.137, 5.0901, -0.2048, -1.544, 3.8438, -1.471, -0.2609, 6.3493, -0.8464, -0.2548, 6.3653, 0.3115, -1.137, 5.0901, -0.2048, 0.7824, 7.1818, -0.4789, -0.1803, 5.4522, -1.6664, 0.9515, 6.8097, -1.0683, 0.7824, 7.1818, -0.4789, -0.2609, 6.3493, -0.8464, -0.1803, 5.4522, -1.6664, 2.4165, 5.7335, -0.6508, 0.9515, 6.8097, -1.0683, 1.4379, 5.2295, -1.8152, 2.4165, 5.7335, -0.6508, 1.5686, 6.857, -0.4389, 0.9515, 6.8097, -1.0683, 0.4374, 4.5707, -2.224, -1.2285, 2.4662, -2.4325, -0.1459, 3.4745, -2.3857, 0.4374, 4.5707, -2.224, -0.1803, 5.4522, -1.6664, -1.2285, 2.4662, -2.4325, -1.8973, 4.0438, -0.0231, -0.7059, 4.7856, 0.8682, -1.6818, 3.3981, 1.0198, -1.8973, 4.0438, -0.0231, -1.137, 5.0901, -0.2048, -0.7059, 4.7856, 0.8682, -2.3162, 3.6228, -0.9721, -2.3926, 3.3478, 0.3255, -2.7435, 3.293, -0.5137, -2.3162, 3.6228, -0.9721, -1.8973, 4.0438, -0.0231, -2.3926, 3.3478, 0.3255, -1.6818, 3.3981, 1.0198, -0.2637, 3.5029, 1.8153, -1.0522, 1.986, 2.2721, -1.6818, 3.3981, 1.0198, -0.7059, 4.7856, 0.8682, -0.2637, 3.5029, 1.8153, 0.4487, 4.8708, 1.6514, 2.8818, 4.7444, 1.3483, 2.2766, 3.3307, 2.1367, 0.4487, 4.8708, 1.6514, 1.4341, 6.0081, 1.2447, 2.8818, 4.7444, 1.3483, 3.0814, 2.2747, -1.5878, 2.0681, 1.0413, -2.9849, 2.9911, 0.177, -2.5055, 3.0814, 2.2747, -1.5878, 2.3543, 3.9299, -1.7328, 2.0681, 1.0413, -2.9849, 0.0444, -0.8451, -3.3989, -2.0395, 0.8474, -2.9039, -2.0489, -0.9812, -3.014, 0.0444, -0.8451, -3.3989, 0.0399, 0.8971, -3.3453, -2.0395, 0.8474, -2.9039, -2.2391, -1.0013, 3.2378, 0.0817, 0.7564, 3.232, -0.0319, -0.907, 3.3884, -2.2391, -1.0013, 3.2378, -2.1314, 0.7603, 3.0046, 0.0817, 0.7564, 3.232, 2.5729, -0.0463, 2.323, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 2.5729, -0.0463, 2.323, 1.9677, 0.877, 2.8315, 2.2766, 3.3307, 2.1367, 2.8449, 1.8063, 1.8788, 3.0694, 0.3768, 0.2917, 2.6862, -0.2733, 1.344, 2.8449, 1.8063, 1.8788, 3.2927, 2.696, 1.0915, 3.0694, 0.3768, 0.2917, 2.1308, -0.8122, -3.0291, 0.0399, 0.8971, -3.3453, 0.0444, -0.8451, -3.3989, 2.1308, -0.8122, -3.0291, 2.0681, 1.0413, -2.9849, 0.0399, 0.8971, -3.3453, -2.0489, -0.9812, -3.014, -3.1353, -2.4738, -1.533, -2.3373, -3.6333, -2.2624, -2.0489, -0.9812, -3.014, -2.8218, -0.0908, -2.3316, -3.1353, -2.4738, -1.533, -2.9289, -0.1232, 2.5316, -2.3213, -3.5933, 2.2613, -3.048, -2.4163, 1.5053, -2.9289, -0.1232, 2.5316, -2.2391, -1.0013, 3.2378, -2.3213, -3.5933, 2.2613, -0.0319, -0.907, 3.3884, 1.9677, 0.877, 2.8315, 1.8364, -0.871, 2.91, -0.0319, -0.907, 3.3884, 0.0817, 0.7564, 3.232, 1.9677, 0.877, 2.8315, 2.6862, -0.2733, 1.344, 3.0799, -1.877, -0.2485, 2.791, -2.0828, 0.6971, 2.6862, -0.2733, 1.344, 3.0694, 0.3768, 0.2917, 3.0799, -1.877, -0.2485, 0.8528, -5.1659, -1.2387, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, 0.8528, -5.1659, -1.2387, 1.6723, -4.1113, -1.5062, 1.5835, -3.611, -1.9401, 0.5989, -4.5033, -1.836, -0.9423, -5.2303, -2.2174, -0.129, -6.0155, -1.6627, 0.5989, -4.5033, -1.836, -0.0313, -3.8492, -2.3572, -0.9423, -5.2303, -2.2174, -1.2211, -6.6432, -1.3733, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -1.2211, -6.6432, -1.3733, -0.9423, -5.2303, -2.2174, -2.3373, -3.6333, -2.2624, -2.3738, -5.4707, -1.3892, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -2.3738, -5.4707, -1.3892, -3.0733, -4.4498, -0.8737, -2.8602, -5.2902, -0.0034, -1.8677, -6.7222, 0.0053, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -1.8677, -6.7222, 0.0053, -2.8602, -5.2902, -0.0034, -3.0246, -4.4027, 0.8468, -2.3434, -5.4566, 1.3758, -0.9484, -5.3598, 2.2535, -1.2352, -6.7568, 1.4079, -2.3434, -5.4566, 1.3758, -2.3213, -3.5933, 2.2613, -0.9484, -5.3598, 2.2535, -0.1504, -6.0468, 1.6347, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, -0.1504, -6.0468, 1.6347, -0.9484, -5.3598, 2.2535, -0.1302, -3.9225, 2.4564, 0.6052, -4.4095, 1.8566, 1.7532, -3.8773, 1.1862, 0.8249, -4.8431, 1.2373, 0.6052, -4.4095, 1.8566, 1.5121, -3.4321, 1.801, 1.7532, -3.8773, 1.1862, -0.6892, -7.3327, -0.0456, -1.8677, -6.7222, 0.0053, -1.2352, -6.7568, 1.4079)
+
+[node name="RockC" instance=ExtResource("1_pb27A")]
+collision_mask = 3
+
+[node name="Rock3" parent="." index="0"]
+surface_material_override/0 = ExtResource("2_pcex8")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"]
+shape = SubResource("ConcavePolygonShape3D_ycsjC")
diff --git a/demo/assets/models/Tunnel.glb b/demo/assets/models/Tunnel.glb
new file mode 100644
index 0000000..ce0cc4f
Binary files /dev/null and b/demo/assets/models/Tunnel.glb differ
diff --git a/demo/assets/models/Tunnel.glb.import b/demo/assets/models/Tunnel.glb.import
new file mode 100644
index 0000000..e9fac26
--- /dev/null
+++ b/demo/assets/models/Tunnel.glb.import
@@ -0,0 +1,44 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://jib546bbort5"
+path="res://.godot/imported/Tunnel.glb-09729773791c3078e933c866961e1b60.scn"
+
+[deps]
+
+source_file="res://demo/assets/models/Tunnel.glb"
+dest_files=["res://.godot/imported/Tunnel.glb-09729773791c3078e933c866961e1b60.scn"]
+
+[params]
+
+nodes/root_type="StaticBody3D"
+nodes/root_name="Scene Root"
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=true
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+_subresources={
+"materials": {
+"material_0": {
+"use_external/enabled": false,
+"use_external/path": "uid://nbbdrx8vma80"
+}
+}
+}
+gltf/naming_version=0
+gltf/embedded_image_handling=1
diff --git a/demo/assets/models/Tunnel.tscn b/demo/assets/models/Tunnel.tscn
new file mode 100644
index 0000000..d2fdf8d
--- /dev/null
+++ b/demo/assets/models/Tunnel.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=3 uid="uid://vvayjv3rbx1d"]
+
+[ext_resource type="PackedScene" uid="uid://jib546bbort5" path="res://demo/assets/models/Tunnel.glb" id="1_1sr0i"]
+[ext_resource type="Material" uid="uid://nbbdrx8vma80" path="res://demo/assets/materials/M_rock23_tp.tres" id="2_lkb1r"]
+
+[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_bh6xx"]
+data = PackedVector3Array(266.67, 5.3424, -18.2649, 266.427, 5.495, -16.0799, 266.642, 5.495, -18.2666, 267.292, 1.9054, -18.2263, 267.068, 1.9054, -15.9524, 266.67, 5.3424, -18.2649, 267.068, 1.9054, -15.9524, 266.427, 5.495, -16.0799, 266.67, 5.3424, -18.2649, 264.802, 8.7484, -18.3807, 264.54, 8.8761, -16.4552, 264.73, 8.8761, -18.3851, 266.642, 5.495, -18.2666, 266.427, 5.495, -16.0799, 264.802, 8.7484, -18.3807, 266.427, 5.495, -16.0799, 264.54, 8.8761, -16.4552, 264.802, 8.7484, -18.3807, 261.76, 11.7626, -18.5693, 261.518, 11.852, -17.0563, 261.668, 11.852, -18.575, 264.73, 8.8761, -18.3851, 264.54, 8.8761, -16.4552, 261.76, 11.7626, -18.5693, 264.54, 8.8761, -16.4552, 261.518, 11.852, -17.0563, 261.76, 11.7626, -18.5693, 257.712, 14.2028, -18.8203, 257.536, 14.2498, -17.8483, 257.633, 14.2498, -18.8252, 261.668, 11.852, -18.575, 261.518, 11.852, -17.0563, 257.712, 14.2028, -18.8203, 261.518, 11.852, -17.0563, 257.536, 14.2498, -17.8483, 257.712, 14.2028, -18.8203, 252.892, 15.9187, -19.1192, 252.826, 15.9302, -18.7853, 252.859, 15.9302, -19.1212, 257.633, 14.2498, -18.8252, 257.536, 14.2498, -17.8483, 252.892, 15.9187, -19.1192, 257.536, 14.2498, -17.8483, 252.826, 15.9302, -18.7853, 252.892, 15.9187, -19.1192, 252.859, 15.9302, -19.1212, 252.826, 15.9302, -18.7853, 250.357, 16.3438, -19.2763, 252.823, -12.1253, -19.1234, 252.826, -12.1193, -18.7853, 252.859, -12.1193, -19.1212, 250.357, -12.5329, -19.2763, 252.826, -12.1193, -18.7853, 252.823, -12.1253, -19.1234, 257.538, -10.4722, -18.8311, 257.536, -10.4389, -17.8483, 257.633, -10.4389, -18.8252, 252.859, -12.1193, -19.1212, 252.826, -12.1193, -18.7853, 257.538, -10.4722, -18.8311, 252.826, -12.1193, -18.7853, 257.536, -10.4389, -17.8483, 257.538, -10.4722, -18.8311, 261.546, -8.1137, -18.5826, 261.518, -8.0411, -17.0563, 261.668, -8.0411, -18.575, 257.633, -10.4389, -18.8252, 257.536, -10.4389, -17.8483, 261.546, -8.1137, -18.5826, 257.536, -10.4389, -17.8483, 261.518, -8.0411, -17.0563, 261.546, -8.1137, -18.5826, 264.614, -5.1782, -18.3923, 264.54, -5.0652, -16.4552, 264.73, -5.0652, -18.3851, 261.668, -8.0411, -18.575, 261.518, -8.0411, -17.0563, 264.614, -5.1782, -18.3923, 261.518, -8.0411, -17.0563, 264.54, -5.0652, -16.4552, 264.614, -5.1782, -18.3923, 266.56, -1.8284, -18.2717, 266.427, -1.6842, -16.0799, 266.642, -1.6842, -18.2666, 264.73, -5.0652, -18.3851, 264.54, -5.0652, -16.4552, 266.56, -1.8284, -18.2717, 264.54, -5.0652, -16.4552, 266.427, -1.6842, -16.0799, 266.56, -1.8284, -18.2717, 267.263, 1.7469, -18.2281, 267.068, 1.9054, -15.9524, 267.292, 1.9054, -18.2263, 266.642, -1.6842, -18.2666, 266.427, -1.6842, -16.0799, 267.263, 1.7469, -18.2281, 266.427, -1.6842, -16.0799, 267.068, 1.9054, -15.9524, 267.263, 1.7469, -18.2281, 267.068, 1.9054, -15.9524, 251.436, 5.495, 33.3375, 266.427, 5.495, -16.0799, 252.04, 1.9054, 33.5877, 251.436, 5.495, 33.3375, 267.068, 1.9054, -15.9524, 266.427, 5.495, -16.0799, 249.659, 8.876, 32.6014, 264.54, 8.8761, -16.4552, 251.436, 5.495, 33.3375, 249.659, 8.876, 32.6014, 266.427, 5.495, -16.0799, 264.54, 8.8761, -16.4552, 246.812, 11.8519, 31.4223, 261.518, 11.852, -17.0563, 249.659, 8.876, 32.6014, 246.812, 11.8519, 31.4223, 264.54, 8.8761, -16.4552, 261.518, 11.852, -17.0563, 243.061, 14.2498, 29.8687, 257.536, 14.2498, -17.8483, 246.812, 11.8519, 31.4223, 243.061, 14.2498, 29.8687, 261.518, 11.852, -17.0563, 257.536, 14.2498, -17.8483, 238.624, 15.9302, 28.0308, 252.826, 15.9302, -18.7853, 243.061, 14.2498, 29.8687, 238.624, 15.9302, 28.0308, 257.536, 14.2498, -17.8483, 250.357, 16.3438, -19.2763, 252.826, 15.9302, -18.7853, 247.551, 16.7956, -19.4503, 252.826, 15.9302, -18.7853, 233.759, 16.7956, 26.0154, 247.551, 16.7956, -19.4503, 238.624, 15.9302, 28.0308, 233.759, 16.7956, 26.0154, 252.826, 15.9302, -18.7853, 247.505, 16.7956, -19.4531, 228.748, 16.7956, 23.9398, 242.014, 16.7956, -19.7936, 247.551, 16.7956, -19.4503, 233.759, 16.7956, 26.0154, 247.505, 16.7956, -19.4531, 233.759, 16.7956, 26.0154, 228.748, 16.7956, 23.9398, 247.505, 16.7956, -19.4531, 241.88, 16.774, -19.8019, 223.882, 15.9302, 21.9244, 236.638, 15.9302, -20.1269, 242.014, 16.7956, -19.7936, 228.748, 16.7956, 23.9398, 241.88, 16.774, -19.8019, 228.748, 16.7956, 23.9398, 223.882, 15.9302, 21.9244, 241.88, 16.774, -19.8019, 236.433, 15.8598, -20.1396, 219.445, 14.2498, 20.0865, 231.736, 14.2498, -20.4309, 236.638, 15.9302, -20.1269, 223.882, 15.9302, 21.9244, 236.433, 15.8598, -20.1396, 223.882, 15.9302, 21.9244, 219.445, 14.2498, 20.0865, 236.433, 15.8598, -20.1396, 231.497, 14.1113, -20.4457, 215.694, 11.8519, 18.5328, 227.592, 11.8519, -20.6878, 231.736, 14.2498, -20.4309, 219.445, 14.2498, 20.0865, 231.497, 14.1113, -20.4457, 219.445, 14.2498, 20.0865, 215.694, 11.8519, 18.5328, 231.497, 14.1113, -20.4457, 227.367, 11.6395, -20.7018, 212.848, 8.876, 17.3537, 224.447, 8.876, -20.8828, 227.592, 11.8519, -20.6878, 215.694, 11.8519, 18.5328, 227.367, 11.6395, -20.7018, 215.694, 11.8519, 18.5328, 212.848, 8.876, 17.3537, 227.367, 11.6395, -20.7018, 224.286, 8.6, -20.8928, 211.071, 5.4951, 16.6177, 222.483, 5.4951, -21.0046, 224.447, 8.876, -20.8828, 212.848, 8.876, 17.3537, 224.286, 8.6, -20.8928, 212.848, 8.876, 17.3537, 211.071, 5.495, 16.6177, 224.286, 8.6, -20.8928, 222.425, 5.18, -21.0082, 210.467, 1.9054, 16.3675, 221.816, 1.9054, -21.046, 222.483, 5.4951, -21.0046, 211.071, 5.495, 16.6177, 222.425, 5.18, -21.0082, 211.071, 5.495, 16.6177, 210.467, 1.9054, 16.3675, 222.425, 5.18, -21.0082, 221.876, 1.5852, -21.0423, 211.071, -1.6842, 16.6177, 222.483, -1.6842, -21.0046, 221.816, 1.9054, -21.046, 210.467, 1.9054, 16.3675, 221.876, 1.5852, -21.0423, 210.467, 1.9054, 16.3675, 211.071, -1.6842, 16.6177, 221.876, 1.5852, -21.0423, 222.652, -1.975, -20.9941, 212.848, -5.0652, 17.3537, 224.447, -5.0652, -20.8828, 222.483, -1.6842, -21.0046, 211.071, -1.6842, 16.6177, 222.652, -1.975, -20.9941, 211.071, -1.6842, 16.6177, 212.848, -5.0652, 17.3537, 222.652, -1.975, -20.9941, 224.694, -5.2991, -20.8675, 215.694, -8.0411, 18.5329, 227.592, -8.0411, -20.6878, 224.447, -5.0652, -20.8828, 212.848, -5.0652, 17.3537, 224.694, -5.2991, -20.8675, 212.848, -5.0652, 17.3537, 215.694, -8.0411, 18.5329, 224.694, -5.2991, -20.8675, 227.872, -8.2033, -20.6705, 219.445, -10.4389, 20.0865, 231.736, -10.4389, -20.4309, 227.592, -8.0411, -20.6878, 215.694, -8.0411, 18.5329, 227.872, -8.2033, -20.6705, 215.694, -8.0411, 18.5329, 219.445, -10.4389, 20.0865, 227.872, -8.2033, -20.6705, 232.001, -10.5298, -20.4144, 223.882, -12.1193, 21.9244, 236.638, -12.1193, -20.1269, 231.736, -10.4389, -20.4309, 219.445, -10.4389, 20.0865, 232.001, -10.5298, -20.4144, 219.445, -10.4389, 20.0865, 223.882, -12.1193, 21.9244, 232.001, -10.5298, -20.4144, 236.848, -12.153, -20.1139, 228.748, -12.9847, 23.9398, 242.014, -12.9847, -19.7936, 236.638, -12.1193, -20.1269, 223.882, -12.1193, 21.9244, 236.848, -12.153, -20.1139, 223.882, -12.1193, 21.9244, 228.748, -12.9847, 23.9398, 236.848, -12.153, -20.1139, 242.142, -12.9847, -19.7857, 233.759, -12.9847, 26.0154, 247.551, -12.9847, -19.4503, 242.014, -12.9847, -19.7936, 228.748, -12.9847, 23.9398, 242.142, -12.9847, -19.7857, 228.748, -12.9847, 23.9398, 233.759, -12.9847, 26.0154, 242.142, -12.9847, -19.7857, 247.592, -12.9781, -19.4478, 238.624, -12.1193, 28.0308, 252.826, -12.1193, -18.7853, 247.592, -12.9781, -19.4478, 252.826, -12.1193, -18.7853, 250.357, -12.5329, -19.2763, 247.551, -12.9847, -19.4503, 233.759, -12.9847, 26.0154, 247.592, -12.9781, -19.4478, 233.759, -12.9847, 26.0154, 238.624, -12.1193, 28.0308, 247.592, -12.9781, -19.4478, 252.826, -12.1193, -18.7853, 243.061, -10.4389, 29.8687, 257.536, -10.4389, -17.8483, 238.624, -12.1193, 28.0308, 243.061, -10.4389, 29.8687, 252.826, -12.1193, -18.7853, 257.536, -10.4389, -17.8483, 246.812, -8.0411, 31.4223, 261.518, -8.0411, -17.0563, 243.061, -10.4389, 29.8687, 246.812, -8.0411, 31.4223, 257.536, -10.4389, -17.8483, 261.518, -8.0411, -17.0563, 249.659, -5.0652, 32.6014, 264.54, -5.0652, -16.4552, 246.812, -8.0411, 31.4223, 249.659, -5.0652, 32.6014, 261.518, -8.0411, -17.0563, 264.54, -5.0652, -16.4552, 251.436, -1.6842, 33.3375, 266.427, -1.6842, -16.0799, 249.659, -5.0652, 32.6014, 251.436, -1.6842, 33.3375, 264.54, -5.0652, -16.4552, 266.427, -1.6842, -16.0799, 252.04, 1.9054, 33.5877, 267.068, 1.9054, -15.9524, 251.436, -1.6842, 33.3375, 252.04, 1.9054, 33.5877, 266.427, -1.6842, -16.0799, 252.04, 1.9054, 33.5877, 227.092, 5.495, 78.8808, 251.436, 5.495, 33.3375, 227.636, 1.9054, 79.2441, 227.092, 5.495, 78.8808, 252.04, 1.9054, 33.5877, 251.436, 5.495, 33.3375, 225.493, 8.876, 77.8123, 249.659, 8.876, 32.6014, 227.092, 5.495, 78.8808, 225.493, 8.876, 77.8123, 251.436, 5.495, 33.3375, 249.659, 8.876, 32.6014, 222.931, 11.8519, 76.1005, 246.812, 11.8519, 31.4223, 225.493, 8.876, 77.8123, 222.931, 11.8519, 76.1005, 249.659, 8.876, 32.6014, 246.812, 11.8519, 31.4223, 219.556, 14.2498, 73.8449, 243.061, 14.2498, 29.8687, 222.931, 11.8519, 76.1005, 219.556, 14.2498, 73.8449, 246.812, 11.8519, 31.4223, 243.061, 14.2498, 29.8687, 215.562, 15.9302, 71.1767, 238.624, 15.9302, 28.0308, 219.556, 14.2498, 73.8449, 215.562, 15.9302, 71.1767, 243.061, 14.2498, 29.8687, 238.624, 15.9302, 28.0308, 211.184, 16.7956, 68.2508, 233.759, 16.7956, 26.0154, 215.562, 15.9302, 71.1767, 211.184, 16.7956, 68.2508, 238.624, 15.9302, 28.0308, 233.759, 16.7956, 26.0154, 206.674, 16.7956, 65.2375, 228.748, 16.7956, 23.9398, 211.184, 16.7956, 68.2508, 206.674, 16.7956, 65.2375, 233.759, 16.7956, 26.0154, 228.748, 16.7956, 23.9398, 202.295, 15.9302, 62.3116, 223.882, 15.9302, 21.9244, 206.674, 16.7956, 65.2375, 202.295, 15.9302, 62.3116, 228.748, 16.7956, 23.9398, 223.882, 15.9302, 21.9244, 198.302, 14.2498, 59.6434, 219.445, 14.2498, 20.0865, 202.295, 15.9302, 62.3116, 198.302, 14.2498, 59.6434, 223.882, 15.9302, 21.9244, 219.445, 14.2498, 20.0865, 194.926, 11.8519, 57.3878, 215.694, 11.8519, 18.5328, 198.302, 14.2498, 59.6434, 194.926, 11.8519, 57.3878, 219.445, 14.2498, 20.0865, 215.694, 11.8519, 18.5328, 192.364, 8.876, 55.676, 212.848, 8.876, 17.3537, 194.926, 11.8519, 57.3878, 192.364, 8.876, 55.676, 215.694, 11.8519, 18.5328, 212.848, 8.876, 17.3537, 190.765, 5.495, 54.6075, 211.071, 5.495, 16.6177, 192.364, 8.876, 55.676, 190.765, 5.495, 54.6075, 212.848, 8.876, 17.3537, 211.071, 5.495, 16.6177, 190.221, 1.9054, 54.2442, 210.467, 1.9054, 16.3675, 190.765, 5.495, 54.6075, 190.221, 1.9054, 54.2442, 211.071, 5.495, 16.6177, 210.467, 1.9054, 16.3675, 190.765, -1.6842, 54.6075, 211.071, -1.6842, 16.6177, 190.221, 1.9054, 54.2442, 190.765, -1.6842, 54.6075, 210.467, 1.9054, 16.3675, 211.071, -1.6842, 16.6177, 192.364, -5.0652, 55.676, 212.848, -5.0652, 17.3537, 190.765, -1.6842, 54.6075, 192.364, -5.0652, 55.676, 211.071, -1.6842, 16.6177, 212.848, -5.0652, 17.3537, 194.926, -8.0411, 57.3878, 215.694, -8.0411, 18.5329, 192.364, -5.0652, 55.676, 194.926, -8.0411, 57.3878, 212.848, -5.0652, 17.3537, 215.694, -8.0411, 18.5329, 198.302, -10.4389, 59.6434, 219.445, -10.4389, 20.0865, 194.926, -8.0411, 57.3878, 198.302, -10.4389, 59.6434, 215.694, -8.0411, 18.5329, 219.445, -10.4389, 20.0865, 202.295, -12.1193, 62.3116, 223.882, -12.1193, 21.9244, 198.302, -10.4389, 59.6434, 202.295, -12.1193, 62.3116, 219.445, -10.4389, 20.0865, 223.882, -12.1193, 21.9244, 206.674, -12.9847, 65.2375, 228.748, -12.9847, 23.9398, 202.295, -12.1193, 62.3116, 206.674, -12.9847, 65.2375, 223.882, -12.1193, 21.9244, 228.748, -12.9847, 23.9398, 211.184, -12.9847, 68.2508, 233.759, -12.9847, 26.0154, 206.674, -12.9847, 65.2375, 211.184, -12.9847, 68.2508, 228.748, -12.9847, 23.9398, 233.759, -12.9847, 26.0154, 215.562, -12.1193, 71.1767, 238.624, -12.1193, 28.0308, 211.184, -12.9847, 68.2508, 215.562, -12.1193, 71.1767, 233.759, -12.9847, 26.0154, 238.624, -12.1193, 28.0308, 219.556, -10.4389, 73.8449, 243.061, -10.4389, 29.8687, 215.562, -12.1193, 71.1767, 219.556, -10.4389, 73.8449, 238.624, -12.1193, 28.0308, 243.061, -10.4389, 29.8687, 222.931, -8.0411, 76.1005, 246.812, -8.0411, 31.4223, 219.556, -10.4389, 73.8449, 222.931, -8.0411, 76.1005, 243.061, -10.4389, 29.8687, 246.812, -8.0411, 31.4223, 225.493, -5.0652, 77.8123, 249.659, -5.0652, 32.6014, 222.931, -8.0411, 76.1005, 225.493, -5.0652, 77.8123, 246.812, -8.0411, 31.4223, 249.659, -5.0652, 32.6014, 227.092, -1.6842, 78.8808, 251.436, -1.6842, 33.3375, 225.493, -5.0652, 77.8123, 227.092, -1.6842, 78.8808, 249.659, -5.0652, 32.6014, 251.436, -1.6842, 33.3375, 227.636, 1.9054, 79.2441, 252.04, 1.9054, 33.5877, 227.092, -1.6842, 78.8808, 227.636, 1.9054, 79.2441, 251.436, -1.6842, 33.3375, 227.636, 1.9054, 79.2441, 194.332, 5.495, 118.8, 227.092, 5.495, 78.8808, 194.794, 1.9054, 119.262, 194.332, 5.495, 118.8, 227.636, 1.9054, 79.2441, 227.092, 5.495, 78.8808, 192.972, 8.876, 117.44, 225.493, 8.876, 77.8123, 194.332, 5.495, 118.8, 192.972, 8.876, 117.44, 227.092, 5.495, 78.8808, 225.493, 8.876, 77.8123, 190.793, 11.8519, 115.261, 222.931, 11.8519, 76.1005, 192.972, 8.876, 117.44, 190.793, 11.8519, 115.261, 225.493, 8.876, 77.8123, 222.931, 11.8519, 76.1005, 187.922, 14.2498, 112.39, 219.556, 14.2498, 73.8449, 190.793, 11.8519, 115.261, 187.922, 14.2498, 112.39, 222.931, 11.8519, 76.1005, 219.556, 14.2498, 73.8449, 184.526, 15.9302, 108.994, 215.562, 15.9302, 71.1767, 187.922, 14.2498, 112.39, 184.526, 15.9302, 108.994, 219.556, 14.2498, 73.8449, 215.562, 15.9302, 71.1767, 180.802, 16.7956, 105.271, 211.184, 16.7956, 68.2508, 184.526, 15.9302, 108.994, 180.802, 16.7956, 105.271, 215.562, 15.9302, 71.1767, 211.184, 16.7956, 68.2508, 176.967, 16.7956, 101.435, 206.674, 16.7956, 65.2375, 180.802, 16.7956, 105.271, 176.967, 16.7956, 101.435, 211.184, 16.7956, 68.2508, 206.674, 16.7956, 65.2375, 173.243, 15.9302, 97.7113, 202.295, 15.9302, 62.3116, 176.967, 16.7956, 101.435, 173.243, 15.9302, 97.7113, 206.674, 16.7956, 65.2375, 202.295, 15.9302, 62.3116, 169.847, 14.2498, 94.3153, 198.302, 14.2498, 59.6434, 173.243, 15.9302, 97.7113, 169.847, 14.2498, 94.3153, 202.295, 15.9302, 62.3116, 198.302, 14.2498, 59.6434, 166.976, 11.8519, 91.4445, 194.926, 11.8519, 57.3878, 169.847, 14.2498, 94.3153, 166.976, 11.8519, 91.4445, 198.302, 14.2498, 59.6434, 194.926, 11.8519, 57.3878, 164.798, 8.876, 89.2658, 192.364, 8.876, 55.676, 166.976, 11.8519, 91.4445, 164.798, 8.876, 89.2658, 194.926, 11.8519, 57.3878, 192.364, 8.876, 55.676, 163.438, 5.495, 87.9057, 190.765, 5.495, 54.6075, 164.798, 8.876, 89.2658, 163.438, 5.495, 87.9057, 192.364, 8.876, 55.676, 190.765, 5.495, 54.6075, 162.975, 1.9054, 87.4435, 190.221, 1.9054, 54.2442, 163.438, 5.495, 87.9057, 162.975, 1.9054, 87.4435, 190.765, 5.495, 54.6075, 190.221, 1.9054, 54.2442, 163.438, -1.6842, 87.9057, 190.765, -1.6842, 54.6075, 162.975, 1.9054, 87.4435, 163.438, -1.6842, 87.9057, 190.221, 1.9054, 54.2442, 190.765, -1.6842, 54.6075, 164.798, -5.0652, 89.2658, 192.364, -5.0652, 55.676, 163.438, -1.6842, 87.9057, 164.798, -5.0652, 89.2658, 190.765, -1.6842, 54.6075, 192.364, -5.0652, 55.676, 166.976, -8.0411, 91.4445, 194.926, -8.0411, 57.3878, 164.798, -5.0652, 89.2658, 166.976, -8.0411, 91.4445, 192.364, -5.0652, 55.676, 194.926, -8.0411, 57.3878, 169.847, -10.4389, 94.3153, 198.302, -10.4389, 59.6434, 166.976, -8.0411, 91.4445, 169.847, -10.4389, 94.3153, 194.926, -8.0411, 57.3878, 198.302, -10.4389, 59.6434, 173.243, -12.1193, 97.7113, 202.295, -12.1193, 62.3116, 169.847, -10.4389, 94.3153, 173.243, -12.1193, 97.7113, 198.302, -10.4389, 59.6434, 202.295, -12.1193, 62.3116, 176.967, -12.9847, 101.435, 206.674, -12.9847, 65.2375, 173.243, -12.1193, 97.7113, 176.967, -12.9847, 101.435, 202.295, -12.1193, 62.3116, 206.674, -12.9847, 65.2375, 180.802, -12.9847, 105.271, 211.184, -12.9847, 68.2508, 176.967, -12.9847, 101.435, 180.802, -12.9847, 105.271, 206.674, -12.9847, 65.2375, 211.184, -12.9847, 68.2508, 184.526, -12.1193, 108.994, 215.562, -12.1193, 71.1767, 180.802, -12.9847, 105.271, 184.526, -12.1193, 108.994, 211.184, -12.9847, 68.2508, 215.562, -12.1193, 71.1767, 187.922, -10.4389, 112.39, 219.556, -10.4389, 73.8449, 184.526, -12.1193, 108.994, 187.922, -10.4389, 112.39, 215.562, -12.1193, 71.1767, 219.556, -10.4389, 73.8449, 190.793, -8.0411, 115.261, 222.931, -8.0411, 76.1005, 187.922, -10.4389, 112.39, 190.793, -8.0411, 115.261, 219.556, -10.4389, 73.8449, 222.931, -8.0411, 76.1005, 192.972, -5.0652, 117.44, 225.493, -5.0652, 77.8123, 190.793, -8.0411, 115.261, 192.972, -5.0652, 117.44, 222.931, -8.0411, 76.1005, 225.493, -5.0652, 77.8123, 194.332, -1.6842, 118.8, 227.092, -1.6842, 78.8808, 192.972, -5.0652, 117.44, 194.332, -1.6842, 118.8, 225.493, -5.0652, 77.8123, 227.092, -1.6842, 78.8808, 194.794, 1.9054, 119.262, 227.636, 1.9054, 79.2441, 194.332, -1.6842, 118.8, 194.794, 1.9054, 119.262, 227.092, -1.6842, 78.8808, 194.794, 1.9054, 119.262, 154.413, 5.495, 151.561, 194.332, 5.495, 118.8, 154.776, 1.9054, 152.104, 154.413, 5.495, 151.561, 194.794, 1.9054, 119.262, 194.332, 5.495, 118.8, 153.344, 8.876, 149.961, 192.972, 8.876, 117.44, 154.413, 5.495, 151.561, 153.344, 8.876, 149.961, 194.332, 5.495, 118.8, 192.972, 8.876, 117.44, 151.632, 11.8519, 147.399, 190.793, 11.8519, 115.261, 153.344, 8.876, 149.961, 151.632, 11.8519, 147.399, 192.972, 8.876, 117.44, 190.793, 11.8519, 115.261, 149.377, 14.2498, 144.024, 187.922, 14.2498, 112.39, 151.632, 11.8519, 147.399, 149.377, 14.2498, 144.024, 190.793, 11.8519, 115.261, 187.922, 14.2498, 112.39, 146.709, 15.9302, 140.031, 184.526, 15.9302, 108.994, 149.377, 14.2498, 144.024, 146.709, 15.9302, 140.031, 187.922, 14.2498, 112.39, 184.526, 15.9302, 108.994, 143.783, 16.7956, 135.652, 180.802, 16.7956, 105.271, 146.709, 15.9302, 140.031, 143.783, 16.7956, 135.652, 184.526, 15.9302, 108.994, 180.802, 16.7956, 105.271, 140.769, 16.7956, 131.142, 176.967, 16.7956, 101.435, 143.783, 16.7956, 135.652, 140.769, 16.7956, 131.142, 180.802, 16.7956, 105.271, 176.967, 16.7956, 101.435, 137.843, 15.9302, 126.763, 173.243, 15.9302, 97.7113, 140.769, 16.7956, 131.142, 137.843, 15.9302, 126.763, 176.967, 16.7956, 101.435, 173.243, 15.9302, 97.7113, 135.175, 14.2498, 122.77, 169.847, 14.2498, 94.3153, 137.843, 15.9302, 126.763, 135.175, 14.2498, 122.77, 173.243, 15.9302, 97.7113, 169.847, 14.2498, 94.3153, 132.92, 11.8519, 119.394, 166.976, 11.8519, 91.4445, 135.175, 14.2498, 122.77, 132.92, 11.8519, 119.394, 169.847, 14.2498, 94.3153, 166.976, 11.8519, 91.4445, 131.208, 8.876, 116.832, 164.798, 8.876, 89.2658, 132.92, 11.8519, 119.394, 131.208, 8.876, 116.832, 166.976, 11.8519, 91.4445, 164.798, 8.876, 89.2658, 130.139, 5.495, 115.233, 163.438, 5.495, 87.9057, 131.208, 8.876, 116.832, 130.139, 5.495, 115.233, 164.798, 8.876, 89.2658, 163.438, 5.495, 87.9057, 129.776, 1.9054, 114.689, 162.975, 1.9054, 87.4435, 130.139, 5.495, 115.233, 129.776, 1.9054, 114.689, 163.438, 5.495, 87.9057, 162.975, 1.9054, 87.4435, 130.139, -1.6842, 115.233, 163.438, -1.6842, 87.9057, 129.776, 1.9054, 114.689, 130.139, -1.6842, 115.233, 162.975, 1.9054, 87.4435, 163.438, -1.6842, 87.9057, 131.208, -5.0652, 116.832, 164.798, -5.0652, 89.2658, 130.139, -1.6842, 115.233, 131.208, -5.0652, 116.832, 163.438, -1.6842, 87.9057, 164.798, -5.0652, 89.2658, 132.92, -8.0411, 119.394, 166.976, -8.0411, 91.4445, 131.208, -5.0652, 116.832, 132.92, -8.0411, 119.394, 164.798, -5.0652, 89.2658, 166.976, -8.0411, 91.4445, 135.175, -10.4389, 122.77, 169.847, -10.4389, 94.3153, 132.92, -8.0411, 119.394, 135.175, -10.4389, 122.77, 166.976, -8.0411, 91.4445, 169.847, -10.4389, 94.3153, 137.843, -12.1193, 126.763, 173.243, -12.1193, 97.7113, 135.175, -10.4389, 122.77, 137.843, -12.1193, 126.763, 169.847, -10.4389, 94.3153, 173.243, -12.1193, 97.7113, 140.769, -12.9847, 131.142, 176.967, -12.9847, 101.435, 137.843, -12.1193, 126.763, 140.769, -12.9847, 131.142, 173.243, -12.1193, 97.7113, 176.967, -12.9847, 101.435, 143.783, -12.9847, 135.652, 180.802, -12.9847, 105.271, 140.769, -12.9847, 131.142, 143.783, -12.9847, 135.652, 176.967, -12.9847, 101.435, 180.802, -12.9847, 105.271, 146.709, -12.1193, 140.031, 184.526, -12.1193, 108.994, 143.783, -12.9847, 135.652, 146.709, -12.1193, 140.031, 180.802, -12.9847, 105.271, 184.526, -12.1193, 108.994, 149.377, -10.4389, 144.024, 187.922, -10.4389, 112.39, 146.709, -12.1193, 140.031, 149.377, -10.4389, 144.024, 184.526, -12.1193, 108.994, 187.922, -10.4389, 112.39, 151.632, -8.0411, 147.399, 190.793, -8.0411, 115.261, 149.377, -10.4389, 144.024, 151.632, -8.0411, 147.399, 187.922, -10.4389, 112.39, 190.793, -8.0411, 115.261, 153.344, -5.0652, 149.961, 192.972, -5.0652, 117.44, 151.632, -8.0411, 147.399, 153.344, -5.0652, 149.961, 190.793, -8.0411, 115.261, 192.972, -5.0652, 117.44, 154.413, -1.6842, 151.561, 194.332, -1.6842, 118.8, 153.344, -5.0652, 149.961, 154.413, -1.6842, 151.561, 192.972, -5.0652, 117.44, 194.332, -1.6842, 118.8, 154.776, 1.9054, 152.104, 194.794, 1.9054, 119.262, 154.413, -1.6842, 151.561, 154.776, 1.9054, 152.104, 194.332, -1.6842, 118.8, 154.776, 1.9054, 152.104, 108.869, 5.495, 175.904, 154.413, 5.495, 151.561, 109.12, 1.9054, 176.508, 108.869, 5.495, 175.904, 154.776, 1.9054, 152.104, 154.413, 5.495, 151.561, 108.133, 8.876, 174.127, 153.344, 8.876, 149.961, 108.869, 5.495, 175.904, 108.133, 8.876, 174.127, 154.413, 5.495, 151.561, 153.344, 8.876, 149.961, 106.954, 11.8519, 171.281, 151.632, 11.8519, 147.399, 108.133, 8.876, 174.127, 106.954, 11.8519, 171.281, 153.344, 8.876, 149.961, 151.632, 11.8519, 147.399, 105.401, 14.2498, 167.53, 149.377, 14.2498, 144.024, 106.954, 11.8519, 171.281, 105.401, 14.2498, 167.53, 151.632, 11.8519, 147.399, 149.377, 14.2498, 144.024, 103.563, 15.9302, 163.092, 146.709, 15.9302, 140.031, 105.401, 14.2498, 167.53, 103.563, 15.9302, 163.092, 149.377, 14.2498, 144.024, 146.709, 15.9302, 140.031, 101.547, 16.7956, 158.227, 143.783, 16.7956, 135.652, 103.563, 15.9302, 163.092, 101.547, 16.7956, 158.227, 146.709, 15.9302, 140.031, 143.783, 16.7956, 135.652, 99.4717, 16.7956, 153.216, 140.769, 16.7956, 131.142, 101.547, 16.7956, 158.227, 99.4717, 16.7956, 153.216, 143.783, 16.7956, 135.652, 140.769, 16.7956, 131.142, 97.4563, 15.9302, 148.35, 137.843, 15.9302, 126.763, 99.4717, 16.7956, 153.216, 97.4563, 15.9302, 148.35, 140.769, 16.7956, 131.142, 137.843, 15.9302, 126.763, 95.6184, 14.2498, 143.913, 135.175, 14.2498, 122.77, 97.4563, 15.9302, 148.35, 95.6184, 14.2498, 143.913, 137.843, 15.9302, 126.763, 135.175, 14.2498, 122.77, 94.0647, 11.8519, 140.162, 132.92, 11.8519, 119.394, 95.6184, 14.2498, 143.913, 94.0647, 11.8519, 140.162, 135.175, 14.2498, 122.77, 132.92, 11.8519, 119.394, 92.8856, 8.876, 137.316, 131.208, 8.876, 116.832, 94.0647, 11.8519, 140.162, 92.8856, 8.876, 137.316, 132.92, 11.8519, 119.394, 131.208, 8.876, 116.832, 92.1496, 5.495, 135.539, 130.139, 5.495, 115.233, 92.8856, 8.876, 137.316, 92.1496, 5.495, 135.539, 131.208, 8.876, 116.832, 130.139, 5.495, 115.233, 91.8994, 1.9054, 134.935, 129.776, 1.9054, 114.689, 92.1496, 5.495, 135.539, 91.8994, 1.9054, 134.935, 130.139, 5.495, 115.233, 129.776, 1.9054, 114.689, 92.1496, -1.6842, 135.539, 130.139, -1.6842, 115.233, 91.8994, 1.9054, 134.935, 92.1496, -1.6842, 135.539, 129.776, 1.9054, 114.689, 130.139, -1.6842, 115.233, 92.8856, -5.0652, 137.316, 131.208, -5.0652, 116.832, 92.1496, -1.6842, 135.539, 92.8856, -5.0652, 137.316, 130.139, -1.6842, 115.233, 131.208, -5.0652, 116.832, 94.0647, -8.0411, 140.162, 132.92, -8.0411, 119.394, 92.8856, -5.0652, 137.316, 94.0647, -8.0411, 140.162, 131.208, -5.0652, 116.832, 132.92, -8.0411, 119.394, 95.6184, -10.4389, 143.913, 135.175, -10.4389, 122.77, 94.0647, -8.0411, 140.162, 95.6184, -10.4389, 143.913, 132.92, -8.0411, 119.394, 135.175, -10.4389, 122.77, 97.4563, -12.1193, 148.35, 137.843, -12.1193, 126.763, 95.6184, -10.4389, 143.913, 97.4563, -12.1193, 148.35, 135.175, -10.4389, 122.77, 137.843, -12.1193, 126.763, 99.4717, -12.9847, 153.216, 140.769, -12.9847, 131.142, 97.4563, -12.1193, 148.35, 99.4717, -12.9847, 153.216, 137.843, -12.1193, 126.763, 140.769, -12.9847, 131.142, 101.547, -12.9847, 158.227, 143.783, -12.9847, 135.652, 99.4717, -12.9847, 153.216, 101.547, -12.9847, 158.227, 140.769, -12.9847, 131.142, 143.783, -12.9847, 135.652, 103.563, -12.1193, 163.092, 146.709, -12.1193, 140.031, 101.547, -12.9847, 158.227, 103.563, -12.1193, 163.092, 143.783, -12.9847, 135.652, 146.709, -12.1193, 140.031, 105.401, -10.4389, 167.53, 149.377, -10.4389, 144.024, 103.563, -12.1193, 163.092, 105.401, -10.4389, 167.53, 146.709, -12.1193, 140.031, 149.377, -10.4389, 144.024, 106.954, -8.0411, 171.281, 151.632, -8.0411, 147.399, 105.401, -10.4389, 167.53, 106.954, -8.0411, 171.281, 149.377, -10.4389, 144.024, 151.632, -8.0411, 147.399, 108.133, -5.0652, 174.127, 153.344, -5.0652, 149.961, 106.954, -8.0411, 171.281, 108.133, -5.0652, 174.127, 151.632, -8.0411, 147.399, 153.344, -5.0652, 149.961, 108.869, -1.6842, 175.904, 154.413, -1.6842, 151.561, 108.133, -5.0652, 174.127, 108.869, -1.6842, 175.904, 153.344, -5.0652, 149.961, 154.413, -1.6842, 151.561, 109.12, 1.9054, 176.508, 154.776, 1.9054, 152.104, 108.869, -1.6842, 175.904, 109.12, 1.9054, 176.508, 154.413, -1.6842, 151.561, 109.12, 1.9054, 176.508, 59.452, 5.495, 190.895, 108.869, 5.495, 175.904, 59.5795, 1.9054, 191.536, 59.452, 5.495, 190.895, 109.12, 1.9054, 176.508, 108.869, 5.495, 175.904, 59.0767, 8.876, 189.008, 108.133, 8.876, 174.127, 59.452, 5.495, 190.895, 59.0767, 8.876, 189.008, 108.869, 5.495, 175.904, 108.133, 8.876, 174.127, 58.4756, 11.8519, 185.986, 106.954, 11.8519, 171.281, 59.0767, 8.876, 189.008, 58.4756, 11.8519, 185.986, 108.133, 8.876, 174.127, 106.954, 11.8519, 171.281, 57.6836, 14.2498, 182.004, 105.401, 14.2498, 167.53, 58.4756, 11.8519, 185.986, 57.6836, 14.2498, 182.004, 106.954, 11.8519, 171.281, 105.401, 14.2498, 167.53, 56.7466, 15.9302, 177.294, 103.563, 15.9302, 163.092, 57.6836, 14.2498, 182.004, 56.7466, 15.9302, 177.294, 105.401, 14.2498, 167.53, 103.563, 15.9302, 163.092, 55.7192, 16.7956, 172.129, 101.547, 16.7956, 158.227, 56.7466, 15.9302, 177.294, 55.7192, 16.7956, 172.129, 103.563, 15.9302, 163.092, 101.547, 16.7956, 158.227, 54.6611, 16.7956, 166.809, 99.4717, 16.7956, 153.216, 55.7192, 16.7956, 172.129, 54.6611, 16.7956, 166.809, 101.547, 16.7956, 158.227, 99.4717, 16.7956, 153.216, 53.6336, 15.9302, 161.644, 97.4563, 15.9302, 148.35, 54.6611, 16.7956, 166.809, 53.6336, 15.9302, 161.644, 99.4717, 16.7956, 153.216, 97.4563, 15.9302, 148.35, 52.6967, 14.2498, 156.934, 95.6184, 14.2498, 143.913, 53.6336, 15.9302, 161.644, 52.6967, 14.2498, 156.934, 97.4563, 15.9302, 148.35, 95.6184, 14.2498, 143.913, 51.9046, 11.8519, 152.952, 94.0647, 11.8519, 140.162, 52.6967, 14.2498, 156.934, 51.9046, 11.8519, 152.952, 95.6184, 14.2498, 143.913, 94.0647, 11.8519, 140.162, 51.3035, 8.876, 149.93, 92.8856, 8.876, 137.316, 51.9046, 11.8519, 152.952, 51.3035, 8.876, 149.93, 94.0647, 11.8519, 140.162, 92.8856, 8.876, 137.316, 50.9283, 5.495, 148.043, 92.1496, 5.495, 135.539, 51.3035, 8.876, 149.93, 50.9283, 5.495, 148.043, 92.8856, 8.876, 137.316, 92.1496, 5.495, 135.539, 50.8007, 1.9054, 147.402, 91.8994, 1.9054, 134.935, 50.9283, 5.495, 148.043, 50.8007, 1.9054, 147.402, 92.1496, 5.495, 135.539, 91.8994, 1.9054, 134.935, 50.9283, -1.6842, 148.043, 92.1496, -1.6842, 135.539, 50.8007, 1.9054, 147.402, 50.9283, -1.6842, 148.043, 91.8994, 1.9054, 134.935, 92.1496, -1.6842, 135.539, 51.3035, -5.0652, 149.93, 92.8856, -5.0652, 137.316, 50.9283, -1.6842, 148.043, 51.3035, -5.0652, 149.93, 92.1496, -1.6842, 135.539, 92.8856, -5.0652, 137.316, 51.9046, -8.0411, 152.952, 94.0647, -8.0411, 140.162, 51.3035, -5.0652, 149.93, 51.9046, -8.0411, 152.952, 92.8856, -5.0652, 137.316, 94.0647, -8.0411, 140.162, 52.6967, -10.4389, 156.934, 95.6184, -10.4389, 143.913, 51.9046, -8.0411, 152.952, 52.6967, -10.4389, 156.934, 94.0647, -8.0411, 140.162, 95.6184, -10.4389, 143.913, 53.6336, -12.1193, 161.644, 97.4563, -12.1193, 148.35, 52.6967, -10.4389, 156.934, 53.6336, -12.1193, 161.644, 95.6184, -10.4389, 143.913, 97.4563, -12.1193, 148.35, 54.6611, -12.9847, 166.809, 99.4717, -12.9847, 153.216, 53.6336, -12.1193, 161.644, 54.6611, -12.9847, 166.809, 97.4563, -12.1193, 148.35, 99.4717, -12.9847, 153.216, 55.7192, -12.9847, 172.129, 101.547, -12.9847, 158.227, 54.6611, -12.9847, 166.809, 55.7192, -12.9847, 172.129, 99.4717, -12.9847, 153.216, 101.547, -12.9847, 158.227, 56.7466, -12.1193, 177.294, 103.563, -12.1193, 163.092, 55.7192, -12.9847, 172.129, 56.7466, -12.1193, 177.294, 101.547, -12.9847, 158.227, 103.563, -12.1193, 163.092, 57.6836, -10.4389, 182.004, 105.401, -10.4389, 167.53, 56.7466, -12.1193, 177.294, 57.6836, -10.4389, 182.004, 103.563, -12.1193, 163.092, 105.401, -10.4389, 167.53, 58.4756, -8.0411, 185.986, 106.954, -8.0411, 171.281, 57.6836, -10.4389, 182.004, 58.4756, -8.0411, 185.986, 105.401, -10.4389, 167.53, 106.954, -8.0411, 171.281, 59.0767, -5.0652, 189.008, 108.133, -5.0652, 174.127, 58.4756, -8.0411, 185.986, 59.0767, -5.0652, 189.008, 106.954, -8.0411, 171.281, 108.133, -5.0652, 174.127, 59.452, -1.6842, 190.895, 108.869, -1.6842, 175.904, 59.0767, -5.0652, 189.008, 59.452, -1.6842, 190.895, 108.133, -5.0652, 174.127, 108.869, -1.6842, 175.904, 59.5795, 1.9054, 191.536, 109.12, 1.9054, 176.508, 59.452, -1.6842, 190.895, 59.5795, 1.9054, 191.536, 108.869, -1.6842, 175.904, 59.5795, 1.9054, 191.536, 8.0596, 5.495, 195.956, 59.452, 5.495, 190.895, 8.0596, 1.9054, 196.61, 8.0596, 5.495, 195.956, 59.5795, 1.9054, 191.536, 59.452, 5.495, 190.895, 8.0596, 8.876, 194.033, 59.0767, 8.876, 189.008, 8.0596, 5.495, 195.956, 8.0596, 8.876, 194.033, 59.452, 5.495, 190.895, 59.0767, 8.876, 189.008, 8.0596, 11.8519, 190.952, 58.4756, 11.8519, 185.986, 8.0596, 8.876, 194.033, 8.0596, 11.8519, 190.952, 59.0767, 8.876, 189.008, 58.4756, 11.8519, 185.986, 8.0596, 14.2498, 186.892, 57.6836, 14.2498, 182.004, 8.0596, 11.8519, 190.952, 8.0596, 14.2498, 186.892, 58.4756, 11.8519, 185.986, 57.6836, 14.2498, 182.004, 8.0596, 15.9302, 182.089, 56.7466, 15.9302, 177.294, 8.0596, 14.2498, 186.892, 8.0596, 15.9302, 182.089, 57.6836, 14.2498, 182.004, 56.7466, 15.9302, 177.294, 8.0596, 16.7956, 176.823, 55.7192, 16.7956, 172.129, 8.0596, 15.9302, 182.089, 8.0596, 16.7956, 176.823, 56.7466, 15.9302, 177.294, 55.7192, 16.7956, 172.129, 8.0596, 16.7956, 171.399, 54.6611, 16.7956, 166.809, 8.0596, 16.7956, 176.823, 8.0596, 16.7956, 171.399, 55.7192, 16.7956, 172.129, 54.6611, 16.7956, 166.809, 8.0596, 15.9302, 166.133, 53.6336, 15.9302, 161.644, 8.0596, 16.7956, 171.399, 8.0596, 15.9302, 166.133, 54.6611, 16.7956, 166.809, 53.6336, 15.9302, 161.644, 8.0596, 14.2498, 161.33, 52.6967, 14.2498, 156.934, 8.0596, 15.9302, 166.133, 8.0596, 14.2498, 161.33, 53.6336, 15.9302, 161.644, 52.6967, 14.2498, 156.934, 8.0596, 11.8519, 157.27, 51.9046, 11.8519, 152.952, 8.0596, 14.2498, 161.33, 8.0596, 11.8519, 157.27, 52.6967, 14.2498, 156.934, 51.9046, 11.8519, 152.952, 8.0596, 8.876, 154.189, 51.3035, 8.876, 149.93, 8.0596, 11.8519, 157.27, 8.0596, 8.876, 154.189, 51.9046, 11.8519, 152.952, 51.3035, 8.876, 149.93, 8.0596, 5.495, 152.266, 50.9283, 5.495, 148.043, 8.0596, 8.876, 154.189, 8.0596, 5.495, 152.266, 51.3035, 8.876, 149.93, 50.9283, 5.495, 148.043, 8.0596, 1.9054, 151.612, 50.8007, 1.9054, 147.402, 8.0596, 5.495, 152.266, 8.0596, 1.9054, 151.612, 50.9283, 5.495, 148.043, 50.8007, 1.9054, 147.402, 8.0596, -1.6842, 152.266, 50.9283, -1.6842, 148.043, 8.0596, 1.9054, 151.612, 8.0596, -1.6842, 152.266, 50.8007, 1.9054, 147.402, 50.9283, -1.6842, 148.043, 8.0596, -5.0652, 154.189, 51.3035, -5.0652, 149.93, 8.0596, -1.6842, 152.266, 8.0596, -5.0652, 154.189, 50.9283, -1.6842, 148.043, 51.3035, -5.0652, 149.93, 8.0596, -8.0411, 157.27, 51.9046, -8.0411, 152.952, 8.0596, -5.0652, 154.189, 8.0596, -8.0411, 157.27, 51.3035, -5.0652, 149.93, 51.9046, -8.0411, 152.952, 8.0596, -10.4389, 161.33, 52.6967, -10.4389, 156.934, 8.0596, -8.0411, 157.27, 8.0596, -10.4389, 161.33, 51.9046, -8.0411, 152.952, 52.6967, -10.4389, 156.934, 8.0596, -12.1193, 166.133, 53.6336, -12.1193, 161.644, 8.0596, -10.4389, 161.33, 8.0596, -12.1193, 166.133, 52.6967, -10.4389, 156.934, 53.6336, -12.1193, 161.644, 8.0596, -12.9847, 171.399, 54.6611, -12.9847, 166.809, 8.0596, -12.1193, 166.133, 8.0596, -12.9847, 171.399, 53.6336, -12.1193, 161.644, 54.6611, -12.9847, 166.809, 8.0596, -12.9847, 176.823, 55.7192, -12.9847, 172.129, 8.0596, -12.9847, 171.399, 8.0596, -12.9847, 176.823, 54.6611, -12.9847, 166.809, 55.7192, -12.9847, 172.129, 8.0596, -12.1193, 182.089, 56.7466, -12.1193, 177.294, 8.0596, -12.9847, 176.823, 8.0596, -12.1193, 182.089, 55.7192, -12.9847, 172.129, 56.7466, -12.1193, 177.294, 8.0596, -10.4389, 186.892, 57.6836, -10.4389, 182.004, 8.0596, -12.1193, 182.089, 8.0596, -10.4389, 186.892, 56.7466, -12.1193, 177.294, 57.6836, -10.4389, 182.004, 8.0596, -8.0411, 190.952, 58.4756, -8.0411, 185.986, 8.0596, -10.4389, 186.892, 8.0596, -8.0411, 190.952, 57.6836, -10.4389, 182.004, 58.4756, -8.0411, 185.986, 8.0596, -5.0652, 194.033, 59.0767, -5.0652, 189.008, 8.0596, -8.0411, 190.952, 8.0596, -5.0652, 194.033, 58.4756, -8.0411, 185.986, 59.0767, -5.0652, 189.008, 8.0596, -1.6842, 195.956, 59.452, -1.6842, 190.895, 8.0596, -5.0652, 194.033, 8.0596, -1.6842, 195.956, 59.0767, -5.0652, 189.008, 59.452, -1.6842, 190.895, 8.0596, 1.9054, 196.61, 59.5795, 1.9054, 191.536, 8.0596, -1.6842, 195.956, 8.0596, 1.9054, 196.61, 59.452, -1.6842, 190.895, 8.0596, 1.9054, 196.61, -43.3328, 5.495, 190.895, 8.0596, 5.495, 195.956, -43.4604, 1.9054, 191.536, -43.3328, 5.495, 190.895, 8.0596, 1.9054, 196.61, 8.0596, 5.495, 195.956, -42.9576, 8.876, 189.008, 8.0596, 8.876, 194.033, -43.3328, 5.495, 190.895, -42.9576, 8.876, 189.008, 8.0596, 5.495, 195.956, 8.0596, 8.876, 194.033, -42.3565, 11.8519, 185.986, 8.0596, 11.8519, 190.952, -42.9576, 8.876, 189.008, -42.3565, 11.8519, 185.986, 8.0596, 8.876, 194.033, 8.0596, 11.8519, 190.952, -41.5644, 14.2498, 182.004, 8.0596, 14.2498, 186.892, -42.3565, 11.8519, 185.986, -41.5644, 14.2498, 182.004, 8.0596, 11.8519, 190.952, 8.0596, 14.2498, 186.892, -40.6275, 15.9302, 177.294, 8.0596, 15.9302, 182.089, -41.5644, 14.2498, 182.004, -40.6275, 15.9302, 177.294, 8.0596, 14.2498, 186.892, 8.0596, 15.9302, 182.089, -39.6001, 16.7956, 172.129, 8.0596, 16.7956, 176.823, -40.6275, 15.9302, 177.294, -39.6001, 16.7956, 172.129, 8.0596, 15.9302, 182.089, 8.0596, 16.7956, 176.823, -38.5419, 16.7956, 166.809, 8.0596, 16.7956, 171.399, -39.6001, 16.7956, 172.129, -38.5419, 16.7956, 166.809, 8.0596, 16.7956, 176.823, 8.0596, 16.7956, 171.399, -37.5145, 15.9302, 161.644, 8.0596, 15.9302, 166.133, -38.5419, 16.7956, 166.809, -37.5145, 15.9302, 161.644, 8.0596, 16.7956, 171.399, 8.0596, 15.9302, 166.133, -36.5775, 14.2498, 156.934, 8.0596, 14.2498, 161.33, -37.5145, 15.9302, 161.644, -36.5775, 14.2498, 156.934, 8.0596, 15.9302, 166.133, 8.0596, 14.2498, 161.33, -35.7855, 11.8519, 152.952, 8.0596, 11.8519, 157.27, -36.5775, 14.2498, 156.934, -35.7855, 11.8519, 152.952, 8.0596, 14.2498, 161.33, 8.0596, 11.8519, 157.27, -35.1844, 8.876, 149.93, 8.0596, 8.876, 154.189, -35.7855, 11.8519, 152.952, -35.1844, 8.876, 149.93, 8.0596, 11.8519, 157.27, 8.0596, 8.876, 154.189, -34.8091, 5.495, 148.043, 8.0596, 5.495, 152.266, -35.1844, 8.876, 149.93, -34.8091, 5.495, 148.043, 8.0596, 8.876, 154.189, 8.0596, 5.495, 152.266, -34.6816, 1.9054, 147.402, 8.0596, 1.9054, 151.612, -34.8091, 5.495, 148.043, -34.6816, 1.9054, 147.402, 8.0596, 5.495, 152.266, 8.0596, 1.9054, 151.612, -34.8091, -1.6842, 148.043, 8.0596, -1.6842, 152.266, -34.6816, 1.9054, 147.402, -34.8091, -1.6842, 148.043, 8.0596, 1.9054, 151.612, 8.0596, -1.6842, 152.266, -35.1844, -5.0652, 149.93, 8.0596, -5.0652, 154.189, -34.8091, -1.6842, 148.043, -35.1844, -5.0652, 149.93, 8.0596, -1.6842, 152.266, 8.0596, -5.0652, 154.189, -35.7855, -8.0411, 152.952, 8.0596, -8.0411, 157.27, -35.1844, -5.0652, 149.93, -35.7855, -8.0411, 152.952, 8.0596, -5.0652, 154.189, 8.0596, -8.0411, 157.27, -36.5775, -10.4389, 156.934, 8.0596, -10.4389, 161.33, -35.7855, -8.0411, 152.952, -36.5775, -10.4389, 156.934, 8.0596, -8.0411, 157.27, 8.0596, -10.4389, 161.33, -37.5145, -12.1193, 161.644, 8.0596, -12.1193, 166.133, -36.5775, -10.4389, 156.934, -37.5145, -12.1193, 161.644, 8.0596, -10.4389, 161.33, 8.0596, -12.1193, 166.133, -38.5419, -12.9847, 166.809, 8.0596, -12.9847, 171.399, -37.5145, -12.1193, 161.644, -38.5419, -12.9847, 166.809, 8.0596, -12.1193, 166.133, 8.0596, -12.9847, 171.399, -39.6001, -12.9847, 172.129, 8.0596, -12.9847, 176.823, -38.5419, -12.9847, 166.809, -39.6001, -12.9847, 172.129, 8.0596, -12.9847, 171.399, 8.0596, -12.9847, 176.823, -40.6275, -12.1193, 177.294, 8.0596, -12.1193, 182.089, -39.6001, -12.9847, 172.129, -40.6275, -12.1193, 177.294, 8.0596, -12.9847, 176.823, 8.0596, -12.1193, 182.089, -41.5644, -10.4389, 182.004, 8.0596, -10.4389, 186.892, -40.6275, -12.1193, 177.294, -41.5644, -10.4389, 182.004, 8.0596, -12.1193, 182.089, 8.0596, -10.4389, 186.892, -42.3565, -8.0411, 185.986, 8.0596, -8.0411, 190.952, -41.5644, -10.4389, 182.004, -42.3565, -8.0411, 185.986, 8.0596, -10.4389, 186.892, 8.0596, -8.0411, 190.952, -42.9576, -5.0652, 189.008, 8.0596, -5.0652, 194.033, -42.3565, -8.0411, 185.986, -42.9576, -5.0652, 189.008, 8.0596, -8.0411, 190.952, 8.0596, -5.0652, 194.033, -43.3328, -1.6842, 190.895, 8.0596, -1.6842, 195.956, -42.9576, -5.0652, 189.008, -43.3328, -1.6842, 190.895, 8.0596, -5.0652, 194.033, 8.0596, -1.6842, 195.956, -43.4604, 1.9054, 191.536, 8.0596, 1.9054, 196.61, -43.3328, -1.6842, 190.895, -43.4604, 1.9054, 191.536, 8.0596, -1.6842, 195.956, -43.4604, 1.9054, 191.536, -92.7502, 5.495, 175.904, -43.3328, 5.495, 190.895, -93.0004, 1.9054, 176.508, -92.7502, 5.495, 175.904, -43.4604, 1.9054, 191.536, -43.3328, 5.495, 190.895, -92.0142, 8.876, 174.127, -42.9576, 8.876, 189.008, -92.7502, 5.495, 175.904, -92.0142, 8.876, 174.127, -43.3328, 5.495, 190.895, -42.9576, 8.876, 189.008, -90.8351, 11.8519, 171.281, -42.3565, 11.8519, 185.986, -92.0142, 8.876, 174.127, -90.8351, 11.8519, 171.281, -42.9576, 8.876, 189.008, -42.3565, 11.8519, 185.986, -89.2814, 14.2498, 167.53, -41.5644, 14.2498, 182.004, -90.8351, 11.8519, 171.281, -89.2814, 14.2498, 167.53, -42.3565, 11.8519, 185.986, -41.5644, 14.2498, 182.004, -87.4435, 15.9302, 163.092, -40.6275, 15.9302, 177.294, -89.2814, 14.2498, 167.53, -87.4435, 15.9302, 163.092, -41.5644, 14.2498, 182.004, -40.6275, 15.9302, 177.294, -85.4282, 16.7956, 158.227, -39.6001, 16.7956, 172.129, -87.4435, 15.9302, 163.092, -85.4282, 16.7956, 158.227, -40.6275, 15.9302, 177.294, -39.6001, 16.7956, 172.129, -83.3525, 16.7956, 153.216, -38.5419, 16.7956, 166.809, -85.4282, 16.7956, 158.227, -83.3525, 16.7956, 153.216, -39.6001, 16.7956, 172.129, -38.5419, 16.7956, 166.809, -81.3372, 15.9302, 148.35, -37.5145, 15.9302, 161.644, -83.3525, 16.7956, 153.216, -81.3372, 15.9302, 148.35, -38.5419, 16.7956, 166.809, -37.5145, 15.9302, 161.644, -79.4992, 14.2498, 143.913, -36.5775, 14.2498, 156.934, -81.3372, 15.9302, 148.35, -79.4992, 14.2498, 143.913, -37.5145, 15.9302, 161.644, -36.5775, 14.2498, 156.934, -77.9456, 11.8519, 140.162, -35.7855, 11.8519, 152.952, -79.4992, 14.2498, 143.913, -77.9456, 11.8519, 140.162, -36.5775, 14.2498, 156.934, -35.7855, 11.8519, 152.952, -76.7665, 8.876, 137.316, -35.1844, 8.876, 149.93, -77.9456, 11.8519, 140.162, -76.7665, 8.876, 137.316, -35.7855, 11.8519, 152.952, -35.1844, 8.876, 149.93, -76.0304, 5.495, 135.539, -34.8091, 5.495, 148.043, -76.7665, 8.876, 137.316, -76.0304, 5.495, 135.539, -35.1844, 8.876, 149.93, -34.8091, 5.495, 148.043, -75.7802, 1.9054, 134.935, -34.6816, 1.9054, 147.402, -76.0304, 5.495, 135.539, -75.7802, 1.9054, 134.935, -34.8091, 5.495, 148.043, -34.6816, 1.9054, 147.402, -76.0304, -1.6842, 135.539, -34.8091, -1.6842, 148.043, -75.7802, 1.9054, 134.935, -76.0304, -1.6842, 135.539, -34.6816, 1.9054, 147.402, -34.8091, -1.6842, 148.043, -76.7665, -5.0652, 137.316, -35.1844, -5.0652, 149.93, -76.0304, -1.6842, 135.539, -76.7665, -5.0652, 137.316, -34.8091, -1.6842, 148.043, -35.1844, -5.0652, 149.93, -77.9456, -8.0411, 140.162, -35.7855, -8.0411, 152.952, -76.7665, -5.0652, 137.316, -77.9456, -8.0411, 140.162, -35.1844, -5.0652, 149.93, -35.7855, -8.0411, 152.952, -79.4992, -10.4389, 143.913, -36.5775, -10.4389, 156.934, -77.9456, -8.0411, 140.162, -79.4992, -10.4389, 143.913, -35.7855, -8.0411, 152.952, -36.5775, -10.4389, 156.934, -81.3372, -12.1193, 148.35, -37.5145, -12.1193, 161.644, -79.4992, -10.4389, 143.913, -81.3372, -12.1193, 148.35, -36.5775, -10.4389, 156.934, -37.5145, -12.1193, 161.644, -83.3525, -12.9847, 153.216, -38.5419, -12.9847, 166.809, -81.3372, -12.1193, 148.35, -83.3525, -12.9847, 153.216, -37.5145, -12.1193, 161.644, -38.5419, -12.9847, 166.809, -85.4282, -12.9847, 158.227, -39.6001, -12.9847, 172.129, -83.3525, -12.9847, 153.216, -85.4282, -12.9847, 158.227, -38.5419, -12.9847, 166.809, -39.6001, -12.9847, 172.129, -87.4435, -12.1193, 163.092, -40.6275, -12.1193, 177.294, -85.4282, -12.9847, 158.227, -87.4435, -12.1193, 163.092, -39.6001, -12.9847, 172.129, -40.6275, -12.1193, 177.294, -89.2814, -10.4389, 167.53, -41.5644, -10.4389, 182.004, -87.4435, -12.1193, 163.092, -89.2814, -10.4389, 167.53, -40.6275, -12.1193, 177.294, -41.5644, -10.4389, 182.004, -90.8351, -8.0411, 171.281, -42.3565, -8.0411, 185.986, -89.2814, -10.4389, 167.53, -90.8351, -8.0411, 171.281, -41.5644, -10.4389, 182.004, -42.3565, -8.0411, 185.986, -92.0142, -5.0652, 174.127, -42.9576, -5.0652, 189.008, -90.8351, -8.0411, 171.281, -92.0142, -5.0652, 174.127, -42.3565, -8.0411, 185.986, -42.9576, -5.0652, 189.008, -92.7502, -1.6842, 175.904, -43.3328, -1.6842, 190.895, -92.0142, -5.0652, 174.127, -92.7502, -1.6842, 175.904, -42.9576, -5.0652, 189.008, -43.3328, -1.6842, 190.895, -93.0004, 1.9054, 176.508, -43.4604, 1.9054, 191.536, -92.7502, -1.6842, 175.904, -93.0004, 1.9054, 176.508, -43.3328, -1.6842, 190.895, -93.0004, 1.9054, 176.508, -138.294, 5.495, 151.561, -92.7502, 5.495, 175.904, -138.657, 1.9054, 152.104, -138.294, 5.495, 151.561, -93.0004, 1.9054, 176.508, -92.7502, 5.495, 175.904, -137.225, 8.876, 149.961, -92.0142, 8.876, 174.127, -138.294, 5.495, 151.561, -137.225, 8.876, 149.961, -92.7502, 5.495, 175.904, -92.0142, 8.876, 174.127, -135.513, 11.8519, 147.399, -90.8351, 11.8519, 171.281, -137.225, 8.876, 149.961, -135.513, 11.8519, 147.399, -92.0142, 8.876, 174.127, -90.8351, 11.8519, 171.281, -133.258, 14.2498, 144.024, -89.2814, 14.2498, 167.53, -135.513, 11.8519, 147.399, -133.258, 14.2498, 144.024, -90.8351, 11.8519, 171.281, -89.2814, 14.2498, 167.53, -130.589, 15.9302, 140.031, -87.4435, 15.9302, 163.092, -133.258, 14.2498, 144.024, -130.589, 15.9302, 140.031, -89.2814, 14.2498, 167.53, -87.4435, 15.9302, 163.092, -127.664, 16.7956, 135.652, -85.4282, 16.7956, 158.227, -130.589, 15.9302, 140.031, -127.664, 16.7956, 135.652, -87.4435, 15.9302, 163.092, -85.4282, 16.7956, 158.227, -124.65, 16.7956, 131.142, -83.3525, 16.7956, 153.216, -127.664, 16.7956, 135.652, -124.65, 16.7956, 131.142, -85.4282, 16.7956, 158.227, -83.3525, 16.7956, 153.216, -121.724, 15.9302, 126.763, -81.3372, 15.9302, 148.35, -124.65, 16.7956, 131.142, -121.724, 15.9302, 126.763, -83.3525, 16.7956, 153.216, -81.3372, 15.9302, 148.35, -119.056, 14.2498, 122.77, -79.4992, 14.2498, 143.913, -121.724, 15.9302, 126.763, -119.056, 14.2498, 122.77, -81.3372, 15.9302, 148.35, -79.4992, 14.2498, 143.913, -116.801, 11.8519, 119.394, -77.9456, 11.8519, 140.162, -119.056, 14.2498, 122.77, -116.801, 11.8519, 119.394, -79.4992, 14.2498, 143.913, -77.9456, 11.8519, 140.162, -115.089, 8.876, 116.832, -76.7665, 8.876, 137.316, -116.801, 11.8519, 119.394, -115.089, 8.876, 116.832, -77.9456, 11.8519, 140.162, -76.7665, 8.876, 137.316, -114.02, 5.495, 115.233, -76.0304, 5.495, 135.539, -115.089, 8.876, 116.832, -114.02, 5.495, 115.233, -76.7665, 8.876, 137.316, -76.0304, 5.495, 135.539, -113.657, 1.9054, 114.689, -75.7802, 1.9054, 134.935, -114.02, 5.495, 115.233, -113.657, 1.9054, 114.689, -76.0304, 5.495, 135.539, -75.7802, 1.9054, 134.935, -114.02, -1.6842, 115.233, -76.0304, -1.6842, 135.539, -113.657, 1.9054, 114.689, -114.02, -1.6842, 115.233, -75.7802, 1.9054, 134.935, -76.0304, -1.6842, 135.539, -115.089, -5.0652, 116.832, -76.7665, -5.0652, 137.316, -114.02, -1.6842, 115.233, -115.089, -5.0652, 116.832, -76.0304, -1.6842, 135.539, -76.7665, -5.0652, 137.316, -116.801, -8.0411, 119.394, -77.9456, -8.0411, 140.162, -115.089, -5.0652, 116.832, -116.801, -8.0411, 119.394, -76.7665, -5.0652, 137.316, -77.9456, -8.0411, 140.162, -119.056, -10.4389, 122.77, -79.4992, -10.4389, 143.913, -116.801, -8.0411, 119.394, -119.056, -10.4389, 122.77, -77.9456, -8.0411, 140.162, -79.4992, -10.4389, 143.913, -121.724, -12.1193, 126.763, -81.3372, -12.1193, 148.35, -119.056, -10.4389, 122.77, -121.724, -12.1193, 126.763, -79.4992, -10.4389, 143.913, -81.3372, -12.1193, 148.35, -124.65, -12.9847, 131.142, -83.3525, -12.9847, 153.216, -121.724, -12.1193, 126.763, -124.65, -12.9847, 131.142, -81.3372, -12.1193, 148.35, -83.3525, -12.9847, 153.216, -127.664, -12.9847, 135.652, -85.4282, -12.9847, 158.227, -124.65, -12.9847, 131.142, -127.664, -12.9847, 135.652, -83.3525, -12.9847, 153.216, -85.4282, -12.9847, 158.227, -130.589, -12.1193, 140.031, -87.4435, -12.1193, 163.092, -127.664, -12.9847, 135.652, -130.589, -12.1193, 140.031, -85.4282, -12.9847, 158.227, -87.4435, -12.1193, 163.092, -133.258, -10.4389, 144.024, -89.2814, -10.4389, 167.53, -130.589, -12.1193, 140.031, -133.258, -10.4389, 144.024, -87.4435, -12.1193, 163.092, -89.2814, -10.4389, 167.53, -135.513, -8.0411, 147.399, -90.8351, -8.0411, 171.281, -133.258, -10.4389, 144.024, -135.513, -8.0411, 147.399, -89.2814, -10.4389, 167.53, -90.8351, -8.0411, 171.281, -137.225, -5.0652, 149.961, -92.0142, -5.0652, 174.127, -135.513, -8.0411, 147.399, -137.225, -5.0652, 149.961, -90.8351, -8.0411, 171.281, -92.0142, -5.0652, 174.127, -138.294, -1.6842, 151.561, -92.7502, -1.6842, 175.904, -137.225, -5.0652, 149.961, -138.294, -1.6842, 151.561, -92.0142, -5.0652, 174.127, -92.7502, -1.6842, 175.904, -138.657, 1.9054, 152.104, -93.0004, 1.9054, 176.508, -138.294, -1.6842, 151.561, -138.657, 1.9054, 152.104, -92.7502, -1.6842, 175.904, -138.657, 1.9054, 152.104, -178.213, 5.495, 118.8, -138.294, 5.495, 151.561, -178.675, 1.9054, 119.262, -178.213, 5.495, 118.8, -138.657, 1.9054, 152.104, -138.294, 5.495, 151.561, -176.853, 8.876, 117.44, -137.225, 8.876, 149.961, -178.213, 5.495, 118.8, -176.853, 8.876, 117.44, -138.294, 5.495, 151.561, -137.225, 8.876, 149.961, -174.674, 11.8519, 115.261, -135.513, 11.8519, 147.399, -176.853, 8.876, 117.44, -174.674, 11.8519, 115.261, -137.225, 8.876, 149.961, -135.513, 11.8519, 147.399, -171.803, 14.2498, 112.39, -133.258, 14.2498, 144.024, -174.674, 11.8519, 115.261, -171.803, 14.2498, 112.39, -135.513, 11.8519, 147.399, -133.258, 14.2498, 144.024, -168.407, 15.9302, 108.994, -130.589, 15.9302, 140.031, -171.803, 14.2498, 112.39, -168.407, 15.9302, 108.994, -133.258, 14.2498, 144.024, -130.589, 15.9302, 140.031, -164.683, 16.7956, 105.271, -127.664, 16.7956, 135.652, -168.407, 15.9302, 108.994, -164.683, 16.7956, 105.271, -130.589, 15.9302, 140.031, -127.664, 16.7956, 135.652, -160.848, 16.7956, 101.435, -124.65, 16.7956, 131.142, -164.683, 16.7956, 105.271, -160.848, 16.7956, 101.435, -127.664, 16.7956, 135.652, -124.65, 16.7956, 131.142, -157.124, 15.9302, 97.7113, -121.724, 15.9302, 126.763, -160.848, 16.7956, 101.435, -157.124, 15.9302, 97.7113, -124.65, 16.7956, 131.142, -121.724, 15.9302, 126.763, -153.728, 14.2498, 94.3153, -119.056, 14.2498, 122.77, -157.124, 15.9302, 97.7113, -153.728, 14.2498, 94.3153, -121.724, 15.9302, 126.763, -119.056, 14.2498, 122.77, -150.857, 11.8519, 91.4445, -116.801, 11.8519, 119.394, -153.728, 14.2498, 94.3153, -150.857, 11.8519, 91.4445, -119.056, 14.2498, 122.77, -116.801, 11.8519, 119.394, -148.678, 8.876, 89.2658, -115.089, 8.876, 116.832, -150.857, 11.8519, 91.4445, -148.678, 8.876, 89.2658, -116.801, 11.8519, 119.394, -115.089, 8.876, 116.832, -147.318, 5.495, 87.9057, -114.02, 5.495, 115.233, -148.678, 8.876, 89.2658, -147.318, 5.495, 87.9057, -115.089, 8.876, 116.832, -114.02, 5.495, 115.233, -146.856, 1.9054, 87.4435, -113.657, 1.9054, 114.689, -147.318, 5.495, 87.9057, -146.856, 1.9054, 87.4435, -114.02, 5.495, 115.233, -113.657, 1.9054, 114.689, -147.318, -1.6842, 87.9057, -114.02, -1.6842, 115.233, -146.856, 1.9054, 87.4435, -147.318, -1.6842, 87.9057, -113.657, 1.9054, 114.689, -114.02, -1.6842, 115.233, -148.678, -5.0652, 89.2658, -115.089, -5.0652, 116.832, -147.318, -1.6842, 87.9057, -148.678, -5.0652, 89.2658, -114.02, -1.6842, 115.233, -115.089, -5.0652, 116.832, -150.857, -8.0411, 91.4445, -116.801, -8.0411, 119.394, -148.678, -5.0652, 89.2658, -150.857, -8.0411, 91.4445, -115.089, -5.0652, 116.832, -116.801, -8.0411, 119.394, -153.728, -10.4389, 94.3153, -119.056, -10.4389, 122.77, -150.857, -8.0411, 91.4445, -153.728, -10.4389, 94.3153, -116.801, -8.0411, 119.394, -119.056, -10.4389, 122.77, -157.124, -12.1193, 97.7113, -121.724, -12.1193, 126.763, -153.728, -10.4389, 94.3153, -157.124, -12.1193, 97.7113, -119.056, -10.4389, 122.77, -121.724, -12.1193, 126.763, -160.848, -12.9847, 101.435, -124.65, -12.9847, 131.142, -157.124, -12.1193, 97.7113, -160.848, -12.9847, 101.435, -121.724, -12.1193, 126.763, -124.65, -12.9847, 131.142, -164.683, -12.9847, 105.271, -127.664, -12.9847, 135.652, -160.848, -12.9847, 101.435, -164.683, -12.9847, 105.271, -124.65, -12.9847, 131.142, -127.664, -12.9847, 135.652, -168.407, -12.1193, 108.994, -130.589, -12.1193, 140.031, -164.683, -12.9847, 105.271, -168.407, -12.1193, 108.994, -127.664, -12.9847, 135.652, -130.589, -12.1193, 140.031, -171.803, -10.4389, 112.39, -133.258, -10.4389, 144.024, -168.407, -12.1193, 108.994, -171.803, -10.4389, 112.39, -130.589, -12.1193, 140.031, -133.258, -10.4389, 144.024, -174.674, -8.0411, 115.261, -135.513, -8.0411, 147.399, -171.803, -10.4389, 112.39, -174.674, -8.0411, 115.261, -133.258, -10.4389, 144.024, -135.513, -8.0411, 147.399, -176.853, -5.0652, 117.44, -137.225, -5.0652, 149.961, -174.674, -8.0411, 115.261, -176.853, -5.0652, 117.44, -135.513, -8.0411, 147.399, -137.225, -5.0652, 149.961, -178.213, -1.6842, 118.8, -138.294, -1.6842, 151.561, -176.853, -5.0652, 117.44, -178.213, -1.6842, 118.8, -137.225, -5.0652, 149.961, -138.294, -1.6842, 151.561, -178.675, 1.9054, 119.262, -138.657, 1.9054, 152.104, -178.213, -1.6842, 118.8, -178.675, 1.9054, 119.262, -138.294, -1.6842, 151.561, -178.675, 1.9054, 119.262, -210.973, 5.495, 78.8808, -178.213, 5.495, 118.8, -211.517, 1.9054, 79.244, -210.973, 5.495, 78.8808, -178.675, 1.9054, 119.262, -178.213, 5.495, 118.8, -209.374, 8.876, 77.8122, -176.853, 8.876, 117.44, -210.973, 5.495, 78.8808, -209.374, 8.876, 77.8122, -178.213, 5.495, 118.8, -176.853, 8.876, 117.44, -206.812, 11.8519, 76.1004, -174.674, 11.8519, 115.261, -209.374, 8.876, 77.8122, -206.812, 11.8519, 76.1004, -176.853, 8.876, 117.44, -174.674, 11.8519, 115.261, -203.437, 14.2498, 73.8449, -171.803, 14.2498, 112.39, -206.812, 11.8519, 76.1004, -203.437, 14.2498, 73.8449, -174.674, 11.8519, 115.261, -171.803, 14.2498, 112.39, -199.443, 15.9302, 71.1767, -168.407, 15.9302, 108.994, -203.437, 14.2498, 73.8449, -199.443, 15.9302, 71.1767, -171.803, 14.2498, 112.39, -168.407, 15.9302, 108.994, -195.064, 16.7956, 68.2508, -164.683, 16.7956, 105.271, -199.443, 15.9302, 71.1767, -195.064, 16.7956, 68.2508, -168.407, 15.9302, 108.994, -164.683, 16.7956, 105.271, -190.555, 16.7956, 65.2374, -160.848, 16.7956, 101.435, -195.064, 16.7956, 68.2508, -190.555, 16.7956, 65.2374, -164.683, 16.7956, 105.271, -160.848, 16.7956, 101.435, -186.176, 15.9302, 62.3116, -157.124, 15.9302, 97.7113, -190.555, 16.7956, 65.2374, -186.176, 15.9302, 62.3116, -160.848, 16.7956, 101.435, -157.124, 15.9302, 97.7113, -182.182, 14.2498, 59.6434, -153.728, 14.2498, 94.3153, -186.176, 15.9302, 62.3116, -182.182, 14.2498, 59.6434, -157.124, 15.9302, 97.7113, -153.728, 14.2498, 94.3153, -178.807, 11.8519, 57.3878, -150.857, 11.8519, 91.4445, -182.182, 14.2498, 59.6434, -178.807, 11.8519, 57.3878, -153.728, 14.2498, 94.3153, -150.857, 11.8519, 91.4445, -176.245, 8.876, 55.676, -148.678, 8.876, 89.2658, -178.807, 11.8519, 57.3878, -176.245, 8.876, 55.676, -150.857, 11.8519, 91.4445, -148.678, 8.876, 89.2658, -174.646, 5.495, 54.6074, -147.318, 5.495, 87.9057, -176.245, 8.876, 55.676, -174.646, 5.495, 54.6074, -148.678, 8.876, 89.2658, -147.318, 5.495, 87.9057, -174.102, 1.9054, 54.2442, -146.856, 1.9054, 87.4435, -174.646, 5.495, 54.6074, -174.102, 1.9054, 54.2442, -147.318, 5.495, 87.9057, -146.856, 1.9054, 87.4435, -174.646, -1.6842, 54.6074, -147.318, -1.6842, 87.9057, -174.102, 1.9054, 54.2442, -174.646, -1.6842, 54.6074, -146.856, 1.9054, 87.4435, -147.318, -1.6842, 87.9057, -176.245, -5.0652, 55.676, -148.678, -5.0652, 89.2658, -174.646, -1.6842, 54.6074, -176.245, -5.0652, 55.676, -147.318, -1.6842, 87.9057, -148.678, -5.0652, 89.2658, -178.807, -8.0411, 57.3878, -150.857, -8.0411, 91.4445, -176.245, -5.0652, 55.676, -178.807, -8.0411, 57.3878, -148.678, -5.0652, 89.2658, -150.857, -8.0411, 91.4445, -182.182, -10.4389, 59.6434, -153.728, -10.4389, 94.3153, -178.807, -8.0411, 57.3878, -182.182, -10.4389, 59.6434, -150.857, -8.0411, 91.4445, -153.728, -10.4389, 94.3153, -186.176, -12.1193, 62.3116, -157.124, -12.1193, 97.7113, -182.182, -10.4389, 59.6434, -186.176, -12.1193, 62.3116, -153.728, -10.4389, 94.3153, -157.124, -12.1193, 97.7113, -190.555, -12.9847, 65.2374, -160.848, -12.9847, 101.435, -186.176, -12.1193, 62.3116, -190.555, -12.9847, 65.2374, -157.124, -12.1193, 97.7113, -160.848, -12.9847, 101.435, -195.064, -12.9847, 68.2508, -164.683, -12.9847, 105.271, -190.555, -12.9847, 65.2374, -195.064, -12.9847, 68.2508, -160.848, -12.9847, 101.435, -164.683, -12.9847, 105.271, -199.443, -12.1193, 71.1767, -168.407, -12.1193, 108.994, -195.064, -12.9847, 68.2508, -199.443, -12.1193, 71.1767, -164.683, -12.9847, 105.271, -168.407, -12.1193, 108.994, -203.437, -10.4389, 73.8449, -171.803, -10.4389, 112.39, -199.443, -12.1193, 71.1767, -203.437, -10.4389, 73.8449, -168.407, -12.1193, 108.994, -171.803, -10.4389, 112.39, -206.812, -8.0411, 76.1004, -174.674, -8.0411, 115.261, -203.437, -10.4389, 73.8449, -206.812, -8.0411, 76.1004, -171.803, -10.4389, 112.39, -174.674, -8.0411, 115.261, -209.374, -5.0652, 77.8122, -176.853, -5.0652, 117.44, -206.812, -8.0411, 76.1004, -209.374, -5.0652, 77.8122, -174.674, -8.0411, 115.261, -176.853, -5.0652, 117.44, -210.973, -1.6842, 78.8808, -178.213, -1.6842, 118.8, -209.374, -5.0652, 77.8122, -210.973, -1.6842, 78.8808, -176.853, -5.0652, 117.44, -178.213, -1.6842, 118.8, -211.517, 1.9054, 79.244, -178.675, 1.9054, 119.262, -210.973, -1.6842, 78.8808, -211.517, 1.9054, 79.244, -178.213, -1.6842, 118.8, -211.517, 1.9054, 79.244, -235.317, 5.495, 33.3375, -210.973, 5.495, 78.8808, -235.921, 1.9054, 33.5877, -235.317, 5.495, 33.3375, -211.517, 1.9054, 79.244, -210.973, 5.495, 78.8808, -233.54, 8.876, 32.6014, -209.374, 8.876, 77.8122, -235.317, 5.495, 33.3375, -233.54, 8.876, 32.6014, -210.973, 5.495, 78.8808, -209.374, 8.876, 77.8122, -230.693, 11.8519, 31.4223, -206.812, 11.8519, 76.1004, -233.54, 8.876, 32.6014, -230.693, 11.8519, 31.4223, -209.374, 8.876, 77.8122, -206.812, 11.8519, 76.1004, -226.942, 14.2498, 29.8687, -203.437, 14.2498, 73.8449, -230.693, 11.8519, 31.4223, -226.942, 14.2498, 29.8687, -206.812, 11.8519, 76.1004, -203.437, 14.2498, 73.8449, -222.505, 15.9302, 28.0308, -199.443, 15.9302, 71.1767, -226.942, 14.2498, 29.8687, -222.505, 15.9302, 28.0308, -203.437, 14.2498, 73.8449, -199.443, 15.9302, 71.1767, -217.64, 16.7956, 26.0154, -195.064, 16.7956, 68.2508, -222.505, 15.9302, 28.0308, -217.64, 16.7956, 26.0154, -199.443, 15.9302, 71.1767, -195.064, 16.7956, 68.2508, -212.629, 16.7956, 23.9398, -190.555, 16.7956, 65.2374, -217.64, 16.7956, 26.0154, -212.629, 16.7956, 23.9398, -195.064, 16.7956, 68.2508, -190.555, 16.7956, 65.2374, -207.763, 15.9302, 21.9244, -186.176, 15.9302, 62.3116, -212.629, 16.7956, 23.9398, -207.763, 15.9302, 21.9244, -190.555, 16.7956, 65.2374, -186.176, 15.9302, 62.3116, -203.326, 14.2498, 20.0865, -182.182, 14.2498, 59.6434, -207.763, 15.9302, 21.9244, -203.326, 14.2498, 20.0865, -186.176, 15.9302, 62.3116, -182.182, 14.2498, 59.6434, -199.575, 11.8519, 18.5329, -178.807, 11.8519, 57.3878, -203.326, 14.2498, 20.0865, -199.575, 11.8519, 18.5329, -182.182, 14.2498, 59.6434, -178.807, 11.8519, 57.3878, -196.729, 8.876, 17.3537, -176.245, 8.876, 55.676, -199.575, 11.8519, 18.5329, -196.729, 8.876, 17.3537, -178.807, 11.8519, 57.3878, -176.245, 8.876, 55.676, -194.952, 5.495, 16.6177, -174.646, 5.495, 54.6074, -196.729, 8.876, 17.3537, -194.952, 5.495, 16.6177, -176.245, 8.876, 55.676, -174.646, 5.495, 54.6074, -194.348, 1.9054, 16.3675, -174.102, 1.9054, 54.2442, -194.952, 5.495, 16.6177, -194.348, 1.9054, 16.3675, -174.646, 5.495, 54.6074, -174.102, 1.9054, 54.2442, -194.952, -1.6842, 16.6177, -174.646, -1.6842, 54.6074, -194.348, 1.9054, 16.3675, -194.952, -1.6842, 16.6177, -174.102, 1.9054, 54.2442, -174.646, -1.6842, 54.6074, -196.729, -5.0652, 17.3537, -176.245, -5.0652, 55.676, -194.952, -1.6842, 16.6177, -196.729, -5.0652, 17.3537, -174.646, -1.6842, 54.6074, -176.245, -5.0652, 55.676, -199.575, -8.0411, 18.5329, -178.807, -8.0411, 57.3878, -196.729, -5.0652, 17.3537, -199.575, -8.0411, 18.5329, -176.245, -5.0652, 55.676, -178.807, -8.0411, 57.3878, -203.326, -10.4389, 20.0865, -182.182, -10.4389, 59.6434, -199.575, -8.0411, 18.5329, -203.326, -10.4389, 20.0865, -178.807, -8.0411, 57.3878, -182.182, -10.4389, 59.6434, -207.763, -12.1193, 21.9244, -186.176, -12.1193, 62.3116, -203.326, -10.4389, 20.0865, -207.763, -12.1193, 21.9244, -182.182, -10.4389, 59.6434, -186.176, -12.1193, 62.3116, -212.629, -12.9847, 23.9398, -190.555, -12.9847, 65.2374, -207.763, -12.1193, 21.9244, -212.629, -12.9847, 23.9398, -186.176, -12.1193, 62.3116, -190.555, -12.9847, 65.2374, -217.64, -12.9847, 26.0154, -195.064, -12.9847, 68.2508, -212.629, -12.9847, 23.9398, -217.64, -12.9847, 26.0154, -190.555, -12.9847, 65.2374, -195.064, -12.9847, 68.2508, -222.505, -12.1193, 28.0308, -199.443, -12.1193, 71.1767, -217.64, -12.9847, 26.0154, -222.505, -12.1193, 28.0308, -195.064, -12.9847, 68.2508, -199.443, -12.1193, 71.1767, -226.942, -10.4389, 29.8687, -203.437, -10.4389, 73.8449, -222.505, -12.1193, 28.0308, -226.942, -10.4389, 29.8687, -199.443, -12.1193, 71.1767, -203.437, -10.4389, 73.8449, -230.693, -8.0411, 31.4223, -206.812, -8.0411, 76.1004, -226.942, -10.4389, 29.8687, -230.693, -8.0411, 31.4223, -203.437, -10.4389, 73.8449, -206.812, -8.0411, 76.1004, -233.54, -5.0652, 32.6014, -209.374, -5.0652, 77.8122, -230.693, -8.0411, 31.4223, -233.54, -5.0652, 32.6014, -206.812, -8.0411, 76.1004, -209.374, -5.0652, 77.8122, -235.317, -1.6842, 33.3375, -210.973, -1.6842, 78.8808, -233.54, -5.0652, 32.6014, -235.317, -1.6842, 33.3375, -209.374, -5.0652, 77.8122, -210.973, -1.6842, 78.8808, -235.921, 1.9054, 33.5877, -211.517, 1.9054, 79.244, -235.317, -1.6842, 33.3375, -235.921, 1.9054, 33.5877, -210.973, -1.6842, 78.8808, -235.921, 1.9054, 33.5877, -250.307, 5.495, -16.0799, -235.317, 5.495, 33.3375, -250.949, 1.9054, -15.9524, -250.307, 5.495, -16.0799, -235.921, 1.9054, 33.5877, -235.317, 5.495, 33.3375, -248.421, 8.876, -16.4552, -233.54, 8.876, 32.6014, -250.307, 5.495, -16.0799, -248.421, 8.876, -16.4552, -235.317, 5.495, 33.3375, -233.54, 8.876, 32.6014, -245.399, 11.8519, -17.0563, -230.693, 11.8519, 31.4223, -248.421, 8.876, -16.4552, -245.399, 11.8519, -17.0563, -233.54, 8.876, 32.6014, -230.693, 11.8519, 31.4223, -241.417, 14.2498, -17.8483, -226.942, 14.2498, 29.8687, -245.399, 11.8519, -17.0563, -241.417, 14.2498, -17.8483, -230.693, 11.8519, 31.4223, -226.942, 14.2498, 29.8687, -236.707, 15.9302, -18.7853, -222.505, 15.9302, 28.0308, -241.417, 14.2498, -17.8483, -236.707, 15.9302, -18.7853, -226.942, 14.2498, 29.8687, -222.505, 15.9302, 28.0308, -231.542, 16.7956, -19.8127, -217.64, 16.7956, 26.0154, -236.707, 15.9302, -18.7853, -231.542, 16.7956, -19.8127, -222.505, 15.9302, 28.0308, -217.64, 16.7956, 26.0154, -226.222, 16.7956, -20.8709, -212.629, 16.7956, 23.9398, -231.542, 16.7956, -19.8127, -226.222, 16.7956, -20.8709, -217.64, 16.7956, 26.0154, -212.629, 16.7956, 23.9398, -221.057, 15.9302, -21.8983, -207.763, 15.9302, 21.9244, -226.222, 16.7956, -20.8709, -221.057, 15.9302, -21.8983, -212.629, 16.7956, 23.9398, -207.763, 15.9302, 21.9244, -216.346, 14.2498, -22.8352, -203.326, 14.2498, 20.0865, -221.057, 15.9302, -21.8983, -216.346, 14.2498, -22.8352, -207.763, 15.9302, 21.9244, -203.326, 14.2498, 20.0865, -212.364, 11.8519, -23.6273, -199.575, 11.8519, 18.5329, -216.346, 14.2498, -22.8352, -212.364, 11.8519, -23.6273, -203.326, 14.2498, 20.0865, -199.575, 11.8519, 18.5329, -209.342, 8.876, -24.2284, -196.729, 8.876, 17.3537, -212.364, 11.8519, -23.6273, -209.342, 8.876, -24.2284, -199.575, 11.8519, 18.5329, -196.729, 8.876, 17.3537, -207.456, 5.495, -24.6036, -194.952, 5.495, 16.6177, -209.342, 8.876, -24.2284, -207.456, 5.495, -24.6036, -196.729, 8.876, 17.3537, -194.952, 5.495, 16.6177, -206.815, 1.9054, -24.7312, -194.348, 1.9054, 16.3675, -207.456, 5.495, -24.6036, -206.815, 1.9054, -24.7312, -194.952, 5.495, 16.6177, -194.348, 1.9054, 16.3675, -207.456, -1.6842, -24.6036, -194.952, -1.6842, 16.6177, -206.815, 1.9054, -24.7312, -207.456, -1.6842, -24.6036, -194.348, 1.9054, 16.3675, -194.952, -1.6842, 16.6177, -209.342, -5.0652, -24.2284, -196.729, -5.0652, 17.3537, -207.456, -1.6842, -24.6036, -209.342, -5.0652, -24.2284, -194.952, -1.6842, 16.6177, -196.729, -5.0652, 17.3537, -212.364, -8.0411, -23.6273, -199.575, -8.0411, 18.5329, -209.342, -5.0652, -24.2284, -212.364, -8.0411, -23.6273, -196.729, -5.0652, 17.3537, -199.575, -8.0411, 18.5329, -216.346, -10.4389, -22.8352, -203.326, -10.4389, 20.0865, -212.364, -8.0411, -23.6273, -216.346, -10.4389, -22.8352, -199.575, -8.0411, 18.5329, -203.326, -10.4389, 20.0865, -221.057, -12.1193, -21.8983, -207.763, -12.1193, 21.9244, -216.346, -10.4389, -22.8352, -221.057, -12.1193, -21.8983, -203.326, -10.4389, 20.0865, -207.763, -12.1193, 21.9244, -226.222, -12.9847, -20.8709, -212.629, -12.9847, 23.9398, -221.057, -12.1193, -21.8983, -226.222, -12.9847, -20.8709, -207.763, -12.1193, 21.9244, -212.629, -12.9847, 23.9398, -231.542, -12.9847, -19.8127, -217.64, -12.9847, 26.0154, -226.222, -12.9847, -20.8709, -231.542, -12.9847, -19.8127, -212.629, -12.9847, 23.9398, -217.64, -12.9847, 26.0154, -236.707, -12.1193, -18.7853, -222.505, -12.1193, 28.0308, -231.542, -12.9847, -19.8127, -236.707, -12.1193, -18.7853, -217.64, -12.9847, 26.0154, -222.505, -12.1193, 28.0308, -241.417, -10.4389, -17.8483, -226.942, -10.4389, 29.8687, -236.707, -12.1193, -18.7853, -241.417, -10.4389, -17.8483, -222.505, -12.1193, 28.0308, -226.942, -10.4389, 29.8687, -245.399, -8.0411, -17.0563, -230.693, -8.0411, 31.4223, -241.417, -10.4389, -17.8483, -245.399, -8.0411, -17.0563, -226.942, -10.4389, 29.8687, -230.693, -8.0411, 31.4223, -248.421, -5.0652, -16.4552, -233.54, -5.0652, 32.6014, -245.399, -8.0411, -17.0563, -248.421, -5.0652, -16.4552, -230.693, -8.0411, 31.4223, -233.54, -5.0652, 32.6014, -250.307, -1.6842, -16.0799, -235.317, -1.6842, 33.3375, -248.421, -5.0652, -16.4552, -250.307, -1.6842, -16.0799, -233.54, -5.0652, 32.6014, -235.317, -1.6842, 33.3375, -250.949, 1.9054, -15.9524, -235.921, 1.9054, 33.5877, -250.307, -1.6842, -16.0799, -250.949, 1.9054, -15.9524, -235.317, -1.6842, 33.3375, -253.7, 5.495, -50.5297, -250.307, 5.495, -16.0799, -253.917, 4.3155, -50.5431, -250.307, 5.495, -16.0799, -250.949, 1.9054, -15.9524, -253.917, 4.3155, -50.5431, -253.917, 4.3155, -50.5431, -250.949, 1.9054, -15.9524, -254.358, 1.9054, -50.5705, -251.765, 8.8761, -50.4097, -248.421, 8.876, -16.4552, -252.406, 7.7562, -50.4494, -248.421, 8.876, -16.4552, -250.307, 5.495, -16.0799, -252.406, 7.7562, -50.4494, -252.406, 7.7562, -50.4494, -250.307, 5.495, -16.0799, -253.7, 5.495, -50.5297, -248.665, 11.8519, -50.2175, -245.399, 11.8519, -17.0563, -249.71, 10.8492, -50.2822, -245.399, 11.8519, -17.0563, -248.421, 8.876, -16.4552, -249.71, 10.8492, -50.2822, -249.71, 10.8492, -50.2822, -248.421, 8.876, -16.4552, -251.765, 8.8761, -50.4097, -244.58, 14.2498, -49.9642, -241.417, 14.2498, -17.8483, -245.992, 13.4212, -50.0517, -241.417, 14.2498, -17.8483, -245.399, 11.8519, -17.0563, -245.992, 13.4212, -50.0517, -245.992, 13.4212, -50.0517, -245.399, 11.8519, -17.0563, -248.665, 11.8519, -50.2175, -239.748, 15.9302, -49.6646, -236.707, 15.9302, -18.7853, -241.472, 15.3308, -49.7714, -236.707, 15.9302, -18.7853, -241.417, 14.2498, -17.8483, -241.472, 15.3308, -49.7714, -241.472, 15.3308, -49.7714, -241.417, 14.2498, -17.8483, -244.58, 14.2498, -49.9642, -234.449, 16.7956, -49.336, -231.542, 16.7956, -19.8127, -236.41, 16.4754, -49.4576, -231.542, 16.7956, -19.8127, -236.707, 15.9302, -18.7853, -236.41, 16.4754, -49.4576, -236.41, 16.4754, -49.4576, -236.707, 15.9302, -18.7853, -239.748, 15.9302, -49.6646, -228.992, 16.7956, -48.9977, -226.222, 16.7956, -20.8709, -231.093, 16.7956, -49.1279, -226.222, 16.7956, -20.8709, -231.542, 16.7956, -19.8127, -231.093, 16.7956, -49.1279, -231.093, 16.7956, -49.1279, -231.542, 16.7956, -19.8127, -234.449, 16.7956, -49.336, -223.693, 15.9302, -48.6691, -221.057, 15.9302, -21.8983, -225.816, 16.2769, -48.8008, -221.057, 15.9302, -21.8983, -226.222, 16.7956, -20.8709, -225.816, 16.2769, -48.8008, -225.816, 16.2769, -48.8008, -226.222, 16.7956, -20.8709, -228.992, 16.7956, -48.9977, -218.861, 14.2498, -48.3695, -216.346, 14.2498, -22.8352, -220.873, 14.9495, -48.4943, -216.346, 14.2498, -22.8352, -221.057, 15.9302, -21.8983, -220.873, 14.9495, -48.4943, -220.873, 14.9495, -48.4943, -221.057, 15.9302, -21.8983, -223.693, 15.9302, -48.6691, -214.776, 11.8519, -48.1162, -212.364, 11.8519, -23.6273, -216.538, 12.8859, -48.2254, -212.364, 11.8519, -23.6273, -216.346, 14.2498, -22.8352, -216.538, 12.8859, -48.2254, -216.538, 12.8859, -48.2254, -216.346, 14.2498, -22.8352, -218.861, 14.2498, -48.3695, -211.676, 8.8761, -47.924, -209.342, 8.876, -24.2284, -213.052, 10.1971, -48.0094, -209.342, 8.876, -24.2284, -212.364, 11.8519, -23.6273, -213.052, 10.1971, -48.0094, -213.052, 10.1971, -48.0094, -212.364, 11.8519, -23.6273, -214.776, 11.8519, -48.1162, -209.741, 5.495, -47.804, -207.456, 5.495, -24.6036, -210.619, 7.0285, -47.8585, -207.456, 5.495, -24.6036, -209.342, 8.876, -24.2284, -210.619, 7.0285, -47.8585, -210.619, 7.0285, -47.8585, -209.342, 8.876, -24.2284, -211.676, 8.8761, -47.924, -209.083, 1.9054, -47.7632, -206.815, 1.9054, -24.7312, -209.385, 3.5542, -47.782, -206.815, 1.9054, -24.7312, -207.456, 5.495, -24.6036, -209.385, 3.5542, -47.782, -209.385, 3.5542, -47.782, -207.456, 5.495, -24.6036, -209.741, 5.495, -47.804, -209.741, -1.6842, -47.804, -207.456, -1.6842, -24.6036, -209.438, -0.0308, -47.7852, -207.456, -1.6842, -24.6036, -206.815, 1.9054, -24.7312, -209.438, -0.0308, -47.7852, -209.438, -0.0308, -47.7852, -206.815, 1.9054, -24.7312, -209.083, 1.9054, -47.7632, -211.676, -5.0652, -47.924, -209.342, -5.0652, -24.2284, -210.791, -3.5191, -47.8691, -209.342, -5.0652, -24.2284, -207.456, -1.6842, -24.6036, -210.791, -3.5191, -47.8691, -210.791, -3.5191, -47.8691, -207.456, -1.6842, -24.6036, -209.741, -1.6842, -47.804, -214.776, -8.0411, -48.1162, -212.364, -8.0411, -23.6273, -213.383, -6.7031, -48.0298, -212.364, -8.0411, -23.6273, -209.342, -5.0652, -24.2284, -213.383, -6.7031, -48.0298, -213.383, -6.7031, -48.0298, -209.342, -5.0652, -24.2284, -211.676, -5.0652, -47.924, -218.861, -10.4389, -48.3695, -216.346, -10.4389, -22.8352, -217.071, -9.3881, -48.2585, -216.346, -10.4389, -22.8352, -212.364, -8.0411, -23.6273, -217.071, -9.3881, -48.2585, -217.071, -9.3881, -48.2585, -212.364, -8.0411, -23.6273, -214.776, -8.0411, -48.1162, -223.693, -12.1193, -48.6691, -221.057, -12.1193, -21.8983, -221.644, -11.4067, -48.5421, -221.057, -12.1193, -21.8983, -216.346, -10.4389, -22.8352, -221.644, -11.4067, -48.5421, -221.644, -11.4067, -48.5421, -216.346, -10.4389, -22.8352, -218.861, -10.4389, -48.3695, -228.992, -12.9847, -48.9977, -226.222, -12.9847, -20.8709, -226.828, -12.6314, -48.8635, -226.222, -12.9847, -20.8709, -221.057, -12.1193, -21.8983, -226.828, -12.6314, -48.8635, -226.828, -12.6314, -48.8635, -221.057, -12.1193, -21.8983, -223.693, -12.1193, -48.6691, -234.449, -12.9847, -49.336, -231.542, -12.9847, -19.8127, -232.31, -12.9847, -49.2034, -231.542, -12.9847, -19.8127, -226.222, -12.9847, -20.8709, -232.31, -12.9847, -49.2034, -232.31, -12.9847, -49.2034, -226.222, -12.9847, -20.8709, -228.992, -12.9847, -48.9977, -239.748, -12.1193, -49.6646, -236.707, -12.1193, -18.7853, -237.755, -12.4449, -49.541, -236.707, -12.1193, -18.7853, -231.542, -12.9847, -19.8127, -237.755, -12.4449, -49.541, -237.755, -12.4449, -49.541, -231.542, -12.9847, -19.8127, -234.449, -12.9847, -49.336, -244.58, -10.4389, -49.9642, -241.417, -10.4389, -17.8483, -242.832, -11.0469, -49.8558, -241.417, -10.4389, -17.8483, -236.707, -12.1193, -18.7853, -242.832, -11.0469, -49.8558, -242.832, -11.0469, -49.8558, -236.707, -12.1193, -18.7853, -239.748, -12.1193, -49.6646, -248.665, -8.0411, -50.2175, -245.399, -8.0411, -17.0563, -247.237, -8.8791, -50.129, -245.399, -8.0411, -17.0563, -241.417, -10.4389, -17.8483, -247.237, -8.8791, -50.129, -247.237, -8.8791, -50.129, -241.417, -10.4389, -17.8483, -244.58, -10.4389, -49.9642, -251.765, -5.0652, -50.4097, -248.421, -5.0652, -16.4552, -250.712, -6.0762, -50.3444, -248.421, -5.0652, -16.4552, -245.399, -8.0411, -17.0563, -250.712, -6.0762, -50.3444, -250.712, -6.0762, -50.3444, -245.399, -8.0411, -17.0563, -248.665, -8.0411, -50.2175, -253.7, -1.6842, -50.5297, -250.307, -1.6842, -16.0799, -253.056, -2.8097, -50.4897, -250.307, -1.6842, -16.0799, -248.421, -5.0652, -16.4552, -253.056, -2.8097, -50.4897, -253.056, -2.8097, -50.4897, -248.421, -5.0652, -16.4552, -251.765, -5.0652, -50.4097, -254.358, 1.9054, -50.5705, -250.949, 1.9054, -15.9524, -254.142, 0.7239, -50.557, -250.949, 1.9054, -15.9524, -250.307, -1.6842, -16.0799, -254.142, 0.7239, -50.557, -254.142, 0.7239, -50.557, -250.307, -1.6842, -16.0799, -253.7, -1.6842, -50.5297, 260.638, 4.4899, -18.6388, 260.502, 4.4899, -17.2583, 260.65, 4.417, -18.6381, 260.65, 4.417, -18.6381, 260.502, 4.4899, -17.2583, 261.075, 1.8384, -18.6118, 260.502, 4.4899, -17.2583, 260.933, 1.8384, -17.1727, 261.075, 1.8384, -18.6118, 259.355, 6.9872, -18.7184, 259.236, 6.9872, -17.5103, 259.386, 6.9269, -18.7165, 259.386, 6.9269, -18.7165, 259.236, 6.9872, -17.5103, 260.638, 4.4899, -18.6388, 259.236, 6.9872, -17.5103, 260.502, 4.4899, -17.2583, 260.638, 4.4899, -18.6388, 257.298, 9.1853, -18.8459, 257.206, 9.1853, -17.9139, 257.337, 9.1441, -18.8435, 257.337, 9.1441, -18.8435, 257.206, 9.1853, -17.9139, 259.355, 6.9872, -18.7184, 257.206, 9.1853, -17.9139, 259.236, 6.9872, -17.5103, 259.355, 6.9872, -18.7184, 254.589, 10.9564, -19.014, 254.533, 10.9564, -18.4458, 254.62, 10.936, -19.012, 254.62, 10.936, -19.012, 254.533, 10.9564, -18.4458, 257.298, 9.1853, -18.8459, 254.533, 10.9564, -18.4458, 257.206, 9.1853, -17.9139, 257.298, 9.1853, -18.8459, 251.383, 12.1977, -19.2127, 251.37, 12.1977, -19.0749, 251.392, 12.1941, -19.2121, 251.392, 12.1941, -19.2121, 251.37, 12.1977, -19.0749, 254.589, 10.9564, -19.014, 251.37, 12.1977, -19.0749, 254.533, 10.9564, -18.4458, 254.589, 10.9564, -19.014, 250.357, 12.3842, -19.2763, 251.37, 12.1977, -19.0749, 251.383, 12.1977, -19.2127, 251.383, -8.5208, -19.2127, 251.37, -8.5208, -19.0749, 251.373, -8.5226, -19.2133, 251.373, -8.5226, -19.2133, 251.37, -8.5208, -19.0749, 250.357, -8.7074, -19.2763, 254.589, -7.2796, -19.0139, 254.533, -7.2796, -18.4458, 254.551, -7.294, -19.0163, 254.551, -7.294, -19.0163, 254.533, -7.2796, -18.4458, 251.383, -8.5208, -19.2127, 254.533, -7.2796, -18.4458, 251.37, -8.5208, -19.0749, 251.383, -8.5208, -19.2127, 257.298, -5.5084, -18.8459, 257.206, -5.5084, -17.9139, 257.247, -5.5419, -18.8491, 257.247, -5.5419, -18.8491, 257.206, -5.5084, -17.9139, 254.589, -7.2796, -19.0139, 257.206, -5.5084, -17.9139, 254.533, -7.2796, -18.4458, 254.589, -7.2796, -19.0139, 259.355, -3.3103, -18.7184, 259.236, -3.3103, -17.5103, 259.305, -3.3636, -18.7215, 259.305, -3.3636, -18.7215, 259.236, -3.3103, -17.5103, 257.298, -5.5084, -18.8459, 259.236, -3.3103, -17.5103, 257.206, -5.5084, -17.9139, 257.298, -5.5084, -18.8459, 260.638, -0.813, -18.6388, 260.502, -0.813, -17.2583, 260.603, -0.8818, -18.641, 260.603, -0.8818, -18.641, 260.502, -0.813, -17.2583, 259.355, -3.3103, -18.7184, 260.502, -0.813, -17.2583, 259.236, -3.3103, -17.5103, 259.355, -3.3103, -18.7184, 261.075, 1.8384, -18.6118, 260.933, 1.8384, -17.1727, 261.062, 1.7625, -18.6126, 261.062, 1.7625, -18.6126, 260.933, 1.8384, -17.1727, 260.638, -0.813, -18.6388, 260.933, 1.8384, -17.1727, 260.502, -0.813, -17.2583, 260.638, -0.813, -18.6388, 260.933, 1.8384, -17.1727, 260.502, 4.4899, -17.2583, 245.855, 4.4899, 31.026, 246.261, 1.8384, 31.194, 260.933, 1.8384, -17.1727, 245.855, 4.4899, 31.026, 260.502, 4.4899, -17.2583, 259.236, 6.9872, -17.5103, 244.662, 6.9872, 30.5317, 245.855, 4.4899, 31.026, 260.502, 4.4899, -17.2583, 244.662, 6.9872, 30.5317, 259.236, 6.9872, -17.5103, 257.206, 9.1853, -17.9139, 242.751, 9.1853, 29.74, 244.662, 6.9872, 30.5317, 259.236, 6.9872, -17.5103, 242.751, 9.1853, 29.74, 257.206, 9.1853, -17.9139, 254.533, 10.9564, -18.4458, 240.232, 10.9564, 28.6967, 242.751, 9.1853, 29.74, 257.206, 9.1853, -17.9139, 240.232, 10.9564, 28.6967, 254.533, 10.9564, -18.4458, 251.37, 12.1977, -19.0749, 237.253, 12.1977, 27.4626, 240.232, 10.9564, 28.6967, 254.533, 10.9564, -18.4458, 237.253, 12.1977, 27.4626, 247.801, 12.8368, -19.4348, 233.986, 12.8369, 26.1093, 250.357, 12.3842, -19.2763, 233.986, 12.8369, 26.1093, 251.37, 12.1977, -19.0749, 250.357, 12.3842, -19.2763, 237.253, 12.1977, 27.4626, 251.37, 12.1977, -19.0749, 233.986, 12.8369, 26.1093, 244.083, 12.8369, -19.6653, 230.621, 12.8369, 24.7155, 247.774, 12.8369, -19.4365, 247.774, 12.8369, -19.4365, 230.621, 12.8369, 24.7155, 247.801, 12.8368, -19.4348, 230.621, 12.8369, 24.7155, 233.986, 12.8369, 26.1093, 247.801, 12.8368, -19.4348, 240.474, 12.1977, -19.8891, 227.354, 12.1977, 23.3622, 244.017, 12.8251, -19.6694, 244.017, 12.8251, -19.6694, 227.354, 12.1977, 23.3622, 244.083, 12.8369, -19.6653, 227.354, 12.1977, 23.3622, 230.621, 12.8369, 24.7155, 244.083, 12.8369, -19.6653, 237.182, 10.9564, -20.0932, 224.374, 10.9564, 22.1281, 240.377, 12.1612, -19.8951, 240.377, 12.1612, -19.8951, 224.374, 10.9564, 22.1281, 240.474, 12.1977, -19.8891, 224.374, 10.9564, 22.1281, 227.354, 12.1977, 23.3622, 240.474, 12.1977, -19.8891, 234.399, 9.1853, -20.2658, 221.855, 9.1853, 21.0848, 237.072, 10.8864, -20.1, 237.072, 10.8864, -20.1, 221.855, 9.1853, 21.0848, 237.182, 10.9564, -20.0932, 221.855, 9.1853, 21.0848, 224.374, 10.9564, 22.1281, 237.182, 10.9564, -20.0932, 232.287, 6.9872, -20.3967, 219.944, 6.9872, 20.2931, 234.297, 9.0794, -20.2721, 234.297, 9.0794, -20.2721, 219.944, 6.9872, 20.2931, 234.399, 9.1853, -20.2658, 219.944, 6.9872, 20.2931, 221.855, 9.1853, 21.0848, 234.399, 9.1853, -20.2658, 230.969, 4.4899, -20.4785, 218.751, 4.4899, 19.7988, 232.215, 6.8504, -20.4012, 232.215, 6.8504, -20.4012, 218.751, 4.4899, 19.7988, 232.287, 6.9872, -20.3967, 218.751, 4.4899, 19.7988, 219.944, 6.9872, 20.2931, 232.287, 6.9872, -20.3967, 230.521, 1.8384, -20.5062, 218.345, 1.8384, 19.6308, 230.942, 4.3342, -20.4801, 230.942, 4.3342, -20.4801, 218.345, 1.8384, 19.6308, 230.969, 4.4899, -20.4785, 218.345, 1.8384, 19.6308, 218.751, 4.4899, 19.7988, 230.969, 4.4899, -20.4785, 230.969, -0.813, -20.4785, 218.751, -0.813, 19.7988, 230.547, 1.6799, -20.5046, 230.547, 1.6799, -20.5046, 218.751, -0.813, 19.7988, 230.521, 1.8384, -20.5062, 218.751, -0.813, 19.7988, 218.345, 1.8384, 19.6308, 230.521, 1.8384, -20.5062, 232.287, -3.3103, -20.3967, 219.944, -3.3103, 20.2931, 231.045, -0.9577, -20.4737, 231.045, -0.9577, -20.4737, 219.944, -3.3103, 20.2931, 230.969, -0.813, -20.4785, 219.944, -3.3103, 20.2931, 218.751, -0.813, 19.7988, 230.969, -0.813, -20.4785, 234.399, -5.5084, -20.2658, 221.855, -5.5085, 21.0848, 232.4, -3.4277, -20.3897, 232.4, -3.4277, -20.3897, 221.855, -5.5085, 21.0848, 232.287, -3.3103, -20.3967, 221.855, -5.5085, 21.0848, 219.944, -3.3103, 20.2931, 232.287, -3.3103, -20.3967, 237.182, -7.2796, -20.0932, 224.374, -7.2796, 22.1281, 234.528, -5.5908, -20.2577, 234.528, -5.5908, -20.2577, 224.374, -7.2796, 22.1281, 234.399, -5.5084, -20.2658, 224.374, -7.2796, 22.1281, 221.855, -5.5085, 21.0848, 234.399, -5.5084, -20.2658, 240.474, -8.5208, -19.8891, 227.354, -8.5208, 23.3622, 237.306, -7.3265, -20.0855, 237.306, -7.3265, -20.0855, 227.354, -8.5208, 23.3622, 237.182, -7.2796, -20.0932, 227.354, -8.5208, 23.3622, 224.374, -7.2796, 22.1281, 237.182, -7.2796, -20.0932, 244.083, -9.16, -19.6653, 230.621, -9.16, 24.7155, 240.575, -8.5387, -19.8829, 240.575, -8.5387, -19.8829, 230.621, -9.16, 24.7155, 240.474, -8.5208, -19.8891, 230.621, -9.16, 24.7155, 227.354, -8.5208, 23.3622, 240.474, -8.5208, -19.8891, 247.801, -9.16, -19.4348, 233.986, -9.16, 26.1093, 244.148, -9.16, -19.6613, 244.148, -9.16, -19.6613, 233.986, -9.16, 26.1093, 244.083, -9.16, -19.6653, 233.986, -9.16, 26.1093, 230.621, -9.16, 24.7155, 244.083, -9.16, -19.6653, 250.357, -8.7074, -19.2763, 251.37, -8.5208, -19.0749, 237.253, -8.5208, 27.4626, 250.357, -8.7074, -19.2763, 237.253, -8.5208, 27.4626, 247.827, -9.1555, -19.4332, 247.827, -9.1555, -19.4332, 237.253, -8.5208, 27.4626, 247.801, -9.16, -19.4348, 237.253, -8.5208, 27.4626, 233.986, -9.16, 26.1093, 247.801, -9.16, -19.4348, 251.37, -8.5208, -19.0749, 254.533, -7.2796, -18.4458, 240.232, -7.2796, 28.6967, 237.253, -8.5208, 27.4626, 251.37, -8.5208, -19.0749, 240.232, -7.2796, 28.6967, 254.533, -7.2796, -18.4458, 257.206, -5.5084, -17.9139, 242.751, -5.5084, 29.74, 240.232, -7.2796, 28.6967, 254.533, -7.2796, -18.4458, 242.751, -5.5084, 29.74, 257.206, -5.5084, -17.9139, 259.236, -3.3103, -17.5103, 244.662, -3.3103, 30.5317, 242.751, -5.5084, 29.74, 257.206, -5.5084, -17.9139, 244.662, -3.3103, 30.5317, 259.236, -3.3103, -17.5103, 260.502, -0.813, -17.2583, 245.855, -0.813, 31.026, 244.662, -3.3103, 30.5317, 259.236, -3.3103, -17.5103, 245.855, -0.813, 31.026, 260.502, -0.813, -17.2583, 260.933, 1.8384, -17.1727, 246.261, 1.8384, 31.194, 245.855, -0.813, 31.026, 260.502, -0.813, -17.2583, 246.261, 1.8384, 31.194, 246.261, 1.8384, 31.194, 245.855, 4.4899, 31.026, 222.07, 4.4899, 75.5251, 222.435, 1.8384, 75.769, 246.261, 1.8384, 31.194, 222.07, 4.4899, 75.5251, 245.855, 4.4899, 31.026, 244.662, 6.9872, 30.5317, 220.996, 6.9872, 74.8075, 222.07, 4.4899, 75.5251, 245.855, 4.4899, 31.026, 220.996, 6.9872, 74.8075, 244.662, 6.9872, 30.5317, 242.751, 9.1853, 29.74, 219.276, 9.1853, 73.6581, 220.996, 6.9872, 74.8075, 244.662, 6.9872, 30.5317, 219.276, 9.1853, 73.6581, 242.751, 9.1853, 29.74, 240.232, 10.9564, 28.6967, 217.009, 10.9564, 72.1435, 219.276, 9.1853, 73.6581, 242.751, 9.1853, 29.74, 217.009, 10.9564, 72.1435, 240.232, 10.9564, 28.6967, 237.253, 12.1977, 27.4626, 214.328, 12.1977, 70.3518, 217.009, 10.9564, 72.1435, 240.232, 10.9564, 28.6967, 214.328, 12.1977, 70.3518, 237.253, 12.1977, 27.4626, 233.986, 12.8369, 26.1093, 211.388, 12.8369, 68.3871, 214.328, 12.1977, 70.3518, 237.253, 12.1977, 27.4626, 211.388, 12.8369, 68.3871, 233.986, 12.8369, 26.1093, 230.621, 12.8369, 24.7155, 208.359, 12.8369, 66.3637, 211.388, 12.8369, 68.3871, 233.986, 12.8369, 26.1093, 208.359, 12.8369, 66.3637, 230.621, 12.8369, 24.7155, 227.354, 12.1977, 23.3622, 205.419, 12.1977, 64.399, 208.359, 12.8369, 66.3637, 230.621, 12.8369, 24.7155, 205.419, 12.1977, 64.399, 227.354, 12.1977, 23.3622, 224.374, 10.9564, 22.1281, 202.737, 10.9564, 62.6073, 205.419, 12.1977, 64.399, 227.354, 12.1977, 23.3622, 202.737, 10.9564, 62.6073, 224.374, 10.9564, 22.1281, 221.855, 9.1853, 21.0848, 200.471, 9.1853, 61.0927, 202.737, 10.9564, 62.6073, 224.374, 10.9564, 22.1281, 200.471, 9.1853, 61.0927, 221.855, 9.1853, 21.0848, 219.944, 6.9872, 20.2931, 198.75, 6.9872, 59.9433, 200.471, 9.1853, 61.0927, 221.855, 9.1853, 21.0848, 198.75, 6.9872, 59.9433, 219.944, 6.9872, 20.2931, 218.751, 4.4899, 19.7988, 197.677, 4.4899, 59.2257, 198.75, 6.9872, 59.9433, 219.944, 6.9872, 20.2931, 197.677, 4.4899, 59.2257, 218.751, 4.4899, 19.7988, 218.345, 1.8384, 19.6308, 197.312, 1.8384, 58.9819, 197.677, 4.4899, 59.2257, 218.751, 4.4899, 19.7988, 197.312, 1.8384, 58.9819, 218.345, 1.8384, 19.6308, 218.751, -0.813, 19.7988, 197.677, -0.813, 59.2257, 197.312, 1.8384, 58.9819, 218.345, 1.8384, 19.6308, 197.677, -0.813, 59.2257, 218.751, -0.813, 19.7988, 219.944, -3.3103, 20.2931, 198.75, -3.3103, 59.9433, 197.677, -0.813, 59.2257, 218.751, -0.813, 19.7988, 198.75, -3.3103, 59.9433, 219.944, -3.3103, 20.2931, 221.855, -5.5085, 21.0848, 200.471, -5.5084, 61.0927, 198.75, -3.3103, 59.9433, 219.944, -3.3103, 20.2931, 200.471, -5.5084, 61.0927, 221.855, -5.5085, 21.0848, 224.374, -7.2796, 22.1281, 202.737, -7.2796, 62.6073, 200.471, -5.5084, 61.0927, 221.855, -5.5085, 21.0848, 202.737, -7.2796, 62.6073, 224.374, -7.2796, 22.1281, 227.354, -8.5208, 23.3622, 205.419, -8.5208, 64.399, 202.737, -7.2796, 62.6073, 224.374, -7.2796, 22.1281, 205.419, -8.5208, 64.399, 227.354, -8.5208, 23.3622, 230.621, -9.16, 24.7155, 208.359, -9.16, 66.3637, 205.419, -8.5208, 64.399, 227.354, -8.5208, 23.3622, 208.359, -9.16, 66.3637, 230.621, -9.16, 24.7155, 233.986, -9.16, 26.1093, 211.388, -9.16, 68.3871, 208.359, -9.16, 66.3637, 230.621, -9.16, 24.7155, 211.388, -9.16, 68.3871, 233.986, -9.16, 26.1093, 237.253, -8.5208, 27.4626, 214.328, -8.5208, 70.3518, 211.388, -9.16, 68.3871, 233.986, -9.16, 26.1093, 214.328, -8.5208, 70.3518, 237.253, -8.5208, 27.4626, 240.232, -7.2796, 28.6967, 217.009, -7.2796, 72.1435, 214.328, -8.5208, 70.3518, 237.253, -8.5208, 27.4626, 217.009, -7.2796, 72.1435, 240.232, -7.2796, 28.6967, 242.751, -5.5084, 29.74, 219.276, -5.5084, 73.6581, 217.009, -7.2796, 72.1435, 240.232, -7.2796, 28.6967, 219.276, -5.5084, 73.6581, 242.751, -5.5084, 29.74, 244.662, -3.3103, 30.5317, 220.996, -3.3103, 74.8075, 219.276, -5.5084, 73.6581, 242.751, -5.5084, 29.74, 220.996, -3.3103, 74.8075, 244.662, -3.3103, 30.5317, 245.855, -0.813, 31.026, 222.07, -0.813, 75.5251, 220.996, -3.3103, 74.8075, 244.662, -3.3103, 30.5317, 222.07, -0.813, 75.5251, 245.855, -0.813, 31.026, 246.261, 1.8384, 31.194, 222.435, 1.8384, 75.769, 222.07, -0.813, 75.5251, 245.855, -0.813, 31.026, 222.435, 1.8384, 75.769, 222.435, 1.8384, 75.769, 222.07, 4.4899, 75.5251, 190.061, 4.4899, 114.529, 190.371, 1.8384, 114.839, 222.435, 1.8384, 75.769, 190.061, 4.4899, 114.529, 222.07, 4.4899, 75.5251, 220.996, 6.9872, 74.8075, 189.147, 6.9872, 113.616, 190.061, 4.4899, 114.529, 222.07, 4.4899, 75.5251, 189.147, 6.9872, 113.616, 220.996, 6.9872, 74.8075, 219.276, 9.1853, 73.6581, 187.684, 9.1853, 112.153, 189.147, 6.9872, 113.616, 220.996, 6.9872, 74.8075, 187.684, 9.1853, 112.153, 219.276, 9.1853, 73.6581, 217.009, 10.9564, 72.1435, 185.757, 10.9564, 110.225, 187.684, 9.1853, 112.153, 219.276, 9.1853, 73.6581, 185.757, 10.9564, 110.225, 217.009, 10.9564, 72.1435, 214.328, 12.1977, 70.3518, 183.476, 12.1977, 107.944, 185.757, 10.9564, 110.225, 217.009, 10.9564, 72.1435, 183.476, 12.1977, 107.944, 214.328, 12.1977, 70.3518, 211.388, 12.8369, 68.3871, 180.976, 12.8369, 105.444, 183.476, 12.1977, 107.944, 214.328, 12.1977, 70.3518, 180.976, 12.8369, 105.444, 211.388, 12.8369, 68.3871, 208.359, 12.8369, 66.3637, 178.4, 12.8369, 102.869, 180.976, 12.8369, 105.444, 211.388, 12.8369, 68.3871, 178.4, 12.8369, 102.869, 208.359, 12.8369, 66.3637, 205.419, 12.1977, 64.399, 175.9, 12.1977, 100.368, 178.4, 12.8369, 102.869, 208.359, 12.8369, 66.3637, 175.9, 12.1977, 100.368, 205.419, 12.1977, 64.399, 202.737, 10.9564, 62.6073, 173.62, 10.9564, 98.0877, 175.9, 12.1977, 100.368, 205.419, 12.1977, 64.399, 173.62, 10.9564, 98.0877, 202.737, 10.9564, 62.6073, 200.471, 9.1853, 61.0927, 171.692, 9.1853, 96.1599, 173.62, 10.9564, 98.0877, 202.737, 10.9564, 62.6073, 171.692, 9.1853, 96.1599, 200.471, 9.1853, 61.0927, 198.75, 6.9872, 59.9433, 170.229, 6.9872, 94.697, 171.692, 9.1853, 96.1599, 200.471, 9.1853, 61.0927, 170.229, 6.9872, 94.697, 198.75, 6.9872, 59.9433, 197.677, 4.4899, 59.2257, 169.316, 4.4899, 93.7837, 170.229, 6.9872, 94.697, 198.75, 6.9872, 59.9433, 169.316, 4.4899, 93.7837, 197.677, 4.4899, 59.2257, 197.312, 1.8384, 58.9819, 169.005, 1.8384, 93.4733, 169.316, 4.4899, 93.7837, 197.677, 4.4899, 59.2257, 169.005, 1.8384, 93.4733, 197.312, 1.8384, 58.9819, 197.677, -0.813, 59.2257, 169.316, -0.813, 93.7837, 169.005, 1.8384, 93.4733, 197.312, 1.8384, 58.9819, 169.316, -0.813, 93.7837, 197.677, -0.813, 59.2257, 198.75, -3.3103, 59.9433, 170.229, -3.3103, 94.697, 169.316, -0.813, 93.7837, 197.677, -0.813, 59.2257, 170.229, -3.3103, 94.697, 198.75, -3.3103, 59.9433, 200.471, -5.5084, 61.0927, 171.692, -5.5084, 96.1599, 170.229, -3.3103, 94.697, 198.75, -3.3103, 59.9433, 171.692, -5.5084, 96.1599, 200.471, -5.5084, 61.0927, 202.737, -7.2796, 62.6073, 173.62, -7.2796, 98.0877, 171.692, -5.5084, 96.1599, 200.471, -5.5084, 61.0927, 173.62, -7.2796, 98.0877, 202.737, -7.2796, 62.6073, 205.419, -8.5208, 64.399, 175.9, -8.5208, 100.368, 173.62, -7.2796, 98.0877, 202.737, -7.2796, 62.6073, 175.9, -8.5208, 100.368, 205.419, -8.5208, 64.399, 208.359, -9.16, 66.3637, 178.4, -9.16, 102.869, 175.9, -8.5208, 100.368, 205.419, -8.5208, 64.399, 178.4, -9.16, 102.869, 208.359, -9.16, 66.3637, 211.388, -9.16, 68.3871, 180.976, -9.16, 105.444, 178.4, -9.16, 102.869, 208.359, -9.16, 66.3637, 180.976, -9.16, 105.444, 211.388, -9.16, 68.3871, 214.328, -8.5208, 70.3518, 183.476, -8.5208, 107.944, 180.976, -9.16, 105.444, 211.388, -9.16, 68.3871, 183.476, -8.5208, 107.944, 214.328, -8.5208, 70.3518, 217.009, -7.2796, 72.1435, 185.757, -7.2796, 110.225, 183.476, -8.5208, 107.944, 214.328, -8.5208, 70.3518, 185.757, -7.2796, 110.225, 217.009, -7.2796, 72.1435, 219.276, -5.5084, 73.6581, 187.684, -5.5084, 112.153, 185.757, -7.2796, 110.225, 217.009, -7.2796, 72.1435, 187.684, -5.5084, 112.153, 219.276, -5.5084, 73.6581, 220.996, -3.3103, 74.8075, 189.147, -3.3103, 113.616, 187.684, -5.5084, 112.153, 219.276, -5.5084, 73.6581, 189.147, -3.3103, 113.616, 220.996, -3.3103, 74.8075, 222.07, -0.813, 75.5251, 190.061, -0.813, 114.529, 189.147, -3.3103, 113.616, 220.996, -3.3103, 74.8075, 190.061, -0.813, 114.529, 222.07, -0.813, 75.5251, 222.435, 1.8384, 75.769, 190.371, 1.8384, 114.839, 190.061, -0.813, 114.529, 222.07, -0.813, 75.5251, 190.371, 1.8384, 114.839, 190.371, 1.8384, 114.839, 190.061, 4.4899, 114.529, 151.057, 4.4899, 146.538, 151.301, 1.8384, 146.903, 190.371, 1.8384, 114.839, 151.057, 4.4899, 146.538, 190.061, 4.4899, 114.529, 189.147, 6.9872, 113.616, 150.339, 6.9872, 145.464, 151.057, 4.4899, 146.538, 190.061, 4.4899, 114.529, 150.339, 6.9872, 145.464, 189.147, 6.9872, 113.616, 187.684, 9.1853, 112.153, 149.19, 9.1853, 143.744, 150.339, 6.9872, 145.464, 189.147, 6.9872, 113.616, 149.19, 9.1853, 143.744, 187.684, 9.1853, 112.153, 185.757, 10.9564, 110.225, 147.675, 10.9564, 141.477, 149.19, 9.1853, 143.744, 187.684, 9.1853, 112.153, 147.675, 10.9564, 141.477, 185.757, 10.9564, 110.225, 183.476, 12.1977, 107.944, 145.884, 12.1977, 138.796, 147.675, 10.9564, 141.477, 185.757, 10.9564, 110.225, 145.884, 12.1977, 138.796, 183.476, 12.1977, 107.944, 180.976, 12.8369, 105.444, 143.919, 12.8369, 135.856, 145.884, 12.1977, 138.796, 183.476, 12.1977, 107.944, 143.919, 12.8369, 135.856, 180.976, 12.8369, 105.444, 178.4, 12.8369, 102.869, 141.896, 12.8369, 132.827, 143.919, 12.8369, 135.856, 180.976, 12.8369, 105.444, 141.896, 12.8369, 132.827, 178.4, 12.8369, 102.869, 175.9, 12.1977, 100.368, 139.931, 12.1977, 129.887, 141.896, 12.8369, 132.827, 178.4, 12.8369, 102.869, 139.931, 12.1977, 129.887, 175.9, 12.1977, 100.368, 173.62, 10.9564, 98.0877, 138.139, 10.9564, 127.206, 139.931, 12.1977, 129.887, 175.9, 12.1977, 100.368, 138.139, 10.9564, 127.206, 173.62, 10.9564, 98.0877, 171.692, 9.1853, 96.1599, 136.625, 9.1853, 124.939, 138.139, 10.9564, 127.206, 173.62, 10.9564, 98.0877, 136.625, 9.1853, 124.939, 171.692, 9.1853, 96.1599, 170.229, 6.9872, 94.697, 135.475, 6.9872, 123.219, 136.625, 9.1853, 124.939, 171.692, 9.1853, 96.1599, 135.475, 6.9872, 123.219, 170.229, 6.9872, 94.697, 169.316, 4.4899, 93.7837, 134.758, 4.4899, 122.145, 135.475, 6.9872, 123.219, 170.229, 6.9872, 94.697, 134.758, 4.4899, 122.145, 169.316, 4.4899, 93.7837, 169.005, 1.8384, 93.4733, 134.514, 1.8384, 121.78, 134.758, 4.4899, 122.145, 169.316, 4.4899, 93.7837, 134.514, 1.8384, 121.78, 169.005, 1.8384, 93.4733, 169.316, -0.813, 93.7837, 134.758, -0.813, 122.145, 134.514, 1.8384, 121.78, 169.005, 1.8384, 93.4733, 134.758, -0.813, 122.145, 169.316, -0.813, 93.7837, 170.229, -3.3103, 94.697, 135.475, -3.3103, 123.219, 134.758, -0.813, 122.145, 169.316, -0.813, 93.7837, 135.475, -3.3103, 123.219, 170.229, -3.3103, 94.697, 171.692, -5.5084, 96.1599, 136.625, -5.5084, 124.939, 135.475, -3.3103, 123.219, 170.229, -3.3103, 94.697, 136.625, -5.5084, 124.939, 171.692, -5.5084, 96.1599, 173.62, -7.2796, 98.0877, 138.139, -7.2796, 127.206, 136.625, -5.5084, 124.939, 171.692, -5.5084, 96.1599, 138.139, -7.2796, 127.206, 173.62, -7.2796, 98.0877, 175.9, -8.5208, 100.368, 139.931, -8.5208, 129.887, 138.139, -7.2796, 127.206, 173.62, -7.2796, 98.0877, 139.931, -8.5208, 129.887, 175.9, -8.5208, 100.368, 178.4, -9.16, 102.869, 141.896, -9.16, 132.827, 139.931, -8.5208, 129.887, 175.9, -8.5208, 100.368, 141.896, -9.16, 132.827, 178.4, -9.16, 102.869, 180.976, -9.16, 105.444, 143.919, -9.16, 135.856, 141.896, -9.16, 132.827, 178.4, -9.16, 102.869, 143.919, -9.16, 135.856, 180.976, -9.16, 105.444, 183.476, -8.5208, 107.944, 145.884, -8.5208, 138.796, 143.919, -9.16, 135.856, 180.976, -9.16, 105.444, 145.884, -8.5208, 138.796, 183.476, -8.5208, 107.944, 185.757, -7.2796, 110.225, 147.675, -7.2796, 141.477, 145.884, -8.5208, 138.796, 183.476, -8.5208, 107.944, 147.675, -7.2796, 141.477, 185.757, -7.2796, 110.225, 187.684, -5.5084, 112.153, 149.19, -5.5084, 143.744, 147.675, -7.2796, 141.477, 185.757, -7.2796, 110.225, 149.19, -5.5084, 143.744, 187.684, -5.5084, 112.153, 189.147, -3.3103, 113.616, 150.339, -3.3103, 145.464, 149.19, -5.5084, 143.744, 187.684, -5.5084, 112.153, 150.339, -3.3103, 145.464, 189.147, -3.3103, 113.616, 190.061, -0.813, 114.529, 151.057, -0.813, 146.538, 150.339, -3.3103, 145.464, 189.147, -3.3103, 113.616, 151.057, -0.813, 146.538, 190.061, -0.813, 114.529, 190.371, 1.8384, 114.839, 151.301, 1.8384, 146.903, 151.057, -0.813, 146.538, 190.061, -0.813, 114.529, 151.301, 1.8384, 146.903, 151.301, 1.8384, 146.903, 151.057, 4.4899, 146.538, 106.558, 4.4899, 170.324, 106.726, 1.8384, 170.729, 151.301, 1.8384, 146.903, 106.558, 4.4899, 170.324, 151.057, 4.4899, 146.538, 150.339, 6.9872, 145.464, 106.064, 6.9872, 169.13, 106.558, 4.4899, 170.324, 151.057, 4.4899, 146.538, 106.064, 6.9872, 169.13, 150.339, 6.9872, 145.464, 149.19, 9.1853, 143.744, 105.272, 9.1853, 167.219, 106.064, 6.9872, 169.13, 150.339, 6.9872, 145.464, 105.272, 9.1853, 167.219, 149.19, 9.1853, 143.744, 147.675, 10.9564, 141.477, 104.229, 10.9564, 164.7, 105.272, 9.1853, 167.219, 149.19, 9.1853, 143.744, 104.229, 10.9564, 164.7, 147.675, 10.9564, 141.477, 145.884, 12.1977, 138.796, 102.994, 12.1977, 161.721, 104.229, 10.9564, 164.7, 147.675, 10.9564, 141.477, 102.994, 12.1977, 161.721, 145.884, 12.1977, 138.796, 143.919, 12.8369, 135.856, 101.641, 12.8369, 158.454, 102.994, 12.1977, 161.721, 145.884, 12.1977, 138.796, 101.641, 12.8369, 158.454, 143.919, 12.8369, 135.856, 141.896, 12.8369, 132.827, 100.247, 12.8369, 155.089, 101.641, 12.8369, 158.454, 143.919, 12.8369, 135.856, 100.247, 12.8369, 155.089, 141.896, 12.8369, 132.827, 139.931, 12.1977, 129.887, 98.8941, 12.1977, 151.822, 100.247, 12.8369, 155.089, 141.896, 12.8369, 132.827, 98.8941, 12.1977, 151.822, 139.931, 12.1977, 129.887, 138.139, 10.9564, 127.206, 97.66, 10.9564, 148.842, 98.8941, 12.1977, 151.822, 139.931, 12.1977, 129.887, 97.66, 10.9564, 148.842, 138.139, 10.9564, 127.206, 136.625, 9.1853, 124.939, 96.6167, 9.1853, 146.324, 97.66, 10.9564, 148.842, 138.139, 10.9564, 127.206, 96.6167, 9.1853, 146.324, 136.625, 9.1853, 124.939, 135.475, 6.9872, 123.219, 95.825, 6.9872, 144.412, 96.6167, 9.1853, 146.324, 136.625, 9.1853, 124.939, 95.825, 6.9872, 144.412, 135.475, 6.9872, 123.219, 134.758, 4.4899, 122.145, 95.3307, 4.4899, 143.219, 95.825, 6.9872, 144.412, 135.475, 6.9872, 123.219, 95.3307, 4.4899, 143.219, 134.758, 4.4899, 122.145, 134.514, 1.8384, 121.78, 95.1627, 1.8384, 142.813, 95.3307, 4.4899, 143.219, 134.758, 4.4899, 122.145, 95.1627, 1.8384, 142.813, 134.514, 1.8384, 121.78, 134.758, -0.813, 122.145, 95.3307, -0.813, 143.219, 95.1627, 1.8384, 142.813, 134.514, 1.8384, 121.78, 95.3307, -0.813, 143.219, 134.758, -0.813, 122.145, 135.475, -3.3103, 123.219, 95.825, -3.3103, 144.412, 95.3307, -0.813, 143.219, 134.758, -0.813, 122.145, 95.825, -3.3103, 144.412, 135.475, -3.3103, 123.219, 136.625, -5.5084, 124.939, 96.6167, -5.5084, 146.324, 95.825, -3.3103, 144.412, 135.475, -3.3103, 123.219, 96.6167, -5.5084, 146.324, 136.625, -5.5084, 124.939, 138.139, -7.2796, 127.206, 97.66, -7.2796, 148.842, 96.6167, -5.5084, 146.324, 136.625, -5.5084, 124.939, 97.66, -7.2796, 148.842, 138.139, -7.2796, 127.206, 139.931, -8.5208, 129.887, 98.8941, -8.5208, 151.822, 97.66, -7.2796, 148.842, 138.139, -7.2796, 127.206, 98.8941, -8.5208, 151.822, 139.931, -8.5208, 129.887, 141.896, -9.16, 132.827, 100.247, -9.16, 155.089, 98.8941, -8.5208, 151.822, 139.931, -8.5208, 129.887, 100.247, -9.16, 155.089, 141.896, -9.16, 132.827, 143.919, -9.16, 135.856, 101.641, -9.16, 158.454, 100.247, -9.16, 155.089, 141.896, -9.16, 132.827, 101.641, -9.16, 158.454, 143.919, -9.16, 135.856, 145.884, -8.5208, 138.796, 102.994, -8.5208, 161.721, 101.641, -9.16, 158.454, 143.919, -9.16, 135.856, 102.994, -8.5208, 161.721, 145.884, -8.5208, 138.796, 147.675, -7.2796, 141.477, 104.229, -7.2796, 164.7, 102.994, -8.5208, 161.721, 145.884, -8.5208, 138.796, 104.229, -7.2796, 164.7, 147.675, -7.2796, 141.477, 149.19, -5.5084, 143.744, 105.272, -5.5084, 167.219, 104.229, -7.2796, 164.7, 147.675, -7.2796, 141.477, 105.272, -5.5084, 167.219, 149.19, -5.5084, 143.744, 150.339, -3.3103, 145.464, 106.064, -3.3103, 169.13, 105.272, -5.5084, 167.219, 149.19, -5.5084, 143.744, 106.064, -3.3103, 169.13, 150.339, -3.3103, 145.464, 151.057, -0.813, 146.538, 106.558, -0.813, 170.324, 106.064, -3.3103, 169.13, 150.339, -3.3103, 145.464, 106.558, -0.813, 170.324, 151.057, -0.813, 146.538, 151.301, 1.8384, 146.903, 106.726, 1.8384, 170.729, 106.558, -0.813, 170.324, 151.057, -0.813, 146.538, 106.726, 1.8384, 170.729, 106.726, 1.8384, 170.729, 106.558, 4.4899, 170.324, 58.2736, 4.4899, 184.97, 58.3592, 1.8384, 185.401, 106.726, 1.8384, 170.729, 58.2736, 4.4899, 184.97, 106.558, 4.4899, 170.324, 106.064, 6.9872, 169.13, 58.0216, 6.9872, 183.704, 58.2736, 4.4899, 184.97, 106.558, 4.4899, 170.324, 58.0216, 6.9872, 183.704, 106.064, 6.9872, 169.13, 105.272, 9.1853, 167.219, 57.618, 9.1853, 181.675, 58.0216, 6.9872, 183.704, 106.064, 6.9872, 169.13, 57.618, 9.1853, 181.675, 105.272, 9.1853, 167.219, 104.229, 10.9564, 164.7, 57.0861, 10.9564, 179.001, 57.618, 9.1853, 181.675, 105.272, 9.1853, 167.219, 57.0861, 10.9564, 179.001, 104.229, 10.9564, 164.7, 102.994, 12.1977, 161.721, 56.457, 12.1977, 175.838, 57.0861, 10.9564, 179.001, 104.229, 10.9564, 164.7, 56.457, 12.1977, 175.838, 102.994, 12.1977, 161.721, 101.641, 12.8369, 158.454, 55.7671, 12.8369, 172.369, 56.457, 12.1977, 175.838, 102.994, 12.1977, 161.721, 55.7671, 12.8369, 172.369, 101.641, 12.8369, 158.454, 100.247, 12.8369, 155.089, 55.0565, 12.8369, 168.797, 55.7671, 12.8369, 172.369, 101.641, 12.8369, 158.454, 55.0565, 12.8369, 168.797, 100.247, 12.8369, 155.089, 98.8941, 12.1977, 151.822, 54.3666, 12.1977, 165.329, 55.0565, 12.8369, 168.797, 100.247, 12.8369, 155.089, 54.3666, 12.1977, 165.329, 98.8941, 12.1977, 151.822, 97.66, 10.9564, 148.842, 53.7375, 10.9564, 162.166, 54.3666, 12.1977, 165.329, 98.8941, 12.1977, 151.822, 53.7375, 10.9564, 162.166, 97.66, 10.9564, 148.842, 96.6167, 9.1853, 146.324, 53.2056, 9.1853, 159.492, 53.7375, 10.9564, 162.166, 97.66, 10.9564, 148.842, 53.2056, 9.1853, 159.492, 96.6167, 9.1853, 146.324, 95.825, 6.9872, 144.412, 52.802, 6.9872, 157.463, 53.2056, 9.1853, 159.492, 96.6167, 9.1853, 146.324, 52.802, 6.9872, 157.463, 95.825, 6.9872, 144.412, 95.3307, 4.4899, 143.219, 52.55, 4.4899, 156.196, 52.802, 6.9872, 157.463, 95.825, 6.9872, 144.412, 52.55, 4.4899, 156.196, 95.3307, 4.4899, 143.219, 95.1627, 1.8384, 142.813, 52.4644, 1.8384, 155.766, 52.55, 4.4899, 156.196, 95.3307, 4.4899, 143.219, 52.4644, 1.8384, 155.766, 95.1627, 1.8384, 142.813, 95.3307, -0.813, 143.219, 52.55, -0.813, 156.196, 52.4644, 1.8384, 155.766, 95.1627, 1.8384, 142.813, 52.55, -0.813, 156.196, 95.3307, -0.813, 143.219, 95.825, -3.3103, 144.412, 52.802, -3.3103, 157.463, 52.55, -0.813, 156.196, 95.3307, -0.813, 143.219, 52.802, -3.3103, 157.463, 95.825, -3.3103, 144.412, 96.6167, -5.5084, 146.324, 53.2056, -5.5084, 159.492, 52.802, -3.3103, 157.463, 95.825, -3.3103, 144.412, 53.2056, -5.5084, 159.492, 96.6167, -5.5084, 146.324, 97.66, -7.2796, 148.842, 53.7375, -7.2796, 162.166, 53.2056, -5.5084, 159.492, 96.6167, -5.5084, 146.324, 53.7375, -7.2796, 162.166, 97.66, -7.2796, 148.842, 98.8941, -8.5208, 151.822, 54.3666, -8.5208, 165.329, 53.7375, -7.2796, 162.166, 97.66, -7.2796, 148.842, 54.3666, -8.5208, 165.329, 98.8941, -8.5208, 151.822, 100.247, -9.16, 155.089, 55.0565, -9.16, 168.797, 54.3666, -8.5208, 165.329, 98.8941, -8.5208, 151.822, 55.0565, -9.16, 168.797, 100.247, -9.16, 155.089, 101.641, -9.16, 158.454, 55.7671, -9.16, 172.369, 55.0565, -9.16, 168.797, 100.247, -9.16, 155.089, 55.7671, -9.16, 172.369, 101.641, -9.16, 158.454, 102.994, -8.5208, 161.721, 56.457, -8.5208, 175.838, 55.7671, -9.16, 172.369, 101.641, -9.16, 158.454, 56.457, -8.5208, 175.838, 102.994, -8.5208, 161.721, 104.229, -7.2796, 164.7, 57.0861, -7.2796, 179.001, 56.457, -8.5208, 175.838, 102.994, -8.5208, 161.721, 57.0861, -7.2796, 179.001, 104.229, -7.2796, 164.7, 105.272, -5.5084, 167.219, 57.618, -5.5084, 181.675, 57.0861, -7.2796, 179.001, 104.229, -7.2796, 164.7, 57.618, -5.5084, 181.675, 105.272, -5.5084, 167.219, 106.064, -3.3103, 169.13, 58.0216, -3.3103, 183.704, 57.618, -5.5084, 181.675, 105.272, -5.5084, 167.219, 58.0216, -3.3103, 183.704, 106.064, -3.3103, 169.13, 106.558, -0.813, 170.324, 58.2736, -0.813, 184.97, 58.0216, -3.3103, 183.704, 106.064, -3.3103, 169.13, 58.2736, -0.813, 184.97, 106.558, -0.813, 170.324, 106.726, 1.8384, 170.729, 58.3592, 1.8384, 185.401, 58.2736, -0.813, 184.97, 106.558, -0.813, 170.324, 58.3592, 1.8384, 185.401, 58.3592, 1.8384, 185.401, 58.2736, 4.4899, 184.97, 8.0596, 4.4899, 189.916, 8.0596, 1.8384, 190.355, 58.3592, 1.8384, 185.401, 8.0596, 4.4899, 189.916, 58.2736, 4.4899, 184.97, 58.0216, 6.9872, 183.704, 8.0596, 6.9872, 188.625, 8.0596, 4.4899, 189.916, 58.2736, 4.4899, 184.97, 8.0596, 6.9872, 188.625, 58.0216, 6.9872, 183.704, 57.618, 9.1853, 181.675, 8.0596, 9.1853, 186.556, 8.0596, 6.9872, 188.625, 58.0216, 6.9872, 183.704, 8.0596, 9.1853, 186.556, 57.618, 9.1853, 181.675, 57.0861, 10.9564, 179.001, 8.0596, 10.9564, 183.829, 8.0596, 9.1853, 186.556, 57.618, 9.1853, 181.675, 8.0596, 10.9564, 183.829, 57.0861, 10.9564, 179.001, 56.457, 12.1977, 175.838, 8.0596, 12.1977, 180.604, 8.0596, 10.9564, 183.829, 57.0861, 10.9564, 179.001, 8.0596, 12.1977, 180.604, 56.457, 12.1977, 175.838, 55.7671, 12.8369, 172.369, 8.0596, 12.8369, 177.068, 8.0596, 12.1977, 180.604, 56.457, 12.1977, 175.838, 8.0596, 12.8369, 177.068, 55.7671, 12.8369, 172.369, 55.0565, 12.8369, 168.797, 8.0596, 12.8369, 173.426, 8.0596, 12.8369, 177.068, 55.7671, 12.8369, 172.369, 8.0596, 12.8369, 173.426, 55.0565, 12.8369, 168.797, 54.3666, 12.1977, 165.329, 8.0596, 12.1977, 169.89, 8.0596, 12.8369, 173.426, 55.0565, 12.8369, 168.797, 8.0596, 12.1977, 169.89, 54.3666, 12.1977, 165.329, 53.7375, 10.9564, 162.166, 8.0596, 10.9564, 166.665, 8.0596, 12.1977, 169.89, 54.3666, 12.1977, 165.329, 8.0596, 10.9564, 166.665, 53.7375, 10.9564, 162.166, 53.2056, 9.1853, 159.492, 8.0596, 9.1853, 163.939, 8.0596, 10.9564, 166.665, 53.7375, 10.9564, 162.166, 8.0596, 9.1853, 163.939, 53.2056, 9.1853, 159.492, 52.802, 6.9872, 157.463, 8.0596, 6.9872, 161.87, 8.0596, 9.1853, 163.939, 53.2056, 9.1853, 159.492, 8.0596, 6.9872, 161.87, 52.802, 6.9872, 157.463, 52.55, 4.4899, 156.196, 8.0596, 4.4899, 160.578, 8.0596, 6.9872, 161.87, 52.802, 6.9872, 157.463, 8.0596, 4.4899, 160.578, 52.55, 4.4899, 156.196, 52.4644, 1.8384, 155.766, 8.0596, 1.8384, 160.139, 8.0596, 4.4899, 160.578, 52.55, 4.4899, 156.196, 8.0596, 1.8384, 160.139, 52.4644, 1.8384, 155.766, 52.55, -0.813, 156.196, 8.0596, -0.813, 160.578, 8.0596, 1.8384, 160.139, 52.4644, 1.8384, 155.766, 8.0596, -0.813, 160.578, 52.55, -0.813, 156.196, 52.802, -3.3103, 157.463, 8.0596, -3.3103, 161.87, 8.0596, -0.813, 160.578, 52.55, -0.813, 156.196, 8.0596, -3.3103, 161.87, 52.802, -3.3103, 157.463, 53.2056, -5.5084, 159.492, 8.0596, -5.5084, 163.939, 8.0596, -3.3103, 161.87, 52.802, -3.3103, 157.463, 8.0596, -5.5084, 163.939, 53.2056, -5.5084, 159.492, 53.7375, -7.2796, 162.166, 8.0596, -7.2796, 166.665, 8.0596, -5.5084, 163.939, 53.2056, -5.5084, 159.492, 8.0596, -7.2796, 166.665, 53.7375, -7.2796, 162.166, 54.3666, -8.5208, 165.329, 8.0596, -8.5208, 169.89, 8.0596, -7.2796, 166.665, 53.7375, -7.2796, 162.166, 8.0596, -8.5208, 169.89, 54.3666, -8.5208, 165.329, 55.0565, -9.16, 168.797, 8.0596, -9.16, 173.426, 8.0596, -8.5208, 169.89, 54.3666, -8.5208, 165.329, 8.0596, -9.16, 173.426, 55.0565, -9.16, 168.797, 55.7671, -9.16, 172.369, 8.0596, -9.16, 177.068, 8.0596, -9.16, 173.426, 55.0565, -9.16, 168.797, 8.0596, -9.16, 177.068, 55.7671, -9.16, 172.369, 56.457, -8.5208, 175.838, 8.0596, -8.5208, 180.604, 8.0596, -9.16, 177.068, 55.7671, -9.16, 172.369, 8.0596, -8.5208, 180.604, 56.457, -8.5208, 175.838, 57.0861, -7.2796, 179.001, 8.0596, -7.2796, 183.829, 8.0596, -8.5208, 180.604, 56.457, -8.5208, 175.838, 8.0596, -7.2796, 183.829, 57.0861, -7.2796, 179.001, 57.618, -5.5084, 181.675, 8.0596, -5.5084, 186.556, 8.0596, -7.2796, 183.829, 57.0861, -7.2796, 179.001, 8.0596, -5.5084, 186.556, 57.618, -5.5084, 181.675, 58.0216, -3.3103, 183.704, 8.0596, -3.3103, 188.625, 8.0596, -5.5084, 186.556, 57.618, -5.5084, 181.675, 8.0596, -3.3103, 188.625, 58.0216, -3.3103, 183.704, 58.2736, -0.813, 184.97, 8.0596, -0.813, 189.916, 8.0596, -3.3103, 188.625, 58.0216, -3.3103, 183.704, 8.0596, -0.813, 189.916, 58.2736, -0.813, 184.97, 58.3592, 1.8384, 185.401, 8.0596, 1.8384, 190.355, 8.0596, -0.813, 189.916, 58.2736, -0.813, 184.97, 8.0596, 1.8384, 190.355, 8.0596, 1.8384, 190.355, 8.0596, 4.4899, 189.916, -42.1544, 4.4899, 184.97, -42.2401, 1.8384, 185.401, 8.0596, 1.8384, 190.355, -42.1544, 4.4899, 184.97, 8.0596, 4.4899, 189.916, 8.0596, 6.9872, 188.625, -41.9025, 6.9872, 183.704, -42.1544, 4.4899, 184.97, 8.0596, 4.4899, 189.916, -41.9025, 6.9872, 183.704, 8.0596, 6.9872, 188.625, 8.0596, 9.1853, 186.556, -41.4988, 9.1853, 181.675, -41.9025, 6.9872, 183.704, 8.0596, 6.9872, 188.625, -41.4988, 9.1853, 181.675, 8.0596, 9.1853, 186.556, 8.0596, 10.9564, 183.829, -40.967, 10.9564, 179.001, -41.4988, 9.1853, 181.675, 8.0596, 9.1853, 186.556, -40.967, 10.9564, 179.001, 8.0596, 10.9564, 183.829, 8.0596, 12.1977, 180.604, -40.3378, 12.1977, 175.838, -40.967, 10.9564, 179.001, 8.0596, 10.9564, 183.829, -40.3378, 12.1977, 175.838, 8.0596, 12.1977, 180.604, 8.0596, 12.8369, 177.068, -39.6479, 12.8369, 172.369, -40.3378, 12.1977, 175.838, 8.0596, 12.1977, 180.604, -39.6479, 12.8369, 172.369, 8.0596, 12.8369, 177.068, 8.0596, 12.8369, 173.426, -38.9374, 12.8369, 168.797, -39.6479, 12.8369, 172.369, 8.0596, 12.8369, 177.068, -38.9374, 12.8369, 168.797, 8.0596, 12.8369, 173.426, 8.0596, 12.1977, 169.89, -38.2475, 12.1977, 165.329, -38.9374, 12.8369, 168.797, 8.0596, 12.8369, 173.426, -38.2475, 12.1977, 165.329, 8.0596, 12.1977, 169.89, 8.0596, 10.9564, 166.665, -37.6183, 10.9564, 162.166, -38.2475, 12.1977, 165.329, 8.0596, 12.1977, 169.89, -37.6183, 10.9564, 162.166, 8.0596, 10.9564, 166.665, 8.0596, 9.1853, 163.939, -37.0865, 9.1853, 159.492, -37.6183, 10.9564, 162.166, 8.0596, 10.9564, 166.665, -37.0865, 9.1853, 159.492, 8.0596, 9.1853, 163.939, 8.0596, 6.9872, 161.87, -36.6828, 6.9872, 157.463, -37.0865, 9.1853, 159.492, 8.0596, 9.1853, 163.939, -36.6828, 6.9872, 157.463, 8.0596, 6.9872, 161.87, 8.0596, 4.4899, 160.578, -36.4309, 4.4899, 156.196, -36.6828, 6.9872, 157.463, 8.0596, 6.9872, 161.87, -36.4309, 4.4899, 156.196, 8.0596, 4.4899, 160.578, 8.0596, 1.8384, 160.139, -36.3452, 1.8384, 155.766, -36.4309, 4.4899, 156.196, 8.0596, 4.4899, 160.578, -36.3452, 1.8384, 155.766, 8.0596, 1.8384, 160.139, 8.0596, -0.813, 160.578, -36.4309, -0.813, 156.196, -36.3452, 1.8384, 155.766, 8.0596, 1.8384, 160.139, -36.4309, -0.813, 156.196, 8.0596, -0.813, 160.578, 8.0596, -3.3103, 161.87, -36.6828, -3.3103, 157.463, -36.4309, -0.813, 156.196, 8.0596, -0.813, 160.578, -36.6828, -3.3103, 157.463, 8.0596, -3.3103, 161.87, 8.0596, -5.5084, 163.939, -37.0865, -5.5084, 159.492, -36.6828, -3.3103, 157.463, 8.0596, -3.3103, 161.87, -37.0865, -5.5084, 159.492, 8.0596, -5.5084, 163.939, 8.0596, -7.2796, 166.665, -37.6183, -7.2796, 162.166, -37.0865, -5.5084, 159.492, 8.0596, -5.5084, 163.939, -37.6183, -7.2796, 162.166, 8.0596, -7.2796, 166.665, 8.0596, -8.5208, 169.89, -38.2475, -8.5208, 165.329, -37.6183, -7.2796, 162.166, 8.0596, -7.2796, 166.665, -38.2475, -8.5208, 165.329, 8.0596, -8.5208, 169.89, 8.0596, -9.16, 173.426, -38.9374, -9.16, 168.797, -38.2475, -8.5208, 165.329, 8.0596, -8.5208, 169.89, -38.9374, -9.16, 168.797, 8.0596, -9.16, 173.426, 8.0596, -9.16, 177.068, -39.6479, -9.16, 172.369, -38.9374, -9.16, 168.797, 8.0596, -9.16, 173.426, -39.6479, -9.16, 172.369, 8.0596, -9.16, 177.068, 8.0596, -8.5208, 180.604, -40.3378, -8.5208, 175.838, -39.6479, -9.16, 172.369, 8.0596, -9.16, 177.068, -40.3378, -8.5208, 175.838, 8.0596, -8.5208, 180.604, 8.0596, -7.2796, 183.829, -40.967, -7.2796, 179.001, -40.3378, -8.5208, 175.838, 8.0596, -8.5208, 180.604, -40.967, -7.2796, 179.001, 8.0596, -7.2796, 183.829, 8.0596, -5.5084, 186.556, -41.4988, -5.5084, 181.675, -40.967, -7.2796, 179.001, 8.0596, -7.2796, 183.829, -41.4988, -5.5084, 181.675, 8.0596, -5.5084, 186.556, 8.0596, -3.3103, 188.625, -41.9025, -3.3103, 183.704, -41.4988, -5.5084, 181.675, 8.0596, -5.5084, 186.556, -41.9025, -3.3103, 183.704, 8.0596, -3.3103, 188.625, 8.0596, -0.813, 189.916, -42.1544, -0.813, 184.97, -41.9025, -3.3103, 183.704, 8.0596, -3.3103, 188.625, -42.1544, -0.813, 184.97, 8.0596, -0.813, 189.916, 8.0596, 1.8384, 190.355, -42.2401, 1.8384, 185.401, -42.1544, -0.813, 184.97, 8.0596, -0.813, 189.916, -42.2401, 1.8384, 185.401, -42.2401, 1.8384, 185.401, -42.1544, 4.4899, 184.97, -90.4387, 4.4899, 170.324, -90.6067, 1.8384, 170.729, -42.2401, 1.8384, 185.401, -90.4387, 4.4899, 170.324, -42.1544, 4.4899, 184.97, -41.9025, 6.9872, 183.704, -89.9445, 6.9872, 169.13, -90.4387, 4.4899, 170.324, -42.1544, 4.4899, 184.97, -89.9445, 6.9872, 169.13, -41.9025, 6.9872, 183.704, -41.4988, 9.1853, 181.675, -89.1527, 9.1853, 167.219, -89.9445, 6.9872, 169.13, -41.9025, 6.9872, 183.704, -89.1527, 9.1853, 167.219, -41.4988, 9.1853, 181.675, -40.967, 10.9564, 179.001, -88.1095, 10.9564, 164.7, -89.1527, 9.1853, 167.219, -41.4988, 9.1853, 181.675, -88.1095, 10.9564, 164.7, -40.967, 10.9564, 179.001, -40.3378, 12.1977, 175.838, -86.8753, 12.1977, 161.721, -88.1095, 10.9564, 164.7, -40.967, 10.9564, 179.001, -86.8753, 12.1977, 161.721, -40.3378, 12.1977, 175.838, -39.6479, 12.8369, 172.369, -85.522, 12.8369, 158.454, -86.8753, 12.1977, 161.721, -40.3378, 12.1977, 175.838, -85.522, 12.8369, 158.454, -39.6479, 12.8369, 172.369, -38.9374, 12.8369, 168.797, -84.1283, 12.8369, 155.089, -85.522, 12.8369, 158.454, -39.6479, 12.8369, 172.369, -84.1283, 12.8369, 155.089, -38.9374, 12.8369, 168.797, -38.2475, 12.1977, 165.329, -82.775, 12.1977, 151.822, -84.1283, 12.8369, 155.089, -38.9374, 12.8369, 168.797, -82.775, 12.1977, 151.822, -38.2475, 12.1977, 165.329, -37.6183, 10.9564, 162.166, -81.5408, 10.9564, 148.842, -82.775, 12.1977, 151.822, -38.2475, 12.1977, 165.329, -81.5408, 10.9564, 148.842, -37.6183, 10.9564, 162.166, -37.0865, 9.1853, 159.492, -80.4976, 9.1853, 146.324, -81.5408, 10.9564, 148.842, -37.6183, 10.9564, 162.166, -80.4976, 9.1853, 146.324, -37.0865, 9.1853, 159.492, -36.6828, 6.9872, 157.463, -79.7058, 6.9872, 144.412, -80.4976, 9.1853, 146.324, -37.0865, 9.1853, 159.492, -79.7058, 6.9872, 144.412, -36.6828, 6.9872, 157.463, -36.4309, 4.4899, 156.196, -79.2116, 4.4899, 143.219, -79.7058, 6.9872, 144.412, -36.6828, 6.9872, 157.463, -79.2116, 4.4899, 143.219, -36.4309, 4.4899, 156.196, -36.3452, 1.8384, 155.766, -79.0436, 1.8384, 142.813, -79.2116, 4.4899, 143.219, -36.4309, 4.4899, 156.196, -79.0436, 1.8384, 142.813, -36.3452, 1.8384, 155.766, -36.4309, -0.813, 156.196, -79.2116, -0.813, 143.219, -79.0436, 1.8384, 142.813, -36.3452, 1.8384, 155.766, -79.2116, -0.813, 143.219, -36.4309, -0.813, 156.196, -36.6828, -3.3103, 157.463, -79.7058, -3.3103, 144.412, -79.2116, -0.813, 143.219, -36.4309, -0.813, 156.196, -79.7058, -3.3103, 144.412, -36.6828, -3.3103, 157.463, -37.0865, -5.5084, 159.492, -80.4976, -5.5084, 146.324, -79.7058, -3.3103, 144.412, -36.6828, -3.3103, 157.463, -80.4976, -5.5084, 146.324, -37.0865, -5.5084, 159.492, -37.6183, -7.2796, 162.166, -81.5408, -7.2796, 148.842, -80.4976, -5.5084, 146.324, -37.0865, -5.5084, 159.492, -81.5408, -7.2796, 148.842, -37.6183, -7.2796, 162.166, -38.2475, -8.5208, 165.329, -82.775, -8.5208, 151.822, -81.5408, -7.2796, 148.842, -37.6183, -7.2796, 162.166, -82.775, -8.5208, 151.822, -38.2475, -8.5208, 165.329, -38.9374, -9.16, 168.797, -84.1283, -9.16, 155.089, -82.775, -8.5208, 151.822, -38.2475, -8.5208, 165.329, -84.1283, -9.16, 155.089, -38.9374, -9.16, 168.797, -39.6479, -9.16, 172.369, -85.522, -9.16, 158.454, -84.1283, -9.16, 155.089, -38.9374, -9.16, 168.797, -85.522, -9.16, 158.454, -39.6479, -9.16, 172.369, -40.3378, -8.5208, 175.838, -86.8753, -8.5208, 161.721, -85.522, -9.16, 158.454, -39.6479, -9.16, 172.369, -86.8753, -8.5208, 161.721, -40.3378, -8.5208, 175.838, -40.967, -7.2796, 179.001, -88.1095, -7.2796, 164.7, -86.8753, -8.5208, 161.721, -40.3378, -8.5208, 175.838, -88.1095, -7.2796, 164.7, -40.967, -7.2796, 179.001, -41.4988, -5.5084, 181.675, -89.1527, -5.5084, 167.219, -88.1095, -7.2796, 164.7, -40.967, -7.2796, 179.001, -89.1527, -5.5084, 167.219, -41.4988, -5.5084, 181.675, -41.9025, -3.3103, 183.704, -89.9445, -3.3103, 169.13, -89.1527, -5.5084, 167.219, -41.4988, -5.5084, 181.675, -89.9445, -3.3103, 169.13, -41.9025, -3.3103, 183.704, -42.1544, -0.813, 184.97, -90.4387, -0.813, 170.324, -89.9445, -3.3103, 169.13, -41.9025, -3.3103, 183.704, -90.4387, -0.813, 170.324, -42.1544, -0.813, 184.97, -42.2401, 1.8384, 185.401, -90.6067, 1.8384, 170.729, -90.4387, -0.813, 170.324, -42.1544, -0.813, 184.97, -90.6067, 1.8384, 170.729, -90.6067, 1.8384, 170.729, -90.4387, 4.4899, 170.324, -134.938, 4.4899, 146.538, -135.182, 1.8384, 146.903, -90.6067, 1.8384, 170.729, -134.938, 4.4899, 146.538, -90.4387, 4.4899, 170.324, -89.9445, 6.9872, 169.13, -134.22, 6.9872, 145.464, -134.938, 4.4899, 146.538, -90.4387, 4.4899, 170.324, -134.22, 6.9872, 145.464, -89.9445, 6.9872, 169.13, -89.1527, 9.1853, 167.219, -133.071, 9.1853, 143.744, -134.22, 6.9872, 145.464, -89.9445, 6.9872, 169.13, -133.071, 9.1853, 143.744, -89.1527, 9.1853, 167.219, -88.1095, 10.9564, 164.7, -131.556, 10.9564, 141.477, -133.071, 9.1853, 143.744, -89.1527, 9.1853, 167.219, -131.556, 10.9564, 141.477, -88.1095, 10.9564, 164.7, -86.8753, 12.1977, 161.721, -129.764, 12.1977, 138.796, -131.556, 10.9564, 141.477, -88.1095, 10.9564, 164.7, -129.764, 12.1977, 138.796, -86.8753, 12.1977, 161.721, -85.522, 12.8369, 158.454, -127.8, 12.8369, 135.856, -129.764, 12.1977, 138.796, -86.8753, 12.1977, 161.721, -127.8, 12.8369, 135.856, -85.522, 12.8369, 158.454, -84.1283, 12.8369, 155.089, -125.776, 12.8369, 132.827, -127.8, 12.8369, 135.856, -85.522, 12.8369, 158.454, -125.776, 12.8369, 132.827, -84.1283, 12.8369, 155.089, -82.775, 12.1977, 151.822, -123.812, 12.1977, 129.887, -125.776, 12.8369, 132.827, -84.1283, 12.8369, 155.089, -123.812, 12.1977, 129.887, -82.775, 12.1977, 151.822, -81.5408, 10.9564, 148.842, -122.02, 10.9564, 127.206, -123.812, 12.1977, 129.887, -82.775, 12.1977, 151.822, -122.02, 10.9564, 127.206, -81.5408, 10.9564, 148.842, -80.4976, 9.1853, 146.324, -120.505, 9.1853, 124.939, -122.02, 10.9564, 127.206, -81.5408, 10.9564, 148.842, -120.505, 9.1853, 124.939, -80.4976, 9.1853, 146.324, -79.7058, 6.9872, 144.412, -119.356, 6.9872, 123.219, -120.505, 9.1853, 124.939, -80.4976, 9.1853, 146.324, -119.356, 6.9872, 123.219, -79.7058, 6.9872, 144.412, -79.2116, 4.4899, 143.219, -118.638, 4.4899, 122.145, -119.356, 6.9872, 123.219, -79.7058, 6.9872, 144.412, -118.638, 4.4899, 122.145, -79.2116, 4.4899, 143.219, -79.0436, 1.8384, 142.813, -118.395, 1.8384, 121.78, -118.638, 4.4899, 122.145, -79.2116, 4.4899, 143.219, -118.395, 1.8384, 121.78, -79.0436, 1.8384, 142.813, -79.2116, -0.813, 143.219, -118.638, -0.813, 122.145, -118.395, 1.8384, 121.78, -79.0436, 1.8384, 142.813, -118.638, -0.813, 122.145, -79.2116, -0.813, 143.219, -79.7058, -3.3103, 144.412, -119.356, -3.3103, 123.219, -118.638, -0.813, 122.145, -79.2116, -0.813, 143.219, -119.356, -3.3103, 123.219, -79.7058, -3.3103, 144.412, -80.4976, -5.5084, 146.324, -120.505, -5.5084, 124.939, -119.356, -3.3103, 123.219, -79.7058, -3.3103, 144.412, -120.505, -5.5084, 124.939, -80.4976, -5.5084, 146.324, -81.5408, -7.2796, 148.842, -122.02, -7.2796, 127.206, -120.505, -5.5084, 124.939, -80.4976, -5.5084, 146.324, -122.02, -7.2796, 127.206, -81.5408, -7.2796, 148.842, -82.775, -8.5208, 151.822, -123.812, -8.5208, 129.887, -122.02, -7.2796, 127.206, -81.5408, -7.2796, 148.842, -123.812, -8.5208, 129.887, -82.775, -8.5208, 151.822, -84.1283, -9.16, 155.089, -125.776, -9.16, 132.827, -123.812, -8.5208, 129.887, -82.775, -8.5208, 151.822, -125.776, -9.16, 132.827, -84.1283, -9.16, 155.089, -85.522, -9.16, 158.454, -127.8, -9.16, 135.856, -125.776, -9.16, 132.827, -84.1283, -9.16, 155.089, -127.8, -9.16, 135.856, -85.522, -9.16, 158.454, -86.8753, -8.5208, 161.721, -129.764, -8.5208, 138.796, -127.8, -9.16, 135.856, -85.522, -9.16, 158.454, -129.764, -8.5208, 138.796, -86.8753, -8.5208, 161.721, -88.1095, -7.2796, 164.7, -131.556, -7.2796, 141.477, -129.764, -8.5208, 138.796, -86.8753, -8.5208, 161.721, -131.556, -7.2796, 141.477, -88.1095, -7.2796, 164.7, -89.1527, -5.5084, 167.219, -133.071, -5.5084, 143.744, -131.556, -7.2796, 141.477, -88.1095, -7.2796, 164.7, -133.071, -5.5084, 143.744, -89.1527, -5.5084, 167.219, -89.9445, -3.3103, 169.13, -134.22, -3.3103, 145.464, -133.071, -5.5084, 143.744, -89.1527, -5.5084, 167.219, -134.22, -3.3103, 145.464, -89.9445, -3.3103, 169.13, -90.4387, -0.813, 170.324, -134.938, -0.813, 146.538, -134.22, -3.3103, 145.464, -89.9445, -3.3103, 169.13, -134.938, -0.813, 146.538, -90.4387, -0.813, 170.324, -90.6067, 1.8384, 170.729, -135.182, 1.8384, 146.903, -134.938, -0.813, 146.538, -90.4387, -0.813, 170.324, -135.182, 1.8384, 146.903, -135.182, 1.8384, 146.903, -134.938, 4.4899, 146.538, -173.942, 4.4899, 114.529, -174.252, 1.8384, 114.839, -135.182, 1.8384, 146.903, -173.942, 4.4899, 114.529, -134.938, 4.4899, 146.538, -134.22, 6.9872, 145.464, -173.028, 6.9872, 113.616, -173.942, 4.4899, 114.529, -134.938, 4.4899, 146.538, -173.028, 6.9872, 113.616, -134.22, 6.9872, 145.464, -133.071, 9.1853, 143.744, -171.565, 9.1853, 112.153, -173.028, 6.9872, 113.616, -134.22, 6.9872, 145.464, -171.565, 9.1853, 112.153, -133.071, 9.1853, 143.744, -131.556, 10.9564, 141.477, -169.638, 10.9564, 110.225, -171.565, 9.1853, 112.153, -133.071, 9.1853, 143.744, -169.638, 10.9564, 110.225, -131.556, 10.9564, 141.477, -129.764, 12.1977, 138.796, -167.357, 12.1977, 107.944, -169.638, 10.9564, 110.225, -131.556, 10.9564, 141.477, -167.357, 12.1977, 107.944, -129.764, 12.1977, 138.796, -127.8, 12.8369, 135.856, -164.857, 12.8369, 105.444, -167.357, 12.1977, 107.944, -129.764, 12.1977, 138.796, -164.857, 12.8369, 105.444, -127.8, 12.8369, 135.856, -125.776, 12.8369, 132.827, -162.281, 12.8369, 102.869, -164.857, 12.8369, 105.444, -127.8, 12.8369, 135.856, -162.281, 12.8369, 102.869, -125.776, 12.8369, 132.827, -123.812, 12.1977, 129.887, -159.781, 12.1977, 100.368, -162.281, 12.8369, 102.869, -125.776, 12.8369, 132.827, -159.781, 12.1977, 100.368, -123.812, 12.1977, 129.887, -122.02, 10.9564, 127.206, -157.5, 10.9564, 98.0877, -159.781, 12.1977, 100.368, -123.812, 12.1977, 129.887, -157.5, 10.9564, 98.0877, -122.02, 10.9564, 127.206, -120.505, 9.1853, 124.939, -155.573, 9.1853, 96.1599, -157.5, 10.9564, 98.0877, -122.02, 10.9564, 127.206, -155.573, 9.1853, 96.1599, -120.505, 9.1853, 124.939, -119.356, 6.9872, 123.219, -154.11, 6.9872, 94.697, -155.573, 9.1853, 96.1599, -120.505, 9.1853, 124.939, -154.11, 6.9872, 94.697, -119.356, 6.9872, 123.219, -118.638, 4.4899, 122.145, -153.197, 4.4899, 93.7837, -154.11, 6.9872, 94.697, -119.356, 6.9872, 123.219, -153.197, 4.4899, 93.7837, -118.638, 4.4899, 122.145, -118.395, 1.8384, 121.78, -152.886, 1.8384, 93.4733, -153.197, 4.4899, 93.7837, -118.638, 4.4899, 122.145, -152.886, 1.8384, 93.4733, -118.395, 1.8384, 121.78, -118.638, -0.813, 122.145, -153.197, -0.813, 93.7837, -152.886, 1.8384, 93.4733, -118.395, 1.8384, 121.78, -153.197, -0.813, 93.7837, -118.638, -0.813, 122.145, -119.356, -3.3103, 123.219, -154.11, -3.3103, 94.697, -153.197, -0.813, 93.7837, -118.638, -0.813, 122.145, -154.11, -3.3103, 94.697, -119.356, -3.3103, 123.219, -120.505, -5.5084, 124.939, -155.573, -5.5084, 96.1599, -154.11, -3.3103, 94.697, -119.356, -3.3103, 123.219, -155.573, -5.5084, 96.1599, -120.505, -5.5084, 124.939, -122.02, -7.2796, 127.206, -157.5, -7.2796, 98.0877, -155.573, -5.5084, 96.1599, -120.505, -5.5084, 124.939, -157.5, -7.2796, 98.0877, -122.02, -7.2796, 127.206, -123.812, -8.5208, 129.887, -159.781, -8.5208, 100.368, -157.5, -7.2796, 98.0877, -122.02, -7.2796, 127.206, -159.781, -8.5208, 100.368, -123.812, -8.5208, 129.887, -125.776, -9.16, 132.827, -162.281, -9.16, 102.869, -159.781, -8.5208, 100.368, -123.812, -8.5208, 129.887, -162.281, -9.16, 102.869, -125.776, -9.16, 132.827, -127.8, -9.16, 135.856, -164.857, -9.16, 105.444, -162.281, -9.16, 102.869, -125.776, -9.16, 132.827, -164.857, -9.16, 105.444, -127.8, -9.16, 135.856, -129.764, -8.5208, 138.796, -167.357, -8.5208, 107.944, -164.857, -9.16, 105.444, -127.8, -9.16, 135.856, -167.357, -8.5208, 107.944, -129.764, -8.5208, 138.796, -131.556, -7.2796, 141.477, -169.638, -7.2796, 110.225, -167.357, -8.5208, 107.944, -129.764, -8.5208, 138.796, -169.638, -7.2796, 110.225, -131.556, -7.2796, 141.477, -133.071, -5.5084, 143.744, -171.565, -5.5084, 112.153, -169.638, -7.2796, 110.225, -131.556, -7.2796, 141.477, -171.565, -5.5084, 112.153, -133.071, -5.5084, 143.744, -134.22, -3.3103, 145.464, -173.028, -3.3103, 113.616, -171.565, -5.5084, 112.153, -133.071, -5.5084, 143.744, -173.028, -3.3103, 113.616, -134.22, -3.3103, 145.464, -134.938, -0.813, 146.538, -173.942, -0.813, 114.529, -173.028, -3.3103, 113.616, -134.22, -3.3103, 145.464, -173.942, -0.813, 114.529, -134.938, -0.813, 146.538, -135.182, 1.8384, 146.903, -174.252, 1.8384, 114.839, -173.942, -0.813, 114.529, -134.938, -0.813, 146.538, -174.252, 1.8384, 114.839, -174.252, 1.8384, 114.839, -173.942, 4.4899, 114.529, -205.951, 4.4899, 75.525, -206.316, 1.8384, 75.7689, -174.252, 1.8384, 114.839, -205.951, 4.4899, 75.525, -173.942, 4.4899, 114.529, -173.028, 6.9872, 113.616, -204.877, 6.9872, 74.8075, -205.951, 4.4899, 75.525, -173.942, 4.4899, 114.529, -204.877, 6.9872, 74.8075, -173.028, 6.9872, 113.616, -171.565, 9.1853, 112.153, -203.157, 9.1853, 73.6581, -204.877, 6.9872, 74.8075, -173.028, 6.9872, 113.616, -203.157, 9.1853, 73.6581, -171.565, 9.1853, 112.153, -169.638, 10.9564, 110.225, -200.89, 10.9564, 72.1435, -203.157, 9.1853, 73.6581, -171.565, 9.1853, 112.153, -200.89, 10.9564, 72.1435, -169.638, 10.9564, 110.225, -167.357, 12.1977, 107.944, -198.209, 12.1977, 70.3518, -200.89, 10.9564, 72.1435, -169.638, 10.9564, 110.225, -198.209, 12.1977, 70.3518, -167.357, 12.1977, 107.944, -164.857, 12.8369, 105.444, -195.268, 12.8369, 68.3871, -198.209, 12.1977, 70.3518, -167.357, 12.1977, 107.944, -195.268, 12.8369, 68.3871, -164.857, 12.8369, 105.444, -162.281, 12.8369, 102.869, -192.24, 12.8369, 66.3636, -195.268, 12.8369, 68.3871, -164.857, 12.8369, 105.444, -192.24, 12.8369, 66.3636, -162.281, 12.8369, 102.869, -159.781, 12.1977, 100.368, -189.3, 12.1977, 64.399, -192.24, 12.8369, 66.3636, -162.281, 12.8369, 102.869, -189.3, 12.1977, 64.399, -159.781, 12.1977, 100.368, -157.5, 10.9564, 98.0877, -186.618, 10.9564, 62.6073, -189.3, 12.1977, 64.399, -159.781, 12.1977, 100.368, -186.618, 10.9564, 62.6073, -157.5, 10.9564, 98.0877, -155.573, 9.1853, 96.1599, -184.352, 9.1853, 61.0927, -186.618, 10.9564, 62.6073, -157.5, 10.9564, 98.0877, -184.352, 9.1853, 61.0927, -155.573, 9.1853, 96.1599, -154.11, 6.9872, 94.697, -182.631, 6.9872, 59.9433, -184.352, 9.1853, 61.0927, -155.573, 9.1853, 96.1599, -182.631, 6.9872, 59.9433, -154.11, 6.9872, 94.697, -153.197, 4.4899, 93.7837, -181.557, 4.4899, 59.2257, -182.631, 6.9872, 59.9433, -154.11, 6.9872, 94.697, -181.557, 4.4899, 59.2257, -153.197, 4.4899, 93.7837, -152.886, 1.8384, 93.4733, -181.192, 1.8384, 58.9818, -181.557, 4.4899, 59.2257, -153.197, 4.4899, 93.7837, -181.192, 1.8384, 58.9818, -152.886, 1.8384, 93.4733, -153.197, -0.813, 93.7837, -181.557, -0.813, 59.2257, -181.192, 1.8384, 58.9818, -152.886, 1.8384, 93.4733, -181.557, -0.813, 59.2257, -153.197, -0.813, 93.7837, -154.11, -3.3103, 94.697, -182.631, -3.3103, 59.9433, -181.557, -0.813, 59.2257, -153.197, -0.813, 93.7837, -182.631, -3.3103, 59.9433, -154.11, -3.3103, 94.697, -155.573, -5.5084, 96.1599, -184.352, -5.5084, 61.0927, -182.631, -3.3103, 59.9433, -154.11, -3.3103, 94.697, -184.352, -5.5084, 61.0927, -155.573, -5.5084, 96.1599, -157.5, -7.2796, 98.0877, -186.618, -7.2796, 62.6073, -184.352, -5.5084, 61.0927, -155.573, -5.5084, 96.1599, -186.618, -7.2796, 62.6073, -157.5, -7.2796, 98.0877, -159.781, -8.5208, 100.368, -189.3, -8.5208, 64.399, -186.618, -7.2796, 62.6073, -157.5, -7.2796, 98.0877, -189.3, -8.5208, 64.399, -159.781, -8.5208, 100.368, -162.281, -9.16, 102.869, -192.24, -9.16, 66.3636, -189.3, -8.5208, 64.399, -159.781, -8.5208, 100.368, -192.24, -9.16, 66.3636, -162.281, -9.16, 102.869, -164.857, -9.16, 105.444, -195.268, -9.16, 68.3871, -192.24, -9.16, 66.3636, -162.281, -9.16, 102.869, -195.268, -9.16, 68.3871, -164.857, -9.16, 105.444, -167.357, -8.5208, 107.944, -198.209, -8.5208, 70.3518, -195.268, -9.16, 68.3871, -164.857, -9.16, 105.444, -198.209, -8.5208, 70.3518, -167.357, -8.5208, 107.944, -169.638, -7.2796, 110.225, -200.89, -7.2796, 72.1435, -198.209, -8.5208, 70.3518, -167.357, -8.5208, 107.944, -200.89, -7.2796, 72.1435, -169.638, -7.2796, 110.225, -171.565, -5.5084, 112.153, -203.157, -5.5084, 73.6581, -200.89, -7.2796, 72.1435, -169.638, -7.2796, 110.225, -203.157, -5.5084, 73.6581, -171.565, -5.5084, 112.153, -173.028, -3.3103, 113.616, -204.877, -3.3103, 74.8075, -203.157, -5.5084, 73.6581, -171.565, -5.5084, 112.153, -204.877, -3.3103, 74.8075, -173.028, -3.3103, 113.616, -173.942, -0.813, 114.529, -205.951, -0.813, 75.525, -204.877, -3.3103, 74.8075, -173.028, -3.3103, 113.616, -205.951, -0.813, 75.525, -173.942, -0.813, 114.529, -174.252, 1.8384, 114.839, -206.316, 1.8384, 75.7689, -205.951, -0.813, 75.525, -173.942, -0.813, 114.529, -206.316, 1.8384, 75.7689, -206.316, 1.8384, 75.7689, -205.951, 4.4899, 75.525, -229.736, 4.4899, 31.026, -230.142, 1.8384, 31.194, -206.316, 1.8384, 75.7689, -229.736, 4.4899, 31.026, -205.951, 4.4899, 75.525, -204.877, 6.9872, 74.8075, -228.543, 6.9872, 30.5317, -229.736, 4.4899, 31.026, -205.951, 4.4899, 75.525, -228.543, 6.9872, 30.5317, -204.877, 6.9872, 74.8075, -203.157, 9.1853, 73.6581, -226.632, 9.1853, 29.74, -228.543, 6.9872, 30.5317, -204.877, 6.9872, 74.8075, -226.632, 9.1853, 29.74, -203.157, 9.1853, 73.6581, -200.89, 10.9564, 72.1435, -224.113, 10.9564, 28.6967, -226.632, 9.1853, 29.74, -203.157, 9.1853, 73.6581, -224.113, 10.9564, 28.6967, -200.89, 10.9564, 72.1435, -198.209, 12.1977, 70.3518, -221.134, 12.1977, 27.4626, -224.113, 10.9564, 28.6967, -200.89, 10.9564, 72.1435, -221.134, 12.1977, 27.4626, -198.209, 12.1977, 70.3518, -195.268, 12.8369, 68.3871, -217.866, 12.8369, 26.1093, -221.134, 12.1977, 27.4626, -198.209, 12.1977, 70.3518, -217.866, 12.8369, 26.1093, -195.268, 12.8369, 68.3871, -192.24, 12.8369, 66.3636, -214.501, 12.8369, 24.7155, -217.866, 12.8369, 26.1093, -195.268, 12.8369, 68.3871, -214.501, 12.8369, 24.7155, -192.24, 12.8369, 66.3636, -189.3, 12.1977, 64.399, -211.234, 12.1977, 23.3622, -214.501, 12.8369, 24.7155, -192.24, 12.8369, 66.3636, -211.234, 12.1977, 23.3622, -189.3, 12.1977, 64.399, -186.618, 10.9564, 62.6073, -208.255, 10.9564, 22.1281, -211.234, 12.1977, 23.3622, -189.3, 12.1977, 64.399, -208.255, 10.9564, 22.1281, -186.618, 10.9564, 62.6073, -184.352, 9.1853, 61.0927, -205.736, 9.1853, 21.0848, -208.255, 10.9564, 22.1281, -186.618, 10.9564, 62.6073, -205.736, 9.1853, 21.0848, -184.352, 9.1853, 61.0927, -182.631, 6.9872, 59.9433, -203.825, 6.9872, 20.2931, -205.736, 9.1853, 21.0848, -184.352, 9.1853, 61.0927, -203.825, 6.9872, 20.2931, -182.631, 6.9872, 59.9433, -181.557, 4.4899, 59.2257, -202.632, 4.4899, 19.7988, -203.825, 6.9872, 20.2931, -182.631, 6.9872, 59.9433, -202.632, 4.4899, 19.7988, -181.557, 4.4899, 59.2257, -181.192, 1.8384, 58.9818, -202.226, 1.8384, 19.6308, -202.632, 4.4899, 19.7988, -181.557, 4.4899, 59.2257, -202.226, 1.8384, 19.6308, -181.192, 1.8384, 58.9818, -181.557, -0.813, 59.2257, -202.632, -0.813, 19.7988, -202.226, 1.8384, 19.6308, -181.192, 1.8384, 58.9818, -202.632, -0.813, 19.7988, -181.557, -0.813, 59.2257, -182.631, -3.3103, 59.9433, -203.825, -3.3103, 20.2931, -202.632, -0.813, 19.7988, -181.557, -0.813, 59.2257, -203.825, -3.3103, 20.2931, -182.631, -3.3103, 59.9433, -184.352, -5.5084, 61.0927, -205.736, -5.5084, 21.0848, -203.825, -3.3103, 20.2931, -182.631, -3.3103, 59.9433, -205.736, -5.5084, 21.0848, -184.352, -5.5084, 61.0927, -186.618, -7.2796, 62.6073, -208.255, -7.2796, 22.1281, -205.736, -5.5084, 21.0848, -184.352, -5.5084, 61.0927, -208.255, -7.2796, 22.1281, -186.618, -7.2796, 62.6073, -189.3, -8.5208, 64.399, -211.234, -8.5208, 23.3622, -208.255, -7.2796, 22.1281, -186.618, -7.2796, 62.6073, -211.234, -8.5208, 23.3622, -189.3, -8.5208, 64.399, -192.24, -9.16, 66.3636, -214.501, -9.16, 24.7155, -211.234, -8.5208, 23.3622, -189.3, -8.5208, 64.399, -214.501, -9.16, 24.7155, -192.24, -9.16, 66.3636, -195.268, -9.16, 68.3871, -217.866, -9.16, 26.1093, -214.501, -9.16, 24.7155, -192.24, -9.16, 66.3636, -217.866, -9.16, 26.1093, -195.268, -9.16, 68.3871, -198.209, -8.5208, 70.3518, -221.134, -8.5208, 27.4626, -217.866, -9.16, 26.1093, -195.268, -9.16, 68.3871, -221.134, -8.5208, 27.4626, -198.209, -8.5208, 70.3518, -200.89, -7.2796, 72.1435, -224.113, -7.2796, 28.6967, -221.134, -8.5208, 27.4626, -198.209, -8.5208, 70.3518, -224.113, -7.2796, 28.6967, -200.89, -7.2796, 72.1435, -203.157, -5.5084, 73.6581, -226.632, -5.5084, 29.74, -224.113, -7.2796, 28.6967, -200.89, -7.2796, 72.1435, -226.632, -5.5084, 29.74, -203.157, -5.5084, 73.6581, -204.877, -3.3103, 74.8075, -228.543, -3.3103, 30.5317, -226.632, -5.5084, 29.74, -203.157, -5.5084, 73.6581, -228.543, -3.3103, 30.5317, -204.877, -3.3103, 74.8075, -205.951, -0.813, 75.525, -229.736, -0.813, 31.026, -228.543, -3.3103, 30.5317, -204.877, -3.3103, 74.8075, -229.736, -0.813, 31.026, -205.951, -0.813, 75.525, -206.316, 1.8384, 75.7689, -230.142, 1.8384, 31.194, -229.736, -0.813, 31.026, -205.951, -0.813, 75.525, -230.142, 1.8384, 31.194, -230.142, 1.8384, 31.194, -229.736, 4.4899, 31.026, -244.383, 4.4899, -17.2583, -244.814, 1.8384, -17.1727, -230.142, 1.8384, 31.194, -244.383, 4.4899, -17.2583, -229.736, 4.4899, 31.026, -228.543, 6.9872, 30.5317, -243.117, 6.9872, -17.5103, -244.383, 4.4899, -17.2583, -229.736, 4.4899, 31.026, -243.117, 6.9872, -17.5103, -228.543, 6.9872, 30.5317, -226.632, 9.1853, 29.74, -241.087, 9.1853, -17.9139, -243.117, 6.9872, -17.5103, -228.543, 6.9872, 30.5317, -241.087, 9.1853, -17.9139, -226.632, 9.1853, 29.74, -224.113, 10.9564, 28.6967, -238.413, 10.9564, -18.4458, -241.087, 9.1853, -17.9139, -226.632, 9.1853, 29.74, -238.413, 10.9564, -18.4458, -224.113, 10.9564, 28.6967, -221.134, 12.1977, 27.4626, -235.251, 12.1977, -19.0749, -238.413, 10.9564, -18.4458, -224.113, 10.9564, 28.6967, -235.251, 12.1977, -19.0749, -221.134, 12.1977, 27.4626, -217.866, 12.8369, 26.1093, -231.782, 12.8369, -19.7648, -235.251, 12.1977, -19.0749, -221.134, 12.1977, 27.4626, -231.782, 12.8369, -19.7648, -217.866, 12.8369, 26.1093, -214.501, 12.8369, 24.7155, -228.21, 12.8369, -20.4754, -231.782, 12.8369, -19.7648, -217.866, 12.8369, 26.1093, -228.21, 12.8369, -20.4754, -214.501, 12.8369, 24.7155, -211.234, 12.1977, 23.3622, -224.742, 12.1977, -21.1653, -228.21, 12.8369, -20.4754, -214.501, 12.8369, 24.7155, -224.742, 12.1977, -21.1653, -211.234, 12.1977, 23.3622, -208.255, 10.9564, 22.1281, -221.579, 10.9564, -21.7944, -224.742, 12.1977, -21.1653, -211.234, 12.1977, 23.3622, -221.579, 10.9564, -21.7944, -208.255, 10.9564, 22.1281, -205.736, 9.1853, 21.0848, -218.905, 9.1853, -22.3263, -221.579, 10.9564, -21.7944, -208.255, 10.9564, 22.1281, -218.905, 9.1853, -22.3263, -205.736, 9.1853, 21.0848, -203.825, 6.9872, 20.2931, -216.876, 6.9872, -22.7299, -218.905, 9.1853, -22.3263, -205.736, 9.1853, 21.0848, -216.876, 6.9872, -22.7299, -203.825, 6.9872, 20.2931, -202.632, 4.4899, 19.7988, -215.609, 4.4899, -22.9819, -216.876, 6.9872, -22.7299, -203.825, 6.9872, 20.2931, -215.609, 4.4899, -22.9819, -202.632, 4.4899, 19.7988, -202.226, 1.8384, 19.6308, -215.178, 1.8384, -23.0675, -215.609, 4.4899, -22.9819, -202.632, 4.4899, 19.7988, -215.178, 1.8384, -23.0675, -202.226, 1.8384, 19.6308, -202.632, -0.813, 19.7988, -215.609, -0.813, -22.9819, -215.178, 1.8384, -23.0675, -202.226, 1.8384, 19.6308, -215.609, -0.813, -22.9819, -202.632, -0.813, 19.7988, -203.825, -3.3103, 20.2931, -216.876, -3.3103, -22.7299, -215.609, -0.813, -22.9819, -202.632, -0.813, 19.7988, -216.876, -3.3103, -22.7299, -203.825, -3.3103, 20.2931, -205.736, -5.5084, 21.0848, -218.905, -5.5084, -22.3263, -216.876, -3.3103, -22.7299, -203.825, -3.3103, 20.2931, -218.905, -5.5084, -22.3263, -205.736, -5.5084, 21.0848, -208.255, -7.2796, 22.1281, -221.579, -7.2796, -21.7944, -218.905, -5.5084, -22.3263, -205.736, -5.5084, 21.0848, -221.579, -7.2796, -21.7944, -208.255, -7.2796, 22.1281, -211.234, -8.5208, 23.3622, -224.742, -8.5208, -21.1653, -221.579, -7.2796, -21.7944, -208.255, -7.2796, 22.1281, -224.742, -8.5208, -21.1653, -211.234, -8.5208, 23.3622, -214.501, -9.16, 24.7155, -228.21, -9.16, -20.4754, -224.742, -8.5208, -21.1653, -211.234, -8.5208, 23.3622, -228.21, -9.16, -20.4754, -214.501, -9.16, 24.7155, -217.866, -9.16, 26.1093, -231.782, -9.16, -19.7648, -228.21, -9.16, -20.4754, -214.501, -9.16, 24.7155, -231.782, -9.16, -19.7648, -217.866, -9.16, 26.1093, -221.134, -8.5208, 27.4626, -235.251, -8.5208, -19.0749, -231.782, -9.16, -19.7648, -217.866, -9.16, 26.1093, -235.251, -8.5208, -19.0749, -221.134, -8.5208, 27.4626, -224.113, -7.2796, 28.6967, -238.413, -7.2796, -18.4458, -235.251, -8.5208, -19.0749, -221.134, -8.5208, 27.4626, -238.413, -7.2796, -18.4458, -224.113, -7.2796, 28.6967, -226.632, -5.5084, 29.74, -241.087, -5.5084, -17.9139, -238.413, -7.2796, -18.4458, -224.113, -7.2796, 28.6967, -241.087, -5.5084, -17.9139, -226.632, -5.5084, 29.74, -228.543, -3.3103, 30.5317, -243.117, -3.3103, -17.5103, -241.087, -5.5084, -17.9139, -226.632, -5.5084, 29.74, -243.117, -3.3103, -17.5103, -228.543, -3.3103, 30.5317, -229.736, -0.813, 31.026, -244.383, -0.813, -17.2583, -243.117, -3.3103, -17.5103, -228.543, -3.3103, 30.5317, -244.383, -0.813, -17.2583, -229.736, -0.813, 31.026, -230.142, 1.8384, 31.194, -244.814, 1.8384, -17.1727, -244.383, -0.813, -17.2583, -229.736, -0.813, 31.026, -244.814, 1.8384, -17.1727, -247.775, 3.5774, -50.1623, -244.814, 1.8384, -17.1727, -247.623, 4.4899, -50.1529, -244.814, 1.8384, -17.1727, -244.383, 4.4899, -17.2583, -247.623, 4.4899, -50.1529, -248.065, 1.8384, -50.1802, -244.814, 1.8384, -17.1727, -247.775, 3.5774, -50.1623, -246.773, 6.1232, -50.1002, -244.383, 4.4899, -17.2583, -246.324, 6.9872, -50.0723, -244.383, 4.4899, -17.2583, -243.117, 6.9872, -17.5103, -246.324, 6.9872, -50.0723, -247.623, 4.4899, -50.1529, -244.383, 4.4899, -17.2583, -246.773, 6.1232, -50.1002, -244.97, 8.4161, -49.9884, -243.117, 6.9872, -17.5103, -244.242, 9.1853, -49.9432, -243.117, 6.9872, -17.5103, -241.087, 9.1853, -17.9139, -244.242, 9.1853, -49.9432, -246.324, 6.9872, -50.0723, -243.117, 6.9872, -17.5103, -244.97, 8.4161, -49.9884, -242.475, 10.3261, -49.8337, -241.087, 9.1853, -17.9139, -241.499, 10.9564, -49.7731, -241.087, 9.1853, -17.9139, -238.413, 10.9564, -18.4458, -241.499, 10.9564, -49.7731, -244.242, 9.1853, -49.9432, -241.087, 9.1853, -17.9139, -242.475, 10.3261, -49.8337, -239.434, 11.7463, -49.6451, -238.413, 10.9564, -18.4458, -238.254, 12.1977, -49.572, -238.413, 10.9564, -18.4458, -235.251, 12.1977, -19.0749, -238.254, 12.1977, -49.572, -241.499, 10.9564, -49.7731, -238.413, 10.9564, -18.4458, -239.434, 11.7463, -49.6451, -236.022, 12.5986, -49.4336, -235.251, 12.1977, -19.0749, -234.696, 12.8369, -49.3513, -235.251, 12.1977, -19.0749, -231.782, 12.8369, -19.7648, -234.696, 12.8369, -49.3513, -238.254, 12.1977, -49.572, -235.251, 12.1977, -19.0749, -236.022, 12.5986, -49.4336, -232.434, 12.8369, -49.2111, -231.782, 12.8369, -19.7648, -231.032, 12.8369, -49.1241, -231.782, 12.8369, -19.7648, -228.21, 12.8369, -20.4754, -231.032, 12.8369, -49.1241, -234.696, 12.8369, -49.3513, -231.782, 12.8369, -19.7648, -232.434, 12.8369, -49.2111, -228.873, 12.449, -48.9903, -228.21, 12.8369, -20.4754, -227.474, 12.1977, -48.9035, -228.21, 12.8369, -20.4754, -224.742, 12.1977, -21.1653, -227.474, 12.1977, -48.9035, -231.032, 12.8369, -49.1241, -228.21, 12.8369, -20.4754, -228.873, 12.449, -48.9903, -225.538, 11.4574, -48.7835, -224.742, 12.1977, -21.1653, -224.229, 10.9564, -48.7023, -224.742, 12.1977, -21.1653, -221.579, 10.9564, -21.7944, -224.229, 10.9564, -48.7023, -227.474, 12.1977, -48.9035, -224.742, 12.1977, -21.1653, -225.538, 11.4574, -48.7835, -222.619, 9.917, -48.6025, -221.579, 10.9564, -21.7944, -221.486, 9.1853, -48.5323, -221.579, 10.9564, -21.7944, -218.905, 9.1853, -22.3263, -221.486, 9.1853, -48.5323, -224.229, 10.9564, -48.7023, -221.579, 10.9564, -21.7944, -222.619, 9.917, -48.6025, -220.281, 7.913, -48.4576, -218.905, 9.1853, -22.3263, -219.404, 6.9872, -48.4032, -218.905, 9.1853, -22.3263, -216.876, 6.9872, -22.7299, -219.404, 6.9872, -48.4032, -221.486, 9.1853, -48.5323, -218.905, 9.1853, -22.3263, -220.281, 7.913, -48.4576, -218.66, 5.5568, -48.357, -216.876, 6.9872, -22.7299, -218.105, 4.4899, -48.3226, -216.876, 6.9872, -22.7299, -215.609, 4.4899, -22.9819, -218.105, 4.4899, -48.3226, -219.404, 6.9872, -48.4032, -216.876, 6.9872, -22.7299, -218.66, 5.5568, -48.357, -217.853, 2.9806, -48.307, -215.609, 4.4899, -22.9819, -217.663, 1.8384, -48.2952, -215.609, 4.4899, -22.9819, -215.178, 1.8384, -23.0675, -217.663, 1.8384, -48.2952, -218.105, 4.4899, -48.3226, -215.609, 4.4899, -22.9819, -217.853, 2.9806, -48.307, -217.914, 0.3311, -48.3108, -215.178, 1.8384, -23.0675, -218.105, -0.813, -48.3226, -215.178, 1.8384, -23.0675, -215.609, -0.813, -22.9819, -218.105, -0.813, -48.3226, -217.663, 1.8384, -48.2952, -215.178, 1.8384, -23.0675, -217.914, 0.3311, -48.3108, -218.846, -2.238, -48.3686, -215.609, -0.813, -22.9819, -219.404, -3.3103, -48.4032, -215.609, -0.813, -22.9819, -216.876, -3.3103, -22.7299, -219.404, -3.3103, -48.4032, -218.105, -0.813, -48.3226, -215.609, -0.813, -22.9819, -218.846, -2.238, -48.3686, -220.602, -4.5753, -48.4775, -216.876, -3.3103, -22.7299, -221.486, -5.5084, -48.5323, -216.876, -3.3103, -22.7299, -218.905, -5.5084, -22.3263, -221.486, -5.5084, -48.5323, -219.404, -3.3103, -48.4032, -216.876, -3.3103, -22.7299, -220.602, -4.5753, -48.4775, -223.084, -6.5404, -48.6314, -218.905, -5.5084, -22.3263, -224.229, -7.2796, -48.7023, -218.905, -5.5084, -22.3263, -221.579, -7.2796, -21.7944, -224.229, -7.2796, -48.7023, -221.486, -5.5084, -48.5323, -218.905, -5.5084, -22.3263, -223.084, -6.5404, -48.6314, -226.149, -8.014, -48.8214, -221.579, -7.2796, -21.7944, -227.474, -8.5208, -48.9035, -221.579, -7.2796, -21.7944, -224.742, -8.5208, -21.1653, -227.474, -8.5208, -48.9035, -224.229, -7.2796, -48.7023, -221.579, -7.2796, -21.7944, -226.149, -8.014, -48.8214, -229.615, -8.9055, -49.0363, -224.742, -8.5208, -21.1653, -231.032, -9.16, -49.1241, -224.742, -8.5208, -21.1653, -228.21, -9.16, -20.4754, -231.032, -9.16, -49.1241, -227.474, -8.5208, -48.9035, -224.742, -8.5208, -21.1653, -229.615, -8.9055, -49.0363, -233.276, -9.16, -49.2633, -228.21, -9.16, -20.4754, -234.696, -9.16, -49.3514, -228.21, -9.16, -20.4754, -231.782, -9.16, -19.7648, -234.696, -9.16, -49.3514, -231.032, -9.16, -49.1241, -228.21, -9.16, -20.4754, -233.276, -9.16, -49.2633, -236.913, -8.7617, -49.4888, -231.782, -9.16, -19.7648, -238.254, -8.5208, -49.572, -231.782, -9.16, -19.7648, -235.251, -8.5208, -19.0749, -238.254, -8.5208, -49.572, -234.696, -9.16, -49.3514, -231.782, -9.16, -19.7648, -236.913, -8.7617, -49.4888, -240.307, -7.7354, -49.6993, -235.251, -8.5208, -19.0749, -241.499, -7.2796, -49.7731, -235.251, -8.5208, -19.0749, -238.413, -7.2796, -18.4458, -241.499, -7.2796, -49.7731, -238.254, -8.5208, -49.572, -235.251, -8.5208, -19.0749, -240.307, -7.7354, -49.6993, -243.258, -6.1439, -49.8822, -238.413, -7.2796, -18.4458, -244.242, -5.5084, -49.9432, -238.413, -7.2796, -18.4458, -241.087, -5.5084, -17.9139, -244.242, -5.5084, -49.9432, -241.499, -7.2796, -49.7731, -238.413, -7.2796, -18.4458, -243.258, -6.1439, -49.8822, -245.591, -4.0841, -50.0268, -241.087, -5.5084, -17.9139, -246.324, -3.3103, -50.0723, -241.087, -5.5084, -17.9139, -243.117, -3.3103, -17.5103, -246.324, -3.3103, -50.0723, -244.242, -5.5084, -49.9432, -241.087, -5.5084, -17.9139, -245.591, -4.0841, -50.0268, -247.172, -1.6801, -50.1249, -243.117, -3.3103, -17.5103, -247.623, -0.813, -50.1528, -243.117, -3.3103, -17.5103, -244.383, -0.813, -17.2583, -247.623, -0.813, -50.1528, -246.324, -3.3103, -50.0723, -243.117, -3.3103, -17.5103, -247.172, -1.6801, -50.1249, -247.913, 0.9249, -50.1708, -244.383, -0.813, -17.2583, -248.065, 1.8384, -50.1802, -244.383, -0.813, -17.2583, -244.814, 1.8384, -17.1727, -248.065, 1.8384, -50.1802, -247.623, -0.813, -50.1528, -244.383, -0.813, -17.2583, -247.913, 0.9249, -50.1708, 252.823, -12.1253, -19.1234, 252.859, -12.1193, -19.1212, 267.292, 1.9054, -18.2263, 252.859, -12.1193, -19.1212, 257.538, -10.4722, -18.8311, 267.292, 1.9054, -18.2263, 257.538, -10.4722, -18.8311, 257.633, -10.4389, -18.8252, 267.292, 1.9054, -18.2263, 257.633, -10.4389, -18.8252, 261.546, -8.1137, -18.5826, 267.292, 1.9054, -18.2263, 261.546, -8.1137, -18.5826, 261.668, -8.0411, -18.575, 267.292, 1.9054, -18.2263, 261.668, -8.0411, -18.575, 264.614, -5.1782, -18.3923, 267.292, 1.9054, -18.2263, 264.614, -5.1782, -18.3923, 264.73, -5.0652, -18.3851, 267.292, 1.9054, -18.2263, 264.73, -5.0652, -18.3851, 266.56, -1.8284, -18.2717, 267.292, 1.9054, -18.2263, 267.263, 1.7469, -18.2281, 266.56, -1.8284, -18.2717, 266.642, -1.6842, -18.2666, 266.56, -1.8284, -18.2717, 267.263, 1.7469, -18.2281, 267.292, 1.9054, -18.2263, 236.638, 15.9302, -20.1269, 236.433, 15.8598, -20.1396, 241.88, 16.774, -19.8019, 242.014, 16.7956, -19.7936, 231.736, 14.2498, -20.4309, 247.505, 16.7956, -19.4531, 231.736, 14.2498, -20.4309, 242.014, 16.7956, -19.7936, 241.88, 16.774, -19.8019, 236.433, 15.8598, -20.1396, 231.736, 14.2498, -20.4309, 241.88, 16.774, -19.8019, 231.736, 14.2498, -20.4309, 231.497, 14.1113, -20.4457, 247.505, 16.7956, -19.4531, 247.551, 16.7956, -19.4503, 227.592, 11.8519, -20.6878, 250.357, 16.3438, -19.2763, 227.592, 11.8519, -20.6878, 247.551, 16.7956, -19.4503, 247.505, 16.7956, -19.4531, 231.497, 14.1113, -20.4457, 227.592, 11.8519, -20.6878, 247.505, 16.7956, -19.4531, 227.592, 11.8519, -20.6878, 227.367, 11.6395, -20.7018, 250.357, 16.3438, -19.2763, 224.447, 8.876, -20.8828, 252.892, 15.9187, -19.1192, 252.859, 15.9302, -19.1212, 224.447, 8.876, -20.8828, 252.859, 15.9302, -19.1212, 250.357, 16.3438, -19.2763, 227.367, 11.6395, -20.7018, 224.447, 8.876, -20.8828, 250.357, 16.3438, -19.2763, 257.633, 14.2498, -18.8252, 250.357, 12.3842, -19.2763, 257.712, 14.2028, -18.8203, 227.592, -8.0411, -20.6878, 227.872, -8.2033, -20.6705, 252.823, -12.1253, -19.1234, 227.872, -8.2033, -20.6705, 231.736, -10.4389, -20.4309, 252.823, -12.1253, -19.1234, 231.736, -10.4389, -20.4309, 232.001, -10.5298, -20.4144, 252.823, -12.1253, -19.1234, 232.001, -10.5298, -20.4144, 236.638, -12.1193, -20.1269, 252.823, -12.1253, -19.1234, 236.638, -12.1193, -20.1269, 236.848, -12.153, -20.1139, 252.823, -12.1253, -19.1234, 236.848, -12.153, -20.1139, 242.014, -12.9847, -19.7936, 252.823, -12.1253, -19.1234, 242.014, -12.9847, -19.7936, 242.142, -12.9847, -19.7857, 252.823, -12.1253, -19.1234, 242.142, -12.9847, -19.7857, 250.357, -12.5329, -19.2763, 252.823, -12.1253, -19.1234, 247.592, -12.9781, -19.4478, 242.142, -12.9847, -19.7857, 247.551, -12.9847, -19.4503, 242.142, -12.9847, -19.7857, 247.592, -12.9781, -19.4478, 250.357, -12.5329, -19.2763, 260.638, 4.4899, -18.6388, 260.65, 4.417, -18.6381, 266.642, 5.495, -18.2666, 266.67, 5.3424, -18.2649, 261.075, 1.8384, -18.6118, 267.292, 1.9054, -18.2263, 260.65, 4.417, -18.6381, 266.67, 5.3424, -18.2649, 266.642, 5.495, -18.2666, 261.075, 1.8384, -18.6118, 266.67, 5.3424, -18.2649, 260.65, 4.417, -18.6381, 264.802, 8.7484, -18.3807, 259.355, 6.9872, -18.7184, 266.642, 5.495, -18.2666, 259.355, 6.9872, -18.7184, 259.386, 6.9269, -18.7165, 266.642, 5.495, -18.2666, 259.386, 6.9269, -18.7165, 260.638, 4.4899, -18.6388, 266.642, 5.495, -18.2666, 261.76, 11.7626, -18.5693, 257.298, 9.1853, -18.8459, 264.73, 8.8761, -18.3851, 257.298, 9.1853, -18.8459, 257.337, 9.1441, -18.8435, 264.73, 8.8761, -18.3851, 259.355, 6.9872, -18.7184, 264.802, 8.7484, -18.3807, 264.73, 8.8761, -18.3851, 259.355, 6.9872, -18.7184, 264.73, 8.8761, -18.3851, 257.337, 9.1441, -18.8435, 257.712, 14.2028, -18.8203, 254.589, 10.9564, -19.014, 261.668, 11.852, -18.575, 254.589, 10.9564, -19.014, 254.62, 10.936, -19.012, 261.668, 11.852, -18.575, 257.298, 9.1853, -18.8459, 261.76, 11.7626, -18.5693, 261.668, 11.852, -18.575, 257.298, 9.1853, -18.8459, 261.668, 11.852, -18.575, 254.62, 10.936, -19.012, 250.357, 12.3842, -19.2763, 251.383, 12.1977, -19.2127, 257.712, 14.2028, -18.8203, 251.392, 12.1941, -19.2121, 257.712, 14.2028, -18.8203, 251.383, 12.1977, -19.2127, 257.712, 14.2028, -18.8203, 251.392, 12.1941, -19.2121, 254.589, 10.9564, -19.014, 251.383, -8.5208, -19.2127, 251.373, -8.5226, -19.2133, 252.823, -12.1253, -19.1234, 251.373, -8.5226, -19.2133, 250.357, -8.7074, -19.2763, 252.823, -12.1253, -19.1234, 254.551, -7.294, -19.0163, 252.823, -12.1253, -19.1234, 254.589, -7.2796, -19.0139, 252.823, -12.1253, -19.1234, 254.551, -7.294, -19.0163, 251.383, -8.5208, -19.2127, 267.292, 1.9054, -18.2263, 257.298, -5.5084, -18.8459, 252.823, -12.1253, -19.1234, 257.247, -5.5419, -18.8491, 252.823, -12.1253, -19.1234, 257.298, -5.5084, -18.8459, 252.823, -12.1253, -19.1234, 257.247, -5.5419, -18.8491, 254.589, -7.2796, -19.0139, 259.355, -3.3103, -18.7184, 259.305, -3.3636, -18.7215, 267.292, 1.9054, -18.2263, 257.298, -5.5084, -18.8459, 267.292, 1.9054, -18.2263, 259.305, -3.3636, -18.7215, 260.638, -0.813, -18.6388, 260.603, -0.8818, -18.641, 267.292, 1.9054, -18.2263, 260.603, -0.8818, -18.641, 259.355, -3.3103, -18.7184, 267.292, 1.9054, -18.2263, 261.075, 1.8384, -18.6118, 261.062, 1.7625, -18.6126, 267.292, 1.9054, -18.2263, 260.638, -0.813, -18.6388, 267.292, 1.9054, -18.2263, 261.062, 1.7625, -18.6126, 252.892, 15.9187, -19.1192, 247.801, 12.8368, -19.4348, 257.633, 14.2498, -18.8252, 250.357, 12.3842, -19.2763, 257.633, 14.2498, -18.8252, 247.801, 12.8368, -19.4348, 247.774, 12.8369, -19.4365, 252.892, 15.9187, -19.1192, 244.083, 12.8369, -19.6653, 252.892, 15.9187, -19.1192, 247.774, 12.8369, -19.4365, 247.801, 12.8368, -19.4348, 224.447, 8.876, -20.8828, 240.474, 12.1977, -19.8891, 252.892, 15.9187, -19.1192, 244.017, 12.8251, -19.6694, 252.892, 15.9187, -19.1192, 240.474, 12.1977, -19.8891, 252.892, 15.9187, -19.1192, 244.017, 12.8251, -19.6694, 244.083, 12.8369, -19.6653, 224.286, 8.6, -20.8928, 237.182, 10.9564, -20.0932, 224.447, 8.876, -20.8828, 224.447, 8.876, -20.8828, 240.377, 12.1612, -19.8951, 240.474, 12.1977, -19.8891, 240.377, 12.1612, -19.8951, 224.447, 8.876, -20.8828, 237.182, 10.9564, -20.0932, 222.483, 5.4951, -21.0046, 234.399, 9.1853, -20.2658, 224.286, 8.6, -20.8928, 234.399, 9.1853, -20.2658, 237.072, 10.8864, -20.1, 224.286, 8.6, -20.8928, 237.072, 10.8864, -20.1, 237.182, 10.9564, -20.0932, 224.286, 8.6, -20.8928, 222.425, 5.18, -21.0082, 232.287, 6.9872, -20.3967, 222.483, 5.4951, -21.0046, 222.483, 5.4951, -21.0046, 234.297, 9.0794, -20.2721, 234.399, 9.1853, -20.2658, 232.287, 6.9872, -20.3967, 234.297, 9.0794, -20.2721, 222.483, 5.4951, -21.0046, 221.816, 1.9054, -21.046, 230.969, 4.4899, -20.4785, 222.425, 5.18, -21.0082, 230.969, 4.4899, -20.4785, 232.215, 6.8504, -20.4012, 222.425, 5.18, -21.0082, 232.287, 6.9872, -20.3967, 222.425, 5.18, -21.0082, 232.215, 6.8504, -20.4012, 222.483, -1.6842, -21.0046, 230.521, 1.8384, -20.5062, 221.876, 1.5852, -21.0423, 230.521, 1.8384, -20.5062, 221.816, 1.9054, -21.046, 221.876, 1.5852, -21.0423, 230.521, 1.8384, -20.5062, 230.942, 4.3342, -20.4801, 221.816, 1.9054, -21.046, 230.969, 4.4899, -20.4785, 221.816, 1.9054, -21.046, 230.942, 4.3342, -20.4801, 224.447, -5.0652, -20.8828, 230.969, -0.813, -20.4785, 222.652, -1.975, -20.9941, 222.483, -1.6842, -21.0046, 230.547, 1.6799, -20.5046, 230.521, 1.8384, -20.5062, 230.969, -0.813, -20.4785, 222.483, -1.6842, -21.0046, 222.652, -1.975, -20.9941, 230.969, -0.813, -20.4785, 230.547, 1.6799, -20.5046, 222.483, -1.6842, -21.0046, 224.447, -5.0652, -20.8828, 231.045, -0.9577, -20.4737, 230.969, -0.813, -20.4785, 231.045, -0.9577, -20.4737, 224.447, -5.0652, -20.8828, 232.287, -3.3103, -20.3967, 227.592, -8.0411, -20.6878, 234.399, -5.5084, -20.2658, 224.694, -5.2991, -20.8675, 224.447, -5.0652, -20.8828, 232.4, -3.4277, -20.3897, 232.287, -3.3103, -20.3967, 234.399, -5.5084, -20.2658, 224.447, -5.0652, -20.8828, 224.694, -5.2991, -20.8675, 234.399, -5.5084, -20.2658, 232.4, -3.4277, -20.3897, 224.447, -5.0652, -20.8828, 227.592, -8.0411, -20.6878, 234.528, -5.5908, -20.2577, 234.399, -5.5084, -20.2658, 237.182, -7.2796, -20.0932, 234.528, -5.5908, -20.2577, 227.592, -8.0411, -20.6878, 252.823, -12.1253, -19.1234, 240.474, -8.5208, -19.8891, 227.592, -8.0411, -20.6878, 227.592, -8.0411, -20.6878, 237.306, -7.3265, -20.0855, 237.182, -7.2796, -20.0932, 240.474, -8.5208, -19.8891, 237.306, -7.3265, -20.0855, 227.592, -8.0411, -20.6878, 240.575, -8.5387, -19.8829, 252.823, -12.1253, -19.1234, 244.083, -9.16, -19.6653, 252.823, -12.1253, -19.1234, 240.575, -8.5387, -19.8829, 240.474, -8.5208, -19.8891, 247.827, -9.1555, -19.4332, 252.823, -12.1253, -19.1234, 250.357, -8.7074, -19.2763, 252.823, -12.1253, -19.1234, 247.827, -9.1555, -19.4332, 247.801, -9.16, -19.4348, 244.148, -9.16, -19.6613, 252.823, -12.1253, -19.1234, 247.801, -9.16, -19.4348, 252.823, -12.1253, -19.1234, 244.148, -9.16, -19.6613, 244.083, -9.16, -19.6653, -244.58, 14.2498, -49.9642, -231.093, 16.7956, -49.1279, -241.472, 15.3308, -49.7714, -244.58, 14.2498, -49.9642, -228.992, 16.7956, -48.9977, -231.093, 16.7956, -49.1279, -231.093, 16.7956, -49.1279, -239.748, 15.9302, -49.6646, -241.472, 15.3308, -49.7714, -231.093, 16.7956, -49.1279, -236.41, 16.4754, -49.4576, -239.748, 15.9302, -49.6646, -234.449, 16.7956, -49.336, -236.41, 16.4754, -49.4576, -231.093, 16.7956, -49.1279, -248.665, 11.8519, -50.2175, -225.816, 16.2769, -48.8008, -245.992, 13.4212, -50.0517, -248.665, 11.8519, -50.2175, -223.693, 15.9302, -48.6691, -225.816, 16.2769, -48.8008, -225.816, 16.2769, -48.8008, -244.58, 14.2498, -49.9642, -245.992, 13.4212, -50.0517, -228.992, 16.7956, -48.9977, -244.58, 14.2498, -49.9642, -225.816, 16.2769, -48.8008, -220.873, 14.9495, -48.4943, -248.665, 11.8519, -50.2175, -249.71, 10.8492, -50.2822, -223.693, 15.9302, -48.6691, -248.665, 11.8519, -50.2175, -220.873, 14.9495, -48.4943, -210.791, -3.5191, -47.8691, -213.383, -6.7031, -48.0298, -211.676, -5.0652, -47.924, -210.791, -3.5191, -47.8691, -214.776, -8.0411, -48.1162, -213.383, -6.7031, -48.0298, -209.741, -1.6842, -47.804, -217.071, -9.3881, -48.2585, -210.791, -3.5191, -47.8691, -209.438, -0.0308, -47.7852, -218.861, -10.4389, -48.3695, -209.741, -1.6842, -47.804, -217.071, -9.3881, -48.2585, -209.741, -1.6842, -47.804, -218.861, -10.4389, -48.3695, -214.776, -8.0411, -48.1162, -210.791, -3.5191, -47.8691, -217.071, -9.3881, -48.2585, -209.083, 1.9054, -47.7632, -221.644, -11.4067, -48.5421, -209.438, -0.0308, -47.7852, -209.385, 3.5542, -47.782, -223.693, -12.1193, -48.6691, -209.083, 1.9054, -47.7632, -221.644, -11.4067, -48.5421, -209.083, 1.9054, -47.7632, -223.693, -12.1193, -48.6691, -218.861, -10.4389, -48.3695, -209.438, -0.0308, -47.7852, -221.644, -11.4067, -48.5421, -210.619, 7.0285, -47.8585, -226.828, -12.6314, -48.8635, -209.741, 5.495, -47.804, -226.828, -12.6314, -48.8635, -209.385, 3.5542, -47.782, -209.741, 5.495, -47.804, -226.828, -12.6314, -48.8635, -223.693, -12.1193, -48.6691, -209.385, 3.5542, -47.782, -254.358, 1.9054, -50.5705, -237.755, -12.4449, -49.541, -234.449, -12.9847, -49.336, -254.358, 1.9054, -50.5705, -239.748, -12.1193, -49.6646, -237.755, -12.4449, -49.541, -254.358, 1.9054, -50.5705, -242.832, -11.0469, -49.8558, -239.748, -12.1193, -49.6646, -254.358, 1.9054, -50.5705, -244.58, -10.4389, -49.9642, -242.832, -11.0469, -49.8558, -254.358, 1.9054, -50.5705, -247.237, -8.8791, -50.129, -244.58, -10.4389, -49.9642, -254.358, 1.9054, -50.5705, -248.665, -8.0411, -50.2175, -247.237, -8.8791, -50.129, -254.358, 1.9054, -50.5705, -250.712, -6.0762, -50.3444, -248.665, -8.0411, -50.2175, -254.358, 1.9054, -50.5705, -251.765, -5.0652, -50.4097, -250.712, -6.0762, -50.3444, -254.358, 1.9054, -50.5705, -253.056, -2.8097, -50.4897, -251.765, -5.0652, -50.4097, -254.142, 0.7239, -50.557, -253.056, -2.8097, -50.4897, -254.358, 1.9054, -50.5705, -253.056, -2.8097, -50.4897, -254.142, 0.7239, -50.557, -253.7, -1.6842, -50.5297, -254.358, 1.9054, -50.5705, -247.623, 4.4899, -50.1529, -253.917, 4.3155, -50.5431, -254.358, 1.9054, -50.5705, -247.775, 3.5774, -50.1623, -247.623, 4.4899, -50.1529, -247.775, 3.5774, -50.1623, -254.358, 1.9054, -50.5705, -248.065, 1.8384, -50.1802, -253.7, 5.495, -50.5297, -246.324, 6.9872, -50.0723, -252.406, 7.7562, -50.4494, -253.917, 4.3155, -50.5431, -246.773, 6.1232, -50.1002, -253.7, 5.495, -50.5297, -246.324, 6.9872, -50.0723, -253.7, 5.495, -50.5297, -246.773, 6.1232, -50.1002, -247.623, 4.4899, -50.1529, -246.773, 6.1232, -50.1002, -253.917, 4.3155, -50.5431, -252.406, 7.7562, -50.4494, -244.242, 9.1853, -49.9432, -251.765, 8.8761, -50.4097, -246.324, 6.9872, -50.0723, -244.97, 8.4161, -49.9884, -252.406, 7.7562, -50.4494, -244.97, 8.4161, -49.9884, -244.242, 9.1853, -49.9432, -252.406, 7.7562, -50.4494, -251.765, 8.8761, -50.4097, -241.499, 10.9564, -49.7731, -249.71, 10.8492, -50.2822, -242.475, 10.3261, -49.8337, -241.499, 10.9564, -49.7731, -251.765, 8.8761, -50.4097, -242.475, 10.3261, -49.8337, -251.765, 8.8761, -50.4097, -244.242, 9.1853, -49.9432, -239.434, 11.7463, -49.6451, -249.71, 10.8492, -50.2822, -241.499, 10.9564, -49.7731, -249.71, 10.8492, -50.2822, -239.434, 11.7463, -49.6451, -238.254, 12.1977, -49.572, -249.71, 10.8492, -50.2822, -234.696, 12.8369, -49.3513, -220.873, 14.9495, -48.4943, -236.022, 12.5986, -49.4336, -249.71, 10.8492, -50.2822, -238.254, 12.1977, -49.572, -249.71, 10.8492, -50.2822, -236.022, 12.5986, -49.4336, -234.696, 12.8369, -49.3513, -220.873, 14.9495, -48.4943, -231.032, 12.8369, -49.1241, -218.861, 14.2498, -48.3695, -231.032, 12.8369, -49.1241, -220.873, 14.9495, -48.4943, -232.434, 12.8369, -49.2111, -234.696, 12.8369, -49.3513, -232.434, 12.8369, -49.2111, -220.873, 14.9495, -48.4943, -218.861, 14.2498, -48.3695, -227.474, 12.1977, -48.9035, -216.538, 12.8859, -48.2254, -218.861, 14.2498, -48.3695, -228.873, 12.449, -48.9903, -227.474, 12.1977, -48.9035, -231.032, 12.8369, -49.1241, -228.873, 12.449, -48.9903, -218.861, 14.2498, -48.3695, -216.538, 12.8859, -48.2254, -224.229, 10.9564, -48.7023, -214.776, 11.8519, -48.1162, -224.229, 10.9564, -48.7023, -216.538, 12.8859, -48.2254, -225.538, 11.4574, -48.7835, -227.474, 12.1977, -48.9035, -225.538, 11.4574, -48.7835, -216.538, 12.8859, -48.2254, -214.776, 11.8519, -48.1162, -221.486, 9.1853, -48.5323, -213.052, 10.1971, -48.0094, -214.776, 11.8519, -48.1162, -222.619, 9.917, -48.6025, -221.486, 9.1853, -48.5323, -224.229, 10.9564, -48.7023, -222.619, 9.917, -48.6025, -214.776, 11.8519, -48.1162, -211.676, 8.8761, -47.924, -219.404, 6.9872, -48.4032, -210.619, 7.0285, -47.8585, -213.052, 10.1971, -48.0094, -220.281, 7.913, -48.4576, -211.676, 8.8761, -47.924, -220.281, 7.913, -48.4576, -219.404, 6.9872, -48.4032, -211.676, 8.8761, -47.924, -221.486, 9.1853, -48.5323, -220.281, 7.913, -48.4576, -213.052, 10.1971, -48.0094, -210.619, 7.0285, -47.8585, -218.66, 5.5568, -48.357, -218.105, 4.4899, -48.3226, -218.66, 5.5568, -48.357, -210.619, 7.0285, -47.8585, -219.404, 6.9872, -48.4032, -217.853, 2.9806, -48.307, -210.619, 7.0285, -47.8585, -218.105, 4.4899, -48.3226, -210.619, 7.0285, -47.8585, -217.853, 2.9806, -48.307, -217.663, 1.8384, -48.2952, -217.914, 0.3311, -48.3108, -210.619, 7.0285, -47.8585, -217.663, 1.8384, -48.2952, -210.619, 7.0285, -47.8585, -217.914, 0.3311, -48.3108, -218.105, -0.813, -48.3226, -210.619, 7.0285, -47.8585, -219.404, -3.3103, -48.4032, -226.828, -12.6314, -48.8635, -210.619, 7.0285, -47.8585, -218.846, -2.238, -48.3686, -219.404, -3.3103, -48.4032, -218.846, -2.238, -48.3686, -210.619, 7.0285, -47.8585, -218.105, -0.813, -48.3226, -226.828, -12.6314, -48.8635, -221.486, -5.5084, -48.5323, -228.992, -12.9847, -48.9977, -220.602, -4.5753, -48.4775, -226.828, -12.6314, -48.8635, -219.404, -3.3103, -48.4032, -226.828, -12.6314, -48.8635, -220.602, -4.5753, -48.4775, -221.486, -5.5084, -48.5323, -223.084, -6.5404, -48.6314, -224.229, -7.2796, -48.7023, -228.992, -12.9847, -48.9977, -223.084, -6.5404, -48.6314, -228.992, -12.9847, -48.9977, -221.486, -5.5084, -48.5323, -228.992, -12.9847, -48.9977, -226.149, -8.014, -48.8214, -227.474, -8.5208, -48.9035, -226.149, -8.014, -48.8214, -228.992, -12.9847, -48.9977, -224.229, -7.2796, -48.7023, -229.615, -8.9055, -49.0363, -228.992, -12.9847, -48.9977, -227.474, -8.5208, -48.9035, -228.992, -12.9847, -48.9977, -229.615, -8.9055, -49.0363, -231.032, -9.16, -49.1241, -231.032, -9.16, -49.1241, -233.276, -9.16, -49.2633, -228.992, -12.9847, -48.9977, -233.276, -9.16, -49.2633, -234.696, -9.16, -49.3514, -228.992, -12.9847, -48.9977, -228.992, -12.9847, -48.9977, -238.254, -8.5208, -49.572, -232.31, -12.9847, -49.2034, -228.992, -12.9847, -48.9977, -236.913, -8.7617, -49.4888, -238.254, -8.5208, -49.572, -234.696, -9.16, -49.3514, -236.913, -8.7617, -49.4888, -228.992, -12.9847, -48.9977, -232.31, -12.9847, -49.2034, -241.499, -7.2796, -49.7731, -234.449, -12.9847, -49.336, -232.31, -12.9847, -49.2034, -240.307, -7.7354, -49.6993, -241.499, -7.2796, -49.7731, -238.254, -8.5208, -49.572, -240.307, -7.7354, -49.6993, -232.31, -12.9847, -49.2034, -234.449, -12.9847, -49.336, -244.242, -5.5084, -49.9432, -254.358, 1.9054, -50.5705, -243.258, -6.1439, -49.8822, -234.449, -12.9847, -49.336, -241.499, -7.2796, -49.7731, -234.449, -12.9847, -49.336, -243.258, -6.1439, -49.8822, -244.242, -5.5084, -49.9432, -254.358, 1.9054, -50.5705, -245.591, -4.0841, -50.0268, -246.324, -3.3103, -50.0723, -245.591, -4.0841, -50.0268, -254.358, 1.9054, -50.5705, -244.242, -5.5084, -49.9432, -247.913, 0.9249, -50.1708, -254.358, 1.9054, -50.5705, -247.623, -0.813, -50.1528, -254.358, 1.9054, -50.5705, -247.913, 0.9249, -50.1708, -248.065, 1.8384, -50.1802, -254.358, 1.9054, -50.5705, -247.172, -1.6801, -50.1249, -247.623, -0.813, -50.1528, -247.172, -1.6801, -50.1249, -254.358, 1.9054, -50.5705, -246.324, -3.3103, -50.0723)
+
+[node name="Tunnel" instance=ExtResource("1_1sr0i")]
+collision_mask = 3
+
+[node name="Tunnel2" parent="." index="0"]
+surface_material_override/0 = ExtResource("2_lkb1r")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." index="1"]
+shape = SubResource("ConcavePolygonShape3D_bh6xx")
diff --git a/demo/assets/textures/asset_licenses.txt b/demo/assets/textures/asset_licenses.txt
new file mode 100644
index 0000000..bcab6b6
--- /dev/null
+++ b/demo/assets/textures/asset_licenses.txt
@@ -0,0 +1,12 @@
+All ambientCG assets are provided under the
+Creative Commons CC0 1.0 Universal License.
+https://docs.ambientcg.com/license/
+
+Grass:
+https://ambientcg.com/view?id=Ground037
+
+Rock:
+https://ambientcg.com/view?id=Rock030
+
+Cliff:
+https://ambientcg.com/view?id=Rock023
diff --git a/demo/assets/textures/ground037_alb_ht.png b/demo/assets/textures/ground037_alb_ht.png
new file mode 100644
index 0000000..d7628fa
Binary files /dev/null and b/demo/assets/textures/ground037_alb_ht.png differ
diff --git a/demo/assets/textures/ground037_alb_ht.png.import b/demo/assets/textures/ground037_alb_ht.png.import
new file mode 100644
index 0000000..eab222b
--- /dev/null
+++ b/demo/assets/textures/ground037_alb_ht.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ddprscrpsofah"
+path.bptc="res://.godot/imported/ground037_alb_ht.png-d854c3c88beba9927351b95063edffa2.bptc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://demo/assets/textures/ground037_alb_ht.png"
+dest_files=["res://.godot/imported/ground037_alb_ht.png-d854c3c88beba9927351b95063edffa2.bptc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=true
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=2
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+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
diff --git a/demo/assets/textures/ground037_nrm_rgh.png b/demo/assets/textures/ground037_nrm_rgh.png
new file mode 100644
index 0000000..d370866
Binary files /dev/null and b/demo/assets/textures/ground037_nrm_rgh.png differ
diff --git a/demo/assets/textures/ground037_nrm_rgh.png.import b/demo/assets/textures/ground037_nrm_rgh.png.import
new file mode 100644
index 0000000..f766f44
--- /dev/null
+++ b/demo/assets/textures/ground037_nrm_rgh.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c1ots7w6i0i1q"
+path.bptc="res://.godot/imported/ground037_nrm_rgh.png-02372c72ee87f1844ee708d952e43e96.bptc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://demo/assets/textures/ground037_nrm_rgh.png"
+dest_files=["res://.godot/imported/ground037_nrm_rgh.png-02372c72ee87f1844ee708d952e43e96.bptc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=true
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=2
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+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
diff --git a/demo/assets/textures/rock023_alb_ht.png b/demo/assets/textures/rock023_alb_ht.png
new file mode 100644
index 0000000..b80135c
Binary files /dev/null and b/demo/assets/textures/rock023_alb_ht.png differ
diff --git a/demo/assets/textures/rock023_alb_ht.png.import b/demo/assets/textures/rock023_alb_ht.png.import
new file mode 100644
index 0000000..a0fadc4
--- /dev/null
+++ b/demo/assets/textures/rock023_alb_ht.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c88j3oj0lf6om"
+path.bptc="res://.godot/imported/rock023_alb_ht.png-9c06b8a5f11d940d1c46d8928f6d6a5c.bptc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://demo/assets/textures/rock023_alb_ht.png"
+dest_files=["res://.godot/imported/rock023_alb_ht.png-9c06b8a5f11d940d1c46d8928f6d6a5c.bptc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=true
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=2
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+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=0
diff --git a/demo/assets/textures/rock023_nrm_rgh.png b/demo/assets/textures/rock023_nrm_rgh.png
new file mode 100644
index 0000000..8b30450
Binary files /dev/null and b/demo/assets/textures/rock023_nrm_rgh.png differ
diff --git a/demo/assets/textures/rock023_nrm_rgh.png.import b/demo/assets/textures/rock023_nrm_rgh.png.import
new file mode 100644
index 0000000..3f48b1c
--- /dev/null
+++ b/demo/assets/textures/rock023_nrm_rgh.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c307hdmos4gtm"
+path.bptc="res://.godot/imported/rock023_nrm_rgh.png-dd524905a15817dac8149681cddc8975.bptc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://demo/assets/textures/rock023_nrm_rgh.png"
+dest_files=["res://.godot/imported/rock023_nrm_rgh.png-dd524905a15817dac8149681cddc8975.bptc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=true
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=2
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+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=0
diff --git a/demo/components/Borders.tscn b/demo/components/Borders.tscn
new file mode 100644
index 0000000..ae00fb3
--- /dev/null
+++ b/demo/components/Borders.tscn
@@ -0,0 +1,57 @@
+[gd_scene load_steps=6 format=3 uid="uid://dwnhqfjq7v1pq"]
+
+[ext_resource type="Material" uid="uid://d0hyi5n6ng25w" path="res://demo/assets/materials/M_rock23_black_tp.tres" id="1_goand"]
+
+[sub_resource type="BoxMesh" id="BoxMesh_1kfaq"]
+size = Vector3(1025.01, 4, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_s0c8u"]
+size = Vector3(1025, 100, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_kfxc8"]
+size = Vector3(3072, 4, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_pyqb2"]
+size = Vector3(1, 100, 3072)
+
+[node name="Borders" type="StaticBody3D"]
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 512)
+mesh = SubResource("BoxMesh_1kfaq")
+skeleton = NodePath("../..")
+surface_material_override/0 = ExtResource("1_goand")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, 512)
+shape = SubResource("BoxShape3D_s0c8u")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, -2560)
+mesh = SubResource("BoxMesh_1kfaq")
+skeleton = NodePath("../..")
+surface_material_override/0 = ExtResource("1_goand")
+
+[node name="CollisionShape3D2" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, -2560)
+shape = SubResource("BoxShape3D_s0c8u")
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="."]
+transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 512, 2, -1024)
+mesh = SubResource("BoxMesh_kfxc8")
+skeleton = NodePath("../..")
+surface_material_override/0 = ExtResource("1_goand")
+
+[node name="CollisionShape3D3" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 512, 50, -1024)
+shape = SubResource("BoxShape3D_pyqb2")
+
+[node name="MeshInstance3D4" type="MeshInstance3D" parent="."]
+transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -512, 2, -1024)
+mesh = SubResource("BoxMesh_kfxc8")
+skeleton = NodePath("../..")
+surface_material_override/0 = ExtResource("1_goand")
+
+[node name="CollisionShape3D4" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -512, 50, -1024)
+shape = SubResource("BoxShape3D_pyqb2")
diff --git a/demo/components/DemoBenchmark.tscn b/demo/components/DemoBenchmark.tscn
new file mode 100644
index 0000000..4561e9d
--- /dev/null
+++ b/demo/components/DemoBenchmark.tscn
@@ -0,0 +1,84 @@
+[gd_scene load_steps=11 format=3 uid="uid://dyt8c2xqmddo2"]
+
+[ext_resource type="Script" uid="uid://chstoagn42gbr" path="res://demo/src/DemoScene.gd" id="1_2gjn4"]
+[ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="2_3obxr"]
+[ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_b7ioy"]
+[ext_resource type="PackedScene" uid="uid://dwnhqfjq7v1pq" path="res://demo/components/Borders.tscn" id="4_nn6wp"]
+[ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="5_bwggt"]
+[ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="6_3r2au"]
+
+[sub_resource type="Gradient" id="Gradient_f1plm"]
+offsets = PackedFloat32Array(0.2, 1)
+colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_bfcw0"]
+noise_type = 2
+frequency = 0.03
+cellular_jitter = 3.0
+cellular_return_type = 0
+domain_warp_enabled = true
+domain_warp_type = 1
+domain_warp_amplitude = 50.0
+domain_warp_fractal_type = 2
+domain_warp_fractal_lacunarity = 1.5
+domain_warp_fractal_gain = 1.0
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_quxx0"]
+seamless = true
+color_ramp = SubResource("Gradient_f1plm")
+noise = SubResource("FastNoiseLite_bfcw0")
+
+[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_klrp5"]
+_shader_parameters = {
+"auto_base_texture": 0,
+"auto_height_reduction": 0.1,
+"auto_overlay_texture": 1,
+"auto_slope": 1.0,
+"bias_distance": 512.0,
+"blend_sharpness": 0.87,
+"depth_blur": 0.0,
+"dual_scale_far": 170.0,
+"dual_scale_near": 100.0,
+"dual_scale_reduction": 0.3,
+"dual_scale_texture": 0,
+"enable_macro_variation": true,
+"enable_projection": true,
+"height_blending": true,
+"macro_variation1": Color(1, 1, 1, 1),
+"macro_variation2": Color(1, 1, 1, 1),
+"macro_variation_slope": 0.333,
+"mipmap_bias": 1.0,
+"noise1_angle": 0.0,
+"noise1_offset": Vector2(0.5, 0.5),
+"noise1_scale": 0.04,
+"noise2_scale": 0.076,
+"noise3_scale": 0.225,
+"noise_texture": SubResource("NoiseTexture2D_quxx0"),
+"projection_angular_division": 1.436,
+"projection_threshold": 0.8,
+"tri_scale_reduction": 0.3,
+&"world_space_normal_blend": true
+}
+auto_shader = true
+dual_scaling = true
+
+[node name="Demo" type="Node"]
+script = ExtResource("1_2gjn4")
+
+[node name="UI" parent="." instance=ExtResource("2_3obxr")]
+
+[node name="World" parent="." instance=ExtResource("3_b7ioy")]
+
+[node name="Borders" parent="." instance=ExtResource("4_nn6wp")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 512, 0, 512)
+
+[node name="Player" parent="." instance=ExtResource("5_bwggt")]
+transform = Transform3D(0.134125, 0, -0.990965, 0, 1, 0, 0.990965, 0, 0.134125, 216.455, 103.968, -1835.34)
+
+[node name="Terrain3D" type="Terrain3D" parent="."]
+data_directory = "res://demo/data"
+material = SubResource("Terrain3DMaterial_klrp5")
+assets = ExtResource("6_3r2au")
+collision_mask = 3
+top_level = true
+metadata/_edit_lock_ = true
diff --git a/demo/components/Enemy.tscn b/demo/components/Enemy.tscn
new file mode 100644
index 0000000..0f64e1f
--- /dev/null
+++ b/demo/components/Enemy.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=6 format=3 uid="uid://di5fovhcyd7re"]
+
+[ext_resource type="Script" path="res://demo/src/Enemy.gd" id="1_yudyn"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_lwhhq"]
+height = 1.5
+
+[sub_resource type="SeparationRayShape3D" id="SeparationRayShape3D_i8f01"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_lsqiy"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_d4cor"]
+albedo_color = Color(1, 0, 0, 1)
+
+[node name="Enemy" type="CharacterBody3D"]
+collision_layer = 2
+script = ExtResource("1_yudyn")
+
+[node name="CollisionShapeBody" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0)
+shape = SubResource("CapsuleShape3D_lwhhq")
+
+[node name="CollisionShapeRay" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 1, 0)
+shape = SubResource("SeparationRayShape3D_i8f01")
+
+[node name="Body" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("CapsuleMesh_lsqiy")
+surface_material_override/0 = SubResource("StandardMaterial3D_d4cor")
+
+[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
+path_desired_distance = 2.0
+debug_enabled = true
diff --git a/demo/components/Environment.tscn b/demo/components/Environment.tscn
new file mode 100644
index 0000000..06cd358
--- /dev/null
+++ b/demo/components/Environment.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=5 format=3 uid="uid://bb2lp50sjndus"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_vibap"]
+sky_top_color = Color(0.192157, 0.282353, 0.509804, 1)
+sky_horizon_color = Color(0.505882, 0.615686, 0.709804, 1)
+ground_bottom_color = Color(0.211765, 0.313726, 0.552941, 1)
+ground_horizon_color = Color(0.505882, 0.615686, 0.709804, 1)
+ground_curve = 0.13
+
+[sub_resource type="Sky" id="Sky_srg7c"]
+sky_material = SubResource("ProceduralSkyMaterial_vibap")
+
+[sub_resource type="Environment" id="Environment_8jcgm"]
+background_mode = 2
+sky = SubResource("Sky_srg7c")
+ambient_light_source = 3
+ambient_light_color = Color(0.55, 0.55, 0.55, 1)
+ambient_light_sky_contribution = 0.3
+tonemap_mode = 3
+
+[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_0gcl0"]
+
+[node name="Environment" type="Node3D"]
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_8jcgm")
+camera_attributes = SubResource("CameraAttributesPractical_0gcl0")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866024, -0.433016, 0.250001, 0, 0.499998, 0.866026, -0.500003, 0.749999, -0.43301, 0, 0, 0)
+shadow_enabled = true
+shadow_blur = 2.0
+directional_shadow_blend_splits = true
+directional_shadow_max_distance = 256.0
diff --git a/demo/components/Player.tscn b/demo/components/Player.tscn
new file mode 100644
index 0000000..b8e81c7
--- /dev/null
+++ b/demo/components/Player.tscn
@@ -0,0 +1,44 @@
+[gd_scene load_steps=7 format=3 uid="uid://domhm87hbhbg1"]
+
+[ext_resource type="Script" path="res://demo/src/Player.gd" id="1_nm1yx"]
+[ext_resource type="Script" path="res://demo/src/CameraManager.gd" id="2_loos7"]
+
+[sub_resource type="SphereShape3D" id="SphereShape3D_smq6u"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_lwhhq"]
+height = 1.5
+
+[sub_resource type="SeparationRayShape3D" id="SeparationRayShape3D_twc2s"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_lsqiy"]
+
+[node name="Player" type="CharacterBody3D"]
+collision_layer = 2
+script = ExtResource("1_nm1yx")
+
+[node name="CameraManager" type="Node3D" parent="."]
+script = ExtResource("2_loos7")
+
+[node name="Arm" type="SpringArm3D" parent="CameraManager"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.906308, 0.422618, 0, -0.422618, 0.906308, 0, 2.32515, -0.0321627)
+shape = SubResource("SphereShape3D_smq6u")
+spring_length = 6.0
+margin = 0.5
+
+[node name="Camera3D" type="Camera3D" parent="CameraManager/Arm"]
+unique_name_in_owner = true
+near = 0.25
+far = 16384.0
+
+[node name="CollisionShapeBody" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0)
+shape = SubResource("CapsuleShape3D_lwhhq")
+
+[node name="CollisionShapeRay" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 1, 0)
+shape = SubResource("SeparationRayShape3D_twc2s")
+
+[node name="Body" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("CapsuleMesh_lsqiy")
diff --git a/demo/components/Tunnel.tscn b/demo/components/Tunnel.tscn
new file mode 100644
index 0000000..7c6c6e7
--- /dev/null
+++ b/demo/components/Tunnel.tscn
@@ -0,0 +1,167 @@
+[gd_scene load_steps=10 format=3 uid="uid://djhl3foqkj4e2"]
+
+[ext_resource type="PackedScene" uid="uid://be6nrf0b8j4l0" path="res://demo/assets/models/RockA.tscn" id="1_m1xck"]
+[ext_resource type="PackedScene" uid="uid://bwvtgwartxt0g" path="res://demo/assets/models/RockB.tscn" id="2_hybky"]
+[ext_resource type="PackedScene" uid="uid://lsvs8a7urkca" path="res://demo/assets/models/RockC.tscn" id="3_nbn1a"]
+[ext_resource type="PackedScene" uid="uid://vvayjv3rbx1d" path="res://demo/assets/models/Tunnel.tscn" id="4_klbpo"]
+[ext_resource type="PackedScene" uid="uid://cribhhvg03u8g" path="res://demo/assets/models/CrystalC.tscn" id="5_bb2w0"]
+[ext_resource type="Material" uid="uid://cso4f2iyuxpmc" path="res://demo/assets/materials/M_crystal_purple.tres" id="6_s6twx"]
+[ext_resource type="Material" uid="uid://ickkffutwcvo" path="res://demo/assets/materials/M_crystal_red.tres" id="7_7fkm2"]
+[ext_resource type="Script" uid="uid://c444j1ucmv5ti" path="res://demo/src/CaveEntrance.gd" id="9_fn2ke"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_goiy4"]
+size = Vector3(530.482, 38.6343, 235.603)
+
+[node name="Tunnel" type="Node3D"]
+
+[node name="EntranceL" type="Node3D" parent="."]
+
+[node name="RockA1" parent="EntranceL" instance=ExtResource("1_m1xck")]
+transform = Transform3D(5.82988, -5.4374, -4.00004, 6.04396, 6.55839, -0.106264, 3.00604, -2.64109, 7.9713, 807.937, 137.298, 467.411)
+
+[node name="RockA2" parent="EntranceL" instance=ExtResource("1_m1xck")]
+transform = Transform3D(4.67905, 4.22076, -5.5926, 6.98724, -3.31022, 3.34764, -0.52024, -6.49718, -5.3387, 766.019, 104.95, 452.605)
+
+[node name="RockA3" parent="EntranceL" instance=ExtResource("1_m1xck")]
+transform = Transform3D(1.19782, 11.0889, 4.42738, -11.9291, 0.921057, 0.9205, 0.510788, -4.49312, 11.1153, 793.166, 76.9378, 454.834)
+
+[node name="RockB1" parent="EntranceL" instance=ExtResource("2_hybky")]
+transform = Transform3D(-2.10522, -4.25988, 3.15298, 3.05166, -3.74811, -3.02637, 4.33304, 0.570028, 3.66329, 824.574, 112.54, 446.946)
+
+[node name="RockB3" parent="EntranceL" instance=ExtResource("2_hybky")]
+transform = Transform3D(4.29222, 4.57142, 1.24961, -2.49948, 3.61605, -4.6432, -4.02641, 2.62846, 4.21447, 773.937, 140.911, 469.972)
+
+[node name="RockC1" parent="EntranceL" instance=ExtResource("3_nbn1a")]
+transform = Transform3D(-1.86839, -3.85072, -1.99813, -0.0474326, -2.1573, 4.20181, -4.33801, 1.6821, 0.814656, 786.081, 135.376, 453.531)
+
+[node name="RockC2" parent="EntranceL" instance=ExtResource("3_nbn1a")]
+transform = Transform3D(-3.90856, -4.56077, 3.35866, 3.81036, 0.902037, 5.65911, -4.19074, 5.07383, 2.01294, 791.859, 164.408, 508.107)
+
+[node name="EntranceR" type="Node3D" parent="."]
+
+[node name="RockA1" parent="EntranceR" instance=ExtResource("1_m1xck")]
+transform = Transform3D(-0.271255, 1.28607, -2.97471, -1.25761, 2.70942, 1.28605, 2.98685, 1.25759, 0.271335, 299.644, 127.203, 424.309)
+
+[node name="RockA2" parent="EntranceR" instance=ExtResource("1_m1xck")]
+transform = Transform3D(2.29164, -2.44454, 3.0731, 3.84134, 0.657286, -2.34167, 0.814771, 3.77671, 2.39666, 318.179, 95.8599, 421.731)
+
+[node name="RockA5" parent="EntranceR" instance=ExtResource("1_m1xck")]
+transform = Transform3D(1.09929, -4.37397, 0.575562, 3.84134, 0.657286, -2.34167, 2.16957, 1.05247, 3.85443, 321.814, 95.8599, 436.931)
+
+[node name="RockA3" parent="EntranceR" instance=ExtResource("1_m1xck")]
+transform = Transform3D(3.70822, 1.86229, 1.85804, 2.19079, -0.408409, -3.96295, -1.45634, 4.12752, -1.23046, 338.177, 116.66, 430.367)
+
+[node name="RockA4" parent="EntranceR" instance=ExtResource("1_m1xck")]
+transform = Transform3D(1.9779, 0.270409, 4.08487, -4.00125, 1.0869, 1.86547, -0.865577, -4.40646, 0.710812, 320.977, 134.583, 441.012)
+
+[node name="RockB1" parent="EntranceR" instance=ExtResource("2_hybky")]
+transform = Transform3D(-1.64956, -1.63286, 1.75857, 0.196316, 2.03499, 2.07367, -2.39171, 1.29322, -1.04267, 295.848, 109.828, 419.45)
+
+[node name="RockB3" parent="EntranceR" instance=ExtResource("2_hybky")]
+transform = Transform3D(-2.03718, 1.29729, 2.88404, -2.82928, 0.784888, -2.35156, -1.41272, -3.44263, 0.550663, 306.288, 128.712, 434.042)
+
+[node name="RockB4" parent="EntranceR" instance=ExtResource("2_hybky")]
+transform = Transform3D(1.97436, 2.87133, -1.41708, -2.82928, 0.784888, -2.35156, -1.49926, 2.30004, 2.57153, 328.686, 129.709, 427.63)
+
+[node name="RockB2" parent="EntranceR" instance=ExtResource("2_hybky")]
+transform = Transform3D(-2.17746, 0.476253, -1.87396, -1.61495, 1.104, 2.15708, 1.06324, 2.65221, -0.561397, 303.461, 107.797, 428.826)
+
+[node name="RockC1" parent="EntranceR" instance=ExtResource("3_nbn1a")]
+transform = Transform3D(1.74949, 1.15588, 0.576742, -1.01213, 1.82987, -0.597152, -0.802676, 0.21197, 2.01002, 335.27, 110.419, 418.362)
+
+[node name="RockC2" parent="EntranceR" instance=ExtResource("3_nbn1a")]
+transform = Transform3D(4.31956, 5.88829, -1.29353, -3.67086, 1.30662, -6.31041, -4.78226, 4.31561, 3.67549, 345.465, 146.581, 480.747)
+
+[node name="TunnelMesh" parent="." instance=ExtResource("4_klbpo")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 546.733, 112.043, 468.686)
+
+[node name="CrystalGroup1" type="Node3D" parent="."]
+transform = Transform3D(0.567756, 0.0788585, -0.00345403, 0.0729824, -0.514888, 0.241124, 0.0300693, -0.239266, -0.520024, 415.722, 122.719, 611.385)
+
+[node name="CrystalC" parent="CrystalGroup1" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(0.990472, -0.134216, 0.0307963, 0.132893, 0.873058, -0.469155, 0.0360812, 0.468779, 0.882577, 0.050354, 1.72128, 4.54956)
+
+[node name="CrystalC3" parent="CrystalGroup1" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(0.999999, 2.98023e-08, 0, 1.40402e-08, 0.999999, 1.19209e-07, 6.89635e-09, -1.49012e-07, 0.999999, 4.00897, 3.69431, -0.617554)
+
+[node name="CrystalC2" parent="CrystalGroup1" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(-0.513116, -0.374576, 0.95887, -0.130632, 1.08641, 0.354495, -1.02111, 0.0492404, -0.527189, -1.70374, 4.03528, -0.568237)
+
+[node name="OmniLight3D" type="OmniLight3D" parent="CrystalGroup1"]
+transform = Transform3D(1, 3.21278e-08, -1.96807e-08, -3.21278e-08, 1, 1.02499e-07, 1.96807e-08, -1.02499e-07, 1, 1.11737, 4.79865, 1.8949)
+light_color = Color(0.4, 0.760784, 1, 1)
+light_energy = 16.0
+shadow_enabled = true
+omni_range = 83.054
+
+[node name="CrystalGroup2" type="Node3D" parent="."]
+transform = Transform3D(-0.536793, -0.0213862, -0.199931, -0.116106, 0.498308, 0.25843, 0.164163, 0.282504, -0.470976, 553.318, 103.571, 647.425)
+
+[node name="CrystalC" parent="CrystalGroup2" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(0.704558, -0.579782, 0.130258, 0.554941, 0.56972, -0.465804, 0.212495, 0.434496, 0.784585, 0.815979, 1.24854, 2.5437)
+
+[node name="Rock3" parent="CrystalGroup2/CrystalC" index="0"]
+surface_material_override/0 = ExtResource("6_s6twx")
+
+[node name="CrystalC3" parent="CrystalGroup2" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(0.127208, -0.0568556, -1.06836, -0.317718, 1.02534, -0.0923969, 1.02161, 0.32596, 0.104294, 4.00861, 3.69458, -0.617188)
+
+[node name="Rock3" parent="CrystalGroup2/CrystalC3" index="0"]
+surface_material_override/0 = ExtResource("6_s6twx")
+
+[node name="CrystalC2" parent="CrystalGroup2" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(-0.642172, -0.213226, 0.774009, 0.484154, 0.687794, 0.591163, -0.640428, 0.733767, -0.329203, 7.28876, 1.71277, 4.52783)
+
+[node name="Rock3" parent="CrystalGroup2/CrystalC2" index="0"]
+surface_material_override/0 = ExtResource("6_s6twx")
+
+[node name="OmniLight3D" type="OmniLight3D" parent="CrystalGroup2"]
+transform = Transform3D(1.74454, 2.98023e-08, -2.98023e-08, -5.60482e-08, 1.74454, 1.49012e-07, 3.43338e-08, -1.78814e-07, 1.74454, 1.11737, 4.79865, 1.8949)
+light_color = Color(0.396078, 0.333333, 0.878431, 1)
+light_energy = 16.0
+shadow_enabled = true
+omni_range = 83.054
+
+[node name="CrystalGroup3" type="Node3D" parent="."]
+transform = Transform3D(-0.536793, -0.0213862, -0.199931, 0.0789312, -0.546626, -0.153451, -0.184932, -0.17123, 0.514837, 711.291, 124.136, 579.252)
+
+[node name="CrystalC" parent="CrystalGroup3" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(0.898105, -0.188271, 0.0864646, 0.196692, 0.654036, -0.618921, 0.0650697, 0.621535, 0.677476, 0.589844, 2.22089, 3.04785)
+
+[node name="Rock3" parent="CrystalGroup3/CrystalC" index="0"]
+surface_material_override/0 = ExtResource("7_7fkm2")
+
+[node name="CrystalC3" parent="CrystalGroup3" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(0.15544, 0.0752453, -1.06348, -0.370935, 1.01139, 0.0173435, 0.999527, 0.363637, 0.171821, 4.00867, 3.69458, -0.617432)
+
+[node name="Rock3" parent="CrystalGroup3/CrystalC3" index="0"]
+surface_material_override/0 = ExtResource("7_7fkm2")
+
+[node name="CrystalC2" parent="CrystalGroup3" instance=ExtResource("5_bb2w0")]
+transform = Transform3D(-0.573134, -0.100696, 0.847536, 0.285048, 0.939695, 0.304405, -0.804489, 0.404691, -0.495942, 7.2887, 1.71283, 4.52771)
+
+[node name="Rock3" parent="CrystalGroup3/CrystalC2" index="0"]
+surface_material_override/0 = ExtResource("7_7fkm2")
+
+[node name="OmniLight3D" type="OmniLight3D" parent="CrystalGroup3"]
+transform = Transform3D(1.74454, 2.98023e-08, -2.98023e-08, -5.60482e-08, 1.74454, 1.49012e-07, 3.43338e-08, -1.78814e-07, 1.74454, 1.11737, 4.79865, 1.8949)
+light_color = Color(0.388235, 0.101961, 0.2, 1)
+light_energy = 16.0
+shadow_enabled = true
+omni_range = 83.054
+
+[node name="CaveArea3D" type="Area3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 549.961, 110.763, 526.656)
+collision_mask = 2
+script = ExtResource("9_fn2ke")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="CaveArea3D"]
+transform = Transform3D(0.998441, 0, -0.0558215, 0, 1, 0, 0.0558215, 0, 0.998441, -8.36676, 3.78637, 30.7709)
+shape = SubResource("BoxShape3D_goiy4")
+
+[editable path="CrystalGroup2/CrystalC"]
+[editable path="CrystalGroup2/CrystalC3"]
+[editable path="CrystalGroup2/CrystalC2"]
+[editable path="CrystalGroup3/CrystalC"]
+[editable path="CrystalGroup3/CrystalC3"]
+[editable path="CrystalGroup3/CrystalC2"]
diff --git a/demo/components/UI.tscn b/demo/components/UI.tscn
new file mode 100644
index 0000000..e36d604
--- /dev/null
+++ b/demo/components/UI.tscn
@@ -0,0 +1,66 @@
+[gd_scene load_steps=2 format=3 uid="uid://d2jihfohphuue"]
+
+[ext_resource type="Script" path="res://demo/src/UI.gd" id="1_why5e"]
+
+[node name="UI" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_why5e")
+
+[node name="Label" type="Label" parent="."]
+unique_name_in_owner = true
+layout_mode = 1
+offset_left = 5.0
+offset_top = 5.0
+offset_right = 275.0
+offset_bottom = 340.0
+theme_override_colors/font_shadow_color = Color(0, 0, 0, 0.662745)
+theme_override_constants/shadow_offset_x = 1
+theme_override_constants/shadow_offset_y = 1
+text = "FPS: 100
+Position: (100, 100, 100)
+Move Speed: 10
+
+Player
+Move: WASDEQ,Space,Mouse
+Move speed: Wheel,+/-,Shift
+Camera view: V
+Gravity toggle: G
+Collision toggle: C
+
+Window
+Quit: F8
+UI toggle: F9
+Render mode: F10
+Full screen: F11
+Mouse toggle: Escape
+"
+
+[node name="Panel" type="Panel" parent="Label"]
+modulate = Color(1, 1, 1, 0.392157)
+show_behind_parent = true
+layout_mode = 0
+offset_left = -5.0
+offset_top = -5.0
+offset_right = 248.0
+offset_bottom = 444.0
+
+[node name="HSeparator" type="HSeparator" parent="Label/Panel"]
+top_level = true
+layout_mode = 0
+offset_left = 6.0
+offset_top = 129.0
+offset_right = 246.0
+offset_bottom = 138.0
+
+[node name="HSeparator2" type="HSeparator" parent="Label/Panel"]
+top_level = true
+layout_mode = 0
+offset_left = 6.0
+offset_top = 310.0
+offset_right = 246.0
+offset_bottom = 319.0
diff --git a/demo/data/assets.tres b/demo/data/assets.tres
new file mode 100644
index 0000000..2dfa5f6
--- /dev/null
+++ b/demo/data/assets.tres
@@ -0,0 +1,57 @@
+[gd_resource type="Terrain3DAssets" load_steps=11 format=3 uid="uid://dal3jhw6241qg"]
+
+[ext_resource type="PackedScene" uid="uid://bn5nf4esciwex" path="res://demo/assets/models/LODExample.tscn" id="1_4jrdu"]
+[ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="2_pog6b"]
+[ext_resource type="Texture2D" uid="uid://ddprscrpsofah" path="res://demo/assets/textures/ground037_alb_ht.png" id="3_g8f2m"]
+[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="3_wncaf"]
+[ext_resource type="Texture2D" uid="uid://c1ots7w6i0i1q" path="res://demo/assets/textures/ground037_nrm_rgh.png" id="4_aw5y1"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_b2vqk"]
+transparency = 4
+cull_mode = 2
+vertex_color_use_as_albedo = true
+backlight_enabled = true
+backlight = Color(0.5, 0.5, 0.5, 1)
+distance_fade_mode = 1
+distance_fade_min_distance = 128.0
+distance_fade_max_distance = 96.0
+
+[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_2qf8x"]
+name = "TextureCard"
+generated_type = 1
+height_offset = 0.5
+material_override = SubResource("StandardMaterial3D_b2vqk")
+last_lod = 0
+last_shadow_lod = 0
+lod0_range = 128.0
+
+[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_or12t"]
+name = "LODExample"
+id = 1
+scene_file = ExtResource("1_4jrdu")
+height_offset = 0.5
+last_lod = 3
+last_shadow_lod = 3
+
+[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_lha57"]
+name = "Cliff"
+albedo_texture = ExtResource("2_pog6b")
+normal_texture = ExtResource("3_wncaf")
+normal_depth = 1.0
+ao_strength = 2.0
+roughness = -0.05
+
+[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_od0q7"]
+name = "Grass"
+id = 1
+albedo_color = Color(0.67451, 0.74902, 0.686275, 1)
+albedo_texture = ExtResource("3_g8f2m")
+normal_texture = ExtResource("4_aw5y1")
+normal_depth = 1.0
+ao_strength = 2.0
+uv_scale = 0.2
+detiling_rotation = 0.161
+
+[resource]
+mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_2qf8x"), SubResource("Terrain3DMeshAsset_or12t")])
+texture_list = Array[Terrain3DTextureAsset]([SubResource("Terrain3DTextureAsset_lha57"), SubResource("Terrain3DTextureAsset_od0q7")])
diff --git a/demo/data/nav_mesh.res b/demo/data/nav_mesh.res
new file mode 100644
index 0000000..c949983
Binary files /dev/null and b/demo/data/nav_mesh.res differ
diff --git a/demo/data/terrain3d_00-01.res b/demo/data/terrain3d_00-01.res
new file mode 100644
index 0000000..d6019c9
Binary files /dev/null and b/demo/data/terrain3d_00-01.res differ
diff --git a/demo/data/terrain3d_00-02.res b/demo/data/terrain3d_00-02.res
new file mode 100644
index 0000000..eb9cd4c
Binary files /dev/null and b/demo/data/terrain3d_00-02.res differ
diff --git a/demo/data/terrain3d_00_00.res b/demo/data/terrain3d_00_00.res
new file mode 100644
index 0000000..12d0868
Binary files /dev/null and b/demo/data/terrain3d_00_00.res differ
diff --git a/demo/src/CameraManager.gd b/demo/src/CameraManager.gd
new file mode 100644
index 0000000..9c40488
--- /dev/null
+++ b/demo/src/CameraManager.gd
@@ -0,0 +1,29 @@
+extends Node3D
+
+const CAMERA_MAX_PITCH: float = deg_to_rad(70)
+const CAMERA_MIN_PITCH: float = deg_to_rad(-89.9)
+const CAMERA_RATIO: float = .625
+
+@export var mouse_sensitivity: float = .002
+@export var mouse_y_inversion: float = -1.0
+
+@onready var _camera_yaw: Node3D = self
+@onready var _camera_pitch: Node3D = %Arm
+
+
+func _ready() -> void:
+ Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+
+
+func _input(p_event: InputEvent) -> void:
+ if p_event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
+ rotate_camera(p_event.relative)
+ get_viewport().set_input_as_handled()
+ return
+
+
+func rotate_camera(p_relative:Vector2) -> void:
+ _camera_yaw.rotation.y -= p_relative.x * mouse_sensitivity
+ _camera_yaw.orthonormalize()
+ _camera_pitch.rotation.x += p_relative.y * mouse_sensitivity * CAMERA_RATIO * mouse_y_inversion
+ _camera_pitch.rotation.x = clamp(_camera_pitch.rotation.x, CAMERA_MIN_PITCH, CAMERA_MAX_PITCH)
diff --git a/demo/src/CameraManager.gd.uid b/demo/src/CameraManager.gd.uid
new file mode 100644
index 0000000..aa06bd2
--- /dev/null
+++ b/demo/src/CameraManager.gd.uid
@@ -0,0 +1 @@
+uid://b62ppvc03a6b1
diff --git a/demo/src/CaveEntrance.gd b/demo/src/CaveEntrance.gd
new file mode 100644
index 0000000..70e7948
--- /dev/null
+++ b/demo/src/CaveEntrance.gd
@@ -0,0 +1,22 @@
+extends Area3D
+
+
+func _ready() -> void:
+ body_entered.connect(_on_body_entered)
+ body_exited.connect(_on_body_exited)
+
+
+func _on_body_entered(body: Node3D) -> void:
+ if body.name == "Player":
+ var env: WorldEnvironment = get_node_or_null("../../Environment/WorldEnvironment")
+ if env:
+ var tween: Tween = get_tree().create_tween()
+ tween.tween_property(env.environment, "ambient_light_energy", .1, .33)
+
+
+func _on_body_exited(body: Node3D) -> void:
+ if body.name == "Player":
+ var env: WorldEnvironment = get_node_or_null("../../Environment/WorldEnvironment")
+ if env:
+ var tween: Tween = get_tree().create_tween()
+ tween.tween_property(env.environment, "ambient_light_energy", 1., .33)
diff --git a/demo/src/CaveEntrance.gd.uid b/demo/src/CaveEntrance.gd.uid
new file mode 100644
index 0000000..28ca46f
--- /dev/null
+++ b/demo/src/CaveEntrance.gd.uid
@@ -0,0 +1 @@
+uid://c444j1ucmv5ti
diff --git a/demo/src/CodeGenerated.gd b/demo/src/CodeGenerated.gd
new file mode 100644
index 0000000..a91652d
--- /dev/null
+++ b/demo/src/CodeGenerated.gd
@@ -0,0 +1,133 @@
+extends Node
+
+var terrain: Terrain3D
+
+
+func _ready() -> void:
+ $UI.player = $Player
+
+ if has_node("RunThisSceneLabel3D"):
+ $RunThisSceneLabel3D.queue_free()
+
+ terrain = await create_terrain()
+
+ # Enable runtime navigation baking using the terrain
+ # Enable `Debug/Visible Navigation` if you wish to see it
+ $RuntimeNavigationBaker.terrain = terrain
+ $RuntimeNavigationBaker.enabled = true
+
+
+func create_terrain() -> Terrain3D:
+ # Create textures
+ var green_gr := Gradient.new()
+ green_gr.set_color(0, Color.from_hsv(100./360., .35, .3))
+ green_gr.set_color(1, Color.from_hsv(120./360., .4, .37))
+ var green_ta: Terrain3DTextureAsset = await create_texture_asset("Grass", green_gr, 1024)
+ green_ta.uv_scale = 0.1
+ green_ta.detiling_rotation = 0.1
+
+ var brown_gr := Gradient.new()
+ brown_gr.set_color(0, Color.from_hsv(30./360., .4, .3))
+ brown_gr.set_color(1, Color.from_hsv(30./360., .4, .4))
+ var brown_ta: Terrain3DTextureAsset = await create_texture_asset("Dirt", brown_gr, 1024)
+ brown_ta.uv_scale = 0.03
+ green_ta.detiling_rotation = 0.1
+
+ var grass_ma: Terrain3DMeshAsset = create_mesh_asset("Grass", Color.from_hsv(120./360., .4, .37))
+
+ # Create a terrain
+ var terrain := Terrain3D.new()
+ terrain.name = "Terrain3D"
+ add_child(terrain, true)
+
+ # Set material and assets
+ terrain.material.world_background = Terrain3DMaterial.NONE
+ terrain.material.auto_shader = true
+ terrain.material.set_shader_param("auto_slope", 10)
+ terrain.material.set_shader_param("blend_sharpness", .975)
+ terrain.assets = Terrain3DAssets.new()
+ terrain.assets.set_texture(0, green_ta)
+ terrain.assets.set_texture(1, brown_ta)
+ terrain.assets.set_mesh_asset(0, grass_ma)
+
+ # Generate height map w/ 32-bit noise and import it with scale
+ var noise := FastNoiseLite.new()
+ noise.frequency = 0.0005
+ var img: Image = Image.create_empty(2048, 2048, false, Image.FORMAT_RF)
+ for x in img.get_width():
+ for y in img.get_height():
+ img.set_pixel(x, y, Color(noise.get_noise_2d(x, y), 0., 0., 1.))
+ terrain.region_size = 1024
+ terrain.data.import_images([img, null, null], Vector3(-1024, 0, -1024), 0.0, 150.0)
+
+ # Instance foliage
+ var xforms: Array[Transform3D]
+ var width: int = 100
+ var step: int = 2
+ for x in range(0, width, step):
+ for z in range(0, width, step):
+ var pos := Vector3(x, 0, z) - Vector3(width, 0, width) * .5
+ pos.y = terrain.data.get_height(pos)
+ xforms.push_back(Transform3D(Basis(), pos))
+ terrain.instancer.add_transforms(0, xforms)
+
+ # Enable the next line and `Debug/Visible Collision Shapes` to see collision
+ #terrain.collision.mode = Terrain3DCollision.DYNAMIC_EDITOR
+
+ return terrain
+
+
+func create_texture_asset(asset_name: String, gradient: Gradient, texture_size: int = 512) -> Terrain3DTextureAsset:
+ # Create noise map
+ var fnl := FastNoiseLite.new()
+ fnl.frequency = 0.004
+
+ # Create albedo noise texture
+ var alb_noise_tex := NoiseTexture2D.new()
+ alb_noise_tex.width = texture_size
+ alb_noise_tex.height = texture_size
+ alb_noise_tex.seamless = true
+ alb_noise_tex.noise = fnl
+ alb_noise_tex.color_ramp = gradient
+ await alb_noise_tex.changed
+ var alb_noise_img: Image = alb_noise_tex.get_image()
+
+ # Create albedo + height texture
+ for x in alb_noise_img.get_width():
+ for y in alb_noise_img.get_height():
+ var clr: Color = alb_noise_img.get_pixel(x, y)
+ clr.a = clr.v # Noise as height
+ alb_noise_img.set_pixel(x, y, clr)
+ alb_noise_img.generate_mipmaps()
+ var albedo := ImageTexture.create_from_image(alb_noise_img)
+
+ # Create normal + rough texture
+ var nrm_noise_tex := NoiseTexture2D.new()
+ nrm_noise_tex.width = texture_size
+ nrm_noise_tex.height = texture_size
+ nrm_noise_tex.as_normal_map = true
+ nrm_noise_tex.seamless = true
+ nrm_noise_tex.noise = fnl
+ await nrm_noise_tex.changed
+ var nrm_noise_img = nrm_noise_tex.get_image()
+ for x in nrm_noise_img.get_width():
+ for y in nrm_noise_img.get_height():
+ var normal_rgh: Color = nrm_noise_img.get_pixel(x, y)
+ normal_rgh.a = 0.8 # Roughness
+ nrm_noise_img.set_pixel(x, y, normal_rgh)
+ nrm_noise_img.generate_mipmaps()
+ var normal := ImageTexture.create_from_image(nrm_noise_img)
+
+ var ta := Terrain3DTextureAsset.new()
+ ta.name = asset_name
+ ta.albedo_texture = albedo
+ ta.normal_texture = normal
+ return ta
+
+
+func create_mesh_asset(asset_name: String, color: Color) -> Terrain3DMeshAsset:
+ var ma := Terrain3DMeshAsset.new()
+ ma.name = asset_name
+ ma.generated_type = Terrain3DMeshAsset.TYPE_TEXTURE_CARD
+ ma.material_override.albedo_color = color
+ return ma
diff --git a/demo/src/CodeGenerated.gd.uid b/demo/src/CodeGenerated.gd.uid
new file mode 100644
index 0000000..108d980
--- /dev/null
+++ b/demo/src/CodeGenerated.gd.uid
@@ -0,0 +1 @@
+uid://dakis6gu8b7nm
diff --git a/demo/src/DemoScene.gd b/demo/src/DemoScene.gd
new file mode 100644
index 0000000..979be71
--- /dev/null
+++ b/demo/src/DemoScene.gd
@@ -0,0 +1,23 @@
+@tool
+extends Node
+
+@onready var terrain: Terrain3D = find_child("Terrain3D")
+
+
+func _ready():
+ if not Engine.is_editor_hint() and has_node("UI"):
+ $UI.player = $Player
+
+ # Load Sky3D into the demo environment if enabled
+ if Engine.is_editor_hint() and has_node("Environment") and \
+ Engine.get_singleton(&"EditorInterface").is_plugin_enabled("sky_3d"):
+ $Environment.queue_free()
+ var sky3d = load("res://addons/sky_3d/src/Sky3D.gd").new()
+ sky3d.name = "Sky3D"
+ add_child(sky3d, true)
+ move_child(sky3d, 1)
+ sky3d.owner = self
+ sky3d.current_time = 10
+ sky3d.enable_editor_time = false
+
+
diff --git a/demo/src/DemoScene.gd.uid b/demo/src/DemoScene.gd.uid
new file mode 100644
index 0000000..2afcbc9
--- /dev/null
+++ b/demo/src/DemoScene.gd.uid
@@ -0,0 +1 @@
+uid://chstoagn42gbr
diff --git a/demo/src/Enemy.gd b/demo/src/Enemy.gd
new file mode 100644
index 0000000..c8d444c
--- /dev/null
+++ b/demo/src/Enemy.gd
@@ -0,0 +1,58 @@
+extends CharacterBody3D
+
+const RETARGET_COOLDOWN: float = 1.0
+
+@export var MOVE_SPEED: float = 50.0
+@export var target: Node3D
+
+@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
+
+var _retarget_timer: float = 1.0
+
+
+func _ready() -> void:
+ nav_agent.velocity_computed.connect(_on_velocity_computed)
+
+
+func _process(p_delta: float) -> void:
+ _retarget_timer += p_delta
+ if _retarget_timer > RETARGET_COOLDOWN and target:
+ # Don't reset the target position every frame. It triggers an A* search, which is expensive.
+ _retarget_timer = 0.0
+ nav_agent.set_target_position(target.global_position)
+
+
+func is_on_nav_mesh() -> bool:
+ var closest_point := NavigationServer3D.map_get_closest_point(nav_agent.get_navigation_map(), global_position)
+ return global_position.distance_squared_to(closest_point) < nav_agent.path_max_distance ** 2
+
+
+func _physics_process(p_delta: float) -> void:
+ if nav_agent.is_navigation_finished():
+ velocity.x = 0.0
+ velocity.z = 0.0
+ else:
+ var next_path_position: Vector3 = nav_agent.get_next_path_position()
+ var current_agent_position: Vector3 = global_position
+ var velocity_xz := (next_path_position - current_agent_position).normalized() * MOVE_SPEED
+ velocity.x = velocity_xz.x
+ velocity.z = velocity_xz.z
+
+ velocity.y -= 40 * p_delta
+
+ if nav_agent.avoidance_enabled:
+ nav_agent.set_velocity(velocity)
+ else:
+ _on_velocity_computed(velocity)
+
+ # Ensure enemy doesn't fall through terrain when collision absent
+ if get_parent().terrain:
+ var height: float = get_parent().terrain.data.get_height(global_position)
+ if not is_nan(height):
+ global_position.y = maxf(global_position.y, height)
+
+
+func _on_velocity_computed(p_safe_velocity: Vector3) -> void:
+ velocity.x = p_safe_velocity.x
+ velocity.z = p_safe_velocity.z
+ move_and_slide()
diff --git a/demo/src/Enemy.gd.uid b/demo/src/Enemy.gd.uid
new file mode 100644
index 0000000..72794d4
--- /dev/null
+++ b/demo/src/Enemy.gd.uid
@@ -0,0 +1 @@
+uid://6j2rrp5f1gjs
diff --git a/demo/src/Player.gd b/demo/src/Player.gd
new file mode 100644
index 0000000..8ae1b84
--- /dev/null
+++ b/demo/src/Player.gd
@@ -0,0 +1,81 @@
+extends CharacterBody3D
+
+@export var MOVE_SPEED: float = 50.0
+@export var JUMP_SPEED: float = 2.0
+@export var first_person: bool = false :
+ set(p_value):
+ first_person = p_value
+ if first_person:
+ var tween: Tween = create_tween()
+ tween.tween_property($CameraManager/Arm, "spring_length", 0.0, .33)
+ tween.tween_callback($Body.set_visible.bind(false))
+ else:
+ $Body.visible = true
+ create_tween().tween_property($CameraManager/Arm, "spring_length", 6.0, .33)
+
+@export var gravity_enabled: bool = true :
+ set(p_value):
+ gravity_enabled = p_value
+ if not gravity_enabled:
+ velocity.y = 0
+
+@export var collision_enabled: bool = true :
+ set(p_value):
+ collision_enabled = p_value
+ $CollisionShapeBody.disabled = ! collision_enabled
+ $CollisionShapeRay.disabled = ! collision_enabled
+
+
+func _physics_process(p_delta) -> void:
+ var direction: Vector3 = get_camera_relative_input()
+ var h_veloc: Vector2 = Vector2(direction.x, direction.z).normalized() * MOVE_SPEED
+ if Input.is_key_pressed(KEY_SHIFT):
+ h_veloc *= 2
+ velocity.x = h_veloc.x
+ velocity.z = h_veloc.y
+ if gravity_enabled:
+ velocity.y -= 40 * p_delta
+ move_and_slide()
+
+
+# Returns the input vector relative to the camera. Forward is always the direction the camera is facing
+func get_camera_relative_input() -> Vector3:
+ var input_dir: Vector3 = Vector3.ZERO
+ if Input.is_key_pressed(KEY_A): # Left
+ input_dir -= %Camera3D.global_transform.basis.x
+ if Input.is_key_pressed(KEY_D): # Right
+ input_dir += %Camera3D.global_transform.basis.x
+ if Input.is_key_pressed(KEY_W): # Forward
+ input_dir -= %Camera3D.global_transform.basis.z
+ if Input.is_key_pressed(KEY_S): # Backward
+ input_dir += %Camera3D.global_transform.basis.z
+ if Input.is_key_pressed(KEY_E) or Input.is_key_pressed(KEY_SPACE): # Up
+ velocity.y += JUMP_SPEED + MOVE_SPEED*.016
+ if Input.is_key_pressed(KEY_Q): # Down
+ velocity.y -= JUMP_SPEED + MOVE_SPEED*.016
+ if Input.is_key_pressed(KEY_KP_ADD) or Input.is_key_pressed(KEY_EQUAL):
+ MOVE_SPEED = clamp(MOVE_SPEED + .5, 5, 9999)
+ if Input.is_key_pressed(KEY_KP_SUBTRACT) or Input.is_key_pressed(KEY_MINUS):
+ MOVE_SPEED = clamp(MOVE_SPEED - .5, 5, 9999)
+ return input_dir
+
+
+func _input(p_event: InputEvent) -> void:
+ if p_event is InputEventMouseButton and p_event.pressed:
+ if p_event.button_index == MOUSE_BUTTON_WHEEL_UP:
+ MOVE_SPEED = clamp(MOVE_SPEED + 5, 5, 9999)
+ elif p_event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
+ MOVE_SPEED = clamp(MOVE_SPEED - 5, 5, 9999)
+
+ elif p_event is InputEventKey:
+ if p_event.pressed:
+ if p_event.keycode == KEY_V:
+ first_person = ! first_person
+ elif p_event.keycode == KEY_G:
+ gravity_enabled = ! gravity_enabled
+ elif p_event.keycode == KEY_C:
+ collision_enabled = ! collision_enabled
+
+ # Else if up/down released
+ elif p_event.keycode in [ KEY_Q, KEY_E, KEY_SPACE ]:
+ velocity.y = 0
diff --git a/demo/src/Player.gd.uid b/demo/src/Player.gd.uid
new file mode 100644
index 0000000..fa920b7
--- /dev/null
+++ b/demo/src/Player.gd.uid
@@ -0,0 +1 @@
+uid://dajlr3n5wjwmb
diff --git a/demo/src/RuntimeNavigationBaker.gd b/demo/src/RuntimeNavigationBaker.gd
new file mode 100644
index 0000000..be3629d
--- /dev/null
+++ b/demo/src/RuntimeNavigationBaker.gd
@@ -0,0 +1,151 @@
+extends Node
+
+signal bake_finished
+
+@export var enabled: bool = true : set = set_enabled
+@export var enter_cost: float = 0.0 : set = set_enter_cost
+@export var travel_cost: float = 1.0 : set = set_travel_cost
+@export_flags_3d_navigation var navigation_layers: int = 1 : set = set_navigation_layers
+@export var template: NavigationMesh : set = set_template
+@export var terrain: Terrain3D
+@export var player: Node3D
+@export var mesh_size := Vector3(256, 512, 256)
+@export var min_rebake_distance: float = 64.0
+@export var bake_cooldown: float = 1.0
+@export_group("Debug")
+@export var log_timing: bool = false
+
+var _scene_geometry: NavigationMeshSourceGeometryData3D
+var _current_center := Vector3(INF,INF,INF)
+
+var _bake_task_id: int = -1
+var _bake_task_timer: float = 0.0
+var _bake_cooldown_timer: float = 0.0
+var _nav_region: NavigationRegion3D
+
+
+func _ready():
+ _nav_region = NavigationRegion3D.new()
+ _nav_region.navigation_layers = navigation_layers
+ _nav_region.enabled = enabled
+ _nav_region.enter_cost = enter_cost
+ _nav_region.travel_cost = travel_cost
+
+ # Enabling edge connections comes with a performance penalty that causes hitches whenever
+ # the nav mesh is updated. The navigation server has to compare each edge, and it does this on
+ # the main thread.
+ _nav_region.use_edge_connections = false
+
+ add_child(_nav_region)
+
+ _update_map_cell_size()
+
+ # If you're using ProtonScatter, you will want to delay this next call until after all
+ # your scatter nodes have finished setting up. Here, we just defer one frame so that nodes
+ # after this one in the tree get set up first
+ parse_scene.call_deferred()
+
+
+func set_enabled(p_value: bool) -> void:
+ enabled = p_value
+ if _nav_region:
+ _nav_region.enabled = enabled
+ set_process(enabled and template)
+
+
+func set_enter_cost(p_value: bool) -> void:
+ enter_cost = p_value
+ if _nav_region:
+ _nav_region.enter_cost = enter_cost
+
+
+func set_travel_cost(p_value: bool) -> void:
+ travel_cost = p_value
+ if _nav_region:
+ _nav_region.travel_cost = travel_cost
+
+
+func set_navigation_layers(p_value: int) -> void:
+ navigation_layers = p_value
+ if _nav_region:
+ _nav_region.navigation_layers = navigation_layers
+
+
+func set_template(p_value: NavigationMesh) -> void:
+ template = p_value
+ set_process(enabled and template)
+ _update_map_cell_size()
+
+
+func parse_scene() -> void:
+ _scene_geometry = NavigationMeshSourceGeometryData3D.new()
+ NavigationServer3D.parse_source_geometry_data(template, _scene_geometry, self)
+
+
+func _update_map_cell_size() -> void:
+ if get_viewport() and template:
+ var map := get_viewport().find_world_3d().navigation_map
+ NavigationServer3D.map_set_cell_size(map, template.cell_size)
+ NavigationServer3D.map_set_cell_height(map, template.cell_height)
+
+
+func _process(p_delta: float) -> void:
+ if _bake_task_id != -1:
+ _bake_task_timer += p_delta
+
+ if not player or _bake_task_id != -1:
+ return
+
+ if _bake_cooldown_timer > 0.0:
+ _bake_cooldown_timer -= p_delta
+ return
+
+ var track_pos := player.global_position
+ if player is CharacterBody3D:
+ # Center on where the player is likely _going to be_:
+ track_pos += player.velocity * bake_cooldown
+
+ if track_pos.distance_squared_to(_current_center) >= min_rebake_distance * min_rebake_distance:
+ _current_center = track_pos
+ _rebake(_current_center)
+
+
+func _rebake(p_center: Vector3) -> void:
+ assert(template != null)
+ _bake_task_id = WorkerThreadPool.add_task(_task_bake.bind(p_center), false, "RuntimeNavigationBaker")
+ _bake_task_timer = 0.0
+ _bake_cooldown_timer = bake_cooldown
+
+
+func _task_bake(p_center: Vector3) -> void:
+ var nav_mesh: NavigationMesh = template.duplicate()
+ nav_mesh.filter_baking_aabb = AABB(-mesh_size * 0.5, mesh_size)
+ nav_mesh.filter_baking_aabb_offset = p_center
+ var source_geometry: NavigationMeshSourceGeometryData3D
+ source_geometry = _scene_geometry.duplicate()
+
+ if terrain:
+ var aabb: AABB = nav_mesh.filter_baking_aabb
+ aabb.position += nav_mesh.filter_baking_aabb_offset
+ var faces: PackedVector3Array = terrain.generate_nav_mesh_source_geometry(aabb, false)
+ source_geometry.add_faces(faces, Transform3D.IDENTITY)
+
+ if source_geometry.has_data():
+ NavigationServer3D.bake_from_source_geometry_data(nav_mesh, source_geometry)
+ _bake_finished.call_deferred(nav_mesh)
+ else:
+ _bake_finished.call_deferred(null)
+
+
+func _bake_finished(p_nav_mesh: NavigationMesh) -> void:
+ if log_timing:
+ print("Navigation bake took ", _bake_task_timer, "s")
+
+ _bake_task_timer = 0.0
+ _bake_task_id = -1
+
+ if p_nav_mesh:
+ _nav_region.navigation_mesh = p_nav_mesh
+
+ bake_finished.emit()
+ assert(!NavigationServer3D.region_get_use_edge_connections(_nav_region.get_region_rid()))
diff --git a/demo/src/RuntimeNavigationBaker.gd.uid b/demo/src/RuntimeNavigationBaker.gd.uid
new file mode 100644
index 0000000..157d6ce
--- /dev/null
+++ b/demo/src/RuntimeNavigationBaker.gd.uid
@@ -0,0 +1 @@
+uid://brh8x1wnycrl5
diff --git a/demo/src/UI.gd b/demo/src/UI.gd
new file mode 100644
index 0000000..970a1ac
--- /dev/null
+++ b/demo/src/UI.gd
@@ -0,0 +1,64 @@
+extends Control
+
+
+var player: Node
+var visible_mode: int = 1
+
+
+func _init() -> void:
+ RenderingServer.set_debug_generate_wireframes(true)
+
+
+func _process(p_delta) -> void:
+ $Label.text = "FPS: %d\n" % Engine.get_frames_per_second()
+ if(visible_mode == 1):
+ $Label.text += "Move Speed: %.1f\n" % player.MOVE_SPEED if player else ""
+ $Label.text += "Position: %.1v\n" % player.global_position if player else ""
+ $Label.text += """
+ Player
+ Move: WASDEQ,Space,Mouse
+ Move speed: Wheel,+/-,Shift
+ Camera View: V
+ Gravity toggle: G
+ Collision toggle: C
+
+ Window
+ Quit: F8
+ UI toggle: F9
+ Render mode: F10
+ Full screen: F11
+ Mouse toggle: Escape / F12
+ """
+
+
+func _unhandled_key_input(p_event: InputEvent) -> void:
+ if p_event is InputEventKey and p_event.pressed:
+ match p_event.keycode:
+ KEY_F8:
+ get_tree().quit()
+ KEY_F9:
+ visible_mode = (visible_mode + 1 ) % 3
+ $Label/Panel.visible = (visible_mode == 1)
+ visible = visible_mode > 0
+ KEY_F10:
+ var vp = get_viewport()
+ vp.debug_draw = (vp.debug_draw + 1 ) % 6
+ get_viewport().set_input_as_handled()
+ KEY_F11:
+ toggle_fullscreen()
+ get_viewport().set_input_as_handled()
+ KEY_ESCAPE, KEY_F12:
+ if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
+ Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+ else:
+ Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
+ get_viewport().set_input_as_handled()
+
+
+func toggle_fullscreen() -> void:
+ if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN or \
+ DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN:
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
+ DisplayServer.window_set_size(Vector2(1280, 720))
+ else:
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
diff --git a/demo/src/UI.gd.uid b/demo/src/UI.gd.uid
new file mode 100644
index 0000000..8a92755
--- /dev/null
+++ b/demo/src/UI.gd.uid
@@ -0,0 +1 @@
+uid://dne6na1m4xku8