Create a line between two points using a frame and trigonometry in a ui

I’m creating a node based editor. When I connect two nodes together, I want a thin line to appear to connect the input and outputs together. I thought this would be some simple trigonometry maths (treat the two points like a triangle) but I can’t get the line to A) be angled correctly and B) be positioned so that it starts at point A and ends at point B.

The two white circles are examples of node connections. I got the absolute position of them (as they are parented to the boxes so the positions are relative to that node) and tried using math.tanh(Blength / Alength) to calculate the angle (I figured because I had Y == Opposite and X == Adjacent, the use of tan would be appropriate) . I did math.sqrt(Alength^2 + Blength^2) to get the hypotenuse, which would end up being the length of the connection line.

Here was my test script:

local Content 		= script.Parent.WidgetBase.Content
local SceneContent 	= workspace.SceneContent

function TanH(A, B)
	return math.tanh(B / A)
end

function Length(A, B)
	return math.sqrt(A^2 + B^2)
end

local A, B = Content.BeginSequence.NodeOutput, Content.EndSequence.NodeInput

local XLength = B.AbsolutePosition.X - A.AbsolutePosition.X
local YLength = B.AbsolutePosition.Y - A.AbsolutePosition.Y

local line = Instance.new("Frame")
line.BorderSizePixel = 0
line.AnchorPoint = Vector2.new(0.5, 0)
line.Size = UDim2.new(0, Length(XLength, YLength), 0, 1)

local rotation = math.deg(TanH(YLength, XLength))
line.Rotation = rotation
line.Position = UDim2.new(0, A.AbsolutePosition.X - (XLength / 2), 0, A.AbsolutePosition.Y - (YLength / 2))
line.Parent = Content.Parent -- Content does not fill the whole screen, but its parent does!

This gives off a very incorrect output as I’m clearly doing something wrong:
image
From the test it seems the length of the line is correct but the angle and position are completely wrong.

Does anyone know how I can correctly angle and position this line so that it starts at the green circle and ends at the red one?

7 Likes

I believe you can still use Magnitude with UIs so, you would first get the magnitude of the 2 UIs, this will be the length of the connection:

local Mag = (Node1.Position - Node2.Position).Magnitude

Then, you would find the angle between the 2 vectors:

--[[
XLength is 2 
YLength is 1
--]]
local Angle = math.abs(YLength / XLength) * 90
--[[
Divide the bigger number by the smaller number
--]]
local function DivideSmaller(X, Y)
    if X > Y then
        return math.abs(YLength / XLength) * 90
    else
        return math.abs(XLength / YLength) * 90
    end
end

You would then find the Middle point between the 2 Nodes simply by subtracting one position by another:

local CenterPosition = Node1.Position - Node2.Position

Then you would just position the frame with the length and the rotation:

Frame.Rotation = DivideSmaller(X, Y)
Frame.Position = CenterPosition
Frame.Size = Udim2.fromOffset(Mag, 1)

This is what the full script would look like:

local XLength = B.AbsolutePosition.X - A.AbsolutePosition.X
local YLength = B.AbsolutePosition.Y - A.AbsolutePosition.Y

local function DivideSmaller(X, Y)
    if X > Y then
        return math.abs(Y / X) * 90
    else
        return math.abs(X/ Y) * 90
    end
end

local Mag = (Node1.Position - Node2.Position).Magnitude
local Angle = math.abs(YLength / XLength) * 90
local CenterPosition = Node1.Position - Node2.Position

Frame.Rotation = DivideSmaller(XLength, YLength)
Frame.Position = CenterPosition
Frame.Size = UDim2.fromOffset(Mag, 1)

I haven’t tested this out but it should work.

2 Likes

I got an error that magnitude isn’t a valid member of udim2, but considering I’m just using offset I should be able to convert the position’s offset to a Vector2, right? I believe magnitude is a vector function so it should work with Vector2 as well.

use magintude on Vector2 then use UDim2.fromOffset(x, y) with the X and Y of that Vector2 value

1 Like

yes, that should work, thanks!

Okay, I think I have it all working except the position part. This is what I have:

local Content 		= script.Parent.WidgetBase.Content
local SceneContent 	= workspace.SceneContent

local A, B = Content.BeginSequence.NodeOutput, Content.EndSequence.NodeInput

local line = Instance.new("Frame")
line.BorderSizePixel = 0

local Mag = (
	  Vector2.new(A.AbsolutePosition.X, A.AbsolutePosition.Y) 
	- Vector2.new(B.AbsolutePosition.X, B.AbsolutePosition.Y)
)

--Mag = UDim2.fromOffset(Mag.X, Mag.Y)

local XLength = B.AbsolutePosition.X - A.AbsolutePosition.X
local YLength = B.AbsolutePosition.Y - A.AbsolutePosition.Y

local CenterPosition = A.AbsolutePosition - B.AbsolutePosition

local function DivideSmaller(X, Y)
	if X > Y then
		return math.abs(Y / X) * 90
	else
		return math.abs(X/ Y) * 90
	end
end

line.Rotation 	= DivideSmaller(XLength, YLength)
line.Position 	= UDim2.fromOffset(CenterPosition.X, CenterPosition.Y)
line.Size 		= UDim2.fromOffset(Mag.Magnitude, 1)

line.Parent 	= Content.Parent

