# My project library #5! Gallery megathread

**URL:** https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623
**Category:** Gallery
**Created:** [November 19, 2021, 5:03pm UTC](https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623 "2021-11-19T17:03:56Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![CodeMasterX](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/codemasterx/32/2942_2.png) [@CodeMasterX](https://discourse.processing.org/u/CodeMasterX)
#### Post date: [November 19, 2021, 5:03pm UTC](https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623/1 "2021-11-19T17:03:56Z")

</div>

Here are some of my projects! I hope you get some ideas/inspiration from it : P

There are 4 previous megathreads with 60 projects in total!

- [library #1](https://discourse.processing.org/t/my-projects-library-codemasterxs-gallery-megathread/26301)
- [library #2](https://discourse.processing.org/t/my-projects-library-2-codemasterxs-gallery-megathread/27161)
- [library #3](https://discourse.processing.org/t/my-projects-library-3-gallery-megathread/29696)
- [library #4](https://discourse.processing.org/t/my-projects-library-4-gallery-megathread/31103/26)

If you have any questions regarding the projects, use the #[project number] while referencing them!  
Any suggestions/ideas are very welcome! Enjoy!

---

<div class="post-metadata">

### Author: ![CodeMasterX](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/codemasterx/32/2942_2.png) [@CodeMasterX](https://discourse.processing.org/u/CodeMasterX)
#### Post date: [November 19, 2021, 5:09pm UTC](https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623/2 "2021-11-19T17:09:06Z")

</div>

**#62 4 point Bézier curve**

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/1/1768d87b013b6a464a7bddf50a0744e60518456c.png)

> **Code**
>
> ```auto
> p po[] = new p[10]; //4 primary, 3 V% across, 2 V$ across secondary, 1 V% across terciary
> ln l[] = new ln[6];
> float V = 0.5;
> boolean pediting = false;
> 
> void setup() {
> size(600, 600);
> stroke(255);
> float offX = 0, offY = 0;
> po[0] = new p(100 + offX, 100 + offY, true);
> po[1] = new p(500 + offX, 100 + offY, true);
> po[2] = new p(500 + offX, 500 + offY, true);
> po[3] = new p(100 + offX, 500 + offY, true);
> 
> for (int i = 4; i < po.length; i++) po[i] = new p(0, 0, false);
> 
> l[0] = new ln(0, 1);
> l[1] = new ln(1, 2);
> l[2] = new ln(2, 3);
> 
> l[3] = new ln(4, 5);
> l[4] = new ln(5, 6);
> 
> l[5] = new ln(7, 8);
> }
> void draw() {
> background(0); 
> if (pediting == false && mousePressed) V = constrain(map(mouseX, 0, width, 0, 1),0,1);
> 
> po[4].setValue( l[0].pos(V) );
> po[5].setValue( l[1].pos(V) );
> po[6].setValue( l[2].pos(V) );
> 
> po[7].setValue( l[3].pos(V) );
> po[8].setValue( l[4].pos(V) );
> 
> po[9].setValue( l[5].pos(V) );
> noStroke();
> for (int i = 0; i < po.length; i++) {
> po[i].display();
> }
> stroke(255);
> for (int i = 0; i < 6 && 0 != l.length; i++) {
> l[i].drawLN();
> }
> 
> drawCurve(0.001, 0,V);
> }
> 
> void drawCurve(float increment, float start, float stop) {
> noFill();
> stroke(0,0,255);
> beginShape();
> for (float vv = start; vv < stop; vv+=increment) {
> PVector cpoint = getPos(vv);
> vertex(cpoint.x, cpoint.y);
> }
> endShape();
> }
> 
> void keyPressed() {
> if (key == ' ') pediting = !pediting;
> }
> 
> void mouseDragged() {
> if (pediting) {
> int maxRadius = 50;
> int closest = -1;
> float closestDist = maxRadius;
> for (int i = 0; i < po.length; i++) if (po[i].canMove==true) {
> float dst = dist(mouseX, mouseY, po[i].pv.x, po[i].pv.y);
> if (dst < closestDist) {
> closestDist = dst;
> closest = i;
> }
> }
> if(closest>=0) {
> po[closest].pv = new PVector(mouseX,mouseY);
> }
> }
> }
> 
> PVector getPos(float V) {
> po[4].setValue( l[0].pos(V) );
> po[5].setValue( l[1].pos(V) );
> po[6].setValue( l[2].pos(V) );
> 
> po[7].setValue( l[3].pos(V) );
> po[8].setValue( l[4].pos(V) );
> 
> po[9].setValue( l[5].pos(V) );
> 
> return l[5].pos(V);
> }
> 
> class ln {
> int p1, p2;
> ln(int p1, int p2) {
> this.p1 = p1;
> this.p2 = p2;
> }
> void drawLN() {
> stroke(255);
> line(po[p1].pv.x, po[p1].pv.y, po[p2].pv.x, po[p2].pv.y);
> }
> PVector pos (float v) {
> return new PVector( lerp(po[p1].pv.x, po[p2].pv.x, v), lerp(po[p1].pv.y, po[p2].pv.y, v) );
> }
> }
> 
> class p {
> PVector pv;
> boolean canMove = false;
> p(float x, float y, boolean canMove) {
> pv = new PVector(x, y);
> this.canMove = canMove;
> }
> void setValue(PVector pv) {
> this.pv = pv;
> }
> void display() {
> fill( (pediting? #00FF00 : #FFFFFF) );
> circle(pv.x, pv.y, (pediting && canMove? 15 : 5));
> }
> }
> 
> ```

Press space to edit point location. Hold mouse to change the completion of the curve.

---

<div class="post-metadata">

### Author: ![CodeMasterX](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/codemasterx/32/2942_2.png) [@CodeMasterX](https://discourse.processing.org/u/CodeMasterX)
#### Post date: [November 19, 2021, 9:55pm UTC](https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623/3 "2021-11-19T21:55:12Z")

</div>

**#62 B | N - point Bézier**

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/3/3c59a563f95d9343ad2b7513a7fdd7d297d96408.png)

> **Code**
>
> ```auto
> int n = 4; //N-point brazier curve
> int prevN = n;
> p po[] = new p[0];
> l ln[] = new l[0];
> float v = 0.5;
> boolean pediting = false;
> boolean displayLines = true;
> boolean displayDots = true;
> void setup() {
> updateStructure(true);
> size(600, 600);
> }
> void draw() {
> if (mousePressed && pediting == false) v = map(mouseX, 0, width, 0, 1);
> background(0);
> updatePoints(v);
> if(displayDots) for (int i = 0; i < po.length; i++) po[i].display();
> if(displayLines) for (int i = 0; i < ln.length; i++) ln[i].display();
> drawCurve(0.01, 0, 1);
> }
> 
> void keyPressed() {
> if (key == ' ') {
> n++;
> updateStructure(false,new p(mouseX,mouseY,true));
> }
> if (key == 'c') {
> n++;
> updateStructure(true);
> }
> if (key == 'e') {
> pediting = !pediting;
> }
> if(key == 'h') displayLines = !displayLines;
> if(key == 'd') displayDots = !displayDots;
> }
> 
> void mouseDragged() {
> if (pediting) {
> int maxRadius = 50;
> int closest = -1;
> float closestDist = maxRadius;
> for (int i = 0; i < po.length; i++) if (po[i].canMove==true) {
> float dst = dist(mouseX, mouseY, po[i].pv.x, po[i].pv.y);
> if (dst < closestDist) {
> closestDist = dst;
> closest = i;
> }
> }
> if (closest>=0) {
> po[closest].pv = new PVector(mouseX, mouseY);
> }
> }
> }
> 
> void drawCurve(float increment, float start, float stop) {
> noFill();
> stroke(0, 0, 255);
> beginShape();
> for (float vv = start; vv < stop; vv+=increment) {
> PVector cpoint = getPos(vv);
> vertex(cpoint.x, cpoint.y);
> }
> endShape();
> }
> 
> PVector getPos(float V) {
> updatePoints(V);
> return po[n*(n+1)/2-1].pv;
> }
> 
> void updatePoints(float V) {
> for (int i = n; i < po.length; i++) {
> po[i].setValue( ln[i-n].pos(V));
> }
> }
> 
> void updateStructure(boolean createPoints, p... addPoint) {
> if (n < 3) n = 3;
> 
> if (addPoint.length>0) n+=addPoint.length-1;
> 
> p newPO[] = new p[n*(n+1)/2];
> ln = new l[n*(n-1)/2];
> for(int i = 0; i < newPO.length; i++) newPO[i] = new p(150,150,true);
> for (int i = 0; i < prevN && i < po.length; i++) newPO[i] = po[i];
> if (addPoint.length>0) for (int i = 0; i < addPoint.length; i++) newPO[i+prevN] = addPoint[i];
> po = newPO;
> 
> if (createPoints) { //creating PRIMARY POINTS
> float cx = width*0.5, cy = height*0.5, r = 250, a = TWO_PI/n;
> for (int i = 0; i < n; i++) {
> po[i] = new p( cx + cos(a*i)*r, cy + sin(a*i)*r, true);
> }
> }
> for (int i = n; i < po.length; i++) po[i] = new p(-1, -1, false); //creating all NON-PRIMARY points
> 
> int counter = 0, counter2 = 0;
> for (int i = 1; i < n; i++) {
> for (int j = i; j < n; j++) {
> ln[counter2] = new l(counter, counter+1);
> counter++;
> counter2++;
> }
> counter++;
> }
> println(prevN,n,addPoint.length);
> prevN = n;
> }
> 
> class p { //point
> PVector pv;
> boolean canMove;
> p (float x, float y, boolean canMove) {
> pv = new PVector(x, y);
> this.canMove = canMove;
> }
> void setValue(PVector pv) {
> this.pv = pv;
> }
> void display() {
> fill( (pediting? #00FF00 : #FFFFFF) ); 
> noStroke();
> circle(pv.x, pv.y, (pediting && canMove? 15 : 5 ));
> }
> }
> class l { //line
> int p1, p2;
> l (int p1, int p2) {
> this.p1 = p1;
> this.p2 = p2;
> }
> PVector pos(float v) {
> if(p1>=po.length-1 || p2>=po.length-1) println(p1,p2);
> return new PVector( lerp(po[p1].pv.x,po[p2].pv.x, v),lerp(po[p1].pv.y,po[p2].pv.y, v) );
> }
> void display() {
> stroke(255);
> line(po[p1].pv.x, po[p1].pv.y, po[p2].pv.x, po[p2].pv.y);
> }
> }
> 
> ```

Instructions:

- when dots are white, drag the mouse across the screen to change V. Only mouseX is counted.
- press ‘e’ to enter editing mode - all dots will turn green and the closest Large dot (primary), if in range of 50px, will be moved to mouse position.
- press ‘c’ to add 1 point and arrange all points into a circle ish shape.
- press ’ ’ to add a point where the cursor is without affecting the others
- press ‘h’ to toggle lines
- press ‘d’ to toggle dots.

---

<div class="post-metadata">

### Author: ![CodeMasterX](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/codemasterx/32/2942_2.png) [@CodeMasterX](https://discourse.processing.org/u/CodeMasterX)
#### Post date: [December 3, 2021, 10:01pm UTC](https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623/4 "2021-12-03T22:01:09Z")

</div>

**#63 Snowflake generator**  
Creates a random snowflake-ish shape. Press ‘space’ to reset and use mouse to (un)pause. Scroll mouse wheel to adjust complexity (speed of changes). Press ‘c’ to clear stage and reset.

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/5/5b82b86840377de5b54f57a4fc72adc8f06e0155.png)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/7/78d3cdca0dbd61c206665230b9b2b52d39a37b21.png)

How to adjust things inside of the program?

- Change the number of sides? - N, default = 6
- Change step length? - L, default 5
- Circle size? - cd, default L\*0.5;
- Update rate? - frameRate( speed );  
Everything else is explained inside of the code.

Just felt like this was a project worth sharing. It is simple and has a fun result. I first saw the idea about a year ago. I don’t remember the whole story, but I saw [this](https://editor.p5js.org/perisher/sketches/PYRvRUiBS) and it surprised me. I couldn’t understand the code at all. I remember staring at it for minutes, yet coming up with nothing. Anyway, have fun.

> **Code**
>
> ```auto
> int N = 6; //sides
> int a = 0, modulo = 3; //editing has no use, besides changing initial value
> float l = 5, cd = l*0.5; //step length, circle diameter
> final float A = TWO_PI/N; //just felt like using constants
> mover m[] = new mover[2*N];
> boolean active = true; //state? Looping / not looping
> color bgColor = color(0), circleColor = color(255);
> void setup() {
> size(600, 600);
> background(bgColor);
> fill(255); 
> noStroke();
> //frameRate(10); //update rate
> for (int i = 0; i < N; i++) {
> m[2*i] = new mover(300, 300, 1, i); //adding pairs of points. 
> m[2*i+1] = new mover(300, 300, -1, i);
> }
> }
> void draw() {
> if (active) {
> fill(bgColor, 3); //fading speed. Set 2nd value to something between 0 and 255. 0 means no clearing trail, while anything >0 means that it is slowly erased. 255 means instant erasure. 
> rect(0, 0, width, height);
> fill(circleColor);
> if (frameCount%modulo==0) a+=round(random(-1, 1)); //changing direction 
> a%=N; //a is always between 0 and N
> a%=-N;
> 
> for (int i = 0; i < 2*N; i++) m[i].move();
> for (int i = 0; i < 2*N; i++) m[i].display();
> }
> }
> void reset() {
> for (int i = 0; i < N; i++) {
> m[2*i] = new mover(300, 300, 1, i);
> m[2*i+1] = new mover(300, 300, -1, i);
> }
> }
> void keyPressed() {
> if (key == ' ') reset();
> if (key == 'c') {
> background(bgColor);
> reset();
> }
> }
> void mousePressed() {
> active=!active;
> }
> void mouseWheel(MouseEvent e) {
> int n = round(e.getCount());
> modulo-=n;
> if(modulo<1) modulo=1; //if modulo = 0, program crashes
> println(modulo); 
> }
> 
> class mover {
> float x, y;
> int am, ao;
> mover(float x, float y, int am, int ao) {
> this.x=x;
> this.y=y;
> this.am=am;
> this.ao=ao;
> }
> void move() {
> int la = (a+ao)*am;
> x+= cos(la*A)*l;
> y+= sin(la*A)*l;
> }
> void display() {
> circle(x, y, cd);
> }
> }
> 
> ```

---

<div class="post-metadata">

### Author: ![CodeMasterX](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/codemasterx/32/2942_2.png) [@CodeMasterX](https://discourse.processing.org/u/CodeMasterX)
#### Post date: [January 14, 2022, 1:00pm UTC](https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623/5 "2022-01-14T13:00:51Z")

</div>

**#64 Pythagoras tree**

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/9/93920442f61301ea677e02ffe963fc37de44f38c.png)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/a/ae4776c6959b9dc92113c16af815ccfa836759dd.png)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/3/36d8e241a7f980aead2612cdf864ed55c7610c4d.png)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/a/afaa11ab265ba5b319bceaf675afcb701472d0be.png)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/1/1a8e74b3119939f46ff6b95057a0dae86a4cca3f.png)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/b/bf9763fc6acf8b47ce4ecae4d6e2b1c82e819a0b.png)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/c/c6902b83a8e6acbaf5295b991482431dd0e82de0.png)

