Dart DocumentationthreeCurvePath

CurvePath class

@author zz85 / http://www.lab4games.net/zz85/blog


Curved Path - a curve path is simply a array of connected

curves, but retains the api of a curve


class CurvePath extends Curve {

	List curves;
	List _bends;

	bool autoClose; // Automatically closes the path

	List cacheLengths = null;

	CurvePath()
		:	curves = [],
			_bends = [],
			autoClose = false,
			super();

	add( curve ) => curves.add( curve );

	checkConnection() {
		// TODO
		// If the ending of curve is not connected to the starting
		// or the next curve, then, this is not a real path
	}

	closePath() {
		// TODO Test
		// and verify for vector3 (needs to implement equals)
		// Add a line curve if start and end of lines are not connected
		var startPoint = curves[0].getPoint(0);
		var endPoint = curves[curves.length-1].getPoint(1);

		if (!startPoint.equals(endPoint)) {
			this.curves.add( new LineCurve(endPoint, startPoint) );
		}

	}

	// To get accurate point with reference to
	// entire path distance at time t,
	// following has to be done:

	// 1. Length of each sub path have to be known
	// 2. Locate and identify type of curve
	// 3. Get t for the curve
	// 4. Return curve.getPointAt(t')
	getPoint( num t ) {

		var d = t * this.length;
		var curveLengths = this.getCurveLengths();
		var i = 0, diff;
		Curve curve;

		// To think about boundaries points.

		while ( i < curveLengths.length ) {

			if ( curveLengths[ i ] >= d ) {

				diff = curveLengths[ i ] - d;
				curve = this.curves[ i ];

				var u = 1 - diff / curve.length;

				return curve.getPointAt( u );

			}

			i ++;

		}

		return null;

		// loop where sum != 0, sum > d , sum+1 <d

	}


	// We cannot use the default THREE.Curve getPoint() with getLength() because in
	// THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
	// getPoint() depends on getLength
	num get length => getCurveLengths().last;

	// Compute lengths and cache them
	// We cannot overwrite getLengths() because UtoT mapping uses it.

	List<num> getCurveLengths() {

		// We use cache values if curves and cache array are same length

		if ( this.cacheLengths != null && this.cacheLengths.length == this.curves.length ) {

			return this.cacheLengths;

		}

		// Get length of subsurve
		// Push sums into cached array

		var lengths = [], sums = 0;
		var i, il = this.curves.length;

		for ( i = 0; i < il; i ++ ) {

			sums += this.curves[ i ].length;
			lengths.add( sums );

		}

		this.cacheLengths = lengths;

		return lengths;

	}


	// Returns min and max coordinates, as well as centroid
	getBoundingBox() {

		var points = getPoints();

		var maxX, maxY, maxZ;
		var minX, minY, minZ;

		maxX = maxY = double.NEGATIVE_INFINITY;
		minX = minY = double.INFINITY;

		var p, i, sum;

		var v3 = points[0] is Vector3;

		sum = (v3) ? new Vector3.zero() : new Vector2.zero();

		int il = points.length;
		for ( i = 0; i < il; i ++ ) {

			p = points[ i ];

			if ( p.x > maxX ) { maxX = p.x;
			} else if ( p.x < minX ) minX = p.x;

			if ( p.y > maxY ) { maxY = p.y;
			} else if ( p.y < minY ) minY = p.y;

			if (v3) {
			  p = p as Vector3;
	      if ( p.z > maxZ ) { maxZ = p.z;
	      } else if ( p.z < minZ ) minZ = p.z;

	      (sum as Vector3).add( p );
	    } else {
	      (sum as Vector2).add( p );
	    }



		}

		var ret = {

			"minX": minX,
			"minY": minY,
			"maxX": maxX,
			"maxY": maxY,
			"centroid": (sum as dynamic).scale( 1.0 / il )

		};

		if (v3) {

	    ret["maxZ"] = maxZ;
	    ret["minZ"] = minZ;

	  }

	  return ret;
	}

	/**************************************************************
	 *	Create Geometries Helpers
	 **************************************************************/

	/// Generate geometry from path points (for Line or ParticleSystem objects)
	createPointsGeometry( {divisions} ) {
		var pts = this.getPoints( divisions, true );
		return this.createGeometry( pts );
	}

