Advertisement
iRadEntertainment

wfc

Jan 6th, 2024
1,335
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
GDScript 12.33 KB | Haiku | 0 0
  1. # wave function collapse background
  2.  
  3. extends Control
  4.  
  5.  
  6. export (int, 12, 128) var T_SIZE : int = 64 #px
  7. export var t_cont_expand : int = 2 #extra cells
  8.  
  9. var clock = 0
  10. export var CLOCK_STEP = 0.01
  11. export var CLOCK_RESTART = 2.5
  12.  
  13. onready var t_cont : Control = $tex_container
  14. var t_folder := "res://graphics/wfc_tileset01/"
  15. var t_instructions := "instructions.txt"
  16.  
  17.  
  18. var grid_size : Vector2
  19. var tiles = [] #[ 0 img, 1 rules[right, top, left, bottom], 2 [rot, flip_h, flip_v] ]
  20. var matrix_texrect := [] # TextureRect nodes (One dimensional array)
  21. var matrix_entropy := [] # It stores the ids of the cells that can possible be there (One dimensional array)
  22.  
  23. var cell_tot : int
  24. var cell_activating := [] # (One dimensional bool array) cells activating
  25. var cell_activated := []  # (One dimensional bool array) cells activated
  26. var cell_collapsed := []  # (One dimensional bool array) cells collapsed
  27.  
  28. var bmap_activating : BitMap
  29. var bmap_activated : BitMap
  30.  
  31.  
  32.  
  33.  
  34. #=================================== INIT ======================================
  35. func _ready():
  36.     #===== TEMP
  37. #   Mng.emit_signal("scene_changed")
  38.     #===== TEMP
  39.     yield(get_tree(), "idle_frame")
  40.     create_tiles()
  41.     create_grid()
  42.     reset_matrix()
  43.  
  44.  
  45. func create_tiles():
  46.     #--- read instructions
  47.     var file = File.new()
  48.     if not file.file_exists(t_folder + t_instructions):
  49.         print("wfc bg: Cannot find instruction file for tileset @ %s"%[t_folder + t_instructions])
  50.         return false
  51.     file.open(t_folder + t_instructions, File.READ)
  52.    
  53.     #--- read original tiles
  54.    
  55.     while not file.eof_reached():
  56.         var parsed = file.get_csv_line()
  57.        
  58.         var img = load(t_folder+parsed[0])
  59.         var rules = [parsed[1], parsed[2], parsed[3], parsed[4]]
  60.        
  61.         #--- create new rotated / flipped tiles
  62.         var variations_rules = []
  63.         for rot in range (0, 360, 90):
  64.             for h in range(2):
  65.                 for v in range(2):
  66.                     var variations = [rot, h == 1, v == 1]
  67.                     var new_rules = rotate_flip_rules(rules, variations)
  68.                    
  69.                     if not new_rules in variations_rules:
  70.                         variations_rules.append(new_rules)
  71.                         tiles.append([img, new_rules, variations])
  72.     if false: # DEBUG
  73.         for tile in tiles:
  74.             print("%s, [%s, %s, %s, %s], [%s, %s, %s]"%[tile[0], tile[1][0], tile[1][1], tile[1][2], tile[1][3], tile[2][0], tile[2][1], tile[2][2]])
  75.  
  76.  
  77. func create_grid():
  78.     #--- adjust cells container rect size
  79.     t_cont.rect_size = rect_size + (Vector2.ONE * T_SIZE * t_cont_expand)
  80.     t_cont.rect_size.x = ceil(t_cont.rect_size.x / T_SIZE) * T_SIZE
  81.     t_cont.rect_size.y = ceil(t_cont.rect_size.y / T_SIZE) * T_SIZE
  82.    
  83.     #--- calculate grid size (cells)
  84.     grid_size.x = ceil(t_cont.rect_size.x / T_SIZE)
  85.     grid_size.y = ceil(t_cont.rect_size.y / T_SIZE)
  86.     bmap_activating = BitMap.new()
  87.     bmap_activated = BitMap.new()
  88.     bmap_activating.resize(grid_size)
  89.     bmap_activated.resize(grid_size)
  90.     cell_tot = grid_size.x*grid_size.y
  91.    
  92.     #--- clear texture container
  93.     for child in t_cont.get_children():
  94.         child.queue_free()
  95.    
  96.     #--- initialize matrix with TextureRect and Entropy
  97.     for _y in range(grid_size.y):
  98.         for _x in range(grid_size.x):
  99.             var tex = TextureRect.new()
  100.             tex.expand = true
  101.             tex.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
  102.             t_cont.add_child(tex)
  103.             matrix_texrect.append(tex)
  104.    
  105.     reset_matrix()
  106.  
  107.  
  108. func reset_matrix():
  109.     #--- reset texrect
  110.     for i in range(matrix_texrect.size()):
  111.         var tex : TextureRect = matrix_texrect[i]
  112.         tex.visible = false
  113.         tex.modulate.a = 0
  114.         tex.rect_size = Vector2.ONE * T_SIZE
  115.         tex.rect_pivot_offset = tex.rect_size/2
  116.         tex.rect_position = cell_id2unit_pos(i) * T_SIZE
  117.    
  118.     #--- reset entropy
  119.     cell_activating = []
  120.     cell_activated = []
  121.     cell_collapsed = []
  122.     matrix_entropy = []
  123.     for _i in range(matrix_texrect.size()):
  124.         matrix_entropy.append(range(tiles.size()))
  125.         cell_activating.append(false)
  126.         cell_activated.append(false)
  127.         cell_collapsed.append(false)
  128.    
  129.     #--- DEBUG
  130.     bmap_activating = BitMap.new()
  131.     bmap_activated = BitMap.new()
  132.     bmap_activating.resize(grid_size)
  133.     bmap_activated.resize(grid_size)
  134.  
  135.  
  136. #=========================== WAVE FUNCTION COLLAPSE ============================
  137. func collapse_cell(cell_id):
  138.     var poss : Array = matrix_entropy[cell_id]
  139.     if poss.empty():
  140.         print("wfc: error, cannot collapse cell")
  141.         return
  142.    
  143.     var cell_tile = poss[randi()%poss.size()]
  144.     matrix_entropy[cell_id] = [cell_tile]
  145.     cell_collapsed[cell_id] = true
  146.     set_cell_tile(cell_id, cell_tile)
  147.    
  148.     #--- reduce entropy from neighbours
  149.     var neighbours = cell_neighbours(cell_id)
  150.     for side in range(4):
  151.         var n_id = neighbours[side] # neighbour id
  152.         if n_id == -1 or cell_collapsed[n_id]:
  153.             continue
  154.         #--- remove options
  155.         match side:
  156.             0: # right neighbour
  157.                 for i in range(matrix_entropy[n_id].size()-1, -1, -1):
  158.                     var possible = matrix_entropy[n_id][i]
  159.                     if tiles[cell_tile][1][0] != tiles[possible][1][2]:
  160.                         matrix_entropy[n_id].erase(possible)
  161.             1: # top neighbour
  162.                 for i in range(matrix_entropy[n_id].size()-1, -1, -1):
  163.                     var possible = matrix_entropy[n_id][i]
  164.                     if tiles[cell_tile][1][1] != tiles[possible][1][3]:
  165. #                       print("removing possibility %s from top neighbour"%possible)
  166. #                       print("this cell top_rule: %s -> possible tile %s cell, rules: %s"%[tiles[cell_tile][1][1], possible, tiles[possible][1]])
  167.                         matrix_entropy[n_id].erase(possible)
  168.             2: # left neighbour
  169.                 for i in range(matrix_entropy[n_id].size()-1, -1, -1):
  170.                     var possible = matrix_entropy[n_id][i]
  171.                     if tiles[cell_tile][1][2] != tiles[possible][1][0]:
  172.                         matrix_entropy[n_id].erase(possible)
  173. #                   if i== 0:
  174. #                       breakpoint
  175.             3: # bot neighbour
  176.                 for i in range(matrix_entropy[n_id].size()-1, -1, -1):
  177.                     var possible = matrix_entropy[n_id][i]
  178.                     if tiles[cell_tile][1][3] != tiles[possible][1][1]:
  179.                         matrix_entropy[n_id].erase(possible)
  180.  
  181.  
  182. func find_least_entropy_cells() -> Array:
  183.     var cells = []
  184.     var min_entropy = INF
  185.     for cell_id in range(cell_tot):
  186.         if cell_collapsed[cell_id] or matrix_entropy[cell_id].size() < 1:
  187.             continue
  188.         var entropy = matrix_entropy[cell_id].size()
  189.         if entropy > min_entropy:
  190.             continue
  191.         elif entropy == min_entropy:
  192.             cells.append(cell_id)
  193.         else:
  194.             min_entropy = entropy
  195.             cells = [cell_id]
  196.    
  197.     return cells
  198.  
  199.  
  200.  
  201. func cell_neighbours(cell_id) -> Array: # [right, top, right, bottom] returns -1 on the sides that are invalid
  202.     var size_x = int(grid_size.x)
  203.    
  204.     var right : int = cell_id + 1 if ((cell_id+1)%size_x != 0) else -1
  205.     var top : int = cell_id - size_x if (cell_id - size_x) >= 0 else -1
  206.     var left : int = cell_id - 1 if (cell_id)%size_x != 0 else -1
  207.     var bot : int = cell_id + size_x if (cell_id + size_x < matrix_entropy.size() ) else -1
  208.    
  209.     return [right, top, left, bot]
  210.  
  211.  
  212. func collapse_next():
  213.     var least_entropy_cells = find_least_entropy_cells()
  214.     if not least_entropy_cells.empty():
  215.         var rand_id = randi()%(least_entropy_cells.size())
  216.         collapse_cell(least_entropy_cells[rand_id])
  217.  
  218.  
  219.  
  220. #============================== CANVAS ANIMATION ===============================
  221. func visualize_all_random_tiles():
  222.     for i in range(matrix_texrect.size()):
  223.         set_cell_tile(i, randi()%tiles.size())
  224.  
  225.  
  226. func set_cell_tile(cell_id, tile_id):
  227.     var tex : TextureRect = matrix_texrect[cell_id]
  228.     tex.visible = true
  229.     tex.modulate.a = 0
  230.     tex.texture = tiles[tile_id][0]
  231.     tex.rect_rotation = tiles[tile_id][2][0]
  232.     tex.flip_h = tiles[tile_id][2][1]
  233.     tex.flip_v = tiles[tile_id][2][2]
  234.    
  235.     cell_activating[cell_id] = true
  236.     bmap_activating.set_bit(cell_id2unit_pos(cell_id), true)
  237.     var texture := ImageTexture.new()
  238.     texture.create_from_image(bmap_activating.convert_to_image(), Image.FORMAT_L8)
  239.     $vb/acting.texture = texture
  240.  
  241.  
  242. func _process(d):
  243.     #--- automated cells collapse
  244.     if true:
  245.         clock += d
  246.         if not wfc_completed():
  247.             if clock >= CLOCK_STEP:
  248.                 clock = 0
  249.                 collapse_next()
  250.         elif clock >= CLOCK_RESTART:
  251.             clock = 0
  252.             reset_matrix()
  253.    
  254.    
  255.     #--- animate cells fade_in via bitmask
  256.     for i in range(cell_activating.size()):
  257.         if cell_activating[i]:
  258.             var tex : TextureRect = matrix_texrect[i]
  259.             tex.modulate.a = min(tex.modulate.a + d * 0.05/CLOCK_STEP, 1)
  260.             if tex.modulate.a >= 1:
  261.                 cell_activating[i] = false
  262.                 cell_activated[i] = true
  263.                 bmap_activated.set_bit(cell_id2unit_pos(i), true)
  264.                 var texture := ImageTexture.new()
  265.                 texture.create_from_image(bmap_activated.convert_to_image(), Image.FORMAT_L8)
  266.                 $vb/act.texture = texture
  267.                
  268.    
  269. #   move_t_cont_with_mouse()
  270.     move_t_cont_with_gravity_vector()
  271.    
  272.     #--- debug
  273.     var pos = t_cont.get_local_mouse_position() / T_SIZE
  274.     pos.x = floor(pos.x)
  275.     pos.y = floor(pos.y)
  276.     var cell = unit_pos2cell_id(pos)
  277.     if cell >= cell_collapsed.size():
  278.         return
  279.     $vb/nfo/values/id.text = "%03d"%cell
  280.     if cell_collapsed[cell]:
  281.         var tile = matrix_entropy[cell][0]
  282.         $vb/nfo/values/rules.text = "%s"%[ tiles[tile][1] ]
  283.     else:
  284.         $vb/nfo/values/rules.text = ""
  285.     $vb/nfo/values/neigh.text = "%s"%[cell_neighbours(cell)]
  286.     $vb/nfo/values/entropy.text = "%s"%[matrix_entropy[cell]]
  287.     $vb/nfo/values/collapsed.text = "%s"%cell_collapsed[cell]
  288.  
  289.  
  290. func move_t_cont_with_mouse():
  291.     var diff : Vector2 = t_cont.rect_size - rect_size
  292.     t_cont.rect_position.x = -(get_local_mouse_position().x / rect_size.x) * diff.x
  293.     t_cont.rect_position.y = -(get_local_mouse_position().y / rect_size.y) * diff.y
  294. func move_t_cont_with_gravity_vector():
  295.     var diff : Vector2 = t_cont.rect_size - rect_size
  296.     var grav = Input.get_gravity().normalized()
  297.     grav.x = (grav.x+1)/2
  298.     grav.y = (grav.y+1)/2
  299.     t_cont.rect_position.x = -grav.x * diff.x
  300.     t_cont.rect_position.y = -grav.y * diff.y
  301.  
  302. #"""
  303. #var noise := OpenSimplexNoise.new()
  304. #var canvas_vel : Vector2
  305. #const canvas_speed = 5 #px/s
  306. #func move_t_cont_with_vel(d):
  307. #   canvas_vel.x += noise.get_noise_2d(OS.get_ticks_usec()*d/1000, 0) * canvas_speed * d
  308. #   canvas_vel.y += noise.get_noise_2d(0, OS.get_ticks_usec()*d/1000) * canvas_speed * d
  309. #   if not bounce_container():
  310. #       t_cont.rect_position += canvas_vel
  311. #
  312. #
  313. #func bounce_container():
  314. #   var pos : Vector2 = t_cont.rect_position
  315. #   var diff : Vector2 = t_cont.rect_size - rect_size
  316. #   var bound_right : bool = canvas_vel.x < 0 and pos.x <= -diff.x
  317. #   var bound_left : bool = canvas_vel.x > 0 and pos.x >= 0
  318. #   var bound_up : bool = canvas_vel.y < 0 and pos.y <= -diff.y
  319. #   var bound_down : bool = canvas_vel.y > 0 and pos.y >= 0
  320. #
  321. #   if bound_right or bound_left:
  322. #       canvas_vel.x = -canvas_vel.x
  323. #       return true
  324. #   elif bound_up or bound_down:
  325. #       canvas_vel.y = -canvas_vel.y
  326. #       return true
  327. #   return false
  328. #"""
  329.  
  330.  
  331. #================================== UTILS ======================================
  332. func cell_id2unit_pos(cell_id) -> Vector2:
  333.     var pos := Vector2()
  334.     pos.x = cell_id%int(grid_size.x)
  335.     pos.y = floor(cell_id/grid_size.x)
  336.     return pos
  337. func unit_pos2cell_id(pos : Vector2) -> int:
  338.     var cell_id : int = (pos.x) + (pos.y * grid_size.x)
  339.     return cell_id
  340. func reverse_string(string : String) -> String:
  341.     var reversed := PoolStringArray([])
  342.     for character in string:
  343.         reversed.insert(0, character)
  344.    
  345.     return reversed.join("")
  346. func wfc_completed() -> bool:
  347.     for val in cell_collapsed:
  348.         if val == false:
  349.             return false
  350.     return true
  351.  
  352.  
  353. func rotate_flip_rules(rules : Array, variations : Array) -> Array:
  354.     var new_rules : Array = rules.duplicate()
  355.     #--- h_flip
  356.     if variations[1]:
  357.         var swap = new_rules[0]
  358.         new_rules[0] = new_rules[2]
  359.         new_rules[1] = reverse_string(new_rules[1])
  360.         new_rules[2] = swap
  361.         new_rules[3] = reverse_string(new_rules[3])
  362.     #--- v_flip
  363.     if variations[2]:
  364.         var swap = new_rules[1]
  365.         new_rules[0] = reverse_string(new_rules[0])
  366.         new_rules[1] = new_rules[3]
  367.         new_rules[2] = reverse_string(new_rules[2])
  368.         new_rules[3] = swap
  369.    
  370.     match variations[0]:
  371.         0: new_rules = rules
  372.         90:
  373.             new_rules[0] = rules[1]
  374.             new_rules[1] = reverse_string(rules[2])
  375.             new_rules[2] = rules[3]
  376.             new_rules[3] = reverse_string(rules[0])
  377.         180:
  378.             new_rules[0] = reverse_string(rules[2])
  379.             new_rules[1] = reverse_string(rules[3])
  380.             new_rules[2] = reverse_string(rules[0])
  381.             new_rules[3] = reverse_string(rules[1])
  382.         270:
  383.             new_rules[0] = reverse_string(rules[3])
  384.             new_rules[1] = rules[0]
  385.             new_rules[2] = reverse_string(rules[1])
  386.             new_rules[3] = rules[2]
  387.    
  388.     return new_rules
  389.  
  390.  
  391. func _input(event):
  392.     if event is InputEventMouseButton:
  393.         if event.is_pressed():
  394.             pass
  395. #           collapse_next()
  396. #          
  397. #           var pos = t_cont.get_local_mouse_position() / T_SIZE
  398. #           pos.x = floor(pos.x)
  399. #           pos.y = floor(pos.y)
  400. #           collapse_cell(unit_pos2cell_id(pos))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement