Skip to content Skip to sidebar Skip to footer

Creating The Butterfly Curve With Arrays

I have two questions, the first being how do I access the indexes within my array separately, because my console.log of [n][0] results in two values - x and y. Secondly, for the bu

Solution 1:

Since you're pushing an array here: n.push([x,y]) you can access the x component of the first element with n[0][0] and the y component of the same element with n[0][1]

Example:

var n = [];
n.push( ["x", "y"] );
console.log( n[0][0] );
console.log( n[0][1] );

As for the useful values of t - in the image you've shown, you'll notice that the same butterfly is drawn several times at different sizes. To draw a complete butterfly, you need to use the range for t of [0..2pi]. If you want to draw two butterflies, you need to use the range [0..4pi]. That is it's cyclic over the same period that a circle is. Unlike a circle however, each cycle doesn't draw over the previous one.

Here's a quick and nasty example:

function byId(id) {
  return document.getElementById(id);
}
window.addEventListener('load', onDocLoaded, false);

function onDocLoaded(evt) {
  butterFly();
}

function butterFly() {
  var pointArray = [];
  var stepSize = 0.05; // ~125 steps for every 360°
  var upperLimit = 4 * Math.PI;
  var scale = 20;
  for (var t = 0.0; t < upperLimit; t += stepSize) {
    var xVal = Math.sin(t) * ((Math.exp(Math.cos(t))) - (2 * Math.cos(4 * t)) - (Math.pow(Math.sin(t / 12), 5)));
    var yVal = Math.cos(t) * ((Math.exp(Math.cos(t))) - (2 * Math.cos(4 * t)) - (Math.pow(Math.sin(t / 12), 5)));
    pointArray.push([scale * xVal, -scale * yVal]); // -1 value since screen-y direction is opposite direction to cartesian coords y
  }
  drawButterFly(pointArray);
}

function drawButterFly(pointArray) {
  var can = byId('myCan');
  var ctx = can.getContext('2d');
  var originX, originY;
  originX = can.width / 2;
  originY = can.height / 2;

  ctx.beginPath();
  for (var i = 0; i < pointArray.length; i++) {
    if (i === 0) {
      ctx.moveTo(originX + pointArray[i][0], originX + pointArray[i][1]);
    } else {
      ctx.lineTo(originX + pointArray[i][0], originY + pointArray[i][1]);
    }
  }
  ctx.closePath();
  ctx.stroke();
}
canvas {
  border: solid 1px red;
}
<canvas id='myCan' width='256' height='256' />

Solution 2:

If I'm not mistaken, the Butterfly curve is given as a pair of parametric equations, meaning you increment t to get the next (x, y) points on your curve. In other words, your t is what you should be using in place of u in your code, and the range of values for t should be 0 .. 24*pi as that's the range in which sin(t / 12) has its unique values).

Here's a version that demonstrates the drawing of the curve to a canvas:

function getPoint(t, S, O) {
  var cos_t = Math.cos(t);
  var factor = Math.exp(cos_t) - 2 * Math.cos(4*t) - Math.pow(Math.sin(t/12), 5);
  return {
    x: S * Math.sin(t) * factor + O.x,
    y: S * cos_t * factor + O.y
  };
}

var canvas = document.getElementById("c");
canvas.width = 300;
canvas.height = 300;
var ctx = canvas.getContext("2d");

// First path
ctx.beginPath();
ctx.strokeStyle = 'blue';

var offset = {x:150, y:120};
var scale = 40;
var maxT = 24 * Math.PI;

var p = getPoint(0, scale, offset);
ctx.moveTo(p.x, canvas.height - p.y);
for (var t = 0.01; t <= maxT; t += 0.01) {
  p = getPoint(t, scale, offset);
  ctx.lineTo(p.x, canvas.height - p.y);
}
ctx.stroke();
#c {
  border: solid 1px black;
}
<canvas id="c"></canvas>

One thing to note: canvases have y = 0 start at the top, so you need to inverse your y (i.e. canvas.height - y) to have your curve orient correctly.

UPDATE: Added animated version

As requested by royhowie, here's an animated version, using requestAnimationFrame:

function getPoint(t, S, O) {
  var cos_t = Math.cos(t);
  var factor = Math.exp(cos_t) - 2 * Math.cos(4*t) - Math.pow(Math.sin(t/12), 5);
  return {
    x: S * Math.sin(t) * factor + O.x,
    y: S * cos_t * factor + O.y
  };
}

var canvas = document.getElementById("c");
canvas.width = 300;
canvas.height = 300;
var ctx = canvas.getContext("2d");

var offset = {x:150, y:120};
var scale = 40;
var maxT = 24 * Math.PI;

var animationID;
var started = false;
var t = 0;

document.getElementById('start').addEventListener('click', function(e) {
  e.preventDefault();
  if (!started) {
    animationID = requestAnimationFrame(animate);
    started = true;
  }
});

document.getElementById('pause').addEventListener('click', function(e) {
  e.preventDefault();
  if (started) {
    cancelAnimationFrame(animationID);
    started = false;
  }
});



function animate() {
  animationID = requestAnimationFrame(animate);
  
  var p = getPoint(t, scale, offset);

  if (t === 0) {
    ctx.beginPath();
    ctx.strokeStyle = 'blue';
    ctx.moveTo(p.x, canvas.height - p.y);
    t += 0.01;
  } else if (t < maxT) {
    ctx.lineTo(p.x, canvas.height - p.y);
    ctx.stroke();
    t += 0.01;
  } else {
    cancelAnimationFrame(animationID);
  }
}
#c {
  border: solid 1px black;
}
<div>
  <button id="start">Start</button>
  <button id="pause">Pause</button>
</div>
<canvas id="c"></canvas>

Solution 3:

Question 1

For an arbitrary integer i, let n[i] = [xi, yi]. xi can then be accessed via n[i][0] and yi via n[i][1]

Question 2

For values of t, I am sure you're gonna want to use sub-integer values, so I recommend using a constant increment value representing the "resolution" of your graph.

Let's call it dt. Also, I'd advise changing your variable names from single letters to something more descriptive, like min_t and max_t, and instead of n I'm going to call your array points.

function drawButterFly(points){
    for (var i = 0, n = points.count; i < n; ++i) {
        var x = points[i][0];
        var y = points[i][1];
        ...
    }
}

function butterFly(min_t, max_t, dt, r) {

    var points = [];
    for (var t = min_t; t < max_t; t+=dt){
        var x = r*Math.sin(t)*...
        var y = r*Math.cos(t)*...
        points.push([x,y]);
    }
    drawButterFly(points, dt);
}

I'm not sure what the other loop inside of that function was for, but if you need it, you can adapt from the pattern above.

Usage example: butterFly(0, 10, 0.01, 3) -> t goes from 0 to 10 with an increment of 0.01, and r=3


Solution 4:

Regarding your first question it's a better option to replace the multidimensional array containing the x and y coordinates with an object. Then when iterating over the array you can check for the object values.

So instead of:

n.push([x,y]);

you should do:

m.push({
    'xPos' : x,
    'yPos' : y
})

Later you can access this by m.xPos or m.yPos Then you can access the x and y values by object literal names.

Regarding the second question: for a good pseudo code implementation of butterfly curves you might check Paul Burke site: http://paulbourke.net/geometry/butterfly/. So t in your case is:

t = i * 24.0 * PI / N;

As you see t is a parametric value which got incremented on each step when iterating over the array.


Post a Comment for "Creating The Butterfly Curve With Arrays"