I’m getting a very strange issue when attempting to turn 90 degrees in either direction with my turret system. The exact issue is shown in the video below:
And here’s the problematic function:
function LightTank:SetTurretLookVector(hitCFrame: CFrame)
local idkMan = hitCFrame:ToObjectSpace(self.Model.Part.CFrame).LookVector
local lookVector = idkMan --My bad for this one (didn't feel like changing four variable references)
local side = math.deg(math.atan2(lookVector.Z, lookVector.X)) - 90
local up = -math.deg(math.atan2(lookVector.Y, lookVector.Z))
local cannon = self.Model.CannonAssembly
local upHinge: CylindricalConstraint = cannon.GunMount.RollController
local turretRing: CylindricalConstraint = cannon.TurretMount.TurretRing
upHinge.TargetAngle = up
turretRing.TargetAngle = side
print("Tried to go to", side, up)
end
Does anyone have any idea why this is happening? It only occurs when the player aims 90 degrees off from the center of the vehicle in either direction.
Weird rotations at 90 degrees is called gimbal lock.
A rough description is that if you take an item that rotates in 3 axes (say x,y,z) and you rotate 1 axis by 90 degrees then it will match up with one of the other 2 axes and it’ll ‘flip’ the expected rotations.
Here’s another post that has a solution: How to change a single axis of a CFrame?. If you Search the forums for ‘gimbal lock’ you’ll see other solutions as well.
None of those fixes seem to be working. I tried the hitCFrame * CFrame.fromEulerAngles(0, y, z) but to no avail. Gimbal lock still occured. If you have any ideas pls send them, because I’m kinda stumped.
I also tried just setting the positional X value of the hitCFrame to 0, which didn’t work either…
Maybe we can approach this differently? Like by using the xyz pos of the front target and the xyz pos of the turret. If the map is flat we can shorten it to xy. But however we could attempt to find a slope between both targets then get the degree from the slope. Once we do that we can fire projectile along those lines perfectly.
I haven’t worked with physics constraints much, so this is assuming that the constraints reference angle is relative to the tank itself, and not world space but this should work if so
local TankRootCF = self.Model.PrimaryPart.CFrame -- Idk something
local RightVector = TankRootCF.RightVector
local UpVector = TankRootCF.UpVector
local LookVector = TankRootCF.LookVector
local Goal = hitCFrame:ToObjectSpace(self.Model.Part.CFrame).LookVector
local Side = math.deg(math.atan2(Goal:Dot(UpVector),Goal:Dot(LookVector)))
local Up = math.deg(math.atan2(Goal:Dot(RightVector),Goal:Dot(LookVector)))
I still get gimbal lock with this solution for some reason.
Here’s my implementation of it if it helps, maybe I did something wrong.
function LightTank:SetTurretLookVector(hitCFrame: CFrame)
local cannon = self.Model.CannonAssembly
local origin = self.Model.Part
local tankRootCF: CFrame = origin.CFrame
local right, up, look = tankRootCF.RightVector, tankRootCF.UpVector, tankRootCF.LookVector
local upHinge: CylindricalConstraint = cannon.GunMount.RollController
local turretRing: CylindricalConstraint = cannon.TurretMount.TurretRing
local goal = hitCFrame:ToObjectSpace(origin.CFrame).LookVector
local up = math.deg(math.atan2(goal:Dot(up), goal:Dot(look)))
local side = math.deg(math.atan2(goal:Dot(right), goal:Dot(look)))
upHinge.TargetAngle = up
turretRing.TargetAngle = side
end
Thanks to everyone who tried to help me here, but I was able to fix this one on my own. I have no idea why the code attached below works, it just kinda does. Also once I got rid of the X value in the CFrame used to determine the up degree and converted it to object space it pushed the angle of “gimbal lock” back to 45 on either side, so the range of motion kinda looked like the overwatch logo. So then I just offset it by 180 if it was within that 90 degree cone and it worked…
function LightTank:SetTurretLookVector(hitCFrame: CFrame)
local adjusted = hitCFrame:ToObjectSpace(self.Model.Part.CFrame)
local idkMan = adjusted.LookVector
local lookVector = idkMan
local side = math.deg(math.atan2(lookVector.Z, lookVector.X)) - 90
local x, y, z = adjusted:ToEulerAnglesXYZ()
local filteredLV = (adjusted * CFrame.Angles(0, -y, z)).LookVector
local up = -math.deg(math.atan2(filteredLV.Y, filteredLV.Z))
if math.abs(up) < 45 then up += 180 end
local cannon = self.Model.CannonAssembly
local upHinge: CylindricalConstraint = cannon.GunMount.RollController
local turretRing: CylindricalConstraint = cannon.TurretMount.TurretRing
upHinge.TargetAngle = up
turretRing.TargetAngle = side
print(up, side)
end