I have made a few animations using complex Fourier Transformations and processing.
Sketch to generate the points
/**
ENGLISH
Instructions:
When the sketch launches you have to import a image.
You can adjust said image by right-clicking.
Left-clicking adds a point to the line.
z removes the last point.
p saves the data directly. The curve will be closed by making a line to the first point.
r saves a edited version of the data.The curve will be closed by going back the route to the first point.
o loads a new image
i loads point data saved using p
*/
/**
GERMAN
Anleitung:
Wenn das Programm gestartet wird kann ein Bild importiert werden
Das Bild kann mit einem Rechtsklick verschoben und vergrößert werden.
Mit einem Linksklick kann ein Punkt hinzugefügt werden
Mit z kann man den letzten Punkt entfernen
Mit p kann man die Daten direkt abspeichern. Die Kurve wird geschlossen, indem eine Linie zum ersten Punkt hinzugefügt werden.
Mit r kann man die Daten abspeichern. Die Kurve wird geschlossen, indem der gleiche weg zurückgegangen wird.
Mit o kann man ein neues Bild laden.
Mit i kann man Daten, die mit p abgespeichert wurden wieder aufrufen.
*/
import javax.swing.*;
import java.util.*;
List<Integer> X;
List<Integer> Y;
int p=0;
volatile PImage im;
volatile boolean allFilesRead=false;
String pushed[]=null;
int sx=0;
int sy=0;
float resc=1;
int tx=0;
int ty=0;
void setup() {
size(900, 900);
X=new ArrayList();
Y=new ArrayList();
selectInput("Data", "loadFile");
while (!allFilesRead);
sx=im.width;
sy=im.height;
im.resize(im.width, im.height);
println(X.size());
p=X.size();
}
void draw() {
background(255);
image(im, tx, ty);
fill(255, 150);
rect(-1, -1, width+2, height+2);
if (p>=1) {
for (int i=1; i<p; i++) line(X.get(i), Y.get(i), X.get(i-1), Y.get(i-1));
}
}
void mousePressed() {
if (mouseButton==LEFT) {
X.add(mouseX);
Y.add(mouseY);
p=X.size();
}
if (mouseButton==RIGHT)getInfo();
}
void keyPressed() {
if (key=='r') {
String[] ret=new String[2*X.size()-1];
int co[][]=new int[2*X.size()][2];
for (int i=0; i<X.size(); i++) {
ret[i]=""+X.get(i)+" "+Y.get(i);
}
for (int i=0; i<X.size()-1; i++) {
ret[i+X.size()]=""+X.get(X.size()-1-i)+" "+Y.get(X.size()-1-i);
}
pushed=ret;
selectOutput("Save As", "saveTxtFile");
println(ret);
}
if (key=='p') {
String[] ret=new String[X.size()];
// int co[][]=new int[X.size()][2];
for (int i=0; i<X.size(); i++) {
ret[i]=X.get(i)+" "+Y.get(i);
}
pushed=ret;
selectOutput("Save As", "saveTxtFile");
println(ret);
}
if (key=='z') {
X.remove(X.size()-1);
Y.remove(Y.size()-1);
p=X.size();
}
if (key=='s') {
color c=get(mouseX, mouseY);
for (int i=mouseX; i>=0; i--) if (c!=color(0, 0, 0)&&(abs(red(c)-red(get(i, mouseY)))>24||abs(green(c)-green(get(i, mouseY)))>24||abs(blue(c)-blue(get(i, mouseY)))>24)) {
X.add(i);
Y.add(mouseY);
p=X.size();
i=-1;
}
}
if (key=='i') {
selectInput("Open image","loadFile");
}
if (key=='o') {
selectInput("Open point data","loadTextFile");
}
}
void mouseDragged() {
if (frameCount%5==0) {
X.add(mouseX);
Y.add(mouseY);
p=X.size();
}
}
public void loadFile(File f) {
im=loadImage(f.getAbsolutePath());
allFilesRead=true;
}
public void loadTextFile(File f) {
String[] load=loadStrings(f);
X.clear();
Y.clear();
for (String st : load) {
String[] splitted=st.split(" ");
X.add(int(splitted[0]));
Y.add(int(splitted[1]));
println(int(splitted[0]), int(splitted[1]));
}
p=X.size();
}
public void saveTxtFile(File f) {
saveStrings(f, pushed);
allFilesRead=true;
}
public void getInfo() {
JPanel c=new JPanel();
JTextField txf=new JTextField(10);
txf.setText(tx+"");
JTextField tyf=new JTextField(10);
tyf.setText(ty+"");
JTextField resf=new JTextField(10);
resf.setText(resc+"");
c.setLayout(new java.awt.GridLayout(3, 2, 10, 10));
c.add(new JLabel("Translation X:"));
c.add(txf);
c.add(new JLabel("Translation Y:"));
c.add(tyf);
c.add(new JLabel("Rescale:"));
c.add(resf);
JOptionPane.showMessageDialog(null, c);
tx=int(txf.getText());
ty=int(tyf.getText());
resc=float(resf.getText());
im.resize((int)(sx*resc), (int)(sy*resc));
}
Sketch to render the animation
/**
ENGLISH
How to use this program:
When the sketch launches you select the color for the line. Next the file with the points will be selected.
These are the instructions what to paint.
Toggleable function:
z disables the display of the epicycles
c disables the display of thee circles only
g makes the slower cycles a lighter color
ENTER makes the sketch focus the point currently painted
f makes thee sketch fokus (0,0) as middle point
l locks all keys
0 halts the program (however new images will be safed)
q makes the program click every 75 frames
Other functions:
w and s zoom in/out
pressing r resets the simulation
pressing r for at least 3 seconds allows selecting a new file to read
t resets all toggelable functions and zooming
Settings.ini settings:
circles: how many circles spin to the left (in reality double the circles are calculated)
delta: Used for integrating numerically must be small but >0
frames: Number of frames needed to complete a complete cycle
record: When true all frames are saved to be made into a video
*/
/**
GERMAN
Anleitung:
Wenn man das Programm öffnet kann man eine Strichfarbe für die Funktion auswählen.
Als nächstes kann man eine Datei laden, aus deren Punkte die Kurve berechnet wird.
Mit z kann man die Zeichnung der Kreise und Zeiger abschalten
Mit c kann man nur die Zeichnung der Kreise abschalten
Mit g kann man dafür sorgen, dass die langsameren Kreise heller werden und die schnelleren dunkler
Mit ENTER kann man den gezeichneten Punkt fokusieren
Mit f kann man die obere ecke fokusieren
Mit w und s kann man rein, bzw rauszoomen
Mit r kann man die Simulation zurücksetzen
Mit t kann man die Ansicht zurücksetzten
Mit l kann man die Tastendrücke sperren/entsperren
Mit 0 hält das Programm an
Mit q clickt das Programm alle 75 Bilder
Wenn man r gedrückt lässt kann man neue Punkte einlesen
Einstellungen:
circles: wie viele Kreise drehen nach links (in wirklichkeit generiert er die Doppelte anzahl an kreien)
delta: Benötigt für das numerische Integrieren, je nähere an 0 desto genauer wird die Berechnung der Kreise aber die Berechnung dauert länger.
frames: Anzahl der Frames insgesammt. Bestimmt die Geschwindigkeit
record: Bestimmt ob die einzelnen Frames abgespeichert werden sollen, sodass man ein Video erstellen kann.
*/
//float[][] v={{100, 300}, {300, 100}, {100, 100}, {300, 300}, {300, 100},{100, 300},{300, 300}, {100, 100}};
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
Robot r;
float[][] v={};
float g[][];
volatile String read[];
float dif=0.0002;
int fk=4000;
ArrayList<Float> p;
ArrayList<Float> p2;
float vv=1;
boolean record=false;
boolean b=false;
boolean c=true;
float re[];
boolean z=true;
int sk=1;
int ll=0;
boolean lock=false;
boolean gradient=false;
float ak[]={0, 0};
boolean focus=false;
int reset=0;
boolean autoclick=false;
int circlenumb=700;
boolean halt=false;
volatile boolean allFilesRead=false;
float[] f(float x) {
float dist[]=new float[v.length];
// for(int i=0;i<v.length;i++) dist[i];
int sc2=0;
int d=0;
int d2=0;
for (int i=0; i<v.length-1; i++) {
d2+=dist(v[(i+1)%v.length][0], v[(i+1)%v.length][1], v[i][0], v[i][1]);
}
for (int i=0; i<v.length-1&&d+dist(v[(i+1)%v.length][0], v[(i+1)%v.length][1], v[i][0], v[i][1])<x/(2*PI)*d2; i++) {
d+=dist(v[(i+1)%v.length][0], v[(i+1)%v.length][1], v[i][0], v[i][1]);
sc2++;
}
float xp=(x/(2*PI)*d2-d)/dist(v[(sc2+1)%v.length][0], v[(sc2+1)%v.length][1], v[sc2][0], v[sc2][1]);
// println(xp,d,(x/(2*PI)*d2-d),dist(v[(sc2+1)%v.length][0], v[(sc2+1)%v.length][1], v[sc2][0], v[sc2][1]));
if (dist(v[(sc2+1)%v.length][0], v[(sc2+1)%v.length][1], v[sc2][0], v[sc2][1])==0) xp=1;
// println(xp);
/*if (xp>1) {
exit();
}*/
float[] ret={(v[(sc2+1)%v.length][0]-v[sc2][0])*xp+v[sc2][0], (v[(sc2+1)%v.length][1]-v[sc2][1])*xp+v[sc2][1]};
//println(ret);
return ret;
}
Color col=Color.RED;
void setup() {
try {
r=new Robot();
}
catch(Exception e) {
}
getSurface().setVisible(false);
readSettings();
JColorChooser c=new JColorChooser(Color.RED);
JOptionPane.showMessageDialog(null, c);
col=c.getColor();
try {
ps=new java.io.PrintStream(sketchPath("data/function"));
}
catch(Exception e) {
}
p=new ArrayList();
p2=new ArrayList();
//read=loadStrings("read.txt");
selectInput("Data", "loadFile");
while (!allFilesRead);
v=new float[read.length][2];
for (int i=0; i<v.length; i++) v[i]=new float[]{float(read[i].split(" ")[0]), float(read[i].split(" ")[1])};
// for (int i=0; i<v.length; i++) v[i][1]=-v[i][1]+850;
size(900, 900);
background(255);
noFill();
frameRate(60);
//strokeWeight(4);
calculates(circlenumb);
getSurface().setVisible(true);
}
int startat=0;
boolean paintThisFrame=true;
void draw() {
if (!halt) {
if (ll>0) ll--;
background(255);//=f(((float)sk/230)%(2*PI));
//if(b) translate(-vv*re[0],-vv*re[1]);
re=newpos(-((float)sk/fk)%(2*PI));
//ellipse(re[0],re[1],10,10);
stroke(255, 0, 0);
point(vv*(re[0]+(b?-re[0]:0))+(b?width/2:0), vv*(re[1]+(b?-re[1]:0))+(b?height/2:0));
if (((float)sk/fk)<=2.2*PI) {
p.add(re[0]);
p2.add(re[1]);
}
if(autoclick) if (sk%75==0) {
r.mousePress(InputEvent.BUTTON1_MASK);
delay(100);
r.mouseRelease(InputEvent.BUTTON1_MASK);
}
if (frameCount%100==0) println(p.size());
ak[0]=re[0];
ak[1]=re[1];
//point(re[0],re[1]);
strokeWeight(2);
stroke(col.getRed(), col.getGreen(), col.getBlue());
if (paintThisFrame) for (int i=0; i<sk-1&&((float)sk/fk)%(2*PI)<=2*PI; i++) {
//re=newpos(((float)i/fk)%(2*PI));
//ellipse(re[0],re[1],10,10);
line(vv*(p.get(i)+(b?-re[0]:0))+(b?width/2:0), vv*(p2.get(i)+(b?-re[1]:0))+(b?height/2:0), vv*(p.get(i+1)+(b?-re[0]:0))+(b?width/2:0), vv*(p2.get(i+1)+(b?-re[1]:0))+(b?height/2:0));
//println(re);
}
stroke(0);
strokeWeight(10);
strokeWeight(1);
float last[]={g[(g.length-1)/2][0], g[(g.length-1)/2][1]};
if (z&&paintThisFrame) for (int i=1; i<g.length; i++) {
if (i==1) {
strokeWeight(5);
point(vv*last[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*last[1]+(b?-vv*ak[1]:0)+(b?height/2:0));
strokeWeight(1);
}
int n=(int)(i/2)*(int)pow(-1, i);
float re2[]=newpos_ex(-((float)sk/fk)%(2*PI), n, (int)((i-1)/2)*(int)pow(-1, i-1));
re[0]=re2[0];
re[1]=re2[1];
//float[] re2=re=newpos_ex(((float)sk/230)%(2*PI),(int)((i-1)/2)*(int)pow(-1,i-1),(int)((i-2)/2)*(int)pow(-1,i-2));
stroke(0);
if (gradient&&paintThisFrame) stroke(255*pow(((float)g.length-(float)i)/(float)g.length, 0.9));
//println(pow(((float)g.length-(float)i)/(float)g.length,0.5));
line(vv*re[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*re[1]+(b?-vv*ak[1]:0)+(b?height/2:0), vv*last[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*last[1]+(b?-vv*ak[1]:0)+(b?height/2:0));
if (!gradient) stroke(90);
if (c&&paintThisFrame)ellipse(vv*last[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*last[1]+(b?-vv*ak[1]:0)+(b?height/2:0), vv*2*sqrt(sq(last[0]-re[0])+sq(last[1]-re[1])), vv*2*sqrt(sq(last[0]-re[0])+sq(last[1]-re[1])));
last[0]=re[0];
last[1]=re[1];
}
if (z&&paintThisFrame) line(vv*(ak[0]+(b?-ak[0]:0))+(b?width/2:0), vv*(ak[1]+(b?-ak[1]:0))+(b?height/2:0), vv*(last[0]+(b?-ak[0]:0))+(b?width/2:0), vv*(last[1]+(b?-ak[1]:0))+(b?height/2:0));
if (!gradient) stroke(90);
if (z&&c&&paintThisFrame) ellipse(vv*re[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*re[1]+(b?-vv*ak[1]:0)+(b?height/2:0), vv*2*sqrt(sq(ak[0]-re[0])+sq(ak[1]-re[1])), vv*2*sqrt(sq(ak[0]-re[0])+sq(ak[1]-re[1])));
;
//if(b)translate(-re[0],-re[1]);
sk++;
if (keyPressed) if (key=='r') {
reset++;
if (reset>=3*frameRate) {
allFilesRead=false;
//read=loadStrings("read.txt");
selectInput("Data", "loadFile");
while (!allFilesRead);
v=new float[read.length][2];
for (int i=0; i<v.length; i++) v[i]=new float[]{float(read[i].split(" ")[0]), float(read[i].split(" ")[1])};
background(355);
calculates(circlenumb);
}
} else {
reset=0;
}
}
if (frameCount>startat-5&&record)saveFrame("animation/frame-######.png");
if (frameCount>startat-10) paintThisFrame=true;
}
float[] integ(int num) {
float sum[]={0, 0};
for (float i=0; i<2*PI; i+=dif) {
float x[]=f(i);
sum[0]+=(x[0]*cos(num*i)-sin(num*i)*x[1])*dif;
sum[1]+=(x[0]*sin(num*i)+cos(num*i)*x[1])*dif;
}
sum[0]/=2*PI;
sum[1]/=2*PI;
return sum;
}
java.io.PrintStream ps;
void calculates(int n) {
JProgressBar prog=new JProgressBar(0, 2*n);
JLabel text=new JLabel(" Calculating");
JFrame wind=new JFrame();
text.setFont(new Font("arial", Font.PLAIN, 22));
wind.setUndecorated(true);
wind.setLayout(new BorderLayout());
wind.setSize(500, 350);
wind.setBackground(new Color(200, 200, 200));
wind.setLocation(displayWidth/2-250, displayHeight/2-175);
wind.add(prog, "South");
wind.add(text, "Center");
prog.setStringPainted(true);
wind.setVisible(true);
g=new float[2*n+1][2];
for (int i=0; i<g.length; i++) {
g[i]=integ(i-n);
println(((float)i/(float)g.length)*100+"%");
prog.setValue(i);
prog.setString((int)(((float)i/(float)g.length)*100)+"%");
text.setText(" Calculated: "+i+"/"+g.length);
}
wind.setVisible(false);
wind.dispose();
}
float[] newpos(float x) {
float sum[]={0, 0};
int l=(g.length-1)/2;
for (int i=0; i<g.length; i++) {
sum[0]+=g[i][0]*cos((i-l)*x)-sin((i-l)*x)*g[i][1];
sum[1]+=g[i][0]*sin((i-l)*x)+cos((i-l)*x)*g[i][1];
}
return sum;
}
float[] newpos_ex(float x, int lu, int lo) {
float sum[]={0, 0};
int l=(g.length-1)/2;
if (lu>lo) {
int zw=lo;
lo=lu;
lu=zw;
}
for (int i=lu+l; i<=lo+l; i++) {
sum[0]+=g[i][0]*cos((i-l)*x)-sin((i-l)*x)*g[i][1];
sum[1]+=g[i][0]*sin((i-l)*x)+cos((i-l)*x)*g[i][1];
}
return sum;
}
void keyPressed() {
if (!lock) {
if (key=='w') {
vv*=1.1;
}
if (key=='t') {
vv=1;
b=false;
c=true;
z=true;
gradient=false;
}
if (key=='s') {
vv/=1.1;
}
if (key=='g') {
gradient=!gradient;
}
if (key==ENTER) {
b=!b;
}
if (key=='c') {
c=!c;
}
if (key=='z') {
z=!z;
}
if (key=='q') {
autoclick=!autoclick;
}
if (key=='r') {
sk=1;
p.clear();
p2.clear();
}
if (key=='f') {
if (!b) focus=!focus;
if (focus) translate(width/2-g[(g.length-1)/2][0], height/2-g[(g.length-1)/2][1]);
if (!focus) translate(-width/2+g[(g.length-1)/2][0], -height/2+g[(g.length-1)/2][1]);
}
}
if (key=='l') {
if (ll>0) lock=!lock;
if (ll==0) ll=50;
}
if (key=='0') {
halt=!halt;
}
}
void readSettings() {
String[] lines=loadStrings(sketchPath("settings.ini"));
for (String line : lines) {
String[] args=line.replaceAll(" ", "").split(":");
switch(args[0]) {
case "circles":
circlenumb=int(args[1]);
break;
case "frames":
fk=(int)(int(args[1])/(2*PI));
break;
case "delta":
dif=float(args[1]);
break;
case "record":
record=boolean(args[1]);
println(record);
break;
}
}
}
void loadFile(File f) {
read=loadStrings(f);
for (String line : read) println(line);
allFilesRead=true;
println(allFilesRead);
}
settings.ini
circles:1000
delta:0.0004
frames: 20000
record:true
Animations
Fourier: https://youtu.be/kWC3FJ-5RhsEinstein: https://youtu.be/aC_QoYJXgQA
Darkest Dungeon caracters:
Jester: https://youtu.be/Y5ia-B9hEVo
Grave Robber: https://youtu.be/XOuQRB8voPI
Occultist: https://youtu.be/vuXpEP7QLBE