local socket		= require "socket"
local maf			= require "maf"
local vec3, quat	= maf.vec3, 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	= 15		-- Angle for fine-steering adjustments in degrees
local YAW_FACTOR			= 1			-- Multiplicative factor for yaw adjustment
local ROLL_FACTOR			= 0.75			-- Multiplicative factor for roll adjustment
local STEERING_SPEED		= 25.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)
}

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

-- Calculate the signed angle between two vectors with respect to a reference axis
local function calculateSignedAngle(from, to, axis)
    local fromNormalized, toNormalized = from:clone():normalize(), 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

-- Smooth step function
local function smoothstep(edge0, edge1, x)
    x = math.max(0, math.min(1, (x - edge0) / (edge1 - edge0)))
    return x * x * (3 - 2 * x)
end

local 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

-- 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 distanceError = positionError:length()
    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)
    local minAngle, maxAngle = -5, FINE_STEERING_ANGLE

    local angleError = calculateAngleBetweenVectors(DIRECTION.FORWARD, rollError)
    local dynamicYawFactor = smoothstep(minAngle, maxAngle, angleError) * YAW_FACTOR

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

    local function clampInput(value)
        return math.max(-1, math.min(1, value))
    end

    targetInput.x, targetInput.y, targetInput.z = clampInput(targetInput.x), clampInput(targetInput.y), clampInput(targetInput.z)

    local input = vec3()
    input.x = clampInput(input.x + (targetInput.x - input.x) * STEERING_SPEED * dt)
    input.y = clampInput(input.y + (targetInput.y - input.y) * STEERING_SPEED * dt)
    input.z = clampInput(input.z + (targetInput.z - input.z) * STEERING_SPEED * dt)

    return targetInput
end

function main()
    local lastTime = socket.gettime()

    while true do
        if isCharInAnyCar(playerPed) then

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

			local plane = storeCarCharIsInNoSave(playerPed)
			local planePosition = vec3(getCarCoordinates(plane))
			local camPosition = vec3(getActiveCameraCoordinates())
			camPosition.z = camPosition.z - 0.1
			local camDirection = (vec3(getActiveCameraPointAt()) - camPosition):normalize() * 10
			local targetPosition = planePosition + camDirection

			local wposX, wposY = convert3DCoordsToScreen(targetPosition:unpack())
			dxDrawRing(wposX, wposY - 65, 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
					plane.field_98C = pitchInput
					plane.field_990 = rollInput
					printStringNow(yawInput, 1)
				end
			end
		end
		
		wait(0)
    end
end


как мне сделать самолет лучше в управлении?
мне кажется, что там не нужна зависимость от времени, мне нужна максимально точная и максимально отзывчивая управляемость по всем осям