Skip to content

Instantly share code, notes, and snippets.

@comficker
Created March 27, 2024 16:17
Show Gist options
  • Save comficker/9df4b1aba322bdf833fe617e0692b4d3 to your computer and use it in GitHub Desktop.
Save comficker/9df4b1aba322bdf833fe617e0692b4d3 to your computer and use it in GitHub Desktop.
export class Fish {
constructor() {
this.pos = createVector(R.random_num(0, W), R.random_num(0, H)); // Position vector of fish
this.poi = createVector(0, 0); // Position vector of interest
this.vel = createVector(R.random_dec() * M, R.random_dec() * M); // initialize velocity in unit vector
this.facing = this.vel.heading() // facing of fish
this.speed = R.random_num(0.3, 1) * M; // magnitude to scale vel
this.maxTurn = PI / R.random_int(512, 1024);
this.fl = round(R.random_int(175, 225) * M);
this.laziness = R.random_int(1, 1000);
this.jitter = round(R.random_num(0, 0.7), 2);
this.mirror = (R.random_dec() < 0.5) ? true : false;
this.turning = 0;
this.n_blobs = R.random_int(5, 15);
this.n_blobs2 = R.random_int(0, 5);
this.blobs = [];
this.blobs2 = [];
this.ftr = R.random_dec();
if (this.ftr < 0.2) {
this.fin_type = "ephemeral";
} else {
this.fin_type = "classic";
}
this.pattern = "rorschach";
this.pr = R.random_dec();
if (this.pr < 0.1) {
this.palette = "white";
} else if (this.pr < 0.25) {
this.palette = "black";
} else if (this.pr < 0.5) {
this.palette = "gold";
} else {
this.palette = "classic";
}
this.col1 = this.palette_picker(this.palette)[0];
this.col2 = this.palette_picker(this.palette)[1];
this.col3 = this.palette_picker(this.palette)[2];
this.fincol = color(strokeCol, 50);
// Patterns
// Initialize blobs
for (let i = 0; i < this.n_blobs; i++) { // define an x position and a radius for each blob
let cx = R.random_int(0, this.fl * 0.9);
let cy = R.random_num(-1, 1);
let r = R.random_num(0.1, 0.5);
this.blobs[i] = new p5.Vector(cx, cy, r);
}
for (let j = 0; j < this.n_blobs2; j++) {
let cx = R.random_int(0, this.fl * 0.9);
let cy = R.random_num(-1, 1);
let r = R.random_num(0, 0.3);
this.blobs2[j] = new p5.Vector(cx, cy, r);
}
}
palette_picker(p) {
let pal;
let c1;
let c2;
let c3;
if (p == "classic") {
c1 = color(255, 82, 45, 10);
c2 = color(10, 230);
c3 = color(250, 200);
} else if (p == "black") {
c1 = color(10, 10);
c2 = color(70, 250);
c3 = color(255, 82, 45, 230);
} else if (p == "white") {
c1 = color(250, 10);
c2 = color(255, 82, 45, 230);
c3 = color(10, 250);
} else if (p == "gold") {
c1 = color(243, 175, 10, 10);
c2 = color(20, 230);
c3 = color(250, 200);
}
pal = [c1, c2, c3];
return (pal);
}
display_fin(fin_type) {
push();
if (fin_type == "classic") { // use classic fin shape
push();
rotate(sin(noise((time / 2 + this.speed))));
fill(250, 50);
this.fincol.setAlpha(100);
stroke(this.fincol);
beginShape();
curveVertex(0, 0);
curveVertex(0, 0);
curveVertex(this.fl * 0.25, 0);
curveVertex(this.fl * 0.2, this.fl * 0.15);
curveVertex(0, this.fl * 0.05);
curveVertex(0, this.fl * 0.05);
endShape();
for (let i = 0; i < 5; i++) {
let x2 = lerp(this.fl * 0.25, this.fl * 0.2, i / 5);
let y2 = lerp(0, this.fl * 0.15, i / 5);
let y1 = lerp(0, this.fl * 0.05, i / 5);
line(0, y1, x2, y2);
}
pop();
} else if (fin_type == "ephemeral") { // waterfall fin type
noFill();
this.fincol.setAlpha(50);
stroke(this.fincol);
strokeWeight(3);
rotate(sin(noise(time / 2 + this.speed))) / 2;
//let r = time / 2 * this.speed;
for (let i = 0; i < 50; i += 5) {
let x1 = -0.025 * this.fl + i / 3 * M;
let x2 = 0.025 * this.fl + i * M;
let x3 = 0.2 * this.fl + i * M;
let x4 = this.speed / 4 * this.fl + 1 / (i + 1) * M;
let y1 = 0 + i * M;
let y2 = -0.05 * this.fl + i * M;
let y3 = 0.05 * this.fl + i * M;
let y4 = 0.05 * this.fl + 1 / (i + 1) * M;
bezier(x1, y1, x2, y2, x3, y3, x4, y4);
}
}
pop();
}
display_top_fin(fin_type) {
push();
if (fin_type == "classic") {
fill(this.fincol);
beginShape();
let a = createVector(0, 0);
let b = createVector(0, 0);
let ang = 0;
for (let i = 0; i < this.fl * 0.75; i++) {
let diff = createVector(cos(ang), sin(ang));
a.add(diff);
if (i < this.fl * 0.3) {
b.add(diff.add(0, -0.3)); // b is used for the tip of the fin
}
if (i > this.fl * 0.2) {
curveVertex(a.x, a.y);
}
ang += this.turning / this.fl;
}
vertex(b.x / 2 + b.x / 2 * noise(time / 2 + this.speed), b.y / 2 + b.y / 2 * noise(time / 2 + this.speed));
endShape(CLOSE);
} else if (fin_type == "ephemeral") {
noFill();
stroke(this.fincol);
strokeWeight(2);
let startn = this.fl * 0.2;
let endn = this.fl * 0.75;
let ang = this.turning / this.fl;
let startx = 0.5 + sin((2 * startn + 1) / 2 * ang) / (2 * sin(ang / 2));
let starty = sin(startn * ang / 2) * sin((startn + 1) * ang / 2) / (sin(ang / 2));
let endx = 0.5 + sin((2 * endn + 1) / 2 * ang) / (2 * sin(ang / 2));
let endy = sin(endn * ang / 2) * sin((endn + 1) * ang / 2) / (sin(ang / 2));
//let r = time / 2 * this.speed;
for (let i = 0; i < 50; i += 5) {
let x1 = startx;
let x2 = startx + 10 * cos(this.turning) * M - i * M;
let x3 = endx - cos(this.turning) * 4 * i * M;
let x4 = endx;
let y1 = starty;
let y2 = starty - i * M;
let y3 = endy - sin(this.turning) * 2 * i * M;
let y4 = endy;
bezier(x1, y1, x2, y2, x3, y3, x4, y4);
}
}
pop();
}
display_pattern(p, blobs, col) {
push();
fill(col);
if (p == "rorschach") {
for (let i = 0; i < this.fl; i++) {
translate(1, 0);
rotate(this.turning / this.fl);
let fw;
if (i < this.fl * 0.6) {
fw = 0.05 * this.fl + pow(this.fl * i, 0.4);
} else if (i < this.fl * 0.75) {
fw = 0.05 * this.fl + pow(this.fl * this.fl * 0.6, 0.4);
} else {
fw = 0.05 * this.fl + pow(this.fl * this.fl * 0.75 - this.fl * 5 * (i - this.fl * 0.75), 0.4);
}
for (let j = 0; j < blobs.length; j++) {
if (i == blobs[j].x) {
blob(0, fw / 2 * blobs[j].y - blobs[j].z * fw / 2, blobs[j].z * fw * 1.5, fw / 2, time + j, ns, false);
if (this.mirror) {
blob(0, -fw / 2 * blobs[j].y + blobs[j].z * fw / 2, blobs[j].z * fw * 1.5, fw / 2, time + j, ns, true);
}
}
}
}
}
pop();
}
display_eyes() {
push();
for (let i = 0; i < 0.75 * this.fl; i++) {
translate(1, 0);
rotate(this.turning / this.fl);
}
fill(255, 230);
//stroke(this.col1);
//strokeWeight(2);
ellipse(0, (0.05 * this.fl + pow(this.fl * this.fl * 0.6, 0.4)) / 2 - 0.025 * this.fl, this.fl * 0.1, this.fl * 0.035);
ellipse(0, -(0.05 * this.fl + pow(this.fl * this.fl * 0.6, 0.4)) / 2 + 0.025 * this.fl, this.fl * 0.1, this.fl * 0.035);
fill(0, 200);
//noStroke();
ellipse(this.fl * 0.015, (0.05 * this.fl + pow(this.fl * this.fl * 0.6, 0.4)) / 2 - 0.025 * this.fl, this.fl * 0.05, this.fl * 0.03);
ellipse(this.fl * 0.015, -(0.05 * this.fl + pow(this.fl * this.fl * 0.6, 0.4)) / 2 + 0.025 * this.fl, this.fl * 0.05, this.fl * 0.03);
pop();
}
display_shadow() {
push();
translate(this.pos.x, this.pos.y);
translate(20 * M, 20 * M);
rotate(this.facing);
fill(20, 3);
for (let i = 0; i < this.fl; i++) {
translate(1, 0);
rotate(this.turning / this.fl);
if (i < this.fl * 0.6) {
ellipse(0, 0, this.fl * 0.05 + pow(this.fl * i, 0.4));
} else if (i < this.fl * 0.75) {
ellipse(0, 0, this.fl * 0.05 + pow(this.fl * this.fl * 0.6, 0.4));
} else {
ellipse(0, 0, this.fl * 0.05 + pow(this.fl * this.fl * 0.75 - this.fl * 5 * (i - this.fl * 0.75), 0.4));
}
}
pop();
}
display_body() { // Function to display the body of the fish + eyes
noStroke();
smooth();
push();
translate(this.pos.x, this.pos.y);
rotate(this.facing);
// Draw rear fins
push();
for (let i = 0; i < this.fl; i++) {
translate(1, 0);
rotate(this.turning / this.fl);
if (i == round(this.fl * 0.2)) {
push();
translate(0, (this.fl * 0.05 + pow(this.fl * this.fl * 0.2, 0.4)) / 2);
rotate(0.65 * PI);
scale(0.5);
this.display_fin(this.fin_type);
pop();
push();
translate(0, -(this.fl * 0.05 + pow(this.fl * this.fl * 0.2, 0.4)) / 2);
rotate(-0.65 * PI);
scale(0.5, -0.5);
this.display_fin(this.fin_type);
pop();
} else if (i == round(this.fl * 0.6)) {
push();
translate(0, (this.fl * 0.05 + pow(this.fl * this.fl * 0.6, 0.4)) / 2);
rotate(0.65 * PI);
this.display_fin(this.fin_type);
pop();
push();
translate(0, -(this.fl * 0.05 + pow(this.fl * this.fl * 0.6, 0.4)) / 2);
rotate(-0.65 * PI);
scale(1, -1)
this.display_fin(this.fin_type);
pop();
}
}
pop();
// Draw the body
push();
for (let i = 0; i < this.fl; i++) {
translate(1, 0);
rotate(this.turning / this.fl);
fill(this.col1);
if (i < this.fl * 0.6) {
ellipse(0, 0, this.fl * 0.05 + pow(this.fl * i, 0.4));
} else if (i < this.fl * 0.75) {
ellipse(0, 0, this.fl * 0.05 + pow(this.fl * this.fl * 0.6, 0.4));
} else {
ellipse(0, 0, this.fl * 0.05 + pow(this.fl * this.fl * 0.75 - this.fl * 5 * (i - this.fl * 0.75), 0.4));
}
}
pop();
// Draw the pattern
push();
this.display_pattern(this.pattern, this.blobs, this.col2);
this.display_pattern(this.pattern, this.blobs2, this.col3);
pop();
// Draw the eyes
this.display_eyes();
// Draw the top fin
this.display_top_fin(this.fin_type);
pop();
}
display_tail() {
push();
translate(this.pos.x, this.pos.y);
rotate(this.facing);
rotate(-this.turning / 6);
scale(-1, 1);
if (this.fin_type == "classic") {
push();
fill(250, 50);
this.fincol.setAlpha(100);
stroke(this.fincol);
rotate(sin(time * PI / 2 * this.speed) / 5);
smooth();
beginShape();
curveVertex(0, 0);
curveVertex(0, 0);
curveVertex(0, this.fl * 0.05);
curveVertex(this.fl * 0.35 * this.speed, this.fl * 0.15)
curveVertex(this.fl * 0.15, 0);
curveVertex(this.fl * 0.35 * this.speed, -this.fl * 0.15)
curveVertex(0, -this.fl * 0.05);
curveVertex(0, 0);
curveVertex(0, 0);
endShape(CLOSE);
pop();
} else if (this.fin_type == "ephemeral") {
noFill();
stroke(this.fincol);
strokeWeight(2);
rotate(sin(time * PI / 2 * this.speed) / 5);
let r = time / 2 * this.speed;
for (let i = -50; i < 50; i += 4) {
let x1 = 0;
let x2 = this.fl * 0.05 + this.fl * 0.25 * noise(r + 0);
let x3 = this.fl * 0.2 + i * noise(r + 15) * M;
let x4 = this.fl * 0.15 + 2 * abs(i) * noise(r + 30) * M;
let y1 = 0;
let y2 = 0 + i * noise(r + 40) * M;
let y3 = this.fl * 0.1 + 2 * i * noise(r + 50) * M;
let y4 = 0 + i * noise(r + 70) * M;
bezier(x1, y1, x2, y2, x3, y3, x4, y4);
}
}
pop();
}
move() {
// Update x and y coords
this.pos.x += this.speed * cos(this.facing);
this.pos.y += this.speed * sin(this.facing);
// Periodic boundary conditions
if (this.pos.x >= W) {
this.pos.x -= W;
}
if (this.pos.y >= H) {
this.pos.y -= H;
}
if (this.pos.x < 0) {
this.pos.x += W;
}
if (this.pos.y < 0) {
this.pos.y += H;
}
}
turn() {
let v1 = p5.Vector.sub(this.poi, this.pos);
let ang = this.vel.angleBetween(v1);
if (ang < -0.01) {
this.turning -= this.maxTurn;
this.facing -= this.maxTurn / 2;
this.vel.rotate(-this.maxTurn / 2);
//this.facing += this.turning / 100;
} else if (ang > 0.01) {
this.turning += this.maxTurn;
this.facing += this.maxTurn / 2;
this.vel.rotate(this.maxTurn / 2);
//this.facing += this.turning / 100;
}
this.turning *= 0.99; // Decay turning
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment