Полезные сниппеты и функции

chromiusj

Известный
Модератор
6,021
4,350
Описание:
Рендер линии в 3D пространстве без артефактов, когда одна из точек линии уходит за камеру.

Принцип работы:
1) Обе точки видны на экране: линия рисуется напрямую через renderDrawLine
2) Ключевой случай: одна точка впереди, другая сзади. Нарисовать такую линию нельзя: проекция точки за камерой даёт «вывернутые» координаты и линию будет косоёбить уводить в разные стороны. Поэтому функция используя бинарный поиск (12 итераций, точность ~1/4096) ищет вдоль отрезка ту точку, которая лежит прямо на границе видимости (z > 0) и использует её вместо настоящей
3) Обе точки за экраном: ничего не рисуем, тут всё логично

Код:
Lua:
local function clipToNearPlane(fx, fy, fz, bx, by, bz)
    local sx, sy
    local left, right = 0.0, 1.0
    for _ = 1, 12 do
        local mid = (left + right) * 0.5
        local _, cx, cy, cz = convert3DCoordsToScreenEx(
            fx + (bx - fx) * mid,
            fy + (by - fy) * mid,
            fz + (bz - fz) * mid
        )
        if cz > 0 then
            left, sx, sy = mid, cx, cy
        else
            right = mid
        end
    end
    return sx, sy
end

function renderDrawLine3D(x1, y1, z1, x2, y2, z2, thickness, color)
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)
    local v1, v2 = sz1 > 0, sz2 > 0

    if not v1 and not v2 then
        return false
    end

    if not v2 then
        sx2, sy2 = clipToNearPlane(x1, y1, z1, x2, y2, z2)
    elseif not v1 then
        sx1, sy1 = clipToNearPlane(x2, y2, z2, x1, y1, z1)
    end

    if not sx1 or not sx2 then
        return false
    end

    renderDrawLine(sx1, sy1, sx2, sy2, thickness, color)
    return true, sx1, sy1, sx2, sy2
end

Пример:
Lua:
function onD3DPresent()
    local x1, y1, z1 = 1806.59, -2547.78, 14
    local x2, y2, z2 = 1806.59, -2567.78, 14

    -- Улучшенный рендер
    renderDrawLine3D(x1, y1, z1, x2, y2, z2, 5, 0xFF00FF00)

    x1 = x1 + 1
    x2 = x2 + 1

    -- Обычный рендер
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)
    if sz1 > 0 and sz2 > 0 then
        renderDrawLine(sx1, sy1, sx2, sy2, 5, 0xFFFF0000)
    end
end

Демонстрация:
Красная линия - обычный рендер
Зелёная линия - улучшенный рендер

Можно заметить, как зелёная линия стабильно отрисовывается под разными углами камеры, в то время как красная нет

Lua:
local NEAR_PLANE_EPSILON = 1e-3
local function clipToNearPlane(fx, fy, fz, wf, bx, by, bz, wb)
    local dw = wf - wb
    if dw == 0 then return nil, nil end
    local t = math.max(0.0, math.min(1.0, (wf - NEAR_PLANE_EPSILON) / dw))
    local _, sx, sy = convert3DCoordsToScreenEx(
        fx + (bx - fx) * t,
        fy + (by - fy) * t,
        fz + (bz - fz) * t
    )
    return sx, sy
end

local function renderDrawLine3D(x1, y1, z1, x2, y2, z2, thickness, color)
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)

    if sz1 <= 0 and sz2 <= 0 then return false end

    if sz2 <= 0 then
        sx2, sy2 = clipToNearPlane(x1, y1, z1, sz1, x2, y2, z2, sz2)
    elseif sz1 <= 0 then
        sx1, sy1 = clipToNearPlane(x2, y2, z2, sz2, x1, y1, z1, sz1)
    end

    if not sx1 or not sx2 then return false end

    renderDrawLine(sx1, sy1, sx2, sy2, thickness, color)
    return true, sx1, sy1, sx2, sy2
end
 

kyrtion

