├── isolines ├── isolines_grid.gd.uid ├── qef_solver.gd.uid ├── isolines_brush.gd.uid ├── isolines_mixer.gd.uid ├── circle_isolines_brush.gd.uid ├── dual_contour_isolines_grid.gd.uid ├── isolines_mixer.gd ├── circle_isolines_brush.gd ├── isolines_brush.gd ├── qef_solver.gd ├── isolines_grid.gd └── dual_contour_isolines_grid.gd ├── materials ├── type_1.gdshader.uid ├── type_2.gdshader.uid ├── type_3.gdshader.uid ├── type_2_albedo.png ├── type_2_normal.png ├── type_3_albedo.png ├── type_3_normal.png ├── type_2_parallax.png ├── type_1.gdshader ├── type_3.gdshader ├── type_3.tres ├── type_2.tres ├── type_2_albedo.png.import ├── type_2_normal.png.import ├── type_3_albedo.png.import ├── type_2_parallax.png.import ├── type_3_normal.png.import ├── type_1.tres └── type_2.gdshader ├── scenes ├── sandbox │ ├── sandbox.gd.uid │ ├── sandbox.tscn │ └── sandbox.gd ├── game_demo │ ├── game_demo.gd.uid │ ├── player.gd.uid │ ├── grass.png │ ├── player.png │ ├── grass.png.import │ ├── player.png.import │ ├── game_demo.gd │ ├── steam_particles.tscn │ ├── player.gd │ ├── player.tscn │ └── game_demo.tscn └── main_menu │ ├── main_menu.gd.uid │ ├── main_menu.gd │ └── main_menu.tscn ├── .editorconfig ├── resource_types ├── alchemy_mixer.gd.uid ├── alchemy_recipe.gd.uid ├── alchemy_recipe.gd └── alchemy_mixer.gd ├── .gitignore ├── screenshot.png ├── .gitattributes ├── README.md ├── alchemy_mixer.tres ├── icon.svg ├── LICENSE.txt ├── icon.svg.import └── project.godot /isolines/isolines_grid.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c2tkjqmg0htgd 2 | -------------------------------------------------------------------------------- /isolines/qef_solver.gd.uid: -------------------------------------------------------------------------------- 1 | uid://de3wgsctboxm2 2 | -------------------------------------------------------------------------------- /materials/type_1.gdshader.uid: -------------------------------------------------------------------------------- 1 | uid://x1nu5r2a6bgd 2 | -------------------------------------------------------------------------------- /materials/type_2.gdshader.uid: -------------------------------------------------------------------------------- 1 | uid://cxpng52j0gi8i 2 | -------------------------------------------------------------------------------- /materials/type_3.gdshader.uid: -------------------------------------------------------------------------------- 1 | uid://wo7smtt2udtc 2 | -------------------------------------------------------------------------------- /scenes/sandbox/sandbox.gd.uid: -------------------------------------------------------------------------------- 1 | uid://7gd75y8t6kus 2 | -------------------------------------------------------------------------------- /isolines/isolines_brush.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b1v0tsoyehbdl 2 | -------------------------------------------------------------------------------- /isolines/isolines_mixer.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dg8jdxcj0bycr 2 | -------------------------------------------------------------------------------- /scenes/game_demo/game_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c5npnq14tfqg0 2 | -------------------------------------------------------------------------------- /scenes/game_demo/player.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ckqedx6mav8lm 2 | -------------------------------------------------------------------------------- /scenes/main_menu/main_menu.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ov7xhhme3xfq 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | -------------------------------------------------------------------------------- /isolines/circle_isolines_brush.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cs2lgjiagka5d 2 | -------------------------------------------------------------------------------- /resource_types/alchemy_mixer.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ba1h6fmmb6281 2 | -------------------------------------------------------------------------------- /resource_types/alchemy_recipe.gd.uid: -------------------------------------------------------------------------------- 1 | uid://m63epohqucb4 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | /android/ 4 | -------------------------------------------------------------------------------- /isolines/dual_contour_isolines_grid.gd.uid: -------------------------------------------------------------------------------- 1 | uid://chh0ri02qmrv4 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/screenshot.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /materials/type_2_albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/materials/type_2_albedo.png -------------------------------------------------------------------------------- /materials/type_2_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/materials/type_2_normal.png -------------------------------------------------------------------------------- /materials/type_3_albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/materials/type_3_albedo.png -------------------------------------------------------------------------------- /materials/type_3_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/materials/type_3_normal.png -------------------------------------------------------------------------------- /scenes/game_demo/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/scenes/game_demo/grass.png -------------------------------------------------------------------------------- /scenes/game_demo/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/scenes/game_demo/player.png -------------------------------------------------------------------------------- /materials/type_2_parallax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apples/godot-dual-contouring-demo/main/materials/type_2_parallax.png -------------------------------------------------------------------------------- /isolines/isolines_mixer.gd: -------------------------------------------------------------------------------- 1 | @abstract 2 | extends Resource 3 | class_name IsolinesMixer 4 | 5 | @abstract func mix_types(surface_type: int, added_type: int) -> int 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot Dual Contouring Demo 2 | 3 | An alternative to marching squares. 4 | 5 | Also implements a basic elemental terrain system, where mixing fire and ice makes mud. 6 | 7 | ![screenshot](screenshot.png) 8 | 9 | -------------------------------------------------------------------------------- /resource_types/alchemy_recipe.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name AlchemyRecipe 3 | 4 | @export var surface_type: int = 0 5 | @export var added_type: int = 0 6 | @export var result_type: int = 0 7 | @export var symmetrical: bool = true 8 | -------------------------------------------------------------------------------- /scenes/main_menu/main_menu.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | 4 | func _on_game_button_pressed() -> void: 5 | get_tree().change_scene_to_file("res://scenes/game_demo/game_demo.tscn") 6 | 7 | 8 | func _on_sandbox_button_pressed() -> void: 9 | get_tree().change_scene_to_file("res://scenes/sandbox/sandbox.tscn") 10 | -------------------------------------------------------------------------------- /materials/type_1.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | render_mode skip_vertex_transform; 3 | 4 | uniform sampler3D noise_texture: repeat_enable, filter_linear; 5 | 6 | uniform mat3 noise_rotation; 7 | 8 | varying vec2 position; 9 | 10 | void vertex() { 11 | VERTEX = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy; 12 | position = VERTEX.xy / 160.0; 13 | } 14 | 15 | void fragment() { 16 | COLOR = texture(noise_texture, noise_rotation * vec3(position, TIME / 2.0)); 17 | } 18 | -------------------------------------------------------------------------------- /resource_types/alchemy_mixer.gd: -------------------------------------------------------------------------------- 1 | extends IsolinesMixer 2 | class_name AlchemyMixer 3 | 4 | @export var recipes: Array[AlchemyRecipe] = [] 5 | 6 | func mix_types(surface_type: int, added_type: int) -> int: 7 | var recipe_i := recipes.find_custom(func (recipe: AlchemyRecipe): 8 | return ( 9 | recipe.surface_type == surface_type and recipe.added_type == added_type 10 | or recipe.symmetrical and recipe.surface_type == added_type and recipe.added_type == surface_type 11 | ) 12 | ) 13 | 14 | if recipe_i == -1: 15 | return added_type 16 | 17 | return recipes[recipe_i].result_type 18 | -------------------------------------------------------------------------------- /materials/type_3.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | render_mode blend_mix; 3 | 4 | uniform vec4 albedo : source_color; 5 | uniform sampler2D texture_albedo : source_color, filter_linear_mipmap, repeat_enable; 6 | 7 | uniform sampler2D texture_normal : hint_roughness_normal, filter_linear_mipmap, repeat_enable; 8 | uniform float normal_scale : hint_range(-16.0, 16.0); 9 | 10 | uniform vec3 uv_scale; 11 | 12 | void vertex() { 13 | UV = UV * uv_scale.xy; 14 | } 15 | 16 | void fragment() { 17 | vec2 base_uv = UV; 18 | 19 | vec4 albedo_tex = texture(texture_albedo, base_uv); 20 | COLOR = albedo * albedo_tex; 21 | 22 | NORMAL_MAP = texture(texture_normal, base_uv).rgb; 23 | NORMAL_MAP_DEPTH = normal_scale; 24 | } 25 | -------------------------------------------------------------------------------- /materials/type_3.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="ShaderMaterial" load_steps=4 format=3 uid="uid://b7jhj1hgu31c8"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://beymkqgit11oq" path="res://materials/type_3_albedo.png" id="1"] 4 | [ext_resource type="Shader" uid="uid://wo7smtt2udtc" path="res://materials/type_3.gdshader" id="1_5jj7q"] 5 | [ext_resource type="Texture2D" uid="uid://cj1hg6vgo4div" path="res://materials/type_3_normal.png" id="3"] 6 | 7 | [resource] 8 | shader = ExtResource("1_5jj7q") 9 | shader_parameter/albedo = Color(1, 1, 1, 1) 10 | shader_parameter/texture_albedo = ExtResource("1") 11 | shader_parameter/texture_normal = ExtResource("3") 12 | shader_parameter/normal_scale = 1.0 13 | shader_parameter/uv_scale = Vector3(1, 1, 1) 14 | -------------------------------------------------------------------------------- /alchemy_mixer.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="AlchemyMixer" load_steps=4 format=3 uid="uid://c0772sqy2o6x0"] 2 | 3 | [ext_resource type="Script" uid="uid://m63epohqucb4" path="res://resource_types/alchemy_recipe.gd" id="1_q4n6d"] 4 | [ext_resource type="Script" uid="uid://ba1h6fmmb6281" path="res://resource_types/alchemy_mixer.gd" id="2_mk6wo"] 5 | 6 | [sub_resource type="Resource" id="Resource_3gy6f"] 7 | script = ExtResource("1_q4n6d") 8 | surface_type = 1 9 | added_type = 2 10 | result_type = 3 11 | metadata/_custom_type_script = "uid://m63epohqucb4" 12 | 13 | [resource] 14 | script = ExtResource("2_mk6wo") 15 | recipes = Array[ExtResource("1_q4n6d")]([SubResource("Resource_3gy6f")]) 16 | metadata/_custom_type_script = "uid://ba1h6fmmb6281" 17 | -------------------------------------------------------------------------------- /materials/type_2.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="ShaderMaterial" load_steps=5 format=3 uid="uid://d21rq7vvpjicu"] 2 | 3 | [ext_resource type="Shader" uid="uid://cxpng52j0gi8i" path="res://materials/type_2.gdshader" id="1"] 4 | [ext_resource type="Texture2D" uid="uid://c0wpqe02oqcy" path="res://materials/type_2_albedo.png" id="2"] 5 | [ext_resource type="Texture2D" uid="uid://bb3i2oxji1dwv" path="res://materials/type_2_normal.png" id="3"] 6 | [ext_resource type="Texture2D" uid="uid://djptkhgj8kagb" path="res://materials/type_2_parallax.png" id="5"] 7 | 8 | [resource] 9 | shader = ExtResource("1") 10 | shader_parameter/texture_albedo = ExtResource("2") 11 | shader_parameter/texture_normal = ExtResource("3") 12 | shader_parameter/texture_parallax = ExtResource("5") 13 | shader_parameter/uv_scale = Vector3(1, 1, 1) 14 | -------------------------------------------------------------------------------- /isolines/circle_isolines_brush.gd: -------------------------------------------------------------------------------- 1 | extends IsolinesBrush 2 | class_name CircleIsolinesBrush 3 | 4 | var type: int 5 | var radius: float 6 | 7 | func get_bounds() -> Rect2: 8 | return Rect2(center, Vector2.ZERO).grow(radius) 9 | 10 | func get_type(cell_pos: Vector2i) -> int: 11 | var pos := Vector2(cell_pos) 12 | var radius2 := radius * radius 13 | var distance2 := center.distance_squared_to(pos) 14 | 15 | if distance2 < radius2: 16 | return type 17 | 18 | return -1 19 | 20 | func get_edge(cell_pos: Vector2i, next_cell_pos: Vector2i) -> PackedVector2Array: 21 | var crossing := Geometry2D.segment_intersects_circle( 22 | Vector2(cell_pos), Vector2(next_cell_pos), 23 | center, radius, 24 | ) 25 | 26 | if crossing == -1.0: 27 | return [] 28 | 29 | var crossing_position := Vector2(cell_pos).lerp(next_cell_pos, crossing) 30 | var normal := (crossing_position - center).normalized() 31 | 32 | return [crossing_position, normal] 33 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /isolines/isolines_brush.gd: -------------------------------------------------------------------------------- 1 | @abstract 2 | extends RefCounted 3 | class_name IsolinesBrush 4 | ## Abstract class representing a brush to be used with an IsolinesGrid. 5 | 6 | ## The center point in grid coordinates of the brush. 7 | var center: Vector2 8 | 9 | ## Computes the boudning rect for this brush around its center. 10 | @abstract func get_bounds() -> Rect2 11 | 12 | ## Gets the type the brush will apply at cell_pos. Returns -1 if outside the brush shape. 13 | ## When a brush is applied, will be called exactly once for each cell determined by [method get_bounds]. 14 | @abstract func get_type(cell_pos: Vector2i) -> int 15 | 16 | ## Gets the edge position of the brush shape between two adjacent cells. 17 | ## Returns a tuple array, the first element is the edge's position, the second element is the normal. 18 | ## If an empty array is returned, the edge data will not be modified. 19 | @abstract func get_edge(cell_pos: Vector2i, next_cell_pos: Vector2i) -> PackedVector2Array 20 | -------------------------------------------------------------------------------- /scenes/main_menu/main_menu.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://qirclxxjy68g"] 2 | 3 | [ext_resource type="Script" uid="uid://ov7xhhme3xfq" path="res://scenes/main_menu/main_menu.gd" id="1_fsom3"] 4 | 5 | [node name="MainMenu" type="Control" unique_id=193112064] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | script = ExtResource("1_fsom3") 13 | 14 | [node name="GameButton" type="Button" parent="." unique_id=1792878324] 15 | layout_mode = 0 16 | offset_left = 264.0 17 | offset_top = 201.0 18 | offset_right = 525.6774 19 | offset_bottom = 279.0 20 | text = "Game Demo" 21 | 22 | [node name="SandboxButton" type="Button" parent="." unique_id=1426468926] 23 | layout_mode = 0 24 | offset_left = 554.0 25 | offset_top = 396.0 26 | offset_right = 875.16125 27 | offset_bottom = 472.0 28 | text = "Sandbox/Debug" 29 | 30 | [connection signal="pressed" from="GameButton" to="." method="_on_game_button_pressed"] 31 | [connection signal="pressed" from="SandboxButton" to="." method="_on_sandbox_button_pressed"] 32 | -------------------------------------------------------------------------------- /scenes/game_demo/grass.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bbbsr6e4ib76e" 6 | path="res://.godot/imported/grass.png-9fd54f9cf2bac7f3829a069906782ffe.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://scenes/game_demo/grass.png" 14 | dest_files=["res://.godot/imported/grass.png-9fd54f9cf2bac7f3829a069906782ffe.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/uastc_level=0 22 | compress/rdo_quality_loss=0.0 23 | compress/hdr_compression=1 24 | compress/normal_map=0 25 | compress/channel_pack=0 26 | mipmaps/generate=false 27 | mipmaps/limit=-1 28 | roughness/mode=0 29 | roughness/src_normal="" 30 | process/channel_remap/red=0 31 | process/channel_remap/green=1 32 | process/channel_remap/blue=2 33 | process/channel_remap/alpha=3 34 | process/fix_alpha_border=true 35 | process/premult_alpha=false 36 | process/normal_map_invert_y=false 37 | process/hdr_as_srgb=false 38 | process/hdr_clamp_exposure=false 39 | process/size_limit=0 40 | detect_3d/compress_to=1 41 | -------------------------------------------------------------------------------- /scenes/game_demo/player.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cclibbh5xnv1u" 6 | path="res://.godot/imported/player.png-154684cc0d696a0dafc0dfe80971587d.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://scenes/game_demo/player.png" 14 | dest_files=["res://.godot/imported/player.png-154684cc0d696a0dafc0dfe80971587d.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/uastc_level=0 22 | compress/rdo_quality_loss=0.0 23 | compress/hdr_compression=1 24 | compress/normal_map=0 25 | compress/channel_pack=0 26 | mipmaps/generate=false 27 | mipmaps/limit=-1 28 | roughness/mode=0 29 | roughness/src_normal="" 30 | process/channel_remap/red=0 31 | process/channel_remap/green=1 32 | process/channel_remap/blue=2 33 | process/channel_remap/alpha=3 34 | process/fix_alpha_border=true 35 | process/premult_alpha=false 36 | process/normal_map_invert_y=false 37 | process/hdr_as_srgb=false 38 | process/hdr_clamp_exposure=false 39 | process/size_limit=0 40 | detect_3d/compress_to=1 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Apples 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /materials/type_2_albedo.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://c0wpqe02oqcy" 6 | path.s3tc="res://.godot/imported/type_2_albedo.png-ccdd5c3380f1655e7fc5b31e3ccc798e.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://materials/type_2_albedo.png" 15 | dest_files=["res://.godot/imported/type_2_albedo.png-ccdd5c3380f1655e7fc5b31e3ccc798e.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=true 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=0 42 | -------------------------------------------------------------------------------- /materials/type_2_normal.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bb3i2oxji1dwv" 6 | path.s3tc="res://.godot/imported/type_2_normal.png-6f9f916b2512ab125e833f11df6d2471.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://materials/type_2_normal.png" 15 | dest_files=["res://.godot/imported/type_2_normal.png-6f9f916b2512ab125e833f11df6d2471.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=1 26 | compress/channel_pack=0 27 | mipmaps/generate=true 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=0 42 | -------------------------------------------------------------------------------- /materials/type_3_albedo.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://beymkqgit11oq" 6 | path.s3tc="res://.godot/imported/type_3_albedo.png-3379d3257c1f3ad73a23e4e4a618356d.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://materials/type_3_albedo.png" 15 | dest_files=["res://.godot/imported/type_3_albedo.png-3379d3257c1f3ad73a23e4e4a618356d.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=true 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=0 42 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cxhg4mt13px1u" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/uastc_level=0 22 | compress/rdo_quality_loss=0.0 23 | compress/hdr_compression=1 24 | compress/normal_map=0 25 | compress/channel_pack=0 26 | mipmaps/generate=false 27 | mipmaps/limit=-1 28 | roughness/mode=0 29 | roughness/src_normal="" 30 | process/channel_remap/red=0 31 | process/channel_remap/green=1 32 | process/channel_remap/blue=2 33 | process/channel_remap/alpha=3 34 | process/fix_alpha_border=true 35 | process/premult_alpha=false 36 | process/normal_map_invert_y=false 37 | process/hdr_as_srgb=false 38 | process/hdr_clamp_exposure=false 39 | process/size_limit=0 40 | detect_3d/compress_to=1 41 | svg/scale=1.0 42 | editor/scale_with_editor_scale=false 43 | editor/convert_colors_with_editor_theme=false 44 | -------------------------------------------------------------------------------- /materials/type_2_parallax.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://djptkhgj8kagb" 6 | path.s3tc="res://.godot/imported/type_2_parallax.png-f765506a482241276e1dd3ec367ce37a.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://materials/type_2_parallax.png" 15 | dest_files=["res://.godot/imported/type_2_parallax.png-f765506a482241276e1dd3ec367ce37a.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=0 26 | compress/channel_pack=0 27 | mipmaps/generate=true 28 | mipmaps/limit=-1 29 | roughness/mode=0 30 | roughness/src_normal="" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=0 42 | -------------------------------------------------------------------------------- /materials/type_3_normal.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cj1hg6vgo4div" 6 | path.s3tc="res://.godot/imported/type_3_normal.png-3739285d15317b17ea3adbc3f83760b3.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://materials/type_3_normal.png" 15 | dest_files=["res://.godot/imported/type_3_normal.png-3739285d15317b17ea3adbc3f83760b3.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/uastc_level=0 23 | compress/rdo_quality_loss=0.0 24 | compress/hdr_compression=1 25 | compress/normal_map=1 26 | compress/channel_pack=0 27 | mipmaps/generate=true 28 | mipmaps/limit=-1 29 | roughness/mode=1 30 | roughness/src_normal="res://materials/type_3_normal.png" 31 | process/channel_remap/red=0 32 | process/channel_remap/green=1 33 | process/channel_remap/blue=2 34 | process/channel_remap/alpha=3 35 | process/fix_alpha_border=true 36 | process/premult_alpha=false 37 | process/normal_map_invert_y=false 38 | process/hdr_as_srgb=false 39 | process/hdr_clamp_exposure=false 40 | process/size_limit=0 41 | detect_3d/compress_to=0 42 | -------------------------------------------------------------------------------- /materials/type_1.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="ShaderMaterial" load_steps=5 format=3 uid="uid://cqiy1ebiqw8o7"] 2 | 3 | [ext_resource type="Shader" uid="uid://x1nu5r2a6bgd" path="res://materials/type_1.gdshader" id="1_p3rma"] 4 | 5 | [sub_resource type="Gradient" id="Gradient_p3rma"] 6 | offsets = PackedFloat32Array(0, 0.355372, 0.575758, 0.606061, 0.652893, 1) 7 | colors = PackedColorArray(0, 0, 0, 1, 1, 0.05, 0, 1, 1, 0.766667, 0, 1, 1, 0.893333, 0.8, 1, 1, 0.685021, 0, 1, 0.337838, 0.0168919, 0, 1) 8 | 9 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_g8rtl"] 10 | noise_type = 2 11 | frequency = 0.097 12 | fractal_type = 0 13 | cellular_distance_function = 1 14 | domain_warp_enabled = true 15 | domain_warp_amplitude = 7.355 16 | domain_warp_frequency = 0.015 17 | 18 | [sub_resource type="NoiseTexture3D" id="NoiseTexture3D_jgthb"] 19 | width = 128 20 | height = 128 21 | depth = 16 22 | noise = SubResource("FastNoiseLite_g8rtl") 23 | color_ramp = SubResource("Gradient_p3rma") 24 | seamless = true 25 | seamless_blend_skirt = 0.2 26 | 27 | [resource] 28 | shader = ExtResource("1_p3rma") 29 | shader_parameter/noise_texture = SubResource("NoiseTexture3D_jgthb") 30 | shader_parameter/noise_rotation = Basis(0.94, -0.005, -0.045, -0.06, 0.97, -0.08, -0.075, 0.665, 0.175) 31 | -------------------------------------------------------------------------------- /scenes/game_demo/game_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const STEAM_PARTICLES = preload("uid://rm26oxjkpqt4") 4 | 5 | var _steam_particles_pool: Array[GPUParticles2D] 6 | 7 | @onready var grid: IsolinesGrid = $DualContourIsolinesGrid 8 | 9 | func _ready() -> void: 10 | pass 11 | 12 | func _on_player_clicky(where: Vector2, type: int) -> void: 13 | var brush := CircleIsolinesBrush.new() 14 | brush.center = grid.to_local(where) 15 | brush.type = type 16 | brush.radius = 4.0 17 | grid.apply_brush(brush) 18 | 19 | func _on_dual_contour_isolines_grid_brush_applied( 20 | cell_positions: PackedVector2Array, 21 | _previous_types: PackedInt32Array, 22 | current_types: PackedInt32Array, 23 | ) -> void: 24 | for i in cell_positions.size(): 25 | if current_types[i] != 3: 26 | continue 27 | 28 | var particles: GPUParticles2D 29 | if _steam_particles_pool.is_empty(): 30 | particles = STEAM_PARTICLES.instantiate() 31 | else: 32 | particles = _steam_particles_pool.pop_back() 33 | 34 | particles.restart() 35 | particles.position = to_local(grid.to_global(cell_positions[i])) 36 | add_child(particles) 37 | particles.emitting = true 38 | 39 | (func (): 40 | await particles.finished 41 | remove_child(particles) 42 | _steam_particles_pool.append(particles) 43 | ).call() 44 | -------------------------------------------------------------------------------- /materials/type_2.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | render_mode blend_mix; 3 | 4 | uniform sampler2D texture_albedo : repeat_enable; 5 | uniform sampler2D texture_normal : repeat_enable, hint_normal; 6 | uniform sampler2D texture_parallax : repeat_enable; 7 | uniform vec3 uv_scale; 8 | 9 | const float p_normal = 1.340000000; 10 | const float p_offset_scale = 0.260000000; 11 | const int p_iter = 24; 12 | 13 | void vertex() { 14 | UV=UV*uv_scale.xy; 15 | } 16 | 17 | void fragment() { 18 | vec2 base_uv = UV; 19 | 20 | int texture_size = textureSize(texture_albedo, 0).x; 21 | if (texture_size < 256) { 22 | base_uv = floor(float(texture_size)*base_uv+0.5)/float(texture_size); 23 | } 24 | 25 | vec4 parallax = vec4(0.0); 26 | { 27 | float t = sin(TIME) * PI / 4.0; 28 | mat2 normal_rot = mat2(vec2(cos(t), -sin(t)), vec2(sin(t), cos(t))); 29 | vec3 view_dir = normalize(vec3(normal_rot * vec2(0.0, 0.25), 1.0)); 30 | for (int j = 0; j < p_iter; j++) { 31 | float ratio = float(j) / float(p_iter); 32 | 33 | parallax += texture(texture_parallax, base_uv - mix(0.0, p_offset_scale, ratio) * view_dir.xy) * mix(1.0, 0.0, ratio); 34 | } 35 | 36 | parallax /= float(p_iter); 37 | } 38 | 39 | vec4 albedo_tex = texture(texture_albedo, base_uv); 40 | COLOR = albedo_tex; 41 | COLOR += parallax; 42 | COLOR.a = 1.0; 43 | NORMAL_MAP = texture(texture_normal, base_uv).rgb; 44 | NORMAL_MAP_DEPTH = p_normal; 45 | } 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /scenes/sandbox/sandbox.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://mm2je5e5d76r"] 2 | 3 | [ext_resource type="Script" uid="uid://7gd75y8t6kus" path="res://scenes/sandbox/sandbox.gd" id="1_ypj6x"] 4 | [ext_resource type="Script" uid="uid://chh0ri02qmrv4" path="res://isolines/dual_contour_isolines_grid.gd" id="2_44b12"] 5 | [ext_resource type="Resource" uid="uid://c0772sqy2o6x0" path="res://alchemy_mixer.tres" id="3_hwrqe"] 6 | [ext_resource type="Material" uid="uid://cqiy1ebiqw8o7" path="res://materials/type_1.tres" id="4_igx0j"] 7 | [ext_resource type="Material" uid="uid://d21rq7vvpjicu" path="res://materials/type_2.tres" id="5_tctsc"] 8 | [ext_resource type="Material" uid="uid://b7jhj1hgu31c8" path="res://materials/type_3.tres" id="6_ai2o8"] 9 | 10 | [node name="Sandbox2" type="Node2D" unique_id=2021601151] 11 | script = ExtResource("1_ypj6x") 12 | 13 | [node name="DualContourIsolinesGrid" type="Node2D" parent="." unique_id=1896332377] 14 | scale = Vector2(16, 16) 15 | script = ExtResource("2_44b12") 16 | mixer = ExtResource("3_hwrqe") 17 | default_material = ExtResource("4_igx0j") 18 | materials = Dictionary[int, Material]({ 19 | 1: ExtResource("4_igx0j"), 20 | 2: ExtResource("5_tctsc"), 21 | 3: ExtResource("6_ai2o8") 22 | }) 23 | debug_draw = true 24 | debug_draw_normals = true 25 | metadata/_custom_type_script = "uid://chh0ri02qmrv4" 26 | 27 | [node name="Camera2D" type="Camera2D" parent="." unique_id=1873588326] 28 | position = Vector2(64.22, 85.61) 29 | process_callback = 0 30 | 31 | [node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=672454845] 32 | 33 | [node name="TypeLabel" type="Label" parent="CanvasLayer" unique_id=1642149995] 34 | offset_right = 40.0 35 | offset_bottom = 23.0 36 | text = "Type 1" 37 | -------------------------------------------------------------------------------- /scenes/sandbox/sandbox.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var brush := CircleIsolinesBrush.new() 4 | 5 | var type: int = 1 6 | 7 | @onready var grid: IsolinesGrid = $DualContourIsolinesGrid 8 | @onready var type_label: Label = $CanvasLayer/TypeLabel 9 | 10 | func _unhandled_input(event: InputEvent) -> void: 11 | if event is InputEventMouseButton: 12 | if event.pressed: 13 | brush.center = grid.to_local(get_viewport_transform().affine_inverse() * event.position) 14 | brush.type = 0 if event.button_index == MOUSE_BUTTON_RIGHT else type 15 | brush.radius = 4.0 16 | grid.apply_brush(brush) 17 | queue_redraw() 18 | 19 | if event is InputEventMouseMotion: 20 | if event.button_mask & MOUSE_BUTTON_MASK_LEFT: 21 | var c = grid.to_local(get_viewport_transform().affine_inverse() * event.position) 22 | if brush.center != c: 23 | brush.center = c 24 | brush.type = type 25 | brush.radius = 4.0 26 | grid.apply_brush(brush) 27 | queue_redraw() 28 | if event.button_mask & MOUSE_BUTTON_MASK_RIGHT: 29 | var c = grid.to_local(get_viewport_transform().affine_inverse() * event.position) 30 | if brush.center != c: 31 | brush.center = c 32 | brush.type = 0 33 | brush.radius = 2.0 34 | grid.apply_brush(brush) 35 | queue_redraw() 36 | 37 | if event is InputEventKey: 38 | if event.pressed and event.keycode == KEY_1: 39 | type = 1 40 | type_label.text = "Type 1" 41 | if event.pressed and event.keycode == KEY_2: 42 | type = 2 43 | type_label.text = "Type 2" 44 | if event.pressed and event.keycode == KEY_3: 45 | type = 3 46 | type_label.text = "Type 3" 47 | 48 | func _draw() -> void: 49 | draw_circle(grid.to_global(brush.center), brush.radius * 16.0, Color.RED, false) 50 | -------------------------------------------------------------------------------- /scenes/game_demo/steam_particles.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://rm26oxjkpqt4"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_ltgbr"] 4 | offsets = PackedFloat32Array(0, 0.3398058, 0.6796116, 1) 5 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0.92718446, 1, 1, 1, 0.77719873, 1, 1, 1, 0) 6 | 7 | [sub_resource type="GradientTexture2D" id="GradientTexture2D_q42xf"] 8 | gradient = SubResource("Gradient_ltgbr") 9 | width = 32 10 | height = 32 11 | fill = 1 12 | fill_from = Vector2(0.5, 0.5) 13 | fill_to = Vector2(0.5, 0) 14 | 15 | [sub_resource type="Curve" id="Curve_ltgbr"] 16 | _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 17 | point_count = 2 18 | 19 | [sub_resource type="CurveTexture" id="CurveTexture_q42xf"] 20 | curve = SubResource("Curve_ltgbr") 21 | 22 | [sub_resource type="Gradient" id="Gradient_q42xf"] 23 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0.627451) 24 | 25 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_fsstd"] 26 | gradient = SubResource("Gradient_q42xf") 27 | 28 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_3ndmp"] 29 | particle_flag_disable_z = true 30 | emission_shape_scale = Vector3(16, 16, 1) 31 | emission_shape = 3 32 | emission_box_extents = Vector3(0.5, 0.5, 0.5) 33 | spread = 180.0 34 | initial_velocity_min = 1.0 35 | initial_velocity_max = 2.0 36 | gravity = Vector3(0, 0, 0) 37 | damping_min = 2.0 38 | damping_max = 2.0 39 | scale_min = 0.25 40 | scale_max = 0.25 41 | color_initial_ramp = SubResource("GradientTexture1D_fsstd") 42 | alpha_curve = SubResource("CurveTexture_q42xf") 43 | 44 | [node name="SteamParticles" type="GPUParticles2D" unique_id=113752460] 45 | emitting = false 46 | amount = 16 47 | texture = SubResource("GradientTexture2D_q42xf") 48 | one_shot = true 49 | explosiveness = 1.0 50 | process_material = SubResource("ParticleProcessMaterial_3ndmp") 51 | -------------------------------------------------------------------------------- /isolines/qef_solver.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name QEFSolver 3 | 4 | static var positions: PackedVector2Array 5 | static var normals: PackedVector2Array 6 | 7 | static func best_fit( 8 | left_edge_position: float, left_edge_normal: Vector2, 9 | bottom_edge_position: float, bottom_edge_normal: Vector2, 10 | right_edge_position: float, right_edge_normal: Vector2, 11 | top_edge_position: float, top_edge_normal: Vector2, 12 | ) -> Vector2: 13 | positions.clear() 14 | normals.clear() 15 | 16 | if left_edge_position != -1.0: 17 | positions.append(Vector2(0.0, left_edge_position)) 18 | normals.append(left_edge_normal) 19 | if bottom_edge_position != -1.0: 20 | positions.append(Vector2(bottom_edge_position, 0.0)) 21 | normals.append(bottom_edge_normal) 22 | if right_edge_position != -1.0: 23 | positions.append(Vector2(1.0, right_edge_position)) 24 | normals.append(right_edge_normal) 25 | if top_edge_position != -1.0: 26 | positions.append(Vector2(top_edge_position, 1.0)) 27 | normals.append(top_edge_normal) 28 | 29 | if positions.size() == 0: 30 | return Vector2(0.5, 0.5) 31 | 32 | if positions.size() == 1: 33 | var closest := Geometry2D.get_closest_point_to_segment_uncapped( 34 | Vector2(0.5, 0.5), 35 | positions[0], 36 | positions[0] + Vector2(normals[0].y, normals[0].x), 37 | ) 38 | return closest.clampf(0.0, 1.0) 39 | 40 | if positions.size() == 2: 41 | var intersects = Geometry2D.line_intersects_line(positions[0], normals[0].orthogonal(), positions[1], normals[1].orthogonal()) 42 | if intersects == null or not Rect2(Vector2.ZERO, Vector2.ONE).has_point(intersects): 43 | return (positions[0] + positions[1]) / 2.0 44 | return intersects 45 | 46 | if positions.size() == 3: 47 | return (positions[0] + positions[1] + positions[2]) / 3.0 48 | 49 | if positions.size() == 4: 50 | return (positions[0] + positions[1] + positions[2] + positions[3]) / 4.0 51 | 52 | return Vector2(0.5, 0.5) 53 | -------------------------------------------------------------------------------- /scenes/game_demo/player.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | signal clicky(where: Vector2, type: int) 4 | 5 | const SPEED = 200.0 6 | 7 | @onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D 8 | @onready var foot_area_2d: Area2D = $FootArea2D 9 | @onready var smoke_particles: GPUParticles2D = $SmokeParticles 10 | @onready var ice_particles: GPUParticles2D = $IceParticles 11 | 12 | var _foot_surface_types: Dictionary[int, int] 13 | 14 | func _physics_process(_delta: float) -> void: 15 | var max_speed := SPEED / 2.0 if 3 in _foot_surface_types else SPEED 16 | 17 | var direction := Input.get_vector("left", "right", "up", "down") 18 | var desired_velocity := Vector2.ZERO 19 | if direction: 20 | desired_velocity = direction * max_speed 21 | 22 | var accel := 300.0 if 2 in _foot_surface_types else 5000.0 23 | 24 | velocity = velocity.move_toward(desired_velocity, _delta * accel) 25 | 26 | if velocity.x < 0.0: 27 | animated_sprite_2d.flip_h = true 28 | elif velocity.x > 0.0: 29 | animated_sprite_2d.flip_h = false 30 | 31 | move_and_slide() 32 | 33 | if velocity: 34 | animated_sprite_2d.play("walk") 35 | else: 36 | animated_sprite_2d.play("idle") 37 | 38 | smoke_particles.emitting = 1 in _foot_surface_types 39 | ice_particles.emitting = 2 in _foot_surface_types 40 | 41 | func _unhandled_input(event: InputEvent) -> void: 42 | if event is InputEventMouseButton: 43 | if event.pressed: 44 | match event.button_index: 45 | MOUSE_BUTTON_LEFT: 46 | clicky.emit(get_global_mouse_position(), 1) 47 | MOUSE_BUTTON_RIGHT: 48 | clicky.emit(get_global_mouse_position(), 2) 49 | 50 | func _on_foot_area_2d_area_shape_entered(area_rid: RID, _area: Area2D, _area_shape_index: int, _local_shape_index: int) -> void: 51 | var type := IsolinesGrid.get_area_type(area_rid) 52 | if type != -1: 53 | var c: int = _foot_surface_types.get(type, 0) 54 | _foot_surface_types[type] = c + 1 55 | 56 | func _on_foot_area_2d_area_shape_exited(area_rid: RID, _area: Area2D, _area_shape_index: int, _local_shape_index: int) -> void: 57 | var type := IsolinesGrid.get_area_type(area_rid) 58 | if type != -1: 59 | _foot_surface_types[type] -= 1 60 | if _foot_surface_types[type] == 0: 61 | _foot_surface_types.erase(type) 62 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Godot Dual Contouring Demo" 14 | run/main_scene="uid://qirclxxjy68g" 15 | config/features=PackedStringArray("4.6", "Mobile") 16 | config/icon="res://icon.svg" 17 | 18 | [display] 19 | 20 | window/stretch/mode="canvas_items" 21 | 22 | [input] 23 | 24 | left={ 25 | "deadzone": 0.2, 26 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) 27 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) 28 | ] 29 | } 30 | right={ 31 | "deadzone": 0.2, 32 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) 33 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) 34 | ] 35 | } 36 | up={ 37 | "deadzone": 0.2, 38 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) 39 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) 40 | ] 41 | } 42 | down={ 43 | "deadzone": 0.2, 44 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) 45 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) 46 | ] 47 | } 48 | 49 | [physics] 50 | 51 | common/physics_interpolation=true 52 | 53 | [rendering] 54 | 55 | renderer/rendering_method="mobile" 56 | -------------------------------------------------------------------------------- /scenes/game_demo/player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=19 format=3 uid="uid://csv4vhnkjn0jq"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cclibbh5xnv1u" path="res://scenes/game_demo/player.png" id="1_cywia"] 4 | [ext_resource type="Script" uid="uid://ckqedx6mav8lm" path="res://scenes/game_demo/player.gd" id="1_rljv4"] 5 | 6 | [sub_resource type="CircleShape2D" id="CircleShape2D_33sph"] 7 | radius = 8.0 8 | 9 | [sub_resource type="AtlasTexture" id="AtlasTexture_hio8u"] 10 | atlas = ExtResource("1_cywia") 11 | region = Rect2(0, 0, 32, 32) 12 | 13 | [sub_resource type="AtlasTexture" id="AtlasTexture_nyeo5"] 14 | atlas = ExtResource("1_cywia") 15 | region = Rect2(0, 0, 32, 32) 16 | 17 | [sub_resource type="AtlasTexture" id="AtlasTexture_qeotj"] 18 | atlas = ExtResource("1_cywia") 19 | region = Rect2(32, 0, 32, 32) 20 | 21 | [sub_resource type="AtlasTexture" id="AtlasTexture_o27md"] 22 | atlas = ExtResource("1_cywia") 23 | region = Rect2(64, 0, 32, 32) 24 | 25 | [sub_resource type="AtlasTexture" id="AtlasTexture_esbyl"] 26 | atlas = ExtResource("1_cywia") 27 | region = Rect2(96, 0, 32, 32) 28 | 29 | [sub_resource type="SpriteFrames" id="SpriteFrames_3ndmp"] 30 | animations = [{ 31 | "frames": [{ 32 | "duration": 1.0, 33 | "texture": SubResource("AtlasTexture_hio8u") 34 | }], 35 | "loop": true, 36 | "name": &"idle", 37 | "speed": 5.0 38 | }, { 39 | "frames": [{ 40 | "duration": 1.0, 41 | "texture": SubResource("AtlasTexture_nyeo5") 42 | }, { 43 | "duration": 1.0, 44 | "texture": SubResource("AtlasTexture_qeotj") 45 | }, { 46 | "duration": 1.0, 47 | "texture": SubResource("AtlasTexture_o27md") 48 | }, { 49 | "duration": 1.0, 50 | "texture": SubResource("AtlasTexture_esbyl") 51 | }], 52 | "loop": true, 53 | "name": &"walk", 54 | "speed": 5.0 55 | }] 56 | 57 | [sub_resource type="CircleShape2D" id="CircleShape2D_tpo70"] 58 | radius = 1.4142135 59 | 60 | [sub_resource type="Curve" id="Curve_rljv4"] 61 | _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), -2.116139, 0.0, 0, 0] 62 | point_count = 2 63 | 64 | [sub_resource type="CurveTexture" id="CurveTexture_w5hi0"] 65 | curve = SubResource("Curve_rljv4") 66 | 67 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_rljv4"] 68 | particle_flag_disable_z = true 69 | emission_shape_scale = Vector3(1, 0.25, 1) 70 | emission_shape = 1 71 | emission_sphere_radius = 6.0 72 | direction = Vector3(0, -1, 0) 73 | initial_velocity_min = 44.059998 74 | initial_velocity_max = 66.08 75 | gravity = Vector3(0, 0, 0) 76 | damping_min = 100.00001 77 | damping_max = 100.00001 78 | scale_min = 3.0 79 | scale_max = 3.0 80 | color = Color(0.17, 0, 0, 1) 81 | alpha_curve = SubResource("CurveTexture_w5hi0") 82 | 83 | [sub_resource type="Curve" id="Curve_w5hi0"] 84 | _data = [Vector2(0, 0.82022476), 0.0, 0.0, 0, 0, Vector2(1, 0), -1.4332106, 0.0, 0, 0] 85 | point_count = 2 86 | 87 | [sub_resource type="CurveTexture" id="CurveTexture_a7cqi"] 88 | curve = SubResource("Curve_w5hi0") 89 | 90 | [sub_resource type="Curve" id="Curve_mtrsa"] 91 | _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 92 | point_count = 2 93 | 94 | [sub_resource type="CurveTexture" id="CurveTexture_1g2gw"] 95 | curve = SubResource("Curve_mtrsa") 96 | 97 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_w5hi0"] 98 | particle_flag_disable_z = true 99 | emission_shape = 3 100 | emission_box_extents = Vector3(12, 12, 1) 101 | angular_velocity_min = -100.000015 102 | angular_velocity_max = 99.999985 103 | angular_velocity_curve = SubResource("CurveTexture_1g2gw") 104 | gravity = Vector3(0, 1, 0) 105 | scale_min = 3.0 106 | scale_max = 3.0 107 | alpha_curve = SubResource("CurveTexture_a7cqi") 108 | 109 | [node name="Player" type="CharacterBody2D" unique_id=1959963127] 110 | texture_filter = 1 111 | motion_mode = 1 112 | script = ExtResource("1_rljv4") 113 | 114 | [node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=848411690] 115 | position = Vector2(0, 1) 116 | shape = SubResource("CircleShape2D_33sph") 117 | 118 | [node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=75749802] 119 | sprite_frames = SubResource("SpriteFrames_3ndmp") 120 | animation = &"walk" 121 | autoplay = "idle" 122 | 123 | [node name="FootArea2D" type="Area2D" parent="." unique_id=681067833] 124 | 125 | [node name="CollisionShape2D" type="CollisionShape2D" parent="FootArea2D" unique_id=1499190253] 126 | position = Vector2(0, 9) 127 | shape = SubResource("CircleShape2D_tpo70") 128 | 129 | [node name="SmokeParticles" type="GPUParticles2D" parent="." unique_id=1999690023] 130 | position = Vector2(0, 9) 131 | emitting = false 132 | amount = 32 133 | lifetime = 0.5 134 | explosiveness = 0.1 135 | process_material = SubResource("ParticleProcessMaterial_rljv4") 136 | 137 | [node name="IceParticles" type="GPUParticles2D" parent="." unique_id=382476454] 138 | physics_interpolation_mode = 2 139 | process_material = SubResource("ParticleProcessMaterial_w5hi0") 140 | 141 | [connection signal="area_shape_entered" from="FootArea2D" to="." method="_on_foot_area_2d_area_shape_entered"] 142 | [connection signal="area_shape_exited" from="FootArea2D" to="." method="_on_foot_area_2d_area_shape_exited"] 143 | -------------------------------------------------------------------------------- /isolines/isolines_grid.gd: -------------------------------------------------------------------------------- 1 | @abstract 2 | @tool 3 | extends Node2D 4 | class_name IsolinesGrid 5 | ## Implements a simple terrain grid designed for contouring. 6 | ## Each grid cell has a single terrain type assigned to it. 7 | ## Cells are modified by applying brushes. 8 | ## 9 | ## The terrain type 0 is special, it use used to indicate empty cells and brushes that erase. 10 | ## 11 | ## Grid data is broken up into chunks, determined by the implementation's chunk size. 12 | ## 13 | ## When handling area RIDs for collision detection, 14 | ## use [method Isolines.get_area_grid] and [method IsolinesGrid.get_area_type] to detect area types. 15 | 16 | ## Emitted when the chunk at chunk_i is updated and contains data (but may not contain surfaces). 17 | ## Will be deferred after the call to apply_brush. 18 | signal chunk_updated(chunk_i: Vector2) 19 | 20 | ## Emitted when the chunk at chunk_i is removed because it contains no data. 21 | ## Will be deferred after the call to apply_brush. 22 | signal chunk_removed(chunk_i: Vector2) 23 | 24 | ## Emitted when a brush is applied. 25 | ## Will be deferred after the call to apply_brush. 26 | @warning_ignore("unused_signal") 27 | signal brush_applied( 28 | cell_positions: PackedVector2Array, 29 | previous_types: PackedInt32Array, 30 | current_types: PackedInt32Array 31 | ) 32 | 33 | static var _area_rid_to_chunk: Dictionary[RID, ChunkInstance] 34 | 35 | ## The type mixer to use when applying brushes. If null, brushes will simply overwrite cells. 36 | @export var mixer: IsolinesMixer 37 | ## The material to use when a type has no specific material. 38 | @export var default_material: Material 39 | ## Maps types to materials. 40 | @export var materials: Dictionary[int, Material] 41 | ## If true, generated meshes will have a UV channel that matches cell coordinates. 42 | @export var generate_uvs: bool = true 43 | ## Scale applied to generated UVs. 44 | @export var uv_scale: Vector2 = Vector2.ONE / 16.0 45 | ## If true, generates collision areas. 46 | @export var collision_enabled: bool = true 47 | ## Collision layer to use for all areas. 48 | @export_flags_2d_physics var collision_layer: int = 1 49 | ## Collision mask to use for all areas. 50 | @export_flags_2d_physics var collision_mask: int = 1 51 | ## If true, draws a wireframe of the grid and surface polygons. 52 | @export var debug_draw: bool = false 53 | ## If true and debug_draw is true, draws surface edge normals. 54 | @export var debug_draw_normals: bool = false 55 | 56 | var _chunk_instances: Dictionary[Vector2i, ChunkInstance] 57 | 58 | ## Gets the IsolinesGrid responsible for the area. 59 | ## If the area is not owned by an IsolinesGrid, returns null. 60 | static func get_area_grid(area_rid: RID) -> IsolinesGrid: 61 | if area_rid in _area_rid_to_chunk: 62 | return _area_rid_to_chunk[area_rid].get_parent() 63 | return null 64 | 65 | ## Gets the surface type of an IsolinesGrid area. 66 | ## If the area is not owned by an IsolinesGrid, returns -1. 67 | static func get_area_type(area_rid: RID) -> int: 68 | if area_rid in _area_rid_to_chunk: 69 | return _area_rid_to_chunk[area_rid].area_types.get(area_rid, -1) 70 | return -1 71 | 72 | ## Gets the type of a specific cell. Returns 0 if a cell's chunk does not exist. 73 | @abstract func get_cell_type(pos: Vector2i) -> int 74 | ## Applies the brush to the grid. Ensure the brush's [member IsolinesBrush.center] is set. 75 | @abstract func apply_brush(brush: IsolinesBrush) -> void 76 | ## Gets the size of a chunk. 77 | @abstract func get_chunk_size() -> Vector2i 78 | ## Gets a list of the identifiers of all loaded chunks. Elements should be interpreted as Vector2i. 79 | @abstract func get_chunks() -> PackedVector2Array 80 | ## Gets the number of surfaces of all types in a chunk. 81 | @abstract func get_chunk_surface_count(chunk_i: Vector2i) -> int 82 | ## Gets the type of a chunk surface. 83 | @abstract func get_chunk_surface_type(chunk_i: Vector2i, surface_i: int) -> int 84 | ## Gets the polygons of a chunk surface. May not always be convex. 85 | @abstract func get_chunk_surface_polygons(chunk_i: Vector2i, surface_i: int) -> Array # Array[PackedVector2Array] 86 | 87 | func _init() -> void: 88 | # Connect to this grid's own chunk signals to handle rendering and physics. 89 | chunk_updated.connect(_on_chunk_updated) 90 | chunk_removed.connect(_on_chunk_removed) 91 | 92 | func _enter_tree() -> void: 93 | # Regenerate chunk nodes, because chunk data may already exist. 94 | for chunk_i in get_chunks(): 95 | _on_chunk_updated(chunk_i) 96 | 97 | func _exit_tree() -> void: 98 | for chunk in _chunk_instances.values(): 99 | chunk.queue_free() 100 | _chunk_instances.clear() 101 | 102 | func _on_chunk_updated(chunk_i: Vector2i) -> void: 103 | # If not in the tree, no chunk nodes will be instantiated. 104 | # Will be called again next time this grid is added to the tree. 105 | if not is_inside_tree(): 106 | return 107 | 108 | var chunk_info: ChunkInstance = _chunk_instances.get(chunk_i, null) 109 | 110 | if not chunk_info: 111 | chunk_info = ChunkInstance.new() 112 | chunk_info.position = Vector2(get_chunk_size() * chunk_i) 113 | chunk_info.chunk_i = chunk_i 114 | _chunk_instances[chunk_i] = chunk_info 115 | add_child(chunk_info, false, Node.INTERNAL_MODE_BACK) 116 | else: 117 | chunk_info.rebuild() 118 | 119 | func _on_chunk_removed(chunk_i: Vector2i) -> void: 120 | if chunk_i in _chunk_instances: 121 | _chunk_instances[chunk_i].queue_free() 122 | _chunk_instances.erase(chunk_i) 123 | 124 | ## Implements per-chunk rendering and physics. 125 | ## Not intended to be used directly. Relies on being a child of an IsolinesGrid. 126 | class ChunkInstance extends Node2D: 127 | ## This chunk's identifier. 128 | var chunk_i: Vector2i 129 | 130 | ## Maps types to canvas item RIDs. Each type has only one canvas item for all surfaces in this chunk. 131 | var type_canvas_items: Dictionary[int, RID] 132 | 133 | ## Maps types to meshes. Each type has only one mesh for all surfaces in this chunk. 134 | ## Each canvas item should have a mesh. 135 | var type_meshes: Dictionary[int, ArrayMesh] 136 | 137 | ## Maps types to physics areas. Each type has only one area for all surfaces in this chunk. 138 | var type_areas: Dictionary[int, RID] 139 | 140 | ## Maps area RIDs to surface types. 141 | var area_types: Dictionary[RID, int] 142 | 143 | ## Maps types to debug canvas item RIDs. 144 | var type_debug_canvas_items: Dictionary[int, RID] 145 | 146 | func _enter_tree() -> void: 147 | rebuild() 148 | 149 | func _exit_tree() -> void: 150 | for rid in type_canvas_items.values(): 151 | RenderingServer.free_rid(rid) 152 | type_canvas_items.clear() 153 | 154 | for rid in type_debug_canvas_items.values(): 155 | RenderingServer.free_rid(rid) 156 | type_debug_canvas_items.clear() 157 | 158 | for area_rid: RID in type_areas.values(): 159 | for i in range(PhysicsServer2D.area_get_shape_count(area_rid) - 1, -1, -1): 160 | var shape_rid := PhysicsServer2D.area_get_shape(area_rid, i) 161 | PhysicsServer2D.area_remove_shape(area_rid, i) 162 | PhysicsServer2D.free_rid(shape_rid) 163 | IsolinesGrid._area_rid_to_chunk.erase(area_rid) 164 | PhysicsServer2D.free_rid(area_rid) 165 | type_areas.clear() 166 | area_types.clear() 167 | 168 | func rebuild() -> void: 169 | var grid: IsolinesGrid = get_parent() 170 | 171 | var chunk_size := Vector2(grid.get_chunk_size()) 172 | 173 | # Keep track of unused types so we can hide their canvas items later. 174 | var unused_types := type_canvas_items.keys() 175 | 176 | # Process surfaces, adding their meshes to the matching canvas item. 177 | for surface_i in grid.get_chunk_surface_count(chunk_i): 178 | var type := grid.get_chunk_surface_type(chunk_i, surface_i) 179 | var polygons := grid.get_chunk_surface_polygons(chunk_i, surface_i) 180 | 181 | unused_types.erase(type) 182 | 183 | var canvas_item: RID = type_canvas_items.get(type, RID()) 184 | var mesh: ArrayMesh = type_meshes.get(type, null) 185 | 186 | if not canvas_item: 187 | canvas_item = RenderingServer.canvas_item_create() 188 | type_canvas_items[type] = canvas_item 189 | RenderingServer.canvas_item_set_parent(canvas_item, get_canvas_item()) 190 | 191 | var type_material: Material = grid.materials.get(type, grid.default_material) 192 | if type_material: 193 | RenderingServer.canvas_item_set_material(canvas_item, type_material) 194 | else: 195 | push_error("IsolinesGrid: No material found! (Please set default_material.)") 196 | 197 | # Ensure that we have a mesh for the canvas item. 198 | if not mesh: 199 | mesh = ArrayMesh.new() 200 | type_meshes[type] = mesh 201 | 202 | RenderingServer.canvas_item_clear(canvas_item) 203 | 204 | assert(mesh) 205 | 206 | # Generate visual mesh. 207 | 208 | var vertex_array: PackedVector2Array 209 | var index_array: PackedInt32Array 210 | 211 | for polygon in polygons: 212 | var indices := Geometry2D.triangulate_polygon(polygon) 213 | var index_array_start := index_array.size() 214 | var index_offset := vertex_array.size() 215 | vertex_array.append_array(polygon) 216 | index_array.append_array(indices) 217 | for i in range(index_array_start, index_array.size()): 218 | index_array[i] += index_offset 219 | 220 | var mesh_arrays: Array 221 | mesh_arrays.resize(Mesh.ARRAY_MAX) 222 | mesh_arrays[Mesh.ARRAY_VERTEX] = vertex_array 223 | mesh_arrays[Mesh.ARRAY_INDEX] = index_array 224 | 225 | if grid.generate_uvs: 226 | var uv_array: PackedVector2Array 227 | uv_array.resize(vertex_array.size()) 228 | for i: int in uv_array.size(): 229 | uv_array[i] = grid.uv_scale * (chunk_size * Vector2(chunk_i) + vertex_array[i]) 230 | mesh_arrays[Mesh.ARRAY_TEX_UV] = uv_array 231 | 232 | mesh.clear_surfaces() 233 | mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_arrays) 234 | 235 | RenderingServer.canvas_item_add_mesh(canvas_item, mesh.get_rid()) 236 | 237 | # Debug drawing. 238 | 239 | if grid.debug_draw and OS.is_debug_build(): 240 | var dbg_canvas_item: RID = type_debug_canvas_items.get(type, RID()) 241 | if not dbg_canvas_item: 242 | dbg_canvas_item = RenderingServer.canvas_item_create() 243 | type_debug_canvas_items[type] = dbg_canvas_item 244 | RenderingServer.canvas_item_set_parent(dbg_canvas_item, canvas_item) 245 | RenderingServer.canvas_item_set_use_parent_material(dbg_canvas_item, false) 246 | RenderingServer.canvas_item_set_z_index(dbg_canvas_item, 50) 247 | RenderingServer.canvas_item_clear(dbg_canvas_item) 248 | for y in grid.get_chunk_size().y: 249 | for x in grid.get_chunk_size().x: 250 | var c: Color = [Color.WHITE, Color.DARK_CYAN, Color.RED, Color.MAGENTA][grid.get_cell_type(Vector2(chunk_i * grid.get_chunk_size()) + Vector2(x, y))] 251 | RenderingServer.canvas_item_add_circle(dbg_canvas_item, Vector2(x, y), 0.05, c) 252 | for polygon in polygons: 253 | var points = PackedVector2Array() 254 | for i in polygon.size(): 255 | points.append(polygon[i]) 256 | points.append(polygon[(i+1) % polygon.size()]) 257 | var colors = PackedColorArray() 258 | @warning_ignore("integer_division") 259 | colors.resize(points.size() / 2) 260 | colors.fill(Color.WHITE) 261 | RenderingServer.canvas_item_add_multiline(dbg_canvas_item, points, colors) 262 | if grid.debug_draw_normals: 263 | var ggrid := grid as DualContourIsolinesGrid 264 | var crossings := ggrid.get_chunk_edge_crossings(chunk_i) 265 | for i in range(0, crossings.size(), 2): 266 | RenderingServer.canvas_item_add_line( 267 | dbg_canvas_item, 268 | crossings[i], crossings[i] + crossings[i + 1], 269 | Color.MAGENTA 270 | ) 271 | 272 | # Canvas item might have previously been unused and hidden. 273 | RenderingServer.canvas_item_set_visible(canvas_item, true) 274 | 275 | # Hide unused canvas items and clear their meshes. 276 | for type in unused_types: 277 | var canvas_item: RID = type_canvas_items[type] 278 | RenderingServer.canvas_item_set_visible(canvas_item, false) 279 | 280 | var mesh: ArrayMesh = type_meshes.get(type, null) 281 | if mesh: 282 | mesh.clear_surfaces() 283 | 284 | # Skip physics generation. 285 | 286 | if not grid.collision_enabled: 287 | return 288 | 289 | # Physics. 290 | 291 | var global_xform := grid.global_transform.translated_local(chunk_size * Vector2(chunk_i)) 292 | 293 | var shape_pool: Array[RID] 294 | 295 | # Clear all areas, but save shapes in a temporary pool for reuse. 296 | for type in type_areas: 297 | var area_rid := type_areas[type] 298 | for i in PhysicsServer2D.area_get_shape_count(area_rid): 299 | shape_pool.append(PhysicsServer2D.area_get_shape(area_rid, i)) 300 | PhysicsServer2D.area_clear_shapes(area_rid) 301 | 302 | # Process surfaces, adding their polygons to the matching area. 303 | for surface_i in grid.get_chunk_surface_count(chunk_i): 304 | var type := grid.get_chunk_surface_type(chunk_i, surface_i) 305 | var polygons := grid.get_chunk_surface_polygons(chunk_i, surface_i) 306 | 307 | var area_rid: RID = type_areas.get(type, RID()) 308 | if not area_rid: 309 | area_rid = PhysicsServer2D.area_create() 310 | type_areas[type] = area_rid 311 | area_types[area_rid] = type 312 | IsolinesGrid._area_rid_to_chunk[area_rid] = self 313 | 314 | PhysicsServer2D.area_set_space(area_rid, grid.get_viewport().world_2d.space) 315 | PhysicsServer2D.area_set_transform(area_rid, global_xform) 316 | PhysicsServer2D.area_set_collision_layer(area_rid, grid.collision_layer) 317 | PhysicsServer2D.area_set_collision_mask(area_rid, grid.collision_mask) 318 | PhysicsServer2D.area_set_monitorable(area_rid, true) 319 | 320 | # This is where an Area2D would implement entered/exited signals. 321 | #PhysicsServer2D.area_set_monitor_callback(area_rid, func (status: int, body_rid: RID, instance_id: int, body_shape_idx: int, self_shape_idx: int): 322 | #print("area_monitor_callback(%s, %s, %s, %s, %s)" % [status, body_rid, instance_id, body_shape_idx, self_shape_idx]) 323 | #) 324 | 325 | for i in polygons.size(): 326 | var polygon: PackedVector2Array = polygons[i] 327 | var shape_rid: RID 328 | if not shape_pool.is_empty(): 329 | shape_rid = shape_pool.pop_back() 330 | else: 331 | shape_rid = PhysicsServer2D.convex_polygon_shape_create() 332 | PhysicsServer2D.shape_set_data(shape_rid, polygon) 333 | PhysicsServer2D.area_add_shape(area_rid, shape_rid) 334 | 335 | # Must free unused shapes, as otherwise they will be leaked. 336 | for rid in shape_pool: 337 | PhysicsServer2D.free_rid(rid) 338 | -------------------------------------------------------------------------------- /isolines/dual_contour_isolines_grid.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends IsolinesGrid 3 | class_name DualContourIsolinesGrid 4 | ## Implements dual contouring for an isolines grid. 5 | ## 6 | ## References: 7 | ## https://catlikecoding.com/unity/tutorials/marching-squares-series/ 8 | ## https://www.mattkeeter.com/projects/contours/ (https://web.archive.org/web/20251116110255/https://www.mattkeeter.com/projects/contours/) 9 | ## https://www.boristhebrave.com/2018/04/15/dual-contouring-tutorial/ (https://web.archive.org/web/20251116110408/https://www.boristhebrave.com/2018/04/15/dual-contouring-tutorial/) 10 | 11 | ## The size of each chunk. Benchmarks required. 12 | const CHUNK_SIZE = Vector2i(16, 16) 13 | 14 | ## If true, cell polygons will be merged together into larger convex polygons. 15 | ## Requires https://github.com/godotengine/godot/pull/104407. 16 | @export var optimize_convex_polygons: bool = true: 17 | set(v): 18 | optimize_convex_polygons = v 19 | update_configuration_warnings() 20 | 21 | var _chunks: Dictionary[Vector2i, Chunk] 22 | var _first_empty_chunk: Chunk 23 | 24 | func _get_configuration_warnings() -> PackedStringArray: 25 | if optimize_convex_polygons: 26 | return ["optimize_convex_polygons is only useful when building godot with https://github.com/godotengine/godot/pull/104407"] 27 | return [] 28 | 29 | func get_cell_type(pos: Vector2i) -> int: 30 | var chunk_i := Vector2i((Vector2(pos) / Vector2(CHUNK_SIZE)).floor()) 31 | var chunk: Chunk = _chunks.get(chunk_i, null) 32 | 33 | if chunk == null: 34 | return 0 35 | 36 | var relative_pos := pos - chunk_i * CHUNK_SIZE 37 | assert(relative_pos.x >= 0 and relative_pos.x < CHUNK_SIZE.x) 38 | assert(relative_pos.y >= 0 and relative_pos.y < CHUNK_SIZE.y) 39 | 40 | return chunk.cell_types[relative_pos.y * CHUNK_SIZE.x + relative_pos.x] 41 | 42 | func apply_brush(brush: IsolinesBrush) -> void: 43 | var bounds := brush.get_bounds().abs() 44 | 45 | # Brush region must be expanded one cell on negative sides, so edge data can be computed. 46 | var cell_region := Rect2i() 47 | cell_region.position = Vector2i((bounds.position - Vector2.ONE).floor()) 48 | cell_region.end = Vector2i(bounds.end.ceil()) + Vector2i.ONE 49 | 50 | var chunks_region := Rect2i() 51 | chunks_region.position = Vector2i((Vector2(cell_region.position) / Vector2(CHUNK_SIZE)).floor()) 52 | chunks_region.end = Vector2i((Vector2(cell_region.end) / Vector2(CHUNK_SIZE)).floor()) + Vector2i.ONE 53 | 54 | var affected_cell_positions := PackedVector2Array() 55 | var affected_cell_previous_types := PackedInt32Array() 56 | var affected_cell_current_types := PackedInt32Array() 57 | 58 | # List of chunks affected by the brush. 59 | var brush_chunks: Array[Chunk] 60 | 61 | # Ensure chunks exist and gather brush_chunks. 62 | for chunk_y: int in chunks_region.size.y: 63 | for chunk_x: int in chunks_region.size.x: 64 | var chunk_i := chunks_region.position + Vector2i(chunk_x, chunk_y) 65 | var chunk: Chunk = _chunks.get(chunk_i, null) 66 | if chunk == null: 67 | if _first_empty_chunk != null: 68 | chunk = _first_empty_chunk 69 | _first_empty_chunk = chunk.next_empty_chunk 70 | chunk.clear() 71 | else: 72 | chunk = Chunk.new() 73 | chunk.chunk_i = chunk_i 74 | _chunks[chunk_i] = chunk 75 | brush_chunks.append(chunk) 76 | 77 | # Apply only types first (including mixer results). 78 | for chunk: Chunk in brush_chunks: 79 | var chunk_rect := Rect2i(chunk.chunk_i * CHUNK_SIZE, CHUNK_SIZE) 80 | var chunk_brush_rect := chunk_rect.intersection(cell_region) 81 | 82 | for y: int in chunk_brush_rect.size.y: 83 | for x: int in chunk_brush_rect.size.x: 84 | var absolute_pos := chunk_brush_rect.position + Vector2i(x, y) 85 | var relative_pos := absolute_pos - chunk_rect.position 86 | var i := relative_pos.y * CHUNK_SIZE.x + relative_pos.x 87 | 88 | var brush_type := brush.get_type(absolute_pos) 89 | if brush_type != -1: 90 | var surface_type := chunk.cell_types[i] 91 | chunk.cell_types[i] = ( 92 | brush_type if not mixer 93 | else mixer.mix_types(chunk.cell_types[i], brush_type) 94 | ) 95 | affected_cell_positions.append(Vector2(absolute_pos)) 96 | affected_cell_previous_types.append(surface_type) 97 | affected_cell_current_types.append(chunk.cell_types[i]) 98 | 99 | # Apply edges. 100 | for chunk: Chunk in brush_chunks: 101 | var chunk_rect := Rect2i(chunk.chunk_i * CHUNK_SIZE, CHUNK_SIZE) 102 | var chunk_brush_rect := chunk_rect.intersection(cell_region) 103 | 104 | var neighbor_y: Chunk = _chunks.get(chunk.chunk_i + Vector2i(0, 1), null) 105 | var neighbor_x: Chunk = _chunks.get(chunk.chunk_i + Vector2i(1, 0), null) 106 | 107 | for y: int in chunk_brush_rect.size.y: 108 | for x: int in chunk_brush_rect.size.x: 109 | var absolute_pos := chunk_brush_rect.position + Vector2i(x, y) 110 | var relative_pos := absolute_pos - chunk_rect.position 111 | var i := relative_pos.y * CHUNK_SIZE.x + relative_pos.x 112 | 113 | # Vertical edges. 114 | 115 | var next_y_pos := absolute_pos + Vector2i(0, 1) 116 | var next_y := \ 117 | chunk.cell_types[i + CHUNK_SIZE.x] if relative_pos.y + 1 < CHUNK_SIZE.y \ 118 | else neighbor_y.cell_types[relative_pos.x] if neighbor_y \ 119 | else 0 120 | 121 | # If the edge is between two cells with the same type, 122 | # it's an internal edge and should not be processed. 123 | if chunk.cell_types[i] == next_y: 124 | chunk.cell_vertical_edge_offsets[i] = -1.0 125 | else: 126 | var edge := brush.get_edge(absolute_pos, next_y_pos) 127 | if edge: 128 | chunk.cell_vertical_edge_offsets[i] = edge[0].y - float(absolute_pos.y) 129 | chunk.cell_vertical_edge_normals[i] = edge[1] 130 | 131 | # Horizontal edges. 132 | 133 | var next_x_pos := absolute_pos + Vector2i(1, 0) 134 | var next_x := \ 135 | chunk.cell_types[i + 1] if relative_pos.x + 1 < CHUNK_SIZE.x \ 136 | else neighbor_x.cell_types[i - relative_pos.x] if neighbor_x \ 137 | else 0 138 | 139 | # If the edge is between two cells with the same type, 140 | # it's an internal edge and should not be processed. 141 | if chunk.cell_types[i] == next_x: 142 | chunk.cell_horizontal_edge_offsets[i] = -1.0 143 | else: 144 | var edge := brush.get_edge(absolute_pos, next_x_pos) 145 | if edge: 146 | chunk.cell_horizontal_edge_offsets[i] = edge[0].x - float(absolute_pos.x) 147 | chunk.cell_horizontal_edge_normals[i] = edge[1] 148 | 149 | # Recompute chunk data. 150 | 151 | for chunk: Chunk in brush_chunks: 152 | chunk.recompute_independent_data(self) 153 | 154 | for chunk: Chunk in brush_chunks: 155 | chunk.recompute_dependent_data(self) 156 | 157 | var updated_chunks := PackedVector2Array() 158 | var removed_chunks := PackedVector2Array() 159 | 160 | for chunk: Chunk in brush_chunks: 161 | var chunk_i := chunk.chunk_i 162 | 163 | # Remove a chunk if it has no surfaces, but also only if the surrounding chunks 164 | # also have no surfaces. This ensures that useful cell vertex data is kept. 165 | # Only neighbors in positive directions need to be considered. 166 | if chunk.surface_types.is_empty(): 167 | if (get_chunk_surface_count(Vector2i(chunk_i.x + 1, chunk_i.y)) == 0 168 | and get_chunk_surface_count(Vector2i(chunk_i.x, chunk_i.y + 1)) == 0 169 | and get_chunk_surface_count(Vector2i(chunk_i.x + 1, chunk_i.y + 1)) == 0 170 | ): 171 | chunk.next_empty_chunk = _first_empty_chunk 172 | _first_empty_chunk = chunk 173 | _chunks.erase(chunk_i) 174 | removed_chunks.append(chunk_i) 175 | else: 176 | updated_chunks.append(chunk_i) 177 | else: 178 | updated_chunks.append(chunk_i) 179 | 180 | # Signals. 181 | 182 | # Emit chunk_removed before chunk_updated so callees can implement more efficient pooling. 183 | for chunk_i: Vector2i in removed_chunks: 184 | chunk_removed.emit(Vector2(chunk_i)) 185 | 186 | for chunk_i: Vector2i in updated_chunks: 187 | chunk_updated.emit(Vector2(chunk_i)) 188 | 189 | # Emit brush_applied last, so the callee can access completely up-to-date data. 190 | brush_applied.emit(affected_cell_positions, affected_cell_previous_types, affected_cell_current_types) 191 | 192 | func get_chunk_size() -> Vector2i: 193 | return CHUNK_SIZE 194 | 195 | func get_chunks() -> PackedVector2Array: 196 | return PackedVector2Array(_chunks.keys()) 197 | 198 | func get_chunk_surface_count(chunk_i: Vector2i) -> int: 199 | var chunk: Chunk = _chunks.get(chunk_i, null) 200 | if chunk == null: 201 | return 0 202 | return chunk.surface_types.size() 203 | 204 | func get_chunk_surface_type(chunk_i: Vector2i, surface_i: int) -> int: 205 | var chunk: Chunk = _chunks.get(chunk_i, null) 206 | return chunk.surface_types[surface_i] 207 | 208 | func get_chunk_surface_polygons(chunk_i: Vector2i, surface_i: int) -> Array[PackedVector2Array]: 209 | var chunk: Chunk = _chunks.get(chunk_i, null) 210 | return chunk.surface_polygons[surface_i] 211 | 212 | func get_chunk_edge_crossings(chunk_i: Vector2i) -> PackedVector2Array: 213 | var chunk: Chunk = _chunks.get(chunk_i, null) 214 | var crossings := PackedVector2Array() 215 | 216 | for i in chunk.cell_types.size(): 217 | @warning_ignore("integer_division") 218 | var pos := Vector2(i % CHUNK_SIZE.x, i / CHUNK_SIZE.x) 219 | 220 | if chunk.cell_vertical_edge_offsets[i] != -1.0: 221 | crossings.append(pos + Vector2(0, chunk.cell_vertical_edge_offsets[i])) 222 | crossings.append(chunk.cell_vertical_edge_normals[i]) 223 | 224 | if chunk.cell_horizontal_edge_offsets[i] != -1.0: 225 | crossings.append(pos + Vector2(chunk.cell_horizontal_edge_offsets[i], 0)) 226 | crossings.append(chunk.cell_horizontal_edge_normals[i]) 227 | 228 | return crossings 229 | 230 | ## Internal chunk data. 231 | class Chunk extends RefCounted: 232 | var chunk_i: Vector2i 233 | 234 | var cell_types: PackedInt32Array 235 | var cell_vertices: PackedVector2Array 236 | var cell_vertical_edge_offsets: PackedFloat32Array 237 | var cell_vertical_edge_normals: PackedVector2Array 238 | var cell_horizontal_edge_offsets: PackedFloat32Array 239 | var cell_horizontal_edge_normals: PackedVector2Array 240 | 241 | var surface_types: PackedInt32Array 242 | var surface_polygons: Array # Array[Array[PackedVector2Array]] 243 | 244 | ## Used when this chunk is in the free list. 245 | var next_empty_chunk: Chunk 246 | 247 | func _init() -> void: 248 | cell_types.resize(CHUNK_SIZE.x * CHUNK_SIZE.y) 249 | cell_vertices.resize(CHUNK_SIZE.x * CHUNK_SIZE.y) 250 | cell_vertical_edge_offsets.resize(CHUNK_SIZE.x * CHUNK_SIZE.y) 251 | cell_vertical_edge_normals.resize(CHUNK_SIZE.x * CHUNK_SIZE.y) 252 | cell_horizontal_edge_offsets.resize(CHUNK_SIZE.x * CHUNK_SIZE.y) 253 | cell_horizontal_edge_normals.resize(CHUNK_SIZE.x * CHUNK_SIZE.y) 254 | clear() 255 | 256 | func clear() -> void: 257 | chunk_i = Vector2i.MIN 258 | cell_types.fill(0) 259 | cell_vertices.fill(Vector2(0.5, 0.5)) 260 | cell_vertical_edge_offsets.fill(-1.0) 261 | cell_vertical_edge_normals.fill(Vector2(0.0, 1.0)) 262 | cell_horizontal_edge_offsets.fill(-1.0) 263 | cell_horizontal_edge_normals.fill(Vector2(1.0, 0.0)) 264 | surface_types.clear() 265 | surface_polygons.clear() 266 | 267 | func recompute_independent_data(grid: DualContourIsolinesGrid) -> void: 268 | var neighbor_y: Chunk = grid._chunks.get(chunk_i + Vector2i(0, 1), null) 269 | var neighbor_x: Chunk = grid._chunks.get(chunk_i + Vector2i(1, 0), null) 270 | 271 | # Compute best vertices 272 | for i: int in cell_types.size(): 273 | @warning_ignore("integer_division") 274 | var relative_pos := Vector2i(i % CHUNK_SIZE.x, i / CHUNK_SIZE.x) 275 | 276 | var left_edge_position: float = cell_vertical_edge_offsets[i] 277 | var left_edge_normal: Vector2 = cell_vertical_edge_normals[i] 278 | var bottom_edge_position: float = cell_horizontal_edge_offsets[i] 279 | var bottom_edge_normal: Vector2 = cell_horizontal_edge_normals[i] 280 | var right_edge_position: float = -1.0 281 | var right_edge_normal: Vector2 282 | if relative_pos.x + 1 < CHUNK_SIZE.x: 283 | right_edge_position = cell_vertical_edge_offsets[i + 1] 284 | right_edge_normal = cell_vertical_edge_normals[i + 1] 285 | elif neighbor_x: 286 | right_edge_position = neighbor_x.cell_vertical_edge_offsets[i + 1 - CHUNK_SIZE.x] 287 | right_edge_normal = neighbor_x.cell_vertical_edge_normals[i + 1 - CHUNK_SIZE.x] 288 | var top_edge_position: float = -1.0 289 | var top_edge_normal: Vector2 290 | if relative_pos.y + 1 < CHUNK_SIZE.y: 291 | top_edge_position = cell_horizontal_edge_offsets[i + CHUNK_SIZE.x] 292 | top_edge_normal = cell_horizontal_edge_normals[i + CHUNK_SIZE.x] 293 | elif neighbor_y: 294 | top_edge_position = neighbor_y.cell_horizontal_edge_offsets[relative_pos.x] 295 | top_edge_normal = neighbor_y.cell_horizontal_edge_normals[relative_pos.x] 296 | 297 | cell_vertices[i] = Vector2(relative_pos) + QEFSolver.best_fit( 298 | left_edge_position, left_edge_normal, 299 | bottom_edge_position, bottom_edge_normal, 300 | right_edge_position, right_edge_normal, 301 | top_edge_position, top_edge_normal, 302 | ) 303 | 304 | func recompute_dependent_data(grid: DualContourIsolinesGrid) -> void: 305 | surface_types.clear() 306 | surface_polygons.clear() 307 | 308 | var neighbor_ny: Chunk = grid._chunks.get(chunk_i + Vector2i(0, -1), null) 309 | var neighbor_nx: Chunk = grid._chunks.get(chunk_i + Vector2i(-1, 0), null) 310 | var neighbor_nxny: Chunk = grid._chunks.get(chunk_i + Vector2i(-1, -1), null) 311 | 312 | var type_polygons: Dictionary[int, Variant] # Dictionary[int, Array[PackedVector2Array]] 313 | 314 | for i: int in cell_types.size(): 315 | var type := cell_types[i] 316 | 317 | if type == 0: 318 | continue 319 | 320 | @warning_ignore("integer_division") 321 | var pos := Vector2i(i % CHUNK_SIZE.x, i / CHUNK_SIZE.x) 322 | 323 | # +------------> 324 | # | e2 --- e3 325 | # | | p | 326 | # | e1 --- e0 327 | # V 328 | var e0 = cell_vertices[i] 329 | var e1 = \ 330 | cell_vertices[i - 1] if pos.x > 0 \ 331 | else neighbor_nx.cell_vertices[i + CHUNK_SIZE.x - 1] - Vector2(CHUNK_SIZE.x, 0.0) if neighbor_nx \ 332 | else Vector2(pos) + Vector2(-0.5, 0.5) 333 | var e2 = \ 334 | cell_vertices[i - CHUNK_SIZE.x - 1] if pos.x > 0 and pos.y > 0 \ 335 | else neighbor_nx.cell_vertices[i - 1] - Vector2(CHUNK_SIZE.x, 0.0) if pos.y > 0 and neighbor_nx \ 336 | else neighbor_ny.cell_vertices[(CHUNK_SIZE.y - 1) * CHUNK_SIZE.x + i - 1] - Vector2(0.0, CHUNK_SIZE.y) if pos.x > 0 and neighbor_ny \ 337 | else neighbor_nxny.cell_vertices[CHUNK_SIZE.y * CHUNK_SIZE.x - 1] - Vector2(CHUNK_SIZE) if neighbor_nxny \ 338 | else Vector2(pos) + Vector2(-0.5, -0.5) 339 | var e3 = \ 340 | cell_vertices[i - CHUNK_SIZE.x] if pos.y > 0 \ 341 | else neighbor_ny.cell_vertices[(CHUNK_SIZE.y - 1) * CHUNK_SIZE.x + i] - Vector2(0.0, CHUNK_SIZE.y) if neighbor_ny \ 342 | else Vector2(pos) + Vector2(0.5, -0.5) 343 | 344 | if type not in type_polygons: 345 | var polygons: Array[PackedVector2Array] 346 | type_polygons[type] = polygons 347 | 348 | # Must be this winding order, counter-clockwise per Geometry2D.is_polygon_clockwise(). 349 | type_polygons[type].append(PackedVector2Array([e0, e1, e2, e3])) 350 | 351 | for type: int in type_polygons: 352 | var polygons: Array[PackedVector2Array] = type_polygons[type] 353 | 354 | # https://github.com/godotengine/godot/pull/104407 355 | #if grid.optimize_convex_polygons: 356 | #polygons = Geometry2D.decompose_many_polygons_in_convex(Geometry2D.merge_many_polygons(polygons)) 357 | 358 | surface_types.append(type) 359 | surface_polygons.append(polygons) 360 | -------------------------------------------------------------------------------- /scenes/game_demo/game_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=11 format=4 uid="uid://eu0ew471ykcs"] 2 | 3 | [ext_resource type="Script" uid="uid://c5npnq14tfqg0" path="res://scenes/game_demo/game_demo.gd" id="1_3ndmp"] 4 | [ext_resource type="Script" uid="uid://chh0ri02qmrv4" path="res://isolines/dual_contour_isolines_grid.gd" id="1_o27md"] 5 | [ext_resource type="Texture2D" uid="uid://bbbsr6e4ib76e" path="res://scenes/game_demo/grass.png" id="2_esbyl"] 6 | [ext_resource type="Material" uid="uid://cqiy1ebiqw8o7" path="res://materials/type_1.tres" id="3_3ndmp"] 7 | [ext_resource type="Resource" uid="uid://c0772sqy2o6x0" path="res://alchemy_mixer.tres" id="4_3ndmp"] 8 | [ext_resource type="Material" uid="uid://d21rq7vvpjicu" path="res://materials/type_2.tres" id="4_lb0st"] 9 | [ext_resource type="Material" uid="uid://b7jhj1hgu31c8" path="res://materials/type_3.tres" id="5_xsmdc"] 10 | [ext_resource type="PackedScene" uid="uid://csv4vhnkjn0jq" path="res://scenes/game_demo/player.tscn" id="6_fmwbw"] 11 | 12 | [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_3ndmp"] 13 | texture = ExtResource("2_esbyl") 14 | texture_region_size = Vector2i(32, 32) 15 | 0:0/0 = 0 16 | 17 | [sub_resource type="TileSet" id="TileSet_lb0st"] 18 | tile_size = Vector2i(32, 32) 19 | sources/0 = SubResource("TileSetAtlasSource_3ndmp") 20 | 21 | [node name="GameDemo" type="Node2D" unique_id=1052594986] 22 | script = ExtResource("1_3ndmp") 23 | 24 | [node name="TileMapLayer" type="TileMapLayer" parent="." unique_id=337945265] 25 | texture_filter = 1 26 | tile_map_data = PackedByteArray("AAABAAAAAAAAAAAAAAABAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAD//wEAAAAAAAAAAAD+/wEAAAAAAAAAAAD9/wEAAAAAAAAAAAD9/wAAAAAAAAAAAAD+/wAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAAEAAAAAAAAAAAACAAEAAAAAAAAAAAABAAIAAAAAAAAAAAAAAAIAAAAAAAAAAAD//wIAAAAAAAAAAAD8/wEAAAAAAAAAAAD8/wAAAAAAAAAAAAD9////AAAAAAAAAAD+////AAAAAAAAAAD/////AAAAAAAAAAAAAP//AAAAAAAAAAABAP//AAAAAAAAAAACAP//AAAAAAAAAAACAAIAAAAAAAAAAAD+/wIAAAAAAAAAAAD9/wIAAAAAAAAAAAD8/wIAAAAAAAAAAAD7/wIAAAAAAAAAAAD7/wEAAAAAAAAAAAD6/wEAAAAAAAAAAAD6/wAAAAAAAAAAAAD7/wAAAAAAAAAAAAD8////AAAAAAAAAAAJAP7/AAAAAAAAAAAJAP//AAAAAAAAAAAJAAAAAAAAAAAAAAAJAAEAAAAAAAAAAAAJAAIAAAAAAAAAAAAIAAIAAAAAAAAAAAAHAAMAAAAAAAAAAAAGAAQAAAAAAAAAAAAFAAUAAAAAAAAAAAAEAAUAAAAAAAAAAAADAAYAAAAAAAAAAAACAAYAAAAAAAAAAAABAAYAAAAAAAAAAAAAAAYAAAAAAAAAAAD//wYAAAAAAAAAAAD+/wYAAAAAAAAAAAD9/wYAAAAAAAAAAAD8/wYAAAAAAAAAAAD7/wUAAAAAAAAAAAD6/wUAAAAAAAAAAAD5/wUAAAAAAAAAAAD5/wQAAAAAAAAAAAD4/wMAAAAAAAAAAAD3/wIAAAAAAAAAAAD2/wEAAAAAAAAAAAD1/wAAAAAAAAAAAAD1////AAAAAAAAAAD1//7/AAAAAAAAAAD1//3/AAAAAAAAAAD1//z/AAAAAAAAAAD2//v/AAAAAAAAAAD2//r/AAAAAAAAAAD3//n/AAAAAAAAAAD4//j/AAAAAAAAAAD5//f/AAAAAAAAAAD6//f/AAAAAAAAAAD7//b/AAAAAAAAAAD8//b/AAAAAAAAAAD9//X/AAAAAAAAAAD+//X/AAAAAAAAAAD///X/AAAAAAAAAAAAAPX/AAAAAAAAAAABAPX/AAAAAAAAAAACAPX/AAAAAAAAAAADAPX/AAAAAAAAAAAEAPX/AAAAAAAAAAAFAPX/AAAAAAAAAAAGAPb/AAAAAAAAAAAHAPb/AAAAAAAAAAAIAPb/AAAAAAAAAAAJAPb/AAAAAAAAAAAKAPb/AAAAAAAAAAALAPf/AAAAAAAAAAAMAPf/AAAAAAAAAAANAPf/AAAAAAAAAAAOAPj/AAAAAAAAAAAPAPn/AAAAAAAAAAAQAPr/AAAAAAAAAAARAPr/AAAAAAAAAAASAPv/AAAAAAAAAAASAPz/AAAAAAAAAAATAP3/AAAAAAAAAAAUAP7/AAAAAAAAAAAUAP//AAAAAAAAAAAUAAAAAAAAAAAAAAAUAAEAAAAAAAAAAAAUAAIAAAAAAAAAAAATAAMAAAAAAAAAAAASAAQAAAAAAAAAAAARAAQAAAAAAAAAAAAQAAUAAAAAAAAAAAAPAAUAAAAAAAAAAAAOAAYAAAAAAAAAAAANAAYAAAAAAAAAAAAMAAcAAAAAAAAAAAALAAcAAAAAAAAAAAAKAAcAAAAAAAAAAAAJAAcAAAAAAAAAAAAIAAcAAAAAAAAAAAAHAAgAAAAAAAAAAAAGAAgAAAAAAAAAAAAFAAgAAAAAAAAAAAAEAAcAAAAAAAAAAAADAAcAAAAAAAAAAAACAAcAAAAAAAAAAAABAAcAAAAAAAAAAAAAAAcAAAAAAAAAAAD//wcAAAAAAAAAAAD+/wcAAAAAAAAAAAD7/wYAAAAAAAAAAAD4/wQAAAAAAAAAAAD3/wQAAAAAAAAAAAD2/wMAAAAAAAAAAAD1/wMAAAAAAAAAAAD0/wIAAAAAAAAAAADz/wIAAAAAAAAAAADz/wEAAAAAAAAAAADy/wAAAAAAAAAAAADx/wAAAAAAAAAAAADw////AAAAAAAAAADw//7/AAAAAAAAAADw//3/AAAAAAAAAADw//z/AAAAAAAAAADx//v/AAAAAAAAAADy//r/AAAAAAAAAADz//n/AAAAAAAAAAD0//n/AAAAAAAAAAD1//j/AAAAAAAAAAD2//j/AAAAAAAAAAD3//f/AAAAAAAAAAD4//f/AAAAAAAAAAD7//f/AAAAAAAAAAD8//f/AAAAAAAAAAD9//f/AAAAAAAAAAD+//f/AAAAAAAAAAD///f/AAAAAAAAAAAAAPf/AAAAAAAAAAABAPf/AAAAAAAAAAACAPj/AAAAAAAAAAADAPj/AAAAAAAAAAAEAPn/AAAAAAAAAAAFAPr/AAAAAAAAAAAGAPv/AAAAAAAAAAAGAPz/AAAAAAAAAAAGAP3/AAAAAAAAAAAGAP7/AAAAAAAAAAAFAP//AAAAAAAAAAAFAAAAAAAAAAAAAAAEAAEAAAAAAAAAAAADAAIAAAAAAAAAAAACAAMAAAAAAAAAAAABAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAD//wUAAAAAAAAAAAD+/wUAAAAAAAAAAAD6/wYAAAAAAAAAAAD5/wYAAAAAAAAAAAD4/wYAAAAAAAAAAAD3/wYAAAAAAAAAAAD2/wYAAAAAAAAAAAD1/wYAAAAAAAAAAAD0/wYAAAAAAAAAAADz/wYAAAAAAAAAAADy/wYAAAAAAAAAAADx/wUAAAAAAAAAAADw/wUAAAAAAAAAAADv/wQAAAAAAAAAAADv/wMAAAAAAAAAAADu/wIAAAAAAAAAAADt/wEAAAAAAAAAAADt/wAAAAAAAAAAAADt////AAAAAAAAAADu//7/AAAAAAAAAADv//3/AAAAAAAAAADx//z/AAAAAAAAAADy//v/AAAAAAAAAADz//v/AAAAAAAAAAD0//v/AAAAAAAAAAD1//v/AAAAAAAAAAD3//r/AAAAAAAAAAD4//r/AAAAAAAAAAD5//r/AAAAAAAAAAD6//r/AAAAAAAAAAD7//r/AAAAAAAAAAD8//r/AAAAAAAAAAD9//r/AAAAAAAAAAD+//r/AAAAAAAAAAD///r/AAAAAAAAAAAAAPr/AAAAAAAAAAABAPr/AAAAAAAAAAACAPv/AAAAAAAAAAADAPz/AAAAAAAAAAAEAP3/AAAAAAAAAAAFAP7/AAAAAAAAAAAFAAEAAAAAAAAAAAAFAAIAAAAAAAAAAAAEAAMAAAAAAAAAAAADAAMAAAAAAAAAAAACAAQAAAAAAAAAAAABAAUAAAAAAAAAAAAAAAUAAAAAAAAAAAD4/wUAAAAAAAAAAAD2/wQAAAAAAAAAAAD1/wIAAAAAAAAAAAD1/wEAAAAAAAAAAAD2////AAAAAAAAAAD3//7/AAAAAAAAAAD4//7/AAAAAAAAAADi/+v/AAAAAAAAAADi/+z/AAAAAAAAAADi/+3/AAAAAAAAAADi/+7/AAAAAAAAAADi/+//AAAAAAAAAADi//D/AAAAAAAAAADi//H/AAAAAAAAAADi//L/AAAAAAAAAADi//P/AAAAAAAAAADi//T/AAAAAAAAAADi//X/AAAAAAAAAADi//b/AAAAAAAAAADi//f/AAAAAAAAAADi//j/AAAAAAAAAADi//n/AAAAAAAAAADi//r/AAAAAAAAAADi//v/AAAAAAAAAADi//z/AAAAAAAAAADi//3/AAAAAAAAAADi//7/AAAAAAAAAADi////AAAAAAAAAADi/wAAAAAAAAAAAADi/wEAAAAAAAAAAADi/wIAAAAAAAAAAADi/wMAAAAAAAAAAADi/wQAAAAAAAAAAADi/wUAAAAAAAAAAADi/wYAAAAAAAAAAADi/wcAAAAAAAAAAADi/wgAAAAAAAAAAADi/wkAAAAAAAAAAADi/woAAAAAAAAAAADi/wsAAAAAAAAAAADi/wwAAAAAAAAAAADi/w0AAAAAAAAAAADi/w4AAAAAAAAAAADi/w8AAAAAAAAAAADi/xAAAAAAAAAAAADi/xEAAAAAAAAAAADi/xIAAAAAAAAAAADi/xMAAAAAAAAAAADj/+v/AAAAAAAAAADj/+z/AAAAAAAAAADj/+3/AAAAAAAAAADj/+7/AAAAAAAAAADj/+//AAAAAAAAAADj//D/AAAAAAAAAADj//H/AAAAAAAAAADj//L/AAAAAAAAAADj//P/AAAAAAAAAADj//T/AAAAAAAAAADj//X/AAAAAAAAAADj//b/AAAAAAAAAADj//f/AAAAAAAAAADj//j/AAAAAAAAAADj//n/AAAAAAAAAADj//r/AAAAAAAAAADj//v/AAAAAAAAAADj//z/AAAAAAAAAADj//3/AAAAAAAAAADj//7/AAAAAAAAAADj////AAAAAAAAAADj/wAAAAAAAAAAAADj/wEAAAAAAAAAAADj/wIAAAAAAAAAAADj/wMAAAAAAAAAAADj/wQAAAAAAAAAAADj/wUAAAAAAAAAAADj/wYAAAAAAAAAAADj/wcAAAAAAAAAAADj/wgAAAAAAAAAAADj/wkAAAAAAAAAAADj/woAAAAAAAAAAADj/wsAAAAAAAAAAADj/wwAAAAAAAAAAADj/w0AAAAAAAAAAADj/w4AAAAAAAAAAADj/w8AAAAAAAAAAADj/xAAAAAAAAAAAADj/xEAAAAAAAAAAADj/xIAAAAAAAAAAADj/xMAAAAAAAAAAADk/+v/AAAAAAAAAADk/+z/AAAAAAAAAADk/+3/AAAAAAAAAADk/+7/AAAAAAAAAADk/+//AAAAAAAAAADk//D/AAAAAAAAAADk//H/AAAAAAAAAADk//L/AAAAAAAAAADk//P/AAAAAAAAAADk//T/AAAAAAAAAADk//X/AAAAAAAAAADk//b/AAAAAAAAAADk//f/AAAAAAAAAADk//j/AAAAAAAAAADk//n/AAAAAAAAAADk//r/AAAAAAAAAADk//v/AAAAAAAAAADk//z/AAAAAAAAAADk//3/AAAAAAAAAADk//7/AAAAAAAAAADk////AAAAAAAAAADk/wAAAAAAAAAAAADk/wEAAAAAAAAAAADk/wIAAAAAAAAAAADk/wMAAAAAAAAAAADk/wQAAAAAAAAAAADk/wUAAAAAAAAAAADk/wYAAAAAAAAAAADk/wcAAAAAAAAAAADk/wgAAAAAAAAAAADk/wkAAAAAAAAAAADk/woAAAAAAAAAAADk/wsAAAAAAAAAAADk/wwAAAAAAAAAAADk/w0AAAAAAAAAAADk/w4AAAAAAAAAAADk/w8AAAAAAAAAAADk/xAAAAAAAAAAAADk/xEAAAAAAAAAAADk/xIAAAAAAAAAAADk/xMAAAAAAAAAAADl/+v/AAAAAAAAAADl/+z/AAAAAAAAAADl/+3/AAAAAAAAAADl/+7/AAAAAAAAAADl/+//AAAAAAAAAADl//D/AAAAAAAAAADl//H/AAAAAAAAAADl//L/AAAAAAAAAADl//P/AAAAAAAAAADl//T/AAAAAAAAAADl//X/AAAAAAAAAADl//b/AAAAAAAAAADl//f/AAAAAAAAAADl//j/AAAAAAAAAADl//n/AAAAAAAAAADl//r/AAAAAAAAAADl//v/AAAAAAAAAADl//z/AAAAAAAAAADl//3/AAAAAAAAAADl//7/AAAAAAAAAADl////AAAAAAAAAADl/wAAAAAAAAAAAADl/wEAAAAAAAAAAADl/wIAAAAAAAAAAADl/wMAAAAAAAAAAADl/wQAAAAAAAAAAADl/wUAAAAAAAAAAADl/wYAAAAAAAAAAADl/wcAAAAAAAAAAADl/wgAAAAAAAAAAADl/wkAAAAAAAAAAADl/woAAAAAAAAAAADl/wsAAAAAAAAAAADl/wwAAAAAAAAAAADl/w0AAAAAAAAAAADl/w4AAAAAAAAAAADl/w8AAAAAAAAAAADl/xAAAAAAAAAAAADl/xEAAAAAAAAAAADl/xIAAAAAAAAAAADl/xMAAAAAAAAAAADm/+v/AAAAAAAAAADm/+z/AAAAAAAAAADm/+3/AAAAAAAAAADm/+7/AAAAAAAAAADm/+//AAAAAAAAAADm//D/AAAAAAAAAADm//H/AAAAAAAAAADm//L/AAAAAAAAAADm//P/AAAAAAAAAADm//T/AAAAAAAAAADm//X/AAAAAAAAAADm//b/AAAAAAAAAADm//f/AAAAAAAAAADm//j/AAAAAAAAAADm//n/AAAAAAAAAADm//r/AAAAAAAAAADm//v/AAAAAAAAAADm//z/AAAAAAAAAADm//3/AAAAAAAAAADm//7/AAAAAAAAAADm////AAAAAAAAAADm/wAAAAAAAAAAAADm/wEAAAAAAAAAAADm/wIAAAAAAAAAAADm/wMAAAAAAAAAAADm/wQAAAAAAAAAAADm/wUAAAAAAAAAAADm/wYAAAAAAAAAAADm/wcAAAAAAAAAAADm/wgAAAAAAAAAAADm/wkAAAAAAAAAAADm/woAAAAAAAAAAADm/wsAAAAAAAAAAADm/wwAAAAAAAAAAADm/w0AAAAAAAAAAADm/w4AAAAAAAAAAADm/w8AAAAAAAAAAADm/xAAAAAAAAAAAADm/xEAAAAAAAAAAADm/xIAAAAAAAAAAADm/xMAAAAAAAAAAADn/+v/AAAAAAAAAADn/+z/AAAAAAAAAADn/+3/AAAAAAAAAADn/+7/AAAAAAAAAADn/+//AAAAAAAAAADn//D/AAAAAAAAAADn//H/AAAAAAAAAADn//L/AAAAAAAAAADn//P/AAAAAAAAAADn//T/AAAAAAAAAADn//X/AAAAAAAAAADn//b/AAAAAAAAAADn//f/AAAAAAAAAADn//j/AAAAAAAAAADn//n/AAAAAAAAAADn//r/AAAAAAAAAADn//v/AAAAAAAAAADn//z/AAAAAAAAAADn//3/AAAAAAAAAADn//7/AAAAAAAAAADn////AAAAAAAAAADn/wAAAAAAAAAAAADn/wEAAAAAAAAAAADn/wIAAAAAAAAAAADn/wMAAAAAAAAAAADn/wQAAAAAAAAAAADn/wUAAAAAAAAAAADn/wYAAAAAAAAAAADn/wcAAAAAAAAAAADn/wgAAAAAAAAAAADn/wkAAAAAAAAAAADn/woAAAAAAAAAAADn/wsAAAAAAAAAAADn/wwAAAAAAAAAAADn/w0AAAAAAAAAAADn/w4AAAAAAAAAAADn/w8AAAAAAAAAAADn/xAAAAAAAAAAAADn/xEAAAAAAAAAAADn/xIAAAAAAAAAAADn/xMAAAAAAAAAAADo/+v/AAAAAAAAAADo/+z/AAAAAAAAAADo/+3/AAAAAAAAAADo/+7/AAAAAAAAAADo/+//AAAAAAAAAADo//D/AAAAAAAAAADo//H/AAAAAAAAAADo//L/AAAAAAAAAADo//P/AAAAAAAAAADo//T/AAAAAAAAAADo//X/AAAAAAAAAADo//b/AAAAAAAAAADo//f/AAAAAAAAAADo//j/AAAAAAAAAADo//n/AAAAAAAAAADo//r/AAAAAAAAAADo//v/AAAAAAAAAADo//z/AAAAAAAAAADo//3/AAAAAAAAAADo//7/AAAAAAAAAADo////AAAAAAAAAADo/wAAAAAAAAAAAADo/wEAAAAAAAAAAADo/wIAAAAAAAAAAADo/wMAAAAAAAAAAADo/wQAAAAAAAAAAADo/wUAAAAAAAAAAADo/wYAAAAAAAAAAADo/wcAAAAAAAAAAADo/wgAAAAAAAAAAADo/wkAAAAAAAAAAADo/woAAAAAAAAAAADo/wsAAAAAAAAAAADo/wwAAAAAAAAAAADo/w0AAAAAAAAAAADo/w4AAAAAAAAAAADo/w8AAAAAAAAAAADo/xAAAAAAAAAAAADo/xEAAAAAAAAAAADo/xIAAAAAAAAAAADo/xMAAAAAAAAAAADp/+v/AAAAAAAAAADp/+z/AAAAAAAAAADp/+3/AAAAAAAAAADp/+7/AAAAAAAAAADp/+//AAAAAAAAAADp//D/AAAAAAAAAADp//H/AAAAAAAAAADp//L/AAAAAAAAAADp//P/AAAAAAAAAADp//T/AAAAAAAAAADp//X/AAAAAAAAAADp//b/AAAAAAAAAADp//f/AAAAAAAAAADp//j/AAAAAAAAAADp//n/AAAAAAAAAADp//r/AAAAAAAAAADp//v/AAAAAAAAAADp//z/AAAAAAAAAADp//3/AAAAAAAAAADp//7/AAAAAAAAAADp////AAAAAAAAAADp/wAAAAAAAAAAAADp/wEAAAAAAAAAAADp/wIAAAAAAAAAAADp/wMAAAAAAAAAAADp/wQAAAAAAAAAAADp/wUAAAAAAAAAAADp/wYAAAAAAAAAAADp/wcAAAAAAAAAAADp/wgAAAAAAAAAAADp/wkAAAAAAAAAAADp/woAAAAAAAAAAADp/wsAAAAAAAAAAADp/wwAAAAAAAAAAADp/w0AAAAAAAAAAADp/w4AAAAAAAAAAADp/w8AAAAAAAAAAADp/xAAAAAAAAAAAADp/xEAAAAAAAAAAADp/xIAAAAAAAAAAADp/xMAAAAAAAAAAADq/+v/AAAAAAAAAADq/+z/AAAAAAAAAADq/+3/AAAAAAAAAADq/+7/AAAAAAAAAADq/+//AAAAAAAAAADq//D/AAAAAAAAAADq//H/AAAAAAAAAADq//L/AAAAAAAAAADq//P/AAAAAAAAAADq//T/AAAAAAAAAADq//X/AAAAAAAAAADq//b/AAAAAAAAAADq//f/AAAAAAAAAADq//j/AAAAAAAAAADq//n/AAAAAAAAAADq//r/AAAAAAAAAADq//v/AAAAAAAAAADq//z/AAAAAAAAAADq//3/AAAAAAAAAADq//7/AAAAAAAAAADq////AAAAAAAAAADq/wAAAAAAAAAAAADq/wEAAAAAAAAAAADq/wIAAAAAAAAAAADq/wMAAAAAAAAAAADq/wQAAAAAAAAAAADq/wUAAAAAAAAAAADq/wYAAAAAAAAAAADq/wcAAAAAAAAAAADq/wgAAAAAAAAAAADq/wkAAAAAAAAAAADq/woAAAAAAAAAAADq/wsAAAAAAAAAAADq/wwAAAAAAAAAAADq/w0AAAAAAAAAAADq/w4AAAAAAAAAAADq/w8AAAAAAAAAAADq/xAAAAAAAAAAAADq/xEAAAAAAAAAAADq/xIAAAAAAAAAAADq/xMAAAAAAAAAAADr/+v/AAAAAAAAAADr/+z/AAAAAAAAAADr/+3/AAAAAAAAAADr/+7/AAAAAAAAAADr/+//AAAAAAAAAADr//D/AAAAAAAAAADr//H/AAAAAAAAAADr//L/AAAAAAAAAADr//P/AAAAAAAAAADr//T/AAAAAAAAAADr//X/AAAAAAAAAADr//b/AAAAAAAAAADr//f/AAAAAAAAAADr//j/AAAAAAAAAADr//n/AAAAAAAAAADr//r/AAAAAAAAAADr//v/AAAAAAAAAADr//z/AAAAAAAAAADr//3/AAAAAAAAAADr//7/AAAAAAAAAADr////AAAAAAAAAADr/wAAAAAAAAAAAADr/wEAAAAAAAAAAADr/wIAAAAAAAAAAADr/wMAAAAAAAAAAADr/wQAAAAAAAAAAADr/wUAAAAAAAAAAADr/wYAAAAAAAAAAADr/wcAAAAAAAAAAADr/wgAAAAAAAAAAADr/wkAAAAAAAAAAADr/woAAAAAAAAAAADr/wsAAAAAAAAAAADr/wwAAAAAAAAAAADr/w0AAAAAAAAAAADr/w4AAAAAAAAAAADr/w8AAAAAAAAAAADr/xAAAAAAAAAAAADr/xEAAAAAAAAAAADr/xIAAAAAAAAAAADr/xMAAAAAAAAAAADs/+v/AAAAAAAAAADs/+z/AAAAAAAAAADs/+3/AAAAAAAAAADs/+7/AAAAAAAAAADs/+//AAAAAAAAAADs//D/AAAAAAAAAADs//H/AAAAAAAAAADs//L/AAAAAAAAAADs//P/AAAAAAAAAADs//T/AAAAAAAAAADs//X/AAAAAAAAAADs//b/AAAAAAAAAADs//f/AAAAAAAAAADs//j/AAAAAAAAAADs//n/AAAAAAAAAADs//r/AAAAAAAAAADs//v/AAAAAAAAAADs//z/AAAAAAAAAADs//3/AAAAAAAAAADs//7/AAAAAAAAAADs////AAAAAAAAAADs/wAAAAAAAAAAAADs/wEAAAAAAAAAAADs/wIAAAAAAAAAAADs/wMAAAAAAAAAAADs/wQAAAAAAAAAAADs/wUAAAAAAAAAAADs/wYAAAAAAAAAAADs/wcAAAAAAAAAAADs/wgAAAAAAAAAAADs/wkAAAAAAAAAAADs/woAAAAAAAAAAADs/wsAAAAAAAAAAADs/wwAAAAAAAAAAADs/w0AAAAAAAAAAADs/w4AAAAAAAAAAADs/w8AAAAAAAAAAADs/xAAAAAAAAAAAADs/xEAAAAAAAAAAADs/xIAAAAAAAAAAADs/xMAAAAAAAAAAADt/+v/AAAAAAAAAADt/+z/AAAAAAAAAADt/+3/AAAAAAAAAADt/+7/AAAAAAAAAADt/+//AAAAAAAAAADt//D/AAAAAAAAAADt//H/AAAAAAAAAADt//L/AAAAAAAAAADt//P/AAAAAAAAAADt//T/AAAAAAAAAADt//X/AAAAAAAAAADt//b/AAAAAAAAAADt//f/AAAAAAAAAADt//j/AAAAAAAAAADt//n/AAAAAAAAAADt//r/AAAAAAAAAADt//v/AAAAAAAAAADt//z/AAAAAAAAAADt//3/AAAAAAAAAADt//7/AAAAAAAAAADt/wIAAAAAAAAAAADt/wMAAAAAAAAAAADt/wQAAAAAAAAAAADt/wUAAAAAAAAAAADt/wYAAAAAAAAAAADt/wcAAAAAAAAAAADt/wgAAAAAAAAAAADt/wkAAAAAAAAAAADt/woAAAAAAAAAAADt/wsAAAAAAAAAAADt/wwAAAAAAAAAAADt/w0AAAAAAAAAAADt/w4AAAAAAAAAAADt/w8AAAAAAAAAAADt/xAAAAAAAAAAAADt/xEAAAAAAAAAAADt/xIAAAAAAAAAAADt/xMAAAAAAAAAAADu/+v/AAAAAAAAAADu/+z/AAAAAAAAAADu/+3/AAAAAAAAAADu/+7/AAAAAAAAAADu/+//AAAAAAAAAADu//D/AAAAAAAAAADu//H/AAAAAAAAAADu//L/AAAAAAAAAADu//P/AAAAAAAAAADu//T/AAAAAAAAAADu//X/AAAAAAAAAADu//b/AAAAAAAAAADu//f/AAAAAAAAAADu//j/AAAAAAAAAADu//n/AAAAAAAAAADu//r/AAAAAAAAAADu//v/AAAAAAAAAADu//z/AAAAAAAAAADu//3/AAAAAAAAAADu////AAAAAAAAAADu/wAAAAAAAAAAAADu/wEAAAAAAAAAAADu/wMAAAAAAAAAAADu/wQAAAAAAAAAAADu/wUAAAAAAAAAAADu/wYAAAAAAAAAAADu/wcAAAAAAAAAAADu/wgAAAAAAAAAAADu/wkAAAAAAAAAAADu/woAAAAAAAAAAADu/wsAAAAAAAAAAADu/wwAAAAAAAAAAADu/w0AAAAAAAAAAADu/w4AAAAAAAAAAADu/w8AAAAAAAAAAADu/xAAAAAAAAAAAADu/xEAAAAAAAAAAADu/xIAAAAAAAAAAADu/xMAAAAAAAAAAADv/+v/AAAAAAAAAADv/+z/AAAAAAAAAADv/+3/AAAAAAAAAADv/+7/AAAAAAAAAADv/+//AAAAAAAAAADv//D/AAAAAAAAAADv//H/AAAAAAAAAADv//L/AAAAAAAAAADv//P/AAAAAAAAAADv//T/AAAAAAAAAADv//X/AAAAAAAAAADv//b/AAAAAAAAAADv//f/AAAAAAAAAADv//j/AAAAAAAAAADv//n/AAAAAAAAAADv//r/AAAAAAAAAADv//v/AAAAAAAAAADv//z/AAAAAAAAAADv//7/AAAAAAAAAADv////AAAAAAAAAADv/wAAAAAAAAAAAADv/wEAAAAAAAAAAADv/wIAAAAAAAAAAADv/wUAAAAAAAAAAADv/wYAAAAAAAAAAADv/wcAAAAAAAAAAADv/wgAAAAAAAAAAADv/wkAAAAAAAAAAADv/woAAAAAAAAAAADv/wsAAAAAAAAAAADv/wwAAAAAAAAAAADv/w0AAAAAAAAAAADv/w4AAAAAAAAAAADv/w8AAAAAAAAAAADv/xAAAAAAAAAAAADv/xEAAAAAAAAAAADv/xIAAAAAAAAAAADv/xMAAAAAAAAAAADw/+v/AAAAAAAAAADw/+z/AAAAAAAAAADw/+3/AAAAAAAAAADw/+7/AAAAAAAAAADw/+//AAAAAAAAAADw//D/AAAAAAAAAADw//H/AAAAAAAAAADw//L/AAAAAAAAAADw//P/AAAAAAAAAADw//T/AAAAAAAAAADw//X/AAAAAAAAAADw//b/AAAAAAAAAADw//f/AAAAAAAAAADw//j/AAAAAAAAAADw//n/AAAAAAAAAADw//r/AAAAAAAAAADw//v/AAAAAAAAAADw/wAAAAAAAAAAAADw/wEAAAAAAAAAAADw/wIAAAAAAAAAAADw/wMAAAAAAAAAAADw/wQAAAAAAAAAAADw/wYAAAAAAAAAAADw/wcAAAAAAAAAAADw/wgAAAAAAAAAAADw/wkAAAAAAAAAAADw/woAAAAAAAAAAADw/wsAAAAAAAAAAADw/wwAAAAAAAAAAADw/w0AAAAAAAAAAADw/w4AAAAAAAAAAADw/w8AAAAAAAAAAADw/xAAAAAAAAAAAADw/xEAAAAAAAAAAADw/xIAAAAAAAAAAADw/xMAAAAAAAAAAADx/+v/AAAAAAAAAADx/+z/AAAAAAAAAADx/+3/AAAAAAAAAADx/+7/AAAAAAAAAADx/+//AAAAAAAAAADx//D/AAAAAAAAAADx//H/AAAAAAAAAADx//L/AAAAAAAAAADx//P/AAAAAAAAAADx//T/AAAAAAAAAADx//X/AAAAAAAAAADx//b/AAAAAAAAAADx//f/AAAAAAAAAADx//j/AAAAAAAAAADx//n/AAAAAAAAAADx//r/AAAAAAAAAADx//3/AAAAAAAAAADx//7/AAAAAAAAAADx////AAAAAAAAAADx/wEAAAAAAAAAAADx/wIAAAAAAAAAAADx/wMAAAAAAAAAAADx/wQAAAAAAAAAAADx/wYAAAAAAAAAAADx/wcAAAAAAAAAAADx/wgAAAAAAAAAAADx/wkAAAAAAAAAAADx/woAAAAAAAAAAADx/wsAAAAAAAAAAADx/wwAAAAAAAAAAADx/w0AAAAAAAAAAADx/w4AAAAAAAAAAADx/w8AAAAAAAAAAADx/xAAAAAAAAAAAADx/xEAAAAAAAAAAADx/xIAAAAAAAAAAADx/xMAAAAAAAAAAADy/+v/AAAAAAAAAADy/+z/AAAAAAAAAADy/+3/AAAAAAAAAADy/+7/AAAAAAAAAADy/+//AAAAAAAAAADy//D/AAAAAAAAAADy//H/AAAAAAAAAADy//L/AAAAAAAAAADy//P/AAAAAAAAAADy//T/AAAAAAAAAADy//X/AAAAAAAAAADy//b/AAAAAAAAAADy//f/AAAAAAAAAADy//j/AAAAAAAAAADy//n/AAAAAAAAAADy//z/AAAAAAAAAADy//3/AAAAAAAAAADy//7/AAAAAAAAAADy////AAAAAAAAAADy/wEAAAAAAAAAAADy/wIAAAAAAAAAAADy/wMAAAAAAAAAAADy/wQAAAAAAAAAAADy/wUAAAAAAAAAAADy/wcAAAAAAAAAAADy/wgAAAAAAAAAAADy/wkAAAAAAAAAAADy/woAAAAAAAAAAADy/wsAAAAAAAAAAADy/wwAAAAAAAAAAADy/w0AAAAAAAAAAADy/w4AAAAAAAAAAADy/w8AAAAAAAAAAADy/xAAAAAAAAAAAADy/xEAAAAAAAAAAADy/xIAAAAAAAAAAADy/xMAAAAAAAAAAADz/+v/AAAAAAAAAADz/+z/AAAAAAAAAADz/+3/AAAAAAAAAADz/+7/AAAAAAAAAADz/+//AAAAAAAAAADz//D/AAAAAAAAAADz//H/AAAAAAAAAADz//L/AAAAAAAAAADz//P/AAAAAAAAAADz//T/AAAAAAAAAADz//X/AAAAAAAAAADz//b/AAAAAAAAAADz//f/AAAAAAAAAADz//j/AAAAAAAAAADz//r/AAAAAAAAAADz//z/AAAAAAAAAADz//3/AAAAAAAAAADz//7/AAAAAAAAAADz////AAAAAAAAAADz/wAAAAAAAAAAAADz/wMAAAAAAAAAAADz/wQAAAAAAAAAAADz/wUAAAAAAAAAAADz/wcAAAAAAAAAAADz/wgAAAAAAAAAAADz/wkAAAAAAAAAAADz/woAAAAAAAAAAADz/wsAAAAAAAAAAADz/wwAAAAAAAAAAADz/w0AAAAAAAAAAADz/w4AAAAAAAAAAADz/w8AAAAAAAAAAADz/xAAAAAAAAAAAADz/xEAAAAAAAAAAADz/xIAAAAAAAAAAADz/xMAAAAAAAAAAAD0/+v/AAAAAAAAAAD0/+z/AAAAAAAAAAD0/+3/AAAAAAAAAAD0/+7/AAAAAAAAAAD0/+//AAAAAAAAAAD0//D/AAAAAAAAAAD0//H/AAAAAAAAAAD0//L/AAAAAAAAAAD0//P/AAAAAAAAAAD0//T/AAAAAAAAAAD0//X/AAAAAAAAAAD0//b/AAAAAAAAAAD0//f/AAAAAAAAAAD0//j/AAAAAAAAAAD0//r/AAAAAAAAAAD0//z/AAAAAAAAAAD0//3/AAAAAAAAAAD0//7/AAAAAAAAAAD0////AAAAAAAAAAD0/wAAAAAAAAAAAAD0/wEAAAAAAAAAAAD0/wMAAAAAAAAAAAD0/wQAAAAAAAAAAAD0/wUAAAAAAAAAAAD0/wcAAAAAAAAAAAD0/wgAAAAAAAAAAAD0/wkAAAAAAAAAAAD0/woAAAAAAAAAAAD0/wsAAAAAAAAAAAD0/wwAAAAAAAAAAAD0/w0AAAAAAAAAAAD0/w4AAAAAAAAAAAD0/w8AAAAAAAAAAAD0/xAAAAAAAAAAAAD0/xEAAAAAAAAAAAD0/xIAAAAAAAAAAAD0/xMAAAAAAAAAAAD1/+v/AAAAAAAAAAD1/+z/AAAAAAAAAAD1/+3/AAAAAAAAAAD1/+7/AAAAAAAAAAD1/+//AAAAAAAAAAD1//D/AAAAAAAAAAD1//H/AAAAAAAAAAD1//L/AAAAAAAAAAD1//P/AAAAAAAAAAD1//T/AAAAAAAAAAD1//X/AAAAAAAAAAD1//b/AAAAAAAAAAD1//f/AAAAAAAAAAD1//n/AAAAAAAAAAD1//r/AAAAAAAAAAD1/wQAAAAAAAAAAAD1/wUAAAAAAAAAAAD1/wcAAAAAAAAAAAD1/wgAAAAAAAAAAAD1/wkAAAAAAAAAAAD1/woAAAAAAAAAAAD1/wsAAAAAAAAAAAD1/wwAAAAAAAAAAAD1/w0AAAAAAAAAAAD1/w4AAAAAAAAAAAD1/w8AAAAAAAAAAAD1/xAAAAAAAAAAAAD1/xEAAAAAAAAAAAD1/xIAAAAAAAAAAAD1/xMAAAAAAAAAAAD2/+v/AAAAAAAAAAD2/+z/AAAAAAAAAAD2/+3/AAAAAAAAAAD2/+7/AAAAAAAAAAD2/+//AAAAAAAAAAD2//D/AAAAAAAAAAD2//H/AAAAAAAAAAD2//L/AAAAAAAAAAD2//P/AAAAAAAAAAD2//T/AAAAAAAAAAD2//X/AAAAAAAAAAD2//b/AAAAAAAAAAD2//f/AAAAAAAAAAD2//n/AAAAAAAAAAD2//z/AAAAAAAAAAD2//3/AAAAAAAAAAD2//7/AAAAAAAAAAD2/wAAAAAAAAAAAAD2/wIAAAAAAAAAAAD2/wUAAAAAAAAAAAD2/wcAAAAAAAAAAAD2/wgAAAAAAAAAAAD2/wkAAAAAAAAAAAD2/woAAAAAAAAAAAD2/wsAAAAAAAAAAAD2/wwAAAAAAAAAAAD2/w0AAAAAAAAAAAD2/w4AAAAAAAAAAAD2/w8AAAAAAAAAAAD2/xAAAAAAAAAAAAD2/xEAAAAAAAAAAAD2/xIAAAAAAAAAAAD2/xMAAAAAAAAAAAD3/+v/AAAAAAAAAAD3/+z/AAAAAAAAAAD3/+3/AAAAAAAAAAD3/+7/AAAAAAAAAAD3/+//AAAAAAAAAAD3//D/AAAAAAAAAAD3//H/AAAAAAAAAAD3//L/AAAAAAAAAAD3//P/AAAAAAAAAAD3//T/AAAAAAAAAAD3//X/AAAAAAAAAAD3//b/AAAAAAAAAAD3//j/AAAAAAAAAAD3//v/AAAAAAAAAAD3//z/AAAAAAAAAAD3//3/AAAAAAAAAAD3////AAAAAAAAAAD3/wAAAAAAAAAAAAD3/wEAAAAAAAAAAAD3/wMAAAAAAAAAAAD3/wUAAAAAAAAAAAD3/wcAAAAAAAAAAAD3/wgAAAAAAAAAAAD3/wkAAAAAAAAAAAD3/woAAAAAAAAAAAD3/wsAAAAAAAAAAAD3/wwAAAAAAAAAAAD3/w0AAAAAAAAAAAD3/w4AAAAAAAAAAAD3/w8AAAAAAAAAAAD3/xAAAAAAAAAAAAD3/xEAAAAAAAAAAAD3/xIAAAAAAAAAAAD3/xMAAAAAAAAAAAD4/+v/AAAAAAAAAAD4/+z/AAAAAAAAAAD4/+3/AAAAAAAAAAD4/+7/AAAAAAAAAAD4/+//AAAAAAAAAAD4//D/AAAAAAAAAAD4//H/AAAAAAAAAAD4//L/AAAAAAAAAAD4//P/AAAAAAAAAAD4//T/AAAAAAAAAAD4//X/AAAAAAAAAAD4//b/AAAAAAAAAAD4//n/AAAAAAAAAAD4//v/AAAAAAAAAAD4//z/AAAAAAAAAAD4//3/AAAAAAAAAAD4////AAAAAAAAAAD4/wAAAAAAAAAAAAD4/wEAAAAAAAAAAAD4/wIAAAAAAAAAAAD4/wcAAAAAAAAAAAD4/wgAAAAAAAAAAAD4/wkAAAAAAAAAAAD4/woAAAAAAAAAAAD4/wsAAAAAAAAAAAD4/wwAAAAAAAAAAAD4/w0AAAAAAAAAAAD4/w4AAAAAAAAAAAD4/w8AAAAAAAAAAAD4/xAAAAAAAAAAAAD4/xEAAAAAAAAAAAD4/xIAAAAAAAAAAAD4/xMAAAAAAAAAAAD5/+v/AAAAAAAAAAD5/+z/AAAAAAAAAAD5/+3/AAAAAAAAAAD5/+7/AAAAAAAAAAD5/+//AAAAAAAAAAD5//D/AAAAAAAAAAD5//H/AAAAAAAAAAD5//L/AAAAAAAAAAD5//P/AAAAAAAAAAD5//T/AAAAAAAAAAD5//X/AAAAAAAAAAD5//b/AAAAAAAAAAD5//j/AAAAAAAAAAD5//n/AAAAAAAAAAD5//v/AAAAAAAAAAD5//z/AAAAAAAAAAD5//3/AAAAAAAAAAD5//7/AAAAAAAAAAD5////AAAAAAAAAAD5/wAAAAAAAAAAAAD5/wEAAAAAAAAAAAD5/wIAAAAAAAAAAAD5/wMAAAAAAAAAAAD5/wcAAAAAAAAAAAD5/wgAAAAAAAAAAAD5/wkAAAAAAAAAAAD5/woAAAAAAAAAAAD5/wsAAAAAAAAAAAD5/wwAAAAAAAAAAAD5/w0AAAAAAAAAAAD5/w4AAAAAAAAAAAD5/w8AAAAAAAAAAAD5/xAAAAAAAAAAAAD5/xEAAAAAAAAAAAD5/xIAAAAAAAAAAAD5/xMAAAAAAAAAAAD6/+v/AAAAAAAAAAD6/+z/AAAAAAAAAAD6/+3/AAAAAAAAAAD6/+7/AAAAAAAAAAD6/+//AAAAAAAAAAD6//D/AAAAAAAAAAD6//H/AAAAAAAAAAD6//L/AAAAAAAAAAD6//P/AAAAAAAAAAD6//T/AAAAAAAAAAD6//X/AAAAAAAAAAD6//b/AAAAAAAAAAD6//j/AAAAAAAAAAD6//n/AAAAAAAAAAD6//v/AAAAAAAAAAD6//z/AAAAAAAAAAD6//3/AAAAAAAAAAD6//7/AAAAAAAAAAD6////AAAAAAAAAAD6/wIAAAAAAAAAAAD6/wMAAAAAAAAAAAD6/wQAAAAAAAAAAAD6/wcAAAAAAAAAAAD6/wgAAAAAAAAAAAD6/wkAAAAAAAAAAAD6/woAAAAAAAAAAAD6/wsAAAAAAAAAAAD6/wwAAAAAAAAAAAD6/w0AAAAAAAAAAAD6/w4AAAAAAAAAAAD6/w8AAAAAAAAAAAD6/xAAAAAAAAAAAAD6/xEAAAAAAAAAAAD6/xIAAAAAAAAAAAD6/xMAAAAAAAAAAAD7/+v/AAAAAAAAAAD7/+z/AAAAAAAAAAD7/+3/AAAAAAAAAAD7/+7/AAAAAAAAAAD7/+//AAAAAAAAAAD7//D/AAAAAAAAAAD7//H/AAAAAAAAAAD7//L/AAAAAAAAAAD7//P/AAAAAAAAAAD7//T/AAAAAAAAAAD7//X/AAAAAAAAAAD7//j/AAAAAAAAAAD7//n/AAAAAAAAAAD7//v/AAAAAAAAAAD7//z/AAAAAAAAAAD7//3/AAAAAAAAAAD7//7/AAAAAAAAAAD7////AAAAAAAAAAD7/wMAAAAAAAAAAAD7/wQAAAAAAAAAAAD7/wcAAAAAAAAAAAD7/wgAAAAAAAAAAAD7/wkAAAAAAAAAAAD7/woAAAAAAAAAAAD7/wsAAAAAAAAAAAD7/wwAAAAAAAAAAAD7/w0AAAAAAAAAAAD7/w4AAAAAAAAAAAD7/w8AAAAAAAAAAAD7/xAAAAAAAAAAAAD7/xEAAAAAAAAAAAD7/xIAAAAAAAAAAAD7/xMAAAAAAAAAAAD8/+v/AAAAAAAAAAD8/+z/AAAAAAAAAAD8/+3/AAAAAAAAAAD8/+7/AAAAAAAAAAD8/+//AAAAAAAAAAD8//D/AAAAAAAAAAD8//H/AAAAAAAAAAD8//L/AAAAAAAAAAD8//P/AAAAAAAAAAD8//T/AAAAAAAAAAD8//X/AAAAAAAAAAD8//j/AAAAAAAAAAD8//n/AAAAAAAAAAD8//v/AAAAAAAAAAD8//z/AAAAAAAAAAD8//3/AAAAAAAAAAD8//7/AAAAAAAAAAD8/wMAAAAAAAAAAAD8/wQAAAAAAAAAAAD8/wUAAAAAAAAAAAD8/wcAAAAAAAAAAAD8/wgAAAAAAAAAAAD8/wkAAAAAAAAAAAD8/woAAAAAAAAAAAD8/wsAAAAAAAAAAAD8/wwAAAAAAAAAAAD8/w0AAAAAAAAAAAD8/w4AAAAAAAAAAAD8/w8AAAAAAAAAAAD8/xAAAAAAAAAAAAD8/xEAAAAAAAAAAAD8/xIAAAAAAAAAAAD8/xMAAAAAAAAAAAD9/+v/AAAAAAAAAAD9/+z/AAAAAAAAAAD9/+3/AAAAAAAAAAD9/+7/AAAAAAAAAAD9/+//AAAAAAAAAAD9//D/AAAAAAAAAAD9//H/AAAAAAAAAAD9//L/AAAAAAAAAAD9//P/AAAAAAAAAAD9//T/AAAAAAAAAAD9//b/AAAAAAAAAAD9//j/AAAAAAAAAAD9//n/AAAAAAAAAAD9//v/AAAAAAAAAAD9//z/AAAAAAAAAAD9//3/AAAAAAAAAAD9//7/AAAAAAAAAAD9/wMAAAAAAAAAAAD9/wQAAAAAAAAAAAD9/wUAAAAAAAAAAAD9/wcAAAAAAAAAAAD9/wgAAAAAAAAAAAD9/wkAAAAAAAAAAAD9/woAAAAAAAAAAAD9/wsAAAAAAAAAAAD9/wwAAAAAAAAAAAD9/w0AAAAAAAAAAAD9/w4AAAAAAAAAAAD9/w8AAAAAAAAAAAD9/xAAAAAAAAAAAAD9/xEAAAAAAAAAAAD9/xIAAAAAAAAAAAD9/xMAAAAAAAAAAAD+/+v/AAAAAAAAAAD+/+z/AAAAAAAAAAD+/+3/AAAAAAAAAAD+/+7/AAAAAAAAAAD+/+//AAAAAAAAAAD+//D/AAAAAAAAAAD+//H/AAAAAAAAAAD+//L/AAAAAAAAAAD+//P/AAAAAAAAAAD+//T/AAAAAAAAAAD+//b/AAAAAAAAAAD+//j/AAAAAAAAAAD+//n/AAAAAAAAAAD+//v/AAAAAAAAAAD+//z/AAAAAAAAAAD+//3/AAAAAAAAAAD+//7/AAAAAAAAAAD+/wMAAAAAAAAAAAD+/wQAAAAAAAAAAAD+/wgAAAAAAAAAAAD+/wkAAAAAAAAAAAD+/woAAAAAAAAAAAD+/wsAAAAAAAAAAAD+/wwAAAAAAAAAAAD+/w0AAAAAAAAAAAD+/w4AAAAAAAAAAAD+/w8AAAAAAAAAAAD+/xAAAAAAAAAAAAD+/xEAAAAAAAAAAAD+/xIAAAAAAAAAAAD+/xMAAAAAAAAAAAD//+v/AAAAAAAAAAD//+z/AAAAAAAAAAD//+3/AAAAAAAAAAD//+7/AAAAAAAAAAD//+//AAAAAAAAAAD///D/AAAAAAAAAAD///H/AAAAAAAAAAD///L/AAAAAAAAAAD///P/AAAAAAAAAAD///T/AAAAAAAAAAD///b/AAAAAAAAAAD///j/AAAAAAAAAAD///n/AAAAAAAAAAD///v/AAAAAAAAAAD///z/AAAAAAAAAAD///3/AAAAAAAAAAD///7/AAAAAAAAAAD//wMAAAAAAAAAAAD//wQAAAAAAAAAAAD//wgAAAAAAAAAAAD//wkAAAAAAAAAAAD//woAAAAAAAAAAAD//wsAAAAAAAAAAAD//wwAAAAAAAAAAAD//w0AAAAAAAAAAAD//w4AAAAAAAAAAAD//w8AAAAAAAAAAAD//xAAAAAAAAAAAAD//xEAAAAAAAAAAAD//xIAAAAAAAAAAAD//xMAAAAAAAAAAAAAAOv/AAAAAAAAAAAAAOz/AAAAAAAAAAAAAO3/AAAAAAAAAAAAAO7/AAAAAAAAAAAAAO//AAAAAAAAAAAAAPD/AAAAAAAAAAAAAPH/AAAAAAAAAAAAAPL/AAAAAAAAAAAAAPP/AAAAAAAAAAAAAPT/AAAAAAAAAAAAAPb/AAAAAAAAAAAAAPj/AAAAAAAAAAAAAPn/AAAAAAAAAAAAAPv/AAAAAAAAAAAAAPz/AAAAAAAAAAAAAP3/AAAAAAAAAAAAAP7/AAAAAAAAAAAAAAMAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAA0AAAAAAAAAAAAAAA4AAAAAAAAAAAAAAA8AAAAAAAAAAAAAABAAAAAAAAAAAAAAABEAAAAAAAAAAAAAABIAAAAAAAAAAAAAABMAAAAAAAAAAAABAOv/AAAAAAAAAAABAOz/AAAAAAAAAAABAO3/AAAAAAAAAAABAO7/AAAAAAAAAAABAO//AAAAAAAAAAABAPD/AAAAAAAAAAABAPH/AAAAAAAAAAABAPL/AAAAAAAAAAABAPP/AAAAAAAAAAABAPT/AAAAAAAAAAABAPb/AAAAAAAAAAABAPj/AAAAAAAAAAABAPn/AAAAAAAAAAABAPv/AAAAAAAAAAABAPz/AAAAAAAAAAABAP3/AAAAAAAAAAABAP7/AAAAAAAAAAABAAMAAAAAAAAAAAABAAgAAAAAAAAAAAABAAkAAAAAAAAAAAABAAoAAAAAAAAAAAABAAsAAAAAAAAAAAABAAwAAAAAAAAAAAABAA0AAAAAAAAAAAABAA4AAAAAAAAAAAABAA8AAAAAAAAAAAABABAAAAAAAAAAAAABABEAAAAAAAAAAAABABIAAAAAAAAAAAABABMAAAAAAAAAAAACAOv/AAAAAAAAAAACAOz/AAAAAAAAAAACAO3/AAAAAAAAAAACAO7/AAAAAAAAAAACAO//AAAAAAAAAAACAPD/AAAAAAAAAAACAPH/AAAAAAAAAAACAPL/AAAAAAAAAAACAPP/AAAAAAAAAAACAPT/AAAAAAAAAAACAPb/AAAAAAAAAAACAPf/AAAAAAAAAAACAPn/AAAAAAAAAAACAPr/AAAAAAAAAAACAPz/AAAAAAAAAAACAP3/AAAAAAAAAAACAP7/AAAAAAAAAAACAAUAAAAAAAAAAAACAAgAAAAAAAAAAAACAAkAAAAAAAAAAAACAAoAAAAAAAAAAAACAAsAAAAAAAAAAAACAAwAAAAAAAAAAAACAA0AAAAAAAAAAAACAA4AAAAAAAAAAAACAA8AAAAAAAAAAAACABAAAAAAAAAAAAACABEAAAAAAAAAAAACABIAAAAAAAAAAAACABMAAAAAAAAAAAADAOv/AAAAAAAAAAADAOz/AAAAAAAAAAADAO3/AAAAAAAAAAADAO7/AAAAAAAAAAADAO//AAAAAAAAAAADAPD/AAAAAAAAAAADAPH/AAAAAAAAAAADAPL/AAAAAAAAAAADAPP/AAAAAAAAAAADAPT/AAAAAAAAAAADAPb/AAAAAAAAAAADAPf/AAAAAAAAAAADAPn/AAAAAAAAAAADAPr/AAAAAAAAAAADAPv/AAAAAAAAAAADAP3/AAAAAAAAAAADAP7/AAAAAAAAAAADAP//AAAAAAAAAAADAAQAAAAAAAAAAAADAAUAAAAAAAAAAAADAAgAAAAAAAAAAAADAAkAAAAAAAAAAAADAAoAAAAAAAAAAAADAAsAAAAAAAAAAAADAAwAAAAAAAAAAAADAA0AAAAAAAAAAAADAA4AAAAAAAAAAAADAA8AAAAAAAAAAAADABAAAAAAAAAAAAADABEAAAAAAAAAAAADABIAAAAAAAAAAAADABMAAAAAAAAAAAAEAOv/AAAAAAAAAAAEAOz/AAAAAAAAAAAEAO3/AAAAAAAAAAAEAO7/AAAAAAAAAAAEAO//AAAAAAAAAAAEAPD/AAAAAAAAAAAEAPH/AAAAAAAAAAAEAPL/AAAAAAAAAAAEAPP/AAAAAAAAAAAEAPT/AAAAAAAAAAAEAPb/AAAAAAAAAAAEAPf/AAAAAAAAAAAEAPj/AAAAAAAAAAAEAPr/AAAAAAAAAAAEAPv/AAAAAAAAAAAEAPz/AAAAAAAAAAAEAP7/AAAAAAAAAAAEAP//AAAAAAAAAAAEAAAAAAAAAAAAAAAEAAIAAAAAAAAAAAAEAAQAAAAAAAAAAAAEAAYAAAAAAAAAAAAEAAgAAAAAAAAAAAAEAAkAAAAAAAAAAAAEAAoAAAAAAAAAAAAEAAsAAAAAAAAAAAAEAAwAAAAAAAAAAAAEAA0AAAAAAAAAAAAEAA4AAAAAAAAAAAAEAA8AAAAAAAAAAAAEABAAAAAAAAAAAAAEABEAAAAAAAAAAAAEABIAAAAAAAAAAAAEABMAAAAAAAAAAAAFAOv/AAAAAAAAAAAFAOz/AAAAAAAAAAAFAO3/AAAAAAAAAAAFAO7/AAAAAAAAAAAFAO//AAAAAAAAAAAFAPD/AAAAAAAAAAAFAPH/AAAAAAAAAAAFAPL/AAAAAAAAAAAFAPP/AAAAAAAAAAAFAPT/AAAAAAAAAAAFAPb/AAAAAAAAAAAFAPf/AAAAAAAAAAAFAPj/AAAAAAAAAAAFAPn/AAAAAAAAAAAFAPv/AAAAAAAAAAAFAPz/AAAAAAAAAAAFAP3/AAAAAAAAAAAFAAMAAAAAAAAAAAAFAAQAAAAAAAAAAAAFAAYAAAAAAAAAAAAFAAcAAAAAAAAAAAAFAAkAAAAAAAAAAAAFAAoAAAAAAAAAAAAFAAsAAAAAAAAAAAAFAAwAAAAAAAAAAAAFAA0AAAAAAAAAAAAFAA4AAAAAAAAAAAAFAA8AAAAAAAAAAAAFABAAAAAAAAAAAAAFABEAAAAAAAAAAAAFABIAAAAAAAAAAAAFABMAAAAAAAAAAAAGAOv/AAAAAAAAAAAGAOz/AAAAAAAAAAAGAO3/AAAAAAAAAAAGAO7/AAAAAAAAAAAGAO//AAAAAAAAAAAGAPD/AAAAAAAAAAAGAPH/AAAAAAAAAAAGAPL/AAAAAAAAAAAGAPP/AAAAAAAAAAAGAPT/AAAAAAAAAAAGAPX/AAAAAAAAAAAGAPf/AAAAAAAAAAAGAPj/AAAAAAAAAAAGAPn/AAAAAAAAAAAGAPr/AAAAAAAAAAAGAP//AAAAAAAAAAAGAAAAAAAAAAAAAAAGAAEAAAAAAAAAAAAGAAIAAAAAAAAAAAAGAAMAAAAAAAAAAAAGAAUAAAAAAAAAAAAGAAYAAAAAAAAAAAAGAAcAAAAAAAAAAAAGAAkAAAAAAAAAAAAGAAoAAAAAAAAAAAAGAAsAAAAAAAAAAAAGAAwAAAAAAAAAAAAGAA0AAAAAAAAAAAAGAA4AAAAAAAAAAAAGAA8AAAAAAAAAAAAGABAAAAAAAAAAAAAGABEAAAAAAAAAAAAGABIAAAAAAAAAAAAGABMAAAAAAAAAAAAHAOv/AAAAAAAAAAAHAOz/AAAAAAAAAAAHAO3/AAAAAAAAAAAHAO7/AAAAAAAAAAAHAO//AAAAAAAAAAAHAPD/AAAAAAAAAAAHAPH/AAAAAAAAAAAHAPL/AAAAAAAAAAAHAPP/AAAAAAAAAAAHAPT/AAAAAAAAAAAHAPX/AAAAAAAAAAAHAPf/AAAAAAAAAAAHAPj/AAAAAAAAAAAHAPn/AAAAAAAAAAAHAPr/AAAAAAAAAAAHAPv/AAAAAAAAAAAHAPz/AAAAAAAAAAAHAP3/AAAAAAAAAAAHAP7/AAAAAAAAAAAHAP//AAAAAAAAAAAHAAAAAAAAAAAAAAAHAAEAAAAAAAAAAAAHAAIAAAAAAAAAAAAHAAQAAAAAAAAAAAAHAAUAAAAAAAAAAAAHAAYAAAAAAAAAAAAHAAcAAAAAAAAAAAAHAAkAAAAAAAAAAAAHAAoAAAAAAAAAAAAHAAsAAAAAAAAAAAAHAAwAAAAAAAAAAAAHAA0AAAAAAAAAAAAHAA4AAAAAAAAAAAAHAA8AAAAAAAAAAAAHABAAAAAAAAAAAAAHABEAAAAAAAAAAAAHABIAAAAAAAAAAAAHABMAAAAAAAAAAAAIAOv/AAAAAAAAAAAIAOz/AAAAAAAAAAAIAO3/AAAAAAAAAAAIAO7/AAAAAAAAAAAIAO//AAAAAAAAAAAIAPD/AAAAAAAAAAAIAPH/AAAAAAAAAAAIAPL/AAAAAAAAAAAIAPP/AAAAAAAAAAAIAPT/AAAAAAAAAAAIAPX/AAAAAAAAAAAIAPf/AAAAAAAAAAAIAPj/AAAAAAAAAAAIAPn/AAAAAAAAAAAIAPr/AAAAAAAAAAAIAPv/AAAAAAAAAAAIAPz/AAAAAAAAAAAIAP3/AAAAAAAAAAAIAP7/AAAAAAAAAAAIAP//AAAAAAAAAAAIAAAAAAAAAAAAAAAIAAEAAAAAAAAAAAAIAAMAAAAAAAAAAAAIAAQAAAAAAAAAAAAIAAUAAAAAAAAAAAAIAAYAAAAAAAAAAAAIAAgAAAAAAAAAAAAIAAkAAAAAAAAAAAAIAAoAAAAAAAAAAAAIAAsAAAAAAAAAAAAIAAwAAAAAAAAAAAAIAA0AAAAAAAAAAAAIAA4AAAAAAAAAAAAIAA8AAAAAAAAAAAAIABAAAAAAAAAAAAAIABEAAAAAAAAAAAAIABIAAAAAAAAAAAAIABMAAAAAAAAAAAAJAOv/AAAAAAAAAAAJAOz/AAAAAAAAAAAJAO3/AAAAAAAAAAAJAO7/AAAAAAAAAAAJAO//AAAAAAAAAAAJAPD/AAAAAAAAAAAJAPH/AAAAAAAAAAAJAPL/AAAAAAAAAAAJAPP/AAAAAAAAAAAJAPT/AAAAAAAAAAAJAPX/AAAAAAAAAAAJAPf/AAAAAAAAAAAJAPj/AAAAAAAAAAAJAPn/AAAAAAAAAAAJAPr/AAAAAAAAAAAJAPv/AAAAAAAAAAAJAPz/AAAAAAAAAAAJAP3/AAAAAAAAAAAJAAMAAAAAAAAAAAAJAAQAAAAAAAAAAAAJAAUAAAAAAAAAAAAJAAYAAAAAAAAAAAAJAAgAAAAAAAAAAAAJAAkAAAAAAAAAAAAJAAoAAAAAAAAAAAAJAAsAAAAAAAAAAAAJAAwAAAAAAAAAAAAJAA0AAAAAAAAAAAAJAA4AAAAAAAAAAAAJAA8AAAAAAAAAAAAJABAAAAAAAAAAAAAJABEAAAAAAAAAAAAJABIAAAAAAAAAAAAJABMAAAAAAAAAAAAKAOv/AAAAAAAAAAAKAOz/AAAAAAAAAAAKAO3/AAAAAAAAAAAKAO7/AAAAAAAAAAAKAO//AAAAAAAAAAAKAPD/AAAAAAAAAAAKAPH/AAAAAAAAAAAKAPL/AAAAAAAAAAAKAPP/AAAAAAAAAAAKAPT/AAAAAAAAAAAKAPX/AAAAAAAAAAAKAPf/AAAAAAAAAAAKAPj/AAAAAAAAAAAKAPn/AAAAAAAAAAAKAPr/AAAAAAAAAAAKAPv/AAAAAAAAAAAKAPz/AAAAAAAAAAAKAP3/AAAAAAAAAAAKAP7/AAAAAAAAAAAKAP//AAAAAAAAAAAKAAAAAAAAAAAAAAAKAAEAAAAAAAAAAAAKAAIAAAAAAAAAAAAKAAMAAAAAAAAAAAAKAAQAAAAAAAAAAAAKAAUAAAAAAAAAAAAKAAYAAAAAAAAAAAAKAAgAAAAAAAAAAAAKAAkAAAAAAAAAAAAKAAoAAAAAAAAAAAAKAAsAAAAAAAAAAAAKAAwAAAAAAAAAAAAKAA0AAAAAAAAAAAAKAA4AAAAAAAAAAAAKAA8AAAAAAAAAAAAKABAAAAAAAAAAAAAKABEAAAAAAAAAAAAKABIAAAAAAAAAAAAKABMAAAAAAAAAAAALAOv/AAAAAAAAAAALAOz/AAAAAAAAAAALAO3/AAAAAAAAAAALAO7/AAAAAAAAAAALAO//AAAAAAAAAAALAPD/AAAAAAAAAAALAPH/AAAAAAAAAAALAPL/AAAAAAAAAAALAPP/AAAAAAAAAAALAPT/AAAAAAAAAAALAPX/AAAAAAAAAAALAPb/AAAAAAAAAAALAPj/AAAAAAAAAAALAPn/AAAAAAAAAAALAPr/AAAAAAAAAAALAPv/AAAAAAAAAAALAPz/AAAAAAAAAAALAP3/AAAAAAAAAAALAP7/AAAAAAAAAAALAP//AAAAAAAAAAALAAAAAAAAAAAAAAALAAEAAAAAAAAAAAALAAIAAAAAAAAAAAALAAMAAAAAAAAAAAALAAQAAAAAAAAAAAALAAUAAAAAAAAAAAALAAYAAAAAAAAAAAALAAgAAAAAAAAAAAALAAkAAAAAAAAAAAALAAoAAAAAAAAAAAALAAsAAAAAAAAAAAALAAwAAAAAAAAAAAALAA0AAAAAAAAAAAALAA4AAAAAAAAAAAALAA8AAAAAAAAAAAALABAAAAAAAAAAAAALABEAAAAAAAAAAAALABIAAAAAAAAAAAALABMAAAAAAAAAAAAMAOv/AAAAAAAAAAAMAOz/AAAAAAAAAAAMAO3/AAAAAAAAAAAMAO7/AAAAAAAAAAAMAO//AAAAAAAAAAAMAPD/AAAAAAAAAAAMAPH/AAAAAAAAAAAMAPL/AAAAAAAAAAAMAPP/AAAAAAAAAAAMAPT/AAAAAAAAAAAMAPX/AAAAAAAAAAAMAPb/AAAAAAAAAAAMAPj/AAAAAAAAAAAMAPn/AAAAAAAAAAAMAPr/AAAAAAAAAAAMAPv/AAAAAAAAAAAMAPz/AAAAAAAAAAAMAP3/AAAAAAAAAAAMAP7/AAAAAAAAAAAMAP//AAAAAAAAAAAMAAAAAAAAAAAAAAAMAAEAAAAAAAAAAAAMAAIAAAAAAAAAAAAMAAMAAAAAAAAAAAAMAAQAAAAAAAAAAAAMAAUAAAAAAAAAAAAMAAYAAAAAAAAAAAAMAAgAAAAAAAAAAAAMAAkAAAAAAAAAAAAMAAoAAAAAAAAAAAAMAAsAAAAAAAAAAAAMAAwAAAAAAAAAAAAMAA0AAAAAAAAAAAAMAA4AAAAAAAAAAAAMAA8AAAAAAAAAAAAMABAAAAAAAAAAAAAMABEAAAAAAAAAAAAMABIAAAAAAAAAAAAMABMAAAAAAAAAAAANAOv/AAAAAAAAAAANAOz/AAAAAAAAAAANAO3/AAAAAAAAAAANAO7/AAAAAAAAAAANAO//AAAAAAAAAAANAPD/AAAAAAAAAAANAPH/AAAAAAAAAAANAPL/AAAAAAAAAAANAPP/AAAAAAAAAAANAPT/AAAAAAAAAAANAPX/AAAAAAAAAAANAPb/AAAAAAAAAAANAPj/AAAAAAAAAAANAPn/AAAAAAAAAAANAPr/AAAAAAAAAAANAPv/AAAAAAAAAAANAPz/AAAAAAAAAAANAP3/AAAAAAAAAAANAP7/AAAAAAAAAAANAP//AAAAAAAAAAANAAAAAAAAAAAAAAANAAEAAAAAAAAAAAANAAIAAAAAAAAAAAANAAMAAAAAAAAAAAANAAQAAAAAAAAAAAANAAUAAAAAAAAAAAANAAcAAAAAAAAAAAANAAgAAAAAAAAAAAANAAkAAAAAAAAAAAANAAoAAAAAAAAAAAANAAsAAAAAAAAAAAANAAwAAAAAAAAAAAANAA0AAAAAAAAAAAANAA4AAAAAAAAAAAANAA8AAAAAAAAAAAANABAAAAAAAAAAAAANABEAAAAAAAAAAAANABIAAAAAAAAAAAANABMAAAAAAAAAAAAOAOv/AAAAAAAAAAAOAOz/AAAAAAAAAAAOAO3/AAAAAAAAAAAOAO7/AAAAAAAAAAAOAO//AAAAAAAAAAAOAPD/AAAAAAAAAAAOAPH/AAAAAAAAAAAOAPL/AAAAAAAAAAAOAPP/AAAAAAAAAAAOAPT/AAAAAAAAAAAOAPX/AAAAAAAAAAAOAPb/AAAAAAAAAAAOAPf/AAAAAAAAAAAOAPn/AAAAAAAAAAAOAPr/AAAAAAAAAAAOAPv/AAAAAAAAAAAOAPz/AAAAAAAAAAAOAP3/AAAAAAAAAAAOAP7/AAAAAAAAAAAOAP//AAAAAAAAAAAOAAAAAAAAAAAAAAAOAAEAAAAAAAAAAAAOAAIAAAAAAAAAAAAOAAMAAAAAAAAAAAAOAAQAAAAAAAAAAAAOAAUAAAAAAAAAAAAOAAcAAAAAAAAAAAAOAAgAAAAAAAAAAAAOAAkAAAAAAAAAAAAOAAoAAAAAAAAAAAAOAAsAAAAAAAAAAAAOAAwAAAAAAAAAAAAOAA0AAAAAAAAAAAAOAA4AAAAAAAAAAAAOAA8AAAAAAAAAAAAOABAAAAAAAAAAAAAOABEAAAAAAAAAAAAOABIAAAAAAAAAAAAOABMAAAAAAAAAAAAPAOv/AAAAAAAAAAAPAOz/AAAAAAAAAAAPAO3/AAAAAAAAAAAPAO7/AAAAAAAAAAAPAO//AAAAAAAAAAAPAPD/AAAAAAAAAAAPAPH/AAAAAAAAAAAPAPL/AAAAAAAAAAAPAPP/AAAAAAAAAAAPAPT/AAAAAAAAAAAPAPX/AAAAAAAAAAAPAPb/AAAAAAAAAAAPAPf/AAAAAAAAAAAPAPj/AAAAAAAAAAAPAPr/AAAAAAAAAAAPAPv/AAAAAAAAAAAPAPz/AAAAAAAAAAAPAP3/AAAAAAAAAAAPAP7/AAAAAAAAAAAPAP//AAAAAAAAAAAPAAAAAAAAAAAAAAAPAAEAAAAAAAAAAAAPAAIAAAAAAAAAAAAPAAMAAAAAAAAAAAAPAAQAAAAAAAAAAAAPAAYAAAAAAAAAAAAPAAcAAAAAAAAAAAAPAAgAAAAAAAAAAAAPAAkAAAAAAAAAAAAPAAoAAAAAAAAAAAAPAAsAAAAAAAAAAAAPAAwAAAAAAAAAAAAPAA0AAAAAAAAAAAAPAA4AAAAAAAAAAAAPAA8AAAAAAAAAAAAPABAAAAAAAAAAAAAPABEAAAAAAAAAAAAPABIAAAAAAAAAAAAPABMAAAAAAAAAAAAQAOv/AAAAAAAAAAAQAOz/AAAAAAAAAAAQAO3/AAAAAAAAAAAQAO7/AAAAAAAAAAAQAO//AAAAAAAAAAAQAPD/AAAAAAAAAAAQAPH/AAAAAAAAAAAQAPL/AAAAAAAAAAAQAPP/AAAAAAAAAAAQAPT/AAAAAAAAAAAQAPX/AAAAAAAAAAAQAPb/AAAAAAAAAAAQAPf/AAAAAAAAAAAQAPj/AAAAAAAAAAAQAPn/AAAAAAAAAAAQAPv/AAAAAAAAAAAQAPz/AAAAAAAAAAAQAP3/AAAAAAAAAAAQAP7/AAAAAAAAAAAQAP//AAAAAAAAAAAQAAAAAAAAAAAAAAAQAAEAAAAAAAAAAAAQAAIAAAAAAAAAAAAQAAMAAAAAAAAAAAAQAAQAAAAAAAAAAAAQAAYAAAAAAAAAAAAQAAcAAAAAAAAAAAAQAAgAAAAAAAAAAAAQAAkAAAAAAAAAAAAQAAoAAAAAAAAAAAAQAAsAAAAAAAAAAAAQAAwAAAAAAAAAAAAQAA0AAAAAAAAAAAAQAA4AAAAAAAAAAAAQAA8AAAAAAAAAAAAQABAAAAAAAAAAAAAQABEAAAAAAAAAAAAQABIAAAAAAAAAAAAQABMAAAAAAAAAAAARAOv/AAAAAAAAAAARAOz/AAAAAAAAAAARAO3/AAAAAAAAAAARAO7/AAAAAAAAAAARAO//AAAAAAAAAAARAPD/AAAAAAAAAAARAPH/AAAAAAAAAAARAPL/AAAAAAAAAAARAPP/AAAAAAAAAAARAPT/AAAAAAAAAAARAPX/AAAAAAAAAAARAPb/AAAAAAAAAAARAPf/AAAAAAAAAAARAPj/AAAAAAAAAAARAPn/AAAAAAAAAAARAPv/AAAAAAAAAAARAPz/AAAAAAAAAAARAP3/AAAAAAAAAAARAP7/AAAAAAAAAAARAP//AAAAAAAAAAARAAAAAAAAAAAAAAARAAEAAAAAAAAAAAARAAIAAAAAAAAAAAARAAMAAAAAAAAAAAARAAUAAAAAAAAAAAARAAYAAAAAAAAAAAARAAcAAAAAAAAAAAARAAgAAAAAAAAAAAARAAkAAAAAAAAAAAARAAoAAAAAAAAAAAARAAsAAAAAAAAAAAARAAwAAAAAAAAAAAARAA0AAAAAAAAAAAARAA4AAAAAAAAAAAARAA8AAAAAAAAAAAARABAAAAAAAAAAAAARABEAAAAAAAAAAAARABIAAAAAAAAAAAARABMAAAAAAAAAAAASAOv/AAAAAAAAAAASAOz/AAAAAAAAAAASAO3/AAAAAAAAAAASAO7/AAAAAAAAAAASAO//AAAAAAAAAAASAPD/AAAAAAAAAAASAPH/AAAAAAAAAAASAPL/AAAAAAAAAAASAPP/AAAAAAAAAAASAPT/AAAAAAAAAAASAPX/AAAAAAAAAAASAPb/AAAAAAAAAAASAPf/AAAAAAAAAAASAPj/AAAAAAAAAAASAPn/AAAAAAAAAAASAPr/AAAAAAAAAAASAP3/AAAAAAAAAAASAP7/AAAAAAAAAAASAP//AAAAAAAAAAASAAAAAAAAAAAAAAASAAEAAAAAAAAAAAASAAIAAAAAAAAAAAASAAMAAAAAAAAAAAASAAUAAAAAAAAAAAASAAYAAAAAAAAAAAASAAcAAAAAAAAAAAASAAgAAAAAAAAAAAASAAkAAAAAAAAAAAASAAoAAAAAAAAAAAASAAsAAAAAAAAAAAASAAwAAAAAAAAAAAASAA0AAAAAAAAAAAASAA4AAAAAAAAAAAASAA8AAAAAAAAAAAASABAAAAAAAAAAAAASABEAAAAAAAAAAAASABIAAAAAAAAAAAASABMAAAAAAAAAAAATAOv/AAAAAAAAAAATAOz/AAAAAAAAAAATAO3/AAAAAAAAAAATAO7/AAAAAAAAAAATAO//AAAAAAAAAAATAPD/AAAAAAAAAAATAPH/AAAAAAAAAAATAPL/AAAAAAAAAAATAPP/AAAAAAAAAAATAPT/AAAAAAAAAAATAPX/AAAAAAAAAAATAPb/AAAAAAAAAAATAPf/AAAAAAAAAAATAPj/AAAAAAAAAAATAPn/AAAAAAAAAAATAPr/AAAAAAAAAAATAPv/AAAAAAAAAAATAPz/AAAAAAAAAAATAP7/AAAAAAAAAAATAP//AAAAAAAAAAATAAAAAAAAAAAAAAATAAEAAAAAAAAAAAATAAIAAAAAAAAAAAATAAQAAAAAAAAAAAATAAUAAAAAAAAAAAATAAYAAAAAAAAAAAATAAcAAAAAAAAAAAATAAgAAAAAAAAAAAATAAkAAAAAAAAAAAATAAoAAAAAAAAAAAATAAsAAAAAAAAAAAATAAwAAAAAAAAAAAATAA0AAAAAAAAAAAATAA4AAAAAAAAAAAATAA8AAAAAAAAAAAATABAAAAAAAAAAAAATABEAAAAAAAAAAAATABIAAAAAAAAAAAATABMAAAAAAAAAAAAUAOv/AAAAAAAAAAAUAOz/AAAAAAAAAAAUAO3/AAAAAAAAAAAUAO7/AAAAAAAAAAAUAO//AAAAAAAAAAAUAPD/AAAAAAAAAAAUAPH/AAAAAAAAAAAUAPL/AAAAAAAAAAAUAPP/AAAAAAAAAAAUAPT/AAAAAAAAAAAUAPX/AAAAAAAAAAAUAPb/AAAAAAAAAAAUAPf/AAAAAAAAAAAUAPj/AAAAAAAAAAAUAPn/AAAAAAAAAAAUAPr/AAAAAAAAAAAUAPv/AAAAAAAAAAAUAPz/AAAAAAAAAAAUAP3/AAAAAAAAAAAUAAMAAAAAAAAAAAAUAAQAAAAAAAAAAAAUAAUAAAAAAAAAAAAUAAYAAAAAAAAAAAAUAAcAAAAAAAAAAAAUAAgAAAAAAAAAAAAUAAkAAAAAAAAAAAAUAAoAAAAAAAAAAAAUAAsAAAAAAAAAAAAUAAwAAAAAAAAAAAAUAA0AAAAAAAAAAAAUAA4AAAAAAAAAAAAUAA8AAAAAAAAAAAAUABAAAAAAAAAAAAAUABEAAAAAAAAAAAAUABIAAAAAAAAAAAAUABMAAAAAAAAAAAAVAOv/AAAAAAAAAAAVAOz/AAAAAAAAAAAVAO3/AAAAAAAAAAAVAO7/AAAAAAAAAAAVAO//AAAAAAAAAAAVAPD/AAAAAAAAAAAVAPH/AAAAAAAAAAAVAPL/AAAAAAAAAAAVAPP/AAAAAAAAAAAVAPT/AAAAAAAAAAAVAPX/AAAAAAAAAAAVAPb/AAAAAAAAAAAVAPf/AAAAAAAAAAAVAPj/AAAAAAAAAAAVAPn/AAAAAAAAAAAVAPr/AAAAAAAAAAAVAPv/AAAAAAAAAAAVAPz/AAAAAAAAAAAVAP3/AAAAAAAAAAAVAP7/AAAAAAAAAAAVAP//AAAAAAAAAAAVAAAAAAAAAAAAAAAVAAEAAAAAAAAAAAAVAAIAAAAAAAAAAAAVAAMAAAAAAAAAAAAVAAQAAAAAAAAAAAAVAAUAAAAAAAAAAAAVAAYAAAAAAAAAAAAVAAcAAAAAAAAAAAAVAAgAAAAAAAAAAAAVAAkAAAAAAAAAAAAVAAoAAAAAAAAAAAAVAAsAAAAAAAAAAAAVAAwAAAAAAAAAAAAVAA0AAAAAAAAAAAAVAA4AAAAAAAAAAAAVAA8AAAAAAAAAAAAVABAAAAAAAAAAAAAVABEAAAAAAAAAAAAVABIAAAAAAAAAAAAVABMAAAAAAAAAAAAWAOv/AAAAAAAAAAAWAOz/AAAAAAAAAAAWAO3/AAAAAAAAAAAWAO7/AAAAAAAAAAAWAO//AAAAAAAAAAAWAPD/AAAAAAAAAAAWAPH/AAAAAAAAAAAWAPL/AAAAAAAAAAAWAPP/AAAAAAAAAAAWAPT/AAAAAAAAAAAWAPX/AAAAAAAAAAAWAPb/AAAAAAAAAAAWAPf/AAAAAAAAAAAWAPj/AAAAAAAAAAAWAPn/AAAAAAAAAAAWAPr/AAAAAAAAAAAWAPv/AAAAAAAAAAAWAPz/AAAAAAAAAAAWAP3/AAAAAAAAAAAWAP7/AAAAAAAAAAAWAP//AAAAAAAAAAAWAAAAAAAAAAAAAAAWAAEAAAAAAAAAAAAWAAIAAAAAAAAAAAAWAAMAAAAAAAAAAAAWAAQAAAAAAAAAAAAWAAUAAAAAAAAAAAAWAAYAAAAAAAAAAAAWAAcAAAAAAAAAAAAWAAgAAAAAAAAAAAAWAAkAAAAAAAAAAAAWAAoAAAAAAAAAAAAWAAsAAAAAAAAAAAAWAAwAAAAAAAAAAAAWAA0AAAAAAAAAAAAWAA4AAAAAAAAAAAAWAA8AAAAAAAAAAAAWABAAAAAAAAAAAAAWABEAAAAAAAAAAAAWABIAAAAAAAAAAAAWABMAAAAAAAAAAAAXAOv/AAAAAAAAAAAXAOz/AAAAAAAAAAAXAO3/AAAAAAAAAAAXAO7/AAAAAAAAAAAXAO//AAAAAAAAAAAXAPD/AAAAAAAAAAAXAPH/AAAAAAAAAAAXAPL/AAAAAAAAAAAXAPP/AAAAAAAAAAAXAPT/AAAAAAAAAAAXAPX/AAAAAAAAAAAXAPb/AAAAAAAAAAAXAPf/AAAAAAAAAAAXAPj/AAAAAAAAAAAXAPn/AAAAAAAAAAAXAPr/AAAAAAAAAAAXAPv/AAAAAAAAAAAXAPz/AAAAAAAAAAAXAP3/AAAAAAAAAAAXAP7/AAAAAAAAAAAXAP//AAAAAAAAAAAXAAAAAAAAAAAAAAAXAAEAAAAAAAAAAAAXAAIAAAAAAAAAAAAXAAMAAAAAAAAAAAAXAAQAAAAAAAAAAAAXAAUAAAAAAAAAAAAXAAYAAAAAAAAAAAAXAAcAAAAAAAAAAAAXAAgAAAAAAAAAAAAXAAkAAAAAAAAAAAAXAAoAAAAAAAAAAAAXAAsAAAAAAAAAAAAXAAwAAAAAAAAAAAAXAA0AAAAAAAAAAAAXAA4AAAAAAAAAAAAXAA8AAAAAAAAAAAAXABAAAAAAAAAAAAAXABEAAAAAAAAAAAAXABIAAAAAAAAAAAAXABMAAAAAAAAAAAAYAOv/AAAAAAAAAAAYAOz/AAAAAAAAAAAYAO3/AAAAAAAAAAAYAO7/AAAAAAAAAAAYAO//AAAAAAAAAAAYAPD/AAAAAAAAAAAYAPH/AAAAAAAAAAAYAPL/AAAAAAAAAAAYAPP/AAAAAAAAAAAYAPT/AAAAAAAAAAAYAPX/AAAAAAAAAAAYAPb/AAAAAAAAAAAYAPf/AAAAAAAAAAAYAPj/AAAAAAAAAAAYAPn/AAAAAAAAAAAYAPr/AAAAAAAAAAAYAPv/AAAAAAAAAAAYAPz/AAAAAAAAAAAYAP3/AAAAAAAAAAAYAP7/AAAAAAAAAAAYAP//AAAAAAAAAAAYAAAAAAAAAAAAAAAYAAEAAAAAAAAAAAAYAAIAAAAAAAAAAAAYAAMAAAAAAAAAAAAYAAQAAAAAAAAAAAAYAAUAAAAAAAAAAAAYAAYAAAAAAAAAAAAYAAcAAAAAAAAAAAAYAAgAAAAAAAAAAAAYAAkAAAAAAAAAAAAYAAoAAAAAAAAAAAAYAAsAAAAAAAAAAAAYAAwAAAAAAAAAAAAYAA0AAAAAAAAAAAAYAA4AAAAAAAAAAAAYAA8AAAAAAAAAAAAYABAAAAAAAAAAAAAYABEAAAAAAAAAAAAYABIAAAAAAAAAAAAYABMAAAAAAAAAAAAZAOv/AAAAAAAAAAAZAOz/AAAAAAAAAAAZAO3/AAAAAAAAAAAZAO7/AAAAAAAAAAAZAO//AAAAAAAAAAAZAPD/AAAAAAAAAAAZAPH/AAAAAAAAAAAZAPL/AAAAAAAAAAAZAPP/AAAAAAAAAAAZAPT/AAAAAAAAAAAZAPX/AAAAAAAAAAAZAPb/AAAAAAAAAAAZAPf/AAAAAAAAAAAZAPj/AAAAAAAAAAAZAPn/AAAAAAAAAAAZAPr/AAAAAAAAAAAZAPv/AAAAAAAAAAAZAPz/AAAAAAAAAAAZAP3/AAAAAAAAAAAZAP7/AAAAAAAAAAAZAP//AAAAAAAAAAAZAAAAAAAAAAAAAAAZAAEAAAAAAAAAAAAZAAIAAAAAAAAAAAAZAAMAAAAAAAAAAAAZAAQAAAAAAAAAAAAZAAUAAAAAAAAAAAAZAAYAAAAAAAAAAAAZAAcAAAAAAAAAAAAZAAgAAAAAAAAAAAAZAAkAAAAAAAAAAAAZAAoAAAAAAAAAAAAZAAsAAAAAAAAAAAAZAAwAAAAAAAAAAAAZAA0AAAAAAAAAAAAZAA4AAAAAAAAAAAAZAA8AAAAAAAAAAAAZABAAAAAAAAAAAAAZABEAAAAAAAAAAAAZABIAAAAAAAAAAAAZABMAAAAAAAAAAAAaAOv/AAAAAAAAAAAaAOz/AAAAAAAAAAAaAO3/AAAAAAAAAAAaAO7/AAAAAAAAAAAaAO//AAAAAAAAAAAaAPD/AAAAAAAAAAAaAPH/AAAAAAAAAAAaAPL/AAAAAAAAAAAaAPP/AAAAAAAAAAAaAPT/AAAAAAAAAAAaAPX/AAAAAAAAAAAaAPb/AAAAAAAAAAAaAPf/AAAAAAAAAAAaAPj/AAAAAAAAAAAaAPn/AAAAAAAAAAAaAPr/AAAAAAAAAAAaAPv/AAAAAAAAAAAaAPz/AAAAAAAAAAAaAP3/AAAAAAAAAAAaAP7/AAAAAAAAAAAaAP//AAAAAAAAAAAaAAAAAAAAAAAAAAAaAAEAAAAAAAAAAAAaAAIAAAAAAAAAAAAaAAMAAAAAAAAAAAAaAAQAAAAAAAAAAAAaAAUAAAAAAAAAAAAaAAYAAAAAAAAAAAAaAAcAAAAAAAAAAAAaAAgAAAAAAAAAAAAaAAkAAAAAAAAAAAAaAAoAAAAAAAAAAAAaAAsAAAAAAAAAAAAaAAwAAAAAAAAAAAAaAA0AAAAAAAAAAAAaAA4AAAAAAAAAAAAaAA8AAAAAAAAAAAAaABAAAAAAAAAAAAAaABEAAAAAAAAAAAAaABIAAAAAAAAAAAAaABMAAAAAAAAAAAAbAOv/AAAAAAAAAAAbAOz/AAAAAAAAAAAbAO3/AAAAAAAAAAAbAO7/AAAAAAAAAAAbAO//AAAAAAAAAAAbAPD/AAAAAAAAAAAbAPH/AAAAAAAAAAAbAPL/AAAAAAAAAAAbAPP/AAAAAAAAAAAbAPT/AAAAAAAAAAAbAPX/AAAAAAAAAAAbAPb/AAAAAAAAAAAbAPf/AAAAAAAAAAAbAPj/AAAAAAAAAAAbAPn/AAAAAAAAAAAbAPr/AAAAAAAAAAAbAPv/AAAAAAAAAAAbAPz/AAAAAAAAAAAbAP3/AAAAAAAAAAAbAP7/AAAAAAAAAAAbAP//AAAAAAAAAAAbAAAAAAAAAAAAAAAbAAEAAAAAAAAAAAAbAAIAAAAAAAAAAAAbAAMAAAAAAAAAAAAbAAQAAAAAAAAAAAAbAAUAAAAAAAAAAAAbAAYAAAAAAAAAAAAbAAcAAAAAAAAAAAAbAAgAAAAAAAAAAAAbAAkAAAAAAAAAAAAbAAoAAAAAAAAAAAAbAAsAAAAAAAAAAAAbAAwAAAAAAAAAAAAbAA0AAAAAAAAAAAAbAA4AAAAAAAAAAAAbAA8AAAAAAAAAAAAbABAAAAAAAAAAAAAbABEAAAAAAAAAAAAbABIAAAAAAAAAAAAbABMAAAAAAAAAAAAcAOv/AAAAAAAAAAAcAOz/AAAAAAAAAAAcAO3/AAAAAAAAAAAcAO7/AAAAAAAAAAAcAO//AAAAAAAAAAAcAPD/AAAAAAAAAAAcAPH/AAAAAAAAAAAcAPL/AAAAAAAAAAAcAPP/AAAAAAAAAAAcAPT/AAAAAAAAAAAcAPX/AAAAAAAAAAAcAPb/AAAAAAAAAAAcAPf/AAAAAAAAAAAcAPj/AAAAAAAAAAAcAPn/AAAAAAAAAAAcAPr/AAAAAAAAAAAcAPv/AAAAAAAAAAAcAPz/AAAAAAAAAAAcAP3/AAAAAAAAAAAcAP7/AAAAAAAAAAAcAP//AAAAAAAAAAAcAAAAAAAAAAAAAAAcAAEAAAAAAAAAAAAcAAIAAAAAAAAAAAAcAAMAAAAAAAAAAAAcAAQAAAAAAAAAAAAcAAUAAAAAAAAAAAAcAAYAAAAAAAAAAAAcAAcAAAAAAAAAAAAcAAgAAAAAAAAAAAAcAAkAAAAAAAAAAAAcAAoAAAAAAAAAAAAcAAsAAAAAAAAAAAAcAAwAAAAAAAAAAAAcAA0AAAAAAAAAAAAcAA4AAAAAAAAAAAAcAA8AAAAAAAAAAAAcABAAAAAAAAAAAAAcABEAAAAAAAAAAAAcABIAAAAAAAAAAAAcABMAAAAAAAAAAAA=") 27 | tile_set = SubResource("TileSet_lb0st") 28 | 29 | [node name="DualContourIsolinesGrid" type="Node2D" parent="." unique_id=1559129779] 30 | scale = Vector2(8, 8) 31 | script = ExtResource("1_o27md") 32 | mixer = ExtResource("4_3ndmp") 33 | default_material = ExtResource("3_3ndmp") 34 | materials = Dictionary[int, Material]({ 35 | 1: ExtResource("3_3ndmp"), 36 | 2: ExtResource("4_lb0st"), 37 | 3: ExtResource("5_xsmdc") 38 | }) 39 | metadata/_custom_type_script = "uid://chh0ri02qmrv4" 40 | 41 | [node name="Player" parent="." unique_id=160405911 instance=ExtResource("6_fmwbw")] 42 | 43 | [node name="Camera2D" type="Camera2D" parent="Player" unique_id=829491219] 44 | zoom = Vector2(3, 3) 45 | process_callback = 0 46 | position_smoothing_enabled = true 47 | 48 | [node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=1710054865] 49 | 50 | [node name="Label" type="Label" parent="CanvasLayer" unique_id=209402566] 51 | anchors_preset = -1 52 | anchor_left = 0.021701388 53 | anchor_top = 0.85339504 54 | anchor_right = 0.1640625 55 | anchor_bottom = 0.962963 56 | grow_vertical = 0 57 | theme_override_constants/outline_size = 8 58 | theme_override_font_sizes/font_size = 24 59 | text = "Left-click: Fire 60 | Right-click: Ice" 61 | metadata/_edit_use_anchors_ = true 62 | 63 | [connection signal="brush_applied" from="DualContourIsolinesGrid" to="." method="_on_dual_contour_isolines_grid_brush_applied"] 64 | [connection signal="clicky" from="Player" to="." method="_on_player_clicky"] 65 | --------------------------------------------------------------------------------