Limit CFrame Rotation

Hello! All day I have been trying different ways to limit the CFrame rotation while following the players mouse. I want to make a tank turret that can only rotate so far. Can someone point me in the right direction? Any advice is appreciated! I’m looking for a simple system.

4 Likes

To determine an angle, you need a reference value. I’ll assume that you have the forward vector for the tank when it is not rotated. We can use the property that the dot product of two vectors is the cosine of the angle between them scaled by both vectors magnitude. If we make the forward vector and the vector from the tank to the mouse location both unit vectors then their dot product will by the cosine of the angle between them. Here is the code:

local maxAngle = math.cos(math.pi/2) -- 90 degrees either way

local function angle(tPos, tForward, mousePos)
    return tForward.Unit:Dot((mousePos - tPos).Unit)
end

local function isInAngleRange(tPos, tForward, mousePos)
    return math.abs(angle(tPos, tForward, mousePos)) < maxAngle
end
2 Likes

Thank you for a fast response! This is unfortunately beyond my capability. I am terrible with this kind of stuff! I think I understand what it is doing from your description but I don’t know how I can implement it into a system. I’m going to read over it more and try my best! Thank you!

Hi. This may be silly and I could be doing this completely wrong. Is this how it is intended to be used? This was made strictly for a test.

local Tank = workspace.Tank
local Core = Tank.Core
local Cannon = Tank.Cannon

local FakeMouse = workspace.FakeMouse

local Base_CFrame = Core.CFrame
local maxAngle = math.cos(math.pi/2)

local function angle(tPos, tForward, mousePos)
    return tForward.Unit:Dot((mousePos - tPos).Unit)
end

local function isInAngleRange(tPos, tForward, mousePos)
    return math.abs(angle(tPos, tForward, mousePos)) < maxAngle
end

function Render()
	local Test = isInAngleRange(Cannon.CFrame.p,Base_CFrame.p,FakeMouse.Position)
	if not Test then
		Cannon.CFrame = CFrame.new(Core.CFrame.p,FakeMouse.Position)
	end
end

game:GetService('RunService').Stepped:Connect(Render)
--game:GetService('RunService').RenderStepped:Connect(Render)

1 Like

Yeah, that’s how you’d use it. I’d make a few minor adjustments though:

local Tank = workspace.Tank
local Core = Tank.Core
local Cannon = Tank.Cannon

local FakeMouse = workspace.FakeMouse

local Base_CFrame = Core.CFrame
local maxAngle = math.cos(math.pi/2)

local function angle(tPos, tForward, mousePos)
    -- This assumes that the turret base is flat. We find the vector to the mouse
    -- and remove the y component so that the height of the mouse's hit position
    -- doesn't mess up the angle.
    local pos = Vector3.new(mousePos.X - tPos.X, 0, mousePos.Z - tPos.Z)
    return tForward.Unit:Dot(pos.Unit)
end

local function isInAngleRange(tPos, tForward, mousePos)
    return math.abs(angle(tPos, tForward, mousePos)) < maxAngle
end

function Render()
        -- this test assumes that the base never moves or rotates
	local Test = isInAngleRange(Base_CFrame.p, Base_CFrame.LookVector, FakeMouse.Position)
	if not Test then
		Cannon.CFrame = CFrame.new(Core.CFrame.p, FakeMouse.Position)
	end
end

game:GetService('RunService').Stepped:Connect(Render)
--game:GetService('RunService').RenderStepped:Connect(Render)

I removed the Y component of the mouse position so that if the mouse is too high then it wont shorten the maximum angle (since some of that angle would be going upwards). I also make the angle range relative to the turret base which it is assumed is flat, never moves, and never rotates.

5 Likes

I am not quite sure if what we have is working correctly. The Test function is returning false no matter where I position the FakeMouse part and not limiting the rotation.

Thank you for giving me your time and please don’t feel obligated to figure this all out for me!

Ah, shoot, I got the range of cosine mixed up. This might fix it, unless there is another issue:

local function isInAngleRange(tPos, tForward, mousePos)
    return angle(tPos, tForward, mousePos) >= maxAngle
end

Just for your notes, the cosine of math.pi/2 is 0, so we are actually checking if the angle is greater than zero. Less than it means that it is pointing more than 90 degrees away so is going in the opposite direction. The range of cosine is [1, -1].

Theta | Cosine
0     |  1 -- 0 degrees
pi/2  |  0 -- 90 degrees
pi    | -1 -- 180 degrees

The maximum angle between two vectors is 180 degrees (pointing opposite directions)

4 Likes

It’s working great now! I can’t thank you enough for all the help you provided!

Thank you so much!

1 Like

I’m not a math wizard and it seems that your problem is already solved, but I would like to also point you towards the presence of math.clamp. It allows you to, well, as the function name states, clamp a value. You can of course do this mathematically too in a function, though for ease-of-access purposes and those not that familiar with mathematical stuff, it helps out some.

To build on that, after making the CFrame pointing at the mouse you can call CFrame:ToEulerAnglesXYZ() to get the X, Y, and Z rotations. Then, you can clamp the Y angle to the angle of the base’s CFrame +/- a maximum angle.

3 Likes