Controls:

- Press any key to generate the next branch
- Left click to change the triangle shape
- Drag right click - change base square

This is a proof of concept, so it has some issues.

> **Code**
>
> ```auto
> float rot = -QUARTER_PI, move = sqrt(2)/2, baseLen=100;
> squ base = getSqu(new PVector[]{new PVector(300-baseLen*0.5, 500), new PVector(300+baseLen*0.5, 500)});
> ArrayList<squ> ss = new ArrayList<squ>();
> float sx=0,sy=0;
> 
> void setup() {
> size(600, 600);
> stroke(255);
> ss.add(base);
> }
> void draw() {
> background(0);
> //base = getSqu(new PVector[]{new PVector(300,300), new PVector(mouseX,mouseY)});
> for (int i = 0; i < ss.size(); i++) ss.get(i).display();
> ss.get(0).displayPoint();
> }
> void keyPressed() { 
> step();
> }
> void mousePressed() {
> if (mouseButton==LEFT) {
> float dir = atan2(base.points[3].y-mouseY, base.points[3].x-mouseX);
> float dst = -dist (base.points[3].x, base.points[3].y, mouseX, mouseY)/baseLen;
> rot = dir;
> move=dst;
> base.updatePrefs();
> ss.clear();
> ss.add(base);
> } else if(mouseButton==RIGHT) {
> sx=mouseX;
> sy=mouseY;
> }
> }
> void mouseReleased() {
> if(mouseButton==RIGHT) {
> base = getSqu(new PVector[]{new PVector(sx,sy),new PVector(mouseX,mouseY)});
> baseLen=dist(sx,sy,mouseX,mouseY);
> base.updatePrefs();
> ss.clear();
> ss.add(base);
> }
> }
> void step() {
> for (int i = 0, m = ss.size(); i < m; i++) ss.get(i).dupe();
> }
> 
> class squ {
> PVector points[], third;
> boolean canDupe = true;
> squ(PVector[] points) {
> this.points = points;
> third = getThirdPoint(new PVector[]{points[3], points[2]});
> }
> void updatePrefs() {
> canDupe=true;
> third = getThirdPoint(new PVector[]{points[3], points[2]});
> }
> void display() {
> for (int i = 0; i < 4; i++) line(points[i].x, points[i].y, points[(i+1)%4].x, points[(i+1)%4].y);
> }
> void displayPoint() {
> circle(third.x, third.y, 5);
> }
> void dupe() {
> if (canDupe) {
> ss.add(getSqu(new PVector[]{points[3], third}));
> ss.add(getSqu(new PVector[]{third, points[2]}));
> canDupe=false;
> }
> }
> }
> 
> PVector getThirdPoint(PVector[] points) {
> float dir = atan2(points[1].y-points[0].y, points[1].x-points[0].x);
> float len = dist(points[0].x, points[0].y, points[1].x, points[1].y);
> 
> return new PVector(points[0].x+ cos(dir+rot)*len*move, points[0].y + sin(dir+rot)*len*move);
> }
> 
> squ getSqu(PVector[] points) {
> PVector output[] = {points[0], points[1], new PVector(), new PVector()};
> float dir = atan2(points[1].y-points[0].y, points[1].x-points[0].x);
> float dir2 = dir - HALF_PI;
> float len = dist(points[0].x, points[0].y, points[1].x, points[1].y);
> float kx = cos(dir2), ky = sin(dir2);
> 
> output[3] = new PVector(points[0].x+kx*len, points[0].y+ky*len);
> output[2] = new PVector(points[1].x+kx*len, points[1].y+ky*len);
> 
> return new squ(output);
> }
> 
> ```

