[Solved] Getting the angle between two frames inside of the player screen

I am looking to make a function that will tell me the exact angles between a frame that can be anywhere on the screen, lets’ call it Frame A, and another frame that can also have any position on the screen.

I want to get the angle in degrees (from 0 to 360) depending on where Frame B is with relation with Frame A.

An example would be:
Screenshot_6 ~

Where FrameA is the Red Frame and FrameB is the Blue Frame. Sadly, checking position and doing cartesian math has been shown to be really inaccurate and because of what I am looking for a solution that involves the angles would be perfect.

Trigonometric functions are not exact. Have you ever wondered why you can’t find a formula for them? We can approximate them with infinitely long Taylor series, and then we have to evaluate enough terms so that we get within a desired degree of accuracy. Unfortunately this is relatively very slow to other operations a computer performs so the accuracy is generally around 1e-12. You’ll have to play around with it to get the exact number for the implementation RBXLua and Luau use, but I’ve seen most languages around that value in my experiments.

Floats don’t do well when you need exact results. As much as we like to pretend that they represent the numbers we work with in math by hand, they don’t. They have limitations on accuracy so perfect results are unattainable with nieve implementations.

That being said, you can write a symbolic math system. For each number you can track it’s all of its terms and their factors. You can also combine terms as long as it doesn’t lead to irrational numbers. But this is a lot of work so not recommended.

The most accurate method to attain the cosine of the angle (think, cos theta = x in a right triangle with radius 1). This can be done by taking the dot product of the lookVector of the red CF and the unit vector from the red CF to the blue CF. Another way to think of this value is “how far forward is the blue CF along the red CF’s look vector”. To find how far left/right the blue CF is, simply use the red CF’s unit rightVector instead of lookVector.

Yet another method is to convert the blue CF to the object space of the red CF. This performs those above calculations for you and stores them in the new position.

How am I supposed to use CFrame if they are GUIs and work with Udim2…? There’s no lookVector there. Or did I misunderstand what you said?

Ah, by frames you meant the Frame instance not CoordinateFrames. My mistake. In that case if you want the angle you can take the atan2 of the absolute position x and y differences. You could also take the acos of the x difference or as in of y difference. You could also put their absolute positions into a CFrame and a 0 for the third axis and use the methods described above.

If you give me a bit, I’ll see if I can’t write a more accurate trigonometric function for you. It’ll be slow though.

2 Likes

Please do, right now I am struggling with this project and this is the only thing I need to finish it up.

I’d suggest this algorithm. I haven’t tried it before and just thought of it, but I suppose you could try it.

Here’s a visual representation

Here, we have two circles. Red and Blue, and they represent the frames in your graphic. We also have a few lines. The light blue one represents how far away the two frames are from each other on the X axis, which we will call the “Distance Value”.

The Red Vector represents the Flat Value, which is simply how far away the blue frame is from the red frame when it is on the same X plane. This can be calculated by “rotating” the frame along a circle’s circumference, whose radius is the normal distance between the two frames. Now, call the Dot Product (represented by the green vector) on the Flat Value vector and the vector between the two frames. By plugging in some magic numbers (I am NOT doing the calculus to prove the area of a circle in a basic devforum post) and fancy math formulas, you can convert the Dot Product value (0-1) to a number in degrees. Bam, my wordvomit is finished.

1 Like

Adding on to what @marfit said… you could get Theta by doing some quick inverse trig. To get the angle, you could do acos(a • b) or in Lua: math.acos(a:Dot(b)) . This is assuming that the vectors are normalized.

Okay, I implemented the expansions for the trigonometric functions. As it turns out, the trig functions already compute the values to the highest precision possible in double precision (2^-52, roughly 1.1e-16). The only way to get higher precision would be to implement symbolic operations, lookup tables for known values, or implement higher precision numbers.

Trig functions + test code
local epsilon = 2^-53
local half_pi = math.pi/2

local function trig(x, init_pow)
	local new = x ^ init_pow
	local sum = 0

	local x2 = x * x

	local power = new
	local factorial = 1
	local sign = true
	local i = init_pow + 1
	while new ~= sum do
		sum = new

		-- Calculate next term
		sign = not sign
		power = power * x2
		factorial = factorial * i * (i + 1)
		i = i + 2
		
		local delta = power / factorial
		if sign then
			new = sum + delta
		else
			new = sum - delta
		end
	end

	return sum
end

local function sine(x)
	return trig(x, 1)
end

local function cosine(x)
	return trig(x, 0)
end

local function tangent(x)
	-- This guy doesn't have a nice series expansion
	return sine(x) / cosine(x)
end

local function arcsine(z)
	local new = z
	local sum = 0

	local z2 = z * z

	local power = z
	local odd_factorial = 1
	local even_factorial = 1
	local i = 1
	-- Values grow to infinity and result in new is NaN...
	-- but IEEE 754 states NaN ~= NaN.
	while new ~= sum and new == new do
		sum = new

		power = power * z2
		odd_factorial = odd_factorial * i
		even_factorial = even_factorial * (i + 1)
		i = i + 2
		new = sum + odd_factorial / even_factorial * power / i
	end

	return sum
