xxxxxxxxxx
251
// Thanks for a great PCD CPH '22! Processing is a lot of fun.
// Toggle debug options with D key.
var debug = false;
// Ellipse variables
var bgColor;
var ellipseColor;
var gridX, gridY;
var w, h;
var n;
var padding;
var p, s; // Config for the smoothstep function used to move vertices. p is offset on x axis, s is steepness of curve. See https://www.desmos.com/calculator/3zhzwbfrxd for example.
// Mouse variables
var mousePosX;
var mousePosY;
var mousePos;
var mouseRadius;
var mouseRadiusSq;
var mouseRadiusDelta = 0;
var pullPower = 2;
var superellipses = [];
// Utility functions
function invlerp(x, y, a) {
return constrain((a - x) / (y - x), 0, 1);
}
function remap(x1, y1, x2, y2, a) {
return lerp(x2, y2, invlerp(x1, y1, a));
}
function customSmoothstep(a, p, s) {
let c = 2 / (1 - s) - 1;
function f(a, n) {
return pow(a, c) / pow(n, c - 1);
}
if (a <= p) {
return f(a, p);
} else {
return 1 - f(1 - a, 1 - p);
}
}
function sgn(w) {
if (w < 0) return -1;
else if (w == 0) return 0;
else return 1;
}
class Superellipse {
constructor(x, y, a, b, n) {
this.x = x;
this.y = y;
this.a = a;
this.b = b;
this.n = n;
this.r = 1;
}
draw() {
beginShape();
for (let t = 0; t < 360; t += 5) {
let cost = cos(t);
let sint = sin(t);
let x = this.x + pow(abs(cost), 2 / this.n) * this.a * this.r * sgn(cost);
let y = this.y + pow(abs(sint), 2 / this.n) * this.b * this.r * sgn(sint);
let pos = createVector(x, y);
// Pull towards mouse
let d = pos.magSq(pos.sub(mousePos));
let dNormalized = 1 - d / mouseRadiusSq;
let lerpFunction = remap(
1,
0,
0.3,
0,
customSmoothstep(dNormalized, p, s)
);
if (d < mouseRadiusSq) {
let amt = constrain(lerpFunction * pullPower, 0, 1);
x = lerp(x, mouseX, amt);
y = lerp(y, mouseY, amt);
}
vertex(x, y);
}
endShape(CLOSE);
}
}
function updateEllipseGrid(x, y, w, h, n, padding) {
superellipses = [];
for (let i = 0; i < x; i++) {
let posX = w / 2 + padding + (w + padding) * i;
for (let j = 0; j < y; j++) {
let posY = h / 2 + padding + (h + padding) * j;
let superellipse = new Superellipse(posX, posY, w / 2, h / 2, n);
superellipses.push(superellipse);
}
}
}
function drawLerpFunction() {
function drawCurve(weight, color) {
stroke(color);
strokeWeight(weight);
noFill();
beginShape();
let resolution = width / 10;
let stepX = width / resolution;
for (let i = 0; i <= resolution; i += 1) {
let tY = (stepX * i) / width;
vertex(stepX * i, height - height * customSmoothstep(tY, p, s));
}
endShape();
}
drawCurve(10, bgColor);
drawCurve(4, ellipseColor);
}
function setTextStyle() {
textSize(15);
textStyle(BOLDITALIC);
stroke(bgColor);
strokeWeight(6);
strokeJoin(ROUND);
fill(ellipseColor);
}
function setup() {
createCanvas(500, 500);
angleMode(DEGREES);
let colors = ["#ffd053", "#111111", "#494949", "#7c7a7a", "#ff5d73"];
colors = shuffle(colors);
bgColor = color(colors.pop());
ellipseColor = color(colors.pop());
p = 0.75;
s = 0.68;
n = 2.5;
mousePos = createVector();
gridX = 4;
gridY = 5;
}
function draw() {
background(bgColor);
//translate(width / 2, height / 2);
mousePosX = mouseX - width / 2;
mousePosY = mouseY - height / 2;
mousePos.set(mouseX, mouseY);
padding = max(width, height) / 10 / max(gridX, gridY);
w = (width - padding * (gridX + 1)) / gridX;
h = (height - padding * (gridY + 1)) / gridY;
mouseRadius = (w + h) / 5 + mouseRadiusDelta;
mouseRadiusSq = mouseRadius * mouseRadius;
updateEllipseGrid(gridX, gridY, w, h, n, padding);
noStroke();
fill(ellipseColor);
for (let superellipse of superellipses) {
superellipse.draw();
}
setTextStyle();
textAlign(LEFT, TOP);
text(
"Move your mouse around. Scroll to change warp size. Change count with arrows.",
10,
height - 38,
width,
38
);
// Debug viz.
if (debug) {
setTextStyle();
textAlign(LEFT, TOP);
text(
"Use left/right to adjust steepness of wobble. Use up/down to adjust width of wobble.",
10,
5,
width * 0.66
);
stroke(0, 0, 0, 128);
strokeWeight(2);
noFill();
circle(mouseX, mouseY, mouseRadius * 2);
drawLerpFunction();
}
}
function mouseWheel(event) {
let minimumDelta = (w + h) / 5 - 10;
let maximumDelta = width / 2 - 20 - (w + h) / 5; //(w + h) / 5 - min(width, height) / 3;
mouseRadiusDelta -= event.delta / 20;
mouseRadiusDelta = constrain(mouseRadiusDelta, -minimumDelta, maximumDelta);
}
function keyPressed() {
if (key === "d") {
debug = !debug;
}
if (debug) {
let sStep = 0.05;
let pStep = 0.05;
if (keyCode === LEFT_ARROW) {
s -= sStep;
}
if (keyCode === RIGHT_ARROW) {
s += sStep;
}
if (keyCode === UP_ARROW) {
p += pStep;
}
if (keyCode === DOWN_ARROW) {
p -= pStep;
}
s = constrain(s, 0, 0.99);
p = constrain(p, 0, 1);
print("s is " + s + ", p is " + p);
} else {
if (keyCode === LEFT_ARROW) {
gridX--;
}
if (keyCode === RIGHT_ARROW) {
gridX++;
}
if (keyCode === UP_ARROW) {
gridY++;
}
if (keyCode === DOWN_ARROW) {
gridY--;
}
}
}