Allowing a CFrame to rotate on only a single axis and reading that rotation

Hello friends. I am developing a system that requires me to get the rotation of a CFrame relative to another CFrame which isn’t 0,0,0.

I need the CFrame’s upVector and rightVector to be based on another CFrame, while allowing its lookVector to be independent from the other (the others lookVector rotation being 0). After achieving that, I need to read that rotation out into a degree system that’s relative to 360 and 0 or -180 and +180. How would I achieve such a task?

Thanks in advance!

2 Likes

Not sure what you mean. Do you just want to rotate a part around the lookVector another part? Or just calculate the relative CFrame/Angle of two parts?

I’m assuming that the lookVector of both CFrames are the same. The position doesn’t matter here, we are only looking for a difference in orientation. These assumptions being the case, we only need one vector to represent the difference between the rotations: either the rightVector or upVector. Since the dot product of two unit vectors is the cosine of the angle between them, the inverse cosine of the dot product will give you the angle between the rotations. Here is what it looks like:

local function angle(cf1, cf2)
  return math.acos(cf1.rightVector:Dot(cf2.rightVector))
end
4 Likes

Thanks a ton, this is exactly what I’m looking for, how would I be able to tell if the rotation is rotating to the right or the left?

At the moment, the current behavior results in the value returning positive regardless of which direction it’s rotated in. This is problematic in my case, how would I make it so one direction is negative and the other is positive?

https://gyazo.com/5f4be0fdd6be7082a3cb2b99f562536f.gif

Perform the same operation, but using the rightVector of one CFrame and the upVector of the other. This offsets the angle by 90 degrees, putting the dot product between [-1 1] instead of [0 1] and the arccos to [-pi/2 pi/2] instead of [0 pi/2].

2 Likes

Thanks, but changing one of them to a right vector did nothing, it just shifted the rotation 90 degrees. Do you think you could help me out again?

Sorry, the acos of [-1 1] is [0 pi], with pi/2 meaning the upvectors are in the same direction. Subtract pi/2 if you want positive / negative values to indicate direction. This will give the [-pi/2 pi/2] range I mentioned last post.

2 Likes
local P1 = script.Parent.P1
local P2 = script.Parent.P2

while wait(0.25) do
   print("A: "..(math.acos(P1.CFrame.upVector:Dot(P2.CFrame.rightVector))-math.pi/2)
end

Oh wow, it worked quite well, only now it’s basically divided the problem into two halves.

https://gyazo.com/a586054e474951600a6366b88bb40985.gif

The left and right parts work, now I need to tell if it’s up or down, is there a simple way to do this?

I was thinking of getting the CFrame then multiplying it by another CFrame out the up or Right vector then comparing the positions, but then that incorporates position which then creates more problems depending on the position and angle of the actual part in the workspace if it’s not laid flat on all other axes.

1 Like

Yes, if the upvectors dot each other is 1, then they are facing the same direction. If they are orthogonal (90 degrees) then it is zero, and if they are in opposite directions then it is -1. So this is the final function:

local function angle(cf1, cf2)
  local theta = math.acos(cf1.upVector:Dot(cf2.rightVector)) - math.pi/2
  local isSameSide = cf1.upVector:Dot(cf2.upVector) > 0
  return theta, isSameSide
end

Generally it is best to use the isSameSide variable in your logic to avoid the issue of -180 and 180 being the same value but having a 360 difference. But, if you must have the angle from pi to -pi, this code will do it:

local function angle(cf1, cf2)
  local theta = math.acos(cf1.upVector:Dot(cf2.rightVector)) - math.pi/2
  local isSameSide = cf1.upVector:Dot(cf2.upVector) > 0
  if isSameSide then
    return theta
  elseif theta < 0 then
    return -math.pi - theta
  else -- Note that 0 (180 degrees) returns positive pi, not -pi.
    return math.pi - theta
  end
end

Another option is to use atan2. To do this, one CFrame would need to be converted into the local space of the other. Here is an example of how it can be done:

local function angle(cf1, cf2)
  local right = (cf1:inverse() * cf2).rightVector
  return math.atan2(right.Y, right.X)
end

This option puts the angle in the range of [0 2pi], and you still have the issue of 0 and 2pi being the same angle but 2pi apart.

For distance in modular space, like angles, you can use this function:

local function dist(a, b, mod)
  if a % mod  > b % mod then
    a, b = b, a
  end
  return math.min(
    (b - a) % mod
    (a + mod - b) % mod
  )
end

This results in 0 - 3pi mod 2pi becoming 0 + 2pi - 3pi mod 2pi which is pi. In addition, the distance between 3/4 pi and 0 is 0 + 2 pi - 3/4 pi mod 2pi which is 1/4 pi.

1 Like