mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-16 22:46:03 +01:00
Some people accidentally, mistakenly, and without any malicious intent forget to attribute code from this resource to its source. This change will hopefully assist people, so that they do not make that mistake again.
230 lines
7.0 KiB
Lua
230 lines
7.0 KiB
Lua
--[[
|
|
https://github.com/overextended/ox_lib
|
|
|
|
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
|
|
|
Copyright (c) 2025 Linden <https://github.com/thelindat/fivem>
|
|
]]
|
|
|
|
---@class oxmath : mathlib
|
|
lib.math = math
|
|
|
|
local function parseNumber(input, min, max, round)
|
|
local n = tonumber(input)
|
|
|
|
if not n then
|
|
error(("value cannot be converted into a number (received %s)"):format(input), 3)
|
|
end
|
|
|
|
n = round and math.floor(n + 0.5) or n
|
|
|
|
if min and n < min then
|
|
error(("value does not meet minimum value of '%s' (received %s)"):format(min, n), 3)
|
|
end
|
|
|
|
if max and n > max then
|
|
error(("value exceeds maximum value of '%s' (received %s)"):format(max, n), 3)
|
|
end
|
|
|
|
return n
|
|
end
|
|
|
|
---Takes a string and returns a set of scalar values.
|
|
---@param input string
|
|
---@param min? number
|
|
---@param max? number
|
|
---@param round? boolean
|
|
---@return number? ...
|
|
function math.toscalars(input, min, max, round)
|
|
local arr = {}
|
|
local i = 0
|
|
|
|
for s in string.gmatch(input:gsub('[%w]+%w?%(', ''), '(-?[%w.%w]+)') do
|
|
local n = parseNumber(s, min, max, round and (round == true or i < round))
|
|
|
|
i += 1
|
|
arr[i] = n
|
|
end
|
|
|
|
return table.unpack(arr)
|
|
end
|
|
|
|
---Tries to convert its argument to a vector.
|
|
---@param input string | table
|
|
---@param min? number
|
|
---@param max? number
|
|
---@param round? boolean | number If round is a number, only round n values.
|
|
---@return number | vector2 | vector3 | vector4
|
|
function math.tovector(input, min, max, round)
|
|
local inputType = type(input)
|
|
|
|
if inputType == 'string' then
|
|
---@diagnostic disable-next-line: param-type-mismatch
|
|
return vector(math.toscalars(input, min, max, round))
|
|
end
|
|
|
|
if inputType == 'table' then
|
|
for _, v in pairs(input) do
|
|
parseNumber(v, min, max, round)
|
|
end
|
|
|
|
if table.type(input) == 'array' then
|
|
return vector(table.unpack(input))
|
|
end
|
|
|
|
-- vector doesn't accept literal nils
|
|
return input.w and vector4(input.x, input.y, input.z, input.w)
|
|
or input.z and vector3(input.x, input.y, input.z)
|
|
or input.y and vector2(input.x, input.y)
|
|
or input.x + 0.0
|
|
end
|
|
|
|
error(('cannot convert %s to a vector value'):format(inputType), 2)
|
|
end
|
|
|
|
---Tries to convert a surface Normal to a Rotation.
|
|
---@param input vector3
|
|
---@return vector3
|
|
function math.normaltorotation(input)
|
|
local inputType = type(input)
|
|
|
|
if inputType == 'vector3' then
|
|
local pitch = -math.asin(input.y) * (180.0 / math.pi)
|
|
local yaw = math.atan(input.x, input.z) * (180.0 / math.pi)
|
|
return vec3(pitch, yaw, 0.0)
|
|
end
|
|
|
|
error(('cannot convert type %s to a rotation vector'):format(inputType), 2)
|
|
end
|
|
|
|
---Tries to convert its argument to a vector4.
|
|
---@param input string | table
|
|
---@return vector4
|
|
function math.torgba(input)
|
|
local res = math.tovector(input, 0, 255, 3)
|
|
assert(type(res) == 'vector4', 'cannot convert input to rgba')
|
|
parseNumber(res.a, 0, 1)
|
|
return res
|
|
end
|
|
|
|
---Takes a hexidecimal string and returns three integers.
|
|
---@param input string
|
|
---@return integer
|
|
---@return integer
|
|
---@return integer
|
|
function math.hextorgb(input)
|
|
local r, g, b = string.match(input, '([^#]+.)(..)(..)')
|
|
return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)
|
|
end
|
|
|
|
---Formats a number as a hexadecimal string.
|
|
---@param n number | string
|
|
---@param upper? boolean
|
|
---@return string
|
|
function math.tohex(n, upper)
|
|
local formatString = ('0x%s'):format(upper and '%X' or '%x')
|
|
return formatString:format(n)
|
|
end
|
|
|
|
---Converts input number into grouped digits
|
|
---@param number number
|
|
---@param seperator? string
|
|
---@return string
|
|
function math.groupdigits(number, seperator) -- credit http://richard.warburton.it
|
|
local left, num, right = string.match(number, '^([^%d]*%d)(%d*)(.-)$')
|
|
return left .. (num:reverse():gsub('(%d%d%d)', '%1' .. (seperator or ',')):reverse()) .. right
|
|
end
|
|
|
|
---Clamp a number between 2 other numbers
|
|
---@param val number
|
|
---@param lower number
|
|
---@param upper number
|
|
---@return number
|
|
function math.clamp(val, lower, upper) -- credit https://love2d.org/forums/viewtopic.php?t=1856
|
|
if lower > upper then lower, upper = upper, lower end -- swap if boundaries supplied the wrong way
|
|
return math.max(lower, math.min(upper, val))
|
|
end
|
|
|
|
---Calculates an intermediate value between `start` and `finish` based on the interpolation `factor`.
|
|
---@generic T : number | vector2 | vector3 | vector4
|
|
---@param start T
|
|
---@param finish T
|
|
---@param factor integer The interpolation factor between 0 and 1.
|
|
---@return T
|
|
function math.interp(start, finish, factor)
|
|
return start + (finish - start) * factor
|
|
end
|
|
|
|
local function interpolateTable(start, finish, factor)
|
|
local interp = math.interp
|
|
local result = {}
|
|
|
|
for k, v in pairs(start) do
|
|
result[k] = interp(v, finish[k], factor)
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
---Linearly interpolates between two values over a specified duration, returning an iterator function that will run once per game-frame.
|
|
---@generic T : number | table | vector2 | vector3 | vector4
|
|
---@param start T -- The starting value of the interpolation.
|
|
---@param finish T -- The ending value of the interpolation.
|
|
---@param duration number -- The duration over which to interpolate over in milliseconds.
|
|
---@return fun(): T, number
|
|
function math.lerp(start, finish, duration)
|
|
local startTime = GetGameTimer()
|
|
local typeStart = type(start)
|
|
local typeFinish = type(finish)
|
|
|
|
if typeStart ~= 'number' and typeStart ~= 'vector2' and typeStart ~= 'vector3' and typeStart ~= 'vector4' and typeStart ~= 'table' then
|
|
error(("expected argument 1 to have type '%s' (received %s)"):format('number | table | vector2 | vector3 | vector4', typeStart))
|
|
end
|
|
|
|
assert(typeFinish == typeStart, ("expected argument 2 to have type '%s' (received %s)"):format(typeStart, typeFinish))
|
|
|
|
local interpFn = typeStart == 'table' and interpolateTable or math.interp
|
|
local step
|
|
|
|
return function()
|
|
if not step then
|
|
step = 0
|
|
return start, step
|
|
end
|
|
|
|
if step == 1 then return end
|
|
|
|
Wait(0)
|
|
step = math.min((GetGameTimer() - startTime) / duration, 1)
|
|
|
|
if step < 1 then
|
|
return interpFn(start, finish, step), step
|
|
end
|
|
|
|
return finish, step
|
|
end
|
|
end
|
|
|
|
---Rounds a number to a whole number or to the specified number of decimal places.
|
|
---@param value number | string
|
|
---@param places? number | string
|
|
---@return number
|
|
function math.round(value, places)
|
|
if type(value) == 'string' then value = tonumber(value) end
|
|
if type(value) ~= 'number' then error('Value must be a number') end
|
|
|
|
if places then
|
|
if type(places) == 'string' then places = tonumber(places) end
|
|
if type(places) ~= 'number' then error('Places must be a number') end
|
|
|
|
if places > 0 then
|
|
local mult = 10 ^ (places or 0)
|
|
return math.floor(value * mult + 0.5) / mult
|
|
end
|
|
end
|
|
|
|
return math.floor(value + 0.5)
|
|
end
|
|
|
|
return lib.math
|