Известный
1,425
555
Lua:
local NEAR_PLANE_EPSILON = 1e-3
local function clipToNearPlane(fx, fy, fz, wf, bx, by, bz, wb)
    local dw = wf - wb
    if dw == 0 then return nil, nil end
    local t = math.max(0.0, math.min(1.0, (wf - NEAR_PLANE_EPSILON) / dw))
    local _, sx, sy = convert3DCoordsToScreenEx(
        fx + (bx - fx) * t,
        fy + (by - fy) * t,
        fz + (bz - fz) * t
    )
    return sx, sy
end

local function renderDrawLine3D(x1, y1, z1, x2, y2, z2, thickness, color)
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)

    if sz1 <= 0 and sz2 <= 0 then return false end

    if sz2 <= 0 then
        sx2, sy2 = clipToNearPlane(x1, y1, z1, sz1, x2, y2, z2, sz2)
    elseif sz1 <= 0 then
        sx1, sy1 = clipToNearPlane(x2, y2, z2, sz2, x1, y1, z1, sz1)
    end

    if not sx1 or not sx2 then return false end

    renderDrawLine(sx1, sy1, sx2, sy2, thickness, color)
    return true, sx1, sy1, sx2, sy2
end
Предлагаю кэшировать функции ради +производительность
(или я переборщил)

Lua:
local math_min, math_max = math.min, math.max
local convert3DCoordsToScreenEx = convert3DCoordsToScreenEx
local renderDrawLine = renderDrawLine
local NEAR_PLANE_EPSILON = 1e-3

local function clipToNearPlane(fx, fy, fz, wf, bx, by, bz, wb)
    local dw = wf - wb
    if dw == 0 then return nil, nil end
    local t = math_max(0.0, math_min(1.0, (wf - NEAR_PLANE_EPSILON) / dw))
    local _, sx, sy = convert3DCoordsToScreenEx(
        fx + (bx - fx) * t,
        fy + (by - fy) * t,
        fz + (bz - fz) * t
    )
    return sx, sy
end

local function renderDrawLine3D(x1, y1, z1, x2, y2, z2, thickness, color)
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)

    if sz1 <= 0 and sz2 <= 0 then return false end

    if sz2 <= 0 then
        sx2, sy2 = clipToNearPlane(x1, y1, z1, sz1, x2, y2, z2, sz2)
    elseif sz1 <= 0 then
        sx1, sy1 = clipToNearPlane(x2, y2, z2, sz2, x1, y1, z1, sz1)
    end

    if not sx1 or not sx2 then return false end

    renderDrawLine(sx1, sy1, sx2, sy2, thickness, color)
    return true, sx1, sy1, sx2, sy2
end
 

vegas

Известный
Проверенный
563
529
Описание: Позволяет создавать несколько одинаковых хуков для samp/arizona events
Lua:
local handler = (function()
    local this = {
        list = {}
    }
    function this:update_require(lib, hook)
        require(lib)[hook] = function(...)
            local arguments = {...}
            local index = 0
            for _, execute in pairs(self.list[lib][hook].handlers) do
                index = index + 1
                local return_arguments = execute(table.unpack(arguments))
                if return_arguments == false then
                    return false
                end
                if return_arguments ~= nil and return_arguments ~= true then
                    arguments = return_arguments
                end
                if index == self.list[lib][hook].count then
                    return arguments
                end
            end
        end
    end
    function this:new(lib, hook, execute)
        if not self.list[lib] then
            self.list[lib] = {}
        end
    
        if not self.list[lib][hook] then
            self.list[lib][hook] = {handlers = {}, last_id = 0, count = 0}
        end
        self.list[lib][hook].count = self.list[lib][hook].count + 1
        self.list[lib][hook].last_id = self.list[lib][hook].last_id + 1
        self.list[lib][hook].handlers[self.list[lib][hook].last_id] = execute
        self:update_require(lib, hook)
        return (function()
            local this_ = {
                index = self.list[lib][hook].last_id,
            }
            function this_:remove()
                this.list[lib][hook].count = this.list[lib][hook].count - 1
                this.list[lib][hook].handlers[self.index] = nil
                this:update_require(lib, hook)
            end
            return this_
        end)()
    end
    
    return this
end)()
Пример: Первые 2 хука увидят событие и изменят значение, 3 хук увидит, ничего не изменив но удалит 4 хук который не должен был отправить клиенту событие,, 5 хук создается внутри 3 и возвращает значение с его изменением
Lua:
handler1 = handler:new('samp.events', 'onGivePlayerMoney', function(money)
    print('handler1')
    return {money + 1}
end)
handler2 = handler:new('samp.events', 'onGivePlayerMoney', function(money)
    print('handler2')
    return {money + 1}
end)
handler3 = handler:new('samp.events', 'onGivePlayerMoney', function(money)
    print('handler3')
    handler4:remove()
    handler5 = handler:new('samp.events', 'onGivePlayerMoney', function(money)
        print('handler5')
        return {money + 200}
    end)
end)
handler4 = handler:new('samp.events', 'onGivePlayerMoney', function(money)
    print('handler4')
    return false
end)
 