	// Generate geometry from equidistance sampling along the path
	createSpacedPointsGeometry( [divisions] ) {
		var pts = this.getSpacedPoints( divisions, true );
		return this.createGeometry( pts );
	}

	createGeometry( points ) {

		var geometry = new Geometry();

		for ( var i = 0; i < points.length; i ++ ) {
		  var z = (points[i] is Vector3) ? points[ i ].z : 0.0;
			geometry.vertices.add( new Vector3( points[ i ].x, points[ i ].y, z) );
		}

		return geometry;
	}


	/**************************************************************
	 *	Bend / Wrap Helper Methods
	 **************************************************************/

	// Wrap path / Bend modifiers?

	addWrapPath( bendpath ) => _bends.add( bendpath );

	getTransformedPoints( segments, {List bends: null} ) {

		var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints
		var i, il;

		if (bends == null) {
			bends = _bends;
		}

		for ( i = 0; i < bends.length; i ++ ) {
			oldPts = this.getWrapPoints( oldPts, bends[ i ] );
		}

		return oldPts;

	}

	getTransformedSpacedPoints( [num segments, List bends = null] ) {

		var oldPts = getSpacedPoints( segments );

		var i, il;

		if (bends == null) {
			bends = _bends;
		}

		for ( i = 0; i < bends.length; i ++ ) {
			oldPts = this.getWrapPoints( oldPts, bends[ i ] );
		}

		return oldPts;
	}

	// This returns getPoints() bend/wrapped around the contour of a path.
	// Read http://www.planetclegg.com/projects/WarpingTextToSplines.html

	getWrapPoints( oldPts, path ) {

		var bounds = getBoundingBox();

		var i, il, p, oldX, oldY, xNorm;

		for ( i = 0; i < oldPts.length; i ++ ) {

			p = oldPts[ i ];

			oldX = p.x;
			oldY = p.y;

			xNorm = oldX / bounds.maxX;

			// If using actual distance, for length > path, requires line extrusions
			//xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance

			xNorm = path.getUtoTmapping( xNorm, oldX );

			// check for out of bounds?

			var pathPt = path.getPoint( xNorm );
			var normal = path.getNormalVector( xNorm ).scale( oldY );

			p.x = pathPt.x + normal.x;
			p.y = pathPt.y + normal.y;

		}

		return oldPts;

	}
}

Extends

Curve > CurvePath

Subclasses

Path

Constructors

new CurvePath() #

Creates a new Object instance.

Object instances have no meaningful state, and are only useful through their identity. An Object instance is equal to itself only.

docs inherited from Object
CurvePath()
		:	curves = [],
			_bends = [],
			autoClose = false,
			super();

Properties

bool autoClose #

bool autoClose

List cacheArcLengths #

inherited from Curve
List cacheArcLengths = null

List cacheLengths #

List cacheLengths = null

List curves #

List curves

final num length #

num get length => getCurveLengths().last;

bool needsUpdate #

inherited from Curve
bool needsUpdate = false

Methods

dynamic add(curve) #

add( curve ) => curves.add( curve );

dynamic addWrapPath(bendpath) #


Bend / Wrap Helper Methods

addWrapPath( bendpath ) => _bends.add( bendpath );

dynamic checkConnection() #

checkConnection() {
		// TODO
		// If the ending of curve is not connected to the starting
		// or the next curve, then, this is not a real path
	}

dynamic closePath() #

closePath() {
		// TODO Test
		// and verify for vector3 (needs to implement equals)
		// Add a line curve if start and end of lines are not connected
		var startPoint = curves[0].getPoint(0);
		var endPoint = curves[curves.length-1].getPoint(1);

		if (!startPoint.equals(endPoint)) {
			this.curves.add( new LineCurve(endPoint, startPoint) );
		}

	}

dynamic createGeometry(points) #

createGeometry( points ) {

		var geometry = new Geometry();

		for ( var i = 0; i < points.length; i ++ ) {
		  var z = (points[i] is Vector3) ? points[ i ].z : 0.0;
			geometry.vertices.add( new Vector3( points[ i ].x, points[ i ].y, z) );
		}

		return geometry;
	}

dynamic createPointsGeometry({divisions}) #


Create Geometries Helpers

Generate geometry from path points (for Line or ParticleSystem objects)

