Source: core/Camera.js

/**
 * @author Inateno / http://inateno.com / http://dreamirl.com
 */

/**
 * @constructor Camera
 * @class the eyes used to see in your scenes :)<br>
 * you have to append it to a Render and have to give it a scene to look in<br>
 * you can move your camera inside a render as you want<br>
 * also you can make more than one camera in a Render or looking in a scene<br>
 * <br><br>
 * example: if you want to make a mini-map, you can make a camera with big sizes (FHD), but little scale(0.2) 
 * and maybe override the render method to call custom rendering for mini-map<br>
 * then you got two camera, these two are looking at the same scene, and are in the same Render 
 * your "mini-map" camera is over the first carmera
 * <br><br>
 * example2: on a split-screen multiplayer game, you can make one camera by player, with input for each cameras
 * @example Game.camera = new DE.Camera( 1920, 1080, 0, 0, { "name": "mainGame", "backgroundColor": "green" } );
 * @param {Int} width initial width inside the render
 * @param {Int} height initial height inside the render
 * @param {Int} x position in the Render
 * @param {Int} y position in the Render
 * @param {Object} [params] optional parameters
 * @property {String} [name="noname"] name your camera
 * @property {String} [tag="none"] assign tags if it's can be useful for you
 * @property {Scene} [scene=null] you can give a scene on creation, or later
**/
define( [ 'DE.CONFIG', 'DE.Sizes', 'DE.Vector2', 'DE.CanvasBuffer', 'DE.CollisionSystem', 'DE.ImageManager', 'DE.Event' ],
function( CONFIG, Sizes, Vector2, CanvasBuffer, CollisionSystem, ImageManager, Event )
{
  function Camera( width, height, x, y, params )
  {
    params = params || {};
    
    this.name   = params.name || "";
    this.tag    = params.tag || "";
    this.scene  = params.scene || null;
    this.gui    = undefined;
    
    this.renderSizes = new Sizes( width, height, params.scale || params.scaleX || 1
                                , params.scale || params.scaleY || 1 );
    this.fieldSizes = new Sizes( width, height, params.scale || params.scaleX || 1
                                , params.scale || params.scaleY || 1 );
    // position in the render (canvas)
    this.renderPosition= new Vector2( x + width * 0.5, y + height * 0.5, params.z || -10 );
    this.savedPosition = new Vector2( x + width * 0.5, y + height * 0.5, params.z || -10 );
    
    // position inside the sceneworld
    this.scenePosition = new Vector2( params.realx || x, params.realy || y
                                    , params.realz || params.z || -10 );
    this.limits = {
      minX: params.minX != undefined ? params.minX : undefined
      ,maxX: params.maxX != undefined ? params.maxX : undefined
      ,minY: params.minY != undefined ? params.minY : undefined
      ,maxY: params.maxY != undefined ? params.maxY : undefined
    };
    
    this.opacity = params.opacity || 1;
    this.backgroundColor = params.backgroundColor || null;
    this.backgroundImage = params.backgroundImage || null;
    
    this.cameras    = new Array();
    this.maxCameras = 0;
    
    this.freeze  = false;
    this.sleep   = false;
    
    this.startX  = 0
    this.startY  = 0
    this._buffer = new CanvasBuffer( this.renderSizes.width, this.renderSizes.height );
    // this._gameObjects = []; // sotre last active objects 
    this._visibleGameObjects = [];
    
    this.indexMouseOver  = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; // 20 touches max ?
    this.lastPointersPos = {};
    /****
     * store between two event types if you asked to prevent some events
     * @private
     */
    this._propagationEvent = [ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ];
    
    // add Events components on camera
    Event.addEventComponents( this );
  }
  
  /****
   * getBuffer@void
    previously the _buffer was private - let to maximise retro-version
    (but I'll disapear, don't user it)
    you shouldn't get this buffer and do action on, but it's usefull for the engine
   */
  Camera.prototype.getBuffer = function(){ return this._buffer; }
  
  /****
   * render@void( ctx@CanvasContext, drawRatio@float, physicRatio@float )
   renderise the camera inside the given ctx
   you shouldn't change this method
   */
  Camera.prototype.render = function( ctx, drawRatio, physicRatio )
  {
    if ( this.sleep )
    {
      return;
    }
    
    var _buffer = this._buffer;
    if ( !this.freeze )
    {
      _buffer.ctx.globalAlpha  = this.opacity;
      
      if ( this.backgroundColor != null )
      {
        _buffer.ctx.fillStyle = this.backgroundColor;
        _buffer.ctx.fillRect( 0, 0, this.renderSizes.width, this.renderSizes.height );
      }
      if ( this.backgroundImage != null )
      {
        _buffer.ctx.drawImage( ImageManager.images[ this.backgroundImage ], 0, 0, this.renderSizes.width, this.renderSizes.height );
      }
      
      _buffer.ctx.save();
      // renderize here game objects
      if ( this.scene )
      {
        var _gameObjects /*= this._gameObjects*/ = this.scene.gameObjects;
        this._visibleGameObjects = [];
        for ( var i = 0, t = _gameObjects.length, g,ratioz; i < t; i++ )
        {
          g = _gameObjects[ i ];
          if ( g && g.enable
            && g.position.z > this.scenePosition.z
            && ( g.position.x + g.biggerOffset.width >= this.scenePosition.x
              && g.position.x - g.biggerOffset.width <= this.scenePosition.x + this.fieldSizes.width )
            && ( g.position.y + g.biggerOffset.height >= this.scenePosition.y
              && g.position.y - g.biggerOffset.height <= this.scenePosition.y + this.fieldSizes.height )
          )
          {
            this._visibleGameObjects.push( g );
            // distance from 10 between object and camera is ratio 1
            ratioz = 10 / ( g.position.z - this.scenePosition.z );
            g.render( _buffer.ctx, physicRatio, ratioz, this.scenePosition, this.renderSizes );
          }
        }
      }
      else
      {
        _buffer.ctx.textAlign = "center";
        _buffer.ctx.fillStyle = "white";
        _buffer.ctx.fillText( "No scene affiliated :(", this.renderSizes.width * 0.5, this.renderSizes.height * 0.5 );
      }
      
      _buffer.ctx.restore();
      _buffer.ctx.globalAlpha = this.opacity;
      
      // debuging - display name and stroke the camera
      if ( CONFIG.DEBUG )
      {
        _buffer.ctx.fillStyle = "white";
        _buffer.ctx.textAlign = "left";
        _buffer.ctx.fillText( "Camera " + this.name, 10, 20);
        
        _buffer.ctx.strokeStyle = "red";
        _buffer.ctx.strokeRect( 0, 0, this.renderSizes.width >> 0
                                   , this.renderSizes.height >> 0 );
        
        _buffer.ctx.fillStyle = "yellow";
        _buffer.ctx.fillRect( this.renderSizes.width - 10, this.renderSizes.height - 10, 10, 10 );
        _buffer.ctx.fillRect( this.renderSizes.width - 10, 0, 10, 10 );
        _buffer.ctx.fillRect( 0, this.renderSizes.height - 10, 10, 10 );
        _buffer.ctx.fillRect( 0, 0, 10, 10 );
        
        _buffer.ctx.fillRect( this.renderSizes.width * 0.5
                            ,this.renderSizes.height * 0.5
                            , 20, 5 );
        _buffer.ctx.fillRect( this.renderSizes.width * 0.5
                            ,this.renderSizes.height * 0.5
                            , 5, 20 );
      }
      
      this.getFocus();
      this.checkLimits();
      this.applyShake();
    }
    
    ctx.translate( this.renderPosition.x * drawRatio >> 0
                  , this.renderPosition.y * drawRatio >> 0 );
    ctx.rotate( this.renderPosition.rotation );
    
    ctx.drawImage( _buffer.canvas
          , -this.renderSizes.width * this.renderSizes.scaleX * drawRatio * 0.5 >> 0
          , -this.renderSizes.height * this.renderSizes.scaleY * drawRatio * 0.5 >> 0
          , this.renderSizes.width * this.renderSizes.scaleX * drawRatio >> 0
          , this.renderSizes.height * this.renderSizes.scaleY * drawRatio >> 0 );
    
    // the GUI will totally change with DOM components, try to not use it
    // prefer using GameObjects in your scene
    if ( this.gui )
    {
      this.gui.render( ctx, this.renderPosition, this.renderSizes, drawRatio, physicRatio );
    }
    
    ctx.rotate( -this.renderPosition.rotation );
    ctx.translate( -this.renderPosition.x * drawRatio >> 0
                  , -this.renderPosition.y * drawRatio >> 0 );
    
  }
  
  /****
   * check camera limits
   */
  Camera.prototype.checkLimits = function()
  {
    var limits = this.limits;
    if ( limits.minX != undefined && this.scenePosition.x < limits.minX )
      this.scenePosition.x = limits.minX;
    else if ( limits.maxX != undefined && this.scenePosition.x + this.renderSizes.width > limits.maxX )
      this.scenePosition.x = limits.maxX - this.renderSizes.width;
    if ( limits.minY != undefined && this.scenePosition.y < limits.minY )
      this.scenePosition.y = limits.minY;
    else if ( limits.maxY != undefined && this.scenePosition.y + this.renderSizes.height > limits.maxY )
      this.scenePosition.y = limits.maxY - this.renderSizes.height;
  }
  
  /***
   * screenChangedSizeIndex@void( physicRatio@float )
   when the engine, or you change quality
   TODO - remove newSizes if this isn't used / useful
   */
  Camera.prototype.screenChangedSizeIndex = function( physicRatio, newSizes )
  {
    this.renderSizes.width  = this.fieldSizes.width * physicRatio >> 0;
    this.renderSizes.height = this.fieldSizes.height * physicRatio >> 0;
    this.renderPosition.x = this.savedPosition.x * physicRatio >> 0;
    this.renderPosition.y = this.savedPosition.y * physicRatio >> 0;
    this._buffer.canvas.width = this.renderSizes.width;
    this._buffer.canvas.height = this.renderSizes.height;
  }
  /****
   * add@void( camera@Camera )
   childs possible with camera, not often usefull but could be
   TODO - I think there is problems with this, I just tried it quickly
   */
  Camera.prototype.add = function( camera )
  {
    this.cameras.push( camera );
    ++this.maxCameras;
  }
  
  /****
   * remove@void( camera@Camera )
   remove a camera affilied in this one
   */
  Camera.prototype.remove = function( camera )
  {
    var pos = this.cameras.indexOf( camera );
    if ( pos == -1 )
    {
      CONFIG.debug.log( "%cRemove camera not found ", 1, "color:orange", camera );
      return;
    }
    
    this.cameras.splice( pos, 1 );
    this.maxCameras--;
  }
  
  /****
   * create a shake with given range
    you can only have one at a time
   */
  Camera.prototype.shake = function( xRange, yRange, duration )
  {
    this.shakeData = {
      "startedAt" : Date.now()
      ,"duration" : duration
      ,"xRange"   : xRange
      ,"yRange"   : yRange
      ,"prevX"    : this.shakeData ? this.shakeData.prevX : 0
      ,"prevY"    : this.shakeData ? this.shakeData.prevY : 0
    };
  }
  
  /****
   * make the shake happen
   */
  Camera.prototype.applyShake = function()
  {
    if ( !this.shakeData )
      return;
    
    var shake = this.shakeData;
    // restore previous shake
    this.scenePosition.x -= shake.prevX;
    this.scenePosition.y -= shake.prevY;
    if ( Date.now() - this.shakeData.startedAt > this.shakeData.duration )
    {
      delete this.shakeData;
      return;
    }
    
    shake.prevX = - ( Math.random() * shake.xRange ) + ( Math.random() * shake.xRange ) >> 0;
    shake.prevY = - ( Math.random() * shake.yRange ) + ( Math.random() * shake.yRange ) >> 0;
    
    this.scenePosition.x += shake.prevX;
    this.scenePosition.y += shake.prevY;
  }
  
  /****
   * focus@void( gameObject@GameObject, offsets@Vector2 )
    give a target to this camera, then camera will focus it until you changed or removed it
    you can lock independent axes
   */
  Camera.prototype.focus = function( gameObject, lock )
  {
    this.target = gameObject;
    this.focusLocks = lock;
  }
  
  /****
   * getFocus@void
    apply focus on target if there is one
   */
  Camera.prototype.getFocus = function()
  {
    if ( !this.target )
      return;
    var pos = this.target.getPos();
    var ratioz = ( Math.abs( this.scenePosition.z + pos.z ) - 10 ) * 0.1 + 1;
    if ( !this.focusLocks.x )
      this.scenePosition.x = pos.x - ( this.fieldSizes.width * 0.5 ) / ratioz;
    if ( !this.focusLocks.y )
      this.scenePosition.y = pos.y - ( this.fieldSizes.height * 0.5 ) / ratioz;
  }
  
  /****
   convertMousePos@MouseVector2( mouse@MouseVector2, physicRatio@float )
   * convert mouse pos with harmonics and ratio
   */
  Camera.prototype.convertMousePos = function( mouse, physicRatio )
  {
    var harmonics = this.renderPosition.getHarmonics();
    if ( harmonics.sin == 0 && harmonics.cos == 0 )
    {
      if ( this.renderSizes.scaleX != 1 || this.renderSizes.scaleY != 1 )
      {
        return { x: ( mouse.x / physicRatio - ( ( this.renderSizes.width - this.renderSizes.width * this.renderSizes.scaleX ) / 2 ) ) / this.renderSizes.scaleX >> 0
          , y: ( mouse.y / physicRatio - ( ( this.renderSizes.height - this.renderSizes.height * this.renderSizes.scaleY ) / 2 ) ) / this.renderSizes.scaleY >> 0
          , "index": mouse.index };
      }
      return { x: mouse.x / physicRatio >> 0
        , y: mouse.y / physicRatio >> 0
        , "index": mouse.index };
    }
    
    var x = ( this.renderSizes.width / 2 - mouse.x );
    var y = ( this.renderSizes.height / 2 - mouse.y );
    
    if ( this.renderSizes.scaleX != 1 || this.renderSizes.scaleY != 1 )
    {
      return {
        "x": ( ( -(x * harmonics.cos + y * harmonics.sin ) + this.renderSizes.width / 2 ) / physicRatio - ( ( this.renderSizes.width - this.renderSizes.width * this.renderSizes.scaleX ) / 2 ) ) / this.renderSizes.scaleX >> 0
        , "y": ( ( (x * harmonics.sin + y * -harmonics.cos ) + this.renderSizes.height / 2 ) / physicRatio - ( ( this.renderSizes.height - this.renderSizes.height * this.renderSizes.scaleY ) / 2 ) ) / this.renderSizes.scaleY >> 0
        , "index": mouse.index
        , "isDown": mouse.isDown
      };
    }
    return {
      "x": ( -(x * harmonics.cos + y * harmonics.sin ) + this.renderSizes.width / 2 ) / physicRatio >> 0
      , "y": ( (x * harmonics.sin + y * -harmonics.cos ) + this.renderSizes.height / 2 ) / physicRatio >> 0
      , "index": mouse.index, "isDown": mouse.isDown
    };
  }
  
  /***
  * custom events
   return true inside these functions if you want stop propagation (not propaging in GameObjects)
  */
  Camera.prototype.onMouseDown  = function(){};
  Camera.prototype.onMouseUp    = function(){};
  Camera.prototype.onMouseMove  = function(){};
  Camera.prototype.onMouseEnter = function(){};
  Camera.prototype.onMouseLeave = function(){};
  Camera.prototype.onMouseClick = function(){};
  
  /* last event, called after all
   very usefull when you want to do something only if you didn't click on anything (just stopPropagation and this won't be called)  */
  Camera.prototype.onLastMouseMove  = function(){};
  Camera.prototype.onLastMouseDown  = function(){};
  Camera.prototype.onLastMouseUp    = function(){};
  Camera.prototype.onLastMouseClick = function(){};
  
  /***
  * onMouseDown@void( mouse@MouseVector2, physicRatio@float )
  */
  Camera.prototype.oOnMouseDown = function( mouse, physicRatio )
  {
    mouse.isDown = true;
    mouse.date   = Date.now();
    this._propagationEvent[ mouse.index ] = mouse;
    this.mouseDetectorHandler( "Down", mouse, physicRatio );
  }
  
  /***
  * onMouseUp@void( mouse@MouseVector2, physicRatio@float )
  */
  Camera.prototype.oOnMouseUp = function( mouse, physicRatio )
  {
    if ( Date.now() < this._propagationEvent[ mouse.index ].date + CONFIG.CLICK_DELAY )
      this.mouseDetectorHandler( "Click", mouse, physicRatio );
    
    this.mouseDetectorHandler( "Up", mouse, physicRatio );
    this._propagationEvent[ mouse.index ] = {};
  }
  
  /***
   * onMouseMove@void( mouse@MouseVector2, physicRatio@float )
   */
  Camera.prototype.oOnMouseMove = function( mouse, physicRatio )
  {
    if ( !this._propagationEvent[ mouse.index ].index )
      this._propagationEvent[ mouse.index ] = mouse;
    
    this.lastPointersPos[ mouse.index ] = mouse;
    this.indexMouseOver[ mouse.index ]  = true;
    this.mouseDetectorHandler( "Move", mouse, physicRatio );
  }
  
  /****
   * onMouseEnter@void( mouse@MouseVector2, physicRatio@float )
   * when the mouse enter in the camera field
   this isn't called by the main mouse handler because it's a custom event over the native
   */
  Camera.prototype.oOnMouseEnter = function( mouse, physicRatio )
  {
    mouse = this.convertMousePos( mouse, physicRatio );
    this.onMouseEnter( mouse );
  }
  
  /****
   * onMouseLeave@void( mouse@MouseVector2, physicRatio@float )
   * when the mouse leave the camera field (same than previously)
   */
  Camera.prototype.oOnMouseLeave = function( mouse, physicRatio )
  {
    this.indexMouseOver[ mouse.index ] = false;
    mouse = this.convertMousePos( mouse, physicRatio );
    this.onMouseLeave( mouse );
  }
  
  /****
   * mouseDetectorHandler@bool( eventType@string, mouse@MouseVector2, physicRatio@float )
   main mouse handler
   */
  Camera.prototype.mouseDetectorHandler = function( eventType, mouse, physicRatio )
  {
    if ( this.freeze || this.sleep || this._propagationEvent[ mouse.index ][ "prevent" + eventType ] )
      return;
    
    mouse = this.convertMousePos( mouse, physicRatio );
    
    for ( var i in this.scene[ "onGlobalMouse" + eventType ] )
    {
      if ( this.scene[ "onGlobalMouse" + eventType ][ i ].enable )
        if ( this.scene[ "onGlobalMouse" + eventType ][ i ][ "onGlobalMouse" + eventType ]( mouse, this._propagationEvent[ mouse.index ] ) )
          return;
    }
    
    this.trigger( "mouse" + eventType, mouse );
    if ( this[ "onMouse" + eventType ]( mouse, this._propagationEvent[ mouse.index ] ) || mouse.stopPropagation )
      return;
    if ( this.gui && this.gui[ "onMouse" + eventType ]( mouse, this._propagationEvent[ mouse.index ] ) || mouse.stopPropagation )
      return;
    
    mouse.x += this.scenePosition.x;
    mouse.y += this.scenePosition.y;
    for ( var i = this._visibleGameObjects.length - 1, g; i >= 0; --i )
    {
      g = this._visibleGameObjects[ i ];
      
      if ( _gameObjectMouseEvent( eventType, g, mouse, this._propagationEvent[ mouse.index ] ) )
        return;
    }
    
    if ( !mouse.stopPropagation && !this._propagationEvent[ mouse.index ][ "preventLast" + eventType ] )
    {
      this.trigger( "onLastMouse" + eventType, mouse, this._propagationEvent[ mouse.index ] );
      this[ "onLastMouse" + eventType ]( mouse, this._propagationEvent[ mouse.index ] );
      
      for ( var i in this.scene[ "onLastGlobalMouse" + eventType ] )
      {
        if ( this.scene[ "onLastGlobalMouse" + eventType ][ i ].enable )
          this.scene[ "onLastGlobalMouse" + eventType ][ i ][ "onLastGlobalMouse" + eventType ]( mouse, this._propagationEvent[ mouse.index ] );
      }
    }
    
  }
  
  /****
   * _gameObjectMouseEvent@bool( eventType@string, g@GameObject, mouse@MouseVector2 )
   */
  function _gameObjectMouseEvent( eventType, g, mouse, propagationEvent )
  {
    if ( !g.enable )
      return false;
    
    if ( g.collider && ( g[ "onMouse" + eventType ] || g.onMouseLeave || g.onMouseEnter ) )
    {
      var wasOver = g.indexMouseOver[ mouse.index ];
      if ( CollisionSystem.checkCollisionWith( mouse, g.collider ) )
      {
        // mouseEnter event occurs here
        g.indexMouseOver[ mouse.index ] = true;
        if ( !wasOver && g.onMouseEnter && g.onMouseEnter( mouse, propagationEvent ) )
          return true;
        if ( g[ "onMouse" + eventType ] && g[ "onMouse" + eventType ]( mouse, propagationEvent ) )
          return true;
      }
      // no collision but was over last frame, there is a leave event
      else if ( wasOver && g.onMouseLeave )
      {
        g.indexMouseOver[ mouse.index ] = false;
        if ( g.onMouseLeave( mouse, propagationEvent ) )
          return true;
      }
    }
    
    for ( var c = g.childrens.length - 1, co; c >= 0; --c )
    {
      if ( _gameObjectMouseEvent( eventType, g.childrens[ c ], mouse, propagationEvent ) )
        return true;
    }
  }
  Event.addEventCapabilities( Camera );
  Camera.prototype.DEName = "Camera";
  
  CONFIG.debug.log( "Camera loaded", 3 );
  return Camera;
} );
Dreamirl Copyright © 2014 And the contributors
Documentation generated by JSDoc 3.2.2 on Thu Apr 24 2014 11:56:40 GMT+0200 (CEST) using the DocStrap template.