xxxxxxxxxx
let N = 20; // total number of epicycles is 2*N + 1
let f = 0.25; // controls how fast epicycles rotate
let color_backgr, color_draw, color_circ, color_radii, color_trail
var button_space, button_speedup, button_speeddown;
function setup() {
createCanvas(windowWidth, windowHeight);
frameRate(500);
color_backgr = color(50);
color_draw = color(255, 255, 255);
color_circ = color(255, 0, 255);
color_radii = color(255, 255, 0);
color_trail = color(0, 255, 0);
background(color_backgr);
button_space = createButton("Touch");
button_space.position(width/45, height/4.4);
button_speedup = createButton("Speed+");
button_speedup.position(width/45, height/4.4 + height/20);
button_speeddown = createButton("Speed-");
button_speeddown.position(width/45, height/4.4 + 2*height/20);
button_space.mousePressed(button_space_fun);
button_speedup.mousePressed(button_speedup_fun);
button_speeddown.mousePressed(button_speeddown_fun);
}
class Complex {
// Implements complex number logic
constructor(x, y) {
this.x = x;
this.y = y;
}
mult(x2, y2) {
let xold = this.x;
let yold = this.y;
this.x = xold * x2 - yold * y2;
this.y = yold * x2 + xold * y2;
}
add(x2, y2) {
this.x += x2;
this.y += y2;
}
abs() {
return sqrt(this.x * this.x + this.y * this.y);
}
}
let u = 100;
function compute_coef(points, k) {
// Computes k-th complex Fourier coefficient C_k
let dt = 2*PI / points.length;
let t = 0.0;
let result = new Complex(0.0, 0.0);
for (let j=0; j<points.length; j++)
{
let px = points[j][0];
let py = points[j][1];
let phi = -k * t;
let exponent = new Complex(cos(phi), sin(phi)); // rewrite exponent using Euler's formula
exponent.mult(px * dt, py * dt);
result.add(exponent.x, exponent.y);
t += dt;
}
result.mult(1/(2*PI), 0.0);
return result;
}
let trail = []; // array consisting of last epicycle which draws the path
let drawing = []; // array consisting of mouse drawing
let state = 0; // track when mouse drawing is finished
let C = []; // complex Fourier coefficients
let t = 0.0;
let dt;
let rectx, rectw, recty, recth;
function draw() {
background(color_backgr);
textSize(16);
strokeWeight(0);
fill(255);
text("Draw a closed path with your mouse\nWhen done, press <SPACE> or touch <Touch> to visualize epicycles\nPress <SPACE> again to clear everything\n\
Use mouse-wheel to change the speed of epicycles", width/60, height/30);
rectx = width/60;
rectw = 70;
recty = height/5;
recth = 30;
// strokeWeight(3);
// stroke(255);
// noFill();
// rect(rectx, recty, rectw, recth);
// strokeWeight(0);
// fill(255);
// text("SPACE", width/45, height/4.4);
if (drawing.length > 0)
{
let oldx = drawing[0][0];
let oldy = drawing[0][1];
let newx, newy;
for (let i=1; i<drawing.length-1; i++)
{
newx = drawing[i][0];
newy = drawing[i][1];
strokeWeight(2);
stroke(color_draw);
// line(width/2 + oldx, height/2 - oldy, width/2 + newx, height/2 - newy);
circle(width/2 + oldx, height/2 - oldy, 1);
strokeWeight(1);
stroke(0);
oldx = newx;
oldy = newy;
}
}
if (state==1) // done drawing with mouse
{
dt = 2*PI / drawing.length;
let Z = []; // array to store epicycle points
Z.push(C[0]);
for (let i=1; i<2*N+1; i++)
{
let phi = pow(-1, i+1)*ceil(i/2.0) * f * t;
let exponent = new Complex(cos(phi), sin(phi));
exponent.mult(C[i].x, C[i].y);
exponent.add(Z[i-1].x, Z[i-1].y);
Z.push(exponent);
}
let vertices = []; // convert Z to drawable pixel data
for (let i=0; i<2*N+1; i++)
{
let px = width/2 + Z[i].x;
let py = height/2 - Z[i].y;
vertices.push([px, py]);
}
// if trail starts repeating, stop appending new (repeating) points
if (t <= 2*PI/f)
trail.push(vertices[2*N+1 - 1]); // last epicycle location
let oldcx = vertices[0][0];
let oldcy = vertices[0][1];
for (let i=1; i<2*N+1; i++)
{
stroke(color_circ);
noFill();
let R = C[i].abs();
strokeWeight(2);
circle(vertices[i-1][0], vertices[i-1][1], 2*R); // epicycles
stroke(0);
let newcx = vertices[i][0];
let newcy = vertices[i][1];
stroke(color_radii);
line(oldcx, oldcy, newcx, newcy); // radii of epicycles
stroke(0);
strokeWeight(1);
oldcx = newcx;
oldcy = newcy;
}
if (trail.length > 1) // draw trail
{
let oldvx = trail[0][0];
let oldvy = trail[0][1];
for (let i=0; i<trail.length-1; i++)
{
let newvx = trail[i][0];
let newvy = trail[i][1];
// if (i > trail.length - trail_size)
stroke(color_trail);
strokeWeight(2);
line(oldvx, oldvy, newvx, newvy);
strokeWeight(1);
stroke(0);
oldvx = newvx;
oldvy = newvy;
}
}
t = t + dt;
}
}
function mouseDragged() {
if (state == 0)
// SPACE box for mobile users
if (!((mouseX > rectx && mouseX < rectx+rectw) && (mouseY > recty && mouseY < recty+recth)))
drawing.push([mouseX - width/2, height/2 - mouseY]);
}
function Compute() {
if (state == 0)
{
for (let n=0; n<2*N+1; n++)
{
let k = pow(-1, n+1) * ceil(n/2.0); // k = 0, 1, -1, 2, -2 ... (2*N + 1 terms)
C.push(compute_coef(drawing, k));
state = 1 - state;
}
}
else
{
drawing = [];
C = [];
trail = [];
t = 0.0;
f = 0.25;
state = 1 - state;
redraw();
}
}
function keyPressed() {
if (keyCode == 32)
Compute();
}
function button_space_fun() {
Compute();
}
let df, factor, fnew;
function mouseWheel(event) {
df = event.delta / 53 / 10;
update_f(df);
}
function button_speedup_fun() {
df = -0.1;
update_f(df);
}
function button_speeddown_fun() {
df = 0.1;
update_f(df);
}
function update_f(df) {
fnew = f - df;
if (fnew < 0.1)
fnew = 0.1;
// update time parameter so that the phase remains the same
factor = fnew / f;
t = 1.0 / factor * t;
f = fnew;
}