[Release] EasyIK | A stupidly simple IK module! | v1.0

Greetings devs!

I have recently figured out how Inverse Kinematics work and was surprised by how simple it actually is. I have tryed figuring them out before but it was always so difficult and unintuitive that I would quickly give up. I have seen modules like this before but they always confused me and where also unintuitive.

So I wrote EasyIK, a very simple inverse kinematics module that can let you write a system in minutes!

Here is an example of how to use it:

Code
-- Require the module
local EasyIK = require(game.ReplicatedStorage.Modules.EasyIK)
-- Create IK arm object
local Arm = EasyIK:CreateArm(workspace.A.Position,workspace.B.Position,3,25,workspace)
-- Make the segments look a bit better (not required)
local Segments = Arm.Segments
for Index,Value in pairs(Segments) do
	Value.Material = Enum.Material.Glass
	Value.Color = Color3.new(0.431373, 0.878431, 1)
end
-- Recalculate
while wait() do
	Arm:Calculate(workspace.A.Position,workspace.B.Position, 2)
end

Result:

As you can see, in just 10 lines of code I have created a working inverse kinematics arm!

This is the first module I have released here, if you have any suggestions please leave them below.
I did not setup a github repo for this since I don’t think it is necessary and I am not sure how to do it, if this get’s enough feedback and support I may set that up.

Model:

Source code:

-- EasyIK | v1.0 writen by Ilikenoodles

local Module = {}

function Module:CreateArm(StartPosition: Vector3,EndPosition: Vector3,SegmentsAmount: number,ArmLength: number,Parent: Instance)
	if not StartPosition or typeof(StartPosition) ~= "Vector3" then error("Invalid start position for IK") return end
	if not EndPosition or typeof(EndPosition) ~= "Vector3" then error("Invalid end position for IK") return end
	if not SegmentsAmount or typeof(SegmentsAmount) ~= "number" then error("You must pass a valid amount of segments for the IK arm") end
	if not ArmLength or typeof(ArmLength) ~= "number" then error("You must pass a valid length for the IK arm") end
	if not Parent then error("You must pass a valid parent for the IK segments") end
	
	local Segments = {}
	
	local function GetSegmentSurfacePosition(Segment: Part,Surface: string)
		if Segment and Surface then
			local HalfSize = (ArmLength / SegmentsAmount) / 2
			if Surface == "Head" then
				return (Segment.CFrame * CFrame.new(0,0,-HalfSize)).Position
			elseif Surface == "Tail" then
				return (Segment.CFrame * CFrame.new(0,0,HalfSize)).Position
			end
		end
	end
	-- Create Segments
	for I = 1, SegmentsAmount do
		local Segment = Instance.new("Part",Parent)
		Segment.Name = "Segment_" .. I
		Segment.Anchored = true
		local Length = ArmLength / SegmentsAmount
		Segment.Size = Vector3.new(1,1,Length)
		Segment.CFrame = CFrame.new(StartPosition) * CFrame.new(0,0,-Length / 2 - Length * (I - 1))
		Segments[SegmentsAmount - I + 1] = Segment
	end
	
	local IK_Arm = {}
	
	IK_Arm.Configuration = {
		["SegmentsAmount"] = SegmentsAmount,
		["ArmLength"] = ArmLength,
		["SegmentLength"] = ArmLength / SegmentsAmount,
		["Parent"] = Parent;
	}
	IK_Arm.Segments = Segments
	
	-- Calculation function
	function IK_Arm:Calculate(PointA: Vector3,PointB: Vector3,Itterations: number)
		if not PointA or typeof(PointA) ~= "Vector3" then PointA = StartPosition warn("Invalid start position for IK calculation") end
		if not PointB or typeof(PointB) ~= "Vector3" then PointB = EndPosition warn("Invalid start position for IK calculation") end
		if not Itterations then Itterations = 1 end
		for Itteration = 1, Itterations do
			-- Calculate
			for I, Segment in ipairs(Segments) do
				local TargetPosition = PointB
				if I > 1 then
					TargetPosition = GetSegmentSurfacePosition(Segments[I - 1],"Tail")
				end
				local Tail = GetSegmentSurfacePosition(Segment,"Tail")
				Segment.CFrame = CFrame.lookAt(Tail,TargetPosition)
				local Head = GetSegmentSurfacePosition(Segment,"Head")
				Segment.CFrame *= CFrame.new(0,0,-(TargetPosition - Head).Magnitude)
			end
			
			-- Offset
			local Offset = GetSegmentSurfacePosition(Segments[#Segments],"Tail") - PointA
			for I, Segment in ipairs(Segments) do
				Segment.Position -= Offset
			end
		end
	end
	
	IK_Arm:Calculate(StartPosition,EndPosition,5)
	
	return IK_Arm
end

return Module
API
EasyIK:CreateArm(StartPos,EndPos,Segments,ArmLength,Parent)

Pretty self explanatory, the 2 positions, Amount of segments, The length of the whole arm and what to parent the segments to. Returns a IK arm object


IK_Arm:Calculate(StartPos,EndPos,Iterations)

Recalculates the IK arm object with a new set up points. Iterations is how many times it will recalculate within the function. Default: 1


IK_ArmObject.Segments -- {Part1, Part2 ...}

A table containing the segments (parts) that make up the arm.


IK_Arm.Configuration --[[ {
		["SegmentsAmount"] = SegmentsAmount,
		["ArmLength"] = ArmLength,
		["SegmentLength"] = ArmLength / SegmentsAmount,
		["Parent"] = Parent;
	}]]

A table containing the information about the IK arm object


Feel free to reply with suggestions, bugs or opinions!

15 Likes

Woah this is awesome. I will definitely try this!

4 Likes