Skip to content

Instantly share code, notes, and snippets.

@KrabCode
Created September 13, 2021 15:48
Show Gist options
  • Select an option

  • Save KrabCode/a73dc6fbfba6858117f8ef69f82c2c00 to your computer and use it in GitHub Desktop.

Select an option

Save KrabCode/a73dc6fbfba6858117f8ef69f82c2c00 to your computer and use it in GitHub Desktop.
Working powder toy with a simple material picker for APDE.
// 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.1f;
int w, h;
int gx,gy,gw,gh;
Particle[][] grid;
private float inputSize = 5;
int sandColor, waterColor, airColor, rockColor, rockStroke;
String[] particleTypes = new String[]{"air", "sand", "water", "rock"};
String selectedType = "water";
boolean prevMousePressed = false;
public void setup() {
fullScreen(P2D);
populateGrid();
pg = createGraphics(width, height, P2D);
}
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(1, 0);
sandColor = color(0.8);
waterColor = color(0.55, 1, 1);
rockColor = color(0.3);
rockStroke = color(0.5f);
fallDirection = 0.1;
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(1.9);
pg.fill(1);
boolean mouseJustPressed = mousePressed && !prevMousePressed;
prevMousePressed = mousePressed;
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/2,gy-gh/2,gw,gh);
}
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() {
float inputSize = 5;
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;
}
@SuppressWarnings("DuplicatedCode")
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);
}
}
@SuppressWarnings("DuplicatedCode")
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) > fallDirection) {
swapParticles(x, y, x + 1, y + 1);
} else if (botLeft != null && isLessDenseThanMe(botLeft) && random(1) > fallDirection) {
swapParticles(x, y, x - 1, y + 1);
} else if (left != null && isAir(left) && random(1) > fallDirection) {
swapParticles(x, y, x - 1, y);
} else if (right != null && isAir(right) && random(1) > fallDirection) {
swapParticles(x, y, x + 1, y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment