Assistance with vertical alignment of tank turret

I was working on simple tank turret system which uses HingeConstraints to align turret vertically and horizontally with mouse position of player. I managed to get horizontal alignment work perfectly but had serious issue with vertical alignment.
System works like this:

There are 3 parts which include MainPart (only non-transparent part on image), TurretAxis (half transparent part above MainPart with horizontal hinge) and ElevationAxis (one at the main gun with vertical hinge)


(Another picture with attachmenents of those two hinges at same position to reduce possible confussion)
image

So my problem is that in some cases the vertical angle is not calculated correctly and I seem to can’t find actual reason why. It’s most likely with math module which I might not use correctly in my goals.

(For next pictures small explanation: red highlighted part is mouse position aka where the gun should aim and green one is current position of gun aim)

Example of correct alignment:


Examples of not correct alignment:

And heres the how the code works:

Theres local script which does all calculations and sends them to server via RemoteEvent:

local tank = workspace["M113 APC"].Misc
local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()
local event = game.ReplicatedStorage.TankThingy


game:GetService("RunService").RenderStepped:Connect(function()
	workspace:WaitForChild("Part2").Position = mouse.Hit.Position
	mouse.TargetFilter = workspace:WaitForChild("Part2")
        -- HORIZONTAL ANGLE CALCULATIONS
	local baseAttachment1 = tank.Part1.TurretAttachment0
	local object_horizontal_offset = (baseAttachment1.WorldCFrame):PointToObjectSpace(mouse.Hit.Position)
	local yaw = math.atan2(object_horizontal_offset.Y, -object_horizontal_offset.Z)

        -- VERTICAL ANGLE CALCULATIONS AKA MAIN PROBLEM
	local turretOrigin = tank.ElevationAxis

	local x = math.abs(mouse.Hit.Position.X - turretOrigin.Position.X)
	local y = math.abs(mouse.Hit.Position.Y - turretOrigin.Position.Y)
	local z = math.abs(mouse.Hit.Position.Z - turretOrigin.Position.Z)
	local pitch = math.atan2(y, x)
	if math.deg(math.atan2(y, z)) < math.deg(math.atan2(y, x)) then
		pitch = math.atan2(y, z)
	end
	if mouse.Hit.Position.Y <= turretOrigin.Position.Y then
		pitch = -pitch
	end
	--print(math.deg(math.atan2(y, z)), math.deg(math.atan2(y, x)))
	event:FireServer(math.deg(pitch), math.deg(yaw))
	
end)

And theres server script inside the tank:

local event = game.ReplicatedStorage.TankThingy
local pitchAxis = script.Parent.Parent.TurretAxis.ElevationHinge
local yawAxis = script.Parent.TurretHinge
local p = workspace:WaitForChild("Part3")
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = script.Parent.Parent.Parent:GetDescendants()

event.OnServerEvent:Connect(function(Player, Pitch, Yaw)
	pitchAxis.TargetAngle = Pitch
	yawAxis.TargetAngle = Yaw
	
	local result = workspace:Raycast(script.Parent.Parent.ElevationAxis.Position, script.Parent.Parent.ElevationAxis.CFrame.LookVector * 1000, params)
	if result then
		if result.Position then
			p.Position = result.Position
		end
		
	end
end)

Note
Server script has raycast part of code which is responsible for current aim position so it’s not affiliated with problem, its mostly in the local script.

Thank you in advance and sorry for any grammatical errors, English isn’t my mother tongue.

If you use the horizontal component of the vector between turretOrigin and mouse.Hit.Position, you should use the whole horizontal component which is a combination of the x and z components. The length of the horizontal component of a vector v is math.sqrt(v.X^2 + v.Z^2). One way you could calculate the vertical angle is the following:

local vectorToTarget = mouse.Hit.Position - turretOrigin.Position
local pitch = math.atan2(vectorToTarget.Y, math.sqrt(vectorToTarget.X^2 + vectorToTarget.Z^2))

However, there’s also an easier way. The sine of the pitch angle is the y component of the vector divided by the length of the vector. So any of the following will work:

  1. using the non-unit vector
local vectorToTarget = mouse.Hit.Position - turretOrigin.Position
local pitch = math.asin(vectorToTarget.Y / vectorToTarget.Magnitude)
  1. using the unit vector (in this case the length is one so no need for writing the division)
local directionToTarget = (mouse.Hit.Position - turretOrigin.Position).Unit
local pitch = math.asin(directionToTarget.Y)

Also, there’s no need to convert the angles to degrees for checking which one is greater. The angle in radians is directly proportional to the angle in degrees so the inequality in your if statement will give the same result regardless of whether you use radians or degrees. math.deg(angleInRadians) is the same as angleInRadians * 180 / math.pi and math.rad(angleInDegrees) is the same as angleInDegrees * math.pi / 180.

1 Like

Thank you, you are right, I might have overlooked that but theres still one question.

How can I include turret already existing vertical angle when the tank is for example on a slope?
Example:

1 Like

I thought I could get angle of MainPart relative to world and add that value to pitch but Im not sure that is correct solution.

1 Like

I’m not sure if adding the angle relative to world would work but you could use PointToObjectSpace in calculating pitch as well. You are already using it in calculating the horizontal angle. You’ll need to use the WorldCFrame of an attachment or part whose position is the point around which pitch rotation happens. When you have the offset vector of the target in local space, you can calculate pitch the same way regardless of the orientation of the tank.

Assuming that you have an attachment at the point around which pitch rotation happens and the UpVector of the WorldCFrame of this attachment is the up direction of the tank, you can calculate pitch in the following way:

-- directionToTarget is a unit vector.
local directionToTarget = attachmentAtPitchRotationPoint.WorldCFrame:PointToObjectSpace(mouse.Hit.Position).Unit
local pitch = math.asin(directionToTarget.Y)
1 Like

Oh my days, thank you so much.
I spent like 20 mins trying to understand why code you suggested didn’t work for me but then I realised I was referring to horizontal attachment all that time lol.
Thank you once again, that’s one more reason for me to dive deeper into math module.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.