local socket = require("socket")


local maf = require 'maf'
local vec3 = maf.vec3
local quat = maf.quat

local samem = require 'SAMemory'

samem.require 'CVehicle'
samem.require 'tHandlingData'
samem.require 'CPlane'
samem.require 'CTransmission'

-- Constants for controlling plane steering behavior
local PITCH_UP_THRESHOLD = -15          -- Pitch angle threshold in degrees
local FINE_STEERING_ANGLE = 135           -- Angle for fine steering adjustments in degrees
local YAW_FACTOR = 0.1                    -- Multiplicative factor for yaw adjustment
local ROLL_FACTOR = 1                -- Multiplicative factor for roll adjustment
local STEERING_SPEED = 5.0             -- Speed of steering control in units per second

-- Direction vectors
local DIRECTION = {
    FORWARD = vec3(0, 1, 0),
    BACKWARD = vec3(0, -1, 0),
    RIGHT = vec3(1, 0, 0),
    LEFT = vec3(-1, 0, 0),
    UP = vec3(0, 0, 1),
    DOWN = vec3(0, 0, -1)
}

-- Calculate the signed angle between two vectors with respect to a reference axis
local function calculateSignedAngle(from, to, axis)
    local fromNormalized = from:clone():normalize()
    local toNormalized = to:clone():normalize()
    local dotProduct = fromNormalized:dot(toNormalized)
    local angle = math.acos(math.max(-1, math.min(1, dotProduct)))  -- Clamp the dot product
    local crossProduct = fromNormalized:cross(toNormalized)
    local sign = crossProduct:dot(axis) < 0 and -1 or 1
    return angle * sign
end

-- Calculate the angle between two vectors in degrees
local function calculateAngleBetweenVectors(v1, v2)
    local normV1, normV2 = v1:normalize(), v2:normalize()
    local dotProduct = math.max(-1, math.min(1, normV1:dot(normV2)))  -- Clamp the dot product
    return math.deg(math.acos(dotProduct))
end

-- Obtain the conjugate of a quaternion
local function conjugateQuaternion(q)
    return quat(-q.x, -q.y, -q.z, q.w)
end

-- Compute the steering input for the plane to move towards a target position
local function computeSteeringInput(plane, dt, targetPosition)
    local planePosition = vec3(getCarCoordinates(plane))
    local planeRotation = quat(getVehicleQuaternion(plane))

    local positionError = targetPosition - planePosition
    local localError = conjugateQuaternion(planeRotation) * positionError

    local errorDir = localError:normalize()
    local pitchError, rollError, yawError
    pitchError = vec3(0, localError.y, localError.z):normalize()
    rollError = vec3(localError.x, localError.y, 0):normalize()
    yawError = vec3(localError.x, 0, localError.z):normalize()

    local targetInput = vec3()
    local pitch = calculateSignedAngle(DIRECTION.FORWARD, pitchError, DIRECTION.RIGHT)

    if -pitch < PITCH_UP_THRESHOLD then
        pitch = (pitch + 360) % 360
    end
    targetInput.x = pitch

	local angleError = calculateAngleBetweenVectors(DIRECTION.FORWARD, rollError)

	if angleError < FINE_STEERING_ANGLE then
		local yaw = -calculateSignedAngle(DIRECTION.FORWARD, yawError, DIRECTION.UP)
		targetInput.y = yaw * YAW_FACTOR
	else
		local roll = calculateSignedAngle(DIRECTION.UP, rollError, DIRECTION.FORWARD)
		targetInput.z = roll * ROLL_FACTOR
	end
	
	if math.abs(targetInput.y) < 0.025 then targetInput.y = 0 end


    targetInput.x = math.max(-1, math.min(1, targetInput.x))
    targetInput.y = math.max(-1, math.min(1, targetInput.y))
    targetInput.z = math.max(-1, math.min(1, targetInput.z))

    local input = vec3()
    input.x = math.max(-1, math.min(1, input.x + (targetInput.x - input.x) * STEERING_SPEED * dt))
    input.y = math.max(-1, math.min(1, input.y + (targetInput.y - input.y) * STEERING_SPEED * dt))
    input.z = math.max(-1, math.min(1, input.z + (targetInput.z - input.z) * STEERING_SPEED * dt))

    return input
end

function main()
    local lastTime = socket.gettime() -- Initial time setup

    while true do
        wait(1)  -- Wait time before loop reiteration

        local currentTime = socket.gettime()
        local dt = currentTime - lastTime
        lastTime = currentTime

        local plane = storeCarCharIsInNoSave(playerPed)
        local planePosition = vec3(getCarCoordinates(plane))
        local camPosition = vec3(getActiveCameraCoordinates())
        local camDirection = (vec3(getActiveCameraPointAt()) - camPosition):normalize() * 10
        local targetPosition = planePosition + camDirection
		
		local wposX, wposY = convert3DCoordsToScreen(targetPosition:unpack())
		--dxDrawRing(posX, posY, radius, width, startAngle, amount, color, absoluteAmount, anglesPerLine)
		dxDrawRing(wposX, wposY, 15, 0.75, 0, 1, 285212671, false, 1 )

        local pitchInput, yawInput, rollInput = computeSteeringInput(plane, dt * 10, targetPosition):unpack()

        if doesVehicleExist(plane) then
            local carPointer = getCarPointer(plane)
            local vehicle = samem.cast("CVehicle *", carPointer)

            if vehicle ~= samem.nullptr and vehicle.nVehicleClass == 0 then
                local plane = samem.cast('CPlane *', vehicle)
                plane.field_988 = yawInput * 2^30
                --plane.field_98C = 0 * 2^30
                plane.field_990 = 0.8 * 2^30
                plane.field_9C4 = 1 * 2^30
            --print(yawInput, pitchInput, math.sign(rollInput))
			end
        end
    end
end


function math.sign(x)
    if x > 0 then
        return 1
    elseif x < 0 then
        return -1
    else
        return 0
    end
end



function dxDrawRing(posX, posY, radius, width, startAngle, amount, color, absoluteAmount, anglesPerLine)
    if type(posX) ~= "number" or type(posY) ~= "number" or type(startAngle) ~= "number" or type(amount) ~= "number" then
        print("Error: One of the required parameters (posX, posY, startAngle, amount) is not a number.")
        return false
    end

    radius = type(radius) == "number" and radius or 50
    width = type(width) == "number" and width or 5
    color = color or 0xFFFFFFFF
    absoluteAmount = type(absoluteAmount) == "boolean" and absoluteAmount or false
    anglesPerLine = type(anglesPerLine) == "number" and anglesPerLine or 1

    local stopAngle = absoluteAmount and (amount + startAngle) or ((amount * 360) + startAngle)

    for angle = startAngle, stopAngle, anglesPerLine do
        local radAngle = math.rad(angle)
        local startX = math.cos(radAngle) * (radius - width)
        local startY = math.sin(radAngle) * (radius - width)
        local endX = math.cos(radAngle) * (radius + width)
        local endY = math.sin(radAngle) * (radius + width)

        renderDrawLine(startX + posX, startY + posY, endX + posX, endY + posY, width, color)
    end

    return math.floor((stopAngle - startAngle) / anglesPerLine)
end


когда самолет близок к таргету - он сильно дергается, пытаясь попасть в таргет, потому что слишком сильно поворачивает. когда самолет слишком далеко от таргета - он поворачивает слишком медленно.
как называется эта проблема? что я могу погуглить на английском для ее реешения