File: /home/lindsay/xeolabs/xeogl-next/xeogl/examples/js/curves/curve.js
/**
**Curve** is the abstract base class for {{#crossLink "SplineCurve"}}{{/crossLink}},
{{#crossLink "CubicBezierCurve"}}{{/crossLink}}, {{#crossLink "QuadraticBezierCurve"}}{{/crossLink}} and {{#crossLink "Path"}}{{/crossLink}}.
## Examples
<ul>
<li>[CubicBezierCurve example](../../examples/#animation_curves_cubicBezier)</li>
<li>[Tweening position along a QuadraticBezierCurve](../../examples/#animation_curves_quadraticBezier)</li>
<li>[Tweening color along a QuadraticBezierCurve](../../examples/#animation_curves_quadraticBezier_color)</li>
<li>[Simple SplineCurve example](../../examples/#animation_curves_spline)</li>
<li>[Moving a PointLight along a SplineCurve](../../examples/#animation_lights_point_world)</li>
<li>[Path example](../../examples/#curves_Path)</li>
</ul>
@class Curve
@module xeogl
@submodule curves
@constructor
@param [scene] {Scene} Parent {{#crossLink "Scene"}}Scene{{/crossLink}}.
@param [cfg] {*} Configuration
@param [cfg.id] {String} Optional ID, unique among all components in the parent {{#crossLink "Scene"}}Scene{{/crossLink}}, generated automatically when omitted.
@param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this Curve.
@param [cfg.t=0] Current position on this Curve, in range between 0..1.
@extends Component
*/
xeogl.Curve = class xeoglCurve extends xeogl.Component {
init(cfg) {
super.init(cfg);
this.t = cfg.t;
}
/**
Progress along this Curve.
Automatically clamps to range [0..1].
Fires a {{#crossLink "Curve/t:event"}}{{/crossLink}} event on change.
@property t
@default 0
@type Number
*/
set t(value) {
value = value || 0;
this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);
/**
* Fired whenever this Curve's
* {{#crossLink "Curve/t:property"}}{{/crossLink}} property changes.
* @event t
* @param value The property's new value
*/
this.fire("t", this._t);
}
get t() {
return this._t;
}
/**
Tangent on this Curve at position {{#crossLink "Curve/t:property"}}{{/crossLink}}.
@property tangent
@type {{Array of Number}}
*/
get tangent() {
return this.getTangent(this._t);
}
/**
Length of this Curve.
@property length
@type {Number}
*/
get length() {
var lengths = this._getLengths();
return lengths[lengths.length - 1];
}
/**
* Returns a normalized tangent vector on this Curve at the given position.
* @method getTangent
* @param {Number} t Position to get tangent at.
* @returns {{Array of Number}} Normalized tangent vector
*/
getTangent(t) {
var delta = 0.0001;
if (t === undefined) {
t = this._t;
}
var t1 = t - delta;
var t2 = t + delta;
if (t1 < 0) {
t1 = 0;
}
if (t2 > 1) {
t2 = 1;
}
var pt1 = this.getPoint(t1);
var pt2 = this.getPoint(t2);
var vec = xeogl.math.subVec3(pt2, pt1, []);
return xeogl.math.normalizeVec3(vec, []);
}
getPointAt(u) {
var t = this.getUToTMapping(u);
return this.getPoint(t);
}
/**
* Samples points on this Curve, at the given number of equally-spaced divisions.
* @method getPoints
* @param {Number} divisions The number of divisions.
* @returns {Array of Array} Array of sampled 3D points.
*/
getPoints(divisions) {
if (!divisions) {
divisions = 5;
}
var d, pts = [];
for (d = 0; d <= divisions; d++) {
pts.push(this.getPoint(d / divisions));
}
return pts;
}
getSpacedPoints(divisions) {
if (!divisions) {
divisions = 5;
}
var d, pts = [];
for (d = 0; d <= divisions; d++) {
pts.push(this.getPointAt(d / divisions));
}
return pts;
}
_getLengths(divisions) {
if (!divisions) {
divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions) : 200;
}
if (this.cacheArcLengths && ( this.cacheArcLengths.length === divisions + 1 ) && !this.needsUpdate) {
return this.cacheArcLengths;
}
this.needsUpdate = false;
var cache = [];
var current;
var last = this.getPoint(0);
var p;
var sum = 0;
cache.push(0);
for (p = 1; p <= divisions; p++) {
current = this.getPoint(p / divisions);
sum += xeogl.math.lenVec3(xeogl.math.subVec3(current, last, []));
cache.push(sum);
last = current;
}
this.cacheArcLengths = cache;
return cache; // { sums: cache, sum:sum }, Sum is in the last element.
}
_updateArcLengths() {
this.needsUpdate = true;
this._getLengths();
}
// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance
getUToTMapping(u, distance) {
var arcLengths = this._getLengths();
var i = 0;
var il = arcLengths.length;
var t;
var targetArcLength; // The targeted u distance value to get
if (distance) {
targetArcLength = distance;
} else {
targetArcLength = u * arcLengths[il - 1];
}
//var time = Date.now();
var low = 0, high = il - 1, 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;
// DONE
}
}
i = high;
if (arcLengths[i] === targetArcLength) {
t = i / ( il - 1 );
return t;
}
var lengthBefore = arcLengths[i];
var lengthAfter = arcLengths[i + 1];
var segmentLength = lengthAfter - lengthBefore;
var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
t = ( i + segmentFraction ) / ( il - 1 );
return t;
}
};