I’m trying to write a turret that takes user input to rotate independently on two axis (clockwise/anti-clockwise, up-down), similar to that of a tank. I’m using two welds to direct rotation:
- At the base, which turns the entire turret clockwise/anti-clockwise.
- On the gun(s), which allows the guns to increase/decrease elevation.
It’s working fine for me, however I’ve been trying to implement a form of gyro-stabilisation so that, for example, when no input is applied but the vehicle underneath the tank is rotating, the gun will compensate by providing counter rotation so that from the gun’s perspective it will remain aiming at the same orientation. My issue is that from the camera’s perspective (mounted to the turret), the turret ‘jitters’ very slightly when this system is enabled and it’s aligned. I’m moderating the CFrame server-side, so I assume my issue is some inconsistency between the server’s physics step and whatever interpolation is happening between frames on the client’s renderstep or some floating-point error with my angle calculations.
Does anyone have any suggestions of ways to compensate this effect? I’d prefer not to have to run the entire “physics” calculation on the client or switch to hinges, which is why I’m making an effort to see if anyone has any ideas.
Physics-step function for horizontal rotation:
local inputs = turrets[turret].inputs;
local stats = turrets[turret].stats;
local pivotCFrame = pivot.CFrame; -- Fixed part (relative to vehicle) at the base of the turret
local relativeCFrame = lastPivotCFrame:inverse() *pivotCFrame;
local forward = relativeCFrame.LookVector;
local up = relativeCFrame.UpVector;
local horizontalAngle = math.deg(math.atan2(forward.X, forward.Z));
local newXZ, newVelXZ, XZCFrame = funcs['processXZ'](inputs, stats, settings, dt, horizontalAngle, pidXZ);
turrets[turret].welds.horizontal.C1 = XZCFrame;
turrets[turret].stats['xzAng'] = newXZ;
turrets[turret].stats['xzVel'] = newVelXZ;
“processXZ” function:
return function(inputs, stats, settings, dt, stabilizationAngle, pid)
local inputXZ = inputs['xz'];
local currXZ = stats['xzAng'];
local velXZ = stats['xzVel'];
if (math.abs(inputXZ) <= 0.02) then inputXZ = 0; end -- Deadzone
local stabAngle = (stabilizationAngle +180);
local smallestStabAngle = (stabAngle +180) %360 -180;
print(`SmallestAngleXZ to StabTarget: {math.floor(stabilizationAngle*100)/100} degrees`);
local targetAcc = dt *settings.Movement['Max_Angular_Acceleration_XZ'];
local targetVel = inputXZ *settings.Movement['Max_Angular_Velocity_XZ'];
local velDiff = targetVel -velXZ;
local deltaVel = math.clamp(velDiff, -targetAcc, targetAcc);
local nextVelXZ = velXZ +deltaVel;
local nextXZ = (currXZ +nextVelXZ *dt)%360;
local targetAngle = (nextXZ +smallestStabAngle);
return nextXZ, nextVelXZ, CFrame.new() * CFrame.Angles(math.rad(targetAngle), 0, 0);
end