HTML5Canvas模拟飞机航班线路动画

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>HTML5Canvas模拟飞机航班线路动画</title>

<style>
*{margin:0;padding:0;}
canvas {
    background:#111;
    background-size:cover;
    display:block;
}
body{overflow: hidden;}
</style>

</head>
<body>

<div></div>

<script>
// queue to land
// number of docked or approaching planes
// change color of plane for modes
// make planes avoid planes
// adjustable settings
// add random wind factors

window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(a){window.setTimeout(a,1E3/60)}}();

$ = {};

$.util = {
  rand: function( min, max ) {
    return Math.random() * ( max - min ) + min;
  },
  randInt: function( min, max ) {
    return Math.floor( Math.random() * ( max - min + 1 ) ) + min;
  },
  norm: function( val, min, max ) {
        return ( val - min ) / ( max - min );
      },
  lerp: function( norm, min, max ) {
    return ( max - min ) * norm + min;
  },
      map: function( val, sMin, sMax, dMin, dMax ) {
        return $.util.lerp( $.util.norm( val, sMin, sMax), dMin, dMax );
    },
  clamp: function( val, min, max ) {
        return Math.min( Math.max( val, Math.min( min, max ) ), Math.max( min, max ) );
    },
      distance: function( p1, p2 ) {
            var dx = p1.x - p2.x,
                    dy = p1.y - p2.y;
            return Math.sqrt( dx * dx + dy * dy );
      },
      angle: function( p1, p2 ) {
            return Math.atan2( p1.y - p2.y, p1.x - p2.x );
      },
  inRange: function( val, min, max ) {
            return val >= Math.min( min, max ) && val <= Math.max( min, max );
      },
  pointInRect: function( x, y, rect ) {
            return $.util.inRange( x, rect.x, rect.x + rect.width ) &&
               $.util.inRange( y, rect.y, rect.y + rect.height );
      },
      pointInArc: function( p, a ) {
            return distance( p, a ) <= a.radius;
      },
      setProps: function( obj, props ) {
            for( var k in props ) {
                  obj[ k ] = props[ k ];
            }
      },
  multicurve: function( points, ctx ) {
            var p0, p1, midx, midy;
    ctx.moveTo(points[0].x, points[0].y);
    for(var i = 1; i < points.length - 2; i += 1) {
      p0 = points[i];
      p1 = points[i + 1];
      midx = (p0.x + p1.x) / 2;
      midy = (p0.y + p1.y) / 2;
      ctx.quadraticCurveTo(p0.x, p0.y, midx, midy);
    }
    p0 = points[points.length - 2];
    p1 = points[points.length - 1];
    ctx.quadraticCurveTo(p0.x, p0.y, p1.x, p1.y);
  }
};

$.init = function() {
  // setup
  $.c = document.createElement( 'canvas' );
  $.ctx = $.c.getContext( '2d' );
  document.body.appendChild( $.c );
  
  // collections
  $.ports = [];
  $.planes = [];  
  
  // events
  window.addEventListener( 'resize', $.reset, false );
  window.addEventListener( 'click', $.reset, false );
  $.reset();
  $.step();
};

$.reset = function() {
  // dimensions
  $.cw = $.c.width = window.innerWidth;
  $.ch = $.c.height = window.innerHeight;
  $.dimAvg = ( $.cw + $.ch ) / 2;
  
  // type / font
  $.ctx.textAlign = 'center';
  $.ctx.textBaseline = 'middle';
  $.ctx.font = '16px monospace';
  
  // options / settings
  $.opt = {};
  $.opt.portCount = 6;
  $.opt.planeCount = 80;
  $.opt.portSpacingDist = $.dimAvg / $.opt.portCount;
  $.opt.holdingDist = 5;
  $.opt.approachDist = 80;
  $.opt.planeDist = 20;  
  $.opt.pathSpacing = 15;
  $.opt.pathCount = 40;
  $.opt.avoidRadius = 30;
  $.opt.avoidMult = 0.025;
  
  // collections
  $.ports.length = 0;
  $.planes.length = 0;
 
  // delta
  $.lt = Date.now();
  $.dt = 1;
  $.et = 0;
  $.tick = 0;
  
  // setup ports
  for( var i = 0; i < $.opt.portCount; i++ ) {
    $.ports.push( new $.Port() );
  }
  
  // setup planes
  for( var i = 0; i < $.opt.planeCount; i++ ) {
    $.planes.push( new $.Plane() );
  }  
};

