xxxxxxxxxx
379
// const WIDTH = 720;
// const HEIGHT = 1290;
const WIDTH = 1080;
const HEIGHT = 1920;
const RECORD = true;
const FRAMES = 750;
const QUALITY = 1;
const LINE_WIDTH = 30;
const OUTLINE_WIDTH = 5;
const THREAD_COUNT = 2;
const THREAD_WIDTH = 0.8;
const THREAD_BALL_WAVE_WIDTH = 0.9;
const WAVE_COUNT = 4.5;
const THREAD_SPIN = 2;
const BALL_COUNT = 2;
const SPOKES_COUNT = THREAD_COUNT * WAVE_COUNT * 15;
let SIZE, SCALE;
let style;
let black, white, purple, orange, pink, green;
function setup() {
colorMode(RGB, 1);
let w, h;
if (RECORD) {
w = WIDTH;
h = HEIGHT;
}else{
let size_f = min(windowWidth / WIDTH , windowHeight / HEIGHT);
w = WIDTH * size_f;
h = HEIGHT * size_f;
}
SIZE = min(w, h);
SCALE = SIZE / 1080;
createCanvas(w, h);
black = color(0, 0, 0.2);
white = color(1, 1, 1);
purple = color(1/3, 0, 1);
orange = color(1, 0.5, 0);
pink = color(1, 0, 0.5);
green = color(0, 1, 0.5);
let bg = purple;
let light = pink;
style = {
bg,
light,
threads: {
lineWidth: LINE_WIDTH,
color: black,
outlineWidth: OUTLINE_WIDTH,
outlineColor: purple,
},
bigBalls: {
r: 1/6,
color: white,
outlineColor: light,
outlineWidth: OUTLINE_WIDTH
},
spokes: {
lineWidth:1/3 * LINE_WIDTH,
color: black,
outlineWidth: 0.5 * OUTLINE_WIDTH,
outlineColor: color(0,0,0,0),
},
smallBalls: {
r: 1/150,
color: white,
outlineColor: bg,
outlineWidth: 0.5 * OUTLINE_WIDTH
}
}
}
function generateThreads() {
let threads = [];
const count = THREAD_COUNT;
for (let i=0; i<count; i++) {
const f = i / count;
threads.push(
generateThread(
THREAD_SPIN * t + f,
style.threads.color,
)
);
}
return threads;
}
function generateThread(t, color) {
const shape = [];
const count = 1000 * QUALITY;
for (let i=0; i<count; i++) {
const f = i / count;
const f_n = (i + 1) / count;
const s = {
kind: "line",
outlineKind: "SIDE",
p1: generateThreadPoint(f, t),
p2: generateThreadPoint(f_n, t),
style.threads,
}
const shade = lerpColor(style.bg, style.light, ballWave(f, 2));
const z = getZ(s);
s.color = lerpColor(color, shade, -z * 7.5);
shape.push(s);
}
return shape;
}
function generateThreadPoint(f, lt) {
const ball_wave = ballWave(f, 5);
const wave = ssin(f * WAVE_COUNT + lt) * (THREAD_WIDTH + THREAD_BALL_WAVE_WIDTH * ball_wave) / 8
const r = 0.6
return createVector(
scos(f) * (0.5 + wave) * r,
ssin(f) * (0.5 + wave) * r,
scos(f * WAVE_COUNT + lt) / 8
);
}
function generateBalls() {
let shapes = [];
const count = BALL_COUNT;
for (let i=0; i<count; i++) {
const f = i / count;
let r = 0.6/2
const lf = f + t + ballMove(f);
shapes.push({
kind: "circle",
p: {
x: scos(lf) * r,
y: ssin(lf) * r,
z: 0
},
style.bigBalls,
})
}
return shapes;
}
function ballMove(f) {
return ssin(f + t * 3 - 1/4) / BALL_COUNT * 1/4
}
function ballWave(f, p) {
let val = 0;
for (let i=0; i<BALL_COUNT; i++) {
let i_f = i / BALL_COUNT;
const lf = -ballMove(i_f);
val = max(val, pow(sinn(f - t + lf + i_f + 0.25), p * 5))
}
return val;
}
function generateSpokes() {
const shapes = [];
const count = SPOKES_COUNT;
for (let i=0; i<count; i++) {
const f = i / count;
const thread_t = t * THREAD_SPIN
shapes.push(generateSpoke(f, thread_t, thread_t + 1 / THREAD_COUNT));
shapes.push(generateSpoke(f, thread_t + 1 / THREAD_COUNT, thread_t));
}
return shapes;
// const offsetZ = 0.05;
// return [...shapes, ...shapes.map(l => ({
// kind: "line",
// p1: l.p1.copy().sub(0,0, offsetZ),
// p2: l.p2.copy().sub(0,0, offsetZ),
// color: l.outlineColor,
// lineWidth: l.lineWidth + l.outlineWidth * 2,
// outlineKind: "NONE",
// }))];
}
function generateSpoke(f, t1, t2) {
let p1 = generateThreadPoint(f, t1);
let p2 = generateThreadPoint(f, t2);
let div = p2.copy().sub(p1)
// p2.sub(div.copy().mult(0.5));
let wave = 1 - ballWave(f, 3);
const offset = (1.75 + 3 * wave) / 22
let move = div.copy().mult(offset);
p1.add(move);
div = p2.copy().sub(p1)
p2.set(p1).add(div.copy().mult(lerp(0, 0.5, wave)));
const shade = lerpColor(style.spokes.outlineColor, style.light, ballWave(f, 4));
return {
kind: "line",
// fullOutline: true,
p1, p2,
style.spokes,
outlineColor: shade,
outlineKind: "FULL",
};
}
function generateBalls2() {
let shapes = [];
const count = 200;
for (let t_i=0; t_i<THREAD_COUNT; t_i++) {
const tf = t_i / THREAD_COUNT;
for (let i=0; i<count; i++) {
const f = i / count;
let r = 1/4
const lf = f;
const p = generateThreadPoint(lf, t + tf);
const deriv = generateDerivative(lf, t + tf);
const norm = {x: deriv.y, y: -deriv.x };
const dist = ssin(t * 3 + f * 3) * 0.025;
const z_offset = scos(t * 3 + f * 3) * 0.025 * 3;
const s = {
kind: "circle",
p: {
x: p.x + norm.x * dist,
y: p.y + norm.y * dist,
z: p.z + z_offset,
},
norm,
style.smallBalls,
}
shapes.push(s);
}
}
return shapes;
}
const EPS = 0.0001
function generateDerivative(f, lt) {
const p1 = generateThreadPoint(f - EPS, lt);
const p2 = generateThreadPoint(f + EPS, lt);
const v = createVector(p2.x - p1.x, p2.y - p1.y)
v.normalize();
return {x: v.x, y: v.y };
}
function getZ(shape) {
const { kind } = shape;
switch(kind) {
case "line": return min(shape.p1.z ,shape.p2.z) ;
case "circle": return shape.p.z;
}
}
function drawLineOutline(l) {
let {p1, p2, color, lineWidth, outlineColor, outlineWidth, outlineKind} = l;
switch(outlineKind) {
case "SIDE":
const norm = createVector(p2.x - p1.x, p2.y - p1.y)
.rotate(-TAU / 4)
.normalize();
const dist = (lineWidth + outlineWidth) * SCALE / 2 * 0.98;
stroke(outlineColor)
strokeWeight(outlineWidth * SCALE);
line(
p1.x * SIZE + norm.x * dist,
p1.y * SIZE + norm.y * dist,
p2.x * SIZE + norm.x * dist,
p2.y * SIZE + norm.y * dist,
);
line(
p1.x * SIZE - norm.x * dist,
p1.y * SIZE - norm.y * dist,
p2.x * SIZE - norm.x * dist,
p2.y * SIZE - norm.y * dist,
);
break;
case "FULL":
stroke(outlineColor)
strokeWeight((lineWidth + outlineWidth * 2) * SCALE);
line(p1.x * SIZE, p1.y * SIZE, p2.x * SIZE, p2.y * SIZE);
break;
}
}
function mouseClicked() {
fullscreen(!fullscreen());
}
let t;
let mx;
let my;
let capture;
function draw() {
if (RECORD && frameCount === 1 && typeof P5Capture !== 'undefined') {
const capture = P5Capture.getInstance();
capture.start({
format: "webm",
duration: FRAMES,
quality: 1,
framerate: 60,
});
}
t = 0.5 * frameCount / FRAMES - 0.1;
mx = mouseX / windowWidth;
my = mouseY / windowHeight;
if (!RECORD && mx > 0 && mx < 1)
t = mx;
translate(width * 0.9, height / 2);
scale(2.25)
background(style.bg);
stroke("white");
let shapes = [
generateThreads(),
generateBalls(),
generateSpokes(),
// ...generateBalls2(),
];
shapes.sort((s1, s2) => getZ(s1) - getZ(s2));
shapes.forEach(l => {
const {kind} = l;
switch(kind) {
case "line": {
let {p1, p2, color, lineWidth} = l;
strokeCap(ROUND);
drawLineOutline(l);
strokeWeight(lineWidth * SCALE);
stroke(color)
line(p1.x * SIZE, p1.y * SIZE, p2.x * SIZE, p2.y * SIZE);
} break;
case "circle": {
const {p, r, color, norm, outlineColor, outlineWidth } = l;
stroke(outlineColor);
strokeWeight(outlineWidth * SCALE);
fill(color);
circle(p.x * SIZE, p.y * SIZE, r * SIZE);
strokeWeight(1);
stroke("black");
// if (norm)
// line(p.x * SIZE, p.y * SIZE, (p.x + norm.x * 0.1) * SIZE, (p.y + norm.y* 0.1) * SIZE)
} break
}
});
}
const ssin = (v) => sin(v * TAU)
const scos = (v) => cos(v * TAU)
const sinn = (v) => ssin(v) * 0.5 + 0.5
const cosn = (v) => scos(v) * 0.5 + 0.5