I’m dealing with a trig issue about angle wrapping, where if an angle crosses the bounds in range [-180, 180), it wraps around. For example, if an angle exceeds 180, it will wrap back around to -180, which is causing the following issue:
The red line in the circle represents -180 and 180. Note how the target radians are being outputted.
This is what I am trying to accomplish:
See how the NPC’s head only wraps when you are directly behind them? How can I accomplish that, no matter what direction the NPC is facing?
Here is the function:
function lookAt(npcCharacter)
local INIT_TARG_RX = npcCharacter.Head.Orientation.X
local INIT_TARG_RY = npcCharacter.Head.Orientation.Y
local MAX_X, MAX_Y = math.rad(30), math.rad(60)
local LOOK_DIST = 10
local FRICTION = 1
local rx, ry = 0, math.rad(INIT_TARG_RY)
local targRX, targRY = 0, math.rad(INIT_TARG_RY)
local function moveHead(initCFrame)
rx += (targRX - rx) * FRICTION
ry += (targRY - ry) * FRICTION
local offset = CFrame.new(0, 0.5, 0) -- 0.5 for half the head's size
local neckCFrame = CFrame.new((initCFrame * CFrame.new(0, 1, 0)).Position)
local newCFrame = neckCFrame * CFrame.fromOrientation(rx, ry, 0) * offset
npcCharacter.Head.CFrame = newCFrame
end
while task.wait() do
-- Don't run if the player don't exist
Character = Player.Character
if not Character then continue end
-- The player's RootPart is required
RootPart = Character:FindFirstChild("HumanoidRootPart")
if not RootPart then continue end
local initCFrame = npcCharacter.Torso.CFrame
local initRX, initRY = initCFrame:ToOrientation()
local magnitude = (RootPart.Position + Vector3.new(0, 1.5, 0) - npcCharacter.Head.Position).Magnitude
if magnitude > LOOK_DIST then -- Is the player out of range?
-- Player is out of range, reset rotation
targRX = math.rad(INIT_TARG_RX)
targRY = math.rad(INIT_TARG_RY)
moveHead(initCFrame)
continue
end
-- Player is in range, stare into the player's soul
local lookAt = RootPart.Position
local rotatedCFrame = CFrame.lookAt(initCFrame.Position, lookAt)
targRX, targRY = rotatedCFrame:ToOrientation()
targRX = math.clamp(targRX, -MAX_X, MAX_X)
targRY = math.clamp(targRY, initRY - MAX_Y, initRY + MAX_Y)
moveHead(initCFrame)
end
end
In the last bit of lines, I am getting the target angles “targRX, targRY” (the angles of which the NPC’s head points towards the player) by using :ToOrientation() (which I’m assuming is the problem because I have to deal with it’s boundaries). I then clamp these angles so that way the NPC’s aren’t snapping their heads around to look at you.
Maybe I’m asking for a function that normalizes these angles? Or maybe this is too extra and I should just use :Lerp()? I looked around the dev forum but only found a single post about angle wrapping, which the solution was using :Lerp() instead, but that solution doesn’t deal with clamping like I have here. I looked on Stack Overflow but that only taught me the terminology of this mess lol.
How do I fix this angle wrapping?