connectionThreshold: 340,
connectionMinDistance: 50,
nodeColors: ["rgb(193,255,0)", "rgb(193,255,0)"],
backgroundColor: "rgb(0,0,0)",
graphLineColor: "rgb(255,255,255)",
let nodeArray = [], logoNodes = [], logoImage, moveStartTime, moveEndTime, targetPositions = [], initialPositions = [], isMoving = false;
let flashInterval = 3000;
function preload() { logoImage = loadImage("unolab-logo-png.png"); }
createCanvas(params.canvasWidth, params.canvasHeight);
while (nodeArray.length < params.nodeAmount) nodeArray.push(createNode());
setInterval(startFlashMovement, flashInterval);
background(params.backgroundColor);
if (isMoving) updateParticleMovement();
nodeArray.forEach(node => { node.update(); node.handleEdge(); });
logoNodes.forEach(node => { node.update(); node.show(); });
nodeArray.forEach(node => node.show());
function extractLogoParticles() {
for (let y = 0; y < logoImage.height; y += params.logoDensity) {
for (let x = 0; x < logoImage.width; x += params.logoDensity) {
const idx = (x + y * logoImage.width) * 4;
if (logoImage.pixels[idx] < 50 && logoImage.pixels[idx + 1] < 50 && logoImage.pixels[idx + 2] < 50 && logoImage.pixels[idx + 3] > 0) {
logoNodes.push(createLogoNode(createVector(map(x, 0, logoImage.width, 0, width), map(y, 0, logoImage.height, 0, height))));
const randomLogoNodes = logoNodes.sort(() => 0.5 - Math.random()).slice(0, Math.floor(logoNodes.length * 0.3));
logoNodes = [...randomLogoNodes, ...randomLogoNodes];
function createLogoNode(pos) {
const vel = createVector(0, 0), acc = createVector(0, 0);
const update = () => { const springForce = p5.Vector.sub(pos, pos).mult(0.05); acc.add(springForce); vel.add(acc); vel.mult(params.friction); pos.add(vel); acc.set(0, 0); };
const show = () => { noStroke(); fill(params.nodeColors[0]); ellipse(pos.x, pos.y, params.logoNodeSize); };
return { pos, update, show };
const pos = createVector(random(width), random(height)), vel = createVector(random(-params.maxSpeed, params.maxSpeed), random(-params.maxSpeed, params.maxSpeed));
if (vel.mag() < params.minSpeed) vel.setMag(random(params.minSpeed, params.maxSpeed));
const acc = createVector(0, 0), type = random(params.gravityOptions);
const handleEdge = () => { if (pos.x < 0) pos.set(width, random(height)); else if (pos.x > width) pos.set(0, random(height)); if (pos.y < 0) pos.set(random(width), height); else if (pos.y > height) pos.set(random(width), 0); };
const update = () => { vel.add(acc); vel.limit(params.maxSpeed); pos.add(vel); vel.mult(params.friction); acc.set(type.x, type.y); };
const show = () => { noStroke(); fill(random(params.nodeColors)); ellipse(pos.x, pos.y, params.nodeSize); };
return { pos, acc, type, handleEdge, update, show };
function gabrielGraph() {
stroke(params.graphLineColor);
strokeWeight(params.lineThickness);
const maxLinesPerNode = 3;
nodeArray.forEach(dynamicNode => {
logoNodes.forEach(logoNode => {
if (connections >= maxLinesPerNode) return;
const distance = dist(dynamicNode.pos.x, dynamicNode.pos.y, logoNode.pos.x, logoNode.pos.y);
if (distance > params.connectionMinDistance && distance < params.connectionThreshold) {
line(dynamicNode.pos.x, dynamicNode.pos.y, logoNode.pos.x, logoNode.pos.y);
logoNodes.forEach(node => node.show());
function startFlashMovement() {
initialPositions = nodeArray.map(node => node.pos.copy());
targetPositions = nodeArray.map(() => createVector(random(width), random(height)));
moveStartTime = millis();
moveEndTime = moveStartTime + 3000;
function updateParticleMovement() {
let progress = (millis() - moveStartTime) / 3000;
if (progress > 1) { progress = 1; isMoving = false; }
const easingFactor = Math.pow(1 - progress, 12);
nodeArray.forEach((node, index) => {
const target = targetPositions[index], initialPos = initialPositions[index];
const direction = p5.Vector.sub(target, initialPos).mult(easingFactor);
node.pos.set(initialPos.x + direction.x, initialPos.y + direction.y);