Source: BezierPath.js

/**
 * @classdesc A refactored BezierPath class.
 *
 * @require Vertex, CubicBezierCurve
 * 
 * @author Ikaros Kappler
 * @date 2013-08-19
 * @modified 2018-08-16 Added closure. Removed the 'IKRS' wrapper.
 * @modified 2018-11-20 Added circular auto-adjustment.
 * @modified 2018-11-25 Added the point constants to the BezierPath class itself.
 * @modified 2018-11-28 Added the locateCurveByStartPoint() function.
 * @modified 2018-12-04 Added the toSVGString() function.
 * @modified 2019-03-23 Added JSDoc tags.
 * @modified 2019-03-23 Changed the fuctions getPoint and getPointAt to match semantics in the Line class.
 * @version 2.0.0
 *
 * @file BezierPath
 * @public
 **/


(function(_context) {
    'use strict';

 
    /**
     * The constructor.<br>
     * <br>
     * This constructor expects a sequence of path points and will approximate
     * the location of control points by picking some between the points.<br>
     * You should consider just constructing empty paths and then add more curves later using
     * the addCurve() function.
     *
     * @constructor
     * @name BezierPath
     * @param {Vertex[]} pathPoints - An array of path vertices (no control points).
     **/
    var BezierPath = function( pathPoints ) {
	if( !pathPoints )
	    pathPoints = [];
	this.totalArcLength = 0.0;
	// Set this flag to true if you want the first point and
	// last point of the path to be auto adjusted, too.
	this.adjustCircular = false;
	this.bezierCurves = [];
	
	for( var i = 1; i < pathPoints.length; i++ ) {
	    var bounds = new THREE.Box2( pathPoints[i].x - pathPoints[i-1].x, 
					 pathPoints[i].y - pathPoints[i-1].y
				       );
	    // Create a new Bezier curve inside the box
	    var bCurve =  new CubicBezierCurve( pathPoints[i-1],
						pathPoints[i],
						new Vertex( pathPoints[i-1].x, 
							    pathPoints[i-1].y - bounds.min/2
							  ),
						// This control point will be auto-adjusted in the next step
						new Vertex( pathPoints[i].x + bounds.max/2,
							    pathPoints[i].y 
							  )
					      );
	    this.bezierCurves.push( bCurve );
	    this.totalArcLength += bCurve.getLength();
	    
	    // Auto adjust the second control point (should be on a linear sub-space)
	    if( this.bezierCurves.length >= 2 ) {
		this.adjustSuccessorControlPoint( this.bezierCurves.length-2, // curveIndex, 
						  true,                       // obtain handle length?
						  true                        // update arc lengths
						);
	    }
	}
    };

    BezierPath.prototype.constructor = BezierPath;

    // +---------------------------------------------------------------------------------
    // | These constants equal the values from CubicBezierCurve.
    // +-------------------------------
    /** @constant {number} */
    BezierPath.START_POINT         = 0;
    /** @constant {number} */
    BezierPath.START_CONTROL_POINT = 1;
    /** @constant {number} */
    BezierPath.END_CONTROL_POINT   = 2;
    /** @constant {number} */
    BezierPath.END_POINT           = 3;
    /** @constant {number} */
    BezierPath.prototype.START_POINT         = 0;
    /** @constant {number} */
    BezierPath.prototype.START_CONTROL_POINT = 1;
    /** @constant {number} */
    BezierPath.prototype.END_CONTROL_POINT   = 2;
    /** @constant {number} */
    BezierPath.prototype.END_POINT           = 3;



    /**
     * Add a cubic bezier curve to the end of this path.
     *
     * @method addCurve
     * @param {CubicBezierCurve} curve - The curve to be added to the end of the path.
     * @instance
     * @memberof BezierPath
     * @return {void}
     **/
    BezierPath.prototype.addCurve = function( curve ) {
	if( curve == null || typeof curve == 'undefined' )
	    throw "Cannot add null curve to bézier path.";
	this.bezierCurves.push( curve );
	if( this.bezierCurves.length > 1 ) {
	    curve.startPoint = this.bezierCurves[this.bezierCurves.length-2].endPoint;
	    this.adjustSuccessorControlPoint(
		this.bezierCurves.length-2, // curveIndex,
		true,                       // obtainHandleLength,  
		true                        // updateArcLengths  
	    );    
	} else {
	    this.totalArcLength += curve.getLength();
	}
    };


 
    /**
     * Locate the curve with the given start point (function returns the index).
     *
     * @method locateCurveByStartPoint
     * @param {Vertex} point - The (curve start-) point to look for.
     * @instance
     * @memberof BezierPath
     * @return {number} The curve index or -1 if curve (start-) point not found
     **/
    BezierPath.prototype.locateCurveByStartPoint = function( point ) {
	for( var i in this.bezierCurves ) {
	    if( this.bezierCurves[i].startPoint.equals(point) )
		return i;
	}
	return -1;
    };


    /**
     * Locate the curve with the given start point (function returns the index).
     *
     * @method locateCurveByEndPoint
     * @param {Vertex} point - The (curve endt-) point to look for.
     * @instance
     * @memberof BezierPath
     * @return {number} The curve index or -1 if curve (end-) point not found
     **/
    BezierPath.prototype.locateCurveByStartControlPoint = function( point ) {
	for( var i in this.bezierCurves ) {
	    if( this.bezierCurves[i].startControlPoint.equals(point) )
		return i;
	}
	return -1;
    };
    

    // +---------------------------------------------------------------------------------
    // | Locate the curve with the given end control point.
    // |
    // | @param point:Vertex The point to look for.
    // | @return Number The index or -1 if not found.
    // +-------------------------------
    BezierPath.prototype.locateCurveByEndControlPoint = function( point ) {
	for( var i in this.bezierCurves ) {
	    if( this.bezierCurves[i].endControlPoint.equals(point) )
		return i;
	}
	return -1;
    };


    
    /**
     * Get the total length of this path.<br>
     * <br>
     * Note that the returned value comes from the curve buffer. Unregistered changes 
     * to the curve points will result in invalid path length values.
     *
     * @method getLength
     * @instance
     * @memberof BezierPath
     * @return {number} The (buffered) length of the path.
     **/
    BezierPath.prototype.getLength = function() {
	return this.totalArcLength;
    };


    // +---------------------------------------------------------------------------------
    // | computeVerticalAreaSize()
    // |
    // | @experimental
    // +-------------------------------
    // NOT IN USE
    /*
    BezierPath.prototype.computeVerticalAreaSize = function( deltaSize, useAbsoluteValues ) {
	var bounds    = this.computeBoundingBox();
	var relativeX = bounds.xMax;
	var size = 0.0;
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    size += this.bezierCurves[i].computeVerticalAreaSize( relativeX,         // An imaginary x-axis at the right bound
								  deltaSize, 
								  useAbsoluteValues 
								);
	}
	return size;
    };
    */


    // +---------------------------------------------------------------------------------
    // | computeVerticalRevolutionVolumeSize()
    // |
    // | @experimental
    // +-------------------------------
    // NOT IN USE
    /*
    BezierPath.prototype.computeVerticalRevolutionVolumeSize = function( useAbsoluteValues ) {	
	var bounds    = this.computeBoundingBox();
	var relativeX = bounds.xMax;
	var volume    = 0.0;
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    volume += this.bezierCurves[i].computeVerticalRevolutionVolumeSize( relativeX,         // An imaginary x-axis at the right bound
										//deltaSize, 
										useAbsoluteValues 
									      );
	}
	return volume;
    };
    */


    
    /**
     * This function is internally called whenever the curve or path configuration
     * changed. It updates the attribute that stores the path length information.<br>
     * <br>
     * If you perform any unregistered changes to the curve points you should call
     * this function afterwards to update the curve buffer. Not updating may
     * result in unexpected behavior.
     *
     * @method updateArcLengths
     * @instance
     * @memberof BezierPath
     * @return {void}
     **/
    BezierPath.prototype.updateArcLengths = function() {
	this.totalArcLength = 0.0;
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    this.bezierCurves[ i ].updateArcLengths();
	    this.totalArcLength += this.bezierCurves[ i ].getLength();
	}
    };


    /**
     * Get the number of curves in this path.
     *
     * @method getCurveCount
     * @instance
     * @memberof BezierPath
     * @return {number} The number of curves in this path.
     **/
    BezierPath.prototype.getCurveCount = function() {
	return this.bezierCurves.length;
    };



    /**
     * Get the cubic bezier curve at the given index.
     *
     * @method getCurveAt
     * @param {number} index - The curve index from 0 to getCurveCount()-1.
     * @instance
     * @memberof BezierPath
     * @return {CubicBezierCurve} The curve at the specified index.
     **/
    BezierPath.prototype.getCurveAt = function( curveIndex ) {
	return this.bezierCurves[ curveIndex ];
    };



    /**
     * Remove the end point of this path (which removes the last curve from this path).<br>
     * <br>
     * Please note that this function does never remove the first curve, thus the path
     * cannot be empty after this call.
     *
     * @method removeEndPoint
     * @instance
     * @memberof BezierPath
     * @return {boolean} Indicating if the last curve was removed.
     **/
    BezierPath.prototype.removeEndPoint = function() {
	if( this.bezierCurves.length <= 1 )
	    return false;
	
	var newArray = [ this.bezierCurves.length-1 ];
	for( var i = 0; i < this.bezierCurves.length-1; i++ ) {
	    newArray[i] = this.bezierCurves[i];
	}
	
	// Update arc length
	this.totalArcLength -= this.bezierCurves[ this.bezierCurves.length-1 ].getLength();
	this.bezierCurves = newArray;	
	return true;
    }


    /**
     * Remove the start point of this path (which removes the first curve from this path).<br>
     * <br>
     * Please note that this function does never remove the last curve, thus the path
     * cannot be empty after this call.<br>
     *
     * @method removeStartPoint
     * @instance
     * @memberof BezierPath
     * @return {boolean} Indicating if the first curve was removed.
     **/
    BezierPath.prototype.removeStartPoint = function() {

	if( this.bezierCurves.length <= 1 )
	    return false;

	var newArray = [ this.bezierCurves.length-1 ];
	for( var i = 1; i < this.bezierCurves.length; i++ ) {

	    newArray[i-1] = this.bezierCurves[i];

	}
	
	// Update arc length
	this.totalArcLength -= this.bezierCurves[ 0 ].getLength();
	this.bezierCurves = newArray;
	
	return true;
    }


    
    /**
     * Removes a path point inside the path.
     *
     * This function joins the bezier curve at the given index with
     * its predecessor, which means that the start point at the given
     * curve index will be removed.
     *
     * @method joinAt
     * @param {number} curveIndex - The index of the curve to be joined with its predecessor.
     * @instance
     * @memberof BezierPath
     * @return {boolean} True if the passed index indicated an inner vertex and the two curves were joined.
     **/
    BezierPath.prototype.joinAt = function( curveIndex ) {

	if( curveIndex < 0 || curveIndex >= this.bezierCurves.length )
	    return false;
	
	var leftCurve  = this.bezierCurves[ curveIndex-1 ];
	var rightCurve = this.bezierCurves[ curveIndex ];

	// Make the length of the new handle double that long
	var leftControlPoint = leftCurve.getStartControlPoint().clone();
	leftControlPoint.sub( leftCurve.getStartPoint() );
	leftControlPoint.multiplyScalar( 2.0 );
	leftControlPoint.add( leftCurve.getStartPoint() );
	
	var rightControlPoint = rightCurve.getEndControlPoint().clone();
	rightControlPoint.sub( rightCurve.getEndPoint() );
	rightControlPoint.multiplyScalar( 2.0 );
	rightControlPoint.add( rightCurve.getEndPoint() );	

	var newCurve = new IKRS.CubicBezierCurve( leftCurve.getStartPoint(),
						  rightCurve.getEndPoint(),
						  leftControlPoint,
						  rightControlPoint 
						);	
	// Place into array
	var newArray = [ this.bezierCurves.length - 1 ];

	for( var i = 0; i < curveIndex-1; i++ ) 
	    newArray[ i ] = this.bezierCurves[i];
	
	newArray[ curveIndex-1 ] = newCurve;
	
	// Shift trailing curves left
	for( var i = curveIndex; i+1 < this.bezierCurves.length; i++ ) 
	    newArray[ i ] = this.bezierCurves[ i+1 ];
		
	this.bezierCurves = newArray;
	this.updateArcLengths();

	return true;
    }



    /**
     * Add a new inner curve point to the path.<br>
     * <br>
     * This function splits the bezier curve at the given index and given
     * curve segment index.
     *
     * @method splitAt
     * @param {number} curveIndex - The index of the curve to split.
     * @param {nunber} segmentIndex - The index of the curve segment where the split should be performed.
     * @instance
     * @memberof BezierPath
     * @return {boolean} True if the passed indices were valid and the path was split.
     **/
    BezierPath.prototype.splitAt = function( curveIndex,
					     segmentIndex 
					   ) {
	// Must be a valid curve index
	if( curveIndex < 0 || curveIndex >= this.bezierCurves.length )
	    return false;

	var oldCurve = this.bezierCurves[ curveIndex ];

	// Segment must be an INNER point!
	// (the outer points are already bezier end/start points!)
	if( segmentIndex < 1 || segmentIndex-1 >= oldCurve.segmentCache.length )
	    return false;

	// Make room for a new curve
	for( var c = this.bezierCurves.length; c > curveIndex; c-- ) {
	    // Move one position to the right
	    this.bezierCurves[ c ] = this.bezierCurves[ c-1 ];	    
	}

	// Accumulate segment lengths
	var u = 0;
	for( var i = 0; i < segmentIndex; i++ )
	    u += oldCurve.segmentLengths[i];
	//var tangent = oldCurve.getTangentAt( u );
	var tangent = oldCurve.getTangent( u );
	tangent = tangent.multiplyScalar( 0.25 ); 

	var leftEndControlPoint = oldCurve.segmentCache[ segmentIndex ].clone();
	leftEndControlPoint.sub( tangent );
	
	var rightStartControlPoint = oldCurve.segmentCache[ segmentIndex ].clone();
	rightStartControlPoint.add( tangent );
	
	// Make the old existing handles a quarter that long
	var leftStartControlPoint = oldCurve.getStartControlPoint().clone();
	// move to (0,0)
	leftStartControlPoint.sub( oldCurve.getStartPoint() );
	leftStartControlPoint.multiplyScalar( 0.25 );
	leftStartControlPoint.add( oldCurve.getStartPoint() );

	var rightEndControlPoint = oldCurve.getEndControlPoint().clone(); 
	// move to (0,0)
	rightEndControlPoint.sub( oldCurve.getEndPoint() );
	rightEndControlPoint.multiplyScalar( 0.25 );
	rightEndControlPoint.add( oldCurve.getEndPoint() );

	var newLeft  = new CubicBezierCurve( oldCurve.getStartPoint(),                      // old start point
					     oldCurve.segmentCache[ segmentIndex ],         // new end point
					     leftStartControlPoint,                         // old start control point 
					     leftEndControlPoint                            // new end control point
					   );
	var newRight = new CubicBezierCurve( oldCurve.segmentCache[ segmentIndex ],         // new start point
					     oldCurve.getEndPoint(),                        // old end point
					     rightStartControlPoint,                        // new start control point 
					     rightEndControlPoint                           // old end control point
					   );
	
	// Insert split curve(s) at free index
	this.bezierCurves[ curveIndex ]     = newLeft;
	this.bezierCurves[ curveIndex + 1 ] = newRight;
	
	// Update total arc length, even if there is only a very little change!
	this.totalArcLength -= oldCurve.getLength();
	this.totalArcLength += newLeft.getLength();
	this.totalArcLength += newRight.getLength();

	return true;
    };


   
    /**
     * Move the whole bezier path by the given (x,y)-amount.
     *
     * @method translate
     * @param {Vertex} amount - The amount to be added (amount.x and amount.y) 
     *                          to each vertex of the curve.
     * @instance
     * @memberof BezierPath
     * @return {void}
     **/
    BezierPath.prototype.translate = function( amount ) {	
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    var curve = this.bezierCurves[ i ];	    
	    curve.getStartPoint().add( amount );
	    curve.getStartControlPoint().add( amount );
	    curve.getEndControlPoint().add( amount );    
	}
	
	// Don't forget to translate the last curve's last point
	var curve = this.bezierCurves[ this.bezierCurves.length-1 ];
	curve.getEndPoint().add( amount );

	this.updateArcLengths();
    };


    
    /**
     * Scale the whole bezier path by the given (x,y)-factors.
     *
     * @method scale
     * @param {Vertex} anchor - The scale origin to scale from.
     * @param {Vertex} amount - The scalars to be multiplied with (ascaling.x and scaling.y)
     * @instance
     * @memberof BezierPath
     * @return {void}
     **/
    BezierPath.prototype.scale = function( anchor,  // Vertex
					   scaling  // Vertex
					 ) {
	
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    var curve = this.bezierCurves[ i ];	    
	    BezierPath._scalePoint( curve.getStartPoint(),        anchor, scaling );
	    BezierPath._scalePoint( curve.getStartControlPoint(), anchor, scaling );
	    BezierPath._scalePoint( curve.getEndControlPoint(),   anchor, scaling );
	    // Do NOT scale the end point here!
	    // Don't forget that the curves are connected and on curve's end point
	    // the the successor's start point (same instance)!
	}
	
	// Finally move the last end point (was not scaled yet)
	if( this.bezierCurves.length > 0 ) {
	    // !!! TODO: THIS CAN BE DROPPED BECAUSE Vertex.scale ALREADY DOES THIS
	    BezierPath._scalePoint( this.bezierCurves[ this.bezierCurves.length-1 ].getEndPoint(),
				    anchor,
				    scaling
				  );
	}
	
	this.updateArcLengths();	
    };


    
    /**
     * A helper function for scaling points.
     *
     * @method _scalePoint
     * @param {Vertex} point - The point to scale.
     * @param {Vertex} anchor - 
     * @param {Vertex} scaling -
     * @private
     * @memberof BezierPath
     * @return {void}
     **/
    // !!! TODO: THIS CAN BE DROPPED BECAUSE Vertex.scale ALREADY DOES THIS!!!
    BezierPath._scalePoint = function( point,   // Vertex
				       anchor,  // Vertex
				       scaling  // Vertex
				     ) {
	// Move point to origin
	point.sub( anchor );
	// Apply scaling
	point.setX( point.x * scaling.x );
	point.setY( point.y * scaling.y );
	// Move back to original position
	point.add( anchor );	
    };



    /**
     * Get the point on the bézier path at the given relative path location.
     *
     * @method getPoint
     * @param {number} u - The relative path position: <pre>0 <= u <= this.getLength()</pre>
     * @instance
     * @memberof BezierPath
     * @return {Vertex} The point at the relative path position.
     **/
    //BezierPath.prototype.getPointAt = function( u ) {
    BezierPath.prototype.getPoint = function( u ) {
	if( u < 0 || u > this.totalArcLength ) {
	    console.log( "[BezierPath.getPoint(u)] u is out of bounds: " + u + "." );
	    return null;
	}
	// Find the spline to extract the value from
	var i = 0;
	var uTemp = 0.0;
	while( i < this.bezierCurves.length &&
	       (uTemp + this.bezierCurves[i].getLength()) < u 
	     ) {
	    
	    uTemp += this.bezierCurves[ i ].getLength();
	    i++;
	}
	
	// if u == arcLength
	//   -> i is max
	if( i >= this.bezierCurves.length )
	    return this.bezierCurves[ this.bezierCurves.length-1 ].getEndPoint().clone();
	
	var bCurve    = this.bezierCurves[ i ];
	var relativeU = u - uTemp;
	//return bCurve.getPointAt( relativeU );
	return bCurve.getPoint( relativeU );
    };


    
    /**
     * Get the point on the bézier path at the given path fraction.
     *
     * @method getPointAt
     * @param {number} t - The absolute path position: <pre>0.0 <= t <= 1.0</pre>
     * @instance
     * @memberof BezierPath
     * @return {Vertex} The point at the absolute path position.
     **/
    //BezierPath.prototype.getPoint = function( t ) {
    BezierPath.prototype.getPointAt = function( t ) {
	//return this.getPointAt( t * this.totalArcLength );
	return this.getPoint( t * this.totalArcLength );
    };

    
  
    /**
     * Get the tangent of the bézier path at the given path fraction.<br>
     * <br>
     * Note that the returned vector is not normalized.
     *
     * @method getTangentAt
     * @param {number} t - The absolute path position: <pre>0.0 <= t <= 1.0</pre>
     * @instance
     * @memberof BezierPath
     * @return {Vertex} The tangent vector at the absolute path position.
     **/
    //BezierPath.prototype.getTangent = function( t ) {
    BezierPath.prototype.getTangentAt = function( t ) {
	//return this.getTangentAt( t * this.totalArcLength );
	return this.getTangent( t * this.totalArcLength );
    };

    
  
    /**
     *  Get the tangent of the bézier path at the given path location.<br>
     * <br>
     * Note that the returned vector is not normalized.
     *
     * @method getTangent
     * @param {number} u - The relative path position: <pre>0 <= u <= getLength()</pre>
     * @instance
     * @memberof BezierPath
     * @return {Vertex} The tangent vector at the relative path position.
     **/
    //BezierPath.prototype.getTangentAt = function( u ) {
    BezierPath.prototype.getTangent = function( u ) {
	if( u < 0 || u > this.totalArcLength ) {
	    console.warn( "[BezierPath.getTangent(u)] u is out of bounds: " + u + "." );
	    return null;
	}
	// Find the spline to extract the value from
	var i = 0;
	var uTemp = 0.0;
	while( i < this.bezierCurves.length &&
	       (uTemp + this.bezierCurves[i].getLength()) < u 
	     ) {   
	    uTemp += this.bezierCurves[ i ].getLength();
	    i++;
	}
	var bCurve    = this.bezierCurves[ i ];
	var relativeU = u - uTemp;
	//return bCurve.getTangentAt( relativeU );
	return bCurve.getTangent( relativeU );
    };



    /**
     * Get the perpendicular of the bézier path at the given absolute path location (fraction).<br>
     * <br>
     * Note that the returned vector is not normalized.
     *
     * @method getPerpendicularAt
     * @param {number} t - The absolute path position: <pre>0.0 <= t <= 1.0</pre>
     * @instance
     * @memberof BezierPath
     * @return {Vertex} The perpendicluar vector at the absolute path position.
     **/
    //BezierPath.prototype.getPerpendicular = function( t ) {
    BezierPath.prototype.getPerpendicularAt = function( t ) {
	//return this.getPerpendicularAt( t * this.totalArcLength );
	return this.getPerpendicular( t * this.totalArcLength );
    };



    /**
     * Get the perpendicular of the bézier path at the given relative path location.<br>
     * <br>
     * Note that the returned vector is not normalized.
     *
     * @method getPerpendicular
     * @param {number} u - The relative path position: <pre>0 <= u <= getLength()</pre>
     * @instance
     * @memberof BezierPath
     * @return {Vertex} The perpendicluar vector at the relative path position.
     **/
    //BezierPath.prototype.getPerpendicularAt = function( u ) {
    BezierPath.prototype.getPerpendicular = function( u ) {
	if( u < 0 || u > this.totalArcLength ) {
	    console.log( "[IKRS.BezierPath.getPerpendicular(u)] u is out of bounds: " + u + "." );
	    return null;
	}

	// Find the spline to extract the value from
	var i = 0;
	var uTemp = 0.0;
	
	while( i < this.bezierCurves.length &&
	       (uTemp + this.bezierCurves[i].getLength()) < u 
	     ) {
	    
	    uTemp += this.bezierCurves[ i ].getLength();
	    i++;

	}

	var bCurve    = this.bezierCurves[ i ];
	var relativeU = u - uTemp;

	// return bCurve.getPerpendicularAt( relativeU );
	return bCurve.getPerpendicular( relativeU );
    };

    

    // NOT IN USE
    /*
    BezierPath.prototype.computeBoundingBox = function() {
	if( this.bezierCurves.length == 0 ) {	    
	    // Empty box
	    return new BoundingBox( 0, 0, 0, 0 );
	}
	var boundingBox = this.bezierCurves[ 0 ].computeBoundingBox();
	for( var i = 1; i < this.bezierCurves.length; i++ ) {
	    var tmpBounds = this.bezierCurves[ i ].computeBoundingBox();
	    boundingBox.xMin = Math.min( boundingBox.xMin, tmpBounds.xMin );
	    boundingBox.xMax = Math.max( boundingBox.xMax, tmpBounds.xMax );
	    boundingBox.yMin = Math.min( boundingBox.yMin, tmpBounds.yMin );
	    boundingBox.yMax = Math.max( boundingBox.yMax, tmpBounds.yMax );    
	}
	return boundingBox;
    };
    */
    

    /**
     * This function moves the addressed curve point (or control point) with
     * keeping up the path's curve integrity.<br>
     * <br>
     * Thus is done by moving neighbour- and control- points as needed.
     *
     * @method moveCurvePoint
     * @param {number} curveIndex - The curve index to move a point from.
     * @param {number} pointID - One of the curve's four point IDs (START_POINT, 
     *                           START_CONTROL_POINT, END_CONTRO_POINT or END_POINT).
     * @param {Vertex} moveAmount - The amount to move the addressed vertex by.
     * @instance
     * @memberof BezierPath
     * @return {void}
     **/
    BezierPath.prototype.moveCurvePoint = function( curveIndex,      // int
						    pointID,         // int
						    moveAmount       // Vertex
						  ) {
	var bCurve = this.getCurveAt( curveIndex );
	bCurve.moveCurvePoint( pointID,
			       moveAmount,
			       true,       // move control point, too
			       true        // updateArcLengths
			     );

	// If inner point and NOT control point
	//  --> move neightbour
	if( pointID == this.START_POINT && (curveIndex > 0 || this.adjustCircular) ) {

	    // Set predecessor's control point!
	    var predecessor = this.getCurveAt( curveIndex-1<0 ? this.bezierCurves.length+(curveIndex-1) : curveIndex-1 );
	    predecessor.moveCurvePoint( this.END_CONTROL_POINT, 
					moveAmount,
					true,                    // move control point, too
					false                    // updateArcLengths
				      );

	} else if( pointID == this.END_POINT && (curveIndex+1 < this.bezierCurves.length || this.adjustCircular) ) {
	    // Set successcor
	    var successor = this.getCurveAt( (curveIndex+1)%this.bezierCurves.length );
	    successor.moveCurvePoint( this.START_CONTROL_POINT, 
				      moveAmount, 
				      true,                  // move control point, too
				      false                  // updateArcLengths
				    );
	    
	} else if( pointID == this.START_CONTROL_POINT && curveIndex > 0 ) {
	    
	    this.adjustPredecessorControlPoint( curveIndex, 
						true,            // obtain handle length?
						false            // update arc lengths
					      );
	    
	} else if( pointID == this.END_CONTROL_POINT && curveIndex+1 < this.getCurveCount() ) {
	    
	    this.adjustSuccessorControlPoint( curveIndex, 
					      true,            // obtain handle length?
					      false            // update arc lengths
					    );
	    
	}

	// Don't forget to update the arc lengths!
	// Note: this can be optimized as only two curves have changed their lengths!
	this.updateArcLengths();
    };


    
    /**
     * This helper function adjusts the given point's predecessor's control point.
     *
     * @method adjustPredecessorControlPoint
     * @param {number} curveIndex - The curve index to move a point from.
     * @param {boolean} obtainHandleLength - Moves the point with keeping the original handle length.
     * @param {boolean} updateArcLength - The amount to move the addressed vertex by.
     * @instance
     * @private
     * @memberof BezierPath
     * @return {void}
     **/
    BezierPath.prototype.adjustPredecessorControlPoint = function( curveIndex,          // int
								   obtainHandleLength,  // boolean
								   updateArcLengths     // boolean
								 ) {
	
	if( !this.adjustCircular && curveIndex <= 0 )
	    return false;

	var mainCurve      = this.getCurveAt( curveIndex );
	var neighbourCurve = this.getCurveAt( curveIndex-1<0 ? this.getCurveCount()+(curveIndex-1) : curveIndex-1 );
	/* return ? */ this.adjustNeighbourControlPoint( mainCurve,
						 neighbourCurve,
						 mainCurve.getStartPoint(),            // the reference point
						 mainCurve.getStartControlPoint(),     // the dragged control point
						 neighbourCurve.getEndPoint(),         // the neighbour's point
						 neighbourCurve.getEndControlPoint(),  // the neighbour's control point to adjust
						 obtainHandleLength,
						 updateArcLengths
					       );
    }


    
    /**
     * This helper function adjusts the given point's successor's control point.
     *
     * @method adjustSuccessorControlPoint
     * @param {number} curveIndex - The curve index to move a point from.
     * @param {boolean} obtainHandleLength - Moves the point with keeping the original handle length.
     * @param {boolean} updateArcLength - The amount to move the addressed vertex by.
     * @instance
     * @private
     * @memberof BezierPath
     * @return {void}
     **/
    BezierPath.prototype.adjustSuccessorControlPoint = function( curveIndex,          // int
								 obtainHandleLength,  // boolean
								 updateArcLengths     // boolean
							       ) {
	// console.log( this.getCurveCount(), curveIndex );
	if( !this.adjustCirculat && curveIndex+1 > this.getCurveCount() )
	    return false; 


	var mainCurve      = this.getCurveAt( curveIndex );
	var neighbourCurve = this.getCurveAt( (curveIndex+1)%this.getCurveCount() );
	return this.adjustNeighbourControlPoint( mainCurve,
						 neighbourCurve,
						 mainCurve.getEndPoint(),                // the reference point
						 mainCurve.getEndControlPoint(),         // the dragged control point
						 neighbourCurve.getStartPoint(),         // the neighbour's point
						 neighbourCurve.getStartControlPoint(),  // the neighbour's control point to adjust
						 obtainHandleLength,
						 updateArcLengths
					       );
    }

    /**
     * This helper function adjusts the given point's successor's control point.
     *
     * @method adjustNeighbourControlPoint
     * @param {CubicBezierCurve} mainCurve
     * @param {CubicBezierCurve} neighbourCurve
     * @param {Vertex} mainPoint
     * @param {Vertex} mainControlPoint
     * @param {Vertex} neighbourPoint
     * @param {Vertex} neighbourControlPoint
     * @param {boolean} obtainHandleLengths
     * @param {boolean} updateArcLengths
     * @instance
     * @private
     * @memberof BezierPath
     * @return {void}
     **/
    // !!! TODO: SHOULDNT THIS BE A STATIC FUNCTION ???
    BezierPath.prototype.adjustNeighbourControlPoint = function( mainCurve,
								 neighbourCurve,
								 mainPoint,
								 mainControlPoint,
								 neighbourPoint,
								 neighbourControlPoint,
								 obtainHandleLengths,  // boolean
								 updateArcLengths
							       ) {

	// Calculate start handle length
	var mainHandleBounds        = new Vertex( mainControlPoint.x - mainPoint.x,
						  mainControlPoint.y - mainPoint.y
						);
	var neighbourHandleBounds   = new Vertex( neighbourControlPoint.x - neighbourPoint.x,
						  neighbourControlPoint.y - neighbourPoint.y
						);
	var mainHandleLength        = Math.sqrt( Math.pow(mainHandleBounds.x,2) + Math.pow(mainHandleBounds.y,2) );
	var neighbourHandleLength   = Math.sqrt( Math.pow(neighbourHandleBounds.x,2) + Math.pow(neighbourHandleBounds.y,2) );

	if( mainHandleLength <= 0.1 ) 
	    return; // no secure length available for division
	
	
	// Just invert the main handle	
	neighbourControlPoint.set( neighbourPoint.x - mainHandleBounds.x * (neighbourHandleLength/mainHandleLength),
				   neighbourPoint.y - mainHandleBounds.y * (neighbourHandleLength/mainHandleLength)
				 );
	neighbourCurve.updateArcLengths();
    };


    
    /**
     * Clone this BezierPath (deep clone).
     *
     * @method clone
     * @instance
     * @memberof BezierPath
     * @return {BezierPath}
     **/
    BezierPath.prototype.clone = function() {
	var path = new BezierPath( null );
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    path.bezierCurves.push( this.bezierCurves[i].clone() );
	    // Connect splines
	    if( i > 0 )
		path.bezierCurves[i-1].endPoint = path.bezierCurves[i].startPoint;
	}
	path.updateArcLengths();
	return path;
    };


    
    /**
     * Compare this and the passed Bézier path.
     *
     * @method equals
     * @param {BezierPath} path - The pass to compare with.
     * @instance
     * @memberof BezierPath
     * @return {boolean}
     **/
    BezierPath.prototype.equals = function( path ) {
	if( !path )
	    return false;
	// Check if path contains the credentials
	if( !path.bezierCurves )
	    return false;
	if( typeof path.bezierCurves.length == "undefined" )
	    return false;
	if( path.bezierCurves.length != this.bezierCurves.length )
	    return false;
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    if( !this.bezierCurves[i].equals(path.bezierCurves[i]) )
		return false;
	}
	return true;
    };


    /**
     * Create a <pre>&lt;path&gt;</pre> SVG representation of this bézier curve.
     *
     * @method toSVGString
     * @param {object=} [options={}] - Like options.className
     * @param {string=} [options.className] - The classname to use for the SVG item.
     * @instance
     * @memberof BezierPath
     * @return {string} The SVG string.
     **/
    BezierPath.prototype.toSVGString = function( options ) {
	options = options || {};
	var buffer = [];
	buffer.push( '<path' );
	if( options.className )
	    buffer.push( ' class="' + options.className + '"' );
	buffer.push( ' d="' );
	for( var c = 0; c < this.bezierCurves.length; c++ ) {
	    if( c > 0 )
		buffer.push( ' ' );
	    buffer.push( this.bezierCurves[c].toSVGPathData() );
	}
	buffer.push( '" />' );
	return buffer.join('');
    };



    /**
     * Create a JSON string representation of this bézier curve.
     *
     * @method toJSON
     * @param {boolean} prettyFormat - If true then the function will add line breaks.
     * @instance
     * @memberof BezierPath
     * @return {string} The JSON string.
     **/
    BezierPath.prototype.toJSON = function( prettyFormat ) {
	var buffer = [];
	buffer.push( "[" ); // array begin
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    if( i > 0 ) 
		buffer.push( "," );
	    if( prettyFormat)
		buffer.push( "\n\t" );
	    else
		buffer.push( " " );
	    buffer.push( this.bezierCurves[i].toJSON( prettyFormat ) );
	}
	if( this.bezierCurves.length != 0 )
	    buffer.push( " " );
	buffer.push( "]" ); // array end
	
	return buffer.join( "" ); // Convert to string, with empty separator.
    };


    /**
     * Parse a BezierPath from the given JSON string.
     *
     * @method fromJSON
     * @param {string} jsonString - The string with the JSON data.
     * @throw An error if the string is not JSON or does not contain a bezier path object.
     * @instance
     * @memberof BezierPath
     * @return {BezierPath} The parsed bezier path instance.
     **/
    BezierPath.fromJSON = function( jsonString ) {

	var obj = JSON.parse( jsonString );

	return IKRS.BezierPath.fromArray( obj );
    };


    /**
     * Create a BezierPath instance from the given array.
     *
     * @method fromArray
     * @param {Vertex[][]} arr - A two-dimensional array containing the bezier path vertices.
     * @throw An error if the array does not contain proper bezier path data.
     * @instance
     * @memberof BezierPath
     * @return {BezierPath} The bezier path instance retrieved from the array data.
     **/
    BezierPath.fromArray = function( arr ) {

	if( !Array.isArray(arr) )
	    throw "[BezierPath.fromArray] Passed object must be an array.";
	
	if( arr.length < 1 )
	    throw "[BezierPath.fromArray] Passed array must contain at least one bezier curve (has " + arr.length + ").";
	
	// Create an empty bezier path
	var bPath = new BezierPath( null );
	var lastCurve = null;
	for( var i = 0; i < arr.length; i++ ) {
	    
	    // Convert object (or array?) to bezier curve
	    var bCurve = null;
	    if( 0 in arr[i] && 1 in arr[i] && 2 in arr[i] && 3 in arr[i] )
		bCurve = CubicBezierCurve.fromArray( arr[i] );
	    else
		bCurve = CubicBezierCurve.fromObject( arr[i] );
	    // Set curve start point?
	    // (avoid duplicate point instances!)
	    if( lastCurve )
		bCurve.startPoint = lastCurve.endPoint;
	    
	    // Add to path's internal list
	    bPath.bezierCurves.push( bCurve );
	    bPath.totalArcLength += bCurve.getLength();
	    
	    
	    lastCurve = bCurve;
	}   
	// Bezier segments added.
	// Recalculate length?
	//bPath.updateArcLengths();
	
	// Done
	return bPath;
    }


    
    /**
     * This function converts the bezier path into a string containing
     * integer values only.
     * The points' float values are rounded to 1 digit after the comma.
     *
     * The returned string represents a JSON array (with leading '[' and
     * trailing ']', the separator is ',').
     *
     * @method toReducedListRepresentation
     * @param {number} digits - The number of digits to be used after the comma '.'.
     * @instance
     * @memberof BezierPath
     * @return {string} The reduced list representation of this path.
     **/
    BezierPath.prototype.toReducedListRepresentation = function( digits ) {
	
	if( typeof digits == "undefined" )
	    digits = 1;
	
	var buffer = [];
	//var digits = 1;
	buffer.push( "[" ); // array begin
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    
	    var curve = this.bezierCurves[i];
	    buffer.push( BezierPath._roundToDigits(curve.getStartPoint().x,digits,false) );
	    buffer.push( "," );
	    buffer.push( BezierPath._roundToDigits(curve.getStartPoint().y,digits,false) );
	    buffer.push( "," );

	    buffer.push( BezierPath._roundToDigits(curve.getStartControlPoint().x,digits,false) );
	    buffer.push( "," );
	    buffer.push( IKRS.BezierPath._roundToDigits(curve.getStartControlPoint().y,digits,false) );
	    buffer.push( "," );
	    
	    buffer.push( BezierPath._roundToDigits(curve.getEndControlPoint().x,digits,false) );
	    buffer.push( "," );
	    buffer.push( BezierPath._roundToDigits(curve.getEndControlPoint().y,digits,false) );
	    buffer.push( "," );		

	}
	if( this.bezierCurves.length != 0 ) {
	    var curve = this.bezierCurves[ this.bezierCurves.length-1 ];
	    buffer.push( BezierPath._roundToDigits(curve.getEndPoint().x,digits,false) );
	    buffer.push( "," );
	    buffer.push( BezierPath._roundToDigits(curve.getEndPoint().y,digits,false) );
	}
	buffer.push( "]" ); // array end
	
	return buffer.join( "" ); // Convert to string, with empty separator.
    };


    /**
     * Parse a BezierPath instance from the reduced list representation.<br>
     * <br>
     * The passed string must represent a JSON array containing numbers only.
     *
     * @method fromReducedListRepresentation
     * @param {string} listJSON - The number of digits to be used after the floating point.
     * @throw An error if the string is malformed.
     * @instance
     * @memberof BezierPath
     * @return {BezierPath} The bezier path instance retrieved from the string.
     **/
    BezierPath.fromReducedListRepresentation = function( listJSON ) {

	// Parse the array
	var pointArray = JSON.parse( listJSON );

	if( !pointArray.length ) {
	    console.log( "Cannot parse bezier path from non-array object nor from empty point list." );
	    throw "Cannot parse bezier path from non-array object nor from empty point list.";
	}
	
	if( pointArray.length < 8 ) {
	    console.log( "Cannot build bezier path. The passed array must contain at least 8 elements (numbers)." );
	    throw "Cannot build bezier path. The passed array must contain at least 8 elements (numbers).";
	}

	// Convert to object
	var bezierPath = new BezierPath( null ); // No points yet
        
	var startPoint        = null; // new THREE.Vector2( pointArray[i], pointArray[i+1] );
	var startControlPoint = null; // new THREE.Vector2( pointArray[i+2], pointArray[i+3] );
	var endControlPoint   = null; // new THREE.Vector2( pointArray[i+4], pointArray[i+5] );
	var endPoint          = null; // new THREE.Vector2( pointArray[i+6], pointArray[i+7] );
	var i = 0;

	//for( var i = 0; i < pointArray.length; i+=3 ) {
	do {
	    
	    if( i == 0 )
		startPoint        = new Vertex( pointArray[i], pointArray[i+1] );
	    startControlPoint = new Vertex( pointArray[i+2], pointArray[i+3] );
	    endControlPoint   = new Vertex( pointArray[i+4], pointArray[i+5] );
	    endPoint          = new Vertex( pointArray[i+6], pointArray[i+7] );

	    var bCurve =  new CubicBezierCurve( startPoint,
						endPoint,
						startControlPoint,
						endControlPoint
					      );
	    bezierPath.bezierCurves.push( bCurve );

	    startPoint = endPoint;
	    
	    i += 6;

	} while( i+2 < pointArray.length );

	bezierPath.updateArcLengths();


	return bezierPath;
    };


    /**
     * A helper function.
     *
     * @method _roundToDigits
     * @param {number} number - 
     * @param {number} digits -
     * @param {boolean} enforceInvisibleDigits -
     * @private
     * @memberof BezierPath
     * @return {string}
     **/
    // !!! TODO: isn't Number.toFixed(...) doing this job???
    BezierPath._roundToDigits = function( number, digits, enforceInvisibleDigits ) {
	if( digits <= 0 )
	    return Math.round(number); 

	var magnitude = Math.pow( 10, digits ); // This could be LARGE :/
	number = Math.round( number * magnitude );
	var result = "" + (number  /  magnitude);
	var index = result.lastIndexOf(".");
	if( index == -1 ) {	
	    //result += ".0";
	    index = result.length;
	}
	if( enforceInvisibleDigits ) {
	    var digitsAfterPoint = result.length - index - 1;
	    var digitsMissing    = enforceInvisibleDigits - digitsAfterPoint;
	    while( digitsMissing-- > 0 )
		result += "&nbsp;";
	}
	
	return result;
    };

    _context.BezierPath = BezierPath;
})(window);