Hi! I’m currently trying to make an IK system for a robotic arm in a game I’m working on. I’m using EgoMoose’s FABRIK module to do this, but I can’t find a way to change the start position? Any help would be appreciated, including other solutions for IK because I am very inexperienced with it at this time!
Here’s my script (look at the comments for solutions I’ve tried
local ik = require(game.Workspace.FABRIK);
local start = game.Workspace.Start;
local finish = game.Workspace.End;
local model = Instance.new("Model", game.Workspace);
local part = workspace.UpperArm:Clone(model)
part.Material = Enum.Material.Plastic;
part.Anchored = true;
part.CanCollide = false;
part.BrickColor = BrickColor.Blue();
function buildChain(start, finish, itr)
local points, lengths = {start}, {};
local distance = (finish - start);
local chains = 2
for i = 1, chains do
points[i+1] = points[i] + distance.unit * itr;
end;
return points;
end;
local parts = {};
local p = buildChain(start.Position, finish.Position, 2.2);
local chain = ik.new(p, finish.Position);
function update()
p = buildChain(start.Position, finish.Position, 2.2); -- Try redoing this equation every update
chain.origin = start.CFrame; -- Try changing the origin every update
chain.target = finish.Position;
chain:solve();
for i, _ in ipairs(chain.joints) do
local n = i < #chain.joints and i + 1;
if n then
local v = chain.joints[n] - chain.joints[i];
if not parts[i] then parts[i] = part:Clone(); parts[i].Parent = model; end;
parts[i].CFrame = CFrame.new(chain.joints[i] + v/2, chain.joints[n]);
end;
end;
end
update();
finish.Changed:connect(update);
start.Changed:connect(update); -- Try updating it whenever the start changes
-- FABRIK
-- Ego
local chain = {};
local plane = {};
-- table sum function
function sum(t)
local s = 0;
for _, value in ipairs(t) do
s = s + value;
end;
return s;
end;
local part = Instance.new("Part");
part.Material = Enum.Material.Plastic;
part.Anchored = true;
part.CanCollide = false;
part.BrickColor = BrickColor.Blue();
local parts = {};
local model = Instance.new("Model", game.Workspace);
function drawline(key, a, v)
if not parts[key] then parts[key] = part:Clone(); parts[key].Parent = model; end;
parts[key].Size = Vector3.new(.2, .2, v.magnitude);
parts[key].CFrame = CFrame.new(a + v/2, a + v);
return parts[key];
end;
-- fabrik chain class
function chain.new(joints, target)
local self = setmetatable({}, {__index = chain});
local lengths = {};
for i = 1, #joints - 1 do
lengths[i] = (joints[i] - joints[i+1]).magnitude;
end;
self.n = #joints;
self.tolerance = 0.1;
self.target = target;
self.joints = joints;
self.lengths = lengths;
self.origin = CFrame.new(joints[1]);
self.totallength = sum(lengths);
-- rotation constraints
self.constrained = false;
self.left = math.rad(89);
self.right = math.rad(89);
self.up = math.rad(89);
self.down = math.rad(89);
return self;
end;
-- this is the hardest part of the code so I super commented it!
function chain:constrain(calc, line, cf)
local scalar = calc:Dot(line) / line.magnitude;
local proj = scalar * line.unit;
-- get axis that are closest
local ups = {cf:vectorToWorldSpace(Vector3.FromNormalId(Enum.NormalId.Top)), cf:vectorToWorldSpace(Vector3.FromNormalId(Enum.NormalId.Bottom))};
local rights = {cf:vectorToWorldSpace(Vector3.FromNormalId(Enum.NormalId.Right)), cf:vectorToWorldSpace(Vector3.FromNormalId(Enum.NormalId.Left))};
table.sort(ups, function(a, b) return (a - calc).magnitude < (b - calc).magnitude end);
table.sort(rights, function(a, b) return (a - calc).magnitude < (b - calc).magnitude end);
local upvec = ups[1];
local rightvec = rights[1];
-- get the vector from the projection to the calculated vector
local adjust = calc - proj;
if scalar < 0 then
-- if we're below the cone flip the projection vector
proj = -proj;
end;
-- get the 2D components
local xaspect = adjust:Dot(rightvec);
local yaspect = adjust:Dot(upvec);
-- get the cross section of the cone
local left = -(proj.magnitude * math.tan(self.left));
local right = proj.magnitude * math.tan(self.right);
local up = proj.magnitude * math.tan(self.up);
local down = -(proj.magnitude * math.tan(self.down));
-- find the quadrant
local xbound = xaspect >= 0 and right or left;
local ybound = yaspect >= 0 and up or down;
local f = calc;
-- check if in 2D point lies in the ellipse
local ellipse = xaspect^2/xbound^2 + yaspect^2/ybound^2;
local inbounds = ellipse <= 1 and scalar >= 0;
if not inbounds then
-- get the angle of our out of ellipse point
local a = math.atan2(yaspect, xaspect);
-- find nearest point
local x = xbound * math.cos(a);
local y = ybound * math.sin(a);
-- convert back to 3D
f = (proj + rightvec * x + upvec * y).unit * calc.magnitude;
end;
-- return our final vector
return f;
end;
function chain:backward()
-- backward reaching; set end effector as target
self.joints[self.n] = self.target;
for i = self.n - 1, 1, -1 do
local r = (self.joints[i+1] - self.joints[i]);
local l = self.lengths[i] / r.magnitude;
-- find new joint position
local pos = (1 - l) * self.joints[i+1] + l * self.joints[i];
self.joints[i] = pos;
end;
end;
function chain:forward()
-- forward reaching; set root at initial position
self.joints[1] = self.origin.p;
local coneVec = (self.joints[2] - self.joints[1]).unit;
for i = 1, self.n - 1 do
local r = (self.joints[i+1] - self.joints[i]);
local l = self.lengths[i] / r.magnitude;
-- setup matrix
local cf = CFrame.new(self.joints[i], self.joints[i] + coneVec);
-- find new joint position
local pos = (1 - l) * self.joints[i] + l * self.joints[i+1];
local t = self:constrain(pos - self.joints[i], coneVec, cf);
self.joints[i+1] = self.constrained and self.joints[i] + t or pos;
coneVec = self.joints[i+1] - self.joints[i];
end;
end;
function chain:solve()
local distance = (self.joints[1] - self.target).magnitude;
if distance > self.totallength then
-- target is out of reach
for i = 1, self.n - 1 do
local r = (self.target - self.joints[i]).magnitude;
local l = self.lengths[i] / r;
-- find new joint position
self.joints[i+1] = (1 - l) * self.joints[i] + l * self.target;
end;
else
-- target is in reach
local bcount = 0;
local dif = (self.joints[self.n] - self.target).magnitude;
while dif > self.tolerance do
self:backward();
self:forward();
dif = (self.joints[self.n] - self.target).magnitude;
-- break if it's taking too long so the game doesn't freeze
bcount = bcount + 1;
if bcount > 10 then break; end;
end;
end;
end;
return chain;```
Issue appears to be that in the solve function of the chain class, it gets the position of the first thing by doing self.joints[1] instead of self.Origin. Could probably get around this by updating self.Joints[1] in your code, but I have no guarantees that this would work 100% since I’ve never used this module before.