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

chromiusj

Известный
Модератор
6,018
4,343
Описание:
Рендер линии в 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,424
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
527
Описание: Позволяет создавать несколько одинаковых хуков для 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

Участник
18
89
Описание: Обёртка над 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
20
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

Tectrex

Известный
164
211
Предлагаю кэшировать функции ради +производительность
(или я переборщил)

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

На самом деле многие люди насмотревшись на ИИ кодинг, считают это идеалом кодинга, локализовать функции для производительности, но мем в том, что ИИ всё это советует потому что обучался на статьях 2010-2014 годов, где люди меряли local sin = math.sin в пустом цикле на стандартном Lua 5.1 и получали +30%. Это потом размножилось по всем гайдам, потом попало в обучающую выборку, и теперь каждый ИИ честно верит что local math_max = math.max это идеал. А на LuaJIT, который используется в гмоде, в мунлодер и ваще везде где применяется сама компиляция just in time тобишь на лету, это в лучшем случае экономия 1 наносекунды, в худшем порождает визуальный мусор в коде, и клеймо нейрослопа