gary
2/11/2014 - 8:27 PM

A Pen by Gary Constable.

A Pen by Gary Constable.

Canvas particles experiments..

Just experimenting with some particles.

A Pen by Gary Constable on CodePen.

License.

<!--
just some more random particles and things..
-->
//utils and calcullations
var utils = {
  lineLength : function(x, y, x0, y0){
    return Math.sqrt((x -= x0) * x + (y -= y0) * y);
  }
}

//particle 
var particle = function(opts){
  this.width = opts.width || 1;
  this.height = opts.height || 1;
  this.x_pos = opts.x_pos || 0;
  this.y_pos = opts.y_pos || 0;
  this.x_speed = opts.x_speed || Math.random()*10;
  this.y_speed = opts.y_speed || Math.random()*10; 
  this.color = opts.color || 'rgb(255,255,255)';
  this.canvas_width = opts.canvas_width || 800;
  this.canvas_height = opts.canvas_width || 500; 
  this.draw = opts.draw || 'dot';
  this.birth_state = this;
}
  
//the old move fuctions
particle.prototype.animate = function(width, height){
  
  W = width;
  H = height;
  p = this;
  p.y_pos += p.y_speed;
  if(p.y_pos < -50) p.y_pos = H+50;
  if(p.y_pos > H+50) p.y_pos = -50;
  
  p.x_pos += p.x_speed;
  if(p.x_pos < -50) p.x_pos = W+50;
  if(p.x_pos > W+50) p.x_pos = -50;
}
 

/*
//move up and down within the mask / bounds area
particle.prototype.animate = function(){
  p = this;
  p.y_pos += p.y_speed;
}
*/

//revese the speed (and so the direction) of the particle
particle.prototype.reverseSpeed = function(){
  //make negative
  if(this.y_speed > 0){ 
    //(20) * (-1)
    this.y_speed = (this.y_speed) * (-1);
  }else{
    //(-20) * (-1)
    this.y_speed = (this.y_speed) * (-1); 
  }
}
   
//https://raw2.github.com/garyconstable/cloaked-octo-hipster/develop/anchor.js
var anchor = function(opts){
  this.radius = opts.radius || 0;
  this.x_pos = opts.x_pos || 0;
  this.y_pos = opts.y_pos || 0;
  this.draw = opts.draw || 'circle';
  this.lifespan = opts.draw || 2000;
  this.created = new Date().getTime();
  this.canvas_width = opts.canvas_width || 800;
  this.canvas_height = opts.canvas_width || 500;
  this.animate = function(){}
  this.birth = this;
}

//particle factory - were already thing expansions....
var particleFactory = function(){};
particleFactory.prototype.create = function ( options, type ) {
  switch(type){
    case 'anchor' : return new anchor( options );
    default: return new particle( options );
  }
};

//canvas
var Canvas = function(options){
  var self = this;
  this.surface = options.surface || "canvas";
  this.surface = document.getElementById(this.surface);
  this.width = this.surface.offsetWidth;
  this.height = this.surface.offsetHeight;
  this.bounds;
  this.non_bounds;
  this.mask = []; 
  var ctx = this.surface.getContext('2d');
  this.ctx = ctx; 
}

//clear canvar
Canvas.prototype.clear = function(){
  ctx = this.ctx;
  
  ctx.clearRect ( 0 , 0 , this.width , this.height );
}
  
//pass to correct drawing function
Canvas.prototype.render = function(opts){
  switch(opts.draw){
    case 'dot' : this.dot(opts); break;
    case 'circle' : this.circle(opts); break; 
  } 
}
  
//draw point
Canvas.prototype.dot = function( opts ){
  ctx.fillStyle = opts.color || 'rgb(255,255,255)';
  ctx.fillRect(opts.x_pos,opts.y_pos,opts.width,opts.height); 
}
  //draw circle
Canvas.prototype.circle = function( opts ){
  radius = opts.radius || 10;
  color = opts.color || 'rgb(255,0,0)';
  ctx.beginPath();
  ctx.fillStyle = color;
  ctx.arc(opts.x_pos, opts.y_pos, radius, 0, Math.PI*2, true); 
  ctx.closePath();
  ctx.fill(); 
}
  
Canvas.prototype.createBounds = function( opts ){
  ctx = this.ctx;
  // this is the unicode hex of a heart
  //str = "2764";
  str = "Hello...!";
  fontStr = "150pt Helvetica Arial, sans-serif";

  ctx.beginPath();
  ctx.font = fontStr;
  ctx.textAlign = "center";
  ctx.fillStyle = "#ffffff";
  //ctx.fillText(String.fromCharCode(parseInt(str, 16)),this.width/2 ,this.height - 50);
  ctx.fillText( str, this.width/2 ,this.height/2);
  ctx.closePath();

  this.mask = ctx.getImageData(0,0,this.width,this.height);
  area = [];
  non_area =[];

  // save all white pixels, these will be used as the bounds for the particles
  //http://falcon80.com/HTMLCanvas/PixelManipulation/getImageData.html
  //https://github.com/ottis/canvas-particles/blob/master/js/particle_system.js
  for (var i = 0; i < this.mask.data.length; i+=4) {

    if (this.mask.data[i] == 255 && this.mask.data[i+1] == 255 && this.mask.data[i+2] == 255 && this.mask.data[i+3] == 255) {
      area.push([this.toPosX(i,this.mask.width),this.toPosY(i,this.mask.width)]);
    }else{
      //non_area.push([this.toPosX(i,mask.width),this.toPosY(i,mask.width)]);
    }
  }
  this.bounds = area;
  this.non_bounds = non_area;
}
  
Canvas.prototype.toPosX = function(i,w) {
  return (i % (4 * w)) / 4;
}

