I don’t think I’ve posted this. I found it in my projects so I thought I’d share it (I ported the solver from somewhere and can’t remember where now).
(Edit: The code for the Bone
class is pretty terrible and relies on the solver updating the hierarchy each frame.)
You should be able to copy this and long-press the New Project button to paste it as a new project.
--# Bone Bone = class() function Bone:init(len) -- you can accept and set parameters here self.pos = vec2(0,0) self.len = len or 100 self.angle = 0 self.child = nil end function Bone:draw() pushStyle() stroke(255) strokeWidth(3.0) smooth() local endPoint = vec2(self.len, 0) endPoint = endPoint:rotate(self.angle) + self.pos line(self.pos.x, self.pos.y, endPoint.x, endPoint.y) noStroke() fill(255) ellipse(self.pos.x, self.pos.y, 10) ellipse(endPoint.x, endPoint.y, 10) popStyle() if self.child then self.child.pos = endPoint self.child.angle = self.child.angle + self.angle self.child:draw() end end --# IKSolver IKSolver = class() function IKSolver:init(b1, b2) self.b1 = b1 self.b2 = b2 self.target = vec2(0,0) self.foundSolution = false end function IKSolver:updateSolution() local targetDistSqr = self.b1.pos:distSqr(self.target) local found = true local eps = 0.0001 local sinAngle2 = 0 local cosAngle2 = 0 local cosAngle2Denom = 2*self.b1.len*self.b2.len local b1LenSqr = self.b1.len * self.b1.len local b2LenSqr = self.b2.len * self.b2.len if cosAngle2Denom > eps then cosAngle2 = (targetDistSqr - b1LenSqr - b2LenSqr) / cosAngle2Denom if cosAngle2 < -1.0 or cosAngle2 > 1.0 then found = false end cosAngle2 = math.max(-1, math.min(1, cosAngle2)) self.b2.angle = math.acos(cosAngle2) sinAngle2 = math.sin(self.b2.angle) else local totalLenSqr = (self.b1.len + self.b2.len) * (self.b1.len + self.b2.len) if targetDistSqr < (totalLenSqr - eps) or targetDistSqr > (totalLenSqr + eps) then found = false end self.b2.angle = 0 cosAngle2 = 1.0 sinAngle2 = 0.0 end local triAdj = self.b1.len + self.b2.len * cosAngle2 local triOpp = self.b2.len * sinAngle2 local tanY = self.target.y * triAdj - self.target.x * triOpp local tanX = self.target.x * triAdj + self.target.y * triOpp self.b1.angle = math.atan2( tanY, tanX ) self.foundSolution = found end --# Main -- IK -- Use this function to perform your initial setup function setup() b1 = Bone(100) b2 = Bone(100) b1.child = b2 solver = IKSolver(b1, b2) end -- This function gets called once every frame function draw() -- This sets a dark background color background(40, 40, 50) -- This sets the line thickness strokeWidth(5) translate( WIDTH/2, HEIGHT/2 ) -- Do your drawing here solver.target = vec2(CurrentTouch.x - WIDTH/2, CurrentTouch.y - HEIGHT/2) solver:updateSolution() b1:draw() ellipse(solver.target.x, solver.target.y, 50) end ```