Skip to content

Instantly share code, notes, and snippets.

@hdf
Last active March 12, 2020 11:37

Revisions

  1. hdf revised this gist Apr 17, 2019. 1 changed file with 12 additions and 5 deletions.
    17 changes: 12 additions & 5 deletions sketch.js
    Original file line number Diff line number Diff line change
    @@ -72,13 +72,20 @@ function epicycles(x, y, rotation, fourier) {
    return createVector(x, y);
    }

    function waves(fourier) {
    /*for (let i = 0; i < fourier.length; i++) {
    function waves(fourierX) {
    /*if (fourierX.length < 1) return;
    let fourier = fourierX.slice(0, 10).sort((a, b) => a.freq - b.freq);
    let l = fourier.length;
    colorMode(HSB, l);
    for (let i = 0; i < l; i++) {
    beginShape();
    for (let i2 = 0; i2 <= (time / TWO_PI) * width; i2++)
    vertex(i2, height/4 + fourier[i].amp + fourier[i].amp * sin(i2/TWO_PI/4 + fourier[i].phase));
    for (let i2 = 0; i2 <= (time / TWO_PI) * width; i2++) {
    vertex(i2, height / 4 + fourier[i].amp + fourier[i].amp * sin(i2 / (TWO_PI * 4) + fourier[i].phase));
    stroke(i, l, l);
    }
    endShape();
    }*/
    }
    colorMode(RGB, 255);*/
    }

    function draw() {
  2. hdf revised this gist Apr 15, 2019. 1 changed file with 13 additions and 4 deletions.
    17 changes: 13 additions & 4 deletions sketch.js
    Original file line number Diff line number Diff line change
    @@ -72,21 +72,30 @@ function epicycles(x, y, rotation, fourier) {
    return createVector(x, y);
    }

    function waves(fourier) {
    /*for (let i = 0; i < fourier.length; i++) {
    beginShape();
    for (let i2 = 0; i2 <= (time / TWO_PI) * width; i2++)
    vertex(i2, height/4 + fourier[i].amp + fourier[i].amp * sin(i2/TWO_PI/4 + fourier[i].phase));
    endShape();
    }*/
    }

    function draw() {
    background(0);

    waves(fourierX);
    let v = epicycles(width / 2, height / 2, 0, fourierX);
    path.unshift(v);

    beginShape();
    noFill();
    for (let i = 0; i < path.length; i++) {
    if (lengths.includes(path.length - i - 2))
    if (lengths.includes(path.length - i)) {
    endShape();
    else if (lengths.includes(path.length - i - 1))
    beginShape();
    else
    vertex(path[i].x, path[i].y);
    }
    vertex(path[i].x, path[i].y);
    }
    endShape();

  3. hdf revised this gist Apr 14, 2019. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion sketch.js
    Original file line number Diff line number Diff line change
    @@ -80,7 +80,6 @@ function draw() {

    beginShape();
    noFill();

    for (let i = 0; i < path.length; i++) {
    if (lengths.includes(path.length - i - 2))
    endShape();
  4. hdf revised this gist Apr 14, 2019. 2 changed files with 20 additions and 3 deletions.
    21 changes: 19 additions & 2 deletions sketch.js
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,7 @@ let x = [];
    let fourierX;
    let time = 0;
    let path = [];
    let lengths = [];
    if (typeof(drawing) == 'undefined')
    var drawing = [];
    let w = 300;
    @@ -27,7 +28,17 @@ window.onhashchange = function() {
    function setup() {
    generatePointsFromSvg((window.location.hash ? window.location.hash.substr(1) : 'gh') + '.svg', (r) => {
    let scale = (w/r[1] < h/r[2]) ? w / (r[1] + 1) : h / (r[2] + 1);
    drawing = r[0].map((e) => {return {x: e.x * scale, y: e.y * scale}});
    drawing = r[0].flat().map((e) => {return {x: e.x * scale, y: e.y * scale}});

    lengths = []; // Do not allow large jumps
    for (let i = 1; i < drawing.length; i++)
    if (Math.sqrt(Math.pow(drawing[i].x - drawing[i-1].x, 2) + Math.pow(drawing[i].y - drawing[i-1].y, 2)) > 10)
    lengths.push(i);

    /*lengths = r[0].map((e) => e.length).slice(0, -1);
    for (let i = 1; i < lengths.length; i++)
    lengths[i] += lengths[i-1];*/

    setup();
    });

    @@ -69,8 +80,14 @@ function draw() {

    beginShape();
    noFill();

    for (let i = 0; i < path.length; i++) {
    vertex(path[i].x, path[i].y);
    if (lengths.includes(path.length - i - 2))
    endShape();
    else if (lengths.includes(path.length - i - 1))
    beginShape();
    else
    vertex(path[i].x, path[i].y);
    }
    endShape();

    2 changes: 1 addition & 1 deletion svg2vec.js
    Original file line number Diff line number Diff line change
    @@ -35,7 +35,7 @@ function generatePointsFromSvg(file, cb, step_point) {
    delete point.alpha;
    data_points.push(point);
    }
    all_points = all_points.concat(data_points);
    all_points.push(data_points);
    }
    return [all_points, width, height];
    }
  5. hdf revised this gist Apr 14, 2019. 2 changed files with 3 additions and 3 deletions.
    2 changes: 1 addition & 1 deletion sketch.js
    Original file line number Diff line number Diff line change
    @@ -32,7 +32,7 @@ function setup() {
    });

    createCanvas(w, h);
    //frameRate(24);
    frameRate(60);
    const skip = 1;
    for (let i = 0; i < drawing.length; i += skip) {
    const c = new Complex(drawing[i].x, drawing[i].y);
    4 changes: 2 additions & 2 deletions svg2vec.js
    Original file line number Diff line number Diff line change
    @@ -27,8 +27,8 @@ function generatePointsFromSvg(file, cb, step_point) {
    let path = paths[i].getAttribute('d').replace(' ', ',');
    // get points at regular intervals
    let data_points = [];
    let c, l = Raphael.getTotalLength(path);
    for (c = 0; c < l; c += step_point) {
    let c, l = Raphael.getTotalLength(path), step = step_point * Math.pow(Math.floor(Math.log(l) / Math.log(10)), 2);
    for (c = 0; c < l; c += step) {
    let point = Raphael.getPointAtLength(path, c);
    point.x = (point.x - (width / 2));
    point.y = (point.y - (height / 2));
  6. hdf revised this gist Apr 14, 2019. 2 changed files with 16 additions and 9 deletions.
    11 changes: 10 additions & 1 deletion sketch.js
    Original file line number Diff line number Diff line change
    @@ -15,8 +15,17 @@ if (typeof(drawing) == 'undefined')
    let w = 300;
    let h = 300;

    window.onhashchange = function() {
    x = [];
    time = 0;
    path = [];
    drawing = [];
    current_svg_xml = '';
    setup();
    };

    function setup() {
    generatePointsFromSvg('gh.svg', (r) => {
    generatePointsFromSvg((window.location.hash ? window.location.hash.substr(1) : 'gh') + '.svg', (r) => {
    let scale = (w/r[1] < h/r[2]) ? w / (r[1] + 1) : h / (r[2] + 1);
    drawing = r[0].map((e) => {return {x: e.x * scale, y: e.y * scale}});
    setup();
    14 changes: 6 additions & 8 deletions svg2vec.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    // Based on: https://github.com/Shinao/PathToPoints
    // To generate text: https://danmarshall.github.io/google-font-to-svg-path/
    // Also consider: https://vecta.io/nano

    let current_svg_xml = '';

    @@ -19,21 +21,17 @@ function generatePointsFromSvg(file, cb, step_point) {
    let paths = doc.getElementsByTagName('path');
    // Read each paths from svg
    let all_points = [];
    let width = 0, height = 0;
    let bbox_path = doc.children[0].getAttribute('viewBox').split(' ');
    let width = parseFloat(bbox_path[2]), height = parseFloat(bbox_path[3]);
    for (let i = 0; i < paths.length; ++i) {
    let path = paths[i].getAttribute('d').replace(' ', ',');
    let bbox_path = doc.children[i].getAttribute('viewBox').split(' ');
    let w = parseFloat(bbox_path[2]);
    if (w > width) width = w;
    let h = parseFloat(bbox_path[3]);
    if (h > height) height = h;
    // get points at regular intervals
    let data_points = [];
    let c, l = Raphael.getTotalLength(path);
    for (c = 0; c < l; c += step_point) {
    let point = Raphael.getPointAtLength(path, c);
    point.x = (point.x - (w / 2));
    point.y = (point.y - (h / 2));
    point.x = (point.x - (width / 2));
    point.y = (point.y - (height / 2));
    delete point.alpha;
    data_points.push(point);
    }
  7. hdf created this gist Apr 10, 2019.
    46 changes: 46 additions & 0 deletions fourier.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    // Coding Challenge 130.3: Drawing with Fourier Transform and Epicycles
    // Daniel Shiffman
    // https://thecodingtrain.com/CodingChallenges/130.1-fourier-transform-drawing.html
    // https://thecodingtrain.com/CodingChallenges/130.2-fourier-transform-drawing.html
    // https://thecodingtrain.com/CodingChallenges/130.3-fourier-transform-drawing.html
    // https://youtu.be/7_vKzcgpfvU
    // https://editor.p5js.org/codingtrain/sketches/ldBlISrsQ

    class Complex {
    constructor(a, b) {
    this.re = a;
    this.im = b;
    }

    add(c) {
    this.re += c.re;
    this.im += c.im;
    }

    mult(c) {
    const re = this.re * c.re - this.im * c.im;
    const im = this.re * c.im + this.im * c.re;
    return new Complex(re, im);
    }
    }

    function dft(x) {
    const X = [];
    const N = x.length;
    for (let k = 0; k < N; k++) {
    let sum = new Complex(0, 0);
    for (let n = 0; n < N; n++) {
    const phi = (TWO_PI * k * n) / N;
    const c = new Complex(cos(phi), -sin(phi));
    sum.add(x[n].mult(c));
    }
    sum.re = sum.re / N;
    sum.im = sum.im / N;

    let freq = k;
    let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
    let phase = atan2(sum.im, sum.re);
    X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
    }
    return X;
    }
    14 changes: 14 additions & 0 deletions gh.svg
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
    16 changes: 16 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Discrete Fourier transformation</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.8/raphael.min.js"></script>
    <script src="svg2vec.js"></script>
    <script src="fourier.js"></script>
    <script src="sketch.js"></script>
    </head>
    <body></body>
    </html>
    75 changes: 75 additions & 0 deletions sketch.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,75 @@
    // Coding Challenge 130.3: Drawing with Fourier Transform and Epicycles
    // Daniel Shiffman
    // https://thecodingtrain.com/CodingChallenges/130.1-fourier-transform-drawing.html
    // https://thecodingtrain.com/CodingChallenges/130.2-fourier-transform-drawing.html
    // https://thecodingtrain.com/CodingChallenges/130.3-fourier-transform-drawing.html
    // https://youtu.be/7_vKzcgpfvU
    // https://editor.p5js.org/codingtrain/sketches/ldBlISrsQ

    let x = [];
    let fourierX;
    let time = 0;
    let path = [];
    if (typeof(drawing) == 'undefined')
    var drawing = [];
    let w = 300;
    let h = 300;

    function setup() {
    generatePointsFromSvg('gh.svg', (r) => {
    let scale = (w/r[1] < h/r[2]) ? w / (r[1] + 1) : h / (r[2] + 1);
    drawing = r[0].map((e) => {return {x: e.x * scale, y: e.y * scale}});
    setup();
    });

    createCanvas(w, h);
    //frameRate(24);
    const skip = 1;
    for (let i = 0; i < drawing.length; i += skip) {
    const c = new Complex(drawing[i].x, drawing[i].y);
    x.push(c);
    }
    fourierX = dft(x);
    fourierX.sort((a, b) => b.amp - a.amp);
    }

    function epicycles(x, y, rotation, fourier) {
    for (let i = 0; i < fourier.length; i++) {
    let prevx = x;
    let prevy = y;
    let freq = fourier[i].freq;
    let radius = fourier[i].amp;
    let phase = fourier[i].phase;
    x += radius * cos(freq * time + phase + rotation);
    y += radius * sin(freq * time + phase + rotation);

    stroke(255, 100);
    noFill();
    ellipse(prevx, prevy, radius * 2);
    stroke(255);
    line(prevx, prevy, x, y);
    }
    return createVector(x, y);
    }

    function draw() {
    background(0);

    let v = epicycles(width / 2, height / 2, 0, fourierX);
    path.unshift(v);

    beginShape();
    noFill();
    for (let i = 0; i < path.length; i++) {
    vertex(path[i].x, path[i].y);
    }
    endShape();

    const dt = TWO_PI / fourierX.length;
    time += dt;

    if (time > TWO_PI) {
    time = 0;
    path = [];
    }
    }
    43 changes: 43 additions & 0 deletions svg2vec.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,43 @@
    // Based on: https://github.com/Shinao/PathToPoints

    let current_svg_xml = '';

    function generatePointsFromSvg(file, cb, step_point) {
    if (typeof(step_point) == 'undefined') step_point = 0.5;
    if (current_svg_xml == '') {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', file, true);
    xhr.onload = function(e) {
    current_svg_xml = this.response;
    cb(generatePointsFromSvg(file, cb, step_point));
    };
    xhr.send();
    return;
    }

    let doc = (new DOMParser()).parseFromString(current_svg_xml, "application/xml");
    let paths = doc.getElementsByTagName('path');
    // Read each paths from svg
    let all_points = [];
    let width = 0, height = 0;
    for (let i = 0; i < paths.length; ++i) {
    let path = paths[i].getAttribute('d').replace(' ', ',');
    let bbox_path = doc.children[i].getAttribute('viewBox').split(' ');
    let w = parseFloat(bbox_path[2]);
    if (w > width) width = w;
    let h = parseFloat(bbox_path[3]);
    if (h > height) height = h;
    // get points at regular intervals
    let data_points = [];
    let c, l = Raphael.getTotalLength(path);
    for (c = 0; c < l; c += step_point) {
    let point = Raphael.getPointAtLength(path, c);
    point.x = (point.x - (w / 2));
    point.y = (point.y - (h / 2));
    delete point.alpha;
    data_points.push(point);
    }
    all_points = all_points.concat(data_points);
    }
    return [all_points, width, height];
    }