diff --git a/.gitignore b/.gitignore index 496ee2c..c8811dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.DS_Store \ No newline at end of file +.DS_Store +render* \ No newline at end of file diff --git a/Processing/wfc_overlapping/Cell.pde b/Processing/wfc_overlapping/Cell.pde new file mode 100644 index 0000000..c954f49 --- /dev/null +++ b/Processing/wfc_overlapping/Cell.pde @@ -0,0 +1,54 @@ + +class Cell { + float x, y, w; + int index; + ArrayList options; + boolean collapsed; + boolean checked; + + Cell(ArrayList tiles, float x, float y, float w, int index) { + this.x = x; + this.y = y; + this.w = w; + this.index = index; + this.options = new ArrayList(); + this.collapsed = false; + this.checked = false; + for (int i = 0; i < tiles.size(); i++) { + this.options.add(i); + } + } + + boolean show() { + if (this.options.size() == 0) { + fill(255, 0, 255); + square(this.x, this.y, this.w); + return true; + } else if (this.collapsed) { + int tileIndex = this.options.get(0); + PImage img = tiles.get(tileIndex).img; + renderCell(img, this.x, this.y, this.w); + } else { + float sumR = 0; + float sumG = 0; + float sumB = 0; + for (int i = 0; i < this.options.size(); i++) { + int tileIndex = this.options.get(i); + PImage img = tiles.get(tileIndex).img; + int index = 1 + 1 * img.width; // center pixel if 3x3 tile + int col = img.pixels[index]; + sumR += red(col); + sumG += green(col); + sumB += blue(col); + } + sumR /= this.options.size(); + sumG /= this.options.size(); + sumB /= this.options.size(); + fill(sumR, sumG, sumB); + stroke(0); + strokeWeight(0.5); + square(this.x, this.y, this.w); + } + return false; + } +} diff --git a/Processing/wfc_overlapping/Tile.pde b/Processing/wfc_overlapping/Tile.pde new file mode 100644 index 0000000..1528412 --- /dev/null +++ b/Processing/wfc_overlapping/Tile.pde @@ -0,0 +1,82 @@ + +class Tile { + PImage img; + int index; + ArrayList[] neighbors; + + Tile(PImage img, int i) { + this.img = img; + this.img.loadPixels(); + this.index = i; + neighbors = new ArrayList[4]; + for (int j = 0; j < 4; j++) { + neighbors[j] = new ArrayList(); + } + } + + void calculateNeighbors(ArrayList tiles) { + for (int i = 0; i < tiles.size(); i++) { + if (this.overlapping(tiles.get(i), TRIGHT)) { + neighbors[TRIGHT].add(i); + } + if (this.overlapping(tiles.get(i), TLEFT)) { + neighbors[TLEFT].add(i); + } + if (this.overlapping(tiles.get(i), TUP)) { + neighbors[TUP].add(i); + } + if (this.overlapping(tiles.get(i), TDOWN)) { + neighbors[TDOWN].add(i); + } + } + } + + boolean overlapping(Tile other, int direction) { + if (direction == TRIGHT) { + for (int i = 1; i < 3; i++) { + for (int j = 0; j < 3; j++) { + int indexA = i + j * 3; + int indexB = (i - 1) + j * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TLEFT) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + int indexA = i + j * 3; + int indexB = (i + 1) + j * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TUP) { + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 3; i++) { + int indexA = i + j * 3; + int indexB = i + (j + 1) * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TDOWN) { + for (int j = 1; j < 3; j++) { + for (int i = 0; i < 3; i++) { + int indexA = i + j * 3; + int indexB = i + (j - 1) * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } + return false; + } +} diff --git a/Processing/wfc_overlapping/data/flowers.png b/Processing/wfc_overlapping/data/flowers.png new file mode 100644 index 0000000..6db3272 Binary files /dev/null and b/Processing/wfc_overlapping/data/flowers.png differ diff --git a/Processing/wfc_overlapping/data/logo.png b/Processing/wfc_overlapping/data/logo.png new file mode 100644 index 0000000..b4e8e11 Binary files /dev/null and b/Processing/wfc_overlapping/data/logo.png differ diff --git a/Processing/wfc_overlapping/data/logo_16_bw.png b/Processing/wfc_overlapping/data/logo_16_bw.png new file mode 100644 index 0000000..ad314a9 Binary files /dev/null and b/Processing/wfc_overlapping/data/logo_16_bw.png differ diff --git a/Processing/wfc_overlapping/data/water.png b/Processing/wfc_overlapping/data/water.png new file mode 100644 index 0000000..9af51fc Binary files /dev/null and b/Processing/wfc_overlapping/data/water.png differ diff --git a/Processing/wfc_overlapping/util.pde b/Processing/wfc_overlapping/util.pde new file mode 100644 index 0000000..c32caa7 --- /dev/null +++ b/Processing/wfc_overlapping/util.pde @@ -0,0 +1,45 @@ + +boolean differentColor(PImage imgA, int indexA, PImage imgB, int indexB) { + int colorA = imgA.pixels[indexA]; + int colorB = imgB.pixels[indexB]; + return colorA != colorB; +} + +void renderCell(PImage img, float x, float y, float w) { + int i = img.width / 2; + int j = img.height / 2; + int index = i + j * img.width; + int col = img.pixels[index]; + fill(col); + stroke(0); + square(x, y, w); +} + +void copyTile(PImage source, int sx, int sy, int w, PImage dest) { + dest.loadPixels(); + source.loadPixels(); + for (int i = 0; i < w; i++) { + for (int j = 0; j < w; j++) { + int pi = (sx + i) % source.width; + int pj = (sy + j) % source.height; + int index = pi + pj * source.width; + int col = source.pixels[index]; + int index2 = i + j * w; + dest.pixels[index2] = col; + } + } + dest.updatePixels(); +} + +ArrayList extractTiles(PImage img) { + ArrayList tiles = new ArrayList(); + img.loadPixels(); + for (int j = 0; j < img.height; j++) { + for (int i = 0; i < img.width; i++) { + PImage tileImage = createImage(3, 3, RGB); + copyTile(img, i, j, 3, tileImage); + tiles.add(new Tile(tileImage, tiles.size())); + } + } + return tiles; +} diff --git a/Processing/wfc_overlapping/wfc_overlapping.pde b/Processing/wfc_overlapping/wfc_overlapping.pde new file mode 100644 index 0000000..28940ed --- /dev/null +++ b/Processing/wfc_overlapping/wfc_overlapping.pde @@ -0,0 +1,145 @@ +import java.util.Comparator; + +PImage sourceImage; +ArrayList tiles; +int DIM = 80; +int maxDepth = 5; +ArrayList grid; + +final int TRIGHT = 0; +final int TLEFT = 1; +final int TUP = 2; +final int TDOWN = 3; + +void setup() { + size(800, 800); + pixelDensity(2); + sourceImage = loadImage("logo_16_bw.png"); + tiles = extractTiles(sourceImage); + for (Tile tile : tiles) { + tile.calculateNeighbors(tiles); + } + float w = width / (float)DIM; + int count = 0; + grid = new ArrayList(); + for (int j = 0; j < DIM; j++) { + for (int i = 0; i < DIM; i++) { + grid.add(new Cell(tiles, i * w, j * w, w, count)); + count++; + } + } + wfc(); +} + +void draw() { + background(0); + println(frameRate); + for (int i = 0; i < grid.size(); i++) { + grid.get(i).show(); + grid.get(i).checked = false; + } + wfc(); + + //saveFrame("render/render####.png"); +} + +void wfc() { + // WAVE FUNCTION COLLAPSE + // Make a copy of grid + ArrayList gridCopy = new ArrayList(grid); + // Remove any collapsed cells + gridCopy.removeIf(a -> a.collapsed); + + // The algorithm has completed if everything is collapsed + if (gridCopy.size() == 0) { + return; + } + + // Pick a cell with least entropy + gridCopy.sort(new Comparator() { + public int compare(Cell a, Cell b) { + return a.options.size() - b.options.size(); + } + }); + + // Keep only the lowest entropy cells + int len = gridCopy.get(0).options.size(); + int stopIndex = gridCopy.size(); + for (int i = 1; i < gridCopy.size(); i++) { + if (gridCopy.get(i).options.size() > len) { + stopIndex = i; + break; + } + } + if (stopIndex > 0) { + gridCopy = new ArrayList(gridCopy.subList(0, stopIndex)); + } + + // Collapse a cell + Cell cell = gridCopy.get((int)random(gridCopy.size())); + cell.collapsed = true; + + int pick = cell.options.get((int)random(cell.options.size())); + cell.options = new ArrayList(); + cell.options.add(pick); + reduceEntropy(grid, cell, 0); +} + +void reduceEntropy(ArrayList grid, Cell cell, int depth) { + if (depth > maxDepth) return; + if (cell.checked) return; + cell.checked = true; + int index = cell.index; + int i = index % DIM; + int j = index / DIM; + + // RIGHT + if (i + 1 < DIM) { + Cell rightCell = grid.get(i + 1 + j * DIM); + boolean checked = checkOptions(cell, rightCell, TRIGHT); + if (checked) { + reduceEntropy(grid, rightCell, depth + 1); + } + } + + // LEFT + if (i - 1 >= 0) { + Cell leftCell = grid.get(i - 1 + j * DIM); + boolean checked = checkOptions(cell, leftCell, TLEFT); + if (checked) { + reduceEntropy(grid, leftCell, depth + 1); + } + } + + // DOWN + if (j + 1 < DIM) { + Cell downCell = grid.get(i + (j + 1) * DIM); + boolean checked = checkOptions(cell, downCell, TDOWN); + if (checked) { + reduceEntropy(grid, downCell, depth + 1); + } + } + + // UP + if (j - 1 >= 0) { + Cell upCell = grid.get(i + (j - 1) * DIM); + boolean checked = checkOptions(cell, upCell, TUP); + if (checked) { + reduceEntropy(grid, upCell, depth + 1); + } + } +} + +boolean checkOptions(Cell cell, Cell neighbor, int direction) { + if (neighbor != null && !neighbor.collapsed) { + ArrayList validOptions = new ArrayList(); + for (int option : cell.options) { + validOptions.addAll(tiles.get(option).neighbors[direction]); + } + // Remove options from neighbor.options that are not in validOptions + neighbor.options.retainAll(validOptions); + return true; + } else { + return false; + } +} diff --git a/Processing/wfc_overlapping_rot_reflect/Cell.pde b/Processing/wfc_overlapping_rot_reflect/Cell.pde new file mode 100644 index 0000000..1ff643c --- /dev/null +++ b/Processing/wfc_overlapping_rot_reflect/Cell.pde @@ -0,0 +1,106 @@ +final float log2 = log(2); + + +class Cell { + float x, y, w; + int index; + ArrayList options; + boolean collapsed; + boolean checked; + float entropy; + boolean dead; + + int totalOptions; + + + Cell(ArrayList tiles, float x, float y, float w, int index) { + this.x = x; + this.y = y; + this.w = w; + this.index = index; + this.options = new ArrayList(); + this.collapsed = false; + this.checked = false; + this.entropy = 0; + for (int i = 0; i < tiles.size(); i++) { + this.options.add(i); + } + this.totalOptions = -1; + this.dead = false; + } + void calculateEntropy() { + + // No options left + if (this.options.size() == 0) { + return; + } + + int currentTotal = this.options.size(); + // Don't need to recalculate entropy + // Possible issue if same # of options but different options? + if (this.totalOptions == currentTotal) { + return; + } + this.totalOptions = currentTotal; + this.entropy = 0; + + // Compute total frequency + float totalFrequency = 0; + for (int option : this.options) { + totalFrequency += tiles.get(option).frequency; + } + + if (totalFrequency == 0) { + return; + } + + // Calculate entropy + for (int option : this.options) { + float frequency = tiles.get(option).frequency; + float probability = frequency / totalFrequency; + this.entropy -= probability * (log(probability) / log2); + } + } + + + boolean show() { + if (this.options.size() == 0 || this.dead) { + // fill(112, 50, 126); + // square(this.x, this.y, this.w); + // return true; + } else if (this.collapsed) { + int tileIndex = this.options.get(0); + PImage img = tiles.get(tileIndex).img; + renderCell(img, this.x, this.y, this.w); + } else { + float sumR = 0; + float sumG = 0; + float sumB = 0; + float sumFreq = 0; + for (int i = 0; i < this.options.size(); i++) { + int tileIndex = this.options.get(i); + Tile t = tiles.get(tileIndex); + PImage img = t.img; + int index = 1 + 1 * img.width; // center pixel if 3x3 tile + int col = img.pixels[index]; + sumR += red(col)*t.frequency; + sumG += green(col)*t.frequency; + sumB += blue(col)*t.frequency; + sumFreq += t.frequency; + } + sumR /= sumFreq; + sumG /= sumFreq; + sumB /= sumFreq; + fill(sumR, sumG, sumB); + stroke(0); + strokeWeight(0.5); + square(this.x, this.y, this.w); + } + return false; + } +} + + +//float log2 (float x) { +// return (log(x) / log(2)); +//} diff --git a/Processing/wfc_overlapping_rot_reflect/Tile.pde b/Processing/wfc_overlapping_rot_reflect/Tile.pde new file mode 100644 index 0000000..e3b4882 --- /dev/null +++ b/Processing/wfc_overlapping_rot_reflect/Tile.pde @@ -0,0 +1,84 @@ + +class Tile { + PImage img; + int index; + int frequency; + ArrayList[] neighbors; + + Tile(PImage img, int i) { + this.img = img; + this.img.loadPixels(); + this.index = i; + this.frequency = 1; + neighbors = new ArrayList[4]; + for (int j = 0; j < 4; j++) { + neighbors[j] = new ArrayList(); + } + } + + void calculateNeighbors(ArrayList tiles) { + for (int i = 0; i < tiles.size(); i++) { + if (this.overlapping(tiles.get(i), TRIGHT)) { + neighbors[TRIGHT].add(i); + } + if (this.overlapping(tiles.get(i), TLEFT)) { + neighbors[TLEFT].add(i); + } + if (this.overlapping(tiles.get(i), TUP)) { + neighbors[TUP].add(i); + } + if (this.overlapping(tiles.get(i), TDOWN)) { + neighbors[TDOWN].add(i); + } + } + } + + boolean overlapping(Tile other, int direction) { + if (direction == TRIGHT) { + for (int i = 1; i < 3; i++) { + for (int j = 0; j < 3; j++) { + int indexA = i + j * 3; + int indexB = (i - 1) + j * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TLEFT) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + int indexA = i + j * 3; + int indexB = (i + 1) + j * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TUP) { + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 3; i++) { + int indexA = i + j * 3; + int indexB = i + (j + 1) * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TDOWN) { + for (int j = 1; j < 3; j++) { + for (int i = 0; i < 3; i++) { + int indexA = i + j * 3; + int indexB = i + (j - 1) * 3; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } + return false; + } +} diff --git a/Processing/wfc_overlapping_rot_reflect/data/flowers.png b/Processing/wfc_overlapping_rot_reflect/data/flowers.png new file mode 100644 index 0000000..b063d7c Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/data/flowers.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/data/logo.png b/Processing/wfc_overlapping_rot_reflect/data/logo.png new file mode 100644 index 0000000..b4e8e11 Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/data/logo.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/data/logo_16_bw-1px.png b/Processing/wfc_overlapping_rot_reflect/data/logo_16_bw-1px.png new file mode 100644 index 0000000..a482ddb Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/data/logo_16_bw-1px.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/data/logo_16_bw.png b/Processing/wfc_overlapping_rot_reflect/data/logo_16_bw.png new file mode 100644 index 0000000..ad314a9 Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/data/logo_16_bw.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/data/logo_16_color-1px-a.png b/Processing/wfc_overlapping_rot_reflect/data/logo_16_color-1px-a.png new file mode 100644 index 0000000..605af2c Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/data/logo_16_color-1px-a.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/data/logo_16_color-1px.png b/Processing/wfc_overlapping_rot_reflect/data/logo_16_color-1px.png new file mode 100644 index 0000000..00f303b Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/data/logo_16_color-1px.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/data/water.png b/Processing/wfc_overlapping_rot_reflect/data/water.png new file mode 100644 index 0000000..9af51fc Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/data/water.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/thumbnail.png b/Processing/wfc_overlapping_rot_reflect/thumbnail.png new file mode 100644 index 0000000..0d610b2 Binary files /dev/null and b/Processing/wfc_overlapping_rot_reflect/thumbnail.png differ diff --git a/Processing/wfc_overlapping_rot_reflect/util.pde b/Processing/wfc_overlapping_rot_reflect/util.pde new file mode 100644 index 0000000..0786319 --- /dev/null +++ b/Processing/wfc_overlapping_rot_reflect/util.pde @@ -0,0 +1,118 @@ + +boolean differentColor(PImage imgA, int indexA, PImage imgB, int indexB) { + int colorA = imgA.pixels[indexA]; + int colorB = imgB.pixels[indexB]; + return colorA != colorB; +} + +void renderCell(PImage img, float x, float y, float w) { + int i = img.width / 2; + int j = img.height / 2; + int index = i + j * img.width; + int col = img.pixels[index]; + fill(col); + stroke(0); + square(x, y, w); +} + +void copyTile(PImage source, int sx, int sy, int w, PImage dest) { + dest.loadPixels(); + source.loadPixels(); + for (int i = 0; i < w; i++) { + for (int j = 0; j < w; j++) { + int pi = (sx + i) % source.width; + int pj = (sy + j) % source.height; + int index = pi + pj * source.width; + int col = source.pixels[index]; + int index2 = i + j * w; + dest.pixels[index2] = col; + } + } + dest.updatePixels(); +} + +PImage rotateImage(PImage img) { + PImage rotated = createImage(3, 3, RGB); + rotated.loadPixels(); + img.loadPixels(); + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 3; y++) { + int srcIndex = x + y * 3; + int destIndex = (2 - y) + x * 3; // Rotate 90 degrees + rotated.pixels[destIndex] = img.pixels[srcIndex]; + } + } + rotated.updatePixels(); + return rotated; +} + +PImage reflectImage(PImage img) { + PImage reflected = createImage(3, 3, RGB); + reflected.loadPixels(); + img.loadPixels(); + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 3; y++) { + int srcIndex = x + y * 3; + int destIndex = (2 - x) + y * 3; // Reflect horizontally + reflected.pixels[destIndex] = img.pixels[srcIndex]; + } + } + reflected.updatePixels(); + return reflected; +} + +ArrayList extractTiles(PImage img) { + ArrayList tiles = new ArrayList(); + HashMap tileDict = new HashMap(); + img.loadPixels(); + for (int j = 0; j < img.height; j++) { + for (int i = 0; i < img.width; i++) { + PImage tileImage = createImage(3, 3, RGB); + copyTile(img, i, j, 3, tileImage); + + ArrayList transformations = generateTransformations(tileImage); + for (PImage t : transformations) { + String key = Base64.getEncoder().encodeToString(convertToByteArray(t.pixels)); + if (tileDict.containsKey(key)) { + tileDict.get(key).frequency++; + } else { + Tile newTile = new Tile(t, tiles.size()); + tiles.add(newTile); + tileDict.put(key, newTile); + } + } + } + } + return tiles; +} + +byte[] convertToByteArray(int[] pixels) { + ByteBuffer buffer = ByteBuffer.allocate(pixels.length * 4); + for (int pixel : pixels) { + buffer.putInt(pixel); + } + return buffer.array(); +} + + +ArrayList generateTransformations(PImage tileImage) { + ArrayList transformations = new ArrayList(); + PImage currentImage = tileImage; + //transformations.add(currentImage); + + for (int k = 0; k < 4; k++) { + currentImage = rotateImage(currentImage); + transformations.add(currentImage); + } + + PImage reflectedImage = reflectImage(tileImage); + currentImage = reflectedImage; + transformations.add(currentImage); + + for (int k = 0; k < 4; k++) { + currentImage = rotateImage(currentImage); + transformations.add(currentImage); + } + + return transformations; +} diff --git a/Processing/wfc_overlapping_rot_reflect/wfc_overlapping_rot_reflect.pde b/Processing/wfc_overlapping_rot_reflect/wfc_overlapping_rot_reflect.pde new file mode 100644 index 0000000..7323d49 --- /dev/null +++ b/Processing/wfc_overlapping_rot_reflect/wfc_overlapping_rot_reflect.pde @@ -0,0 +1,203 @@ +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.Comparator; + +PImage sourceImage; +ArrayList tiles; +int DIMW, DIMH; +float w = 10; +int maxDepth = 256; +ArrayList grid; + +int seedCount = 9; + +final int TRIGHT = 0; +final int TLEFT = 1; +final int TUP = 2; +final int TDOWN = 3; + +void setup() { + size(640, 480); + pixelDensity(2); + sourceImage = loadImage("flowers.png"); + tiles = extractTiles(sourceImage); + for (Tile tile : tiles) { + tile.calculateNeighbors(tiles); + } + startOver(); +} + +void startOver() { + seedCount++; + println(seedCount); + randomSeed(seedCount); + DIMW = floor(width/w); + DIMH = floor(height/w); + int count = 0; + grid = new ArrayList(); + for (int j = 0; j < DIMH; j++) { + for (int i = 0; i < DIMW; i++) { + grid.add(new Cell(tiles, i * w, j * w, w, count)); + count++; + } + } + wfc(true); + background(0); +} + +void draw() { + //if (frameCount % 600 == 0) { + // println(frameRate); + //} + for (int i = 0; i < grid.size(); i++) { + grid.get(i).show(); + grid.get(i).checked = false; + } + wfc(false); + + saveFrame("render"+seedCount+"/render####.png"); +} + +void wfc(boolean firstTime) { + // WAVE FUNCTION COLLAPSE + // Make a copy of grid + ArrayList gridCopy = new ArrayList(grid); + // Remove any collapsed cells + gridCopy.removeIf(a -> a.collapsed); + + // The algorithm has completed if everything is collapsed + if (gridCopy.size() == 0) { + save("thumbnail.png"); + println("Completed"); + noLoop(); + return; + } + + // Calculate entropy for each cell + for (Cell cell : gridCopy) { + cell.calculateEntropy(); + } + + // Sort by entropy (lowest first) + gridCopy.sort(new Comparator() { + public int compare(Cell a, Cell b) { + return Float.compare(a.entropy, b.entropy); + } + } + ); + + // Keep only the lowest entropy cells + float minEntropy = gridCopy.get(0).entropy; + int stopIndex = gridCopy.size(); + for (int i = 1; i < gridCopy.size(); i++) { + if (gridCopy.get(i).entropy > minEntropy) { + stopIndex = i; + break; + } + } + if (stopIndex > 0) { + gridCopy = new ArrayList(gridCopy.subList(0, stopIndex)); + } + + // Collapse a cell + int r = (int)random(gridCopy.size()); + if (firstTime) { + r = gridCopy.size()/2 + DIMW/2; + } + + Cell cell = gridCopy.get(r); + cell.collapsed = true; + int pick = weightedSelection(cell.options); + if (pick == -1) { + println("ran into a conflict"); + startOver(); + //noLoop(); + return; + + // hack to keep going + //pick = 15; + //cell.options = new ArrayList(); + //cell.options.add(pick); + //cell.dead = true; + } else { + cell.options = new ArrayList(); + cell.options.add(pick); + reduceEntropy(grid, cell, 0); + } +} + +int weightedSelection(ArrayList options) { + float total = 0; + for (int option : options) { + total += tiles.get(option).frequency; + } + + float r = random(1) * total; + float sum = 0; + for (int option : options) { + sum += tiles.get(option).frequency; + if (r < sum) { + return option; + } + } + return -1; +} + +void reduceEntropy(ArrayList grid, Cell cell, int depth) { + if (depth > maxDepth) return; + if (cell.checked) return; + cell.checked = true; + int index = cell.index; + int i = index % DIMW; + int j = index / DIMW; + + // RIGHT + if (i + 1 < DIMW) { + Cell rightCell = grid.get(i + 1 + j * DIMW); + boolean checked = checkOptions(cell, rightCell, TRIGHT); + if (checked) { + reduceEntropy(grid, rightCell, depth + 1); + } + } + + // LEFT + if (i - 1 >= 0) { + Cell leftCell = grid.get(i - 1 + j * DIMW); + boolean checked = checkOptions(cell, leftCell, TLEFT); + if (checked) { + reduceEntropy(grid, leftCell, depth + 1); + } + } + + // DOWN + if (j + 1 < DIMH) { + Cell downCell = grid.get(i + (j + 1) * DIMW); + boolean checked = checkOptions(cell, downCell, TDOWN); + if (checked) { + reduceEntropy(grid, downCell, depth + 1); + } + } + + // UP + if (j - 1 >= 0) { + Cell upCell = grid.get(i + (j - 1) * DIMW); + boolean checked = checkOptions(cell, upCell, TUP); + if (checked) { + reduceEntropy(grid, upCell, depth + 1); + } + } +} + +boolean checkOptions(Cell cell, Cell neighbor, int direction) { + if (neighbor != null && !neighbor.collapsed) { + ArrayList validOptions = new ArrayList(); + for (int option : cell.options) { + validOptions.addAll(tiles.get(option).neighbors[direction]); + } + // Remove options from neighbor.options that are not in validOptions + neighbor.options.retainAll(validOptions); + return true; + } else { + return false; + } +} diff --git a/diagrams/misc/index.html b/diagrams/misc/index.html new file mode 100644 index 0000000..b16b597 --- /dev/null +++ b/diagrams/misc/index.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ + + diff --git a/diagrams/misc/sketch.js b/diagrams/misc/sketch.js new file mode 100644 index 0000000..16f7426 --- /dev/null +++ b/diagrams/misc/sketch.js @@ -0,0 +1,55 @@ +function setup() { + createCanvas(1200, 400); + background(255); + textFont('Courier', 16); + + let w = 25; + let h = 25; + translate(25, 0); + for (let x = 0; x < 40; x++) { + let x1 = x * w + w; + let y1 = 20; + strokeWeight(2); + noFill(); + stroke(0); + rect(x1, y1, w, h); + fill(0); + noStroke(); + textAlign(CENTER, CENTER); + let arr = ['R', 'G', 'B', 'A']; + textSize(16); + text(arr[x % 4], x1 + w / 2, y1 + h / 2); + textSize(12); + text(nf(x, 2), x1 + w / 2, y1 + h * 1.5); + } + w = 50; + h = 50; + translate(25, 75); + + // draw a 5 x 4 grid of rectangles + for (let x = 0; x < 5; x++) { + let x1 = x * w + w; + textSize(16); + text(x, x1 + w / 2, w - h / 4); + for (let y = 0; y < 4; y++) { + let y1 = y * h + w; + strokeWeight(2); + stroke(0); + noFill(); + rect(x1, y1, w, h); + fill(0); + noStroke(); + textAlign(CENTER, CENTER); + textSize(16); + if (x == 2 && y == 1) { + //text(`${x},${y}`, x1 + w / 2, y1 + h / 2); + text(`x,y`, x1 + w / 2, y1 + h / 2); + } + } + } + for (let y = 0; y < 4; y++) { + let y1 = y * h + w; + + text(y, 30, y1 + h / 2); + } +} diff --git a/diagrams/wfc/cell.js b/diagrams/wfc/cell.js new file mode 100644 index 0000000..7078566 --- /dev/null +++ b/diagrams/wfc/cell.js @@ -0,0 +1,48 @@ +class Cell { + constructor(tiles, x, y, w, index) { + this.x = x; + this.y = y; + this.w = w; + this.index = index; + this.options = []; + this.collapsed = false; + this.checked = false; + for (let i = 0; i < tiles.length; i++) { + this.options.push(i); + } + } + + show() { + if (this.options.length == 0) { + fill(255, 0, 255); + square(this.x, this.y, this.w); + return true; + } else if (this.collapsed) { + let tileIndex = this.options[0]; + let img = tiles[tileIndex].img; + renderCell(img, this.x, this.y, this.w); + } else { + let sumR = 0; + let sumG = 0; + let sumB = 0; + for (let i = 0; i < this.options.length; i++) { + let tileIndex = this.options[i]; + let img = tiles[tileIndex].img; + // (1 + 1 * 3) * 4 + sumR += img.pixels[16]; // if 3x3 tile!! + sumG += img.pixels[17]; + sumB += img.pixels[18]; + } + sumR /= this.options.length; + sumG /= this.options.length; + sumB /= this.options.length; + fill(sumR, sumG, sumB); + square(this.x, this.y, this.w); + // fill(0); + // noStroke(); + // textSize(this.w / 2); + // textAlign(CENTER, CENTER); + // text(this.options.length, this.x + this.w / 2, this.y + this.w / 2); + } + } +} diff --git a/diagrams/wfc/images/city.png b/diagrams/wfc/images/city.png new file mode 100644 index 0000000..3b9c4c3 Binary files /dev/null and b/diagrams/wfc/images/city.png differ diff --git a/diagrams/wfc/images/example.png b/diagrams/wfc/images/example.png new file mode 100644 index 0000000..03ba6dc Binary files /dev/null and b/diagrams/wfc/images/example.png differ diff --git a/diagrams/wfc/images/flowers.png b/diagrams/wfc/images/flowers.png new file mode 100644 index 0000000..6db3272 Binary files /dev/null and b/diagrams/wfc/images/flowers.png differ diff --git a/diagrams/wfc/images/water.png b/diagrams/wfc/images/water.png new file mode 100644 index 0000000..9af51fc Binary files /dev/null and b/diagrams/wfc/images/water.png differ diff --git a/diagrams/wfc/index.html b/diagrams/wfc/index.html new file mode 100644 index 0000000..46b95ba --- /dev/null +++ b/diagrams/wfc/index.html @@ -0,0 +1,15 @@ + + + + + + + + +
+ + + + + + diff --git a/diagrams/wfc/sketch.js b/diagrams/wfc/sketch.js new file mode 100644 index 0000000..987202c --- /dev/null +++ b/diagrams/wfc/sketch.js @@ -0,0 +1,175 @@ +let sourceImage; +let tiles; +let DIM = 40; +let maxDepth = 5; +let grid = []; + +let scl = 50; +let endR, endC; + +function preload() { + sourceImage = loadImage('images/city.png'); +} + +function setup() { + createCanvas(sourceImage.width * scl, sourceImage.height * scl); + console.log(sourceImage.width, sourceImage.height); + endR = sourceImage.height - 2; + endC = sourceImage.width - 2; + + tiles = extractTiles(sourceImage); + for (let tile of tiles) { + tile.calculateNeighbors(tiles); + } + let w = width / DIM; + let count = 0; + for (let j = 0; j < DIM; j++) { + for (let i = 0; i < DIM; i++) { + grid.push(new Cell(tiles, i * w, j * w, w, count)); + count++; + } + } + // wfc(); + frameRate(1); +} + +function draw() { + background(51); + + //renderImage(tiles[0].img, 0, 0, 10); + + renderImage(sourceImage, 0, 0, scl); + + // let w = width / DIM; + // for (let i = 0; i < grid.length; i++) { + // let failed = grid[i].show(); + // grid[i].checked = false; + // } + // wfc(); + + let w = scl; + let count = 0; + + // for (let j = 0; j < endR; j++) { + // for (let i = 0; i < endC; i++) { + // // let x = 20 + i * (w + 4); + // // let y = 10 + j * (w + 4); + // x = i * (w + 0); + // y = j * (w + 0); + // renderImage(tiles[count].img, x, y, w / 3); + // count++; + // } + // } + + // renderImage(tiles[count].img, 0, 0, width / 3); + // count++; + // console.log(count); + // // save("tile.png"); + // if (count > tiles.length - 1) { + // noLoop(); + // } + + noLoop(); +} + +let count = 0; + +function wfc() { + // WAVE FUNCTION COLLAPSE + // Make a copy of grid + let gridCopy = grid.slice(); + // Remove any collapsed cells + gridCopy = gridCopy.filter((a) => !a.collapsed); + + // The algorithm has completed if everything is collapsed + if (gridCopy.length == 0) { + return; + } + + // Pick a cell with least entropy + gridCopy.sort((a, b) => { + return a.options.length - b.options.length; + }); + + // Keep only the lowest entropy cells + let len = gridCopy[0].options.length; + let stopIndex = 0; + for (let i = 1; i < gridCopy.length; i++) { + if (gridCopy[i].options.length > len) { + stopIndex = i; + break; + } + } + if (stopIndex > 0) gridCopy.splice(stopIndex); + + // Collapse a cell + const cell = random(gridCopy); + cell.collapsed = true; + + const pick = random(cell.options); + if (pick == undefined) { + console.log('ran into a conflict'); + noLoop(); + return; + } + cell.options = [pick]; + reduceEntropy(grid, cell, 0); +} + +function reduceEntropy(grid, cell, depth) { + if (depth > maxDepth) return; + if (cell.checked) return; + cell.checked = true; + let index = cell.index; + let i = floor(index % DIM); + let j = floor(index / DIM); + + // RIGHT + if (i + 1 < DIM) { + let rightCell = grid[i + 1 + j * DIM]; + let checked = checkOptions(cell, rightCell, TRIGHT); + if (checked) { + reduceEntropy(grid, rightCell, depth + 1); + } + } + + // LEFT + if (i - 1 >= 0) { + let leftCell = grid[i - 1 + j * DIM]; + let checked = checkOptions(cell, leftCell, TLEFT); + if (checked) { + reduceEntropy(grid, leftCell, depth + 1); + } + } + + // DOWN + if (j + 1 < DIM) { + let downCell = grid[i + (j + 1) * DIM]; + let checked = checkOptions(cell, downCell, TDOWN); + if (checked) { + reduceEntropy(grid, downCell, depth + 1); + } + } + + // UP + if (j - 1 >= 0) { + let upCell = grid[i + (j - 1) * DIM]; + let checked = checkOptions(cell, upCell, TUP); + if (checked) { + reduceEntropy(grid, upCell, depth + 1); + } + } +} + +function checkOptions(cell, neighbor, direction) { + if (neighbor && !neighbor.collapsed) { + let validOptions = []; + for (let option of cell.options) { + validOptions = validOptions.concat(tiles[option].neighbors[direction]); + } + neighbor.options = neighbor.options.filter((elt) => validOptions.includes(elt)); + return true; + } else { + return false; + } +} diff --git a/diagrams/wfc/style.css b/diagrams/wfc/style.css new file mode 100644 index 0000000..9386f1c --- /dev/null +++ b/diagrams/wfc/style.css @@ -0,0 +1,7 @@ +html, body { + margin: 0; + padding: 0; +} +canvas { + display: block; +} diff --git a/diagrams/wfc/tile.js b/diagrams/wfc/tile.js new file mode 100644 index 0000000..1d82fb9 --- /dev/null +++ b/diagrams/wfc/tile.js @@ -0,0 +1,92 @@ +const TRIGHT = 0; +const TLEFT = 1; +const TUP = 2; +const TDOWN = 3; + +class Tile { + constructor(img, i) { + this.img = img; + this.img.loadPixels(); + this.index = i; + this.neighbors = []; + this.neighbors[TRIGHT] = []; + this.neighbors[TLEFT] = []; + this.neighbors[TUP] = []; + this.neighbors[TDOWN] = []; + } + + calculateNeighbors(tiles) { + for (let i = 0; i < tiles.length; i++) { + if (this.overlapping(tiles[i], TRIGHT)) { + this.neighbors[TRIGHT].push(i); + } + if (this.overlapping(tiles[i], TLEFT)) { + this.neighbors[TLEFT].push(i); + } + if (this.overlapping(tiles[i], TUP)) { + this.neighbors[TUP].push(i); + } + if (this.overlapping(tiles[i], TDOWN)) { + this.neighbors[TDOWN].push(i); + } + } + } + + overlapping(other, direction) { + if (direction == TRIGHT) { + for (let i = 1; i < 3; i++) { + for (let j = 0; j < 3; j++) { + let indexA = (i + j * 3) * 4; + let indexB = (i - 1 + j * 3) * 4; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TLEFT) { + for (let i = 0; i < 2; i++) { + for (let j = 0; j < 3; j++) { + let indexA = (i + j * 3) * 4; + let indexB = (i + 1 + j * 3) * 4; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TUP) { + for (let j = 0; j < 2; j++) { + for (let i = 0; i < 3; i++) { + let indexA = (i + j * 3) * 4; + let indexB = (i + (j + 1) * 3) * 4; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } else if (direction == TDOWN) { + for (let j = 1; j < 3; j++) { + for (let i = 0; i < 3; i++) { + let indexA = (i + j * 3) * 4; + let indexB = (i + (j - 1) * 3) * 4; + if (differentColor(this.img, indexA, other.img, indexB)) { + return false; + } + } + } + return true; + } + } +} + +function differentColor(imgA, indexA, imgB, indexB) { + let rA = imgA.pixels[indexA + 0]; + let gA = imgA.pixels[indexA + 1]; + let bA = imgA.pixels[indexA + 2]; + let rB = imgB.pixels[indexB + 0]; + let gB = imgB.pixels[indexB + 1]; + let bB = imgB.pixels[indexB + 2]; + return rA !== rB || gA !== gB || bA !== bB; +} diff --git a/diagrams/wfc/util.js b/diagrams/wfc/util.js new file mode 100644 index 0000000..6e54761 --- /dev/null +++ b/diagrams/wfc/util.js @@ -0,0 +1,67 @@ +function renderImage(img, x, y, w) { + for (let i = 0; i < img.width; i++) { + for (let j = 0; j < img.height; j++) { + let index = (i + j * img.width) * 4; + let r = img.pixels[index + 0]; + let g = img.pixels[index + 1]; + let b = img.pixels[index + 2]; + fill(r, g, b); + stroke(0); + //noStroke(); + strokeWeight(2); + square(x + i * w, y + j * w, w); + } + } + + noFill(); + strokeWeight(1); + stroke(51); + square(x, y, img.width * w); +} + +function renderCell(img, x, y, w) { + let i = floor(img.width / 2); + let j = floor(img.width / 2); + let index = (i + j * img.width) * 4; + let r = img.pixels[index + 0]; + let g = img.pixels[index + 1]; + let b = img.pixels[index + 2]; + fill(r, g, b); + noStroke(); + square(x, y, w); +} + +function copyTile(source, sx, sy, w, dest) { + dest.loadPixels(); + for (let i = 0; i < w; i++) { + for (let j = 0; j < w; j++) { + let pi = (sx + i) % source.width; + let pj = (sy + j) % source.height; + let index = (pi + pj * source.width) * 4; + let r = source.pixels[index + 0]; + let g = source.pixels[index + 1]; + let b = source.pixels[index + 2]; + let a = source.pixels[index + 3]; + let index2 = (i + j * w) * 4; + dest.pixels[index2 + 0] = r; + dest.pixels[index2 + 1] = g; + dest.pixels[index2 + 2] = b; + dest.pixels[index2 + 3] = a; + } + } + dest.updatePixels(); +} + +function extractTiles(img) { + let tiles = []; + img.loadPixels(); + for (let j = 0; j < endR; j++) { + for (let i = 0; i < endC; i++) { + let tileImage = createImage(3, 3); + // tileImage.copy(img, i, j, 3, 3, 0, 0, 3, 3); + copyTile(img, i, j, 3, tileImage); + tiles.push(new Tile(tileImage, tiles.length)); + } + } + return tiles; +} diff --git a/overlapping-model/cell.js b/overlapping-model/cell.js new file mode 100644 index 0000000..5df0e57 --- /dev/null +++ b/overlapping-model/cell.js @@ -0,0 +1,15 @@ +class Cell { + constructor(value, index) { + this.collapsed = false; + this.index = index; + this.checked = false; + if (value instanceof Array) { + this.options = value; + } else { + this.options = []; + for (let i = 0; i < value; i++) { + this.options[i] = i; + } + } + } +} diff --git a/overlapping-model/example.png b/overlapping-model/example.png new file mode 100644 index 0000000..03ba6dc Binary files /dev/null and b/overlapping-model/example.png differ diff --git a/overlapping-model/flowers.png b/overlapping-model/flowers.png new file mode 100644 index 0000000..6db3272 Binary files /dev/null and b/overlapping-model/flowers.png differ diff --git a/index.html b/overlapping-model/index.html similarity index 100% rename from index.html rename to overlapping-model/index.html diff --git a/overlapping-model/sketch.js b/overlapping-model/sketch.js new file mode 100644 index 0000000..634e690 --- /dev/null +++ b/overlapping-model/sketch.js @@ -0,0 +1,266 @@ +let img; +let tiles = []; +let grid = []; +const tileSize = 3; +const DIM = 40; + +const recursion_depth = DIM; + +let toggle = false; + +const LEFT = 0; +const RIGHT = 1; +const UP = 2; +const DOWN = 3; + +function preload() { + img = loadImage('flowers.png'); +} + +function setup() { + createCanvas(400, 400); + extractTiles(img); + for (let t of tiles) { + t.adjacancies(tiles); + } + startOver(); + wfc(); +} + +function copyWrap(source, destination, sx, sy, w, h) { + source.loadPixels(); + destination.loadPixels(); + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + let px = (sx + x) % source.width; + let py = (sy + y) % source.height; + let index = (px + py * source.width) * 4; + let dIndex = (x + y * w) * 4; + destination.pixels[dIndex + 0] = source.pixels[index + 0]; + destination.pixels[dIndex + 1] = source.pixels[index + 1]; + destination.pixels[dIndex + 2] = source.pixels[index + 2]; + destination.pixels[dIndex + 3] = source.pixels[index + 3]; + } + } + destination.updatePixels(); +} + +function extractTiles(img) { + const w = img.width; + const h = img.height; + + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + let pattern = createImage(tileSize, tileSize); + copyWrap(img, pattern, x, y, tileSize, tileSize); + // pattern.copy(img, x, y, tileSize, tileSize, 0, 0, tileSize, tileSize); + tiles.push(new Tile(pattern, tiles.length)); + } + } +} + +function startOver() { + for (let i = 0; i < DIM * DIM; i++) { + grid[i] = new Cell(tiles.length, i); + } +} + +function draw() { + // frameRate(1); + // draw all the extracted tiles + background(0); + + // draw everything + const w = width / DIM; + const h = height / DIM; + for (let j = 0; j < DIM; j++) { + for (let i = 0; i < DIM; i++) { + let cell = grid[i + j * DIM]; + cell.checked = false; + if (cell.collapsed) { + let index = cell.options[0]; + // if (mouseIsPressed) { + // renderTile(tiles[index].img, i * w, j * h, w, h); + // } else { + renderCenter(tiles[index].img, i * w, j * h, w, h); + //} + } else { + let rSum = 0; + let gSum = 0; + let bSum = 0; + for (let i = 0; i < cell.options.length; i++) { + let tileIndex = cell.options[i]; + let tile = tiles[tileIndex]; + // center pixel + let x = floor(tileSize / 2); + let y = floor(tileSize / 2); + let index = (x + y * tileSize) * 4; + rSum += tile.img.pixels[index + 0]; + gSum += tile.img.pixels[index + 1]; + bSum += tile.img.pixels[index + 2]; + } + rSum /= cell.options.length; + gSum /= cell.options.length; + bSum /= cell.options.length; + fill(rSum, gSum, bSum); + noStroke(); + rect(i * w, j * h, w, h); + + if (cell.options.length == 0) { + fill(255, 0, 0, 100); + noStroke(); + rect(i * w, j * h, w, h); + } + } + } + } + + wfc(); + + // showAllTiles(); + // renderTile(tiles[0].img, 0, 100, width / DIM, height / DIM); +} + +function showAllTiles() { + let x = 0; + let y = 0; + const w = width / DIM; + const h = height / DIM; + + for (let i = 0; i < tiles.length; i++) { + let x = w * (i % 4); + let y = h * floor(i / 4); + + renderTile(tiles[i].img, x, y, w, h); + } +} + +// function mousePressed() { +// redraw(); +// } + +function reduce(cell, tiles, depth = 0) { + if (cell.checked) return; + cell.checked = true; + + if (depth > recursion_depth) return; + + const x = cell.index % DIM; + const y = floor(cell.index / DIM); + + // RIGHT + if (x + 1 < DIM) { + let rightCell = grid[x + 1 + y * DIM]; + if (rightCell !== undefined && !rightCell.collapsed) { + let validOptions = []; + for (let i of cell.options) { + validOptions = validOptions.concat(tiles[i].adjacents[RIGHT]); + } + // only validOptions that are already in rightCell.options can stay + rightCell.options = rightCell.options.filter((x) => validOptions.includes(x)); + reduce(rightCell, tiles, depth + 1); + } + } + + // LEFT + if (x - 1 >= 0) { + let leftCell = grid[x - 1 + y * DIM]; + if (leftCell !== undefined && !leftCell.collapsed) { + let validOptions = []; + for (let i of cell.options) { + validOptions = validOptions.concat(tiles[i].adjacents[LEFT]); + } + // only validOptions that are already in leftCell.options can stay + leftCell.options = leftCell.options.filter((x) => validOptions.includes(x)); + reduce(leftCell, tiles, depth + 1); + } + } + + // UP + if (y - 1 >= 0) { + let upCell = grid[x + (y - 1) * DIM]; + if (upCell !== undefined && !upCell.collapsed) { + let validOptions = []; + for (let i of cell.options) { + validOptions = validOptions.concat(tiles[i].adjacents[UP]); + } + // only validOptions that are already in upCell.options can stay + upCell.options = upCell.options.filter((x) => validOptions.includes(x)); + reduce(upCell, tiles, depth + 1); + } + } + + // DOWN + if (y + 1 < DIM) { + let downCell = grid[x + (y + 1) * DIM]; + if (downCell !== undefined && !downCell.collapsed) { + let validOptions = []; + for (let i of cell.options) { + validOptions = validOptions.concat(tiles[i].adjacents[DOWN]); + } + // only validOptions that are already in downCell.options can stay + downCell.options = downCell.options.filter((x) => validOptions.includes(x)); + reduce(downCell, tiles, depth + 1); + } + } +} + +function wfc() { + // Pick cell with least entropy + let gridCopy = grid.slice(); + gridCopy = gridCopy.filter((a) => !a.collapsed); + if (gridCopy.length == 0) { + return; + } + gridCopy.sort((a, b) => { + return a.options.length - b.options.length; + }); + + let len = gridCopy[0].options.length; + let stopIndex = 0; + for (let i = 1; i < gridCopy.length; i++) { + if (gridCopy[i].options.length > len) { + stopIndex = i; + break; + } + } + if (stopIndex > 0) gridCopy.splice(stopIndex); + + const cell = random(gridCopy); + cell.collapsed = true; + const pick = random(cell.options); + cell.options = [pick]; + reduce(cell, tiles); +} + +function renderCenter(pattern, x, y, w, h) { + // pattern.loadPixels(); + // hardcoding center pixel + let i = 1; + let j = 1; + let index = (i + j * tileSize) * 4; + const r = pattern.pixels[index + 0]; + const g = pattern.pixels[index + 1]; + const b = pattern.pixels[index + 2]; + fill(r, g, b); + noStroke(); + rect(x, y, w, h); +} + +function renderTile(pattern, x, y, w, h) { + // pattern.loadPixels(); + for (let i = 0; i < tileSize; i++) { + for (let j = 0; j < tileSize; j++) { + let index = (i + j * tileSize) * 4; + const r = pattern.pixels[index + 0]; + const g = pattern.pixels[index + 1]; + const b = pattern.pixels[index + 2]; + fill(r, g, b); + noStroke(); + rect(x + (i * w) / tileSize, y + (j * h) / tileSize, w / tileSize, h / tileSize); + } + } + // stroke(255, 0, 0); + // noFill(); + // rect(x, y, w, h); +} diff --git a/overlapping-model/tile.js b/overlapping-model/tile.js new file mode 100644 index 0000000..a912d41 --- /dev/null +++ b/overlapping-model/tile.js @@ -0,0 +1,80 @@ +class Tile { + constructor(img, i) { + this.img = img; + this.index = i; + this.adjacents = []; + } + + adjacancies(tiles) { + this.adjacents[LEFT] = []; + this.adjacents[RIGHT] = []; + this.adjacents[UP] = []; + this.adjacents[DOWN] = []; + for (let t of tiles) { + if (this.index == t.index) continue; + if (compareHEdge(this.img, t.img, 0, 1)) { + this.adjacents[LEFT].push(t.index); + } + if (compareHEdge(this.img, t.img, 1, 0)) { + this.adjacents[RIGHT].push(t.index); + } + if (compareVEdge(this.img, t.img, 0, 1)) { + this.adjacents[UP].push(t.index); + } + if (compareVEdge(this.img, t.img, 1, 0)) { + this.adjacents[DOWN].push(t.index); + } + } + } +} + +function compareHEdge(a, b, aEdge, bEdge) { + a.loadPixels(); + b.loadPixels(); + let same = true; + + for (j = 0; j < 2; j++) { + for (let i = 0; i < tileSize; i++) { + let colA = aEdge + (j % tileSize); + let colB = bEdge + (j % tileSize); + let aIndex = (colA + i * tileSize) * 4; + let bIndex = (colB + i * tileSize) * 4; + let ar = a.pixels[aIndex + 0]; + let br = b.pixels[bIndex + 0]; + let ag = a.pixels[aIndex + 1]; + let bg = b.pixels[bIndex + 1]; + let ab = a.pixels[aIndex + 2]; + let bb = b.pixels[bIndex + 2]; + if (ar != br || ag != bg || ab != bb) { + same = false; + break; + } + } + } + return same; +} + +function compareVEdge(a, b, aEdge, bEdge) { + a.loadPixels(); + b.loadPixels(); + let same = true; + for (j = 0; j < 2; j++) { + for (let i = 0; i < tileSize; i++) { + let rowA = aEdge + (j % tileSize); + let rowB = bEdge + (j % tileSize); + let aIndex = (i + rowA * tileSize) * 4; + let bIndex = (i + rowB * tileSize) * 4; + let ar = a.pixels[aIndex + 0]; + let br = b.pixels[bIndex + 0]; + let ag = a.pixels[aIndex + 1]; + let bg = b.pixels[bIndex + 1]; + let ab = a.pixels[aIndex + 2]; + let bb = b.pixels[bIndex + 2]; + if (ar != br || ag != bg || ab != bb) { + same = false; + break; + } + } + } + return same; +} diff --git a/overlapping-model/water.png b/overlapping-model/water.png new file mode 100644 index 0000000..9af51fc Binary files /dev/null and b/overlapping-model/water.png differ diff --git a/cell.js b/tile-model/cell.js similarity index 100% rename from cell.js rename to tile-model/cell.js diff --git a/tile-model/index.html b/tile-model/index.html new file mode 100644 index 0000000..dc7b1fc --- /dev/null +++ b/tile-model/index.html @@ -0,0 +1,20 @@ + + + + + + Wave Function Collapse + + + + +
+ + + + + diff --git a/sketch.js b/tile-model/sketch.js similarity index 100% rename from sketch.js rename to tile-model/sketch.js diff --git a/tile.js b/tile-model/tile.js similarity index 100% rename from tile.js rename to tile-model/tile.js diff --git a/tiles/circuit-coding-train/0.png b/tile-model/tiles/circuit-coding-train/0.png similarity index 100% rename from tiles/circuit-coding-train/0.png rename to tile-model/tiles/circuit-coding-train/0.png diff --git a/tiles/circuit-coding-train/1.png b/tile-model/tiles/circuit-coding-train/1.png similarity index 100% rename from tiles/circuit-coding-train/1.png rename to tile-model/tiles/circuit-coding-train/1.png diff --git a/tiles/circuit-coding-train/10.png b/tile-model/tiles/circuit-coding-train/10.png similarity index 100% rename from tiles/circuit-coding-train/10.png rename to tile-model/tiles/circuit-coding-train/10.png diff --git a/tiles/circuit-coding-train/11.png b/tile-model/tiles/circuit-coding-train/11.png similarity index 100% rename from tiles/circuit-coding-train/11.png rename to tile-model/tiles/circuit-coding-train/11.png diff --git a/tiles/circuit-coding-train/12.png b/tile-model/tiles/circuit-coding-train/12.png similarity index 100% rename from tiles/circuit-coding-train/12.png rename to tile-model/tiles/circuit-coding-train/12.png diff --git a/tiles/circuit-coding-train/2.png b/tile-model/tiles/circuit-coding-train/2.png similarity index 100% rename from tiles/circuit-coding-train/2.png rename to tile-model/tiles/circuit-coding-train/2.png diff --git a/tiles/circuit-coding-train/3.png b/tile-model/tiles/circuit-coding-train/3.png similarity index 100% rename from tiles/circuit-coding-train/3.png rename to tile-model/tiles/circuit-coding-train/3.png diff --git a/tiles/circuit-coding-train/4.png b/tile-model/tiles/circuit-coding-train/4.png similarity index 100% rename from tiles/circuit-coding-train/4.png rename to tile-model/tiles/circuit-coding-train/4.png diff --git a/tiles/circuit-coding-train/5.png b/tile-model/tiles/circuit-coding-train/5.png similarity index 100% rename from tiles/circuit-coding-train/5.png rename to tile-model/tiles/circuit-coding-train/5.png diff --git a/tiles/circuit-coding-train/6.png b/tile-model/tiles/circuit-coding-train/6.png similarity index 100% rename from tiles/circuit-coding-train/6.png rename to tile-model/tiles/circuit-coding-train/6.png diff --git a/tiles/circuit-coding-train/7.png b/tile-model/tiles/circuit-coding-train/7.png similarity index 100% rename from tiles/circuit-coding-train/7.png rename to tile-model/tiles/circuit-coding-train/7.png diff --git a/tiles/circuit-coding-train/8.png b/tile-model/tiles/circuit-coding-train/8.png similarity index 100% rename from tiles/circuit-coding-train/8.png rename to tile-model/tiles/circuit-coding-train/8.png diff --git a/tiles/circuit-coding-train/9.png b/tile-model/tiles/circuit-coding-train/9.png similarity index 100% rename from tiles/circuit-coding-train/9.png rename to tile-model/tiles/circuit-coding-train/9.png diff --git a/tiles/circuit/0.png b/tile-model/tiles/circuit/0.png similarity index 100% rename from tiles/circuit/0.png rename to tile-model/tiles/circuit/0.png diff --git a/tiles/circuit/1.png b/tile-model/tiles/circuit/1.png similarity index 100% rename from tiles/circuit/1.png rename to tile-model/tiles/circuit/1.png diff --git a/tiles/circuit/10.png b/tile-model/tiles/circuit/10.png similarity index 100% rename from tiles/circuit/10.png rename to tile-model/tiles/circuit/10.png diff --git a/tiles/circuit/11.png b/tile-model/tiles/circuit/11.png similarity index 100% rename from tiles/circuit/11.png rename to tile-model/tiles/circuit/11.png diff --git a/tiles/circuit/12.png b/tile-model/tiles/circuit/12.png similarity index 100% rename from tiles/circuit/12.png rename to tile-model/tiles/circuit/12.png diff --git a/tiles/circuit/2.png b/tile-model/tiles/circuit/2.png similarity index 100% rename from tiles/circuit/2.png rename to tile-model/tiles/circuit/2.png diff --git a/tiles/circuit/3.png b/tile-model/tiles/circuit/3.png similarity index 100% rename from tiles/circuit/3.png rename to tile-model/tiles/circuit/3.png diff --git a/tiles/circuit/4.png b/tile-model/tiles/circuit/4.png similarity index 100% rename from tiles/circuit/4.png rename to tile-model/tiles/circuit/4.png diff --git a/tiles/circuit/5.png b/tile-model/tiles/circuit/5.png similarity index 100% rename from tiles/circuit/5.png rename to tile-model/tiles/circuit/5.png diff --git a/tiles/circuit/6.png b/tile-model/tiles/circuit/6.png similarity index 100% rename from tiles/circuit/6.png rename to tile-model/tiles/circuit/6.png diff --git a/tiles/circuit/7.png b/tile-model/tiles/circuit/7.png similarity index 100% rename from tiles/circuit/7.png rename to tile-model/tiles/circuit/7.png diff --git a/tiles/circuit/8.png b/tile-model/tiles/circuit/8.png similarity index 100% rename from tiles/circuit/8.png rename to tile-model/tiles/circuit/8.png diff --git a/tiles/circuit/9.png b/tile-model/tiles/circuit/9.png similarity index 100% rename from tiles/circuit/9.png rename to tile-model/tiles/circuit/9.png diff --git a/tiles/demo-tracks/blank.png b/tile-model/tiles/demo-tracks/blank.png similarity index 100% rename from tiles/demo-tracks/blank.png rename to tile-model/tiles/demo-tracks/blank.png diff --git a/tiles/demo-tracks/down.png b/tile-model/tiles/demo-tracks/down.png similarity index 100% rename from tiles/demo-tracks/down.png rename to tile-model/tiles/demo-tracks/down.png diff --git a/tiles/demo-tracks/left.png b/tile-model/tiles/demo-tracks/left.png similarity index 100% rename from tiles/demo-tracks/left.png rename to tile-model/tiles/demo-tracks/left.png diff --git a/tiles/demo-tracks/right.png b/tile-model/tiles/demo-tracks/right.png similarity index 100% rename from tiles/demo-tracks/right.png rename to tile-model/tiles/demo-tracks/right.png diff --git a/tiles/demo-tracks/up.png b/tile-model/tiles/demo-tracks/up.png similarity index 100% rename from tiles/demo-tracks/up.png rename to tile-model/tiles/demo-tracks/up.png diff --git a/tiles/demo/blank.png b/tile-model/tiles/demo/blank.png similarity index 100% rename from tiles/demo/blank.png rename to tile-model/tiles/demo/blank.png diff --git a/tiles/demo/down.png b/tile-model/tiles/demo/down.png similarity index 100% rename from tiles/demo/down.png rename to tile-model/tiles/demo/down.png diff --git a/tiles/demo/left.png b/tile-model/tiles/demo/left.png similarity index 100% rename from tiles/demo/left.png rename to tile-model/tiles/demo/left.png diff --git a/tiles/demo/right.png b/tile-model/tiles/demo/right.png similarity index 100% rename from tiles/demo/right.png rename to tile-model/tiles/demo/right.png diff --git a/tiles/demo/up.png b/tile-model/tiles/demo/up.png similarity index 100% rename from tiles/demo/up.png rename to tile-model/tiles/demo/up.png diff --git a/tiles/mountains/blank.png b/tile-model/tiles/mountains/blank.png similarity index 100% rename from tiles/mountains/blank.png rename to tile-model/tiles/mountains/blank.png diff --git a/tiles/mountains/down.png b/tile-model/tiles/mountains/down.png similarity index 100% rename from tiles/mountains/down.png rename to tile-model/tiles/mountains/down.png diff --git a/tiles/mountains/left.png b/tile-model/tiles/mountains/left.png similarity index 100% rename from tiles/mountains/left.png rename to tile-model/tiles/mountains/left.png diff --git a/tiles/mountains/right.png b/tile-model/tiles/mountains/right.png similarity index 100% rename from tiles/mountains/right.png rename to tile-model/tiles/mountains/right.png diff --git a/tiles/mountains/up.png b/tile-model/tiles/mountains/up.png similarity index 100% rename from tiles/mountains/up.png rename to tile-model/tiles/mountains/up.png diff --git a/tiles/pipes/blank.png b/tile-model/tiles/pipes/blank.png similarity index 100% rename from tiles/pipes/blank.png rename to tile-model/tiles/pipes/blank.png diff --git a/tiles/pipes/down.png b/tile-model/tiles/pipes/down.png similarity index 100% rename from tiles/pipes/down.png rename to tile-model/tiles/pipes/down.png diff --git a/tiles/pipes/left.png b/tile-model/tiles/pipes/left.png similarity index 100% rename from tiles/pipes/left.png rename to tile-model/tiles/pipes/left.png diff --git a/tiles/pipes/right.png b/tile-model/tiles/pipes/right.png similarity index 100% rename from tiles/pipes/right.png rename to tile-model/tiles/pipes/right.png diff --git a/tiles/pipes/up.png b/tile-model/tiles/pipes/up.png similarity index 100% rename from tiles/pipes/up.png rename to tile-model/tiles/pipes/up.png diff --git a/tiles/polka/blank.png b/tile-model/tiles/polka/blank.png similarity index 100% rename from tiles/polka/blank.png rename to tile-model/tiles/polka/blank.png diff --git a/tiles/polka/down.png b/tile-model/tiles/polka/down.png similarity index 100% rename from tiles/polka/down.png rename to tile-model/tiles/polka/down.png diff --git a/tiles/polka/left.png b/tile-model/tiles/polka/left.png similarity index 100% rename from tiles/polka/left.png rename to tile-model/tiles/polka/left.png diff --git a/tiles/polka/right.png b/tile-model/tiles/polka/right.png similarity index 100% rename from tiles/polka/right.png rename to tile-model/tiles/polka/right.png diff --git a/tiles/polka/up.png b/tile-model/tiles/polka/up.png similarity index 100% rename from tiles/polka/up.png rename to tile-model/tiles/polka/up.png diff --git a/tiles/rail/tile0.png b/tile-model/tiles/rail/tile0.png similarity index 100% rename from tiles/rail/tile0.png rename to tile-model/tiles/rail/tile0.png diff --git a/tiles/rail/tile1.png b/tile-model/tiles/rail/tile1.png similarity index 100% rename from tiles/rail/tile1.png rename to tile-model/tiles/rail/tile1.png diff --git a/tiles/rail/tile2.png b/tile-model/tiles/rail/tile2.png similarity index 100% rename from tiles/rail/tile2.png rename to tile-model/tiles/rail/tile2.png diff --git a/tiles/rail/tile3.png b/tile-model/tiles/rail/tile3.png similarity index 100% rename from tiles/rail/tile3.png rename to tile-model/tiles/rail/tile3.png diff --git a/tiles/rail/tile4.png b/tile-model/tiles/rail/tile4.png similarity index 100% rename from tiles/rail/tile4.png rename to tile-model/tiles/rail/tile4.png diff --git a/tiles/rail/tile5.png b/tile-model/tiles/rail/tile5.png similarity index 100% rename from tiles/rail/tile5.png rename to tile-model/tiles/rail/tile5.png diff --git a/tiles/rail/tile6.png b/tile-model/tiles/rail/tile6.png similarity index 100% rename from tiles/rail/tile6.png rename to tile-model/tiles/rail/tile6.png diff --git a/tiles/roads/blank.png b/tile-model/tiles/roads/blank.png similarity index 100% rename from tiles/roads/blank.png rename to tile-model/tiles/roads/blank.png diff --git a/tiles/roads/down.png b/tile-model/tiles/roads/down.png similarity index 100% rename from tiles/roads/down.png rename to tile-model/tiles/roads/down.png diff --git a/tiles/roads/left.png b/tile-model/tiles/roads/left.png similarity index 100% rename from tiles/roads/left.png rename to tile-model/tiles/roads/left.png diff --git a/tiles/roads/right.png b/tile-model/tiles/roads/right.png similarity index 100% rename from tiles/roads/right.png rename to tile-model/tiles/roads/right.png diff --git a/tiles/roads/up.png b/tile-model/tiles/roads/up.png similarity index 100% rename from tiles/roads/up.png rename to tile-model/tiles/roads/up.png diff --git a/tiles/track-generator/index.html b/tile-model/tiles/track-generator/index.html similarity index 100% rename from tiles/track-generator/index.html rename to tile-model/tiles/track-generator/index.html diff --git a/tiles/track-generator/sketch.js b/tile-model/tiles/track-generator/sketch.js similarity index 100% rename from tiles/track-generator/sketch.js rename to tile-model/tiles/track-generator/sketch.js diff --git a/tiles/track-generator/train.frag b/tile-model/tiles/track-generator/train.frag similarity index 100% rename from tiles/track-generator/train.frag rename to tile-model/tiles/track-generator/train.frag diff --git a/tiles/track-generator/train.vert b/tile-model/tiles/track-generator/train.vert similarity index 100% rename from tiles/track-generator/train.vert rename to tile-model/tiles/track-generator/train.vert diff --git a/tiles/train-tracks/blank.png b/tile-model/tiles/train-tracks/blank.png similarity index 100% rename from tiles/train-tracks/blank.png rename to tile-model/tiles/train-tracks/blank.png diff --git a/tiles/train-tracks/down.png b/tile-model/tiles/train-tracks/down.png similarity index 100% rename from tiles/train-tracks/down.png rename to tile-model/tiles/train-tracks/down.png diff --git a/tiles/train-tracks/left.png b/tile-model/tiles/train-tracks/left.png similarity index 100% rename from tiles/train-tracks/left.png rename to tile-model/tiles/train-tracks/left.png diff --git a/tiles/train-tracks/right.png b/tile-model/tiles/train-tracks/right.png similarity index 100% rename from tiles/train-tracks/right.png rename to tile-model/tiles/train-tracks/right.png diff --git a/tiles/train-tracks/up.png b/tile-model/tiles/train-tracks/up.png similarity index 100% rename from tiles/train-tracks/up.png rename to tile-model/tiles/train-tracks/up.png