How to maintain consistent X-axis/Z-axis tilt when rotating a model 180 degrees on the Y-axis in Roblox?

I didn’t know how better to write the title, but I have one single task in mind, and that is creating a simple helicopter for a commission. In terms of physics, I have a base part with attachments that hold everything else together using physics constraints, and a physics part under the base part that a linear velocity will be asserted upon (Due to the base part doing things like rotating down when moving forward, leading to a forward velocity causing the helicopter to go down). I’ve gotten the movement of the physics part to stay constant in the X and Z rotational axis, but to retain the helicopter’s Y rotation (so that if the helicopter looks left or right due to the camera, the linear velocity will also go in that direction).
The issue arises with how the model calculates rotation. What I mean is that when I rotate the helicopter 15 degrees on the X axis to make it look up, if I then shift it 180 degrees on the Y axis, it’ll look down (it shouldn’t do that). Here’s video:


Here’s code for the movement handler:

local Helicoptor = script.Parent
local Base = script.Parent.Base
local LinVelocity = Base.PhysicsPart.LinearVelocity
local DirectionValues = Helicoptor.DirectionValues
local RightOrientation = Helicoptor.Base.RightOrientation
local ApproachingForward, ApproachingSide, ApproachingUp = false, false, false
local MaxForwardTilt, MaxSideTilt, MaxUpTilt = 15, 15, 15
local TweenService = game:GetService("TweenService")

while task.wait() do
	local OriginalForwardValue = DirectionValues.Forward.Value
	local OriginalSideValue = DirectionValues.Side.Value
	local OriginalUpValue = DirectionValues.Up.Value
	if LinVelocity.VectorVelocity.Z ~= OriginalForwardValue and not ApproachingForward then
		ApproachingForward = true
		task.spawn(function()
			local WantedOrientation = 0
			local ForwardValue = 0
			if DirectionValues.Forward.Value ~= 0 then
				ForwardValue = DirectionValues.Forward.Value/math.abs(DirectionValues.Forward.Value)
				WantedOrientation = MaxForwardTilt * ForwardValue
			end
			for i = 0, 1, 0.01 do
				if Helicoptor.DirectionValues.Forward.Value == OriginalForwardValue then
					task.wait()
					if LinVelocity.VectorVelocity.Y == 0 then
						RightOrientation.Orientation = RightOrientation.Orientation:Lerp(Vector3.new(-WantedOrientation, RightOrientation.Orientation.Y, RightOrientation.Orientation.Z), i)
					end
					LinVelocity.VectorVelocity = LinVelocity.VectorVelocity:Lerp(Vector3.new(LinVelocity.VectorVelocity.X, LinVelocity.VectorVelocity.Y, Helicoptor.DirectionValues.Forward.Value), i)
				else
					ApproachingForward = false
				end
			end
			ApproachingForward = false
		end)
	end
	if LinVelocity.VectorVelocity.X ~= OriginalSideValue and not ApproachingSide then
		ApproachingSide = true
		task.spawn(function()
			local WantedOrientation = 0
			if DirectionValues.Side.Value ~= 0 then
				WantedOrientation = MaxSideTilt * DirectionValues.Side.Value/math.abs(DirectionValues.Side.Value)
			end
			for i = 0, 1, 0.01 do
				if Helicoptor.DirectionValues.Side.Value == OriginalSideValue then
					task.wait()
					RightOrientation.Orientation = RightOrientation.Orientation:Lerp(Vector3.new(RightOrientation.Orientation.X, RightOrientation.Orientation.Y, -WantedOrientation), i)
					LinVelocity.VectorVelocity = LinVelocity.VectorVelocity:Lerp(Vector3.new(-Helicoptor.DirectionValues.Side.Value, LinVelocity.VectorVelocity.Y, LinVelocity.VectorVelocity.Z), i)
				else
					ApproachingSide = false
				end
			end
			ApproachingSide = false
		end)
	end
	if LinVelocity.VectorVelocity.Y ~= OriginalUpValue and not ApproachingUp then
		ApproachingUp = true
		task.spawn(function()
			local WantedOrientation = 0
			if DirectionValues.Up.Value ~= 0 then
				WantedOrientation = MaxUpTilt * DirectionValues.Up.Value/math.abs(DirectionValues.Up.Value)
			end
			for i = 0, 1, 0.01 do
				if Helicoptor.DirectionValues.Up.Value == OriginalUpValue then
					task.wait(0.01)
					RightOrientation.Orientation = RightOrientation.Orientation:Lerp(Vector3.new(-WantedOrientation, RightOrientation.Orientation.Y, RightOrientation.Orientation.Z), i)
					LinVelocity.VectorVelocity = LinVelocity.VectorVelocity:Lerp(Vector3.new(LinVelocity.VectorVelocity.X, Helicoptor.DirectionValues.Up.Value, LinVelocity.VectorVelocity.Z), i)
				else
					ApproachingUp = false
				end
			end
			ApproachingUp = false
		end)
	end
	RightOrientation.Orientation = Vector3.new(RightOrientation.Orientation.X, 180 - Helicoptor.DirectionValues.CameraY.Value, RightOrientation.Orientation.Z)
end --]]```

What you’re currently doing is rotating the object relative to its Object Space. What you want to do is rotate it relative to the World Space.

You can achieve this by getting the rotation component of the CFrame and multiplying your rotation vector with it:

local Helicopter = --> the helicopter
local helicopterCf = Helicopter.CFrame
local tilt = CFrame.fromOrientation(0, math.rad(180), 0) --> rotating on the global y-axis 180 degrees
local relativeRotation = helicopterCf.Rotation:ToObjectSpace(tilt)
local appliedRotation = relativeRotation:ToWorldSpace(helicopterCf.Rotation)

Helicopter.CFrame *= appliedRotation

Just saw this. Test ran it and it does work. Haven’t applied this to my main script, but I’ll still mark this as the solution as it does function properly.