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
```