Последнее редактирование:

pathtohell

Участник
20
97
Описание: Обёртка над imgui.Begin, которая плавно меняет прозрачность окна в зависимости от расстояния курсора. При наведении — полная непрозрачность, при удалении — плавное затухание по smoothstep-кривой. Все параметры опциональны.

demo.gif


Код:
Lua:
local states = {}

--- @param name string
--- @param open userdata|nil imgui.new.bool pointer, nil = no close button
--- @param flags number|nil
--- @param minAlpha number|nil default 0.25
--- @param maxAlpha number|nil default 1.0
--- @param fadeDist number|nil default 200.0
--- @param fadeIn number|nil default 14.0
--- @param fadeOut number|nil default 6.0
--- @return boolean visible, boolean show
function imgui.BeginAutoOpacity(name, open, flags, minAlpha, maxAlpha, fadeDist, fadeIn, fadeOut)
    minAlpha = minAlpha or 0.25
    maxAlpha = maxAlpha or 1.0
    fadeDist = fadeDist or 200.0
    fadeIn = fadeIn or 14.0
    fadeOut = fadeOut or 6.0

    local s = states[name] or { alpha = maxAlpha }
    states[name] = s

    local style = imgui.GetStyle()
    local prevAlpha = style.Alpha
    style.Alpha = s.alpha

    local visible, show = imgui.Begin(name, open, flags)

    if not visible then
        style.Alpha = prevAlpha
        imgui.End()
        if open and not open[0] then states[name] = nil end
        return false, show
    end

    local io = imgui.GetIO()
    local wp = imgui.GetWindowPos()
    local ws = imgui.GetWindowSize()
    local mx = io.MousePos.x
    local my = io.MousePos.y
    local dx = math.max(wp.x - mx, 0, mx - (wp.x + ws.x))
    local dy = math.max(wp.y - my, 0, my - (wp.y + ws.y))
    local dist = math.sqrt(dx * dx + dy * dy)

    local target = maxAlpha
    if not imgui.IsWindowHovered() then
        local t = math.min(dist / fadeDist, 1.0)
        target = maxAlpha - (maxAlpha - minAlpha) * (t * t * (3 - 2 * t))
    end

    local speed = (target > s.alpha) and fadeIn or fadeOut
    local delta = math.min(io.DeltaTime * speed, 1.0)
    s.alpha = math.max(minAlpha, math.min(maxAlpha, s.alpha + (target - s.alpha) * delta))

    return true, show
end

function imgui.EndAutoOpacity()
    imgui.GetStyle().Alpha = 1.0
    imgui.End()
end

Пример использования:
Lua:
local mainWindowOpen = imgui.new.bool(true)

imgui.OnFrame(function() return mainWindowOpen[0] end, function()
    if imgui.BeginAutoOpacity("Main Window", mainWindowOpen) then
        imgui.Text("Hello!")
    end
    imgui.EndAutoOpacity()
end)
 

Niourozi

Участник
20
26
Description: Detects the top speed of the current vehicle automatically by sampling velocity over time. Resets automatically when switching vehicles. Usage: local top = getVehicleTopSpeed(veh) — call every tick. Returns top speed in km/h or nil while calibrating.

Code:
code:
local memory = require 'memory'

local _s = { samples={}, sampleClock=0, detectedTop=nil, lastVeh=nil }

