-
Notifications
You must be signed in to change notification settings - Fork 0
/
sketch.js
307 lines (260 loc) · 8.86 KB
/
sketch.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/*
Author(s): Team Uhhhhhh
Leif, Muhammad, Rayane
Date: 06/11/2022
Description: This script uses p5.js to simulate the behavior of slime molds in the genus Physarum.
*/
//////////////////////////////// SETTING CONSTANTS /////////////////////////////////////
// Pre-setup pi
const INIT_PI = 3.14159265358979323846;
// Dimensions of the image to be rendered
const imageWidth = 200; const imageHeight = 200;
// Mouse Click Radius
let clickRadius = 15; let radiusPrompt;
// The number of particles spawned per click
const particlesPerClick = 1000;
// The maximum number of particles that can be spawned
const maxParticles = 100000;
// Diffusion size
const diffusionSize = 2;
// Decay factor (the rate at which the attracters that direct the slime mold's growth decay)
let decayFactor = 0.9; let decayPrompt;
// Sensor angle (in radians)
let sensorAngle = INIT_PI / 8; let SAprompt;
// Rotate angle (in radians)
let rotateAngle = INIT_PI / 4; let RAprompt;
// Step size
let stepSize = 1; let SSprompt;
// Sensor Offset Distance
const sensorOffsetDistance = 9;
// Attract factor
const attractionFactor = 5;
// Random Density
const randomDensity = 0.2;
// Sensor Width
// const sensorWidth = 1;
// Arrays containing the particles, attracters, and emitters in the scene
let particles = [];
let attracters = [];
let emitters = [];
// Number of particles emitted per second by emitters in the scene
let numParticlesEmitted = 5;
// Image of attractors (the visual output by p5)
let imageVisualOutput;
// buttons
let updateButton;
// let clearButton = createButton("CLEAR");
// let randomButton = createButton("RANDOM");
// The other 2 buttons are in setup()
//////////////////////////////// /////////////////////////////////////
function setup() {
imageVisualOutput = createImage(imageWidth, imageHeight);
imageVisualOutput.loadPixels();
//// InputSliders
//create the settings input boxes
radiusPrompt = createInput(str(clickRadius));
decayPrompt = createInput(str(decayFactor));
SAprompt = createInput(str((sensorAngle / PI) * 180));
RAprompt = createInput(str(str((rotateAngle / PI) * 180)));
SSprompt = createInput(str(stepSize));
//size of input boxes
decayPrompt.size(40);
radiusPrompt.size(40);
SAprompt.size(40);
RAprompt.size(40);
SSprompt.size(40);
//putting them inside the html
radiusPrompt.parent("radiusSlider");
decayPrompt.parent("decaySlider");
SAprompt.parent("sensorAngleSlider");
RAprompt.parent("rotateAngleSlider");
SSprompt.parent("stepSizeSlider");
/// Buttons
//clear
let clearButton = createButton("CLEAR");
clearButton.mousePressed(clearGrid);
clearButton.parent("clear&random&updateButtons");
//random
let randomButton = createButton("RANDOM");
randomButton.mousePressed(randomSpread);
randomButton.parent("clear&random&updateButtons");
//add update button
//update
updateButton = createButton("UPDATE");
updateButton.mousePressed(updateValues);
updateButton.parent("clear&random&updateButtons");
updateButton.parent("clear&random&updateButtons");
//Create Canvas
canvas = createCanvas(imageWidth, imageHeight);
canvas.mouseClicked(canvasClick);
canvas.parent("slimeContainer");
background(0);
// add zeroes to an attracters array for every pixel in the image.
for (let i = 0; i < imageWidth; i++) {
let length = [];
for (let j = 0; j < imageHeight; j++) {
length.push(0);
}
attracters.push(length);
}
}
// The main method basically
function draw() {
// Base settings
background(0);
stroke(255, 165, 0);
strokeWeight(5);
fill(255, 255, 255, 50);
textAlign(CENTER, CENTER);
image(imageVisualOutput, 0, 0);
//
// Emitters settings
// for every x in emitters.
for (let x of emitters) {
for (
let i = 0;
i < numParticlesEmitted && particles.length < maxParticles;
i++
) {
particles.push([x[0], x[1], TWO_PI * Math.random()]);
}
point(x[0], x[1]);
}
stroke(0, 0, 0, 0);
ellipse(mouseX, mouseY, 2 * clickRadius, 2 * clickRadius);
stroke(255);
/*strokeWeight(1);
for(let xs of particles){
point(xs[0],xs[1]);
}*/
sense();
updateParticles();
decay();
}
//Button Functions CLEAR, RANDOM and UPDATE
function clearGrid() {
particles = [];
emitters = [];
}
function randomSpread() {
clearGrid();
for (let i = 0; i < imageWidth * imageHeight * randomDensity; i++) {
particles.push([
Math.random() * imageWidth,
Math.random() * imageHeight,
TWO_PI * Math.random(),
]);
}
}
function updateValues() {
clickRadius = parseInt(radiusPrompt.value());
decayFactor = parseFloat(decayPrompt.value());
sensorAngle = parseFloat((SAprompt.value() / 180) * PI);
rotateAngle = parseFloat((RAprompt.value() / 180) * PI);
stepSize = parseFloat(SSprompt.value());
}
/////////////////////////////////////////////
function writePixel(image, x, y, g) {
let index = (x + y * imageWidth) * 4;
image.pixels[index] = g;
image.pixels[index + 1] = g;
image.pixels[index + 2] = 0;
image.pixels[index + 3] = 255;
}
function modifiedRound(i, axis) {
let x = round(i);
if (x < 0 && axis == "x")
x += imageWidth;
else if (x >= imageWidth && axis == "x")
x %= imageWidth;
else if (x < 0 && axis == "y")
x += imageHeight;
else if (x >= imageHeight && axis == "y")
x %= imageHeight;
return x;
}
function sense() {
for (let i = 0; i < particles.length; i++) {
// setting the sensors
let options = [0, 0, 0];
options[1] = // middle sensor
attracters[modifiedRound(particles[i][0] + sensorOffsetDistance * cos(particles[i][2]), "x")][modifiedRound(particles[i][1] + sensorOffsetDistance * sin(particles[i][2]), "y")];
options[0] = // left sensor
attracters[modifiedRound(particles[i][0] + sensorOffsetDistance * cos(particles[i][2] + sensorAngle), "x")][modifiedRound(particles[i][1] + sensorOffsetDistance * sin(particles[i][2] + sensorAngle), "y")];
options[2] = // right sensor
attracters[modifiedRound(particles[i][0] + sensorOffsetDistance * cos(particles[i][2] - sensorAngle), "x")][modifiedRound(particles[i][1] + sensorOffsetDistance * sin(particles[i][2] - sensorAngle), "y")];
///Programming the sensors conditions
if (options[1] >= options[2] && options[1] >= options[0]) { //if middle sensor is bigger than the other two sensors, no change in direction.
continue;
} else if (options[0] > options[2]) { //if left sensor is bigger than the right sensor, turn left.
particles[i][2] = (particles[i][2] + rotateAngle) % TWO_PI;
} else if (options[0] < options[2]) { //if right sensor is bigger than the left sensor, turn right.
particles[i][2] = (particles[i][2] - rotateAngle) % TWO_PI;
} else { //if left sensor is as big as the right sensor, and bigger than the middle sensor,
let rand = Math.random(); //turn randomly.
if (rand < 0.5) {
particles[i][2] = (particles[i][2] + rotateAngle) % TWO_PI;
} else {
particles[i][2] = (particles[i][2] - rotateAngle) % TWO_PI;
}
}
}
}
function updateParticles() {
for (let i = 0; i < particles.length; i++) {
let x = (particles[i][0] + stepSize * cos(particles[i][2])) % imageWidth;
let y = (particles[i][1] + stepSize * sin(particles[i][2])) % imageHeight;
if (x < 0) {
x += imageWidth;
}
if (y < 0) {
y += imageHeight;
}
particles[i][0] = x;
particles[i][1] = y;
let p1 = modifiedRound(particles[i][0], "x");
let p2 = modifiedRound(particles[i][1], "y");
attracters[p1][p2] += attractionFactor;
}
}
function decay() {
for (let i = 0; i < imageWidth; i++) {
for (let j = 0; j < imageHeight; j++) {
writePixel(imageVisualOutput, i, j, attracters[i][j]);
}
}
imageVisualOutput.filter(BLUR, diffusionSize);
for (let i = 0; i < imageWidth; i++) {
for (let j = 0; j < imageHeight; j++) {
attracters[i][j] = imageVisualOutput.pixels[(i + j * imageWidth) * 4] * decayFactor;
}
}
imageVisualOutput.updatePixels();
}
function canvasClick() {
if (keyIsDown(SHIFT)) { //SHIFT + CLICK to delete slime
const notRemoved = [];
for (let xs of particles) {
if (
Math.sqrt((mouseX - xs[0]) ** 2 + (mouseY - xs[1]) ** 2) > clickRadius
) {
notRemoved.push(xs);
}
}
particles = notRemoved;
} else if (keyIsDown(ALT)) { //ALT + CLICK to add emitters
emitters.push([mouseX, mouseY]);
} else {
for (
let i = 0;
i < particlesPerClick && particles.length < maxParticles;
i++
) {
let dis = clickRadius * Math.random();
let ang = TWO_PI * Math.random();
let x = mouseX + dis * cos(ang);
let y = mouseY + dis * sin(ang);
particles.push([x, y, TWO_PI * Math.random()]);
}
}
}