"Wave Collapse Function" algorithm in Processing

Please find below an implementation of the “tiled model”.

com-optimize(1)

The difference is that, unlike the “overlapping model” above, adjacency constraints are not inferred from a source image. As a result, constraints and frequencies (if needed) have to be entered manually (hard-coded).

A couple of improvements

Writing all the adjacencies of each tile can be cumbersome (especially with large tilesets) so I decided to add a connector-based system (inspired by this article by Marian42) to facilitate the task. Instead of storing all the possible adjacent tiles in each direction for each tile, it is now only required to associate each face of a tile with its corresponding symmetry index. All faces sharing the same symmetry index will be adjacent.

Also, because most tilesets don’t come with the rotated versions of each tile, I added a mechanism that rotates a tile and stores the transformated output based on its file name.

Here for example, tile “a” will be rotated 3 times at a 90-degree angle and each transformation will be added to the tiles array. “b” is not taken into account and “c” will be rotated only once. As a result the final array will contain 7 tiles:
Capture%20d%E2%80%99%C3%A9cran%20(134)

The mechanism also automatically creates and rotates the connectors (arrays of symmetry indices) of each new rotated tile.

Limits

In some cases you may want (for cosmetic reasons) to prevent a specific tile to be placed near another tile even though adjacent rules allow this placement. So far I’m writing these avoidance rules (as I call them) by hand but it would be really convenient (provided it is possible) to automate that part as much as possible.

Full script

from random import choice, sample
from collections import deque
from java.io import File

w, h, s = 80, 45, 10
directions = ((-1, 0), (1, 0), (0, -1), (0, 1))

def setup():
    size(w*s, h*s, P2D)
    background('#FFFFFF')
    frameRate(1000)
    
    global W, A, H, tiles
    
    path = File("\Knots").listFiles()
    connectors = [(0, 1, 1, 0), (0, 0, 0, 0), (1, 0, 1, 0)]
        
    tiles = []
    count = 0
    
    # Iterating through a folder of unique tiles
    for i, png in enumerate(path):
        
        # appending tile to 'tiles' array lit
        tiles.append(loadImage(png.getAbsolutePath()))
                
        # if rotation information included in tile name --> allow rotation
        name = png.getName().split('.')[0]
        if '-' in name:

            # rotate by 90° the number of times specified in tile name 
            for rot in xrange(int(name[-1])):
                cp = tiles[-1]
                pg = createGraphics(cp.width, cp.height)
                pg.beginDraw()
                pg.pushMatrix()
                pg.translate(cp.width, 0)
                pg.rotate(HALF_PI)
                pg.image(cp, 0, 0)
                pg.popMatrix()
                pg.endDraw()
                
                # add rotated tile to 'tiles' array list
                tiles.append(pg)
  
                # create a corresponding "connector" array by rotating the indices of the previous tile
                c = deque(connectors[i + count])
                c.rotate(1)
                count += 1
                connectors.insert(i + count, tuple(c))
                
            
    ntiles = len(tiles) 

    W = dict(enumerate(tuple(set(range(ntiles)) for i in xrange(w*h)))) 
    H = dict(enumerate(sample(tuple(ntiles if i > 0 else ntiles-1 for i in xrange(w*h)), w*h)))
    A = dict(enumerate([[set() for dir in xrange(4)] for i in xrange(ntiles)]))
    
    for i1 in xrange(ntiles):
        for i2 in xrange(ntiles):
            if connectors[i1][0] == connectors[i2][2]:
                A[i1][0].add(i2)
                A[i2][1].add(i1)
                
            if connectors[i1][1] == connectors[i2][3]: 
                A[i1][2].add(i2)
                A[i2][3].add(i1)
    
                
def draw():    
    global H, W
    
    if not H:
        print 'finished'
        noLoop()
        return

    emin = min(H, key = H.get) 
    id = choice(list(W[emin]))
    W[emin] = {id}
    del H[emin]
    
    stack = {emin}
    while stack:
        idC = stack.pop() 
        for dir, t in enumerate(directions):
            x = (idC%w + t[0])%w
            y = (idC/w + t[1])%h
            idN = x + y * w 
            if idN in H: 
                possible = {n for idP in W[idC] for n in A[idP][dir]}
                if not W[idN].issubset(possible):
                    intersection = possible & W[idN] 
                
                    if not intersection:
                        print 'contradiction'
                        noLoop()
                        break

                    W[idN] = intersection
                    H[idN] = len(W[idN]) - random(.1)
                    stack.add(idN)
   
    image(tiles[id], (emin%w) * s, (emin/w) * s, s, s)
8 Likes