////////////////////////////////////////////////////////////////////// // Calc Arc Points // Trying to get points of an arc based on the current Chord Length and a Constant Arc Length // Trying to replicate this example from an old youtube video on IK targeting using an arc to position bones. // https://youtu.be/UNrrd_XhPMA // The chord is the start and end position of an IK Target, so I would like to create an arc path to help align bones ////////////////////////////////////////////////////////////////////// const PI_2 = Math.PI * 2; const PI_H = Math.PI / 2; function calcArc(){ //............................................. // Calculate the angle based on how long the chord is in relation to the chainLen // So the shorter the the chord the closer to 360, and the longer the chord, closer to 0 degrees // If the coord is >= to arc length, then there is no curve but just a straight line. let chordLen = vChordEnd.length(), // Need to keep the Chord length as is because this needs to work for IK Targeting. arcLen = 300, // Arc Length is a Constant, Need to stay the same length radMap = 1 - (chordLen / arcLen), // if 0.5 = Center = pi, > 0.5 = over pi (bottom) , < 0.5 = under pi (top) angle = radMap * PI_2, // Angle based on how long the chord is in relation to arc length. radius = arcLen / angle, // ArcLength / Angle = Radius midPnt = Vec2.lerp(vChordStart, vChordEnd, 0.5); // Mid Point on chord //............................................. // Find the center point of the circle, Get the height of the curve. // This is the height between the midpoint of the chord and midpoint of the curve. let sagitta = radius - radius * (1- Math.cos(angle/2)), centerPnt = new Vec2( midPnt.x, midPnt.y - sagitta ); /* let chordLenHalf = chordLen / 2, sagitta = radius - Math.sqrt( (radius * radius) - (chordLenHalf * chordLenHalf) ), centerDif = radius - sagitta, centerPnt = new Vec2( midPnt.x, midPnt.y - centerDif ); */ //............................................. // Get the points on the curve. let itmCount = 16, // How many divisions on the arc to make angle_H = angle * 0.5, // Angle zero starts in the up direction, so create offsets arcStart = PI_H + angle_H, arcInc = (arcStart - (PI_H - angle_H)) / itmCount; // (Start - End) / Cnt gData.arcPoints.length = 0; //Clear Array for(var i = 0; i <= itmCount; i++) gData.arcPoints.push( arcStart - i * arcInc ); //............................................. gData.radius = radius; gData.midPoint.copy(midPnt); gData.centerPoint.copy(centerPnt); gData.sagitta = sagitta; gData.angle = angle * 180 / Math.PI; } ////////////////////////////////////////////////////////////////////// // Setup ////////////////////////////////////////////////////////////////////// var vChordStart, vChordEnd, lerpMin = 2, lerpMax = 299, gLoop = 0; var gData; window.addEventListener("load",function(){ initCanvas(true); vChordStart = new Vec2(0,0), vChordEnd = new Vec2(300, 0); gData = { radius : 0, sagitta : 0, midPoint : new Vec2(), centerPoint : new Vec2(), angle : 0, arcPoints : new Array() }; window.requestAnimationFrame(renderLoop); }); function renderLoop(){ //ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(-canvas.width*0.5, -canvas.height*0.5, canvas.width, canvas.height); //Clear from Center gLoop += 0.01; var t = Math.sin(gLoop) * 0.5 + 0.5; vChordEnd.x = lerpMin * (1-t) + lerpMax * t; calcArc(); draw(); window.requestAnimationFrame(renderLoop); } function draw(){ //......................................... // CHORD ctx.strokeStyle = "#050505"; vecLine(vChordStart, vChordEnd); // Chord Line ctx.fillStyle = "#00ff00"; circle(vChordStart, 5, true); // Start of Chord ctx.fillStyle = "#ff0000"; circle(vChordEnd, 5, true); // End of Chord ctx.fillStyle = "#d0d0d0"; circle(gData.midPoint, 5, true); // Mid of Chord //......................................... //CIRCLE ctx.fillStyle = "#000000"; circle(gData.centerPoint, 5, true); // Center Point ctx.setLineDash([3, 4]); circle(gData.centerPoint, gData.radius); // Radius var v = new Vec2( gData.midPoint ); // Sagitta v.y += gData.sagitta; ctx.setLineDash([]); ctx.strokeStyle = "#000000"; circle(v, 10); ctx.strokeStyle = "#ffffff"; // Arc Points for(var i=0; i < gData.arcPoints.length; i++){ v.x = gData.radius * Math.cos( gData.arcPoints[i] ) + gData.centerPoint.x; v.y = gData.radius * Math.sin( gData.arcPoints[i] ) + gData.centerPoint.y; circle(v, 8); } //......................................... /* ctx.fillStyle = "#d0d0d0"; circle(midPnt, 5, true); circle(centerPnt, 5, true); text(midPnt.x, midPnt.y+24, "Mid"); text(0, 80, "Radius : " + radius ); circle(centerPnt, radius); */ //......................................... ctx.fillStyle = "#f0f0f0"; //text(0, 60, "Chord Length : " + vChordEnd.length() ); //text(0, 80, "Radius : " + gData.radius + " " + (gData.radius * 2) ); //text(0, 100, "Angle : " + gData.angle ); //text(vChordStart.x, vChordStart.y+24, "Start"); //text(vChordEnd.x, vChordEnd.y+24, "End"); } ///////////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////// let canvas, ctx; function initCanvas(centerOrigin=false){ 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; if(centerOrigin){ 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"; ctx.strokeStyle = "#00ff00"; ctx.fillStyle = "#ffffff" ctx.font = "14px Arial"; var box = canvas.getBoundingClientRect(); canvas.offsetX = box.left; // Help get X,Y in relation to the canvas position. canvas.offsetY = box.top; } function circle(v, radius, isFill = false){ ctx.beginPath(); ctx.arc(v.x, v.y, radius ,0, Math.PI*2, false); if(isFill) ctx.fill(); else ctx.stroke(); } function line(x0, y0, x1, y1){ ctx.beginPath(); ctx.moveTo( x0, y0 ); ctx.lineTo( x1, y1 ); ctx.stroke(); } function vecLine(v0, v1){ ctx.beginPath(); ctx.moveTo( v0[0], v0[1] ); ctx.lineTo( v1[0], v1[1] ); ctx.stroke(); } function text(x,y,str){ ctx.scale(1,-1); ctx.fillText(str,x,y); ctx.scale(1,-1); } ///////////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////// 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 setLength(len){ return this.normalize().scale(len); } 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 }

!