$.Port = function() {
  this.x = $.util.rand( $.cw * 0.1, $.cw * 0.9 );
  this.y = $.util.rand( $.ch * 0.1, $.ch * 0.9 );
  while( !this.validSpacing() ) {
    this.x = $.util.rand( $.cw * 0.1, $.cw * 0.9 );
    this.y = $.util.rand( $.ch * 0.1, $.ch * 0.9 );
  }
};

$.Port.prototype.validSpacing = function() {
  var spaced = true,
      i = $.ports.length;
  while( i-- ) {
    var otherPort = $.ports[ i ];
    if( $.util.distance( otherPort, this ) < $.opt.portSpacingDist ) {
      spaced = false;
      break;
    }
  }
  return spaced;
};

$.Port.prototype.update = function( i ) {
  var j = $.planes.length;
  this.approachingCount = 0;
  while( j-- ) {
    var plane = $.planes[ j ];
    if( plane.destIndex == i && plane.approaching ) {
      this.approachingCount++;
    }
  }
};

$.Port.prototype.render = function( i ) {
  $.ctx.beginPath();  
  $.ctx.arc( this.x, this.y, 3 + ( this.approachingCount + 5 ), 0, Math.PI * 2 );
  $.ctx.fillStyle = 'hsla(120, 90%, 80%, ' + ( 0.35 + Math.sin( $.et / 20 ) * 0.2 ) + ')';
  $.ctx.fill();  
 
  $.ctx.fillStyle = '#fff';
  $.ctx.fillText( this.approachingCount, this.x, this.y - 30 );
};

$.Plane = function( opt ) {
  this.originIndex = $.util.randInt( 0, $.ports.length - 1 );
  this.origin = $.ports[ this.originIndex ];
  this.path = [];
  this.x = this.origin.x;
  this.y = this.origin.y;
  this.vx = $.util.rand( -0.35, 0.35 );
  this.vy = $.util.rand( -0.35, 0.35 );
  this.vmax = 1;
  this.accel = 0.01;
  this.decel = 0.96;
  this.angle = 0;
  this.approaching = false;
  this.holding = false;
  this.setDest();  
};

$.Plane.prototype.setDest = function() {
  if( this.destIndex != undefined ) {
    this.originIndex = this.destIndex;
    this.origin = $.ports[ this.originIndex ];
  }
  this.destIndex = $.util.randInt( 0, $.ports.length - 1 );
  while( this.destIndex == this.originIndex ) {
    this.destIndex = $.util.randInt( 0, $.ports.length - 1 );    
  }
  this.dest = $.ports[ this.destIndex ];
  this.approaching = false;
  this.holding = false;
}

$.Plane.prototype.update = function( i ) {
  this.ox = this.x;
  this.oy = this.y;
  if( $.tick % $.opt.pathSpacing == 0 ) {
    this.path.push( { x: this.x, y: this.y } );    
  }
  if( this.path.length > $.opt.pathCount ) {
    this.path.shift();
  }
  
  this.angle = $.util.angle( this.dest, this );
  this.speed = ( Math.abs( this.vx ) + Math.abs( this.vy ) ) / 2;
  
  if( !$.util.pointInRect( this.x, this.y, { x: 0, y: 0, width: $.cw, height: $.ch } ) ) {
    this.vx *= this.decel;
    this.vy *= this.decel;    
  }
  
  if( this.speed > 0.1 ) {
    if( $.util.distance( this.dest, this ) < $.opt.approachDist ) {
      this.vx *= this.decel;
      this.vy *= this.decel;    
      this.approaching = true;
   }
  }
  
  if( $.util.distance( this.dest, this ) < $.opt.holdingDist ) {
    this.holding = true;
    this.setDest();
  }
  
  // plane checks
  /*var j = i;
  while( j-- ) {
    var otherPlane = $.planes[ j ];
    if( $.util.distance( otherPlane, this ) < $.opt.avoidRadius ) {
      var angle = $.util.angle( otherPlane, this );
      var changer = ( ( Math.abs( this.vx ) + Math.abs( this.vy ) + Math.abs( otherPlane.vx ) + Math.abs( otherPlane.vy ) ) / 4 ) * $.opt.avoidMult;
      this.vx -= Math.cos( angle ) * changer;
      this.vy -= Math.sin( angle ) * changer;
      otherPlane.vx += Math.cos( angle ) * changer;
      otherPlane.vy += Math.sin( angle ) * changer;
    }
  }*/
  
  this.vx += Math.cos( this.angle ) * this.accel;
  this.vy += Math.sin( this.angle ) * this.accel;
  if( this.speed > this.vmax ) {
    this.vx *= this.decel;
    this.vy *= this.decel;
  }
  
  this.x += this.vx * $.dt;
  this.y += this.vy * $.dt;
};