Canvas.prototype.toPosY = function(i, w){
  return Math.floor(i / (4 * w));
}
  
//get the color of the specifed pixel
Canvas.prototype.pixelColor = function(objectx, objecty){  
  var imageData = this.mask
  var inputData = imageData.data; 
  var pData = (~~objectx + (~~objecty * this.mask.width)) * 4;
  var r = inputData[pData],
      g = inputData[pData + 1],
      b = inputData[pData + 2],
      a = inputData[pData + 3];

  return [r,g,b,a];
}

Canvas.prototype.addToMask = function(obj){
  
  
  var tc = Canvas;
  console.log(tc);
  
 
  var c = document.createElement('canvas');
  c.id = "tc";
  c.width = tc.width;
  c.height = tc.height;
   
  //this.canvas.bound.push(black pixel)
}

Canvas.prototype.removeFromMask = function(){
  //this.canvas.bound.remove(black pixel)
}


// i think it would be better to keep this seperate
var particleSystem = function(options){
  this.total_particles = 1000; 
  this.particles_drawn = 0;
  this.particles = [ ];
  this.nodes = [ ];
  this.nodes_pointer = [ ];
  this.canvas = options.canvas; 
  this.createParticles();
  this.self = this;
}
  
//create the particles
particleSystem.prototype.createParticles = function(){  
    
    pf = new particleFactory(); 
    w = this.canvas.width;
    h = this.canvas.height;
    x = 0;
    y = 0; 
  
  /*
  // create the particles, add base 2 number, higher = less particles
  for(var i=0; i<this.canvas.bounds.length;i+=30){          
         
    this.particles.push( pf.create({ 
      x_pos: this.canvas.bounds[i][0],
      y_pos: this.canvas.bounds[i][1],
      color: 'rgb(0,92,92)',
      width: 3,
      height: 3,
      y_speed: Math.floor(Math.random() * 5) + 1
    }) );
    this.particles_drawn++;
  } 
  */
  
  // create the particles, add base 2 number, higher = less particles
  for(var i=0; i<this.total_particles; i++){          
    
    obj = pf.create({ 
      x_pos: this.canvas.width / 2,
      y_pos: (this.canvas.height /  8) * 7,
      color: 'rgb(0,92,92)',
      width: 6,
      height: 6,
      //y_speed: -5,
      //y_speed: Math.floor(Math.random() * 5) + 1
    })
    
    //random num ->
    num = Math.floor(Math.random() * 100) + 1;
     
    
    //x speed 
    if(num < 51){
      obj.x_speed = -(Math.floor(Math.random() * 5) + 1);
    }
    
    if(num > 50){
      obj.x_speed = (Math.floor(Math.random() * 5) + 1); 
    }
    
    
    //y speed 
    if(num < 51){
      obj.y_speed = -(Math.floor(Math.random() * 5) + 1);
    }
    
    if(num > 50){
      obj.y_speed = (Math.floor(Math.random() * 5) + 1); 
    }
    
    
    
    
    
    
    
    
    
     
    this.particles.push( obj );
    this.particles_drawn++;
  } 
  
}
    
//remove element from particle system array
particleSystem.prototype.remove = function(particle, index){
  if (typeof particle.lifespan != "undefined") {
    if( (new Date().getTime() - particle.created) > particle.lifespan){
      return this.crop(index, particle.created);
    }
  }
  return false;
}
    
//render loop clear canvas and redraw
particleSystem.prototype.render = function(self){  
  
  var i=0;
  
  this.canvas.clear();

  this.particles.forEach( function(particle) {   

    if( self.remove(particle, i) === false ){ 

      //change the particle in some way
      if (typeof particle.animate !== "undefined") {
        
        /*
        //get the pixel color 
        var pc = self.canvas.pixelColor(particle.x_pos, particle.y_pos);  
        
        
        //if its a black pixel 
        if(pc[0] == 0 && pc[1] == 0 && pc[2] == 0 && pc[3] == 0){
          try{
            particle.reverseSpeed();
          }catch(ex){} 
          //contiune in the same direction..
        }
        */
        
        //move the particle
        particle.animate( this.canvas.width, this.canvas.height ); 
      } 
      //render the particle
      Canvas.render(particle);  
    }//eo remove
    i++;
  });  
}
  
//remove element from particles array
particleSystem.prototype.crop = function(index, ts){
  //remove from the drawing array
  this.particles.splice(index, 1);
  this.particles_drawn--;
  //remove the node pointers to the node
  var i = this.nodes_pointer.indexOf(ts);
  if(i !== -1){
    this.nodes.splice(i, 1);
    this.nodes_pointer.splice(i, 1);
    //console.log(self.nodes);
  }
  return true;
}
  
//app 
var App = function(options){   
  //create a canvas and add to body
  var c = document.createElement('canvas');
  c.id = "canvas";
  c.width = 800;
  c.height = 500;
  c.style.border = "1px solid white";
  document.getElementsByTagName("body")[0].style.background = 'black';
  document.getElementsByTagName("body")[0].appendChild(c);
  
  //create canvas obj and mouselistener
  Canvas = new Canvas({ 'surface' : 'canvas' }); 
  //create the shape
  //Canvas.createBounds(); 
  //create the particle system obj
  this.ps = new particleSystem({'canvas' : Canvas})
  
  self = this;
}

//render loop
App.prototype.run = function(){
  self.ps.render(self.ps); 
  requestAnimationFrame( self.run ); 
}  

//animation frame 
window.requestAnimFrame = (function(){ 
  return  window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || 
    function( callback ){
    window.setTimeout(callback, 1000 / 60);
  };
})();




//init + animate + the go button..
var app = new App().run();