// 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. // -- http://rhin.crai.archi.fr/rld/plugin_details.php?id=990 // -- https://github.com/tasaboia/Procedural-Rope-Generator/blob/master/Assets/CatenaryTeste/Scripts/Catenary.cs let canvas, ctx; window.addEventListener("load",function(){ initCanvas(); var segCnt = 10, // How many divisions (segments) to make ropeLen = 370, // Rope/Chain Length. pntA = new Vec2(-170, 80), // Starting Point of the Rope pntB = new Vec2(100, 0); // Ending Point of the Rope //........................... //Draw Main Points circle(pntA, 5); circle(pntB, 5); //........................... // This determines the sagging factor let A = catenary.getA(pntA, pntB, ropeLen); //........................... // Draw in between points of the curve let dist = pntB.length( pntA ), // Length between Two Points distHalf = dist * 0.5, // ... Half of that segInc = dist / segCnt, // Size of Each Segment offset = catenary(A, -distHalf), // First C on curve, use it as an Offset to align everything. pnt = new Vec2(), xpos, c, i; let y; //todo not need, only for testing inverting the sag for(i=1; i < segCnt; i++){ Vec2.lerp(pntA, pntB, i / segCnt, pnt); y = pnt.y; // only for inverting testing, throw away if only want downward sag xpos = i * segInc - distHalf; // x position between two points but using half as zero center c = catenary(A, xpos); // get a y value, but needs to be changed to work with coord system. pnt[1] -= (offset - c); // Current lerped Y minus C of starting point minus current C circle(pnt, 4); pnt[1] = y + (offset - c); // Add Offset C to Lerp Y, inverts the sag upwards circle(pnt, 1); } }); function catenary(a, x){ return a * Math.cosh( x / a ); } catenary.getA = function(vec0, vec1, ropeLen){ //Solving A comes from : http://rhin.crai.archi.fr/rld/plugin_details.php?id=990 let yDelta = vec1[1] - vec0[0], vecLen = vec1.length(vec0); if(yDelta > ropeLen || vecLen > ropeLen){ console.log("not enough rope"); return null; } if(yDelta < 0){ //Swop verts, low end needs to be on the left side var tmp = vec0; vec0 = vec1; vec1 = vec0; yDelta *= -1; } //.................................... const max_tries = 100; let vec3 = new Vec2( vec1[0], vec0[1] ), e = Number.MAX_VALUE, a = 100, aTmp = 0, yRopeDelta = 0.5 * Math.sqrt(ropeLen*ropeLen - yDelta*yDelta), //Optimize the loop vecLenHalf = 0.5 * vecLen, //Optimize the loop i; for(i=0; i < max_tries; i++){ //aTmp = 0.5 * vecLen / ( Math.asinh( 0.5 * Math.sqrt(ropeLen**2 - yDelta**2) / a ) ); aTmp = vecLenHalf / ( Math.asinh( yRopeDelta / a ) ); e = Math.abs( (aTmp - a) / a ); a = aTmp; if(e < 0.001) break; } //console.log("tries", i); return a; } function initCanvas(){ canvas = document.getElementById("FungiCanvas"); ctx = canvas.getContext("2d"); let w = window.innerWidth, h = window.innerHeight; canvas.style.width = w + "px"; canvas.style.height = h + "px"; canvas.width = w; canvas.height = h; ctx.translate(w * 0.5, h * 0.5); //Move Origin to Center ctx.scale(1,-1); //Flip Y so it points Up. ctx.lineWidth = 1; ctx.strokeStyle = "#050505"; line(-200,0, 200,0); line(0, 10, 0, -10); ctx.strokeStyle = "#00ff00"; } function circle(v, radius){ ctx.beginPath(); ctx.arc(v.x, v.y, radius ,0, Math.PI*2, false); ctx.stroke(); } function line(x0,y0, x1,y1){ ctx.beginPath(); ctx.moveTo( x0, y0 ); ctx.lineTo( x1, y1 ); ctx.stroke(); } class Vec2 extends Float32Array{ constructor(ini){ super(2); if(ini instanceof Vec2 || (ini && ini.length == 2)){ this[0] = ini[0]; this[1] = ini[1]; } else if(arguments.length == 2){ this[0] = arguments[0]; this[1] = arguments[1]; } else{ this[0] = this[1] = ini || 0; } } //---------------------------------------------- //Getters and Setters get x(){ return this[0]; } set x(val){ this[0] = val; } get y(){ return this[1]; } set y(val){ this[1] = val; } set(x,y){ this[0] = x; this[1] = y; return this;} clone(){ return new Vec2(this); } copy(v){ this[0] = v[0]; this[1] = v[1]; return this; } fromAngleLen(ang, len){ this[0] = len * Math.cos(ang); this[1] = len * Math.sin(ang); return this; } getAngle(v = null){ if(v){ return Math.acos( Vec2.dot(this,v) / (this.length() * v.length()) ); //var x = v[0] - this[0], // y = v[1] - this[1]; //return Math.atan2(y, x); } return Math.atan2(this[1], this[0]); } //When values are very small, like less then 0.000001, just make it zero. nearZero(x = 1e-6,y = 1e-6){ if(Math.abs(this[0]) <= x) this[0] = 0; if(Math.abs(this[1]) <= y) this[1] = 0; return this; } //endregion //---------------------------------------------- // Methods length(v){ //Only get the magnitude of this vector if(v === undefined) return Math.sqrt( this[0]*this[0] + this[1]*this[1] ); //Get magnitude based on another vector var x = this[0] - v[0], y = this[1] - v[1]; return Math.sqrt( x*x + y*y ); } lengthSqr(v){ //Only get the squared magnitude of this vector if(v === undefined) return this[0]*this[0] + this[1]*this[1]; //Get squared magnitude based on another vector var x = this[0] - v[0], y = this[1] - v[1]; return x*x + y*y; } normalize(out = null){ var mag = Math.sqrt( this[0]*this[0] + this[1]*this[1] ); if(mag == 0) return this; out = out || this; out[0] = this[0] / mag; out[1] = this[1] / mag; return out; } lerp(v, t, out){ out = out || this; var tMin1 = 1 - t; //Linear Interpolation : (1 - t) * v0 + t * v1; out[0] = this[0] * tMin1 + v[0] * t; out[1] = this[1] * tMin1 + v[1] * t; return out; } rotate(ang, out){ out = out || this; var cos = Math.cos(ang), sin = Math.sin(ang), x = this[0], y = this[1]; out[0] = x * cos - y * sin; out[1] = x * sin + y * cos; return out; } invert(out = null){ out = out || this; out[0] = -this[0]; out[1] = -this[1]; return out; } //endregion //---------------------------------------------- // Math add(v, out=null){ out = out || this; out[0] = this[0] + v[0]; out[1] = this[1] + v[1]; return out; } addXY(x, y, out=null){ out = out || this; out[0] = this[0] + x; out[1] = this[1] + y; return out; } sub(v, out=null){ out = out || this; out[0] = this[0] - v[0]; out[1] = this[1] - v[1]; return out; } mul(v, out=null){ out = out || this; out[0] = this[0] * v[0]; out[1] = this[1] * v[1]; return out; } div(v, out=null){ out = out || this; out[0] = (v[0] != 0)? this[0] / v[0] : 0; out[1] = (v[1] != 0)? this[1] / v[1] : 0; return out; } scale(v, out=null){ out = out || this; out[0] = this[0] * v; out[1] = this[1] * v; return out; } divInvScale(v, out=null){ out = out || this; out[0] = (this[0] != 0)? v / this[0] : 0; out[1] = (this[1] != 0)? v / this[1] : 0; return out; } floor(out=null){ out = out || this; out[0] = Math.floor( this[0] ); out[1] = Math.floor( this[1] ); return out; } //endregion //---------------------------------------------- //region Static static add(a,b,out){ out = out || new Vec2(); out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; return out; } static sub(a, b, out){ out = out || new Vec2(); out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; return out; } static scale(v, s, out = null){ out = out || new Vec2(); out[0] = v[0] * s; out[1] = v[1] * s; return out; } static dot(a,b){ return a[0] * b[0] + a[1] * b[1]; } static floor(v, out=null){ out = out || new Vec2(); out[0] = Math.floor( v[0] ); out[1] = Math.floor( v[1] ); return out; } static fract(v, out=null){ out = out || new Vec2(); out[0] = v[0] - Math.floor( v[0] ); out[1] = v[1] - Math.floor( v[1] ); return out; } static length(v0,v1){ var x = v0[0] - v1[0], y = v0[1] - v1[1]; return Math.sqrt( x*x + y*y ); } static lerp(v0, v1, t, out){ out = out || new Vec2(); var tMin1 = 1 - t; //Linear Interpolation : (1 - t) * v0 + t * v1; out[0] = v0[0] * tMin1 + v1[0] * t; out[1] = v0[1] * tMin1 + v1[1] * t; return out; } //endregion }

!