Skip to content

Instantly share code, notes, and snippets.

@sketchpunk
Last active January 19, 2023 06:26
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sketchpunk/cbfe82229234f5ccc58f6b2dd9fa98b0 to your computer and use it in GitHub Desktop.
Save sketchpunk/cbfe82229234f5ccc58f6b2dd9fa98b0 to your computer and use it in GitHub Desktop.
Catenary Curve for 2D / 3D in Javascript
// How to increment using the slope and offset comes from tasaboia's example but did not have a solution for A
// But found a way to calc for A from a ruby Wire Tool plugin, but it's incrementing did not work as well as tasaboia
// So mixing the two results in a good implementation of the Catenary Curve. All Credit belongs to those two developers.
// All I did is mash the code together and fixed / optimized it - SketchpunkLabs
// https://github.com/tasaboia/Procedural-Rope-Generator/blob/master/Assets/CatenaryTeste/Scripts/Catenary.cs
// http://rhin.crai.archi.fr/rld/plugin_details.php?id=990
function catenary(a, x){ return a * Math.cosh( x / a ); }
catenary.MAX_TRIES = 100;
catenary.getA = function(vecLen, maxLen){
if(vecLen > maxLen){ console.log("exceed max length of catenary"); return null; }
//....................................
let e = Number.MAX_VALUE,
a = 100,
aTmp = 0,
maxLenHalf = 0.5 * maxLen,//Math.sqrt(maxLen*maxLen - yDelta*yDelta), //by accident found this works fine without all the sqrt & stuff
vecLenHalf = 0.5 * vecLen,
i;
for(i=0; i < catenary.MAX_TRIES; i++){
aTmp = vecLenHalf / ( Math.asinh( maxLenHalf / a ) );
e = Math.abs( (aTmp - a) / a );
a = aTmp;
if(e < 0.001) break;
}
//console.log("tries", i);
return a;
}
//generate vector points along the curve between two end points
catenary.getSegmentPoints = function(v0, v1, maxLen, segCnt=5, invert = false){
let vecLen = v1.length( v0 ), // Length between Two Points
vecLenHalf = vecLen * 0.5, // ... Half of that
segInc = vecLen / segCnt, // Size of Each Segment
A = catenary.getA(vecLen, maxLen),
offset = catenary(A, -vecLenHalf), // Need starting C to base things at Zero, subtract offset from each c point
rtn = new Array(),
pnt,xpos, c, i;
for(i=1; i < segCnt; i++){
pnt = [0,0,0];
v0.lerp(v1, i / segCnt, pnt); //Vec3.lerp(v0, v1, i / segCnt, pnt);
xpos = i * segInc - vecLenHalf; // x position between two points but using half as zero center
c = offset - catenary(A, xpos); // get a y value, but needs to be changed to work with coord system.
if(!invert) pnt[1] -= c;
else pnt[1] += c;
rtn.push(pnt);
}
return rtn;
}
//TODO, This function creates a parabolic-like curve with its center at zero (-1 to 1).
//With that in mind, It creates the same set of values for both sides. To optimize this
//further, only calcuate from 0 to 1 then repeat those values backwards so we only process
//unique values and just repeat them for 0 to -1. They are the exact same Y values, no need to invert.
catenary.getByLengths = function(vecLen, maxLen, segCnt){
let vecLenHalf = vecLen * 0.5, // ... Half of that
segInc = vecLen / segCnt, // Size of Each Segment
A = catenary.getA(vecLen, maxLen),
offset = catenary(A, -vecLenHalf), // Need starting C to base things at Zero, subtract offset from each c point
rtn = new Array(segCnt - 1),
i;
//loop in a -1 to 1 way.
for(i=1; i < segCnt; i++) rtn[i-1] = offset - catenary(A, i * segInc - vecLenHalf);
return rtn;
}
let maxLen = 5.0,
segCnt = 20,
posA = new Vec3(1, 0.2, -0.5),
posB = new Vec3(-2, 1.5, 1);
DVao.vecPoint( ePoint, posA, 0); //you the dev can ignore this, its how I visualize vector points in 3D with Fungi
DVao.vecPoint( ePoint, posB, 0);
// Get All Points on the curve between two end points
var ary = catenary.getSegmentPoints(posA, posB, maxLen, segCnt, false);
for(let i=0; i < ary.length; i++) DVao.vecPoint( ePoint, ary[i], 2);
//Get Curve Y Positions, then apply it to the slope of the curve
//Basicly doing this manually instead of using getSegmentPoints.
//Going this route is more optimized if you need to use Vector Length for other things
//so you can skip using one less sqrt call where getSegmentPoints will calculate vector length itself.
let ary = catenary.getByLengths(posB.length(posA), maxLen, segCnt);
let vec = new Vec3();
for(let i=0; i < ary.length; i++){
Vec3.lerp(posA, posB, (i+1) / segCnt, vec);
vec.y -= ary[i];
DVao.vecPoint( ePoint, vec, 2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment