Skip to content

Instantly share code, notes, and snippets.

@sketchpunk
Last active March 27, 2024 07:23
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sketchpunk/f2efce80845fe4938c32516c29cccca8 to your computer and use it in GitHub Desktop.
Save sketchpunk/f2efce80845fe4938c32516c29cccca8 to your computer and use it in GitHub Desktop.
Curves in Javascript

Curves in Javascript

Collection of algorithms related to using curves written in javascript.

Curve Types

  • Cubic Bezier Splines : inc. derivative, 2nd derivative, normals, easing
  • Catmull Rom
  • Kochanek Bartels ( TCB Splines ) : inc. derivative
  • Lemniscate of Bernoulli and Gerono : inc. derivative
  • Watt's Curve
  • Torus Knot : inc. derivative, 2nd derivative
  • Lissajous
  • Easing
  • Parabola
  • 3D Arc / Circle
  • Interpolation ( Vec3 / Quaternions ): Cosine, Cubic Spline, Catmull, Hermite, Linear

Other

  • Arc Length Smoothing
  • Curve Caching

References

//X and Y axis need to be normalized vectors, 90 degrees of eachother.
static planeCircle(vecCenter, xAxis, yAxis, angle, radius, out){
let sin = Math.sin(angle),
cos = Math.cos(angle);
out[0] = vecCenter[0] + radius * cos * xAxis[0] + radius * sin * yAxis[0];
out[1] = vecCenter[1] + radius * cos * xAxis[1] + radius * sin * yAxis[1];
out[2] = vecCenter[2] + radius * cos * xAxis[2] + radius * sin * yAxis[2];
return out;
}
//X and Y axis need to be normalized vectors, 90 degrees of eachother.
static planeEllipse(vecCenter, xAxis, yAxis, angle, xRadius, yRadius, out){
let sin = Math.sin(angle),
cos = Math.cos(angle);
out[0] = vecCenter[0] + xRadius * cos * xAxis[0] + yRadius * sin * yAxis[0];
out[1] = vecCenter[1] + xRadius * cos * xAxis[1] + yRadius * sin * yAxis[1];
out[2] = vecCenter[2] + xRadius * cos * xAxis[2] + yRadius * sin * yAxis[2];
return out;
}
function CatmullRom( p0, p1, p2, p3, t, out){
let tt = t * t, ttt = tt * t;
//https://www.mvps.org/directx/articles/catmull/
//q(t) = 0.5 * ( (2 * P1) + (-P0 + P2) * t + (2*P0 - 5*P1 + 4*P2 - P3) * t^2 + (-P0 + 3*P1- 3*P2 + P3) * t^3)
out = out || new THREE.Vector3();
out.x = 0.5 * ( (2 * p1.x) + (-p0.x + p2.x) * t + ( 2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * tt + ( -p0.x + 3 * p1.x - 3 * p2.x + p3.x ) * ttt );
out.y = 0.5 * ( (2 * p1.y) + (-p0.y + p2.y) * t + ( 2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * tt + ( -p0.y + 3 * p1.y - 3 * p2.y + p3.y ) * ttt );
out.z = 0.5 * ( (2 * p1.z) + (-p0.z + p2.z) * t + ( 2 * p0.z - 5 * p1.z + 4 * p2.z - p3.z) * tt + ( -p0.z + 3 * p1.z - 3 * p2.z + p3.z ) * ttt );
return out;
}
// Is hermite but the end points are used as tangents
// different types (b) uniform, (c) chordal and (d) centripetal parameterization.
// uniform (alpha = 0), blue to centripetal (alpha = 0.5) and red to chordal (alpha = 1) parametrization.
// this might be cardinal
static uniform_catmull_rom( p0, p1, p2, p3, t, tension = 0.5, out = null ){
let xt0 = tension * ( p2.x - p0.x ),
xt1 = tension * ( p3.x - p1.x ),
yt0 = tension * ( p2.y - p0.y ),
yt1 = tension * ( p3.y - p1.y ),
zt0 = tension * ( p2.z - p0.z ),
zt1 = tension * ( p3.z - p1.z ),
tt = t * t,
ttt = tt * t;
out = out || new THREE.Vector3();
out.x = p1.x + xt0 * t + (-3 * p1.x + 3 * p2.x - 2 * xt0 - xt1) * tt + (2 * p1.x - 2 * p2.x + xt0 + xt1) * ttt;
out.y = p1.y + yt0 * t + (-3 * p1.y + 3 * p2.y - 2 * yt0 - yt1) * tt + (2 * p1.y - 2 * p2.y + yt0 + yt1) * ttt;
out.z = p1.z + zt0 * t + (-3 * p1.z + 3 * p2.z - 2 * zt0 - zt1) * tt + (2 * p1.z - 2 * p2.z + zt0 + zt1) * ttt;
return 0;
}
function nonuniform_catmull_rom( p0, p1, p2, p3, t, out = null ){
let curveType = 'chordal';
// init Centripetal / Chordal Catmull-Rom
var tt = t * t;
var ttt = tt * t;
var pow = (curveType === 'chordal') ? 0.5 : 0.25; //centripetal
var dt0 = ( ( p1.x-p0.x ) ** 2 + ( p1.y-p0.y ) ** 2 + ( p1.z-p0.z ) ** 2 ) ** pow;
var dt1 = ( ( p2.x-p1.x ) ** 2 + ( p2.y-p1.y ) ** 2 + ( p2.z-p1.z ) ** 2 ) ** pow;
var dt2 = ( ( p3.x-p2.x ) ** 2 + ( p3.y-p2.y ) ** 2 + ( p3.z-p2.z ) ** 2 ) ** pow;
// safety check for repeated points
if ( dt1 < 1e-4 ) dt1 = 1.0;
if ( dt0 < 1e-4 ) dt0 = dt1;
if ( dt2 < 1e-4 ) dt2 = dt1;
dt0 *= 0.1;
dt1 *= 0.1;
dt2 *= 0.1;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// compute tangents when parameterized in [t1,t2]
var xt1 = ( p1.x - p0.x ) / dt0 - ( p2.x - p0.x ) / ( dt0 + dt1 ) + ( p2.x - p1.x ) / dt1;
var xt2 = ( p2.x - p1.x ) / dt1 - ( p3.x - p1.x ) / ( dt1 + dt2 ) + ( p3.x - p2.x ) / dt2;
var yt1 = ( p1.y - p0.y ) / dt0 - ( p2.y - p0.y ) / ( dt0 + dt1 ) + ( p2.y - p1.y ) / dt1;
var yt2 = ( p2.y - p1.y ) / dt1 - ( p3.y - p1.y ) / ( dt1 + dt2 ) + ( p3.y - p2.y ) / dt2;
var zt1 = ( p1.z - p0.z ) / dt0 - ( p2.z - p0.z ) / ( dt0 + dt1 ) + ( p2.z - p1.z ) / dt1;
var zt2 = ( p2.z - p1.z ) / dt1 - ( p3.z - p1.z ) / ( dt1 + dt2 ) + ( p3.z - p2.z ) / dt2;
// rescale tangents for parametrization in [0,1]
xt1 *= dt1;
xt2 *= dt1;
yt1 *= dt1;
yt2 *= dt1;
zt1 *= dt1;
zt2 *= dt1;
out = out || new THREE.Vector3();
out.x = p1.x + xt1 * t + (- 3 * p1.x + 3 * p2.x - 2 * xt1 - xt2) * tt + ( 2 * p1.x - 2 * p2.x + xt1 + xt2) * ttt;
out.y = p1.y + yt1 * t + (- 3 * p1.y + 3 * p2.y - 2 * yt1 - yt2) * tt + ( 2 * p1.y - 2 * p2.y + yt1 + yt2) * ttt;
out.z = p1.z + zt1 * t + (- 3 * p1.z + 3 * p2.z - 2 * zt1 - zt2) * tt + ( 2 * p1.z - 2 * p2.z + zt1 + zt2) * ttt;
return out;
}
function CBezierEase(target, x0,y0, x1,y1, x2,y2, x3,y3 ){
const TRIES = 30;
const MARGIN = 0.001;
if(target <= 0.00001) return 0;
else if(target > 0.99999 ) return 1;
let a = 0,
b = 1,
loop = 0,
t,tt, i, ii, x;
while( loop++ < TRIES ){
t = (b - a) * 0.5 + a;
i = 1 - t;
tt = t * t;
ii = i * i;
x = i*ii*x0 + 3*t*ii*x1 + 3*tt*i*x2 + t*tt*x3;
//console.log("x",loop, x, target, Math.abs(target - x));
if( Math.abs(target - x) < MARGIN ) break; //console.log("found target at", t);
if(target > x) a = t;
else if(target < x) b = t;
}
return i*ii*y0 + 3*t*ii*y1 + 3*tt*i*y2 + t*tt*y3;
}
class Bezier{
constructor(){
this.points = [];
this.curveCnt = 0;
}
loadFlatArray( ary ){
let i;
for( i=0; i < ary.length; i+=2 ) this.points.append( [ ary[i], ary[i+1] ] );
this.curveCnt = Math.floor( ( this.points.length - 1 ) / 3 );
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
append( rect ){ //TODO, NEED TO MAKE THIS WORK CORRECTLY
let x = 0;
if( this.curveCnt != 0 ) x = this.points[ this.points.length-1 ][0];
else this.points.push( [ 0, 0 ] );
this.points.push(
[ 0.3, 0.3 ],
[ 0.7, 0.7 ],
[ 1, 1 ],
);
this.curveCnt++;
return this;
}
set( i, xi, yi ){
this.points[ i ][ 0 ] = xi;
this.points[ i ][ 1 ] = yi;
return this;
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
at( t=0, out=null ){
let i;
out = out || [ 0, 0 ];
//Final Curve in the spline
//This only work if norm is less then 1, 1 can screw things up so this condition prevents things from breaking.
if(t >= 1){
t = 1;
i = this.points.length - 4;
}else if( t <= 0 ){
t = 0;
i = 0;
}else{ //Determine which curve is being accessed.
if(t < 0) t = 0;
t *= (this.points.length - 1) * 0.33333333333; // divide by 3
i = t | 0; // Curve index by stripping out the decimal
t -= i; // Strip out the whole number to get the decimal norm to be used for the curve ( FRACT )
i *= 3; // Each curve starts at the 4th point in the array, so times 3 gets us the index where the curve starts.
}
return Bezier.at( this.points[i], this.points[i+1], this.points[i+2], this.points[i+3], t, out );
}
/////////////////////////////////////////////////////////////////////////////
// Main Bezier Equation to get Point based on 4 Points
/////////////////////////////////////////////////////////////////////////////
static at( p0, p1, p2, p3, t, out ){
let i = 1 - t,
ii = i * i,
iii = ii * i,
tt = t * t,
ttt = tt * t,
iit3 = 3 * ii * t,
itt3 = 3 * i * tt;
out = out || [ 0, 0 ];
out[0] = iii * p0[0] + iit3 * p1[0] + itt3 * p2[0] + ttt * p3[0];
out[1] = iii * p0[1] + iit3 * p1[1] + itt3 * p2[1] + ttt * p3[1];
return out;
}
/* First Derivative represents Curve's Tangent or the Speed Direction*/
static atDerivative(p0, p1, p2, p3, t, out){ // bigO( 31 )
//Clamp t betwen 0 and 1
if(t > 1) t = 1;
else if(t < 0) t = 0;
var i = 1 - t,
ii3 = 3 * i * i,
it6 = 6 * i * t,
tt3 = 3 * t * t;
out = out || new Vec3();
out.x = ii3 * (p1.x - p0.x) + it6 * (p2.x - p1.x) + tt3 * (p3.x - p2.x);
out.y = ii3 * (p1.y - p0.y) + it6 * (p2.y - p1.y) + tt3 * (p3.y - p2.y);
out.z = ii3 * (p1.z - p0.z) + it6 * (p2.z - p1.z) + tt3 * (p3.z - p2.z);
return out;
}
//https://stackoverflow.com/questions/35901079/calculating-the-inflection-point-of-a-cubic-bezier-curve
static derivative_2(p0, p1, p2, p3, t, out){ // bigO ( 31 )
//Clamp t betwen 0 and 1
if(t > 1) t = 1;
else if(t < 0) t = 0;
out = out || new Vec3();
let t6 = 6 * t;
out.x = t6 * (p3.x + 3 * (p1.x - p2.x) - p0.x) + 6 * (p0.x - 2 * p1.x + p2.x);
out.y = t6 * (p3.y + 3 * (p1.y - p2.y) - p0.y) + 6 * (p0.y - 2 * p1.y + p2.y);
out.z = t6 * (p3.z + 3 * (p1.z - p2.z) - p0.z) + 6 * (p0.z - 2 * p1.z + p2.z);
return out;
}
//Get the 3 axis directions for a specific spot
static axis(p0, p1, p2, p3, t, out = null){
if(! out) out = { x:new Vec3(), y:new Vec3(), z:new Vec3() };
Bezier.derivative(p0, p1, p2, p3, t, out.z).normalize();
let d2 = Bezier.derivative_2(p0, p1, p2, p3, t).normalize();
Vec3.cross(d2, out.z, out.x).normalize();
let cp = out.x, //Short cuts
d1 = out.z;
out.y[0] = (cp.x * cp.x) * d1.x + (cp.x * cp.y - cp.z) * d1.y + (cp.x * cp.z + cp.y) * d1.z;
out.y[1] = (cp.x * cp.y + cp.z) * d1.x + (cp.y * cp.y) * d1.y + (cp.y * cp.z - cp.x) * d1.z;
out.y[2] = (cp.x * cp.z - cp.y) * d1.x + (cp.y * cp.z + cp.x) * d1.y + (cp.z * cp.z) * d1.z;
out.y.normalize();
return out;
}
static applySplineMidControl(ary, a, b, c, scale){
//http://scaledinnovation.com/analytics/splines/aboutSplines.html
let lenBA = ary[a].length( ary[b] ), // Length of M to A
lenBC = ary[c].length( ary[b] ), // Length of M to B
lenACi = 1 / (lenBA + lenBC), // Total Length of MA+MB inverted
scaleA = scale * lenBA * lenACi, // Using the lengths, normalize it
scaleB = scale * lenBC * lenACi,
deltaAC = Vec3.sub( ary[c], ary[a] ); // Slope of A and B, used as the line for the mid control pnts
ary[b].sub( Vec3.scale(deltaAC, scaleA), ary[b-1] );
ary[b].add( Vec3.scale(deltaAC, scaleB), ary[b+1] );
}
}
class CurveArcLength{
constructor(){
this.samples = null;
this.sampleCnt = 0;
this.sampleCntInv = 0;
this.arcLength = 0;
}
fromCurve( c, s=null ){
// Change Sample Count
if( s ){
this.sampleCnt = s;
this.sampleCntInv = 1 / s;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let p0 = [ 0, 0 ],
p1 = [ 0, 0 ],
i;
c.at( 0 , p0 ); // Save point at Time Zero
this.samples = new Array( this.sampleCnt+1 ); // New Sample Array
this.samples[ 0 ] = 0; // First Value is zero.
this.arcLength = 0; // Reset Arc Length
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for( i=1; i <= s; i++ ){
c.at( i * this.sampleCntInv , p1 );
this.arcLength += Math.sqrt( (p1[0] - p0[0]) ** 2 + (p1[1] - p0[1]) ** 2 );
this.samples[ i ] = this.arcLength;
p0[ 0 ] = p1[ 0 ]; // Save point for next loop
p0[ 1 ] = p1[ 1 ];
}
return this;
}
mapT( t ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Return Extremes, else set variables
if(t <= 0) return 0;
if(t >= 1) return 1;
let targetLen = t * this.arcLength,
minIdx = 0,
min = 0,
max = this.sampleCnt;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Binary Search to find the idx of a sample
// that is just below the target length so if the target is 10,
// find index that has a length at 10 or ver close on the min side,like 9
while( min < max ){
minIdx = min + ( ((max - min) * 0.5) | 0 ); //get mid index, use pipe for same op as Math.floor()
// if sample is under target, use mid index+1 as new min
// else sample is over target, use index as max for next iteration
if(this.samples[ minIdx ] < targetLen) min = minIdx + 1;
else max = minIdx;
}
// if by chance sample is over target because we ended up with max index, go back one.
if( this.samples[ minIdx ] > targetLen ) minIdx--;
//Check if the idex is within bounds
if( minIdx < 0 ) return 0; // Well, can't have less then 0 as an index :)
if( minIdx >= this.sampleCnt ) return 1; // if the max value is less then target, just return 1;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interpolate between the min and max indexes.
let minLen = this.samples[ minIdx ];
if( minLen == targetLen ) return minIdx * this.sampleCntInv;
// Where does the targetLen exist between the min and maxLen... this t is the
// Length interplation between the two sample points. Since Each sample point
// itself is the t of the curve. So for example,
// minIdx is 2 and we have 10 samples, So we can get the curve t by doing minIdx / SampleCnt
// Now are target leng lives between index 2 and 3... So by finding the gradient value between
// for example 0.5... So we're on index 2 but we need an extra half of index... So 2.5 sample index
// We take that value and divide - 2.5 / sampleCnt = t of curve in relation to arc length.
let maxLen = this.samples[ minIdx + 1 ],
tLen = (targetLen - minLen) / ( maxLen - minLen );
return ( minIdx + tLen ) * this.sampleCntInv;
}
}
class CurveCache{
constructor(){
this.samples = new Array();
this.sampleCnt = 0;
this.sampleCntInv = 0;
}
fromCurve( c, s ){
let i;
for(i=0; i <= s; i++) this.samples.push( c.at( i / s ) );
this.sampleCnt = this.samples.length - 1;
this.sampleCntInv = 1 / this.sampleCnt;
}
at( t, out=null ){
out = out || new THREE.Vector3();
if( t <= 0 ) return out.copy( this.samples[ 0 ] );
if( t >= 1 ) return out.copy( this.samples[ this.sampleCnt ] );
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let i;
t = this.sampleCnt * t;
i = t | 0;
return out.lerpVectors( this.samples[i], this.samples[i+1], t - i );
}
}
class Easing{
static linear(k){ return k; }
//-----------------------------------------------
static quad_In(k){ return k * k; }
static quad_Out(k){ return k * (2 - k); }
static quad_InOut(k) {
if ((k *= 2) < 1) return 0.5 * k * k;
return - 0.5 * (--k * (k - 2) - 1);
}
//-----------------------------------------------
static cubic_In(k){ return k * k * k; }
static cubic_Out(k){ return --k * k * k + 1; }
static cubic_InOut(k){
if((k *= 2) < 1) return 0.5 * k * k * k;
return 0.5 * ((k -= 2) * k * k + 2);
}
//-----------------------------------------------
static quart_In(k){ return k * k * k * k; }
static quart_Out(k){ return 1 - (--k * k * k * k); }
static quart_InOut(k){
if((k *= 2) < 1) return 0.5 * k * k * k * k;
return - 0.5 * ((k -= 2) * k * k * k - 2);
}
//-----------------------------------------------
static quint_In(k){ return k * k * k * k * k; }
static quint_Out(k){ return --k * k * k * k * k + 1; }
static quint_InOut(k){
if((k *= 2) < 1) return 0.5 * k * k * k * k * k;
return 0.5 * ((k -= 2) * k * k * k * k + 2);
}
//-----------------------------------------------
static sine_In(k){ return 1 - Math.cos(k * Math.PI / 2); }
static sine_Out(k){ return Math.sin(k * Math.PI / 2); }
static sine_InOut(k){ return 0.5 * (1 - Math.cos(Math.PI * k)); }
//-----------------------------------------------
static exp_In(k){ return k === 0 ? 0 : Math.pow(1024, k - 1); }
static exp_Out(k){ return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k); }
static exp_InOut(k){
if (k === 0 || k === 1) return k;
if((k *= 2) < 1) return 0.5 * Math.pow(1024, k - 1);
return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2);
}
//-----------------------------------------------
static circ_In(k){ return 1 - Math.sqrt(1 - k * k); }
static circ_Out(k){ return Math.sqrt(1 - (--k * k)); }
static circ_InOut(k){
if((k *= 2) < 1) return - 0.5 * (Math.sqrt(1 - k * k) - 1);
return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
}
//-----------------------------------------------
static elastic_In(k) {
if (k === 0 || k === 1) return k;
return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
}
static elastic_Out(k) {
if (k === 0 || k === 1) return k;
return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1;
}
static elastic_InOut(k) {
if (k === 0 || k === 1) return k;
k *= 2;
if (k < 1) return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1;
}
//-----------------------------------------------
static back_In(k){ return k * k * ((1.70158 + 1) * k - 1.70158); }
static back_Out(k){ return --k * k * ((1.70158 + 1) * k + 1.70158) + 1; }
static back_InOut(k){
var s = 1.70158 * 1.525;
if((k *= 2) < 1) return 0.5 * (k * k * ((s + 1) * k - s));
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
}
//-----------------------------------------------
static bounce_In(k){ return 1 - Easing.bounce_Out(1 - k); }
static bounce_Out(k){
if(k < (1 / 2.75)) return 7.5625 * k * k;
else if(k < (2 / 2.75)) return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
else if(k < (2.5 / 2.75)) return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
else return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
}
static bounce_InOut(k){
if(k < 0.5) return Easing.bounce_In(k * 2) * 0.5;
return Easing.bounce_Out(k * 2 - 1) * 0.5 + 0.5;
}
//-----------------------------------------------
//EXTRA EQUATIONS FOUND
static bouncy(t, jump=6, offset=1){
var rad = 6.283185307179586 * t; //PI_2 * t
return (offset + Math.sin(rad)) / 2 * Math.sin(jump * rad);
}
}
class HermiteSpline{
constructor( is_loop=false ){
this.points = new Array();
this.curve_cnt = 0;
this.point_cnt = 0;
this.is_loop = is_loop;
// Private PreComputed Values for each sample of the curve
this.time = 0;
this.tension = 0;
this.bias = 0;
this.ten_bias_p = 0;
this.ten_bias_n = 0;
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
add( pnt, tension=0, bias=0 ){
this.points.push( { pos:pnt, tension, bias } );
this.point_cnt = this.points.length;
this.curve_cnt = this.point_cnt - 3;
return this;
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
at( t=0, out=null, dx_out = null ){
let i = this._calc_params( t ),
a = this.points[ i[0] ].pos,
b = this.points[ i[1] ].pos,
c = this.points[ i[2] ].pos,
d = this.points[ i[3] ].pos;
if( out ) this._curve_pos( a, b, c, d, this.time, out );
if( dx_out ) this._curve_pos_dxdy( a, b, c, d, this.time, dx_out );
return out || dx_out
}
_calc_params( t ){
let i, tt, ti, ai, bi, ci, di;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Figure out the starting index of the curve and the time on the curve.
if( t > 1 ) t = 1;
else if( t < 0 ) t = 0;
if( this.is_loop ){
if( t != 1 ){
tt = t * this.point_cnt;
i = tt | 0;
tt -= i;
}else{
i = this.point_cnt - 1;
tt = 1;
}
ti = 1 - tt;
ai = this.mod( i-1, this.point_cnt );
bi = i;
ci = this.mod( i+1, this.point_cnt );
di = this.mod( i+2, this.point_cnt );
}else{ // Determine which curve is being accessed
if( t != 1 ){
tt = t * this.curve_cnt;
i = tt | 0; // Curve index by stripping out the decimal, BitwiseOR 0 same op as Floor
tt -= i; // Strip out the whole number to get the decimal norm to be used for the curve ( FRACT )
}else{
i = this.point_cnt - 4;
tt = 1;
}
ti = 1 - tt; // Time Inverse
ai = i;
bi = i + 1;
ci = i + 2;
di = i + 3;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Pre-caluate Paramters for Curve & Derivative Equations
this.time = tt;
this.tension = ti * this.points[ bi ].tension + tt * this.points[ ci ].tension;
this.bias = ti * this.points[ bi ].bias + tt * this.points[ ci ].bias;
this.ten_bias_n = ( 1 - this.bias) * ( 1 - this.tension ) * 0.5;
this.ten_bias_p = ( 1 + this.bias) * ( 1 - this.tension ) * 0.5;
return [ai, bi, ci, di];
}
_curve_pos( a, b, c, d, t, out ){
let t2 = t * t,
t3 = t2 * t,
a0 = 2*t3 - 3*t2 + 1,
a1 = t3 - 2*t2 + t,
a2 = t3 - t2,
a3 = -2*t3 + 3*t2;
out[0] = a0*b[0] + a1 * ( (b[0]-a[0]) * this.ten_bias_p + (c[0]-b[0]) * this.ten_bias_n ) + a2 * ( (c[0]-b[0]) * this.ten_bias_p + (d[0]-c[0]) * this.ten_bias_n ) + a3*c[0];
out[1] = a0*b[1] + a1 * ( (b[1]-a[1]) * this.ten_bias_p + (c[1]-b[1]) * this.ten_bias_n ) + a2 * ( (c[1]-b[1]) * this.ten_bias_p + (d[1]-c[1]) * this.ten_bias_n ) + a3*c[1];
out[2] = a0*b[2] + a1 * ( (b[2]-a[2]) * this.ten_bias_p + (c[2]-b[2]) * this.ten_bias_n ) + a2 * ( (c[2]-b[2]) * this.ten_bias_p + (d[2]-c[2]) * this.ten_bias_n ) + a3*c[2];
return out;
}
_curve_pos_dxdy( a, b, c, d, t, out ){
let tt = t * t,
tt6 = 6 * tt,
tt3 = 3 * tt,
a0 = tt6 - 6*t,
a1 = tt3 - 4*t + 1,
a2 = tt3 - 2*t,
a3 = 6*t - tt6;
out[0] = a0 * b[0] + a1 * ( (b[0]-a[0]) * this.ten_bias_p + (c[0]-b[0]) * this.ten_bias_n ) + a2 * ( (c[0]-b[0]) * this.ten_bias_p + (d[0]-c[0]) * this.ten_bias_n ) + a3 * c[0];
out[1] = a0 * b[1] + a1 * ( (b[1]-a[1]) * this.ten_bias_p + (c[1]-b[1]) * this.ten_bias_n ) + a2 * ( (c[1]-b[1]) * this.ten_bias_p + (d[1]-c[1]) * this.ten_bias_n ) + a3 * c[1];
out[2] = a0 * b[2] + a1 * ( (b[2]-a[2]) * this.ten_bias_p + (c[2]-b[2]) * this.ten_bias_n ) + a2 * ( (c[2]-b[2]) * this.ten_bias_p + (d[2]-c[2]) * this.ten_bias_n ) + a3 * c[2];
return out;
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
mod( a, b ){ let v = a % b; return ( v < 0 )? b+v : v; } // Modulas that handles Negatives, so (-1, 5) = 4
get_samples( s_cnt ){
let max = s_cnt-1,
max_inv = 1 / max,
out = new Array( s_cnt ),
i, t;
for( i=0; i <= max; i++ ){
let p = [0,0,0];
t = i * max_inv;
out[ i ] = this.at( t, p );
}
return out;
}
}
class CurveLenMap{
constructor( c=null, samp_cnt=20 ){
this.len_array = null; // Total length at each sample step
this.len_inc_array = null; // Length Traveled per step
this.time_array = null; // Curve T Value at each step
this.arc_len = 0; // Total Arc Length
this.samp_cnt = 0; // How Many samples taken.
if( c ) this.from_curve( c, samp_cnt );
}
from_curve( c, samp_cnt=20 ){
this.len_array = new Array( samp_cnt );
this.len_inc_array = new Array( samp_cnt );
this.time_array = new Array( samp_cnt );
this.arc_len = 0;
this.samp_cnt = samp_cnt;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let max = samp_cnt - 1,
max_inv = 1 / max,
prev_p = [ 0, 0, 0 ],
curr_p = [ 0, 0, 0 ],
i, t, len;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
c.at( 0, prev_p );
this.len_array[0] = 0;
this.time_array[0] = 0;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for( i=1; i <= max; i++ ){
t = i * max_inv;
c.at( t, curr_p );
//......................................
// Length Traveled since prevous point.
len = Math.sqrt(
( prev_p[0] - curr_p[0] ) ** 2 +
( prev_p[1] - curr_p[1] ) ** 2 +
( prev_p[2] - curr_p[2] ) ** 2 );
this.arc_len += len;
this.len_inc_array[ i-1 ] = len;
this.len_array[ i ] = this.arc_len;
this.time_array[ i ] = t;
//......................................
prev_p[0] = curr_p[0];
prev_p[1] = curr_p[1];
prev_p[2] = curr_p[2];
}
return this;
}
get( t ){
if( t >= 1 ) return 1;
if( t <= 0 ) return 0;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let i, t_len = this.arc_len * t;
for( i=this.samp_cnt-1; i >= 0; i-- ){ // Search for first length SMALLER then the searching one
if( this.len_array[i] < t_len ){
let tt = ( t_len - this.len_array[ i ] ) / this.len_inc_array[ i ], // Normalize the Search Length
ti = 1 - tt;
return this.time_array[ i ] * ti + this.time_array[ i+1 ] * tt; // Lerp Between this sample time and the next one.
}
}
return 0;
}
}
function kochanek_bartels( p0, p1, p2, p3, tension, continuity, bias, t, out ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// OPTIMIZATION NOTES :
// If interpolating a curve, TCB and Tangents shouldn't be calc for each point.
// Precalc then reuse values for each t of the curve.
// FOR splines, d0a, d0b, d1a, d1b Can be calced for all curves, then just do the tangents per curve.
let d0a = ((1 - tension) * ( 1 + bias ) * ( 1 + continuity)) * 0.5,
d0b = ((1 - tension) * ( 1 - bias ) * ( 1 - continuity)) * 0.5,
d1a = ((1 - tension) * ( 1 + bias ) * ( 1 - continuity)) * 0.5,
d1b = ((1 - tension) * ( 1 - bias ) * ( 1 + continuity)) * 0.5,
d0x = d0a * ( p1.x - p0.x ) + d0b * ( p2.x - p1.x ), // Incoming Tangent
d0y = d0a * ( p1.y - p0.y ) + d0b * ( p2.y - p1.y ),
d0z = d0a * ( p1.z - p0.z ) + d0b * ( p2.z - p1.z ),
d1x = d1a * ( p2.x - p1.x ) + d1b * ( p3.x - p2.x ), // Outgoing Tangent
d1y = d1a * ( p2.y - p1.y ) + d1b * ( p3.y - p2.y ),
d1z = d1a * ( p2.z - p1.z ) + d1b * ( p3.z - p2.z );
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interpolate a point on the curve
let tt = t * t,
ttt = tt * t;
out = out || new THREE.Vector3();
out.x = p1.x + d0x * t + (- 3 * p1.x + 3 * p2.x - 2 * d0x - d1x) * tt + ( 2 * p1.x - 2 * p2.x + d0x + d1x) * ttt;
out.y = p1.y + d0y * t + (- 3 * p1.y + 3 * p2.y - 2 * d0y - d1y) * tt + ( 2 * p1.y - 2 * p2.y + d0y + d1y) * ttt;
out.z = p1.z + d0z * t + (- 3 * p1.z + 3 * p2.z - 2 * d0z - d1z) * tt + ( 2 * p1.z - 2 * p2.z + d0z + d1z) * ttt;
return out;
}
function kochanek_bartels_dxdy( p0, p1, p2, p3, tension, continuity, bias, t, out ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// OPTIMIZATION NOTES :
// If interpolating a curve, TCB and Tangents shouldn't be calc for each point.
// Precalc then reuse values for each t of the curve.
// FOR splines, d0a, d0b, d1a, d1b Can be calced for all curves, then just do the tangents per curve.
let d0a = ((1 - tension) * ( 1 + bias ) * ( 1 + continuity)) * 0.5,
d0b = ((1 - tension) * ( 1 - bias ) * ( 1 - continuity)) * 0.5,
d1a = ((1 - tension) * ( 1 + bias ) * ( 1 - continuity)) * 0.5,
d1b = ((1 - tension) * ( 1 - bias ) * ( 1 + continuity)) * 0.5,
d0x = d0a * ( p1.x - p0.x ) + d0b * ( p2.x - p1.x ), // Incoming Tangent
d0y = d0a * ( p1.y - p0.y ) + d0b * ( p2.y - p1.y ),
d0z = d0a * ( p1.z - p0.z ) + d0b * ( p2.z - p1.z ),
d1x = d1a * ( p2.x - p1.x ) + d1b * ( p3.x - p2.x ), // Outgoing Tangent
d1y = d1a * ( p2.y - p1.y ) + d1b * ( p3.y - p2.y ),
d1z = d1a * ( p2.z - p1.z ) + d1b * ( p3.z - p2.z );
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interpolate a point on the curve
let tt = t * t,
ttt = tt * t;
out = out || new THREE.Vector3();
out.x = d0x + (- 3 * p1.x + 3 * p2.x - 2 * d0x - d1x) * 2 * t + ( 2 * p1.x - 2 * p2.x + d0x + d1x) * 3 * tt;
out.y = d0y + (- 3 * p1.y + 3 * p2.y - 2 * d0y - d1y) * 2 * t + ( 2 * p1.y - 2 * p2.y + d0y + d1y) * 3 * tt;
out.z = d0z + (- 3 * p1.z + 3 * p2.z - 2 * d0z - d1z) * 2 * t + ( 2 * p1.z - 2 * p2.z + d0z + d1z) * 3 * tt;
return out;
}
/*
// Create Curve
let cnt = 30;
let kb = new KochanekBartels( 7 );
kb .set( 1, null, null, null, 1.0 )
.set( 2, null, null, 0.4, 1.0 )
//.set( 2, null, null, 0.4, 1.0, 2.0, 5.0 )
.set( 3, null, null, -0.2 )
.set( 4, null, null, 0.1 );
kb.debug( cnt );
*/
class KochanekBartels{
constructor( pCnt=4, t=0, b=0, c=0 ){
this.points = new Array();
this.curveCnt = (pCnt - 3);
let i,v;
for( i=0; i < pCnt; i++ ){
v = new THREE.Vector3( i*0.3, 0, 0 );
v.t = t;
v.b = b;
v.c = c;
this.points.push( v );
}
this.T = 0;
this.tension = 0;
this.bias = 0;
this.continuity = 0;
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
_calcParams( t ){
let i;
if(t >= 1){ // Final Curve in the spline
this.T = 1;
i = this.points.length - 4;
}else if( t <= 0 ){ // First Curve in the Spine
this.T = 0;
i = 0;
}else{ // Determine which curve is being accessed.
this.T = t * this.curveCnt;
i = this.T | 0; // Curve index by stripping out the decimal, BitwiseOR 0 same op as Floor
this.T -= i; // Strip out the whole number to get the decimal norm to be used for the curve ( FRACT )
}
let ti = ( 1-this.T ),
ii = i + 1,
iii = i + 2;
this.tension = ti * this.points[ ii ].t + this.T * this.points[ iii ].t;
this.bias = ti * this.points[ ii ].b + this.T * this.points[ iii ].b;
this.continuity = ti * this.points[ ii ].c + this.T * this.points[ iii ].c;
return i;
}
set( i, x=null, y=null, z=null, t=null, b=null, c=null ){
let p = this.points[ i ];
if( x != null ) p.x = x;
if( y != null ) p.y = y;
if( z != null ) p.z = z;
if( t != null ) p.t = t;
if( b != null ) p.b = b;
if( c != null ) p.c = c;
return this;
}
at( t=0, out=null, dxOut = null ){
let i = this._calcParams( t );
out = out || new THREE.Vector3();
// If DxDy out is available to save time from calcuting all the parameters for the segment
if( dxOut ) KochanekBartels.dxdy( this.points[i], this.points[i+1], this.points[i+2], this.points[i+3], this.T, this.tension, bias, this.continuity, dxOut );
// Get Position on Curve
return KochanekBartels.at( this.points[i], this.points[i+1], this.points[i+2], this.points[i+3], this.T, this.tension, this.bias, this.continuity, out );
}
dxdy( t, out = null ){
let i = this._calcParams( t );
out = out || new THREE.Vector3();
return KochanekBartels.dxdy( this.points[i], this.points[i+1], this.points[i+2], this.points[i+3], this.T, this.tension, this.bias, this.continuity, out );
}
samples( s ){
let out = new Array( s+1 ),
si = 1 / s,
i;
for( i=0; i <= s; i++ ) out[ i ] = this.at( i * si );
return out;
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
debug( s = 10 ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Display Points
let last = this.points.length - 1,
i = 0, c;
for( i=0; i < this.points.length; i++ ){
c = ( i == 0 || i == last )? 0x00ff00 : 0xff0000;
gDebug.pointVec( this.points[ i ], 40, c );
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let v0 = new THREE.Vector3(),
v1 = new THREE.Vector3(),
d = new THREE.Vector3(),
dd = new THREE.Vector3(),
si = 1 / s,
t;
this.at( 0, v0 );
for( i=1; i <= s; i++ ){
t = i * si;
this.at( t, v1 );
gDebug.lineVec( v0, v1, 0xffff00 );
gDebug.pointVec( v1, 10, 0xffff00 );
//this.dxdy( t, d ).normalize().multiplyScalar(0.2);
//gDebug.lineVec( v1, dd.addVectors( v1, d ), 0x00ffff );
v0.copy( v1 );
}
return this;
}
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
// https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline
// Tension = Curveness, Bias = Over Shoot, Continuity = Breaks out the curve between points
static at( p0, p1, p2, p3, t, tension=0, bias=0, continuity=0, out=null ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// OPTIMIZATION NOTES :
// If interpolating a curve, TCB and Tangents shouldn't be calc for each point.
// Precalc then reuse values for each t of the curve.
// FOR splines, d0a, d0b, d1a, d1b Can be calced for all curves, then just do the tangents per curve.
let d0a = ((1 - tension) * ( 1 + bias ) * ( 1 + continuity)) * 0.5,
d0b = ((1 - tension) * ( 1 - bias ) * ( 1 - continuity)) * 0.5,
d1a = ((1 - tension) * ( 1 + bias ) * ( 1 - continuity)) * 0.5,
d1b = ((1 - tension) * ( 1 - bias ) * ( 1 + continuity)) * 0.5,
d0x = d0a * ( p1.x - p0.x ) + d0b * ( p2.x - p1.x ), // Incoming Tangent
d0y = d0a * ( p1.y - p0.y ) + d0b * ( p2.y - p1.y ),
d0z = d0a * ( p1.z - p0.z ) + d0b * ( p2.z - p1.z ),
d1x = d1a * ( p2.x - p1.x ) + d1b * ( p3.x - p2.x ), // Outgoing Tangent
d1y = d1a * ( p2.y - p1.y ) + d1b * ( p3.y - p2.y ),
d1z = d1a * ( p2.z - p1.z ) + d1b * ( p3.z - p2.z );
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interpolate a point on the curve
let tt = t * t,
ttt = tt * t;
out = out || new THREE.Vector3();
out.x = p1.x + d0x * t + (- 3 * p1.x + 3 * p2.x - 2 * d0x - d1x) * tt + ( 2 * p1.x - 2 * p2.x + d0x + d1x) * ttt;
out.y = p1.y + d0y * t + (- 3 * p1.y + 3 * p2.y - 2 * d0y - d1y) * tt + ( 2 * p1.y - 2 * p2.y + d0y + d1y) * ttt;
out.z = p1.z + d0z * t + (- 3 * p1.z + 3 * p2.z - 2 * d0z - d1z) * tt + ( 2 * p1.z - 2 * p2.z + d0z + d1z) * ttt;
return out;
}
static dxdy( p0, p1, p2, p3, t, tension=0, bias=0, continuity=0, out=null ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// OPTIMIZATION NOTES :
// If interpolating a curve, TCB and Tangents shouldn't be calc for each point.
// Precalc then reuse values for each t of the curve.
// FOR splines, d0a, d0b, d1a, d1b Can be calced for all curves, then just do the tangents per curve.
let d0a = ((1 - tension) * ( 1 + bias ) * ( 1 + continuity)) * 0.5,
d0b = ((1 - tension) * ( 1 - bias ) * ( 1 - continuity)) * 0.5,
d1a = ((1 - tension) * ( 1 + bias ) * ( 1 - continuity)) * 0.5,
d1b = ((1 - tension) * ( 1 - bias ) * ( 1 + continuity)) * 0.5,
d0x = d0a * ( p1.x - p0.x ) + d0b * ( p2.x - p1.x ), // Incoming Tangent
d0y = d0a * ( p1.y - p0.y ) + d0b * ( p2.y - p1.y ),
d0z = d0a * ( p1.z - p0.z ) + d0b * ( p2.z - p1.z ),
d1x = d1a * ( p2.x - p1.x ) + d1b * ( p3.x - p2.x ), // Outgoing Tangent
d1y = d1a * ( p2.y - p1.y ) + d1b * ( p3.y - p2.y ),
d1z = d1a * ( p2.z - p1.z ) + d1b * ( p3.z - p2.z );
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interpolate a point on the curve
let tt = t * t,
t2 = 2 * t,
tt3 = 3 * tt;
out = out || new THREE.Vector3();
out.x = d0x + (- 3 * p1.x + 3 * p2.x - 2 * d0x - d1x) * t2 + ( 2 * p1.x - 2 * p2.x + d0x + d1x) * tt3;
out.y = d0y + (- 3 * p1.y + 3 * p2.y - 2 * d0y - d1y) * t2 + ( 2 * p1.y - 2 * p2.y + d0y + d1y) * tt3;
out.z = d0z + (- 3 * p1.z + 3 * p2.z - 2 * d0z - d1z) * t2 + ( 2 * p1.z - 2 * p2.z + d0z + d1z) * tt3;
return out;
}
}
function lemniscate_gerono(t, out){
out = out || [0,0];
out[0] = Math.cos(t);
out[1] = Math.sin(2*t) * 0.5;
return out;
}
function lemniscate_gerono_der(t, out){
out = out || [0,0];
out[0] = -Math.sin(t);
out[1] = Math.cos(2*t);
return out;
}
function lemniscate_bernoulli(t, out){
out = out || [0,0];
let t2 = t*2,
scale = 2 / (3 - Math.cos(t2));
out[0] = scale * Math.cos(t);
out[1] = scale * Math.sin(t2) * 0.5;
return out;
}
// Thanks to @teormech (Alexander Hohlov) for helping me solve the derivative of Y for bernoulli
// because of what I learned of his example i was able to get the derivative of X
// First derivative
// x = (-2sin(x) - 4cos(x)sin(2x)) / (3-cos(2x))
// y = (6cos(2x) - 2) / (3-cos(2x))^2
function lemniscate_bernoulli_der(t, out){
out = out || [0,0];
let t2 = 2 * t,
cos2t = Math.cos(t2),
cos2t_3 = 3 - cos2t;
out[0] = (-2 * Math.sin(t) - 4 * Math.cos(t) * Math.sin(t2)) / cos2t_3;
out[1] = (6 * cos2t - 2) / (cos2t_3 * cos2t_3);
return out;
}
/* [[ RESOURCES ]]
http://archive.gamedev.net/archive/reference/articles/article1497.html
http://paulbourke.net/miscellaneous/interpolation/
https://codeplea.com/simple-interpolation
https://codeplea.com/introduction-to-splines
https://codeplea.com/triangular-interpolation // Barycentric Coordinates and Alternatives
*/
class Lerp{
////////////////////////////////////////////////////////////////
// Linear
////////////////////////////////////////////////////////////////
static linear( t, a, b ){ return (1-t) * a + t * b; }
static linear_quat( t, a, b, out ){
let ti = 1 - t;
out[0] = a[0] * ti + b[0] * t;
out[1] = a[1] * ti + b[1] * t;
out[2] = a[2] * ti + b[2] * t;
out[3] = a[3] * ti + b[3] * t;
return out.normalize();
}
static linear_vec3( t, a, b, out ){
let ti = 1 - t;
out[0] = a[0] * ti + b[0] * t;
out[1] = a[1] * ti + b[1] * t;
out[2] = a[2] * ti + b[2] * t;
return out;
}
////////////////////////////////////////////////////////////////
// Hermite Spline
////////////////////////////////////////////////////////////////
// http://paulbourke.net/miscellaneous/interpolation/
static hermite( t, a, b, c, d, tension, bias ){
let m0 = (b-a) * (1+bias) * (1-tension) * 0.5;
m0 += (c-b) * (1-bias) * (1-tension) * 0.5;
let m1 = (c-b) * (1+bias) * (1-tension) * 0.5;
m1 += (d-c) * (1-bias) * (1-tension) * 0.5;
let t2 = t * t;
let t3 = t2 * t;
let a0 = 2*t3 - 3*t2 + 1;
let a1 = t3 - 2*t2 + t;
let a2 = t3 - t2;
let a3 = -2*t3 + 3*t2;
return a0*b + a1*m0 + a2*m1 + a3*c ;
}
function hermite_dxdy( t, a, b, c, d, tension, bias ){
let m0 = ( (b-a) * (1+bias) * (1-tension) * 0.5 ) + ( (c-b) * (1-bias) * (1-tension) * 0.5 ),
m1 = ( (c-b) * (1+bias) * (1-tension) * 0.5 ) + ( (d-c) * (1-bias) * (1-tension) * 0.5 ),
tt = t * t,
tt6 = 6 * tt,
tt3 = 3 * tt,
a0 = tt6 - 6*t,
a1 = tt3 - 4*t + 1,
a2 = tt3 - 2*t,
a3 = 6*t - tt6;
return a0*b + a1*m0 + a2*m1 + a3*c ;
}
// Optimized to compute a quaternion without repeating many operations
static hermite_quat( t, a, b, c, d, tension, bias, out ){
let btn = (1-bias) * (1-tension) * 0.5,
btp = (1+bias) * (1-tension) * 0.5,
t2 = t * t,
t3 = t2 * t,
a0 = 2*t3 - 3*t2 + 1,
a1 = t3 - 2*t2 + t,
a2 = t3 - t2,
a3 = -2*t3 + 3*t2;
out[0] = a0*b[0] + a1 * ( (b[0]-a[0]) * btp + (c[0]-b[0]) * btn ) + a2 * ( (c[0]-b[0]) * btp + (d[0]-c[0]) * btn ) + a3*c[0];
out[1] = a0*b[1] + a1 * ( (b[1]-a[1]) * btp + (c[1]-b[1]) * btn ) + a2 * ( (c[1]-b[1]) * btp + (d[1]-c[1]) * btn ) + a3*c[1];
out[2] = a0*b[2] + a1 * ( (b[2]-a[2]) * btp + (c[2]-b[2]) * btn ) + a2 * ( (c[2]-b[2]) * btp + (d[2]-c[2]) * btn ) + a3*c[2];
out[3] = a0*b[3] + a1 * ( (b[3]-a[3]) * btp + (c[3]-b[3]) * btn ) + a2 * ( (c[3]-b[3]) * btp + (d[3]-c[3]) * btn ) + a3*c[3];
return out.normalize();
}
function hermite_vec3( t, a, b, c, d, tension, bias, out ){
let btn = (1-bias) * (1-tension) * 0.5,
btp = (1+bias) * (1-tension) * 0.5,
t2 = t * t,
t3 = t2 * t,
a0 = 2*t3 - 3*t2 + 1,
a1 = t3 - 2*t2 + t,
a2 = t3 - t2,
a3 = -2*t3 + 3*t2;
out[0] = a0*b[0] + a1 * ( (b[0]-a[0]) * btp + (c[0]-b[0]) * btn ) + a2 * ( (c[0]-b[0]) * btp + (d[0]-c[0]) * btn ) + a3*c[0];
out[1] = a0*b[1] + a1 * ( (b[1]-a[1]) * btp + (c[1]-b[1]) * btn ) + a2 * ( (c[1]-b[1]) * btp + (d[1]-c[1]) * btn ) + a3*c[1];
out[2] = a0*b[2] + a1 * ( (b[2]-a[2]) * btp + (c[2]-b[2]) * btn ) + a2 * ( (c[2]-b[2]) * btp + (d[2]-c[2]) * btn ) + a3*c[2];
return out;
}
function hermite_vec3_dxdy( t, a, b, c, d, tension, bias, out ){
let btn = (1-bias) * (1-tension) * 0.5,
btp = (1+bias) * (1-tension) * 0.5,
tt = t * t,
tt6 = 6 * tt,
tt3 = 3 * tt,
a0 = tt6 - 6*t,
a1 = tt3 - 4*t + 1,
a2 = tt3 - 2*t,
a3 = 6*t - tt6;
out[0] = a0 * b[0] + a1 * ( (b[0]-a[0]) * btp + (c[0]-b[0]) * btn ) + a2 * ( (c[0]-b[0]) * btp + (d[0]-c[0]) * btn ) + a3 * c[0];
out[1] = a0 * b[1] + a1 * ( (b[1]-a[1]) * btp + (c[1]-b[1]) * btn ) + a2 * ( (c[1]-b[1]) * btp + (d[1]-c[1]) * btn ) + a3 * c[1];
out[2] = a0 * b[2] + a1 * ( (b[2]-a[2]) * btp + (c[2]-b[2]) * btn ) + a2 * ( (c[2]-b[2]) * btp + (d[2]-c[2]) * btn ) + a3 * c[2];
return out;
}
////////////////////////////////////////////////////////////////
// Catmull-Rom
////////////////////////////////////////////////////////////////
// http://paulbourke.net/miscellaneous/interpolation/
static catmull( t, a, b, c, d ){
let t2 = t*t;
let a0 = -0.5*a + 1.5*b - 1.5*c + 0.5*d;
let a1 = a - 2.5*b + 2*c - 0.5*d;
let a2 = -0.5*a + 0.5*c;
let a3 = b;
return a0*t*t2 + a1*t2 + a2*t + a3;
}
static catmull_quat( t, a, b, c, d, out ){
let t2 = t * t,
t3 = t * t2;
out[0] = ( -0.5*a[0] + 1.5*b[0] - 1.5*c[0] + 0.5*d[0] )*t3 + ( a[0] - 2.5*b[0] + 2*c[0] - 0.5*d[0] )*t2 + ( -0.5*a[0] + 0.5*c[0] )*t + b[0];
out[1] = ( -0.5*a[1] + 1.5*b[1] - 1.5*c[1] + 0.5*d[1] )*t3 + ( a[1] - 2.5*b[1] + 2*c[1] - 0.5*d[1] )*t2 + ( -0.5*a[1] + 0.5*c[1] )*t + b[1];
out[2] = ( -0.5*a[2] + 1.5*b[2] - 1.5*c[2] + 0.5*d[2] )*t3 + ( a[2] - 2.5*b[2] + 2*c[2] - 0.5*d[2] )*t2 + ( -0.5*a[2] + 0.5*c[2] )*t + b[2];
out[3] = ( -0.5*a[3] + 1.5*b[3] - 1.5*c[3] + 0.5*d[3] )*t3 + ( a[3] - 2.5*b[3] + 2*c[3] - 0.5*d[3] )*t2 + ( -0.5*a[3] + 0.5*c[3] )*t + b[3];
return out.normalize();
}
// http://archive.gamedev.net/archive/reference/articles/article1497.html
// ta > td is the time value of the specific key frames the values belong to.
static catmull_irregular_frames( t, a, b, c, d, ta, tb, tc, td ){
//let bb = ((b-a) / (tb-ta)) * 0.5 + ((c-b) / (tb-ta)) * 0.5; // Original but the second denom seems wrong.
//let cc = ((c-a) / (tc-tb)) * 0.5 + ((d-c) / (tc-tb)) * 0.5;
let t2 = t * t;
let t3 = t * t2;
let bb = ((b-a) / (tb-ta)) * 0.5 + ((c-b) / (tc-tb)) * 0.5; // Tangent at b
let cc = ((c-a) / (tc-tb)) * 0.5 + ((d-c) / (td-tc)) * 0.5; // Tangent at c
let ti = 1.0; //tc - tb; // This hurts the animation with the BB, CC change
return b * (2 * t3 - 3 * t2 + 1) +
c * (3 * t2 - 2* t3) +
bb * ti * (t3 - 2 * t2 + t) +
cc * ti * (t3 - t2);
}
////////////////////////////////////////////////////////////////
// Cubic
////////////////////////////////////////////////////////////////
// http://archive.gamedev.net/archive/reference/articles/article1497.html
static cubic( t, a, b ){
let t2 = t * t,
t3 = t2 * t;
return a * ( 2*t3 - 3*t2 + 1 ) + b * ( 3 * t2 - 2 * t3 );
}
static cubic_quat( t, a, b, out ){
//a * ( 2*t3 - 3*t2 + 1 ) + b * ( 3 * t2 - 2 * t3 );
let t2 = t * t,
t3 = t * t2,
aa = ( 2*t3 - 3*t2 + 1 ),
bb = ( 3 * t2 - 2 * t3 );
out[0] = a[0] * aa + b[0] * bb;
out[1] = a[1] * aa + b[1] * bb;
out[2] = a[2] * aa + b[2] * bb;
out[3] = a[3] * aa + b[3] * bb;
return out.normalize();
}
// http://paulbourke.net/miscellaneous/interpolation/
static cubic_spline( t, a, b, c, d ){
let t2 = t*t;
let a0 = d - c - a + b;
let a1 = a - b - a0;
let a2 = c - a;
let a3 = b;
return a0*t*t2 + a1*t2 + a2*t + a3;
}
static cubic_spline_quat( t, a, b, c, d, out ){
let t2 = t * t,
t3 = t * t2,
a0 = d[0] - c[0] - a[0] + b[0],
a1 = d[1] - c[1] - a[1] + b[1],
a2 = d[2] - c[2] - a[2] + b[2],
a3 = d[3] - c[3] - a[3] + b[3];
out[0] = a0*t3 + ( a[0] - b[0] - a0 )*t2 + ( c[0] - a[0] )*t + b[0];
out[1] = a1*t3 + ( a[1] - b[1] - a1 )*t2 + ( c[1] - a[1] )*t + b[1];
out[2] = a2*t3 + ( a[2] - b[2] - a2 )*t2 + ( c[2] - a[2] )*t + b[2];
out[3] = a3*t3 + ( a[3] - b[3] - a3 )*t2 + ( c[3] - a[3] )*t + b[3];
return out.normalize();
}
static cubic_spline_vec3( t, a, b, c, d, out ){
let t2 = t * t,
t3 = t * t2,
a0 = d[0] - c[0] - a[0] + b[0],
a1 = d[1] - c[1] - a[1] + b[1],
a2 = d[2] - c[2] - a[2] + b[2];
out[0] = a0*t3 + ( a[0] - b[0] - a0 )*t2 + ( c[0] - a[0] )*t + b[0];
out[1] = a1*t3 + ( a[1] - b[1] - a1 )*t2 + ( c[1] - a[1] )*t + b[1];
out[2] = a2*t3 + ( a[2] - b[2] - a2 )*t2 + ( c[2] - a[2] )*t + b[2];
return out;
}
////////////////////////////////////////////////////////////////
// Cosine
////////////////////////////////////////////////////////////////
// NOTE : Can calulate about the same curve without cos by using smoothstep equations which would be better
// smoothstep( t ){ return 3*t**2 - 2*t**3; }
// smoothTStep(t){ return (t**2) * (3 - 2 * t); }
// http://paulbourke.net/miscellaneous/interpolation/
static cosine( t, a, b ){
t = (1 - Math.cos( t * Math.PI)) / 2;
return a * (1-t) + b * t;
}
static cosine_quat( t, a, b, out ){
let tt = ( 1 - Math.cos( t * Math.PI ) ) * 0.5,
ti = 1 - tt;
out[0] = a[0] * ti + b[0] * tt;
out[1] = a[1] * ti + b[1] * tt;
out[2] = a[2] * ti + b[2] * tt;
out[3] = a[3] * ti + b[3] * tt;
return out.normalize();
}
}
//https://codepen.io/jstrutz/pen/pvXOdz
//tAngle 0 -> PI_2
// Curves.lissajous(3,1,1, 1,0,1, i*inc , v);
// Curves.lissajous(5,2,1, 0,-2,1, i * inc , v);
// Curves.lissajous(2,3,1, 0,0.4,1, i * inc, v);
function lissajous(xRng, yRng, zRng, xOffset, yOffset, zOffset, tAngle, out=null){
out = out || new Vec3();
out[0] = Math.sin(xRng * tAngle + xOffset);
out[1] = Math.sin(yRng * tAngle + yOffset);
out[2] = Math.sin(zRng * tAngle + zOffset);
return out;
}
//https://github.com/spite/looper/blob/master/loops/233.js#L124
// Curves.lissajous_2(1, 5,4,5,1,2, 0, i/cnt, v);
// Curves.lissajous_2(1, 5,1,5,1,2, 0, i/cnt, v);
function lissajous_2(radius, a,b,c,d,e, offset, t, out=null){
out = out || new Vec3();
//let tt = Maths.PI_2 - (t * Maths.PI_2 + i * range / cnt + offset * i);
let tt = t * Maths.PI_2 + offset;
out[0] = radius * Math.cos(a * tt) + radius * Math.cos(b * tt);
out[1] = radius * Math.sin(a * tt) + radius * Math.sin(d * tt);
out[2] = 2 * radius * Math.sin(e * tt);
return out
}
function hermite( t, a, b, c, d, tension, bias ){
// http://paulbourke.net/miscellaneous/interpolation/
let m0 = ( (b-a) * (1+bias) * (1-tension) * 0.5 ) + ( (c-b) * (1-bias) * (1-tension) * 0.5 ),
m1 = ( (c-b) * (1+bias) * (1-tension) * 0.5 ) + ( (d-c) * (1-bias) * (1-tension) * 0.5 ),
t2 = t * t,
t3 = t2 * t,
a0 = 2*t3 - 3*t2 + 1,
a1 = t3 - 2*t2 + t,
a2 = t3 - t2,
a3 = -2*t3 + 3*t2;
return a0*b + a1*m0 + a2*m1 + a3*c ;
}
function hermite_dxdy( t, a, b, c, d, tension, bias ){
let m0 = ( (b-a) * (1+bias) * (1-tension) * 0.5 ) + ( (c-b) * (1-bias) * (1-tension) * 0.5 ),
m1 = ( (c-b) * (1+bias) * (1-tension) * 0.5 ) + ( (d-c) * (1-bias) * (1-tension) * 0.5 ),
tt = t * t,
tt6 = 6 * tt,
tt3 = 3 * tt,
a0 = tt6 - 6*t,
a1 = tt3 - 4*t + 1,
a2 = tt3 - 2*t,
a3 = 6*t - tt6;
return a0*b + a1*m0 + a2*m1 + a3*c ;
}
// https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline
// Tension = Curveness, Bias = Over Shoot, Continuity = Breaks out the curve between points
function kochanek_bartels( t, a, b, c, d, tension=0, bias=0, continuity=0 ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// OPTIMIZATION NOTES :
// If interpolating a curve, TCB and Tangents shouldn't be calc for each point.
// Precalc then reuse values for each t of the curve.
// FOR splines, d0a, d0b, d1a, d1b Can be calced for all curves, then just do the tangents per curve.
let d0a = ((1 - tension) * ( 1 + bias ) * ( 1 + continuity)) * 0.5,
d0b = ((1 - tension) * ( 1 - bias ) * ( 1 - continuity)) * 0.5,
d1a = ((1 - tension) * ( 1 + bias ) * ( 1 - continuity)) * 0.5,
d1b = ((1 - tension) * ( 1 - bias ) * ( 1 + continuity)) * 0.5,
d0x = d0a * ( b - a ) + d0b * ( c - b ), // Incoming Tangent,
d1x = d1a * ( c - b ) + d1b * ( d - c ); // Outgoing Tangent;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interpolate a point on the curve
let tt = t * t,
ttt = tt * t;
return b + d0x * t + (- 3 * b + 3 * c - 2 * d0x - d1x) * tt + ( 2 * b - 2 * c + d0x + d1x) * ttt;
}
function kochanek_bartels_dxdy( t, a, b, c, d, tension=0, bias=0, continuity=0, out=null ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// OPTIMIZATION NOTES :
// If interpolating a curve, TCB and Tangents shouldn't be calc for each point.
// Precalc then reuse values for each t of the curve.
// FOR splines, d0a, d0b, d1a, d1b Can be calced for all curves, then just do the tangents per curve.
let d0a = ((1 - tension) * ( 1 + bias ) * ( 1 + continuity)) * 0.5,
d0b = ((1 - tension) * ( 1 - bias ) * ( 1 - continuity)) * 0.5,
d1a = ((1 - tension) * ( 1 + bias ) * ( 1 - continuity)) * 0.5,
d1b = ((1 - tension) * ( 1 - bias ) * ( 1 + continuity)) * 0.5,
d0x = d0a * ( b - a ) + d0b * ( c - b ), // Incoming Tangent,
d1x = d1a * ( c - b ) + d1b * ( d - c ); // Outgoing Tangent;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interpolate a point on the curve
let tt = t * t,
t2 = 2 * t,
tt3 = 3 * tt;
return d0x + (- 3 * b + 3 * c - 2 * d0x - d1x) * t2 + ( 2 * b - 2 * c + d0x + d1x) * tt3;
}
static parabola( x, k ){ return Math.pow( 4 * x * ( 1 - x ), k ); }
function torus_knot( out, t, p=2, q=5, radius=1 ){
// https://blackpawn.com/texts/pqtorus/
// https://en.wikipedia.org/wiki/Torus_knot
let x = t * p * Math.PI * 2,
v = q / p * x,
cv = Math.cos( v );
out[0] = radius * ( 2 + cv ) * 0.5 * Math.cos( x );
out[1] = radius * ( 2 + cv ) * 0.5 * Math.sin( x ) ;
out[2] = radius * Math.sin( v ) * 0.5;
return out;
}
// first derivative - tangent of curve
function torus_knot_dxdy( out, t, p=2, q=5, radius=1 ){
let x = t * p * Math.PI * 2;
// https://www.symbolab.com/solver/derivative-calculator
// Original Torus Knot Equation
// x = r * ( 2 + cos( q/p * x ) ) * 0.5 * cos( x )
// y = r * ( 2 + cos( q/p * x ) ) * 0.5 * sin( x )
// z = r * sin( q/p * x ) * 0.5
out[0] = 0.5 * radius * ( -Math.sin( x ) * ( 2 + Math.cos( q * x / p ) ) - ( q * Math.sin( q * x / p ) * Math.cos( x ) / p ) );
out[1] = 0.5 * radius * ( Math.cos( x ) * ( 2 + Math.cos( q * x / p ) ) - ( q * Math.sin( q * x / p ) * Math.sin( x ) / p ) );
out[2] = radius * 0.5 * q * Math.cos( q * x / p ) / p ;
return out;
}
// second derivative - normal of curve
function torus_knot_dxdy2( out, t, inc=0, p=2, q=5, radius=1 ){
let x = t * p * Math.PI * 2;
// https://www.wolframalpha.com/
// First Derivative
// 0.5 * r * ( -sin( x ) * ( 2 + cos( q * x / p ) ) - ( q * sin( q * x / p ) * cos( x ) / p ) )
// 0.5 * r * ( cos( x ) * ( 2 + cos( q * x / p ) ) - ( q * sin( q * x / p ) * sin( x ) / p ) )
// r * 0.5 * q * cos( q * x / p ) / p
out[0] = -(0.5 * radius * ( Math.cos(x) * ((p**2 + q**2) * Math.cos((q * x)/p) + 2 * p**2) - 2 * p * q * Math.sin(x) * Math.sin((q * x)/p)))/p**2;
out[1] = -( 0.5 * radius * (
Math.sin(x) * ((p**2 + q**2) * Math.cos((q * x)/p) + 2 * p**2) + 2 * p * q * Math.cos(x) * Math.sin((q * x)/p))) / p**2;
out[2] = -(0.5 * q**2 * radius * Math.sin((q * x)/p))/p**2;
return out;
}
///////////////////////////////////////////////////////////////////////////////
// OPTIMIZED VERSION
///////////////////////////////////////////////////////////////////////////////
function torus_knot( out, t, p=2, q=5, radius=1 ){
// https://blackpawn.com/texts/pqtorus/
// https://en.wikipedia.org/wiki/Torus_knot
let x = t * p * Math.PI * 2,
qpx = q / p * x,
rh = radius * 0.5,
qpx_xy = rh * (2 + Math.cos( qpx ));
out[0] = qpx_xy * Math.cos( x );
out[1] = qpx_xy * Math.sin( x );
out[2] = rh * Math.sin( qpx );
return out;
}
// first derivative - tangent of curve
function torus_knot_dxdy( out, t, p=2, q=5, radius=1 ){
let x = t * p * Math.PI * 2,
rh = radius * 0.5,
pi = 1 / p,
qpx = q * x * pi,
sin_x = Math.sin( x ),
cos_x = Math.cos( x ),
sin_qpx = Math.sin( qpx ),
cos_qpx = Math.cos( qpx );
// https://www.symbolab.com/solver/derivative-calculator
// Original Torus Knot Equation
// x = r * ( 2 + cos( q/p * x ) ) * 0.5 * cos( x )
// y = r * ( 2 + cos( q/p * x ) ) * 0.5 * sin( x )
// z = r * sin( q/p * x ) * 0.5
out[0] = rh * ( -sin_x * ( 2 + cos_qpx ) - q*sin_qpx*cos_x*pi );
out[1] = rh * ( cos_x * ( 2 + cos_qpx ) - q*sin_qpx*sin_x*pi );
out[2] = rh * q * cos_qpx * pi ;
return out;
}
// second derivative - normal of curve
function torus_knot_dxdy2( out, t, p=2, q=5, radius=1 ){
let x = t * p * Math.PI * 2,
rh = radius * 0.5,
pq2 = 2 * p * q,
qxp = q * x / p,
pp = p*p,
ppi = 1 / pp,
qq = q*q,
cos_x = Math.cos( x ),
sin_x = Math.sin( x ),
cos_qxp = Math.cos( qxp ),
sin_qxp = Math.sin( qxp ),
com = (pp + qq) * cos_qxp + 2 * pp,
n_rh_pp = -rh * ppi;
// https://www.wolframalpha.com/
// First Derivative
// 0.5 * r * ( -sin( x ) * ( 2 + cos( q * x / p ) ) - ( q * sin( q * x / p ) * cos( x ) / p ) )
// 0.5 * r * ( cos( x ) * ( 2 + cos( q * x / p ) ) - ( q * sin( q * x / p ) * sin( x ) / p ) )
// r * 0.5 * q * cos( q * x / p ) / p
out[0] = n_rh_pp * ( cos_x * com - pq2 * sin_x * sin_qxp );
out[1] = n_rh_pp * ( sin_x * com + pq2 * cos_x * sin_qxp );
out[2] = -0.5 * qq * radius * sin_qxp * ppi;
return out;
}
function test_torus_knot_3d(){
let v = new App.Vec3(); //current pos
let p = new App.Vec3(); //prev
let len = 60;
let t = new App.Vec3();
let b = new App.Vec3();
let n = new App.Vec3();
torus_knot( p, 0, 2, 3, 1 ); // Get First Point
for( let i=1; i <= len; i++ ){
torus_knot( v, i / len, 2, 3, 1 );
torus_knot_dxdy( t, i / len, 2, 3, 1 );
torus_knot_dxdy2( n, i / len, 2, 3, 1 );
t.norm(); // Tangent - Fwd
b.from_cross( n, t ).norm(); // BiNormal - Left
n.from_cross( t, b ).norm(); // Normal - Up (Orthoginal)
App.Debug
.ln( p, v, "red" ) // View Curve Line
.pnt( v, "red", 0.05, 1 ) // View Point on Curve
.ln( v, b.scale(0.2).add(v), "green" ) // Left
.ln( v, n.scale(0.2).add(v), "blue" ) // up
.ln( v, t.scale(0.2).add(v), "white" ); // fwd
p.copy( v ); // Save for next point
}
}
function test_torus_knot_3d_less_turning(){
let v = new App.Vec3(); // current pos
let p = new App.Vec3(); // prev
let len = 60;
let t = new App.Vec3();
let b = new App.Vec3();
let n = new App.Vec3();
torus_knot( p, 0, 2, 3, 1 ); // Inital Pos
for( let i=1; i <= len; i++ ){
torus_knot( v, i / len, 2, 3, 1 );
torus_knot_dxdy( t, i / len, 2, 3, 1 );
torus_knot_dxdy( n, (i + 0.00001) / len, 2, 3, 1 ); // Future Tangent
t.norm(); // Tangent - Fwd
n.add( v ); // Temp Normal - Up (Adding to pos makes it curve around less somehow )
b.from_cross( n, t ).norm(); // BiNormal - Left
n.from_cross( t, b ).norm(); // Normal - Up
App.Debug
.ln( p, v, "red" )
.pnt( v, "red", 0.05, 1 )
.ln( v, App.Vec3.add( v, b.scale(0.2) ), "green" ) // Left
.ln( v, App.Vec3.add( v, n.scale(0.2) ), "blue" ) // Up
.ln( v, App.Vec3.add( v, t.scale(0.2) ), "white" ) // Fwd
;
p.copy( v );
}
}
/*https://www.mathcurve.com/courbes2d.gb/watt/watt.shtml
Polar parametrization:
d^2 = a^2 + b^2 - c^2
length = b * cos(t);
sinTheta = (d^2 - b^2 * cos(t)^2) / ( 2 * a * b * sin(t) )
a : Distance between the center of 2 circles
b : Radius of Circles
c : Length of Rods
*/
function watts_curve(a, b, c, t, out){
var bb = b*b,
dd = a*a + bb - c*c,
cosT = Math.cos(t),
length = b * cosT,
theta = (dd - bb * cosT * cosT) / (2 * a * b * Math.sin(t));
out = out || new Vec2();
out.fromAngleLen(theta, length);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment