WorldGenWaveFunctionCollapse/WaveFunctionCollapse.pde
2022-05-15 17:28:42 +02:00

300 lines
8.8 KiB
Plaintext

// I stole the 'textures', i.e. the images, from this video: https://www.youtube.com/watch?v=20KHNA9jTsE
final int cubicDimensions = 10;
ArrayList<PImage> tiles = new ArrayList<PImage>();
ArrayList<Tile> grid = new ArrayList<Tile>(), copy;
int cubesWidth, cubesHeight;
int selectedIndex = 0;
void setup() {
fullScreen();
cubesWidth = width / cubicDimensions;
cubesHeight = height / cubicDimensions;
tiles.add(loadImage("LargeTrees.png"));
tiles.add(loadImage("SmallTrees.png"));
tiles.add(loadImage("Grass.png"));
tiles.add(loadImage("Sand.png"));
tiles.add(loadImage("Water.png"));
for (int j = 0; j < cubesHeight; j++) {
for (int i = 0; i < cubesWidth; i++) {
grid.add(new Tile(i, j));
}
}
copy = new ArrayList<Tile>(grid);
}
void draw() {
background(0);
// if (mousePressed) {
// int xI = floor(mouseX / cubicDimensions);
// int yI = floor(mouseY / cubicDimensions);
// int in = yI*cubesWidth+xI;
// Tile t = grid.get(in);
// if (!t.isCollapsed) {
// if (mouseButton == LEFT) {
// t.collapse();
// } else {
// t.collapse(selectedIndex);
// }
// ArrayList<Bundle> generation = new ArrayList<Bundle>();
// generation.add(new Bundle(t, t.update(null, 0)));
// int genCount = 1;
// while (generation.size() > 0) {
// ArrayList<Bundle> newGen = new ArrayList<Bundle>();
// for (Bundle b : generation) {
// for (Tile g : b.c) {
// if (!g.isUpdated) {
// ArrayList<Tile> nbrs = g.update(b.p, genCount);
// newGen.add(new Bundle(g, nbrs));
// }
// }
// }
// genCount++;
// generation = new ArrayList<Bundle>(newGen);
// }
// for (Tile g : grid) {
// g.isUpdated = false;
// }
// }
// }
// Display Grid of Tiles
for (Tile t : grid) {
t.display();
}
// Overlay Grid Pattern
noFill();
stroke(255, 255/4);
for (int j = 0; j < cubesHeight; j++) {
for (int i = 0; i < cubesWidth; i++) {
rect(i*cubicDimensions, j*cubicDimensions, cubicDimensions, cubicDimensions);
}
}
// Display selected index of tile to choose & framerate
textSize(24);
fill(255,0,0);
stroke(255,0,0);
textAlign(LEFT, TOP);
text(selectedIndex, 0, 0);
textAlign(RIGHT, TOP);
text(frameRate, width, 0);
// Collapse multiple Cells per frame
for (int i = 0; i < 20; i++) {
if (copy.size() > 0) {
autoCollapse();
} else {
break;
}
}
}
void keyPressed() {
if (key == CODED) {
switch (keyCode) {
case LEFT:
selectedIndex -= (selectedIndex > 0) ? 1 : 0;
break;
case RIGHT:
selectedIndex += (selectedIndex < tiles.size()-1) ? 1 : 0;
break;
}
}
}
void autoCollapse() {
// floor(random(copy.size()))
int in = floor(random(5))*cubesWidth+floor(random(5));
if (in > copy.size()-1) {
in = floor(map(in, 0, 4*cubesWidth+4, 0, copy.size()-1));
}
Tile t = copy.get(in);
copy.remove(in);
if (!t.isCollapsed) {
t.collapse();
ArrayList<Bundle> generation = new ArrayList<Bundle>();
generation.add(new Bundle(t, t.update(null, 0)));
int genCount = 1;
while (generation.size() > 0) {
ArrayList<Bundle> newGen = new ArrayList<Bundle>();
for (Bundle b : generation) {
for (Tile g : b.c) {
if (!g.isUpdated) {
ArrayList<Tile> nbrs = g.update(b.p, genCount);
newGen.add(new Bundle(g, nbrs));
}
}
}
genCount++;
generation = new ArrayList<Bundle>(newGen);
}
for (Tile g : grid) {
g.isUpdated = false;
}
}
}
class Bundle {
ArrayList<Tile> c;
Tile p;
Bundle(Tile parent, ArrayList<Tile> children) {
p = parent;
c = children;
}
}
class Tile {
ArrayList<Integer> possibilities = new ArrayList<Integer>();
boolean isCollapsed = false, isUpdated;
int xIndex, yIndex, state;
Tile(int xI, int yJ) {
xIndex = xI;
yIndex = yJ;
for (int i = 0; i < tiles.size(); i++) {
possibilities.add(i);
}
}
ArrayList<Tile> update(Tile other, int count) {
ArrayList<Tile> neighbors = new ArrayList<Tile>();
if (!isUpdated) {
neighbors = getNeighbors();
isUpdated = true;
// fill(255);
// stroke(255);
// float xPos = xIndex*cubicDimensions;
// float yPos = yIndex*cubicDimensions;
// textAlign(CENTER, CENTER);
// textSize(cubicDimensions / 2);
// text(count, xPos+cubicDimensions/2, yPos+cubicDimensions/2);
// Recalculate all possible states
if (other != null) {
for (int k = possibilities.size()-1; k >= 0; k--) {
boolean misfit = true;
for (int h = other.possibilities.size()-1; h >= 0; h--) {
if (abs(other.possibilities.get(h) - possibilities.get(k)) <= 1) {
misfit = false;
}
}
if (misfit) {
possibilities.remove(k);
}
}
}
}
return neighbors;
}
ArrayList<Tile> getNeighbors() {
ArrayList<Tile> neighbors = new ArrayList<Tile>();
for (int j = -1; j < 2; j++) {
for (int i = -1; i < 2; i++) {
if (!(i == 0 && j == 0) && abs(i)+abs(j) < 2) {
int nX = xIndex + i;
int nY = yIndex + j;
if (nX >= 0 && nX < cubesWidth && nY >= 0 && nY < cubesHeight) {
int nI = nY * cubesWidth + nX;
Tile nT = grid.get(nI);
neighbors.add(nT);
}
}
}
}
return neighbors;
}
void collapse() {
int tileIndex = -1;
ArrayList<Tile> neighbors = getNeighbors();
ArrayList<Integer> probabilities = new ArrayList<Integer>();
for (int i = 0; i < tiles.size(); i++) {
int tileCount = 1;
for (Tile n : neighbors) {
if (n.isCollapsed && n.state == i) {
tileCount++;
}
}
if (i == 4 || i == 2) { tileCount = floor(pow(tileCount,3)/(8/3))+1; }
probabilities.add(floor(pow(tileCount,3)));
}
int totalTiles = 0;
for (int i = 0; i < probabilities.size(); i++) {
boolean isPossible = false;
for (int j = 0 ; j < possibilities.size(); j++) {
if (i == possibilities.get(j)) {
isPossible = true;
break;
}
}
if (!isPossible) {
probabilities.set(i, 0);
} else {
totalTiles += probabilities.get(i);
}
}
if (totalTiles > 0) {
float randomValue = random(1), probability = 0;
// println(randomValue, probabilities);
for (int i = 0; i < probabilities.size(); i++) {
probability += ((float)probabilities.get(i))/totalTiles;
if (randomValue <= probability) {
tileIndex = i;
break;
}
}
}
if (tileIndex == -1) {
state = possibilities.get(floor(random(possibilities.size())));
} else {
state = tileIndex;
}
finishCollapse();
}
void collapse(int inputState) {
state = inputState;
finishCollapse();
}
void finishCollapse() {
for (int k = possibilities.size()-1; k >= 0; k--) {
if (possibilities.get(k) != state) {
possibilities.remove(k);
}
}
isCollapsed = true;
}
void display() {
float xPos = xIndex*cubicDimensions;
float yPos = yIndex*cubicDimensions;
if (isCollapsed) {
image(tiles.get(state), xPos, yPos, cubicDimensions, cubicDimensions);
} else {
// fill(255);
// stroke(255);
// textAlign(CENTER, CENTER);
// textSize(cubicDimensions / 2);
// text(possibilities.size(), xPos+cubicDimensions/2, yPos+cubicDimensions/2);
}
}
}