Trying to update the start point of FABRIK

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
2 Likes

Could you send the module so I could see what’s going on behind the constructor function?

-- 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;```
1 Like

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.

If I do both chain.origin and chain.joints[1] it updates the first point every frame, and lets it rotate perfectly!

chain.joints[1] = start.Position; -- Try changing the origin every update
chain.origin = start.CFrame
chain.target = finish.Position;

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.