Source: CubicBezierCurve.js

/**
 * @classdesc A refactored cubic bezier curve class.
 *
 * @requires Vertex
 *
 * @author   Ikaros Kappler
 * @date     2013-08-15
 * @modified 2018-08-16 Added a closure. Removed the wrapper class 'IKRS'. Replaced class THREE.Vector2 by Vertex class.
 * @modified 2018-11-19 Added the fromArray(Array) function.
 * @modified 2018-11-28 Added the locateCurveByPoint(Vertex) function.
 * @modified 2018-12-04 Added the toSVGPathData() function.
 * @modified 2019-03-20 Added JSDoc tags.
 * @modified 2019-03-23 Changed the signatures of getPoint, getPointAt and getTangent (!version 2.0).
 * @version  2.0.0
 *
 * @file CubicBezierCurve
 * @public
 **/


(function(_context) {
    'use strict';

    /**
     * The constructor.
     *
     * @constructor
     * @name CubicBezierCurve
     * @param {Vertex} startPoint - The Bézier curve's start point.
     * @param {Vertex} endPoint   - The Bézier curve's end point.
     * @param {Vertex} startControlPoint - The Bézier curve's start control point.
     * @param {Vertex} endControlPoint   - The Bézier curve's end control point.
     **/
    var CubicBezierCurve = function ( startPoint,
				      endPoint,
				      startControlPoint,
				      endControlPoint
				    ) {		
	
	this.startPoint         = startPoint;
	this.startControlPoint  = startControlPoint;
	this.endPoint           = endPoint;
	this.endControlPoint    = endControlPoint;	
	this.curveIntervals     = 30;
	// An array of points
	this.segmentCache       = [];
	// An array of floats
	this.segmentLengths     = [];
	// float
	this.arcLength          = null;
	
	this.updateArcLengths();
    };

    /** @constant {number} */
    CubicBezierCurve.START_POINT         = 0;
    /** @constant {number} */
    CubicBezierCurve.START_CONTROL_POINT = 1;
    /** @constant {number} */
    CubicBezierCurve.END_CONTROL_POINT   = 2;
    /** @constant {number} */
    CubicBezierCurve.END_POINT           = 3;

    
    // CubicBezierCurve.prototype = new Object();
    CubicBezierCurve.prototype.constructor = CubicBezierCurve; 

    /** @constant {number} */
    CubicBezierCurve.prototype.START_POINT         = 0;
    /** @constant {number} */
    CubicBezierCurve.prototype.START_CONTROL_POINT = 1;
    /** @constant {number} */
    CubicBezierCurve.prototype.END_CONTROL_POINT   = 2;
    /** @constant {number} */
    CubicBezierCurve.prototype.END_POINT           = 3;

    
    /**
     * Move the given curve point (the start point, end point or one of the two
     * control points).
     *
     * @method moveCurvePoint
     * @param {number} pointID - The numeric identicator of the point to move. Use one of the four constants.
     * @param {Vertex} moveAmount - The amount to move the specified point by.
     * @param {boolean} moveControlPoint - Move the control points along with their path point (if specified point is a path point).
     * @param {boolean} updateArcLengths - Specifiy if the internal arc segment buffer should be updated.
     * @instance
     * @memberof CubicBezierCurve
     * @return {void}
     **/
    CubicBezierCurve.prototype.moveCurvePoint = function( pointID,           // int
							  moveAmount,        // Vertex
							  moveControlPoint,  // boolean
							  updateArcLengths   // boolean
							) {
	if( pointID == this.START_POINT ) {
	    this.getStartPoint().add( moveAmount );
	    if( moveControlPoint )
		this.getStartControlPoint().add( moveAmount );

	} else if( pointID == this.START_CONTROL_POINT ) {
	    this.getStartControlPoint().add( moveAmount );

	} else if( pointID == this.END_CONTROL_POINT ) {
	    this.getEndControlPoint().add( moveAmount );

	} else if( pointID == this.END_POINT ) {
	    this.getEndPoint().add( moveAmount );
	    if( moveControlPoint )
		this.getEndControlPoint().add( moveAmount );

	} else {
	    console.log( "[IKRS.CubicBezierCurve.moveCurvePoint] pointID '" + pointID +"' invalid." );
	}
	
	if( updateArcLengths )
	    this.updateArcLengths();
    }



    /**
     * Translate the whole curve by the given {x,y} amount: moves all four points.
     *
     * @method translate
     * @param {Vertex} amount - The amount to translate this curve by.
     * @instance
     * @memberof CubicBezierCurve
     * @return {CubicBezierCurve} this
     **/
    CubicBezierCurve.prototype.translate = function( amount ) {
	this.startPoint.add( amount );
	this.startControlPoint.add( amount );
	this.endControlPoint.add( amount );
	this.endPoint.add( amount );   
    };


    // THIS SEEMS NOT TO BE IN USE
    /*
    CubicBezierCurve._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 total curve length.<br>
     * <br> 
     * As not all Bézier curved have a closed formula to calculate their lengths, this
     * implementation uses a segment buffer (with a length of 30 segments). So the 
     * returned length is taken from the arc segment buffer.<br>
     * <br>
     * Note that if the curve points were changed and the segment buffer was not
     * updated this function might return wrong (old) values.
     *
     * @method getLength
     * @instance
     * @memberof CubicBezierCurve
     * @return {number} >= 0
     **/
    CubicBezierCurve.prototype.getLength = function() {
	return this.arcLength;
    };


    // +---------------------------------------------------------------------------------
    // | Get the curve index for the given start or end point.
    // |
    // | If the passed vertex does not equal the start- nor the end-point this function
    // | returns -1.
    // |
    // | @param vert:Vertex The start or end point to search for.
    // | @return Number The curve index.
    // +-------------------------------
    /*
    CubicBezierCurve.prototype.locateCurveByPoint = function( vert ) {
	for( var i = 0; i < this.bezierCurves.length; i++ ) {
	    if( this.bezierCurves[i].startPoint.equals(vert) || this.bezierCurves[i].endPoint.equals(vert) )
		return i;
	}
	return -1;
    };
    */

    
    // +---------------------------------------------------------------------------------
    // |  This function computes the area size of this bezier curve in an y-axis 
    // |  integrational approach.
    // | 
    // |  For each bezier segment (which are linear segments) the distance to a given
    // |  relative Y axis is computed (position of Y axis specified by 'relativeX'
    // |  parameter).
    // | 
    // |  Each resulting sub area has a determined segment height and thus a determined
    // |  area size. The sum of all segment area sizes is returned.
    // +-------------------------------
    // NOT IN USE.
    /*
    CubicBezierCurve.prototype.computeVerticalAreaSize = function( relativeX,
								   deltaSize, 
								   useAbsoluteValues 
								 ) {
	
	if( deltaSize == 0 )
	    throw "Cannot compute bezier curve's vertical area size with delta=0.";
	
	if( this.segmentCache.length <= 1 )
	    return 0.0;


	var size = 0.0;
	for( var i = 0; i+1 < this.segmentCache.length; i++ ) {

	    size += this._computeVerticalAreaSizeForSegment( relativeX,
							     deltaSize,
							     useAbsoluteValues,
							     i
							   );

	}

	return size;
    };
    */

    /**
     * This helper function computes the area size of the given segment (param segmentIndex).
     **/
    // NOT IN USE
    /*
    CubicBezierCurve.prototype._computeVerticalAreaSizeForSegment = function( relativeX,
									      deltaSize, 
									      useAbsoluteValues, 
									      segmentIndex 
									    ) {

	// Two points make a segment.
	// So at least two points must be available. Otherwise there is no area (size=0).
	if( segmentIndex+1 >= this.segmentCache.length )
	    return 0.0;

	var segmentA      = this.segmentCache[ segmentIndex ];
	var segmentB      = this.segmentCache[ segmentIndex+1 ];
	var segmentHeight = segmentB.y - segmentA.y;
	
	
	var relativeA = relativeX - segmentA.x;
	var relativeB = relativeX - segmentB.x;
	var averageX = relativeB + (relativeA - relativeB) / 2.0;
        
	if( useAbsoluteValues )
	    return Math.abs( segmentHeight * averageX );
	else
	    return segmentHeight * averageX;              // May be negative
	
    };
    */
    

    /**
     * This function computes the volume size of that revolution solid which outline
     * is determined by the bezier curve.
     *
     * The calculation uses the segments area sizes to compute each layer's volume.
     **/
    // NOT IN USE
    /*
    CubicBezierCurve.prototype.computeVerticalRevolutionVolumeSize = function( relativeX,
									       //deltaSize, 
									       useAbsoluteValues 
									     ) {
		
	if( this.segmentCache.length <= 1 )
	    return 0.0;


	var volume = 0.0;
	for( var i = 0; i+1 < this.segmentCache.length; i++ ) {

	    volume += this._computeVerticalRevolutionVolumeSizeForSegment( relativeX,
									   //deltaSize,
									   useAbsoluteValues,
									   i
									 );

	}

	return volume;
    };
    */

    /**
     * This helper function computes the area size of the given segment (param segmentIndex).
     **/
    // NOT IN USE
    /*
    CubicBezierCurve.prototype._computeVerticalRevolutionVolumeSizeForSegment = function( relativeX,
											  //deltaSize, 
											  useAbsoluteValues, 
											  segmentIndex 
											) {

	// Two points make a segment.
	// So at least two points must be available. Otherwise there is no area (size=0).
	if( segmentIndex+1 >= this.segmentCache.length )
	    return 0.0;

	var segmentA      = this.segmentCache[ segmentIndex ];
	var segmentB      = this.segmentCache[ segmentIndex+1 ];
	var segmentHeight = segmentB.y - segmentA.y;
	
	
	var relativeA = relativeX - segmentA.x;
	var relativeB = relativeX - segmentB.x;
	//var averageX  = relativeB + (relativeA - relativeB) / 2.0;
	var averageX  = (relativeA + relativeB) / 2.0;

	// Volume is PI * square(radius) * height
	var volume    = Math.PI * Math.pow(averageX,2) * segmentHeight;

	if( useAbsoluteValues )
	    return Math.abs( volume );
	else
	    return volume;              // May be negative
	
    };
    */


    
    /**
     * Uptate the internal arc segment buffer and their lengths.<br>
     * <br>
     * All class functions update the buffer automatically; if any
     * curve point is changed by other reasons you should call this
     * function to keep actual values in the buffer.
     *
     * @method updateArcLengths
     * @instance
     * @memberof CubicBezierCurve
     * @return {void}
     **/
    CubicBezierCurve.prototype.updateArcLengths = function() {
	var 
	pointA = new Vertex( this.startPoint.x, this.startPoint.y ),
	pointB = new Vertex( 0, 0 ),
	curveStep = 1.0/this.curveIntervals;
	
	var   u = curveStep; 
	// Clear segment cache
	this.segmentCache = [];
	// Push start point into buffer
	this.segmentCache.push( this.startPoint );	
	this.segmentLengths = [];	
	this.arcLength = 0.0;

	for( var i = 0; i < this.curveIntervals; i++) {	    
	    pointB = this.getPoint( (i+1) * curveStep );  // parameter is 'u' (not 't')
	    
	    // Store point into cache
	    this.segmentCache.push( pointB ); 

	    // Calculate segment length
	    var tmpLength = Math.sqrt( Math.pow(pointA.x-pointB.x,2) + Math.pow(pointA.y-pointB.y,2) );
	    this.segmentLengths.push( tmpLength );
	    this.arcLength += tmpLength;
	    
	    pointA = pointB;
            u += curveStep;
	} // END for
    }; // END function


    /**
     * Get the start point of the curve.<br>
     * <br>
     * This function just returns this.startPoint.
     *
     * @method getStartPoint
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} this.startPoint
     **/
    CubicBezierCurve.prototype.getStartPoint = function() {
	return this.startPoint;
    };

    /**
     * Get the end point of the curve.<br>
     * <br>
     * This function just returns this.endPoint.
     *
     * @method getEndPoint
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} this.endPoint
     **/
    CubicBezierCurve.prototype.getEndPoint = function() {
	return this.endPoint;
    };

    /**
     * Get the start control point of the curve.<br>
     * <br>
     * This function just returns this.startControlPoint.
     *
     * @method getStartControlPoint
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} this.startControlPoint
     **/
    CubicBezierCurve.prototype.getStartControlPoint = function() {
	return this.startControlPoint;
    };

    /**
     * Get the end control point of the curve.<br>
     * <br>
     * This function just returns this.endControlPoint.
     *
     * @method getEndControlPoint
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} this.endControlPoint
     **/
    CubicBezierCurve.prototype.getEndControlPoint = function() {
	return this.endControlPoint;
    };


    /**
     * Get one of the four curve points specified by the passt point ID.
     *
     * @method getEndControlPoint
     * @param {number} id - One of START_POINT, START_CONTROL_POINT, END_CONTROL_POINT or END_POINT.
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} 
     **/
    CubicBezierCurve.prototype.getPointByID = function( id ) {
	if( id == this.START_POINT ) return this.startPoint;
	if( id == this.END_POINT ) return this.endPoint;
	if( id == this.START_CONTROL_POINT ) return this.startControlPoint;
	if( id == this.END_CONTROL_POINT ) return this.endControlPoint;
	throw "Invalid point ID '" + id +"'.";
    };


    /**
     * Get the curve point at a given position t, where t is in [0,1].<br>
     * <br>
     * @see Line.pointAt
     *
     * @method getPointAt
     * @param {number} t - The position on the curve in [0,1] (0 means at 
     *                     start point, 1 means at end point, other values address points in bertween).
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} 
     **/
    CubicBezierCurve.prototype.getPointAt = function( t ) {
	// Perform some powerful math magic
	var x = this.startPoint.x * Math.pow(1.0-t,3) + this.startControlPoint.x*3*t*Math.pow(1.0-t,2)
	    + this.endControlPoint.x*3*Math.pow(t,2)*(1.0-t)+this.endPoint.x*Math.pow(t,3);
	var y = this.startPoint.y*Math.pow(1.0-t,3)+this.startControlPoint.y*3*t*Math.pow(1.0-t,2)
	    + this.endControlPoint.y*3*Math.pow(t,2)*(1.0-t)+this.endPoint.y*Math.pow(t,3);
	return new Vertex( x, y );
    };


    /**
     * Get the curve point at a given position u, where u is in [0,arcLength].<br>
     * <br>
     * @see CubicBezierCurve.getPointAt
     *
     * @method getPoint
     * @param {number} u - The position on the curve in [0,arcLength] (0 means at 
     *                     start point, arcLength means at end point, other values address points in bertween).
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} 
     **/
    CubicBezierCurve.prototype.getPoint = function( u ) {  
	return this.getPointAt( u / this.arcLength );
    };


    /**
     * Get the curve tangent vector at a given absolute curve position t in [0,1].<br>
     * <br>
     * Note that the returned tangent vector (end point) is not normalized.
     *
     * @method getTangent
     * @param {number} t - The position on the curve in [0,1].
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} 
     **/
    CubicBezierCurve.prototype.getTangentAt = function( t ) {
	var a = this.getStartPoint();
	var b = this.getStartControlPoint();
	var c = this.getEndControlPoint();
	var d = this.getEndPoint();
	
	// This is the shortened one
	var t2 = t * t;
	var t3 = t * t2;
	// (1 - t)^2 = (1-t)*(1-t) = 1 - t - t + t^2 = 1 - 2*t + t^2
	var nt2 = 1 - 2*t + t2;

	var tX = -3 * a.x * nt2 + 
	    b.x * (3 * nt2 - 6 *(t-t2) ) +
	    c.x * (6 *(t-t2) - 3*t2) +
	    3*d.x*t2;
	var tY = -3 * a.y * nt2 + 
	    b.y * (3 * nt2 - 6 *(t-t2) ) +
	    c.y * (6 *(t-t2) - 3*t2) +
	    3*d.y*t2;
	
	// Note: my implementation does NOT normalize tangent vectors!
	return new Vertex( tX, tY );
    }


    /**
     * Convert a relative curve position u to the absolute curve position t.
     *
     * @method convertU2t
     * @param {number} u - The relative position on the curve in [0,arcLength].
     * @instance
     * @memberof CubicBezierCurve
     * @return {number} 
     **/
    CubicBezierCurve.prototype.convertU2T = function( u ) {
	return Math.max( 0.0, 
			 Math.min( 1.0, 
				   ( u / this.arcLength ) 
				 )
		       );
    }

    
    /**
     * Get the curve tangent vector at a given relative position u in [0,arcLength].<br>
     * <br>
     * Note that the returned tangent vector (end point) is not normalized.
     *
     * @method getTangent
     * @param {number} u - The position on the curve in [0,arcLength].
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} 
     **/
    //CubicBezierCurve.prototype.getTangentAt = function( u ) {
    CubicBezierCurve.prototype.getTangent = function( u ) {
	// return this.getTangent( this.convertU2T(u) );
	return this.getTangentAt( this.convertU2T(u) );
    }
    

    /**
     * Get the curve perpendicular at a given relative position u in [0,arcLength] as a vector.<br>
     * <br>
     * Note that the returned vector (end point) is not normalized.
     *
     * @method getPerpendicular
     * @param {number} u - The relative position on the curve in [0,arcLength].
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} 
     **/
    //CubicBezierCurve.prototype.getPerpendicularAt = function( u ) {
    CubicBezierCurve.prototype.getPerpendicular = function( u ) {
	//return this.getPerpendicular( this.convertU2T(u) );
	return this.getPerpendicularAt( this.convertU2T(u) );
    }


    /**
     * Get the curve perpendicular at a given absolute position t in [0,1] as a vector.<br>
     * <br>
     * Note that the returned vector (end point) is not normalized.
     *
     * @method getPerpendicularAt
     * @param {number} u - The absolute position on the curve in [0,1].
     * @instance
     * @memberof CubicBezierCurve
     * @return {Vertex} 
     **/
    //CubicBezierCurve.prototype.getPerpendicular = function( t ) {
    CubicBezierCurve.prototype.getPerpendicularAt = function( t ) {
	//var tangentVector = this.getTangent( t );
	var tangentVector = this.getTangentAt( t );
	var perpendicular = new Vertex( tangentVector.y, - tangentVector.x );
	return perpendicular;
    }


    // NOT IN USE
    /*
    CubicBezierCurve.prototype.computeBoundingBox = function() {
	return BoundingBox2.computeFromPoints( this.segmentCache );
    }
    */


    /**
     * Clone this Bézier curve (deep clone).
     *
     * @method clone
     * @instance
     * @memberof CubicBezierCurve
     * @return {CubicBezierCurve} 
     **/
    CubicBezierCurve.prototype.clone = function() {
	var curve = new CubicBezierCurve( this.getStartPoint().clone(),
					  this.getEndPoint().clone(),
					  this.getStartControlPoint().clone(),
					  this.getEndControlPoint().clone()
					);
	return curve;
    }


    /**
     * Check if this and the specified curve are equal.<br>
     * <br>
     * All four points need to be equal for this, the Vertex.equals function is used.<br>
     * <br>
     * Please note that this function is not type safe (comparison with any object will fail).
     *
     * @method clone
     * @param {CubicBezierCurve} curve - The curve to compare with.
     * @instance
     * @memberof CubicBezierCurve
     * @return {boolean} 
     **/
    CubicBezierCurve.prototype.equals = function( curve ) {	
	if( !curve )
	    return false;	
	if( !curve.startPoint ||
	    !curve.endPoint ||
	    !curve.startControlPoint ||
	    !curve.endControlPoint )
	    return false;	
	return this.startPoint.equals(curve.startPoint) 
	    && this.endPoint.equals(curve.endPoint)
	    && this.startControlPoint.equals(curve.startControlPoint)
	    && this.endControlPoint.equals(curve.endControlPoint);
	
    }


  
    /**
     * Create an SVG path data representation of this bézier curve.
     *
     * Path data string format is:<br>
     *  <pre>'M x0 y1 C dx0 dy1 dx1 dy1 x1 x2'</pre><br>
     * or in other words<br>
     *   <pre>'M startoint.x startPoint.y C startControlPoint.x startControlPoint.y endControlPoint.x endControlPoint.y endPoint.x endPoint.y'</pre>
     *
     * @method toSVGPathData
     * @instance
     * @memberof CubicBezierCurve
     * @return {string}  The SVG path data string.
     **/
    CubicBezierCurve.prototype.toSVGPathData = function() {
	var buffer = [];
	buffer.push( 'M ' );
	buffer.push( this.startPoint.x );
	buffer.push( ' ' );
	buffer.push( this.startPoint.y );
	buffer.push( ' C ' );
	buffer.push( this.startControlPoint.x );
	buffer.push( ' ' );
	buffer.push( this.startControlPoint.y );
	buffer.push( ' ' );
	buffer.push( this.endControlPoint.x );
	buffer.push( ' ' );
	buffer.push( this.endControlPoint.y );
	buffer.push( ' ' );
	buffer.push( this.endPoint.x );
	buffer.push( ' ' );
	buffer.push( this.endPoint.y );
	return buffer.join('');
    }


    /**
     * Convert this curve to a JSON string.
     *
     * @method toJSON
     * @param {boolean=} [prettyFormat=false] - If set to true the function will add line breaks.
     * @instance
     * @memberof CubicBezierCurve
     * @return {string} The JSON data.
     **/
    CubicBezierCurve.prototype.toJSON = function( prettyFormat ) {
	var jsonString = "{ " + // begin object
            ( prettyFormat ? "\n\t" : "" ) +
	    "\"startPoint\" : [" + this.getStartPoint().x + "," + this.getStartPoint().y + "], " +
	    ( prettyFormat ? "\n\t" : "" ) +
	    "\"endPoint\" : [" + this.getEndPoint().x + "," + this.getEndPoint().y + "], " +
	    ( prettyFormat ? "\n\t" : "" ) +
	    "\"startControlPoint\": [" + this.getStartControlPoint().x + "," + this.getStartControlPoint().y + "], " +
	    ( prettyFormat ? "\n\t" : "" ) +
	    "\"endControlPoint\" : [" + this.getEndControlPoint().x + "," + this.getEndControlPoint().y + "]" +
	    ( prettyFormat ? "\n\t" : "" ) +
	    " }";  // end object
	return jsonString;
    }

    
    /**
     * Parse a Bézier curve from the given JSON string.
     *
     * @method fromJSON
     * @param {string} jsonString - The JSON data to parse.
     * @memberof CubicBezierCurve
     * @throws An exception if the JSON string is malformed.
     * @return {CubicBezierCurve}
     **/
    CubicBezierCurve.fromJSON = function( jsonString ) {
	var obj = JSON.parse( jsonString );
	return CubicBezierCurve.fromObject( obj );
    }


    /**
     * Try to convert the passed object to a CubicBezierCurve.
     *
     * @method fromObject
     * @param {object} obj - The object to convert.
     * @memberof CubicBezierCurve
     * @throws An exception if the passed object is malformed.
     * @return {CubicBezierCurve}
     **/
    CubicBezierCurve.fromObject = function( obj ) {
	
	if( typeof obj !== "object" ) 
	    throw "[IKRS.CubicBezierCurve.fromObject] Can only build from object.";


	if( !obj.startPoint )
	    throw "[IKRS.CubicBezierCurve.fromObject] Object member \"startPoint\" missing.";
	if( !obj.endPoint )
	    throw "[IKRS.CubicBezierCurve.fromObject] Object member \"endPoint\" missing.";
	if( !obj.startControlPoint )
	    throw "[IKRS.CubicBezierCurve.fromObject] Object member \"startControlPoint\" missing.";
	if( !obj.endControlPoint )
	    throw "[IKRS.CubicBezierCurve.fromObject] Object member \"endControlPoint\" missing.";
	
	return new CubicBezierCurve( new Vertex(obj.startPoint[0],        obj.startPoint[1]),
				     new Vertex(obj.endPoint[0],          obj.endPoint[1]),
				     new Vertex(obj.startControlPoint[0], obj.startControlPoint[1]),
				     new Vertex(obj.endControlPoint[0],   obj.endControlPoint[1])
				   );
    };


    
    /**
     * Convert a 4-element array of vertices to a cubic bézier curve.
     *
     * @method fromArray
     * @param {Vertex[]} arr -  [ startVertex, endVertex, startControlVertex, endControlVertex ]
     * @memberof CubicBezierCurve
     * @throws An exception if the passed array is malformed.
     * @return {CubicBezierCurve}
     **/
    CubicBezierCurve.fromArray = function( arr ) {	
	if( !Array.isArray(arr) ) 
	    throw "[IKRS.CubicBezierCurve.fromArray] Can only build from object.";
	if( arr.length != 4 )
	    throw "[IKRS.CubicBezierCurve.fromArray] Can only build from array with four elements.";
	return new CubicBezierCurve( arr[0],
				     arr[1],
				     arr[2],
				     arr[3]
				   );
    };

    _context.CubicBezierCurve = CubicBezierCurve;
    
})(window); // END closure