Lua Utility Functions

Clamp value between min and max:

local function clamp(value, min, max)
	if value < min then
		return min
	end
	if value > max then
		return max
	end
	return value
end

Change of base formula for math.log() expressions:

local function log_base(base, expression)
	return math.log(expression) / math.log(base)
end

Check if value is between min and max:

local function is_within_range(value, min, max)
    return min <= value and value <= max
end

Calculate Manhattan distance between two points:

local function manhattan_distance(from, to)
    return vmath.vector3(to.x - from.x, to.y - from.y, to.z - from.z)
end

Get the sign of a number (1 for positive numbers, -1 for negative numbers, and 0 for zero):

local function sign(n)
    return n > 0 and 1 or (n < 0 and -1 or 0)
end

Converts a hex string to a vector4

function hex_to_color(hex)
  local r, g, b = hex:match("#(%x%x)(%x%x)(%x%x)")
  r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)
  return vmath.vector4(r / 255, g / 255, b / 255, 1)
end
1 Like

Great idea!

This is a function which I find that I often need myself. I wonder if it should be part of vmath?

7 Likes

Check if point is within rectangle:

local function is_within_rectangle(point_x, point_y, rect_x, rect_y, rect_width, rect_height)
    return rect_x <= point_x and point_x <= rect_x + rect_width and rect_y <= point_y and point_y <= rect_y + rect_height
end

I think that would be a good addition. Maybe better for the math module, but if I understand correctly, math is built into the Lua language.

Here’s a bunch of stuff I collected over the years:

Also, a good animation tool I’ve found useful are low-pass filters for creating snapping effects or smoothing out movements:

https://github.com/critique-gaming/crit/blob/master/crit/filters.lua

4 Likes

I wrote this function when I was trying to pprint some enormous tables full of subtables.

    --individually prints each line of a nested table
local function recursive_pprint(t, table_name)

    if not t then
        return
    end

    table_name = table_name or ""
    local type_table = "table"
    for key, value in pairs(t) do
        if type(value) == type_table then
            recursive_pprint(value, key)
        else
            print(table_name .. ": " .. key .. ": ", value)
        end
    end
4 Likes

Awesome. I actually have something similar too. See table_util.dump() above. The cool part is that it outputs valid Lua most of the time

3 Likes

Accurate framerate-independent lerp with delta-time:

-- `rate` is the lerp coefficient per second. So rate=0.5 halves the difference every second.
local function lerpdt(from, to, rate, dt)
	local diff = from - to           -- Target value is just an offset. Remove it and add it back.
	return diff * (1 - rate)^dt + to -- Flip rate so it's the expected direction (0 = no change).
end

Thanks to this site for the correct explanation.

Normally, the rate is the lerp coefficient per second. To adjust the time frame, divide dt by the desired time.

For example, if you want to halve a value every 1/60th of a second, do:

lerpdt(from, to, 0.5, dt/(1/60))

Has been unit tested. A basic test:

from, to, rate = 10, 0, 0.25

-- Lerp over 1 second all at once:
local result = lerpdt(from, to, rate, 1)

-- Lerp iteratively with times adding up to 1:
from = lerpdt(from, to, rate, 0.75)
from = lerpdt(from, to, rate, 0.01)
from = lerpdt(from, to, rate, 0.21)
from = lerpdt(from, to, rate, 0.01)
from = lerpdt(from, to, rate, 0.01)
from = lerpdt(from, to, rate, 0.01)
-- `from` and `result` should be equal (with some floating point error).
4 Likes

Thanks everyone. I will change the list in the first post to include categories to improve organization.

I’m having some trouble seeing how this is different than vmath.lerp() example from the docs: API reference (vmath)

It looks to me like the new part is the rate parameter?

The function I posted is framerate-independent. It can be used continuously in update() (i.e. without having a start and end time to the interpolation).

Let’s say you have a value that you want to halve every frame. Maybe you’re remaking Nuclear Throne and you want to damp the velocity of a shotgun bullet. If you do:

function update(self, dt)
	self.velocity = vmath.lerp(0.5, self.velocity, 0)
end

That is wrong. It will vary quite a bit depending on your framerate.
This is also wrong:

self.velocity = vmath.lerp(0.5*dt, self.velocity, 0)

Basically, you need this any time you want to continuously move one value towards another. Camera smoothing is another good example.

6 Likes

One of my favorite Lua function libs:

7 Likes

Hmm. That looks interesting. It’s definitely not a “LERP” since that’s a linear interpolation and the result here is not linear. More rather it’s sort-of like a low-pass filter, as in a function that always tends towards the target (if you take its limit), but “lags behind” a bit hence cutting off any abrupt movements (higher frequencies). I’m happy to see I’m not the only one using low pass filters for animation, yay!

I use this version lifted off the Wikipedia article on low pass filters:

-- @tparam number cutoff_frequency The cut-off frequency (in Hz) of the filter.
-- @treturn LowPassFilter The new filter function.
function M.low_pass(cutoff_frequency)
  local RC = 1.0 / (cutoff_frequency * 2.0 * math.pi);
  return function (previous_output, input, dt)
    local alpha = dt / (dt + RC);
    return previous_output + alpha * (input - previous_output);
  end
end
8 Likes

Yes, the clamp function is really useful. Is it part of vmath now?

1 Like

No, we haven’t made any additions to vmath.