text: /*
   * MelonJS Game Engine
   * Copyright (C) 2011 - 2018 Olivier Biot
   * http://www.melonjs.org
   *
   */
  
  (function () {
      // some ref shortcut
      var MIN = Math.min, MAX = Math.max;
  
      var targetV = new me.Vector2d();
  
      /**
       * a camera/viewport Object
       * @class
       * @extends me.Renderable
       * @memberOf me
       * @constructor
       * @param {Number} minX start x offset
       * @param {Number} minY start y offset
       * @param {Number} maxX end x offset
       * @param {Number} maxY end y offset
       */
      me.Viewport = me.Renderable.extend(
      /** @scope me.Viewport.prototype */ {
          /** @ignore */
          init : function (minX, minY, maxX, maxY) {
              this._super(me.Renderable, "init", [minX, minY, maxX - minX, maxY - minY]);
  
              /**
               * Axis definition
               * @property NONE
               * @property HORIZONTAL
               * @property VERTICAL
               * @property BOTH
               * @public
               * @constant
               * @enum {Number}
               * @name AXIS
               * @memberOf me.Viewport
               */
              this.AXIS = {
                  NONE : 0,
                  HORIZONTAL : 1,
                  VERTICAL : 2,
                  BOTH : 3
              };
  
              /**
               * Camera bounds
               * @public
               * @type me.Rect
               * @name bounds
               * @memberOf me.Viewport
               */
              this.bounds = new me.Rect(-Infinity, -Infinity, Infinity, Infinity);
  
              /**
               * [IMTERNAL] enable or disable damping
               * @private
               * @type {Boolean}
               * @name smoothFollow
               * @see me.Viewport.damping
               * @default true
               * @memberOf me.Viewport
               */
              this.smoothFollow = true;
  
              /**
               * viewport damping for smooth transition [0 .. 1].
               * 1 being the maximum value and will snap the viewport to the target position
               * @public
               * @type {Number}
               * @name damping
               * @default 1.0
               * @memberOf me.Viewport
               */
              this.damping = 1.0;
  
              // offset for shake effect
              this.offset = new me.Vector2d();
  
              // target to follow
              this.target = null;
  
              // default value follow
              this.follow_axis = this.AXIS.NONE;
  
              // shake variables
              this._shake = {
                  intensity : 0,
                  duration : 0,
                  axis : this.AXIS.BOTH,
                  onComplete : null
              };
  
              // flash variables
              this._fadeOut = {
                  color : null,
                  tween : null
              };
              // fade variables
              this._fadeIn = {
                  color : null,
                  tween : null
              };
  
              // set a default deadzone
              this.setDeadzone(this.width / 6, this.height / 6);
  
              // for backward "compatiblity" (in terms of behavior)
              this.anchorPoint.set(0, 0);
  
              // enable event detection on the viewport
              this.isKinematic = false;
          },
  
          // -- some private function ---
  
          /** @ignore */
          _followH : function (target) {
              var targetX = this.pos.x;
              if ((target.x - this.pos.x) > (this.deadzone.right)) {
                  targetX = MIN((target.x) - (this.deadzone.right), this.bounds.width - this.width);
              }
              else if ((target.x - this.pos.x) < (this.deadzone.pos.x)) {
                  targetX = MAX((target.x) - this.deadzone.pos.x, this.bounds.pos.x);
              }
              return targetX;
  
          },
  
          /** @ignore */
          _followV : function (target) {
              var targetY = this.pos.y;
              if ((target.y - this.pos.y) > (this.deadzone.bottom)) {
                  targetY = MIN((target.y) - (this.deadzone.bottom), this.bounds.height - this.height);
              }
              else if ((target.y - this.pos.y) < (this.deadzone.pos.y)) {
                  targetY = MAX((target.y) - this.deadzone.pos.y, this.bounds.pos.y);
              }
              return targetY;
          },
  
          // -- public function ---
  
          /**
           * reset the viewport to specified coordinates
           * @name reset
           * @memberOf me.Viewport
           * @function
           * @param {Number} [x=0]
           * @param {Number} [y=0]
           */
          reset : function (x, y) {
              // reset the initial viewport position to 0,0
              this.pos.x = x || 0;
              this.pos.y = y || 0;
  
              // reset the target
              this.unfollow();
  
              // damping default value
              this.smoothFollow = true;
              this.damping = 1.0;
  
              // reset the transformation matrix
              this.currentTransform.identity();
          },
  
          /**
           * change the deadzone settings.
           * the "deadzone" defines an area within the current viewport in which
           * the followed renderable can move without scrolling the viewport.
           * @name setDeadzone
           * @see me.Viewport.follow
           * @memberOf me.Viewport
           * @function
           * @param {Number} w deadzone width
           * @param {Number} h deadzone height
           */
          setDeadzone : function (w, h) {
              if (typeof(this.deadzone) === "undefined") {
                  this.deadzone = new me.Rect(0, 0, 0, 0);
              }
  
              // reusing the old code for now...
              this.deadzone.pos.set(
                  ~~((this.width - w) / 2),
                  ~~((this.height - h) / 2 - h * 0.25)
              );
              this.deadzone.resize(w, h);
  
              this.smoothFollow = false;
  
              // force a camera update
              this.updateTarget();
  
              this.smoothFollow = true;
          },
  
  
          /**
           * resize the viewport
           * @name resize
           * @memberOf me.Viewport
           * @function
           * @param {Number} w new width of the viewport
           * @param {Number} h new height of the viewport
           * @return {me.Viewport} this viewport
          */
          resize : function (w, h) {
              // parent consctructor, resize viewport rect
              this._super(me.Renderable, "resize", [w, h]);
  
              // disable damping while resizing
              this.smoothFollow = false;
  
              // update bounds
              var level = me.levelDirector.getCurrentLevel();
              this.setBounds(
                  0, 0,
                  Math.max(w, level ? level.width : 0),
                  Math.max(h, level ? level.height : 0)
              );
  
              // reset everthing
              this.setDeadzone(w / 6, h / 6);
              this.update();
              this.smoothFollow = true;
  
              me.event.publish(me.event.VIEWPORT_ONRESIZE, [ this.width, this.height ]);
  
              return this;
          },
  
          /**
           * set the viewport boundaries (set to the world limit by default).
           * the viewport is bound to the given coordinates and cannot move/be scrolled outside of it.
           * @name setBounds
           * @memberOf me.Viewport
           * @function
           * @param {Number} x world left limit
           * @param {Number} y world top limit
           * @param {Number} w world width limit
           * @param {Number} h world height limit
           */
          setBounds : function (x, y, w, h) {
              this.smoothFollow = false;
              this.bounds.pos.set(x, y);
              this.bounds.resize(w, h);
              this.moveTo(this.pos.x, this.pos.y);
              this.update();
              this.smoothFollow = true;
          },
  
          /**
           * set the viewport to follow the specified renderable. <br>
           * (this will put the viewport center around the given target)
           * @name follow
           * @memberOf me.Viewport
           * @function
           * @param {me.Renderable|me.Vector2d} target renderable or position vector to follow
           * @param {me.Viewport.AXIS} [axis=this.AXIS.BOTH] Which axis to follow
           * @param {Number} [damping=1] default damping value
           * @example
           * // set the viewport to follow this renderable on both axis, and enable damping
           * me.game.viewport.follow(this, me.game.viewport.AXIS.BOTH, 0.1);
           */
          follow : function (target, axis, damping) {
              if (target instanceof me.Renderable) {
                  this.target = target.pos;
              }
              else if ((target instanceof me.Vector2d) || (target instanceof me.Vector3d))  {
                  this.target = target;
              }
              else {
                  throw new me.Renderable.Error("invalid target for viewport.follow");
              }
              // if axis is null, camera is moved on target center
              this.follow_axis = (
                  typeof(axis) === "undefined" ? this.AXIS.BOTH : axis
              );
  
              this.smoothFollow = false;
  
              if (typeof damping !== "number") {
                  this.damping = 1;
              } else {
                  this.damping = me.Math.clamp(damping, 0.0, 1.0);
              }
  
              // force a camera update
              this.updateTarget();
  
              this.smoothFollow = true;
          },
  
          /**
           * unfollow the current target
           * @name unfollow
           * @memberOf me.Viewport
           * @function
           */
          unfollow : function () {
              this.target = null;
              this.follow_axis = this.AXIS.NONE;
          },
  
          /**
           * move the viewport upper-left position by the specified offset.
           * @name move
           * @memberOf me.Viewport
           * @see me.Viewport.focusOn
           * @function
           * @param {Number} x
           * @param {Number} y
           * @example
           * // Move the viewport up by four pixels
           * me.game.viewport.move(0, -4);
           */
          move : function (x, y) {
              this.moveTo(this.pos.x + x, this.pos.y + y);
          },
  
          /**
           * move the viewport upper-left position to the specified coordinates
           * @name moveTo
           * @memberOf me.Viewport
           * @see me.Viewport.focusOn
           * @function
           * @param {Number} x
           * @param {Number} y
           */
          moveTo : function (x, y) {
              this.pos.x = me.Math.clamp(
                  x,
                  this.bounds.pos.x,
                  this.bounds.width - this.width
              );
              this.pos.y = me.Math.clamp(
                  y,
                  this.bounds.pos.y,
                  this.bounds.height - this.height
              );
  
              //publish the corresponding message
              me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]);
          },
  
          /** @ignore */
          updateTarget : function () {
              if (this.target) {
  
                  targetV.setV(this.pos);
  
                  switch (this.follow_axis) {
                      case this.AXIS.NONE:
                          //this.focusOn(this.target);
                          break;
  
                      case this.AXIS.HORIZONTAL:
                          targetV.x = this._followH(this.target);
                          break;
  
                      case this.AXIS.VERTICAL:
                          targetV.y = this._followV(this.target);
                          break;
  
                      case this.AXIS.BOTH:
                          targetV.x = this._followH(this.target);
                          targetV.y = this._followV(this.target);
                          break;
  
                      default:
                          break;
                  }
  
                  if (!this.pos.equals(targetV)) {
                      // update the camera position
                      if (this.smoothFollow === true && this.damping < 1.0) {
                          this.pos.lerp(targetV, this.damping);
                      } else {
                          this.pos.setV(targetV);
                      }
                      return true;
                  }
              }
  
              return false;
          },
  
          /** @ignore */
          update : function (dt) {
              var updated = this.updateTarget(dt);
  
              if (this._shake.duration > 0) {
                  this._shake.duration -= dt;
                  if (this._shake.duration <= 0) {
                      this._shake.duration = 0;
                      this.offset.setZero();
                      if (typeof(this._shake.onComplete) === "function") {
                          this._shake.onComplete();
                      }
                  }
                  else {
                      if (this._shake.axis === this.AXIS.BOTH ||
                          this._shake.axis === this.AXIS.HORIZONTAL) {
                          this.offset.x = (Math.random() - 0.5) * this._shake.intensity;
                      }
                      if (this._shake.axis === this.AXIS.BOTH ||
                          this._shake.axis === this.AXIS.VERTICAL) {
                          this.offset.y = (Math.random() - 0.5) * this._shake.intensity;
                      }
                  }
                  // updated!
                  updated = true;
              }
  
              if (updated === true) {
                  //publish the corresponding message
                  me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]);
              }
  
              // check for fade/flash effect
              if ((this._fadeIn.tween != null) || (this._fadeOut.tween != null)) {
                  updated = true;
              }
  
              return updated;
          },
  
          /**
           * shake the camera
           * @name shake
           * @memberOf me.Viewport
           * @function
           * @param {Number} intensity maximum offset that the screen can be moved
           * while shaking
           * @param {Number} duration expressed in milliseconds
           * @param {me.Viewport.AXIS} [axis=this.AXIS.BOTH] specify on which axis you
           *   want the shake effect
           * @param {Function} [onComplete] callback once shaking effect is over
           * @param {Boolean} [force] if true this will override the current effect
           * @example
           * // shake it baby !
           * me.game.viewport.shake(10, 500, me.game.viewport.AXIS.BOTH);
           */
          shake : function (intensity, duration, axis, onComplete, force) {
              if (this._shake.duration === 0 || force === true) {
                  this._shake.intensity = intensity;
                  this._shake.duration = duration;
                  this._shake.axis = axis || this.AXIS.BOTH;
                  this._shake.onComplete = typeof (onComplete) === "function" ? onComplete : undefined;
              }
          },
  
          /**
           * fadeOut(flash) effect<p>
           * screen is filled with the specified color and slowly goes back to normal
           * @name fadeOut
           * @memberOf me.Viewport
           * @function
           * @param {me.Color|String} color a CSS color value
           * @param {Number} [duration=1000] expressed in milliseconds
           * @param {Function} [onComplete] callback once effect is over
           * @example
           * // fade the viewport to white upon dying, reload the level, and then fade out back
           * me.game.viewport.fadeIn("#fff", 150, function() {
           *     me.audio.play("die", false);
           *     me.levelDirector.reloadLevel();
           *     me.game.viewport.fadeOut("#fff", 150);
           * });
           */
          fadeOut : function (color, duration, onComplete) {
              this._fadeOut.color = me.pool.pull("me.Color").copy(color);
              this._fadeOut.tween = me.pool.pull("me.Tween", this._fadeOut.color)
                  .to({ alpha: 0.0 }, duration || 1000)
                  .onComplete(onComplete || null);
              this._fadeOut.tween.isPersistent = true;
              this._fadeOut.tween.start();
          },
  
          /**
           * fadeIn effect <p>
           * fade to the specified color
           * @name fadeIn
           * @memberOf me.Viewport
           * @function
           * @param {me.Color|String} color a CSS color value
           * @param {Number} [duration=1000] expressed in milliseconds
           * @param {Function} [onComplete] callback once effect is over
           * @example
           * // flash  the viewport to white for 75ms
           * me.game.viewport.fadeIn("#FFFFFF", 75);
           */
          fadeIn : function (color, duration, onComplete) {
              this._fadeIn.color = me.pool.pull("me.Color").copy(color);
              var _alpha = this._fadeIn.color.alpha;
              this._fadeIn.color.alpha = 0.0;
              this._fadeIn.tween = me.pool.pull("me.Tween", this._fadeIn.color)
                  .to({ alpha: _alpha }, duration || 1000)
                  .onComplete(onComplete || null);
              this._fadeIn.tween.isPersistent = true;
              this._fadeIn.tween.start();
          },
  
          /**
           * return the viewport width
           * @name getWidth
           * @memberOf me.Viewport
           * @function
           * @return {Number}
           */
          getWidth : function () {
              return this.width;
          },
  
          /**
           * return the viewport height
           * @name getHeight
           * @memberOf me.Viewport
           * @function
           * @return {Number}
           */
          getHeight : function () {
              return this.height;
          },
  
          /**
           * set the viewport position around the specified object
           * @name focusOn
           * @memberOf me.Viewport
           * @function
           * @param {me.Renderable}
           */
          focusOn : function (target) {
              var bounds = target.getBounds();
              this.moveTo(
                  target.pos.x + bounds.pos.x + (bounds.width / 2),
                  target.pos.y + bounds.pos.y + (bounds.height / 2)
              );
          },
  
          /**
           * check if the specified rectangle is in the viewport
           * @name isVisible
           * @memberOf me.Viewport
           * @function
           * @param {me.Rect} rect
           * @return {Boolean}
           */
          isVisible : function (rect) {
              return rect.overlaps(this);
          },
  
          /**
           * convert the given "local" (screen) coordinates into world coordinates
           * @name localToWorld
           * @memberOf me.Viewport
           * @function
           * @param {Number} x
           * @param {Number} y
           * @param {Number} [v] an optional vector object where to set the
           * converted value
           * @return {me.Vector2d}
           */
          localToWorld : function (x, y, v) {
              // TODO memoization for one set of coords (multitouch)
              v = v || new me.Vector2d();
              v.set(x, y).add(this.pos).sub(me.game.world.pos);
              if (!this.currentTransform.isIdentity()) {
                  this.currentTransform.multiplyVectorInverse(v);
              }
              return v;
          },
  
          /**
           * convert the given world coordinates into "local" (screen) coordinates
           * @name worldToLocal
           * @memberOf me.Viewport
           * @function
           * @param {Number} x
           * @param {Number} y
           * @param {Number} [v] an optional vector object where to set the
           * converted value
           * @return {me.Vector2d}
           */
          worldToLocal : function (x, y, v) {
              // TODO memoization for one set of coords (multitouch)
              v = v || new me.Vector2d();
              v.set(x, y)
              if (!this.currentTransform.isIdentity()) {
                  this.currentTransform.multiplyVector(v);
              }
              return v.sub(this.pos).add(me.game.world.pos);
          },
  
          /**
           * render the camera effects
           * @ignore
           */
          draw : function (renderer) {
              // fading effect
              if (this._fadeIn.tween) {
                  renderer.clearColor(this._fadeIn.color);
                  // remove the tween if over
                  if (this._fadeIn.color.alpha === 1.0) {
                      this._fadeIn.tween = null;
                      me.pool.push(this._fadeIn.color);
                      this._fadeIn.color = null;
                  }
              }
  
              // flashing effect
              if (this._fadeOut.tween) {
                  renderer.clearColor(this._fadeOut.color);
                  // remove the tween if over
                  if (this._fadeOut.color.alpha === 0.0) {
                      this._fadeOut.tween = null;
                      me.pool.push(this._fadeOut.color);
                      this._fadeOut.color = null;
                  }
              }
          }
      });
  })();
  