local function getVehicleTopSpeed(veh)
    if veh ~= _s.lastVeh then
        _s.samples     = {}
        _s.sampleClock = 0
        _s.detectedTop = nil
        _s.lastVeh     = veh
    end
    local ptr = getCarPointer(veh)
    if not ptr or ptr == 0 then return nil end
    local now = os.clock()
    if now - _s.sampleClock < 0.05 then return _s.detectedTop end
    _s.sampleClock = now
    local vx = memory.getfloat(ptr + 0x44)
    local vy = memory.getfloat(ptr + 0x48)
    local vz = memory.getfloat(ptr + 0x4C)
    local speed = math.sqrt(vx*vx + vy*vy + vz*vz) * 180
    table.insert(_s.samples, speed)
    if #_s.samples > 8 then table.remove(_s.samples, 1) end
    if #_s.samples < 5 then return nil end
    local maxV, minV = 0, math.huge
    for _, v in ipairs(_s.samples) do
        if v > maxV then maxV = v end
        if v < minV then minV = v end
    end
    if (maxV - minV) <= 1 and maxV > 30 then
        _s.detectedTop = maxV
    end
    return _s.detectedTop
end

Example:
example:
local memory = require 'memory'

local _s = { samples={}, sampleClock=0, detectedTop=nil, lastVeh=nil }

local function getVehicleTopSpeed(veh)
    if veh ~= _s.lastVeh then
        _s.samples     = {}
        _s.sampleClock = 0
        _s.detectedTop = nil
        _s.lastVeh     = veh
    end
    local ptr = getCarPointer(veh)
    if not ptr or ptr == 0 then return nil end
    local now = os.clock()
    if now - _s.sampleClock < 0.05 then return _s.detectedTop end
    _s.sampleClock = now
    local vx = memory.getfloat(ptr + 0x44)
    local vy = memory.getfloat(ptr + 0x48)
    local vz = memory.getfloat(ptr + 0x4C)
    local speed = math.sqrt(vx*vx + vy*vy + vz*vz) * 180
    table.insert(_s.samples, speed)
    if #_s.samples > 8 then table.remove(_s.samples, 1) end
    if #_s.samples < 5 then return nil end
    local maxV, minV = 0, math.huge
    for _, v in ipairs(_s.samples) do
        if v > maxV then maxV = v end
        if v < minV then minV = v end
    end
    if (maxV - minV) <= 1 and maxV > 30 then
        _s.detectedTop = maxV
    end
    return _s.detectedTop
end

function main()
    repeat wait(500) until isSampAvailable()
    local lastReported = nil
    while true do
        wait(25)
        if isCharInAnyCar(PLAYER_PED) then
            local veh = storeCarCharIsInNoSave(PLAYER_PED)
            local top = getVehicleTopSpeed(veh)
            if top and top ~= lastReported then
                lastReported = top
                sampAddChatMessage('Top speed: ' .. math.floor(top) .. ' km/h', -1)
            end
        else
            lastReported = nil
        end
    end
end
 
  • Эм
Реакции: Cosmo и Corenale

Baklaxa

Новичок
14
4
Получает проценты заряда батареи

LUA:
local ffi   = require "ffi"
local kernel = ffi.load "kernel32"

ffi.cdef [[
typedef struct {
    unsigned char ACLineStatus;
    unsigned char BatteryFlag;
    unsigned char BatteryLifePercent;
    unsigned char SystemStatusFlag;
    unsigned long  BatteryLifeTime;
    unsigned long  BatteryFullLifeTime;
} SYSTEM_POWER_STATUS;
int GetSystemPowerStatus(SYSTEM_POWER_STATUS* lpSystemPowerStatus);
]]


local function batareya()
    local s = ffi.new "SYSTEM_POWER_STATUS"
    if kernel.GetSystemPowerStatus(s) ~= 0 and s.BatteryLifePercent ~= 255 then
        return s.BatteryLifePercent
    end
    return nil
end

function main()
    while not isSampAvailable() do wait(100) end

    sampRegisterChatCommand("btr", function()
        local p = batareya()
        sampAddChatMessage(p and tostring(p) or "none", -1)
    end)

    while true do wait(0) end
end