for ease and for both my sanity and your sanity, im going to strictly follow the format:
-
What do you want to achieve? Keep it simple and clear!
i want to extend an R15 arm to a specified point using FABRIK -
What is the issue? Include screenshots / videos if possible!
(sorry for straight gyazo links, it spits it out when i try to upload an image from a link)
including the target in my FABRIK chain causes the entire thing to bug out:
local points = {shoulderPoint,elbowPoint,wristPoint,target.Position}
https://i.gyazo.com/62d72574e732399ae640602e7a1c0f98.mp4
and omitting the target point almost works, but it skips the last entry and therefore skips the hand:
local points = {shoulderPoint,elbowPoint,wristPoint}
https://i.gyazo.com/b9fb6804d77e4bd9fdee71ebb543fe93.mp4
-
What solutions have you tried so far? Did you look for solutions on the Developer Hub?
ive tried using the 2-joint-2-limb system, but i have hands i have to work with as well, and it fell apart quickly. (plus, a post on there recommended FABRIK for th
ive had more luck with FABRIK, and im trying to get it to work, which is the point of this post
Code:
FABRIK module, written by egomoose
-- 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;
my arm script, utilizing his FABRIK module
--frqst
local runService = game:GetService("RunService")
local ik = require(workspace.FABRIK)
local model = workspace.FrqstbiteDummy
local upperArm = model.RightUpperArm
local lowerArm = model.RightLowerArm
local hand = model.RightHand
local target = workspace.ArmFinish
function getMedian(vec1,vec2) --midpoint between vectors, used for easily getting joint between two parts
return (vec1 + vec2)/2
end
function getExtreme(part,mult) --gets top or bottom of part; 1 for top, -1 for bottom
local size = part.Size
local cf = part.CFrame
return (cf * CFrame.new(0,(size.Y/2) * mult,0)).Position
end
local shoulderPoint = getExtreme(upperArm,1)
local elbowPoint = getMedian(getExtreme(upperArm,-1),getExtreme(lowerArm,1))
local wristPoint = getMedian(getExtreme(lowerArm,-1),getExtreme(hand,1))
local parts = {upperArm,lowerArm,hand}
local points = {shoulderPoint,elbowPoint,wristPoint}
local chain = ik.new(points, target.Position)
function update()
chain.target = target.Position
chain:solve()
for i,_ in ipairs(chain.joints) do
local newVal = i < #chain.joints and i + 1
if newVal then
local diff = chain.joints[newVal] - chain.joints[i]
parts[i].CFrame = CFrame.new(chain.joints[i] + diff/2, chain.joints[newVal]) * CFrame.Angles(math.rad(90),0,0)
end;
end;
end
update()
target:GetPropertyChangedSignal("CFrame"):Connect(update)