xxxxxxxxxx
429
let dots;
const pars = ["attraction", "repulsion", "alignment"];
let sliders;
let colors;
let dogeImg;
function setup() {
createCanvas(100, 100);
windowResized();
colorMode(RGB, 1); // normalize colour values between 0 - 1
noStroke();
setupDots();
setupSliders();
colors = ["#FFA41C", "#FF4F84", "#7058FF", "#2C81ED", "#B0EB33"]
.map(s => color(s));
dogeImg = loadImage("doge.png");
}
let sounds;
function preload() {
soundFormats('mp3');
sounds = [
// loadSound("sfx/whilhelm-scream.mp3"),
// loadSound("sfx/damn-son.mp3"),
loadSound("sfx/illuminati.mp3"),
loadSound("sfx/shooting-stars.mp3"),
loadSound("sfx/wow.mp3"),
]
.map(s => ({s,i: random()}))
.sort((s1, s2) => s2.i - s1.i)
.map(({s}) => s);
}
let hoverControls = false;
let pull = false;
let controlsEl;
function setupSliders() {
controlsEl = document.getElementById("controls");
controlsEl.addEventListener("mouseover", () => hoverControls = true);
controlsEl.addEventListener("mouseout", () => hoverControls = false);
controlsEl.addEventListener("touchstart", () => hoverControls = true);
controlsEl.addEventListener("touchend", () => hoverControls = false);
pars.forEach(par => {
const slider = document.createElement('input');
slider.id = par;
slider.type = 'range';
slider.min = 0;
slider.max = 1;
slider.step = 0.01;
slider.value = 0.5;
const label = document.createElement('label');
label.innerHTML = par;
label.appendChild(slider);
controls.appendChild(label);
});
}
function mousePressed() {
if (!hoverControls)
pull = true;
}
function mouseReleased() {
pull = false;
}
function keyPressed() {
if (key === " ") {
startMeme();
}
if (key == "F10")
fullscreen(!fullscreen());
if (key == "`") {
SQUARE_VIEW = !SQUARE_VIEW;
windowResized();
}
}
function doubleClicked() {
startMeme();
}
let SIZE;
let DIMS;
let SQUARE_VIEW=false;
function windowResized() {
SIZE = min(windowWidth, windowHeight);
if (SQUARE_VIEW)
resizeCanvas(SIZE, SIZE);
else
resizeCanvas(windowWidth, windowHeight);
DIMS = createVector(width / SIZE, height / SIZE);
}
function getPar(name) {
const el = document.getElementById(name);
if (!el) return 0;
return el.type === "checkbox" ? el.checked : el.value;
}
const count = 500;
function setupDots() {
dots = [];
for (let i = 0; i < count; i++) {
const x = random() * DIMS.x;
const y = random() * DIMS.y;
const v = dirFromCenter(x, y);
dots.push({
v,
pos: createVector(x, y),
vel: createVector(0, 0),
});
}
}
function dirFromCenter(x, y) {
const vec = createVector(x - 0.5, y - 0.5);
return vec.heading() / TWO_PI; // divide by 2 * PI to normalize angle beteen 0 - 1
}
let avgPos;
function getDistVec(p1, p2) {
return p5.Vector.sub(p1, p2);
const minDistX = [
p1.x - p2.x,
p1.x - p2.x - 1,
p1.x - p2.x + 1
].sort((v1, v2) => abs(v1) - abs(v2))[0];
const minDistY = [
p1.y - p2.y,
p1.y - p2.y - 1,
p1.y - p2.y + 1
].sort((v1, v2) => abs(v1) - abs(v2))[0];
return createVector(minDistX, minDistY);
}
let mousePull = 0;
// flocking behaviour, but instead of checking each dot for each dot you pick 1 target dot at random
function updateDots() {
const friction = 0.95;
const minVel = 0.001;
const maxVel = 0.05;
const maxAlignVel = 0.002;
const checks = 5;
const attractionRadius = getPar("attraction") * 1;
const repulsionRadius = getPar("repulsion") * 1;
const alignmentRadius = getPar("alignment") * 0.25;
const inertia = 0.1;
const attractionFactor = getPar("attraction") * 0.1;
const repulsionFactor = getPar("repulsion") * 0.1;
const alignmentFactor = getPar("alignment") * 5;
const mVec = createVector(mouseX / SIZE, mouseY / SIZE);
if (pull)
mousePull += 0.0001;
else if (mousePull > 0) {
dots.forEach(d => {
const dmVec = p5.Vector.sub(d.pos, mVec);
d.vel.add(p5.Vector.mult(
dmVec.normalize(),
pow(30 * mousePull, 2) * random()
));
});
mousePull = 0;
}
avgPos = createVector();
dots.filter(d => !d.hidden).forEach(d => {
for (let i=0; i<checks; i++) {
const d2 = dots[floor(random() * dots.length)];
// const vec = p5.Vector.sub(d2.pos, d.pos);
const vec = getDistVec(d2.pos, d.pos);
const dist = vec.mag();
const revDist = 1 - dist;
// const distPow = pow(dist, 2);
// const revDistPow = pow(revDist, 2)
const attraction = dist < attractionRadius
? dist * attractionFactor
: 0;
const repulsion = dist < repulsionRadius
? revDist * repulsionFactor
: 0;
const move = (attraction - repulsion) / checks;
const align = dist < alignmentRadius
? revDist * alignmentFactor / checks
: 0;
d.vel.add(
p5.Vector.mult(
p5.Vector.add(
p5.Vector.mult(vec, move),
p5.Vector.mult(
d2.vel.copy().normalize(),
min(maxAlignVel, d2.vel.mag() * align)
)
),
inertia
)
);
}
if (pull) {
const force = p5.Vector.sub(
mVec,
d.pos
);
d.vel.add(force.mult(mousePull));
}
d.vel.mult(pow(friction, 1 / (1 + d.vel.mag() * 10)));
d.vel.setMag(constrain(d.vel.mag(), minVel, maxVel));
d.pos.add(d.vel);
d.pos.set(
mod(d.pos.x, DIMS.x),
mod(d.pos.y, DIMS.y)
)
avgPos.add(d.pos);
});
avgPos.div(count);
}
// irrational number (also golden ratio)
const PHI = (1 + Math.sqrt(5)) / 2
let t;
const frames = 300;
function draw() {
fill("white");
textAlign(CENTER, CENTER);
const upscale = 1.05;
scale(upscale, upscale);
translate(
width * (1 - upscale) * 0.5,
height * (1 - upscale) * 0.5
);
t = (frameCount / frames);
background(0,0,0,0.1);
if (memeStart != null)
drawMeme();
updateDots();
drawDots();
}
function drawDots() {
dots.filter(d => !d.hidden).forEach((d, i) => {
const targetV = d.vel.heading() / TAU;
// const targetV = dirFromCenter(d.pos.x, d.pos.y);
d.v += (targetV - d.v) * 1;
const morph = 0;
fill(
cosn(d.v + t * morph),
cosn(d.v + 0.33 + t * (PI - 3) * morph),
cosn(d.v + 0.66 + t * (PHI - 1) * morph)
);
// fill(gradient(d.v));
const dist = p5.Vector.sub(avgPos, d.pos).mag();
circle(d.pos.x * SIZE, d.pos.y * SIZE, 0.05 * dist * SIZE);
});
}
let memeStart;
let soundIndex = 0;
function startMeme() {
if (memeStart == null) {
memeStart = t;
controlsEl.style.visibility = "hidden";
dots.forEach(d => d.hidden = true);
spawnParticles(0.1, 0.05, 0.4, getEmoji2(0));
dots.forEach(d => d.pos.set(random() * DIMS.x, random() * DIMS.y));
sounds[soundIndex % sounds.length].play();
soundIndex++;
}
}
function endMeme() {
controlsEl.style.visibility = "visible";
memeStart = null;
}
let particles = [];
function spawnParticles(rand=0.001, speedMult=0.015, lifeMult=0.3, text) {
dots.filter(d => d.hidden).forEach(d => {
if (random() < rand) {
text = text ?? getEmoji(random());
const count = 3;
const rot = random();
const speed = random() * speedMult;
for (let i =0; i<count; i++) {
const f = i / count;
const vel = createVector(1,1).setMag(speed).setHeading((rot + f) * TAU);
particles.push({
text,
pos: d.pos.copy(),
vel,
size: random() * 0.1,
born: t,
life: lifeMult * random(),
});
}
}
});
}
function drawMeme() {
const lt = t - memeStart;
spawnParticles(0.0025 * pow(lt, 1.15));
drawParticles();
drawDoge(pow(lt, 2));
if (lt >= 1)
endMeme();
}
function drawParticles() {
const friction = 0.9;
particles.forEach(p => {
p.vel.mult(friction);
p.pos.add(p.vel);
textSize(p.size * SIZE);
text(p.text, p.pos.x * SIZE, p.pos.y * SIZE);
});
particles = particles.filter(p => particleLife(p) < 1);
}
function drawDoge(lt) {
const x = (cosn(lt * 2) - 0.5) * 0.5 + DIMS.x / 2;
const y = (1.2 - lt * 1.5) * DIMS.y;
const SCALE = SIZE / 1080;
const offsetX = 0.09;
const offsetY = 0.08;
textSize(300 * SCALE);
push();
translate((x -offsetX) * SIZE, (y - offsetY) * SIZE);
scale(-1, 1)
text("🚀", 0, 0);
pop();
image(dogeImg,
(x - offsetX) * SIZE,
(y - offsetY - 0.2) * SIZE,
dogeImg.width * SCALE,
dogeImg.height * SCALE
);
dots.forEach(d => {
if (d.hidden) {
d.hidden = d.pos.y < y
if (!d.hidden)
d.pos.x = x;
}
});
}
const particleLife = (p) => (t - p.born) / p.life;
function gradient(f) {
f = fract(f);
const i = floor(f * colors.length);
const i2 = (i+1) % colors.length;
const lf = fract(f * colors.length);
return lerpColor(colors[i], colors[i2], lf);
}
const emojis = ["✨", "🌟", "⭐", "☄️"];
const getEmoji = (f) => emojis[floor(emojis.length * fract(f))];
const emojis2 = ["🥳", "🤪", "😜"];
const getEmoji2 = (f) => emojis2[floor(emojis2.length * fract(f))];
// normalized cos function (normalized phase and amplitude between 0 - 1)
function cosn(v) {
return cos(v * TWO_PI) * 0.5 + 0.5;
}
function mod(i, n) {
return ((i % n) + n) % n;
}