2013-09-28

Canvas plasma particles

Intro

One of the things I love the most about the HTML5 canvas are particle systems. For this post I prepared a small but good looking example. One of the colleagues from work even thought that I used some kind of graphical framework to do this and couldn't believe it's just plain old javascript.

The Particle definition

 var Particle = function (ind) {
  // remember the index inside array
  // faster performance on collisions
  this.ind = ind;

  // initial position
  this.x = randomMax(canvas.width);
  this.y = randomMax(canvas.height);

  // random direction vectors
  this.dy = -5 + randomMax(10);
  this.dx = -5 + randomMax(10);

  // radius (small flash in the beginning)
  this.r = randomMax(radiusmax);

  // give it a random color from the set
  this.color = colorSet[Math.floor(Math.random() * colorSet.length)];
 };

Particle draw method

 // reduce the size of the particle down to the minimal size
 // using log function - looks nicer and more organic
 this.r = this.r > miParticleSize ?
  flashfactor * (Math.log(this.r) / Math.LN10)
  : miParticleSize;

 // adjust particle position
 this.y += this.dy;
 this.x += this.dx;

 // check for collision with other particles
 // only on same color
 for (var i = this.ind + 1; i < particleSystem.particles.length; i++) {
  if (distance(this, particleSystem.particles[i]) < criticalDistance 
  && this.color === particleSystem.particles[i].color) {
   this.r = radiusmax;
   particleSystem.particles[i].r = radiusmax;
  }
 }

 // if the particle is outside of the canvas
 // or moving vectors are both 0
 if (this.x < 0 || this.x > canvas.width 
  || this.y < 0 || this.y > canvas.height
  || (this.dy === 0 && this.dx === 0)) {

  // initialize the particle again
  this.x = randomMax(canvas.width);
  this.y = randomMax(canvas.height);
  this.dy = -5 + randomMax(10);
  this.dx = -5 + randomMax(10);
 }

 ctx.beginPath();

 // this is the part that makes people thing it's a framework
 // simple radial gradient
 fillStyle = ctx.createRadialGradient(this.x, this.y, this.r * 0.001,
  this.x, this.y, this.r
 );
 fillStyle.addColorStop(0, this.color);
 fillStyle.addColorStop(1, particleBackground);

 // particle drawing code
 ctx.fillStyle = fillStyle;
 ctx.beginPath();
 ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
 ctx.fill();

Other than helper function and standard shims that's all there is to it.

Firefox issues

I couldn't find why firefox deals so bad with a larger number of particles, essentially everything freezes past 100 particles on firefox. Initialy I thought it's my draw method, but turned out that there's not much to be improved there.

Even when I removed the draw code and let the firefox "draw" the objects with an empty method the performace was still very poor (around 5-6 fps). If you perhaps figured it out or know about some issues please let me know!

And here's my example on:
Codepen

Pi to rad visualization

Intro

Remy Sharp organized a competition to celebrate JS Bin's 5th birthday. In short, there are animated gif images and the people have to write javascript / css to match the gifs. By the time I started trying to figure out the assignments most of the stuff was already solved by these wonderful, incredible and very smart people:

Analysis

An example that looked solvable to me was this one: When I saw it the first time I knew it's going to take a couple of tricks to get the timings right and since I wasn't completely satisfied with the way I solved the number Pi visualization I decided that I had to implement a state machine.

State machine

The usual state machine switches the states cycle by cycle. I modified the concept slightly so that each state checks and maintains the end conditions for the state but the switch happens with a timeout.

In it's end condition every state initiates the transition to the new state. Please not that the state will not be changed immediately and that the code for state change will be called multiple times. In order to avoid quirky behavior I used a simple flag named newState so that the timeout for the new state is actually set only once avoiding multiple timeouts.

An important feature of setTimeout function is that it doesn't maintain the this context so I had to apply the usual that equals this pattern.

 PiRad.prototype.changeState = function (newState, timeout) {
   var that = this;
   (function() {
     if (newState !== this.nextState) {
       setTimeout(function() {that.state = newState;}, timeout);
       this.nextState = newState;
     }
   })();
 };

Sticking the straight red line to the circle

