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`

.