Skip to content

Instantly share code, notes, and snippets.

@KrabCode
Created January 16, 2025 20:55
Show Gist options
  • Save KrabCode/7a8d2949553e04b6a8f809a5d88f6402 to your computer and use it in GitHub Desktop.
Save KrabCode/7a8d2949553e04b6a8f809a5d88f6402 to your computer and use it in GitHub Desktop.
// Should work on APDE
// Based on a GDC talk called "Exploring the Tech and Design of Noita"
// https://www.youtube.com/watch?v=prXuyMCgbTc
private PGraphics pg;
float scale = 0.075f;
float prevScale = scale;
float rectWidth, rectHeight;
float fallDirection = 0.05;
int w, h;
int gx, gy, gw, gh;
Particle[][] grid;
private float inputSize = 4;
int sandColor, waterColor, airColor, rockColor, rockStroke;
String[] particleTypes = new String[]{"air", "sand", "water", "rock"};
String selectedType = "water";
boolean prevMousePressed = false;
boolean mouseJustPressed = false;
public void setup() {
fullScreen();
populateGrid();
pg = createGraphics(width, height);
}
private void populateGrid() {
h = ceil(height * scale);
w = ceil(width * scale);
grid = new Particle[w][h];
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
grid[x][y] = new Particle();
}
}
}
public void draw() {
colorMode(HSB, 1, 1, 1, 1);
airColor = color(0);
sandColor = color(0.8);
waterColor = color(0.55, 1, 1);
rockColor = color(0.3);
rockStroke = color(0.5f);
mouseJustPressed = mousePressed && !prevMousePressed;
prevMousePressed = mousePressed;
if (mousePressedOutsideGui()) {
inputParticles();
}
spawnParticles();
updateGrid();
pg.beginDraw();
pg.colorMode(HSB, 1, 1, 1, 1);
pg.background(0);
displayGrid();
displayGUI();
pg.endDraw();
image(pg, 0, 0);
}
void displayGUI() {
gx = width /2;
gy = height - 200;
gw = 500;
gh = 100;
pg.rectMode(CENTER);
pg.strokeWeight(5);
pg.fill(1);
for (int i = 0; i < 4; i++) {
float iNorm = norm(i, 0, 3);
String type = particleTypes[i];
if (type.equals(selectedType)) {
pg.stroke(1);
} else {
pg.stroke(0.3);
}
if (type.equals("air")) {
pg.fill(airColor);
}
if (type.equals("water")) {
pg.fill(waterColor);
}
if (type.equals("rock")) {
pg.fill(rockColor);
}
if (type.equals("sand")) {
pg.fill(sandColor);
}
float x = gx - gw/2 + iNorm * gw;
if (mouseJustPressed &&
pointRect(mouseX, mouseY,
x-gh/2, gy-gh/2, gh, gh)) {
selectedType = type;
}
pg.rect(x, gy, gh, gh);
}
}
boolean pointRect(float px, float py, float rx, float ry, float rw, float rh) {
return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh;
}
boolean mousePressedOutsideGui() {
return mousePressed &&
!pointRect(mouseX, mouseY,
gx-gw*0.75, gy-gh*0.75, gw*1.5, gh*1.5);
}
float particlesSpawned = 0;
private void spawnParticles() {
String rainType = "water";
float particlesToSpawn = frameCount;
while (particlesSpawned < particlesToSpawn) {
int x = floor(random(w));
grid[x][0] = new Particle(rainType);
particlesSpawned += 2;
}
}
private void updateGrid() {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
Particle p = grid[x][y];
p.hasBeenUpdated = false;
}
}
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
Particle p = grid[x][y];
p.update(x, y);
}
}
}
private void displayGrid() {
pg.rectMode(CORNER);
pg.noStroke();
rectWidth = width / (float) w + 1;
rectHeight = height / (float) h + 1;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
float screenX = map(x, 0, w, 0, width);
float screenY = map(y, 0, h, 0, height);
Particle p = grid[x][y];
pg.fill(p.getFill());
pg.rect(screenX, screenY, rectWidth, rectHeight);
}
}
}
void inputParticles() {
int inputX = floor(map(mouseX, 0, width, 0, w));
int inputY = floor(map(mouseY, 0, height, 0, h));
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
if (dist(x, y, inputX, inputY) < inputSize) {
Particle p = new Particle(selectedType);
grid[x][y] = p;
}
}
}
}
class Particle {
String type = "air";
boolean hasBeenUpdated = false;
Particle() {
}
Particle(String type) {
this.type = type;
}
void update(int x, int y) {
if (type.equals("air")) {
return;
}
if (type.equals("rock")) {
return;
}
if (type.equals("sand")) {
updateSand(x, y);
}
if (type.equals("water")) {
updateWater(x, y);
}
}
int getFill() {
if (type.equals("air")) {
return airColor;
}
if (type.equals("sand")) {
return sandColor;
}
if (type.equals("water")) {
return waterColor;
}
if (type.equals("rock")) {
return rockColor;
}
return 0;
}
boolean isAir(Particle other) {
return other.getDensity() == 0;
}
boolean isLessDenseThanMe(Particle other) {
return other.getDensity() < getDensity();
}
float getDensity() {
if (type.equals("air")) {
return 0;
}
if (type.equals("rock")) {
return 100;
}
if (type.equals("sand")) {
return 10;
}
if (type.equals("water")) {
return 2;
}
return 0;
}
Particle getParticleSafely(int x, int y) {
// avoid index out of bounds, return null instead
if (x < 0 || x >= w || y < 0 || y >= h) {
return null;
}
return grid[x][y];
}
void swapParticles(int x0, int y0, int x1, int y1) {
Particle a = grid[x0][y0];
Particle b = grid[x1][y1];
a.hasBeenUpdated = true;
b.hasBeenUpdated = true;
grid[x0][y0] = b;
grid[x1][y1] = a;
}
private void updateSand(int x, int y) {
Particle bot = getParticleSafely(x, y + 1);
Particle botRight = getParticleSafely(x + 1, y + 1);
Particle botLeft = getParticleSafely(x - 1, y + 1);
if (hasBeenUpdated) {
return;
}
if (bot != null && isLessDenseThanMe(bot)) {
swapParticles(x, y, x, y + 1);
} else if (botRight != null && isLessDenseThanMe(botRight) && random(1) > fallDirection) {
swapParticles(x, y, x + 1, y + 1);
} else if (botLeft != null && isLessDenseThanMe(botLeft) && random(1) > fallDirection) {
swapParticles(x, y, x - 1, y + 1);
}
}
private void updateWater(int x, int y) {
Particle bot = getParticleSafely(x, y + 1);
Particle botRight = getParticleSafely(x + 1, y + 1);
Particle botLeft = getParticleSafely(x - 1, y + 1);
Particle left = getParticleSafely(x - 1, y);
Particle right = getParticleSafely(x + 1, y);
if (hasBeenUpdated) {
return;
}
if (bot != null && isLessDenseThanMe(bot)) {
swapParticles(x, y, x, y + 1);
} else if (botRight != null && isLessDenseThanMe(botRight) && random(1) > 0.5) {
swapParticles(x, y, x + 1, y + 1);
} else if (botLeft != null && isLessDenseThanMe(botLeft)) {
swapParticles(x, y, x - 1, y + 1);
} else if (left != null && isAir(left) && random(1) > 0.5) {
swapParticles(x, y, x - 1, y);
} else if (right != null && isAir(right)) {
swapParticles(x, y, x + 1, y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment