<aside>
mouseX
and mouseY
constrain mouse input
and ensure my mouse is not in a position that attracts the fish when I am typing the code.Nature of Code
and the ideas inside are very helpful about an effective and efficient random system.Perlin Noise
for the beginning positions of the fish Group.p5. Vector
for better algorithms and use a p5.fillGradient
library.
</aside><aside>
velocity
and acceleration
and separate them into three types: radical(30%)
, moderate(60%)
, and shifting(10%)
. All will change their color and reaction to the mouse.update function
and the regular moveFreely()
function. I am familiar with the basic physical system so it is just fun and reliable.noise()
to create the basic existing spots. Perlin Noise is important here because I want fish to be natural. I first generate points randomly and then times them with a NoiseScale
to adjust the input scale for Perlin noise so I can get a smoother, more gradual transition. I want the fish position to have some pattern but not rigorously so I ask it to generate a more than noiseVal > 0.6
point and it has up to 100 times to show.shine()
so the spot is entertaining to watch. I tried many times to find the right color-changing pattern that matches and looks good.
</aside>class Fish {
constructor(x, y, type, size, baseColor) {
this.pos = createVector(x, y);
this.size = size;
this.baseColor = baseColor;
this.type = type;
if (type == "radical") {
this.vel = createVector(random(-0.2, 0.2), random(-0.2, 0.2));
} else if (type == "moderate") {
this.vel = createVector(random(-1, 1), random(-1, 1));
} else if (type == "shifting") {
this.vel = createVector(random(-2, 2), random(-2, 2));
}
this.acc = createVector(0, 0);
}
applyForce(force) {
this.acc.add(force);
}
update() {
this.vel.add(this.acc);
this.pos.add(this.vel);
if (this.pos.x <= 0 || this.pos.x >= 400) {
if (this.type == "radical") {
this.vel.x *= -0.5;
} else if (this.type == "moderate") {
this.vel.x *= -0.5;
} else if (this.type == "shifting") {
this.vel.x *= -1;
}
this.pos.x = constrain(this.pos.x, 0, 400);
}
if (this.pos.y <= 0 || this.pos.y >= 400) {
if (this.type == "radical") {
this.vel.y *= -0.5;
} else if (this.type == "moderate") {
this.vel.y *= -0.5;
} else if (this.type == "shifting") {
this.vel.y *= -1;
}
this.pos.y = constrain(this.pos.y, 0, 400);
}
this.acc.mult(0);
}
attracted(target, G, low, high) {
let force = p5.Vector.sub(target, this.pos);
let dsquared = force.magSq();
dsquared = constrain(dsquared, low, high);
let strength = G / dsquared;
force.setMag(strength);
this.applyForce(force);
}
shine() {
let stabilityX = constrain(map(abs(this.vel.x), 0, 1, 75, 100), 75, 100);
let stabilityY = constrain(map(abs(this.vel.y), 0, 3, 25, 100), 25, 100);
colorMode(HSL, 360, 100, 100, 1);
let hue = 0;
if (this.type == "radical") {
hue = 30;
} else if (this.type == "moderate") {
hue = 20;
} else if (this.type == "shifting") {
hue = 60;
}
this.baseColor = color(hue, stabilityX, stabilityY);
}
moveFreely() {
let force;
if (this.type == "radical") {
force = p5.Vector.random2D().mult(0.1);
} else if (this.type == "moderate") {
force = p5.Vector.random2D().mult(0.15);
} else if (this.type == "shifting") {
force = p5.Vector.random2D().mult(0.3);
}
this.applyForce(force);
}
show() {
fill(this.baseColor);
noStroke();
ellipse(this.pos.x, this.pos.y, this.size);
}
}
https://editor.p5js.org/sl9964/full/tFLq-EFPT
let noiseScale = 0.02;
let points = [];
let fishGroup = [];
let gradientBG;
let gradientBG1;
let type;
function preload() {
// fish=loadImage('seafish.webp');
}
function setup() {
createCanvas(400, 400);
colorMode(HSL, 360, 100, 100, 1);
createGradient();
generatePoints(50);
for (let p of points) {
let randomList = [0, 1, 1, 1, 2, 2, 2, 2, 2, 2];
let types = random(0, 10) / 1;
if ((randomList[types] = 0)) {
type = "radical";
} else if ((randomList[types] = 1)) {
type = "moderate";
} else {
type = "shifting";
}
fishGroup.push(new Fish(p.x, p.y, type, 10, "white"));
}
}
function draw() {
image(gradientBG, 0, 0);
image(gradientBG1, 0, 0);
createAxis();
sampleColor = "white";
for (let fish of fishGroup) {
if (
0 < mouseX &&
mouseX < width * 0.8 &&
0 < mouseY &&
mouseY < height * 0.8
) {
let mouse = createVector(mouseX, mouseY);
fish.attracted(mouse, 20, 25, 200);
} else {
fish.moveFreely();
}
fish.show();
fish.update();
fish.shine();
}
}
function createGradient() {
gradientBG = createGraphics(400, 400);
let gradient = {
from: [0, 0],
to: [0, 400],
steps: [
color("hsl(0,100%,95%)"),
color("hsl(0,100%,80%)"),
color("hsl(200,100%,50%)"),
],
};
fillGradient("linear", gradient, gradientBG);
gradientBG.noStroke();
gradientBG.rect(0, 0, 400, 400);
gradientBG1 = createGraphics(400, 400);
let gradient2 = {
from: [0, 0],
to: [400, 0],
steps: [
color(360, 0, 100, 0.5),
color(360, 0, 100, 0.25),
color(360, 0, 100, 0),
],
};
fillGradient("linear", gradient2, gradientBG1);
gradientBG1.noStroke();
gradientBG1.rect(0, 0, 400, 400);
}
function createAxis() {
strokeWeight(2);
stroke("white");
line(0, 200, 400, 200);
line(200, 0, 200, 400);
}
function generatePoints(num) {
for (let i = 0; i < num; i++) {
let validPoint = false;
let attempts = 0;
while (!validPoint && attempts < 100) {
let candidate = createVector(random(width), random(height));
let noiseVal = noise(candidate.x * noiseScale, candidate.y * noiseScale);
if (noiseVal > 0.6) {
points.push(candidate);
validPoint = true;
}
attempts++;
}
}
}