$.Plane.prototype.render = function( i ) {  
  if( this.approaching ) {
    $.ctx.strokeStyle = 'hsla(0, 80%, 50%, 1)';
  } else {
    $.ctx.strokeStyle = 'hsla(180, 80%, 50%, 1)'; 
  }
 
  $.ctx.beginPath();
  $.ctx.moveTo( this.x, this.y );
  var angle = $.util.angle( { x: this.ox, y: this.oy }, this );
  $.ctx.lineWidth = 2;
  $.ctx.lineTo( 
    this.x - Math.cos( angle ) * ( 3 + this.speed * 2 ), 
    this.y - Math.sin( angle ) * ( 3 + this.speed * 2 ) 
  );
  $.ctx.stroke();
  
  var pathLength = this.path.length;
  if( pathLength > 1) {
    $.ctx.strokeStyle = 'hsla(0, 0%, 100%, 0.15)';
    $.ctx.lineWidth = 1;
    $.ctx.beginPath();
    
    if( pathLength >= $.opt.pathCount ) {
      var angle = $.util.angle( this.path[ 1 ], this.path[ 0 ] ),
          dx = this.path[ 0 ].x - this.path[ 1 ].x,
          dy = this.path[ 0 ].y - this.path[ 1 ].y,
          dist = Math.sqrt( dx * dx + dy * dy ),
          x = this.path[ 0 ].x + Math.cos( angle ) * ( dist * ( ( $.tick % $.opt.pathSpacing ) / $.opt.pathSpacing ) ),
          y = this.path[ 0 ].y + Math.sin( angle ) * ( dist * ( ( $.tick % $.opt.pathSpacing ) / $.opt.pathSpacing ) );
    } else {
      var x = this.path[ 0 ].x,
          y = this.path[ 0 ].y
    }
        
    $.ctx.moveTo( x, y );
    for( var i = 1; i < pathLength; i++ ) {
      var point = this.path[ i ];
      $.ctx.lineTo( point.x, point.y );  
    }
    $.ctx.lineTo( this.x, this.y );
    $.ctx.stroke();
  }
};

$.step = function() {
  requestAnimFrame( $.step );
  
  // clear
  $.ctx.globalCompositeOperation = 'destination-out';
  $.ctx.fillStyle = 'hsla(0, 0%, 0%, 1)';
  $.ctx.fillRect( 0, 0, $.cw, $.ch );
  $.ctx.globalCompositeOperation = 'lighter';
  
  // collections
  var i;
  i = $.ports.length; while( i-- ) { $.ports[ i ].update( i ) }
  i = $.planes.length; while( i-- ) { $.planes[ i ].update( i ) }
  i = $.ports.length; while( i-- ) { $.ports[ i ].render( i ) }
  i = $.planes.length; while( i-- ) { $.planes[ i ].render( i ) }
  
  // delta
  var now = Date.now();
    $.dt = $.util.clamp( ( now - $.lt ) / ( 1000 / 60 ), 0.001, 10 );
    $.lt = now;
  $.et += $.dt;
  $.tick++;
};

$.init();
</script>

</body>
</html>


HTML5Canvas模拟飞机航班线路动画

评论者:[[ schemeInfo.user.username ]]

评论内容:[[ schemeInfo.pbody ]]

评论时间:[[ schemeInfo.ptime ]]





发表你的评论:

提交评论
上一篇:
下一篇: