@OldPalHappy A simple idea, but a minesweeper game for level 1 challenge and an algorithm that solves it 100% of the time for level 2
abnotaddable
@abnotaddable
Posts made by abnotaddable

RE: Snack Break Ideas!

RE: How to Properly save Player Data to Data Stores
@Link150 tbh the "oh no" in bold capital letters was reasonable since it helps convey the problem better but everywhere else just makes this post feel very "loud" if you know what I'm saying. Other than that this is a very helpful post since it makes people more aware of the data loss issue

RE: Made a cool Desert Deagle 44. My first gun creation. What do ya`ll think?
looks cool, how did you make the animation? just wondering...

RE: [Roblox] Single joint Inverse Kinematics using Law of Cosines
@Link150 Just updated it, hopefully a lot better! If you notice any more mistakes or improvements I could make, I am open to suggestions. All I want to make is a reliable source for people, so they too can do inverse kinematics. I didn't have many resources that clearly explained a single joint IK equation in a simplistic way. (for roblox)

[Roblox] Single joint Inverse Kinematics using Law of Cosines
This tutorial will cover single joint Inverse Kinematics using Law of Cosines.
To start off we have some questions we may need to answer:
1. What is Forward Kinematics?
Forward Kinematics is where you solve for the end effector (the target position, or end of the two or more joints) given the joint angles and lengths.2. What is Inverse Kinematics?
Inverse Kinematics is the inverse of Forward Kinematics, where you are given the end effector (target position/point) and solve for the joint angles, given the lengths of the joints and distance from target..3. Is it a lot of math?
Not really, its only a few lines and its not too hard either. But keep in mind this is only one method of completing inverse kinematics with a single joint, there are plenty of other methods you can choose from.4. What about when I have more than one joint?
As I said, there are plenty of other methods, some are more accurate than others at the cost of more computing power.
Some of the methods that cover multiple joints include:
• Jacobian Inverse Kinematics
• Cyclic Coordinate Descent
• Forward And Backward Reaching Inverse Kinematics (FABRIK)I should note that EgoMoose (someone who has admirable math skills) has a comprehensive tutorial on FABRIK on the Roblox Wiki. You can see it here.
I guess we can go straight into the tutorial now!
• Insert 4 parts into workspace, name them: "origin", "target", "a", "b".
The "origin" part will be the start of the joint (where the parts are connected to) and the "target" part is where we will want to reach (the end effector). The parts "a" and "b"
Note: you don't have to create an origin or target part, you just need something where there is a consistent CFrame you can access.
• Make the parts "origin" and "target" semi transparent (0.5 transparency) and cube shaped (Size '1,1,1').
This step isn't actually necessary (except the size, partially) it just makes it more pretty to look at.
• Make the parts "a" and "b" Size '0.5, 0.5, p' and '0.5, 0.5, q'.
Lengths "p" and "q" can be any number, you can pick one between 1 and 10 for this tutorial to keep it simple. You can also colour them so you know which part is which. These will be the joint lengths.
• Make sure all the parts are anchored and have can collide off.
So our parts don't fall off into the abyss and there aren't any collisions, duh.
• Insert an empty script into workspace.
This will be where we can write the code to solve the Inverse kinematics
• Define all the parts we just created:local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b
This is so we can index these at any time without having to write out the whole path, saving us time and effort!
• Define the lengths "a" and "b". We can do this using the part size, however you can also use numbers.local la, lb = pa.Size.Z, pb.Size.Z or local la, lb = 4, 4
If you are using numbers, make sure they match the parts length. This is important to the Cosine Law where you need all the lengths of all the sides in order to solve for the angles.
• Create a function called "solveIK" with the parameters "originpos", "targetpos", "a", "b" and "angle".
local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) end
This is so we can call it multiple times around the script, it also makes it alot neater.
As you can guess, the "originpos" and "targetpos" arguments are the origin position (vector) and target position (vector, end effector).
The "a" and "b" arguments are the lengths of our two joints.
The "angle" argument is the plane rotation. I've done a diagram to explain.• Solve the Plane CFrame, this is what the joint will be calculated on. There are many methods to solve this but we will use a simple method.
local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) local planeCF = CFrame.new(originpos, targetpos) end
What this does is set the vector construct of the CFrame to the origin position, and angles it towards the target position. This will be what we solve the inverse kinematics on.
We will need to use the "angle" parameter to rotate the Plane CFrame.local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) local planeCF = CFrame.new(originpos, targetpos) * CFrame.Angles(0, 0, math.rad(angle or 0)) end
This rotates the plane in any direction, for example, this may be good for creating elbows.
If the angle parameter is not supplied, it will default to 0, hence the "or 0".
• Solve for length "c"local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) local planeCF = CFrame.new(originpos, targetpos) * CFrame.Angles(0, 0, math.rad(angle or 0)) local c = (originpos  targetpos).magnitude end
This is basic vector math, where one vector is taken away from another, generating a new vector we can get the magnitude (length of).
• Now that we have the length "a", "b" and "c" we can use Law of Cosines to solve the missing angles "B" and "C" (the angle name is that of the opposite side)
Note: The Law of Cosines is normally written as c^2 = a^2 + b^2  2ab cos(C), so we need to rearrange this to get cos(A or B or C):
cos(A) = (b^2 + c^2  a^2)/(2bc)
cos(B) = (a^2 + c^2  b^2)/(2ac)
cos(C) = (a^2 + b^2  c^2)/(2ab)
You can read more about the Law Of Cosines here.
In roblox you will need to use the math.acos function to get the angle in radians:angle = math.acos((b^2 + c^2  a^2)/(2*b*c))
The acos function is the inverse of the cos function.
• Now we need to put this inside of our function, solving for angles "B" and "C":
This is the triangle we are solving.local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) local planeCF = CFrame.new(originpos, targetpos) * CFrame.Angles(0, 0, math.rad(angle or 0)) local c = (originpos  targetpos).magnitude local B = math.acos((a*a + c*c  b*b)/(2*a*c)) local C = math.pi math.acos((a*a + b*b  c*c)/(2*a*b)) return planeCF, B, C return it so we can use the values end
Note: We need to take angle C away from pi (180 degrees) so it is the correct orientation.
Generally, if the angle isn't correct, try adding these to the end of the statement:
+math.pi, +math.pi/2, math.pi, math.pi/2
e.glocal B = math.acos((a*a + c*c  b*b)/(2*a*c)) + math.pi/2
(sorry about why I can't provide a legitimate reason to this, I just know that this sometimes works)
Testing it:
• Our function is almost complete but you can test it now and it should work.
• We need to use the "Stepped" event of RunService so that it will run in our script:game:GetService("RunService").Stepped:Connect(function() end)
This runs every step of the run service, which is roughly 1/60th of a second (much better than while loops), however if you are using local scripts, make sure you use RenderStepped as this will bind to the client refresh rate.
• Now all we need to is call our function and set the cframes of the joints and we are ready to go!local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) local planeCF = CFrame.new(originpos, targetpos) * CFrame.Angles(0, 0, math.rad(angle or 0)) local c = (originpos  targetpos).magnitude local B = math.acos((a*a + c*c  b*b)/(2*a*c)) local C = math.pi math.acos((a*a + b*b  c*c)/(2*a*b)) return planeCF, B, C end game:GetService("RunService").Stepped:Connect(function() local planeCF, B, C = solveIK(origin.Position, target.Position, la, lb) pa.CFrame = planeCF * CFrame.Angles(B, 0, 0) * CFrame.new(0, 0, la/2) pb.CFrame = pa.CFrame * CFrame.new( 0, 0, la/2) * CFrame.Angles(C, 0, 0) * CFrame.new(0, 0, lb/2) end)
When we are setting the CFrame of our part "pa" we are setting it to the plane CFrame (where the vector construction of it is the same as the origin, and pointing at the target position) then we are setting the rotation of the part so it in the right direction of the triange, then offsetting it so that only the end of the part is touching the origin. Note how in roblox you have to very specific order for offsets and rotationsfor specific translations. One will make you do a rotation around the origin point (offset * angle) while the other will rotate it then offset it (transform it), (angle * offset), which in most cases is what you want.
The offset for pb is setting it to the the CFrame of pa, then offsetting it by half of length a (to put it at the end of part a), then applying the correct triangle rotation to it, then offsetting it by half of length b (so the end of b touches the end of a), thus creating a joint.
You should notice that if you run this, the parts do make a joint, however if you go outside the max length they will disappear or if one is shorter than the other, it will also disappear.Making some improvements:
• All we need to now is make it so that when the target is out of reach, the parts point to the target but stay attached to the origin.
So, we add some if statements:local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) local planeCF = CFrame.new(originpos, targetpos) * CFrame.Angles(0, 0, math.rad(angle or 0)) local c = (originpos  targetpos).magnitude if c < math.max(a, b)  math.min(a, b) then when one part is smaller than the other and the target is out of reach elseif c > a + b then if the target is too far away from the origin else local B = math.acos((a*a + c*c  b*b)/(2*a*c)) local C = math.pi math.acos((a*a + b*b  c*c)/(2*a*b)) return planeCF, B, C end end game:GetService("RunService").Stepped:Connect(function() local planeCF, B, C = solveIK(origin.Position, target.Position, la, lb) pa.CFrame = planeCF * CFrame.Angles(B, 0, 0) * CFrame.new(0, 0, la/2) pb.CFrame = pa.CFrame * CFrame.new( 0, 0, la/2) * CFrame.Angles(C, 0, 0) * CFrame.new(0, 0, lb/2) end)
The first if statement checks if the target is too close to the origin (if the parts are not the same length, this is the only time this if statement will run), where the long part offsets the short part too far off the origin so it can't reach it, or vice versa.
The second if statement checks if the target is out of reach. this sets the parts as straight angles and makes them point at the target part, while being attached to the origin part.
• Now all we need to do it calculate what should happen if one of the first two occur.local origin, target, pa, pb = workspace.origin, workspace.target, workspace.a, workspace.b local la, lb = pa.Size.Z, pb.Size.Z local function solveIK(originpos, targetpos, a, b, angle) local planeCF = CFrame.new(originpos, targetpos) * CFrame.Angles(0, 0, math.rad(angle or 0)) local c = (originpos  targetpos).magnitude if c < math.max(a, b)  math.min(a, b) then local x = 0 if a < b then x = 1 end return planeCF, math.pi*x, math.pi elseif c > a + b then return planeCF, 0, 0 else local B = math.acos((a*a + c*c  b*b)/(2*a*c)) local C = math.pi math.acos((a*a + b*b  c*c)/(2*a*b)) return planeCF, B, C end end game:GetService("RunService").Stepped:Connect(function() local planeCF, B, C = solveIK(origin.Position, target.Position, la, lb) pa.CFrame = planeCF * CFrame.Angles(B, 0, 0) * CFrame.new(0, 0, la/2) pb.CFrame = pa.CFrame * CFrame.new( 0, 0, la/2) * CFrame.Angles(C, 0, 0) * CFrame.new(0, 0, lb/2) end)
I should state for the first one there is no "best method" to solve this, however the one I have given should do the job.
It returns the plane cf, and part "a" remains attached to the the origin part, in the direction it normally would, then it makes the part "b" attempt to point to the target position. of course this can never be truly solved because of the length problems.
The "x" variable" accounts for when part "a" is either longer or shorter than part "b".
Of course, the second one just returns the planeCF and then the angles are 0 since it needs to point straight to the origin.
• There we go! Thats all the code you will need to do single joint Inverse kinematics![Updated on 29/11/2018 @ 19:52PM]
Thank you for reading this tutorial. If you notice any mistakes or improvements that could be made, you can tell me.
Tutorial inspired by WhoBloxedWho.