end

local function arccosine(z)
	return half_pi - arcsine(z)
end

local function arctangent(z)
	local new = z
	local sum = 0

	local z2 = z * z

	local power = z
	local sign = true
	local i = 1
	-- Values grow to infinity and result in new is NaN...
	-- but IEEE 754 states NaN ~= NaN.
	while new ~= sum and new == new do
		sum = new
		
		sign = not sign
		power = power * z2
		i = i + 2
		local delta = power / i
		if sign then
			new = sum + delta
		else
			new = sum - delta
		end
	end

	return sum
end

-- Test results against built-in functions.
local count = 4
for i = 0, 2 * math.pi, math.pi * 2/count do
	print('pi:', i / math.pi)
	print('sin:', sine(i) - math.sin(i))
	print('cos:', cosine(i) - math.cos(i))
	print('tan:', tangent(i) - math.tan(i))
	
	local x = math.cos(i)
	local y = math.sin(i)
	
	print('arcsin:', arcsine(y) - math.asin(y))
	print('arccos:', arccosine(x) - math.acos(x))
	print('arctan:', arctangent(y/x) - math.atan(y/x))
	print()
end

That being decided, the angle between two frames seems to be most accurately represented in this code (the method that everyone has been saying in different ways):

local right = Vector2.new(1, 0)
local function angle(frame_1, frame_2)
    local d = frame_1.AbsolutePosition - frame_2.AbsolutePosition
    return math.acos(right:Dot(d.Unit))
end

Although this doesn’t give a full 360 degree range, so you could do this:

local function angle(frame_1, frame_2)
    local d = frame_1.AbsolutePosition - frame_2.AbsolutePosition
    return math.atan2(d.Y, d.X)
end

or some magic wizardry with the signs of the dot product of the right vector and up vector.

2 Likes

The second option seems to be printing numbers from -2.8 and 2.8. I am not really sure how can I work with that…

The Code:

local Mouse = game.StarterGui.SpellCasting.Mouse
local Core = game.StarterGui.SpellCasting.Core

local function angle(frame_1, frame_2)
    local d = frame_1.AbsolutePosition - frame_2.AbsolutePosition
    return math.atan2(d.Y, d.X)
end

while wait() do
	warn(angle(Core, Mouse))
end

I didn’t get to state what I am trying to achieve after all too, maybe this will help. I want to see if the GUI is inside one of those 4 sections of the frame, and because checking if it is between 45º and 135º for north as an example seemed to work way better than checking the positions of the screen that were too inaccurate.

https://gyazo.com/1fa50aaec5bfabbea216263d7eaf2f81

atan2 returns the angle in the range (-pi, pi]. Due to which CFrame is first, 0 is pointing left. More positive values are clockwise rotations, while negative values are the opposite.

Ah, well, if that is your use case, you can simply check if the *absolute X or Y position difference is greater, and then if the greater axis is positive or negative to determine which section of the frame the position is in.

To add on to what @IdiomicLanguage said, here’s how you convert the values you are currently receiving to the values you’re expecting:

v = -math.deg(v - math.pi)

You shouldn’t be using any of these algorithms if you just want to check what quadrant the frame is in.

Yea but if you look at how the X and Y Positions work on roblox screen, (0,0) is the top left part of the screen and (MaxSizeOfScreenX, MaxSizeOfScreenY) is on the bottom right. Not only that, but on certain screens after a certain part the X and Y turn negative because it’s above the average screen size.

Strangely enough it has a little offset above where the frame is :thinking:

Also it doesn’t really tells the angle correctly for some reason.

Anchor point of the center frame isn’t set to its center. You’re comparing its top left corner to the center point of the invisible frame, which is 72 degrees. That should also explain the inaccuracy.

The reason the pixel position turns negative is due to the position being in the area normally reserved for the Roblox core GUI / header.

You will notice that pixels on the top left to bottom right corner all have the same x and y value. Those closer to the top right have a larger x value and those closer to the bottom left have a larger y value. If we offset the origin from the top left of the screen to the big frame with the 4 quadrants, then the absolute Y being larger than the absolute X means the position falls into either the South or North quadrants, otherwise the East or West. You can then use the sign of the major axis to determine which of those two quadrants the position is in. For example if the absolute difference of the y value is larger than the quadrant is either the North or South. If Y is negative then it is the North quadrant.

image

Anchor Point was set to 0.5 0.5
The frame is 100x100

Can’t replicate.

Using formula -deg(atan2(x2-x1,y2-x1)-pi). Offsets are different, but mine is exactly accurate.

Tried changing the sizes for both of them, looks like they got more accurate. But values like 90 are turning into 90.32423 and etc. Mind showing your code?