Naive WFC kind of working
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
63
client.gd
Normal file
63
client.gd
Normal file
@@ -0,0 +1,63 @@
|
||||
extends Node
|
||||
|
||||
#Master list of chunks
|
||||
var _chunks: Array[Chunk] = []
|
||||
|
||||
#Create some test samples - these samples must ALWAYS be 9-elements long, 5th element is the result
|
||||
var _samples: Array[PackedInt32Array] = [
|
||||
[1,1,1, 1,1,1, 1,1,1],
|
||||
]
|
||||
|
||||
@onready
|
||||
var wfc: Node = get_node("../Generator")
|
||||
|
||||
func _ready() -> void:
|
||||
var ruleset: PackedInt32Array = read_sample_ruleset("sample1.png")
|
||||
var samples: Array[PackedInt32Array] = parse_samples_from_ruleset(ruleset)
|
||||
|
||||
#print(ruleset)
|
||||
#print(samples)
|
||||
wfc.generate_chunk_at(0,0,_chunks,samples)
|
||||
|
||||
## Read the png file, and parse it to a useable ruleset
|
||||
func read_sample_ruleset(filename: String) -> PackedInt32Array:
|
||||
var img: Image = Image.load_from_file(filename)
|
||||
var png: PackedByteArray = img.get_data()
|
||||
var hex: PackedInt32Array = []
|
||||
|
||||
@warning_ignore("integer_division")
|
||||
var size: int = (png.size() / 3)
|
||||
hex.resize(size)
|
||||
|
||||
print(png)
|
||||
for i in range(size): #the file is assumed to be in RGB format
|
||||
hex[i] = (png[i * 3] << 16) | (png[i * 3 + 1] << 8) | (png[i * 3 + 2] << 0)
|
||||
print(i, "(", hex[i], "): ", png[i * 3], ",", png[i * 3 + 1], ",", png[i * 3 + 2])
|
||||
return hex
|
||||
|
||||
func parse_samples_from_ruleset(ruleset: PackedInt32Array) -> Array[PackedInt32Array]:
|
||||
#for now, assume the ruleset is 8x8
|
||||
const RULESET_WIDTH: int = 8
|
||||
const RULESET_HEIGHT: int = 8
|
||||
|
||||
var samples: Array[PackedInt32Array] = []
|
||||
|
||||
for x in range(1, RULESET_WIDTH-1):
|
||||
for y in range(1, RULESET_HEIGHT-1):
|
||||
var sample: PackedInt32Array = [
|
||||
ruleset[(y -1) * RULESET_WIDTH + (x -1)],
|
||||
ruleset[(y -1) * RULESET_WIDTH + (x )],
|
||||
ruleset[(y -1) * RULESET_WIDTH + (x +1)],
|
||||
|
||||
ruleset[(y ) * RULESET_WIDTH + (x -1)],
|
||||
ruleset[(y ) * RULESET_WIDTH + (x )],
|
||||
ruleset[(y ) * RULESET_WIDTH + (x +1)],
|
||||
|
||||
ruleset[(y +1) * RULESET_WIDTH + (x -1)],
|
||||
ruleset[(y +1) * RULESET_WIDTH + (x )],
|
||||
ruleset[(y +1) * RULESET_WIDTH + (x +1)],
|
||||
]
|
||||
|
||||
samples.append(sample)
|
||||
|
||||
return samples
|
||||
1
client.gd.uid
Normal file
1
client.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://jmttidfcknea
|
||||
1
icon.svg
Normal file
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
After Width: | Height: | Size: 995 B |
43
icon.svg.import
Normal file
43
icon.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ba3kljgd5yvc2"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
20
project.godot
Normal file
20
project.godot
Normal file
@@ -0,0 +1,20 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Wave"
|
||||
run/main_scene="uid://d2g4ooi0x6ebo"
|
||||
config/features=PackedStringArray("4.5", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="mobile"
|
||||
BIN
sample1.png
Normal file
BIN
sample1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 B |
40
sample1.png.import
Normal file
40
sample1.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dy1xsv1acvc4i"
|
||||
path="res://.godot/imported/sample1.png-8b828765c2f6336dd2e8e4a034a21542.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sample1.png"
|
||||
dest_files=["res://.godot/imported/sample1.png-8b828765c2f6336dd2e8e4a034a21542.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
12
scene.tscn
Normal file
12
scene.tscn
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://d2g4ooi0x6ebo"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bf6phxhnvh0ul" path="res://wfc/generator.gd" id="1_ulcgi"]
|
||||
[ext_resource type="Script" uid="uid://jmttidfcknea" path="res://client.gd" id="2_nxogm"]
|
||||
|
||||
[node name="Scene" type="Node2D"]
|
||||
|
||||
[node name="Generator" type="Node" parent="."]
|
||||
script = ExtResource("1_ulcgi")
|
||||
|
||||
[node name="Client" type="Node" parent="."]
|
||||
script = ExtResource("2_nxogm")
|
||||
31
wfc/chunk.gd
Normal file
31
wfc/chunk.gd
Normal file
@@ -0,0 +1,31 @@
|
||||
class_name Chunk extends Object
|
||||
## A Chunk of map data and bundled metadata, always aligned to CHUNK_WIDTH and CHUNK_HEIGHT
|
||||
## The default data values are 0, which means "NOT SET"
|
||||
## Chunks can be partially unset, to allow for prefabs
|
||||
## Any values of -1 are "NOT VALID"
|
||||
|
||||
const CHUNK_WIDTH: int = 16
|
||||
const CHUNK_HEIGHT: int = 16
|
||||
|
||||
var data: PackedInt32Array
|
||||
var x: int
|
||||
var y: int
|
||||
|
||||
func _init(_x: int, _y: int):
|
||||
assert(_x % CHUNK_WIDTH == 0)
|
||||
assert(_y % CHUNK_HEIGHT == 0)
|
||||
x = _x
|
||||
y = _y
|
||||
data = PackedInt32Array()
|
||||
data.resize(CHUNK_WIDTH * CHUNK_HEIGHT)
|
||||
data.fill(0)
|
||||
|
||||
func get_tile(tile_x: int, tile_y: int) -> int:
|
||||
if tile_x < 0 or tile_y < 0 or tile_x >= CHUNK_WIDTH or tile_y >= CHUNK_HEIGHT: return -1
|
||||
return data[tile_y * CHUNK_WIDTH + tile_x]
|
||||
|
||||
func set_tile(tile_x: int, tile_y: int, value: int) -> int:
|
||||
assert(value > 0) #do NOT clear a tile with this function
|
||||
if tile_x < 0 or tile_y < 0 or tile_x >= CHUNK_WIDTH or tile_y >= CHUNK_HEIGHT: return -1
|
||||
data[tile_y * CHUNK_WIDTH + tile_x] = value
|
||||
return value
|
||||
1
wfc/chunk.gd.uid
Normal file
1
wfc/chunk.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://due7w7hripbuk
|
||||
52
wfc/generator.gd
Normal file
52
wfc/generator.gd
Normal file
@@ -0,0 +1,52 @@
|
||||
extends Node
|
||||
## The generator works with pure numbers, you'll need something else to convert it to actual tiles
|
||||
|
||||
## Makes a new chunk, derived from the given WFC samples
|
||||
func generate_chunk_at(_x: int, _y: int, _chunk_array: Array[Chunk], _samples: Array[PackedInt32Array]) -> Chunk:
|
||||
#TODO: fix the chunk-edges
|
||||
var chunk: Chunk = Chunk.new(_x, _y)
|
||||
|
||||
var latch: int = 1
|
||||
while latch:
|
||||
latch = 0 #if set to true, the generator needs another pass
|
||||
#iterate over unset tiles with valid neighbours
|
||||
for x in range(Chunk.CHUNK_WIDTH):
|
||||
for y in range((Chunk.CHUNK_HEIGHT)):
|
||||
if chunk.data[y * Chunk.CHUNK_HEIGHT + x] == 0: #direct access, to skip extra checks
|
||||
#TODO: convert this to the low-entropy method
|
||||
if _set_tile_from_samples(chunk, x, y, _samples) > 0:
|
||||
latch += 1
|
||||
print("latch: ", latch)
|
||||
|
||||
print(chunk.data)
|
||||
_chunk_array.append(chunk)
|
||||
return chunk
|
||||
|
||||
## Returns the value set, or -1 if no options found
|
||||
func _set_tile_from_samples(chunk: Chunk, tile_x: int, tile_y: int, _samples: Array[PackedInt32Array]) -> int:
|
||||
var valid: Array[PackedInt32Array] = []
|
||||
|
||||
#use a lambda for easy reading below
|
||||
var compare := func (tile_value: int, sample: int) -> bool:
|
||||
return tile_value <= 0 or tile_value == sample
|
||||
|
||||
#find all valid samples
|
||||
for sample in _samples:
|
||||
if !compare.call(chunk.get_tile(tile_x -1, tile_y -1), sample[0]): continue
|
||||
if !compare.call(chunk.get_tile(tile_x , tile_y -1), sample[1]): continue
|
||||
if !compare.call(chunk.get_tile(tile_x +1, tile_y -1), sample[2]): continue
|
||||
if !compare.call(chunk.get_tile(tile_x -1, tile_y ), sample[3]): continue
|
||||
# //
|
||||
if !compare.call(chunk.get_tile(tile_x +1, tile_y ), sample[5]): continue
|
||||
if !compare.call(chunk.get_tile(tile_x -1, tile_y +1), sample[6]): continue
|
||||
if !compare.call(chunk.get_tile(tile_x , tile_y +1), sample[7]): continue
|
||||
if !compare.call(chunk.get_tile(tile_x +1, tile_y +1), sample[8]): continue
|
||||
|
||||
valid.append(sample)
|
||||
print("Valid samples: ", valid.size())
|
||||
if valid.size() <= 0: return -1
|
||||
var s = valid.pick_random()
|
||||
|
||||
#set the tile to the randomly selected value
|
||||
chunk.set_tile(tile_x, tile_y, s[4])
|
||||
return s[4]
|
||||
1
wfc/generator.gd.uid
Normal file
1
wfc/generator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bf6phxhnvh0ul
|
||||
Reference in New Issue
Block a user