10

Link to my code pen here

I would like opinions on how i could make the canvas dots move in a more fluid, liquid like way.

I've tried limiting the direction for a certain number of renders (draw()), which has improved it a little! But it still lacks fluidity, coming across as rigid and 'hard-coded'.

This switch statement is the way direction is set, either passed a random integer or the previous direction.

 switch (direction) {
                    case 1:
                      star.x--;
                      break;
                    case 2:
                      star.x++;
                      break;
                    case 3:
                      star.y--;
                      break;
                    case 4:
                      star.y++;
                      break;
                    case 5:
                      star.y--;
                      star.x--;
                      break;
                    case 6:
                      star.y--;
                      star.x++;
                      break;
                    case 7:
                      star.y++;
                      star.x++;
                      break;
                    case 8:
                      star.y++;
                      star.x--;
                      break;
}

Thanks!

rssfrncs
  • 991
  • 4
  • 19
  • 2
    I would use an easing function to decelerate / accelerate the particles when they change direction. – Eduardo Jun 02 '15 at 20:04
  • I think the main thing that makes it look "unnatural" to me is that they are all making large direction changes at basically the exact same time. Of course, I'm imagining them as bugs so.. – Alex W Jun 02 '15 at 21:06

1 Answers1

11

You can do this by:

  • Generating a cardinal spline based on random line points
  • Walk along that line and plot a star on current position

Why cardinal spline? A cardinal spline will generate a smooth line between a set of points. If also has a tension value. By exaggerating the tension value (i.e. outside the normal [0,1] range) it will produce curly lines instead.

snapshot

// draw example lines
var ctx = c.getContext("2d"), p = [0,100, 25,40, 50,70, 75,50, 100,80, 125,32, 150,100, 175,60];
ctx.font = "bold 16px sans-serif";

render(0); render(0.5); render(-2);
ctx.setTransform(1,0,0,1,0, 110);

render(-2, 3, "Segments: 3"); render(-2, 9, "Segments: 9"); render(-2, 25, "Segments: 25");

function render(t, seg, txt) {
  ctx.beginPath();
  ctx.moveTo(0, 100);
  ctx.curve(p, t, seg || 20);
  ctx.stroke();
  ctx.fillText(txt ? txt : (!t ? "Plain poly-line" : "Cardinal, tension: " + t), 0, 20);
  ctx.translate(200,0);
}

We can take advantage of this property and plot a point along such a line to produce a liquid-ish movement. The movement can be refined by de/increase the segment resolution between each line in the spline which will affect the smoothness as well as the speed.

Other advantages is that we don't have to calculate anything in the animation itself (there will be an initial "peak" when setting up the points (cache) though), we just update the array pointer and render. And the distribution will be fairly even as the points are forces along somewhat evenly distributed (invisible) paths.

How to implement it can vary of course - here is one example of an approach:

Example implementation

Define a star object (it should really be prototyped but for the sake of demo):

function Star(ctx, xseg) {

    var points = [],              // holds points for cardinal points
        cPos = 0, oPos = -1,      // positions in line
        len,
        w = ctx.canvas.width,
        x = -10, y = -10;

    // this iterates and loop the point list            
    this.animate = function() {
        cPos++;

        if (cPos > len - 2) {
            cPos = 0; oPos = -1;
        }

        var pos = cPos * 2;
        x = points[pos];
        y = points[pos + 1];

        drawStar();
    }

    // render some star
    function drawStar() {
        ctx.rect(x, y, 2, 2);
    }

    // This generate a set of random points, then converts those into
    // points for a cardinal spline (linked as script).
    function generatePath() {
        var w = ctx.canvas.width,
            h = ctx.canvas.height,
            numOfSeg = 20,
            dh = h / numOfSeg,
            i= 0, l, x, y;

        for(; i<= numOfSeg; i++) {          
            x = xseg + w / 8 * Math.random();
            y = h - (i * dh + ((dh / 2) * Math.random() - (dh / 4)));
            points.push(x, y);
        }

        points = curve(points, -2, 200 * Math.random() + 100);
        l = points.length;

        // adjust for out of edges
        for(i = 0; i < l; i += 2) if (points[i] > w) points[i] -= w;
        len = points.length / 2;
        cPos = parseInt(len * Math.random());
    }   
    generatePath();
}

Full example

function Star(ctx, xseg) {

  var points = [],              // holds points for cardinal points
      cPos = 0, oPos = -1,      // positions in line
      len,
      w = ctx.canvas.width,
      x = -10, y = -10;

  this.animate = function() {
    cPos++;
    if (cPos > len - 2) {
      cPos = 0;  oPos = -1;
    }

    var pos = cPos * 2;
    x = points[pos];
    y = points[pos + 1];

    drawStar();
  };

  function drawStar() {
    ctx.moveTo(x + 2, y);
    ctx.arc(x, y, 2, 0, Math.PI*2);
  }

  function generatePath() {
    var w = ctx.canvas.width,
        h = ctx.canvas.height,
        numOfSeg = 20,
        dh = h / numOfSeg,
        i= 0, l, x, y;

    for(; i <= numOfSeg; i++) {         
      x = xseg + w / 8 * Math.random();
      y = h - (i * dh + ((dh / 2) * Math.random() - (dh / 4)));
      points.push(x, y);
    }

    points = getCurvePoints(points, -2, (400 * Math.random() + 200)|0);
    l = points.length;

    for(i = 0; i < l; i += 2) if (points[i] > w) points[i] -= w;
    len = points.length / 2;
    cPos = (len * Math.random())|0;

  }
  generatePath();
}

// Main code
var canvas = document.querySelector("canvas"),
    ctx = canvas.getContext("2d"),
    stars = [],
    numOfStars = 100,
    segs = canvas.width / numOfStars, 
    i = 0,
    throttle = 0,
    delay = 2;

// create stars
for(; i < numOfStars; i++) stars.push(new Star(ctx, i * segs - segs));

ctx.fillStyle = "#fff";
ctx.shadowColor ="#fff";
ctx.shadowBlur = 7;

// ANIMATE
(function animate() {      
  if (!throttle) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    for(var i = 0; i < stars.length; i++) stars[i].animate();
    ctx.fill(); 
  }

  throttle++;
  if (throttle === delay) throttle = 0;

  requestAnimationFrame(animate);
})();

See code for cardinal spline implementation in this answer.

Another approach is to use particles and variate the velocity using for example a sinus function. For this to work optimally you may need a velocity grid instead to affect a particle based on location. The grid can have random directions and velocities.

Community
  • 1
  • 1
  • why do the dots seem to "flap"? (like little butterflies) – Woodrow Barlow Jun 02 '15 at 20:57
  • @WoodrowBarlow it's actually a left-over from an earlier code this is based on. I had that going to simulate gold flakes at the time. I am thinking of removing that feature though to simplify the code. –  Jun 02 '15 at 20:58
  • Shimmer effect removed, code can be found in [history](http://stackoverflow.com/revisions/30606133/3). –  Jun 02 '15 at 21:03
  • Thanks for your detailed answer, i will check this out later and let your know how it goes! – rssfrncs Jun 03 '15 at 06:17