Another challenge I had to face was transforming the straight line into a part of circumference. I gave this a lot of thought and came to a conclusion that this wood look best if I moved the end slowly to the destination on the circle and bind the beginning with a quadratic curve.

 // from stickRedLineToCircle state

 // move the dot to the destination
 this.movingCenter.x -= 2;
 this.movingCenter.y += 0.7;

 // from drawRedDotWithMovingCenterCurved function

 // end coordinates of a top
 var endx = ~~(cwidth / 2 + Math.cos(-1) * this.r);
 var endy = ~~(cheight / 2 + Math.sin(-1) * this.r);

 // calculate the distance 
 var distance = ~~(Math.sqrt(
  Math.pow(endx - this.movingCenter.x, 2) 
  +
  Math.pow(endy - this.movingCenter.y, 2))
 );

 // this factor came out from a lot of try and fail
 var factor = 1.12 + distance / 400;

 // Bézier control point
 var x = ~~(cwidth / 2 + Math.cos(-0.5) * this.r * factor);
 var y = ~~(cheight / 2 + Math.sin(-0.5) * this.r * factor);

 // drawing code
 ctx.lineWidth = 2;
 ctx.beginPath();
 ctx.moveTo(this.movingDot.x, this.movingDot.y);
 ctx.quadraticCurveTo(x, y, this.movingCenter.x, this.movingCenter.y);
 ctx.stroke();

And here's my example on:
JS Bin
Codepen

Number Pi visualization

Intro

Remy Sharp organized a competition to celebrate JS Bin's 5th birthday. By the time I started trying to figure out the assignments most of the stuff was already solved by these wonderful, incredible and very smart people:

Analysis

Another example that looked solvable to me was this one: This example is more about timings and matching colors and shapes while the math is pretty straight forward and relatively simple.

Drawing Markers

The markers on the animation all have pretty diamond shapes. To draw them I used this function:

  function drawMarker(x, y, color) {
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x - 10, y - 20);
    ctx.lineTo(x, y - 40);
    ctx.lineTo(x + 10, y - 20);
    ctx.closePath();
    ctx.fillStyle = color;
    ctx.fill();
  }

Drawing the Wheel

To spin and move the wheel around I used simple HTML5 canvas constructs translate and rotate. I just pretty much had to draw the wheel once and the rest was handled with those functions. The tricky parts are drawing the circumference in red color and leaving the trail.

It turned out this wasn't that complicated. The wheel will spin for an angle of 2 * Pi so in each animation iteration the angle property was increased, the wheel was rotated and the circumference was just drawn up to the angle value that the wheel traveled. At the same time the trail is straighten and calculated to that angle. It's much better to show it with the code:

  var maxAngle = angle >= 2 * Math.PI ? 2 * Math.PI : angle;

  ctx.translate(startx + 2 * angle * r, fixedy);
  ctx.rotate(angle);

  // red trail around the circle
  ctx.beginPath();
  ctx.arc(0, 0, 73, Math.PI / 2, 2 * Math.PI - maxAngle + Math.PI / 2);
  ctx.stroke();

  // red trail on the ground
  ctx.beginPath();
  ctx.moveTo(fixedx, fixedy);
  ctx.lineTo(startx + 2 * maxAngle * r, fixedy);
  ctx.stroke();
 
other than that it's all try, analyze, draw again loop.

And here's my example on:
JS Bin
Codepen

tan function visualization

Intro

Remy Sharp organized a competition to celebrate JS Bin's 5th birthday. All of the details can be found under this link. A lot was going on in my life at the time when the competition was opened so I didn't think I'll even catch an opportunity to solve some of the assignments that were posted. By the time I started trying to figure out the examples most of the stuff was already solved by these wonderful, incredible and very smart people:

Tan function

I started looking at the posted assignments and crosschecking if some of them were already solved. I found that this one still wasn't solved. Since it looked solvable to me I gave it a go. I've spent most of the evening analyzing the movement, drawing triangles, calculating the angles, making canvas experiments. At one point I came to a conclusion that the moving dot is nothing more than a representation of a tan function and felt a bit stupid because it should have been obvious!

Implementation

After I figured out it was a tan function the hard way, everything was pretty easy. All of the code is essentially here:

   // spin the circle around
   angle = (angle + dangle) % ( 2 * Math.PI);  

   // calculate the green dot position
   anglex = Math.cos(angle) * circlehalf;
   angley = Math.sin(angle) * circlehalf;
   drawdotx = circlex + anglex;
   drawdoty = circley + angley;
   
   // calculate the connecting points on the graph
   linex = circlehalf / Math.tan(angle);
   linex2 = circlehalf / Math.tan(angle + Math.PI);

The trick

The only tricky part was with drawing the graph. Because connecting the graph algorithmically caused the unexpected line to appear every time the dot switched from positive to negative infinity. But it turned out it could all be solved with a simple if statement:

     for (var i = cheight; i > 0; i-- ) {
      ctx.beginPath();

      ctx.moveTo(circlex - linex, cheight - hsplit - i - 1);

      linex = circlehalf / Math.tan(angle - i * dangle);

      ctx.lineTo(circlex - linex, cheight - hsplit - i);

      // avoids drawing a connection between +/- infinity
      if ((circlex - linex) < cwidth) {
        ctx.stroke();
      }

    }
 

And here's my example on:
JS Bin
Codepen