Source: runner.js

var Burner = require('burner');
var Mover = require('./items/mover');
var Oscillator = require('./items/oscillator');
var Particle = require('./items/particle');
var Utils = require('burner').Utils;
var Vector = require('burner').Vector;
var SimplexNoise = require('quietriot');
var Easing = require('./easing');
var Mask = require('./mask');

/**
 * Creates a new Runner.
 * @param {Object} base A map of properties describing the base of the tornado.
 * @param {Object} debris A map of properties describing the debris at the base of the tornado.
 * @param {Object} spine A map of properties describing the tornado's spine.
 * @param {Object} shell A map of properties describing the tornado's shell (funnel).
 * @constructor
 */
function Runner(base, opt_debris, opt_spine, opt_shell) {
  if (!base) {
    throw new Error('Runner requires a \'base\' parameter.');
  }
  this.base = base;
  this.debris = opt_debris || null;
  this.spine = opt_spine || null;
  this.shell = opt_shell || null;
}

/**
 * Holds a Perlin noise value.
 * @type {number}
 * @memberof Runner
 */
Runner.noise = 0;

/**
 * Initializes an instance of Runner.
 * @param {Object} [opt_options=] A map of initial world properties.
 * @param {Object} [opt_options.el = document.body] World's DOM object.
 * @param {number} [opt_options.width = 800] World width in pixels.
 * @param {number} [opt_options.height = 600] World height in pixels.
 * @param {number} [opt_options.borderWidth = 1] World border widthin pixels.
 * @param {string} [opt_options.borderStyle = 'solid'] World border style.
 * @param {Object} [opt_options.borderColor = 0, 0, 0] World border color.
 * @memberof Runner
 */
Runner.prototype.init = function(opt_options) {

  var options = opt_options || {};

  Burner.System.Classes = {
    Mover: Mover,
    Oscillator: Oscillator,
    Particle: Particle
  };
  Burner.System.setup(this._setupCallback.bind(this, options));
  Burner.System.clock = 10000; // advance the clock so we start deeper in the noise space
  Burner.System.loop(this._getNoise.bind(this));
};

/**
 * Sets up the world and items.
 * @param {Object} options World options.
 * @memberof Runner
 * @private
 */
Runner.prototype._setupCallback = function(options) {

  var radionado = options.radionado;

  var world = Burner.System.add('World', {
    el: options.el || document.body,
    color: options.color || [40, 40, 40],
    width: options.width || 800,
    height: options.height || 600,
    borderWidth: options.borderWidth || 1,
    borderStyle: options.borderStyle || 'solid',
    borderColor: options.borderColor || [0, 0, 0],
    gravity: new Vector(),
    c: 0
  });

  // BASE
  this.base.configure(world);
  var myBase = Burner.System.add('Oscillator', this.base);

  // DEBRIS
  if (this.debris) {
    this.debris.configure({
      parent: myBase
    });
    Burner.System.add('Mover', this.debris);
  }

  // SPINE
  if (this.spine) {
    for (var i = 0, max = Math.floor(world.height / this.spine.density); i < max; i++) {

      var ease = Easing[this.spine.easing].call(null, i, 0, 1, max - 1);

      // joints
      var joint = Burner.System.add('Mover', {
        parent: myBase,
        offsetDistance: ease * world.height,
        offsetAngle: 270,
        opacity: this.spine.opacity,
        afterStep: this._jointAfterStep
      });
      joint.index = i;
      joint.offsetFromAxis = this.spine.offsetFromAxis;

      // use Perlin noise to generate the parent node's offset from the funnel's y-axis
      // use easing so the effect is amplified
      joint.offsetFromCenter = Easing.easeInSine(i, 0, 1, max - 1) *
          SimplexNoise.noise(i * 0.1, 0) * 20;

      // pillows
      if (this.shell) {
        var easeShellShape = Easing[this.shell.easing].call(null, i, 0, 1, max - 1);

        var colorNoise = Math.floor(Utils.map(SimplexNoise.noise(i * 0.05, 0),
            -1, 1, this.shell.colorMin, this.shell.colorMax));

        if (radionado) {
          for (var j = 0; j < (i + 1) * 1; j++) {

            Burner.Item.baseElement = 'input';
            Burner.Item.baseElementAttributes = {
              type: 'radio'
            };

            var rand = Utils.getRandomNumber;
            var osc = Burner.System.add('Oscillator', {
              perlin: false,
              parent: joint,
              blur: easeShellShape,
              color: [colorNoise, colorNoise, colorNoise],
              opacity: 1,
              scale: easeShellShape * 1,
              amplitude: new Vector(Utils.map(easeShellShape, 0, 1, 1, rand(50, 200)),
                  Utils.map(easeShellShape, 0, 1, 1, rand(10, 64))),
              acceleration: new Burner.Vector(2 / (i + 1), 0.05),
              aVelocity: new Burner.Vector(rand(0, 40), rand(0, 40))
            });
            if (rand(1, 2) === 1) {
              osc.el.checked = 'checked';
            }
          }
        } else {
          Burner.System.add('Oscillator', {
            width: this.shell.width,
            height: this.shell.height,
            parent: joint,
            opacity: this.shell.opacity,
            color: [colorNoise, colorNoise, colorNoise],
            boxShadowBlur: (easeShellShape * this.shell.blur) + this.shell.minFunnelWidth,
            boxShadowSpread: (easeShellShape * this.shell.spread) + this.shell.minFunnelWidth,
            boxShadowColor: [colorNoise, colorNoise, colorNoise],
            perlin: false,
            amplitude: new Vector((2 - easeShellShape) * 1, 0),
            acceleration: new Vector(1 / (i + 1), 0)
          });
        }
      }
    }
  }
};

/**
 * Called at the end of the joints' step function.
 * @memberof Runner
 * @private
 */
Runner.prototype._jointAfterStep = function() {
  if (this.offsetFromAxis) {
    var offset = this.index * this.offsetFromCenter * Runner.noise;
    this.location.x = this.location.x + (offset);
  }
};

/**
 * Called at the end of each animation frame.
 * @memberof Runner
 * @private
 */
Runner.prototype._getNoise = function() {
  Runner.noise = SimplexNoise.noise(Burner.System.clock * 0.0001, 0);
};

module.exports = Runner;