---

<div class="post-metadata">

### Author: ![CodeMasterX](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/codemasterx/32/2942_2.png) [@CodeMasterX](https://discourse.processing.org/u/CodeMasterX)
#### Post date: [September 3, 2022, 2:41pm UTC](https://discourse.processing.org/t/my-project-library-5-gallery-megathread/33623/6 "2022-09-03T14:41:59Z")

</div>

**#65 Noise flow field**  
It’s been a while… a few months actually…

Images are here: couldn’t upload for some reason

[[Album] Noise flow field ![](https://i.imgur.com/8u7ubpd.jpeg?fb "Noise flow field") ](https://imgur.com/a/bkUWVh2)

A flow field is a field of vectors, which tell the agents operating on it in which direction to move towards. A noise flow field is a flow field that uses Perlin noise to generate the directions and so the values near each other are similar.

Features:

- Basic flow field functionalities
- Screenshot key bind (ENTER)
- Built in presets and examples (of all the images shown above)
- Ability to edit everything about the presets without needing to modify the code (inside the running program)
- Console popups with lots of features
- A lot of customizations
- A lot of color options (and logic settings)
- Comments
- And much much more!

Warning: the code is far more complex that the typical flow field program. It is because I added so many features. Just the basic program wouldn’t even be 30 lines. Not to mention 500…

> **Code**
>
> ```auto
> /*
> Better noise flow field, with more settings!
> */
> import javax.swing.JOptionPane; //library to get the popups working
> 
> String commandTutorialText = //'help' command text
> "Commands:\n"+
> "'clear': clears the window.\n"+
> "'pause': toggle pause\n"+
> "'load': enter the load sequence\n"+
> "'savep': save the current settings/color manager to presets\n"+
> "'print': prints active settings and color-manager, in the form of code\n"+
> "'list': get the number of settings and color-manager presets\n"+
> "'set': enter the sequence to modify current active preset\n"+
> "'rush': iterate N frames, without updating the screen\n"+
> "'teleport': teleport all agents to random locations\n"+
> "\n"+
> "'exit': terminate the program\n"+
> "'back': return to the simulation\n"
> ;
> 
> ColorManager cm; //active color-manager
> ArrayList<ColorManager> colorPresets = new ArrayList<ColorManager>();// color-manager presets
> Settings s; //active settings
> ArrayList<Settings> settingsPresets = new ArrayList<Settings>(); //Settings settingsPresets[];
> Particle p[]; //agents / particles
> Console console; //console
> boolean paused = false;
> 
> void setup() {
> fullScreen();
> initialize(); //create presets & create console
> loadSettingPreset(1);
> loadColorPreset(0);
> //printPresetCode(colorPresets.get(1)); //this will output the code required to implement a preset created within the program
>   
> //if you are annoyed when at having to close the popup window everytime on launch, comment out this line
> console.printMessage("Instructions:\nWhen focused on the window, the following actions can be taken:\nPress space: bring out command console\nPress enter: screenshot the screen (will be saved into the sketch's folder)\nEnjoy!");
>   
> }
> void draw() {
> //if not paused -> do stuff : P
> if(paused == false) doStuff();
> }
> 
> void doStuff() {
> //update color. not needed for some presets, but necessary for others
> cm.updateColor();
> //set stroke to updated color 
> stroke(cm.getColor());
> for(int i = 0; i < s.n; i++) {
> //updating and drawing agents
> p[i].update();
> p[i].display();
> }
> }
> 
> void keyPressed() {
> char k = (""+key).toLowerCase().charAt(0); //turning char into lowercase
> if(k == ' ') {
> console.enterCommandWindow("enter 'help' to see command list");
> return;
> }
> if(k == ENTER) {
> saveFrame("screenshots/screenshot_########.png");
> }
> }
> 
> void reset() {
> noiseSeed(int(random(100000))); //create new noise flow field
> cm.clearBackground();
> loadSettingsPreset(s); //reload active presets
> loadColorPreset(cm);
> }
> class ColorManager {
> boolean use_rgb; //true -> rgb, false -> hsb
> boolean bounce; //true -> if(vx > 255 || vx < 0) cx *= -1; //false -> if(vx > 255) vx = 0. if(vx<0) vx = 255
>     
> //original color -> useful when printing preset code (if i didn't add this, it would only show the final color, not the origin)
> float ogv1, ogv2, ogv3, ogv4;
>     
> //color components. I am not saying red, green, blue, alpha values since it could also be HSBA, so it is easier to say components
> float v1, v2, v3, v4;
> //rate of change of color components
> float c1, c2, c3, c4;
> //background components
> float b1, b2, b3;
>     
> ColorManager(boolean usingRGB, boolean bounceColor, float startColor[], float colorChange[], float background[] ) {
>       
> //i could add rules on what to do if there are not enough elements in the color arrays, but I can't be bothered right now.
> if(startColor.length != 4 || colorChange.length != 4 || background.length != 3) {
> println("\nERROR\nA color preset doesn't have the right amount of color information");
> console.printMessage("\nERROR\nA color preset doesn't have the right amount of color information");
> exit();
> }
>       
> use_rgb = usingRGB;
> setColorMode();
> bounce = bounceColor;
> v1 = startColor[0];
> v2 = startColor[1];
> v3 = startColor[2];
> v4 = startColor[3];
>       
> c1 = colorChange[0];
> c2 = colorChange[1];
> c3 = colorChange[2];
> c4 = colorChange[3];
>       
> b1 = background[0];
> b2 = background[1];
> b3 = background[2];
>       
> ogv1 = v1;
> ogv2 = v2;
> ogv3 = v3;
> ogv4 = v4;
> }
> void setColorMode() {
> if(use_rgb) colorMode(RGB,255,255,255,255); //the 255s decide the range. don't change it
> else colorMode(HSB,255,255,255,255);
> }
>     
>     
> void updateColor() {
> v1 += c1;
> v2 += c2;
> v3 += c3;
> v4 += c4;
> if(bounce) {
> if(v1 > 255 || v1 < 0) {c1 *= -1;}
> if(v2 > 255 || v2 < 0) {c2 *= -1;}
> if(v3 > 255 || v3 < 0) {c3 *= -1;}
> if(v4 > 255 || v4 < 0) {c4 *= -1;}
> return;
> }
> //loop color mode:
> if(v1 > 255) v1 = 0;
> if(v2 > 255) v2 = 0;
> if(v3 > 255) v3 = 0;
> if(v4 > 255) v4 = 0;
>       
> if(v1 < 0) v1 = 255;
> if(v2 < 0) v2 = 255;
> if(v3 < 0) v3 = 255;
> if(v4 < 0) v4 = 255;
> }    
> void clearBackground() {
> background(b1,b2,b3);
> }
> color getColor() {
> return color(v1,v2,v3,v4); 
> }
> }
> 
> class Particle {
> float x, y; //position
> boolean crossedBorder; //if it has crossed the border in the last frame
> boolean exists; //if it exists
> Particle(float x, float y) {
> setPosition(x,y);
> crossedBorder = false;
> exists = true;
> }
> void setPosition(float x, float y) {
> this.x = x;
> this.y = y;
> }
> void update() {
> if(exists == false) return;
> //interpret the field data of 0-1 as rotation
> float a = noise(x*s.noiseM, y*s.noiseM)*TWO_PI;
> //add a random number between 0 and randomDiviation
> a += random(s.randomDiviation);
> //move 1 pixel in the direction the agent is rotated in
> x+= cos(a);
> y+= sin(a);
> //if it is offscreen, trigger the function
> if(x>width||x<0||y>height||y<0) outOfScreen();
> }
> void display() {
> if(crossedBorder == true || exists == false) { crossedBorder = false; return; };
> //display it only if hasn't crossed the border since last update & exists
>     
> point(x,y);
> }
> void outOfScreen() {
> crossedBorder = false;
> switch(s.actionOnLeave) {
> case 0: //stop existing
> exists = false;
> break;
> case 1: //teleport randomly
> setPosition(random(width),random(height));
> break;
> case 2: //come out the other side
> if(x > width) x = 0;
> if(y > height) y = 0;
> if(x < 0) x = width;
> if(y < 0) y = height;          
> break;
> case 3: //mirror location
> if(x > width) {
> x = 0;
> y = height-y;
> }
> if(x < 0) {
> x = width;
> y = height-y;
> }
> //y
> if(y > height) {
> y = 0;
> x = width-x;
> }
> if(y < 0) {
> y = height;
> x = width-x;
> }
> break;
> }
> }
>   
> }
> 
> class Settings {
> int n; //number of agents - higher the number, more lines
> float noiseM; //scale of perlin noise map used to create the flow field
> float randomDiviation; //add a random number when moving. '0' results in sharp lines, '6.28' results in thicker lines.
> int actionOnLeave; //0 -> vanish, 1 -> teleport randomly, 2 -> x > w -> x = 0 ,3 -> x > w -> {x = 0 && y = height-y }
> Settings(int n, float noiseMultiplier, float randomDiviation, int actionOnOffscreen) {
> this.n = n;
> noiseM = noiseMultiplier;
> this.randomDiviation = randomDiviation;    
> actionOnLeave = actionOnOffscreen;
> }
> }
> void initialize() {
> console = new Console(); //create the console
>   
>   
> //addPreset is an overloaded function that can recieve either a Settings obj or ColorManager obj
> addPreset(new Settings(
> 100000, //number of agents
> 0.005, //noise scale multiplier (should be a low number)
> 0, //random diviation (agent angle = noise() + random( randomDiviation). Setting it to TWO_PI*n will create a random flow field
> 2 //action on agent crossing the border
> ));
>   
> addPreset(new ColorManager(
> true, //using RGB (false -> HSB)
> true, //bouncing colors (what to do when clr > 255 || clr < 0)? (true : colr change speed *= -1 | false: loop around)
> new float[]{0,0,0,20}, //starting color
> new float[]{0,0,0,0}, //color change speed
> new float[]{255,255,255} //background color
> ));
>   
> addPreset(new Settings( 100, 0.005, 0, 1 ));
>   
>   
> //bg = background, static = non changing, dynamic = changing
>   
> //white bg with static black lines
> addPreset(new ColorManager( true, true, new float[]{0,0,0,20}, new float[]{0,0,0,0}, new float[]{255,255,255}));
> //black bg with static white lines
> addPreset(new ColorManager( true, true, new float[]{255,255,255,20}, new float[]{0,0,0,0}, new float[]{0,0,0}));
> //black bg with static electic blue lines
> addPreset(new ColorManager( true, true, new float[]{0,200,255,20}, new float[]{0,0,0,0}, new float[]{0,0,0}));
> //black bg with static red lines
> addPreset(new ColorManager( true, true, new float[]{255,0,0,20}, new float[]{0,0,0,0}, new float[]{0,0,0}));
> //black bg with static magenta lines
> addPreset(new ColorManager( true, true, new float[]{255,0,255,20}, new float[]{0,0,0,0}, new float[]{0,0,0}));
> //black bg with static yellow lines
> addPreset(new ColorManager( true, true, new float[]{255,255,0,20}, new float[]{0,0,0,0}, new float[]{0,0,0}));
> //white bg with dynamic gray lines
> addPreset(new ColorManager( true, true, new float[]{0,0,0,0}, new float[]{1,1,1,1}, new float[]{255,255,255}));
>   
>   
> addPreset(new ColorManager( false, true, new float[]{42,255,255,20}, new float[]{1,0,0,0}, new float[]{0,0,0}));
>   
> //black bg with bright dynamic lines, that start with red -> orange -> yellow -> green ->blue -> violet -> red
> //change the '0.1' to something larger if you want a faster change
> addPreset(new ColorManager( false, false, new float[]{0,255,255,20}, new float[]{0.1,0,0,0}, new float[]{0,0,0}));
>   
> }
> void addPreset(ColorManager cl) { //overloaded function that can accept ColorManager & Settings class
> colorPresets.add(cl);  
> }
> void addPreset(Settings st) {
> settingsPresets.add(st);  
> }
> void loadColorPreset(ColorManager cl) {
> cm = cl;
> cm.setColorMode();
> cm.clearBackground();
> }
> boolean loadColorPreset(int id) {
> if(id<0 || colorPresets.size()<= id) return false;
> loadColorPreset(colorPresets.get(id));
> return true;
> }
> void loadSettingsPreset(Settings st) {
> s = st;
> if(cm != null) cm.clearBackground();
> p = new Particle[s.n];
> for(int i = 0; i < s.n; i++) {
> p[i] = new Particle(random(width),random(height));  
> }
> }
> boolean loadSettingPreset(int id) {
> if(id<0 || settingsPresets.size()<= id) return false;
> loadSettingsPreset(settingsPresets.get(id));  
> return true;
> }
> void printPresetCode(Settings st) {
> println("addPreset(new Settings("+st.n,",",st.noiseM,",",st.randomDiviation,",",st.actionOnLeave+"));");
> }
> void printPresetCode(ColorManager cl) {
> println("addPreset(new ColorManager(",
> cl.use_rgb,",",
> cl.bounce,",",
> "new float[]{", cl.ogv1,",", cl.ogv2,",", cl.ogv3,",", cl.ogv4, "},",
> "new float[]{", cl.c1,",", cl.c2,",", cl.c3,",", cl.c4, "},",
> "new float[]{", cl.b1,",", cl.b2,",", cl.b3,"}",
> "));");
> }
> 
> class Console {
> void printMessage(String message) { //create a popup with the message
> JOptionPane.showMessageDialog(null, message);  
> }
> void enterCommandWindow(String text) { //run the command the user said
> String userInput = JOptionPane.showInputDialog(null,text,"Enter a command");
> if(userInput == null) userInput = "";
> handleUserInput(userInput.toLowerCase());
> }
> String getUserInput(String text) { //get user input (from popup window)
> String userInput = JOptionPane.showInputDialog(null,text);
> if(userInput == null) userInput = "";
> return userInput.toLowerCase();
>     
> }
> void handleUserInput(String userInput) { //decide what to do with the user's input
>   
> /*
> From here on out, the code becomes pretty weird.
> If you don't understand it, just know that
> it is just a bunch of if-else statements that determine what to do
>     
> Nothing amazing. And if it is hard to read... imagine what i had to go through to write it : P
> it took 2x as long to make it than it took to make everything else
> */
>   
> userInput = userInput.toLowerCase();
> if(userInput.equals("help")) {
> enterCommandWindow(commandTutorialText);
> return;
> }
> if(userInput.equals("clear")) reset();
> if(userInput.equals("back")) return;
> if(userInput.equals("exit")) exit();
>     
> if(userInput.equals("pause")) paused = !paused;
> if(userInput.equals("teleport")) {
> for(int i = 0; i < s.n; i++) p[i].setPosition(random(width),random(height));
> }
>     
> if(userInput.equals("rush")) {
> int N = int(getUserInput("Enter the number of frames to be iterated"));
> for(int i = 0; i < N; i++) doStuff();
> }
>     
> if(userInput.equals("list")) printMessage("There are currently " + colorPresets.size() + " color presets and " + settingsPresets.size() + " settings presets");
> if(userInput.equals("print")) {
> print("Settings & Color presets: \n"); 
> printPresetCode(s);
> println();
> printPresetCode(cm);
> }
> if(userInput.equals("load")) {
> String ui = getUserInput("Choose preset type: \n'color': load color preset\n'settings': load settings preset");
> if(ui.equals("color")) {
> int id = int(getUserInput("Which preset to load?\nAvailable: 0-"+(colorPresets.size()-1) ));
> loadColorPreset(id);
> }
> if(ui.equals("settings")) {
> int id = int(getUserInput("Which preset to load?\nAvailable: 0-"+(settingsPresets.size()-1) ));
> loadSettingPreset(id);
> }
> return;
> }
> if(userInput.equals("savep")) {
> String ui = getUserInput("Which active preset to save?\n'color'\n'settings'\n'both'");
> boolean saveSettings = ui.equals("settings");
> boolean saveColor = ui.equals("color");
> if(ui.equals("both")) {
> saveSettings = true;
> saveColor = true;
> }
> if(saveSettings) addPreset(s);
> if(saveColor) addPreset(cm);
> }
> if(userInput.equals("set")) {
> String ui = getUserInput("Properties of what to modify?\n'color'\n'settings'");
> boolean modifySettings = ui.equals("settings");
> boolean modifyColor = ui.equals("color");
> if(modifySettings) {
> ui = getUserInput("Which value to modify?\nAvaiable:\n'n': number of agents\n'm': noise scale\n'r': randomness\n'a': what action to do when agent leaves the screen");
> if(ui.equals("n")) {
> int newN = int(getUserInput("Enter the new number of agents"));           
> s.n = newN;
> }
> if(ui.equals("m")) {
> int newM = int(getUserInput("Enter the new noise scale (0.005 by default)"));           
> s.noiseM = newM;
> }
> if(ui.equals("r")) {
> int newR = int(getUserInput("Enter the new random diviation (0 by default)"));           
> s.randomDiviation = newR;
> }
> if(ui.equals("a")) {
> int newA = int(getUserInput("Enter the new action when an agent leaves the screen\nAvailable:\n"+
> "'0': they disappear\n'1': they are teleported randomly on the screen\n'2': they loop around\n'3': thier location is mirrorer"));           
> if(newA>=0 && newA<=3) s.actionOnLeave = newA;
> else printMessage("Invalid action type");
> }
> loadSettingsPreset(s);
> return;
> }
> //modify color
> if(modifyColor) {
> ui = getUserInput("Which color-manager property to change?"+
> "\n'mode':Color mode"+"\n'background': Background color\n'color': starting color\n'change': speed of change for color properties\n'bounce': what to do when reaching values 0/255");
> if(ui.equals("mode")) {
> ui = getUserInput("Enter new mode.\nAvailable:'rgb': rgba mode\n'hsb': hsba/hsva mode");
> if(ui.equals("rgb")) cm.use_rgb = true;
> else if(ui.equals("hsb")) cm.use_rgb = false;
> else printMessage("Invalid mode type");
> }         
>          
> if(ui.equals("background")) {
> ui = getUserInput("Enter the "+((cm.use_rgb)? "RGB" : "HSB")+" values, separated by space.\nExample: 1.0 2.0 3.0");
> String splitUI[] = split(ui," ");
> if(splitUI.length != 3) {
> printMessage("incorrect amount of data");
> return;
> }
> cm.b1 = float(splitUI[0]);
> cm.b2 = float(splitUI[1]);
> cm.b3 = float(splitUI[2]);
> }
> if(ui.equals("color")) {
> ui = getUserInput("Enter the "+((cm.use_rgb)? "RGBA" : "HSBA")+" values, separated by space.\nExample: 1.0 2.0 3.0 4.0");
> String splitUI[] = split(ui," ");
> if(splitUI.length != 4) {
> printMessage("incorrect amount of data");
> return;
> }
> cm.v1 = float(splitUI[0]);
> cm.v2 = float(splitUI[1]);
> cm.v3 = float(splitUI[2]);
> cm.v4 = float(splitUI[3]);
>            
> cm.ogv1 = cm.v1;
> cm.ogv2 = cm.v2;
> cm.ogv3 = cm.v3;
> cm.ogv4 = cm.v4;
> }
> if(ui.equals("change")) {
> ui = getUserInput("Enter the "+((cm.use_rgb)? "RGBA" : "HSBA")+" change values, separated by space.\nThis tells how much the colors will change each frame\nExample: 1.0 2.0 3.0 4.0");
> String splitUI[] = split(ui," ");
> if(splitUI.length != 4) {
> printMessage("incorrect amount of data");
> return;
> }
> cm.c1 = float(splitUI[0]);
> cm.c2 = float(splitUI[1]);
> cm.c3 = float(splitUI[2]);
> cm.c4 = float(splitUI[3]);
> }
> if(ui.equals("bounce")) {
> ui = getUserInput("What to do when color values hit 0 or 255?\nAvailable:\n'bounce': start going in the other direction\n'loop': go to the other extreme (0->255, 255->0)");
> if(ui.equals("loop")) cm.bounce = false;
> if(ui.equals("bounce")) cm.bounce = true;
> }
> loadColorPreset(cm);
> return;
> }      
>        
> }    
> }
> }
> 
> ```