I modified some parts as I needed to use absolute position for everything otherwise the line is 20 pixels long lol.

However, the position of the final result is in the negatives. Rotation and size look correct from the properties tab, but its positioned at (0, -90, 0, -50)

image

Any idea as to why, or have I maybe done the position calculation wrong?

Edit: It’s definitely wrong. The absolute position of A is at 350, 200
Edit 2: Removed anchor point. Didn’t fix the issue but it would probably have caused more down the line

Okay, so I looked at the code and reviewed it. I realized I did the positioning wrong, you need to get the average of the 2 positions:

local CenterPos = (Pos1 + Pos2)/2 -- They have to be vectors

hmmm

image

It’s closer than it was before. Also, I think the rotation might be off slightly but I think I know why that is.


local A, B = Content.BeginSequence.NodeOutput, Content.EndSequence.NodeInput

local line = Instance.new("Frame")
line.BorderSizePixel = 0

local Mag = (
	  Vector2.new(A.AbsolutePosition.X, A.AbsolutePosition.Y) 
	- Vector2.new(B.AbsolutePosition.X, B.AbsolutePosition.Y)
)

--Mag = UDim2.fromOffset(Mag.X, Mag.Y)

local XLength = B.AbsolutePosition.X - A.AbsolutePosition.X
local YLength = B.AbsolutePosition.Y - A.AbsolutePosition.Y

local CenterPosition = (A.AbsolutePosition + B.AbsolutePosition) / 2

local function DivideSmaller(X, Y)
	if X > Y then
		return math.abs(Y / X) * 90
	else
		return math.abs(X/ Y) * 90
	end
end

line.Rotation 	= DivideSmaller(XLength, YLength)
line.Position 	= UDim2.fromOffset(CenterPosition.X, CenterPosition.Y)
line.Size 		= UDim2.fromOffset(Mag.Magnitude, 1)

line.Parent 	= Content.Parent

Not quite sure why this is slightly off.

2 Likes

Make sure the anchor point of the line is 0.5, 0.5:
image
It’s pretty close for me.


I believe that the rotation is actually correct, it’s just the rotation is an int value so it wouldn’t have decimals(therefore less accurate, more rounded number).

ohhhhh, okay. I reset the anchor point to 0,0, let me go see if that makes a difference

It’s still off, but a little closer. This is a really weird issue:
image
It appears the X is lined up properly though, just not the Y. Also, the line doesn’t look like it’s going to meet up. Can you send me your code so I can compare and maybe spot any issues?

I’m still working on it, although I found some problems with the code and I’m trying to debug it, will edit the reply later if I have a solution:

local RS = game:GetService("RunService")
local TS = game:GetService("TweenService")
local TI = TweenInfo.new(4, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true)

local Parent = script.Parent
local F1 = Instance.new("Frame", Parent)
F1.AnchorPoint = Vector2.new(0.5, 0.5)
F1.Position = UDim2.fromOffset(math.random(1, 300), math.random(1, 300))
F1.Size = UDim2.fromOffset(20, 20)
local F2 = Instance.new("Frame", Parent)
F2.Position = UDim2.fromOffset(math.random(1, 300), math.random(1, 300))
F2.Size = UDim2.fromOffset(20, 20)
F2.AnchorPoint = Vector2.new(0.5, 0.5)

local function DivideSmaller(X, Y)
	return math.atan2(Y, X) * 180 / math.pi
end

TS:Create(F1, TI, {Position = UDim2.fromScale(0.8, 0.5)}):Play()

local Frame = Instance.new("Frame")

RS.RenderStepped:Connect(function()
	local XLength = F1.AbsolutePosition.X - F2.AbsolutePosition.X
	local YLength = F1.AbsolutePosition.Y - F2.AbsolutePosition.Y
	local Mag = (
		Vector2.new(F1.AbsolutePosition.X, F1.AbsolutePosition.Y) 
		- Vector2.new(F2.AbsolutePosition.X, F2.AbsolutePosition.Y)
	)
	local CenterPosition = (F2.AbsolutePosition + F1.AbsolutePosition) / 2
	Frame.Rotation = DivideSmaller(XLength, YLength)
	Frame.Position = UDim2.fromOffset(CenterPosition.X, CenterPosition.Y)
	Frame.Size = UDim2.fromOffset(Mag.Magnitude, 1)
end)

Frame.Parent = Parent
Frame.AnchorPoint = Vector2.new(0.5, 0.5)

This is fully working code, try this.


I’ve researched the web and I found that you can get the angle from this formula:

math.atan2(Y, X) * 180 / math.pi

To test this code, just create a ScreenGui in StarterGui and paste this script in a LocalScript.

12 Likes

Sorry my computer froze and I had to restart it. I’ll check out the script once I get studio up again

1 Like

Okay, will do. I was testing it like that before as well. There were some funky angles produced by the previous method. A loop to make one end go up and down made the line spin really fast lol.

Oh nice! It does work! All i need to do now is position the line from the center of the node connector and not the top corner. Should be a very basic calculation.

Also, marked that as the solution. Its great to see it working with loops too as that’s how it’s going to be run in the end product!

1 Like

Works absolutely perfectly! Just did some formatting to make it look nicer and the end result is exactly what I wanted, thank you so much @VegetationBush ! :smiley:

3 Likes

Can you show me it, Im having a similar issue