Interactive NFT by @andriibakulin
<code>
const ITEMS_COUNT = 40;
let gEntities = [];
let gSizeArea;
let gSizePoints;
let gDistance, gDistanceSqr;
// MagicLogic
class Entity
{
constructor(index, count, xOffset, yOffset, direction)
{
const seq01 = index / count;
this.xOffset = xOffset;
this.yOffset = yOffset;
this.direction = direction;
this.color = seq01 * 255;
this.offset = seq01 * Math.PI;
this.angel = seq01 * Math.PI * 2;
this.power = new SmoothValue();
// Dynamic vars
this.circleX = this.circleY = null;
this.drawAtX = this.drawAtY = null;
this.colorV = null;
this.tick = 0;
}
next(dt)
{
this.tick++;
this.color += dt * 128;
this.offset += dt;
this.angel -= dt / 2;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Update dynamic attrs
const radius = gSizeArea * 0.15;
this.circleX = Math.sin(this.angel * this.direction) * radius;
this.circleY = Math.cos(this.angel * this.direction) * radius;
this.drawAtX = gSizeArea * this.xOffset + this.circleX;
this.drawAtY = gSizeArea * this.yOffset + this.circleY;
this.colorV = normalizeColorComp(this.color);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
let dstOffset = 0;
if (mouseIsPressed)
{
const dstLengthSqr = (mouseX-this.drawAtX)**2 + (mouseY-this.drawAtY)**2;
if (dstLengthSqr < gDistanceSqr)
dstOffset = Math.sqrt(dstLengthSqr) / gDistance;
}
if (dstOffset > 0)
this.power.update(1 - dstOffset, 0.5 * dt);
else
this.power.update(0, 2 * dt);
}
render()
{
let offset = -this.power.v * 2 * cos(this.offset);
const winCenterX = gSizeArea/2 - this.drawAtX;
const winCenterY = gSizeArea/2 - this.drawAtY;
resetMatrix();
translate(this.drawAtX, this.drawAtY);
strokeWeight(gSizePoints);
stroke(this.colorV, 255, 255);
point(0,0);
for (let i=-8; i<=8; i++)
{
let vv = 0.2 * i;
let xx = this.circleX * offset * vv;
let yy = this.circleY * offset * vv;
if (abs(xx) < 5 && abs(yy) < 5)
continue;
if (i===5)
{
let alpha = this.power.v*255;
if (alpha > 0)
{
strokeWeight(3);
stroke(this.colorV, 255, 255, alpha);
line(winCenterX,winCenterY, xx,yy);
}
}
strokeWeight(5-Math.abs(i/2));
stroke(this.colorV, 255, 255, (this.power.v+0.3)*255);
point(xx, yy);
}
}
}
// Lifecycle
function setup()
{
updateConsts();
createCanvas(gSizeArea, gSizeArea);
background(0);
pixelDensity(1);
frameRate(60);
colorMode(HSB, 255);
angleMode(DEGREES);
const dlt = 0.21;
for (let index=0; index<ITEMS_COUNT; index++)
{
gEntities.push(new Entity(index, ITEMS_COUNT, 0.50-dlt, 0.50-dlt, +1));
gEntities.push(new Entity(index, ITEMS_COUNT, 0.50+dlt, 0.50-dlt, -1));
gEntities.push(new Entity(index, ITEMS_COUNT, 0.50-dlt, 0.50+dlt, -1));
gEntities.push(new Entity(index, ITEMS_COUNT, 0.50+dlt, 0.50+dlt, +1));
}
}
function windowResized()
{
updateConsts();
resizeCanvas(gSizeArea, gSizeArea);
}
function draw()
{
const dt = deltaTime/1000;
background(0);
for (const idx in gEntities)
{
const ent = gEntities[idx];
ent.next(dt);
ent.render();
}
}
// Helpers
function updateConsts()
{
gSizeArea = getAreaSize();
gDistance = gSizeArea / 5;
gDistanceSqr = gDistance ** 2;
gSizePoints = clamp(gSizeArea / 66, 5, 12);
}
function normalizeColorComp(v)
{
v %= 256;
return v >= 0 ? v : v+256;
}
function getAreaSize()
{
return Math.min(window.innerWidth, window.innerHeight);
}
function clamp(value, min, max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
// Helpers : Smooth
function smoothValue(v0, v1, delta)
{
if (v0 === null)
return v1;
const dv = v1 - v0;
return Math.abs(dv) <= delta ? v1 : (dv < 0 ? v0 - delta : v0 + delta);
}
class SmoothValue
{
constructor(v=null)
{
this.v = v;
}
update(v, delta)
{
this.v = smoothValue(this.v, v, delta);
}
}
</code>