Please find below an implementation of the “tiled model”.
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:
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)