xxxxxxxxxx
203
// Differences from Schiffman Version:
// included offscreen cells so you don't get edge cluster conditions
// used objects with location and chemical amounts, and on/off screen variables
// maps colors across hue spectrum instead of grayscale, so you get rainbows
// has interactive (mouseclick) seeding
let cells = [];
let buffer = 50; // amount of cells outsize the visible edges
let cellSize = 4; // width and height (in pixels) of each cell. min 1. higher = better performance but lower resolution
let initialSeedRadius = 10; // initial size of chemical b seed, dropped in center of canvas
let scaledBuffer = buffer / cellSize; // buffer adjusted for cellSize / grid resolution
let dA = 1; // diffusion rate for chemical a
let dB = 0.5; // diffusion rate for chemical b
let feed = 0.055; // speed at which chemical b is added (defaults = 0.055)
let k = 0.062; // 'kill rate' for chemical a(?) I think (constant, rate at which chem a is used up, I think) (defaults = 0.62)
function setup() {
createCanvas(400, 400);
textSize(18);
colorMode(HSB, 100); // makes it easier to draw rainbows than RGB
createCells(); // initializes the 2D array of cell objects
seed(); // creates initial addition of chemical b as drop in center
}
function draw() {
if (focused || frameCount < 30) {
drawCells();
diffuseCells();
if (mouseIsPressed) {
mouseSeeding();
}
drawInstructions();
} else {
drawUnpauseInstructions();
}
}
function drawUnpauseInstructions() {
noStroke();
fill(255);
textAlign(CENTER);
text('click to activate', width / 2, height - height / 5);
}
function createCells() {
// for(let i = 0; i < (width / cellSize) + 2*(buffer / cellSize); i++){
// cells[i] = new Cell(i - scaledBuffer, 15);
// }
for (let i = 0; i < (width / cellSize) + 2 * scaledBuffer; i++) {
cells[i] = [];
for (let j = 0; j < (height / cellSize) + 2 * scaledBuffer; j++) {
cells[i][j] = new Cell(i - scaledBuffer, j - scaledBuffer);
}
}
}
// dras all onscreen cells
function drawCells() {
for (let i = 0; i < cells.length; i++) {
for (let j = 0; j < cells[i].length; j++) {
if (cells[i][j].onScreen) {
cells[i][j].draw();
}
}
}
}
// sumilates chemical diffusion, using Karl Sim's reaction diffusion algorythm, and updates cell properties
function diffuseCells() {
// for every cell, on or off screen, except the edge cases...
for (let i = 1; i < cells.length - 1; i++) {
for (let j = 1; j < cells[i].length - 1; j++) {
// calculate new values of a and b based off existing vales of that cell and it's surrounding cells
let a = cells[i][j].a
let b = cells[i][j].b
cells[i][j].aNext = a +
((dA * proximity(i, j, 'a')) -
(a * b * b) +
(feed * (1 - a)))
cells[i][j].bNext = b +
((dB * proximity(i, j, 'b')) +
(a * b * b) -
(b * (k + feed)))
}
}
// once all new chemical values are added, swap the new calculations for the main values
interateCellValues();
}
// calculates how much chemicals in adjacent cells influence the reaction in the current cell
function proximity(i, j, char) {
let answer = 0;
if (char === 'a') {
answer += cells[i][j].a * -1;
answer += cells[i + 1][j].a * 0.2;
answer += cells[i - 1][j].a * 0.2;
answer += cells[i][j + 1].a * 0.2;
answer += cells[i][j - 1].a * 0.2;
answer += cells[i + 1][j + 1].a * 0.05;
answer += cells[i + 1][j - 1].a * 0.05;
answer += cells[i - 1][j + 1].a * 0.05;
answer += cells[i - 1][j - 1].a * 0.05;
} else if (char === 'b') {
answer += cells[i][j].b * -1;
answer += cells[i + 1][j].b * 0.2;
answer += cells[i - 1][j].b * 0.2;
answer += cells[i][j + 1].b * 0.2;
answer += cells[i][j - 1].b * 0.2;
answer += cells[i + 1][j + 1].b * 0.05;
answer += cells[i + 1][j - 1].b * 0.05;
answer += cells[i - 1][j + 1].b * 0.05;
answer += cells[i - 1][j - 1].b * 0.05;
}
return answer;
}
function interateCellValues() {
for (let i = 0; i < cells.length; i++) {
for (let j = 0; j < cells[i].length; j++) {
cells[i][j].a = cells[i][j].aNext;
cells[i][j].b = cells[i][j].bNext;
}
}
}
// creates seed for cells
function seed() {
let centerX = floor(cells.length / 2);
let centerY = floor(cells.length / 2);
let r = floor(initialSeedRadius / cellSize);
for (let i = centerX - r; i < centerX + r; i++) {
for (let j = centerY - r; j < centerY + r; j++) {
cells[i][j].b = 1.0;
}
}
}
function mouseSeeding() {
if (mouseX < 0 || mouseX > width || mouseY < 0 || mouseY > height) {
return;
// this prevents seeding outside the buffer zone, which would cause a null pointer error
}
//maps mouseX & Y position to their relative location within the array of cells (cause 1 pixel might not equal one cell)
mappedMouseX = floor(map(mouseX, 0, width, scaledBuffer, cells.length - scaledBuffer));
mappedMouseY = floor(map(mouseY, 0, height, scaledBuffer, cells.length - scaledBuffer));
// adds chem a or b, based on mouse button
if (mouseButton === LEFT) {
if (cells[mappedMouseX][mappedMouseY].b < 1.0) {
cells[mappedMouseX][mappedMouseY].b += 0.2;
}
if (cells[cells.length - mappedMouseX][cells.length - mappedMouseY].b < 1.0) {
cells[cells.length - mappedMouseX][cells.length - mappedMouseY].b += 0.2;
}
if (cells[mappedMouseX][cells.length - mappedMouseY].b < 1.0) {
cells[mappedMouseX][cells.length - mappedMouseY].b += 0.2;
}
if (cells[cells.length - mappedMouseX][mappedMouseY].b < 1.0) {
cells[cells.length - mappedMouseX][mappedMouseY].b += 0.2;
}
} else if (mouseButton === RIGHT) {
if (cells[mappedMouseX][mappedMouseY].a < 1.0) {
cells[mappedMouseX][mappedMouseY].a += 0.35;
}
}
}
function drawInstructions() {
noStroke();
fill(255);
textAlign(CENTER);
text('hold l/r mouse buttons to add more colors', width / 2, height - 20);
}
// prevents context menu popup on right mouse button release
function mouseReleased() {
return false;
}
// cell object, contains location, amount of each chemical, and weather it's inside the window or not
class Cell {
constructor(x, y) {
this.a = 1; // amount of chemical a in this cell
this.b = 0; // amount of chemical b in this cell
this.aNext = this.a; // amount of chemical a in this cell for next round
this.bNext = this.b; // amount of chemical a in this cell for next round
this.x = x; // x location of cell
this.y = y; // y location of cell
// bool, true if this cell is within the window
if (this.x >= 0 && this.x < width && this.y >= 0 && this.y < height) {
this.onScreen = true;
} else this.onScreen = false;
}
draw() {
let sat = 100; // saturation
if (this.a > 0.9 && this.b < 0.1) sat = 50;
let c = color(map(this.a - this.b, 0, 1, 100, 0), sat, 100);
fill(c);
noStroke();
square(this.x * cellSize, this.y * cellSize, cellSize);
}
}