createPointsGeometry( {divisions} ) {
		var pts = this.getPoints( divisions, true );
		return this.createGeometry( pts );
	}

dynamic createSpacedPointsGeometry([divisions]) #

createSpacedPointsGeometry( [divisions] ) {
		var pts = this.getSpacedPoints( divisions, true );
		return this.createGeometry( pts );
	}

dynamic getBoundingBox() #

getBoundingBox() {

		var points = getPoints();

		var maxX, maxY, maxZ;
		var minX, minY, minZ;

		maxX = maxY = double.NEGATIVE_INFINITY;
		minX = minY = double.INFINITY;

		var p, i, sum;

		var v3 = points[0] is Vector3;

		sum = (v3) ? new Vector3.zero() : new Vector2.zero();

		int il = points.length;
		for ( i = 0; i < il; i ++ ) {

			p = points[ i ];

			if ( p.x > maxX ) { maxX = p.x;
			} else if ( p.x < minX ) minX = p.x;

			if ( p.y > maxY ) { maxY = p.y;
			} else if ( p.y < minY ) minY = p.y;

			if (v3) {
			  p = p as Vector3;
	      if ( p.z > maxZ ) { maxZ = p.z;
	      } else if ( p.z < minZ ) minZ = p.z;

	      (sum as Vector3).add( p );
	    } else {
	      (sum as Vector2).add( p );
	    }



		}

		var ret = {

			"minX": minX,
			"minY": minY,
			"maxX": maxX,
			"maxY": maxY,
			"centroid": (sum as dynamic).scale( 1.0 / il )

		};

		if (v3) {

	    ret["maxZ"] = maxZ;
	    ret["minZ"] = minZ;

	  }

	  return ret;
	}

List<num> getCurveLengths() #

List<num> getCurveLengths() {

		// We use cache values if curves and cache array are same length

		if ( this.cacheLengths != null && this.cacheLengths.length == this.curves.length ) {

			return this.cacheLengths;

		}

		// Get length of subsurve
		// Push sums into cached array

		var lengths = [], sums = 0;
		var i, il = this.curves.length;

		for ( i = 0; i < il; i ++ ) {

			sums += this.curves[ i ].length;
			lengths.add( sums );

		}

		this.cacheLengths = lengths;

		return lengths;

	}

List getLengths({num divisions: null}) #

inherited from Curve
List getLengths( {num divisions: null} ) {

		if (divisions == null) divisions = (_arcLengthDivisions != null) ? (_arcLengthDivisions): 200;

		if ( cacheArcLengths != null
			&& ( cacheArcLengths.length == divisions + 1 )
			&& !needsUpdate) {

			//console.log( "cached", this.cacheArcLengths );
			return cacheArcLengths;
		}

		needsUpdate = false;

		var cache = [];
		var current;
		var last = getPoint( 0.0 );
		var sum = 0;

		cache.add( 0 );

		for ( var p = 1; p <= divisions; p ++ ) {

			current = getPoint ( p / divisions );

			var distance;

			// TODO(nelsonsilva) - Must move distanceTo to IVector interface os create a new IHasDistance
			if (current is Vector3) {
			  distance = (current as Vector3).absoluteError( last as Vector3 );
			} else {
      distance = (current as Vector2).absoluteError( last as Vector2);
    }

			sum += distance;
			cache.add( sum );
			last = current;

		}

		cacheArcLengths = cache;

		return cache; // { sums: cache, sum:sum }; Sum is in the last element.
	}

dynamic getPoint(num t) #

getPoint( num t ) {

		var d = t * this.length;
		var curveLengths = this.getCurveLengths();
		var i = 0, diff;
		Curve curve;

		// To think about boundaries points.

		while ( i < curveLengths.length ) {

			if ( curveLengths[ i ] >= d ) {

				diff = curveLengths[ i ] - d;
				curve = this.curves[ i ];

				var u = 1 - diff / curve.length;

				return curve.getPointAt( u );

			}

			i ++;

		}

		return null;

		// loop where sum != 0, sum > d , sum+1 <d

	}

V getPointAt(u) #

inherited from Curve
V getPointAt( u ) {
		var t = getUtoTmapping( u );
		return getPoint( t );
	}

List<V> getPoints([num divisions = null, closedPath = false]) #

