229 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
	
		
			7 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 © 2025 Linden <https://github.com/thelindat>
 | |
| ]]
 | |
| 
 | |
| ---@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
 | 
