Gyro-stabilisation system for turret

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:

  1. At the base, which turns the entire turret clockwise/anti-clockwise.
  2. 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

Also ignore the mess with the maths and variables, I’ve had to make a lot of revisions and haven’t had a chance to clean it up yet properly.

You can lerp the welds instead of setting them frame by frame.

Would lerp not require a physics step function anyway? Or you mean do the physics calculation on a slower loop and lerp the frames between?

If you recalculate the lerp every frame or whatever it won’t be jerky like ‘stepping’ the CFrame instantly each frame, especially when you have big differences to move.
Actually slowing it down might give a slight lag which could be more realistic.

I’m already slowing it down through my acceleration function. It’s essentially lerping the CFrame anyway by applying its own smoothing function to the angle, and this specific rotation only occurs on a single axis.

In practice I’m not sure how I’d even go about adding the lerp. x.C1 = x.C1:lerp(targetCFrame, alpha), but what alpha would I use and how would that differ from what the code is already doing?

Ah, sorry, I didn’t really understand the smoothing function code you had so I thought you were just changing the CFrame in steps.

No worries, I probably could’ve put a bit more effort into cleaning it up before posting it here.

Are you doing the client clone technique?

Seperate server from the client and it will be smoother.