inherited from Curve
List<V> getPoints( [num divisions = null, closedPath = false] ) {

	  if (divisions == null) divisions = 5;

		var d, pts = [];

		for ( d = 0; d <= divisions; d ++ ) {
			pts.add( this.getPoint( d / divisions ) );
		}

		return pts;
	}

List<V> getSpacedPoints([num divisions = 5, closedPath = false]) #

inherited from Curve
List<V> getSpacedPoints( [num divisions = 5, closedPath = false] ) {


		var d, pts = [];

		for ( d = 0; d <= divisions; d ++ ) {
			pts.add( this.getPointAt( d / divisions ) );
		}

		return pts;
	}

V getTangent(t) #

inherited from Curve
V getTangent( t ) {

		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;

		var pt1 = getPoint( t1 );
		var pt2 = getPoint( t2 );

		var vec = pt2 - pt1;
		return vec.normalize();
	}

V getTangentAt(u) #

inherited from Curve
V getTangentAt( u ) {
		var t = getUtoTmapping( u );
		return getTangent( t );
	}

dynamic getTransformedPoints(segments, {List bends: null}) #

getTransformedPoints( segments, {List bends: null} ) {

		var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints
		var i, il;

		if (bends == null) {
			bends = _bends;
		}

		for ( i = 0; i < bends.length; i ++ ) {
			oldPts = this.getWrapPoints( oldPts, bends[ i ] );
		}

		return oldPts;

	}

dynamic getTransformedSpacedPoints([num segments, List bends = null]) #

getTransformedSpacedPoints( [num segments, List bends = null] ) {

		var oldPts = getSpacedPoints( segments );

		var i, il;

		if (bends == null) {
			bends = _bends;
		}

		for ( i = 0; i < bends.length; i ++ ) {
			oldPts = this.getWrapPoints( oldPts, bends[ i ] );
		}

		return oldPts;
	}

List<V> getUPoints([List uList, closedPath = false]) #

inherited from Curve
List<V> getUPoints( [List uList , closedPath = false] ) {
 var pts = [];

 for ( var u in uList ) {
   pts.add( this.getPointAt( u ) );
 }

 return pts;
}

dynamic getUtoTmapping(u, {distance: null}) #

inherited from Curve
getUtoTmapping( u, {distance: null} ) {

		var arcLengths = getLengths();

		int i = 0, il = arcLengths.length;

		var targetArcLength; // The targeted u distance value to get

		if (distance != null) {
			targetArcLength = distance;
		} else {
			targetArcLength = u * arcLengths[ il - 1 ];
		}

		//var time = Date.now();

		// binary search for the index with largest value smaller than target u distance

		var low = 0, high = il - 1, comparison;

		while ( low <= high ) {

			i = ( low + ( high - low ) / 2 ).floor().toInt(); // 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;
				continue;

			} else if ( comparison > 0 ) {

				high = i - 1;
				continue;

			} else {

				high = i;
				break;

				// DONE

			}

		}

		i = high;

		//console.log('b' , i, low, high, Date.now()- time);

		if ( arcLengths[ i ] == targetArcLength ) {

			var t = i / ( il - 1 );
			return t;

		}

		// we could get finer grain at lengths, or use simple interpolatation 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

	    var t = ( i + segmentFraction ) / ( il -1 );

		return t;
	}

dynamic getWrapPoints(oldPts, path) #

getWrapPoints( oldPts, path ) {

		var bounds = getBoundingBox();

		var i, il, p, oldX, oldY, xNorm;

		for ( i = 0; i < oldPts.length; i ++ ) {

			p = oldPts[ i ];

			oldX = p.x;
			oldY = p.y;

			xNorm = oldX / bounds.maxX;

			// If using actual distance, for length > path, requires line extrusions
			//xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance

			xNorm = path.getUtoTmapping( xNorm, oldX );

			// check for out of bounds?

			var pathPt = path.getPoint( xNorm );
			var normal = path.getNormalVector( xNorm ).scale( oldY );

			p.x = pathPt.x + normal.x;
			p.y = pathPt.y + normal.y;

		}

		return oldPts;

	}

dynamic updateArcLengths() #

inherited from Curve
updateArcLengths() {
		needsUpdate = true;
		getLengths();
	}