text: /**
   * @author       Richard Davey <rich@photonstorm.com>
   * @copyright    2019 Photon Storm Ltd.
   * @license      {@link https://opensource.org/licenses/MIT|MIT License}
   */
  
  var Class = require('../utils/Class');
  var FromPoints = require('../geom/rectangle/FromPoints');
  var Rectangle = require('../geom/rectangle/Rectangle');
  var Vector2 = require('../math/Vector2');
  
  /**
   * @classdesc
   * A Base Curve class, which all other curve types extend.
   *
   * Based on the three.js Curve classes created by [zz85](http://www.lab4games.net/zz85/blog)
   *
   * @class Curve
   * @memberof Phaser.Curves
   * @constructor
   * @since 3.0.0
   *
   * @param {string} type - [description]
   */
  var Curve = new Class({
  
      initialize:
  
      function Curve (type)
      {
          /**
           * String based identifier for the type of curve.
           *
           * @name Phaser.Curves.Curve#type
           * @type {string}
           * @since 3.0.0
           */
          this.type = type;
  
          /**
           * The default number of divisions within the curve.
           *
           * @name Phaser.Curves.Curve#defaultDivisions
           * @type {integer}
           * @default 5
           * @since 3.0.0
           */
          this.defaultDivisions = 5;
  
          /**
           * The quantity of arc length divisions within the curve.
           *
           * @name Phaser.Curves.Curve#arcLengthDivisions
           * @type {integer}
           * @default 100
           * @since 3.0.0
           */
          this.arcLengthDivisions = 100;
  
          /**
           * An array of cached arc length values.
           *
           * @name Phaser.Curves.Curve#cacheArcLengths
           * @type {number[]}
           * @default []
           * @since 3.0.0
           */
          this.cacheArcLengths = [];
  
          /**
           * Does the data of this curve need updating?
           *
           * @name Phaser.Curves.Curve#needsUpdate
           * @type {boolean}
           * @default true
           * @since 3.0.0
           */
          this.needsUpdate = true;
  
          /**
           * [description]
           *
           * @name Phaser.Curves.Curve#active
           * @type {boolean}
           * @default true
           * @since 3.0.0
           */
          this.active = true;
  
          /**
           * A temporary calculation Vector.
           *
           * @name Phaser.Curves.Curve#_tmpVec2A
           * @type {Phaser.Math.Vector2}
           * @private
           * @since 3.0.0
           */
          this._tmpVec2A = new Vector2();
  
          /**
           * A temporary calculation Vector.
           *
           * @name Phaser.Curves.Curve#_tmpVec2B
           * @type {Phaser.Math.Vector2}
           * @private
           * @since 3.0.0
           */
          this._tmpVec2B = new Vector2();
      },
  
      /**
       * Draws this curve on the given Graphics object.
       *
       * The curve is drawn using `Graphics.strokePoints` so will be drawn at whatever the present Graphics stroke color is.
       * The Graphics object is not cleared before the draw, so the curve will appear on-top of anything else already rendered to it.
       *
       * @method Phaser.Curves.Curve#draw
       * @since 3.0.0
       *
       * @generic {Phaser.GameObjects.Graphics} G - [graphics,$return]
       *
       * @param {Phaser.GameObjects.Graphics} graphics - The Graphics instance onto which this curve will be drawn.
       * @param {integer} [pointsTotal=32] - The resolution of the curve. The higher the value the smoother it will render, at the cost of rendering performance.
       *
       * @return {Phaser.GameObjects.Graphics} The Graphics object to which the curve was drawn.
       */
      draw: function (graphics, pointsTotal)
      {
          if (pointsTotal === undefined) { pointsTotal = 32; }
  
          //  So you can chain graphics calls
          return graphics.strokePoints(this.getPoints(pointsTotal));
      },
      
      /**
       * Returns a Rectangle where the position and dimensions match the bounds of this Curve.
       *
       * You can control the accuracy of the bounds. The value given is used to work out how many points
       * to plot across the curve. Higher values are more accurate at the cost of calculation speed.
       *
       * @method Phaser.Curves.Curve#getBounds
       * @since 3.0.0
       *
       * @param {Phaser.Geom.Rectangle} [out] - The Rectangle to store the bounds in. If falsey a new object will be created.
       * @param {integer} [accuracy=16] - The accuracy of the bounds calculations.
       *
       * @return {Phaser.Geom.Rectangle} A Rectangle object holding the bounds of this curve. If `out` was given it will be this object.
       */
      getBounds: function (out, accuracy)
      {
          if (!out) { out = new Rectangle(); }
          if (accuracy === undefined) { accuracy = 16; }
  
          var len = this.getLength();
  
          if (accuracy > len)
          {
              accuracy = len / 2;
          }
  
          //  The length of the curve in pixels
          //  So we'll have 1 spaced point per 'accuracy' pixels
  
          var spaced = Math.max(1, Math.round(len / accuracy));
  
          return FromPoints(this.getSpacedPoints(spaced), out);
      },
  
      /**
       * Returns an array of points, spaced out X distance pixels apart.
       * The smaller the distance, the larger the array will be.
       *
       * @method Phaser.Curves.Curve#getDistancePoints
       * @since 3.0.0
       *
       * @param {integer} distance - The distance, in pixels, between each point along the curve.
       *
       * @return {Phaser.Geom.Point[]} An Array of Point objects.
       */
      getDistancePoints: function (distance)
      {
          var len = this.getLength();
  
          var spaced = Math.max(1, len / distance);
  
          return this.getSpacedPoints(spaced);
      },
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getEndPoint
       * @since 3.0.0
       *
       * @param {Phaser.Math.Vector2} [out] - Optional Vector object to store the result in.
       *
       * @return {Phaser.Math.Vector2} Vector2 containing the coordinates of the curves end point.
       */
      getEndPoint: function (out)
      {
          if (out === undefined) { out = new Vector2(); }
  
          return this.getPointAt(1, out);
      },
  
      /**
       * Get total curve arc length
       *
       * @method Phaser.Curves.Curve#getLength
       * @since 3.0.0
       *
       * @return {number} [description]
       */
      getLength: function ()
      {
          var lengths = this.getLengths();
  
          return lengths[lengths.length - 1];
      },
  
  
      /**
       * Get list of cumulative segment lengths
       *
       * @method Phaser.Curves.Curve#getLengths
       * @since 3.0.0
       *
       * @param {integer} [divisions] - [description]
       *
       * @return {number[]} [description]
       */
      getLengths: function (divisions)
      {
          if (divisions === undefined) { divisions = this.arcLengthDivisions; }
  
          if ((this.cacheArcLengths.length === divisions + 1) && !this.needsUpdate)
          {
              return this.cacheArcLengths;
          }
  
          this.needsUpdate = false;
  
          var cache = [];
          var current;
          var last = this.getPoint(0, this._tmpVec2A);
          var sum = 0;
  
          cache.push(0);
  
          for (var p = 1; p <= divisions; p++)
          {
              current = this.getPoint(p / divisions, this._tmpVec2B);
  
              sum += current.distance(last);
  
              cache.push(sum);
  
              last.copy(current);
          }
  
          this.cacheArcLengths = cache;
  
          return cache; // { sums: cache, sum:sum }; Sum is in the last element.
      },
  
      // Get point at relative position in curve according to arc length
  
      // - u [0 .. 1]
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getPointAt
       * @since 3.0.0
       *
       * @generic {Phaser.Math.Vector2} O - [out,$return]
       *
       * @param {number} u - [description]
       * @param {Phaser.Math.Vector2} [out] - [description]
       *
       * @return {Phaser.Math.Vector2} [description]
       */
      getPointAt: function (u, out)
      {
          var t = this.getUtoTmapping(u);
  
          return this.getPoint(t, out);
      },
  
      // Get sequence of points using getPoint( t )
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getPoints
       * @since 3.0.0
       *
       * @param {integer} [divisions] - [description]
       *
       * @return {Phaser.Math.Vector2[]} [description]
       */
      getPoints: function (divisions)
      {
          if (divisions === undefined) { divisions = this.defaultDivisions; }
  
          var points = [];
  
          for (var d = 0; d <= divisions; d++)
          {
              points.push(this.getPoint(d / divisions));
          }
  
          return points;
      },
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getRandomPoint
       * @since 3.0.0
       *
       * @generic {Phaser.Math.Vector2} O - [out,$return]
       *
       * @param {Phaser.Math.Vector2} [out] - [description]
       *
       * @return {Phaser.Math.Vector2} [description]
       */
      getRandomPoint: function (out)
      {
          if (out === undefined) { out = new Vector2(); }
  
          return this.getPoint(Math.random(), out);
      },
  
      // Get sequence of points using getPointAt( u )
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getSpacedPoints
       * @since 3.0.0
       *
       * @param {integer} [divisions] - [description]
       *
       * @return {Phaser.Math.Vector2[]} [description]
       */
      getSpacedPoints: function (divisions)
      {
          if (divisions === undefined) { divisions = this.defaultDivisions; }
  
          var points = [];
  
          for (var d = 0; d <= divisions; d++)
          {
              var t = this.getUtoTmapping(d / divisions, null, divisions);
  
              points.push(this.getPoint(t));
          }
  
          return points;
      },
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getStartPoint
       * @since 3.0.0
       *
       * @generic {Phaser.Math.Vector2} O - [out,$return]
       *
       * @param {Phaser.Math.Vector2} [out] - [description]
       *
       * @return {Phaser.Math.Vector2} [description]
       */
      getStartPoint: function (out)
      {
          if (out === undefined) { out = new Vector2(); }
  
          return this.getPointAt(0, out);
      },
  
      /**
       * Returns a unit vector tangent at t
       * In case any sub curve does not implement its tangent derivation,
       * 2 points a small delta apart will be used to find its gradient
       * which seems to give a reasonable approximation
       *
       * @method Phaser.Curves.Curve#getTangent
       * @since 3.0.0
       *
       * @generic {Phaser.Math.Vector2} O - [out,$return]
       *
       * @param {number} t - [description]
       * @param {Phaser.Math.Vector2} [out] - [description]
       *
       * @return {Phaser.Math.Vector2} Vector approximating the tangent line at the point t (delta +/- 0.0001)
       */
      getTangent: function (t, out)
      {
          if (out === undefined) { out = new Vector2(); }
  
          var delta = 0.0001;
          var t1 = t - delta;
          var t2 = t + delta;
  
          // Capping in case of danger
  
          if (t1 < 0)
          {
              t1 = 0;
          }
  
          if (t2 > 1)
          {
              t2 = 1;
          }
  
          this.getPoint(t1, this._tmpVec2A);
          this.getPoint(t2, out);
  
          return out.subtract(this._tmpVec2A).normalize();
      },
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getTangentAt
       * @since 3.0.0
       *
       * @generic {Phaser.Math.Vector2} O - [out,$return]
       *
       * @param {number} u - [description]
       * @param {Phaser.Math.Vector2} [out] - [description]
       *
       * @return {Phaser.Math.Vector2} [description]
       */
      getTangentAt: function (u, out)
      {
          var t = this.getUtoTmapping(u);
  
          return this.getTangent(t, out);
      },
  
      //  Given a distance in pixels, get a t to find p.
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getTFromDistance
       * @since 3.0.0
       *
       * @param {integer} distance - [description]
       * @param {integer} [divisions] - [description]
       *
       * @return {number} [description]
       */
      getTFromDistance: function (distance, divisions)
      {
          if (distance <= 0)
          {
              return 0;
          }
  
          return this.getUtoTmapping(0, distance, divisions);
      },
  
      // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#getUtoTmapping
       * @since 3.0.0
       *
       * @param {number} u - [description]
       * @param {integer} distance - [description]
       * @param {integer} [divisions] - [description]
       *
       * @return {number} [description]
       */
      getUtoTmapping: function (u, distance, divisions)
      {
          var arcLengths = this.getLengths(divisions);
  
          var i = 0;
          var il = arcLengths.length;
  
          var targetArcLength; // The targeted u distance value to get
  
          if (distance)
          {
              //  Cannot overshoot the curve
              targetArcLength = Math.min(distance, arcLengths[il - 1]);
          }
          else
          {
              targetArcLength = u * arcLengths[il - 1];
          }
  
          // binary search for the index with largest value smaller than target u distance
  
          var low = 0;
          var high = il - 1;
          var comparison;
  
          while (low <= high)
          {
              i = Math.floor(low + (high - low) / 2); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
  
              comparison = arcLengths[i] - targetArcLength;
  
              if (comparison < 0)
              {
                  low = i + 1;
              }
              else if (comparison > 0)
              {
                  high = i - 1;
              }
              else
              {
                  high = i;
                  break;
              }
          }
  
          i = high;
  
          if (arcLengths[i] === targetArcLength)
          {
              return i / (il - 1);
          }
  
          // we could get finer grain at lengths, or use simple interpolation between two points
  
          var lengthBefore = arcLengths[i];
          var lengthAfter = arcLengths[i + 1];
  
          var segmentLength = lengthAfter - lengthBefore;
  
          // determine where we are between the 'before' and 'after' points
  
          var segmentFraction = (targetArcLength - lengthBefore) / segmentLength;
  
          // add that fractional amount to t
  
          return (i + segmentFraction) / (il - 1);
      },
  
      /**
       * [description]
       *
       * @method Phaser.Curves.Curve#updateArcLengths
       * @since 3.0.0
       */
      updateArcLengths: function ()
      {
          this.needsUpdate = true;
  
          this.getLengths();
      }
  
  });
  
  module.exports = Curve;
  