Mini Functions Repository

General discussion about LÖVE, Lua, game development, puns, and unicorns.
ZBoyer1000
Prole
Posts: 39
Joined: Sat Nov 28, 2015 10:13 am

Re: Mini Functions Repository

Post by ZBoyer1000 » Sat Apr 16, 2016 5:37 pm

Here is another function that I created that can shift a number over a specific period of time. It's pretty good to use if you don't want to waste time making your own clocks that do similar things. To spice up things, I included "resetType" with four settings to help. :P

Code: Select all

 numList = {} 
function shiftToNum(name,time,min,max,resetType) 
 nIndex = 0
 for i = 1,#numList do if numList[i][1] == tostring(name) then nIndex = i end end
 if nIndex == 0 and max > min then
  local var = {tostring(name), a=min, b=1, c=0} table.insert(numList, var) elseif nIndex ~= 0 then
  numList[nIndex].c = numList[nIndex].c + 1
  if numList[nIndex].c == time then numList[nIndex].c = 0
  numList[nIndex].a = numList[nIndex].a + math.ceil(numList[nIndex].b)
  if numList[nIndex].a >= max and resetType == "ReturnToMin" then numList[nIndex].a =  min
  elseif numList[nIndex].a >= max and resetType == "ShiftToMin" then numList[nIndex].b = -1
  elseif numList[nIndex].a >= max and resetType == "StopAtMax" then numList[nIndex].a = max  numList[nIndex].b = 0
  elseif numList[nIndex].a >= max and resetType == "StopAtMin" then numList[nIndex].a = min  numList[nIndex].b = 0
  end if numList[nIndex].a <= min then numList[nIndex].b = 1 end
  end
 end
 if nIndex ~= 0 then return numList[nIndex].a else return 0 end
end 
Usage and example:
love.graphics.draw()
local wColor = shiftToNum("Pulse", 4, 0, 255, "ShiftToMin")
love.graphics.setColor(wColor, wColor, wColor)
love.graphics.rectangle("fill",0,0,100,100)
end

User avatar
raingloom
Prole
Posts: 36
Joined: Wed Apr 22, 2015 12:35 am
Location: Always elsewhere

Re: Mini Functions Repository

Post by raingloom » Mon Apr 25, 2016 1:06 am

A HSV to RGB converter I just finished. (Github gist + a full window test)
edit: hue is a radian, the rest are reals between 0 and 1, just in case that wasn't clear
Slightly minified version:

Code: Select all

local sqrt, sin, cos = math.sqrt, math.sin, math.cos
local pi = math.pi
local r1, r2 =  0          ,  1.0
local g1, g2 = -sqrt( 3 )/2, -0.5
local b1, b2 =  sqrt( 3 )/2, -0.5
local function HSVToRGB( h, s, v, a )
  h=h+pi/2--because the r vector is up (TODO: realign vectors)
  local r, g, b = 1, 1, 1
  local h1, h2 = cos( h ), sin( h )
  r = h1*r1 + h2*r2
  g = h1*g1 + h2*g2
  b = h1*b1 + h2*b2
  r = r + (1-r)*s
  g = g + (1-g)*s
  b = b + (1-b)*s
  r,g,b = r*v, g*v, b*v
  return r*255, g*255, b*255, (a or 1) * 255
end
I'm pretty sure it's not perfect and would love to hear some opinions, especially if anything could be done about the banding I see.
(a few tests from my blog)
Last edited by raingloom on Sat Apr 30, 2016 12:21 am, edited 1 time in total.

User avatar
undef
Party member
Posts: 438
Joined: Mon Jun 10, 2013 3:09 pm
Location: Berlin
Contact:

Re: Mini Functions Repository

Post by undef » Mon Apr 25, 2016 6:17 am

I wrote this fancy left-pad function:

Code: Select all

function leftpad( str, len, ch )  ch = ch or " "
    return string.rep( ch, len - #str ) .. str
end
Hmm, maybe I should put it on Luarocks...
twitter | steam | indieDB

Check out quadrant on Steam!

User avatar
pgimeno
Party member
Posts: 1942
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Easing functions collection

Post by pgimeno » Fri Apr 29, 2016 1:43 am

This is a series of easing functions that can be used for tweening. Unlike most easing functions used everywhere else, none of these are defined piecewise, they are all one single function. And except for bounce, I believe they are all infinitely derivable in [0, 1], i.e. smooth.

They are all intended for use with a lerp function. I posted a formulation that, as I later discovered, still has stability problems, so here's a better one:

Code: Select all

function lerp(from, to, t)
  return t < 0.5 and from + (to-from)*t or to + (from-to)*(1-t)
end
And they are used like this. You're assumed to have a starting value and an ending value (from and to) and you want to generate a smooth transition between both with a certain profile. You then iterate a number 't' from 0 to 1 in regular time increments, and pass that number through the easing function. Then use it to do a lerp. Like this:

Code: Select all

current_value = lerp(from, to, ease_function(t))
Some tweening libraries come with a way to plug in your own easing function. If your tweening library doesn't support it, you can always do it manually by choosing 0 and 1 as source and target value and linear easing, so that you can use the result as above.

Now for the easing functions themselves. There's a whole family which I call the "sigmoid" because they resemble one. They are functions that meet these criteria:
  • They are defined in the interval [0, 1].
  • The value at 0 is 0, and the value at 1 is 1. (That happens for all functions presented here).
  • They have rotational symmetry around the point (1/2, 1/2), that is, f(x) = 1-f(1-x).
  • The slope at 0 is 0, the slope at 1 is 0, and the slope everywhere else is >= 0 (in theory strictly > 0).
  • The second derivative is >= 0 in [0, 1/2], exactly 0 in 1/2, and <= 0 in [1/2, 1]. This means that it curls up from the left to the centre, and down from the centre to the right. As a consequence, the slope at the centre is maximum.
One obvious choice for a sigmoid-like function is a segment of the sine function, which already has that "S" shape we're looking for. Here's an easing function based on it:

Code: Select all

-- Slope at centre: 1.5708
function ease_sigmoid_sinusoidal(t)
  return (1-math.cos(t*math.pi))/2
end
An exponential sigmoid. It's a bit "lazy" at the extremes (slow to take off and land).

Code: Select all

-- Slope at centre: 2
function ease_sigmoid_exp(t)
  local partial = math.exp((-1)/t)
  return partial/(math.exp((-1)/(1 - t)) + partial)
end
There's a whole class of sigmoids which is based on polynomials. I've done a bit of research looking for ways of generating them, and found three methods. The most well-known sigmoid (3t²-2t³) can be generated by two of them:

Code: Select all

-- Slope at centre: 1.5
function ease_sigmoid(t)
  return t*t*(3-t*2)
end
If that slope is too sharp (goes too fast on the middle and too slow on the ends), here's a general way to generate any gentler slope (it also reveals one of the three methods mentioned):

Code: Select all

function ease_sigmoid_param(t, param)
  param = param or 6
  return lerp(t^param, (1-(1-t)^param), t)
end
When param is either 2 or 3, it generates the previous sigmoid (3t²-2t³). Greater values generate gentler slopes. A fractional number between 2 and 3 generates a slightly steeper slope, though using fractional numbers doesn't technically produce a polynomial.

The second generation method yields polynomials with a steeper slope, and involves using an algebra program, which Lua isn't, so I have no general function to generate them. I'll add here some of the sigmoids I've found in increasing order of slope:

Code: Select all

function ease_sigmoid_fast(t)
  -- Slope at the middle: 1.875
  return t^3*(10+t*(-15+t*6))
end
This happens to be the function used by Ken Perlin in his improved Perlin noise article. He may have generated it the same way as I did, not sure.

Higher degree leads to steeper slopes, but the calculations get heavier and heavier:

Code: Select all

-- Slope at the middle: 2.1875
function ease_sigmoid_faster(t)
  return t^4*(35+t*(-84+t*(70+t*-20)))
end

-- Slope at the middle: m=3.142
function ease_sigmoid_fastest(t)
  return t^8*(6435+t*(-40040+t*(108108+t*(-163800+t*(150150+t*(-83160+t*(25740+t
*-3432)))))))
end
Note the latter is quite heavy on calculations (degree 15, with all terms up to degree 8 present).

The third generation method consists of chaining several sigmoids, and yields a slope which is the slope of the original one squared, e.g.

Code: Select all

-- Slope at centre: 2.25 (=1.5^2)
function ease_sigmoid_double(t)
  return ease_sigmoid(ease_sigmoid(t))
end
The well-known and widely used "in-out-cubic", defined piecewise, is not "smooth". Its second derivative at the middle is not zero, therefore it's not a true sigmoid as defined here. Its slope at the middle is 3, which is why I've posted one with a slope >3. Note that despite the difference in slope, a better substitutive for it is ease_sigmoid_faster.

Enough sigmoids. Now for the other easing functions. Exponential:

Code: Select all

-- Exponentially reach target
function ease_exp(t)
  return (1-math.exp(-8*t))/0.9996645373720975
end
The magic numbers are to select the speed and for making the result be exactly 1 when the input is 1. This function is approximately the explicit formula for the iterative one that Sulunia posted upthread. Being explicit has the advantage that it can use dt to adjust to changes in frame rate.

Bounce. This one is the non-derivable one. The first, commented out formula works, but does not start too well. Unfortunately it's not adjustable.

Code: Select all

function ease_bounce(t)
  -- This one starts quite badly.
  --  return (1-math.abs(math.cos(math.pi/(1-t)))*(1-t)^2)

  -- Adjust the window to grab a better range. Plenty of magic numbers here,
  -- sorry. These were obtained by doing the math for the adjustment.
  t = (1-t*0.999999999)*0.5271666475893665
  return 1-math.abs(math.cos(math.pi/t))*t^2*3.79559296602621
end
The rest of functions all go out of the 0-1 range deliberately (but are still 0 for an input of 0 and 1 for an input of 1).

Outback. This one is fairly well known. It overshoots the target then goes back and stops smoothly. The amount of overshoot can be adjusted using a parameter.

Code: Select all

function ease_outback(t, param)
  t = 1-t
  return 1-t*t*(t+(t-1)*(param or 1.701540198866824))
end
The magic number for the default parameter gives exactly a 10% overshoot. Most other sites use 1.70158 which is slightly above 10%. If you want to calculate the parameter for a given overshoot ratio, you can use the following function:

Code: Select all

function calc_outback_param(h)
  local P = (91.125*h + 410.0625*h^2 + 307.546875*h^3
             + 0.5*math.sqrt(33215.0625*h^2*(h + 1))
            )^(1/3)
  return 2.25*h + (13.5*h + 15.1875*h^2)/P + P/3
end
Don't use negative values, and cache the result rather than using it every iteration, as it's computation-intensive as you can guess.

This one is similar. It goes the opposite way at the start then smoothly approaches the target. I didn't make a function to adjust the overshoot, but maybe I will in future.

Code: Select all

function ease_inback(t, param)
  return 1-(1-t)^2*(1+t*(param or 3.48050701420725))
end
Use a parameter greater than 2. Using exactly 2 results in the classic sigmoid (3t²-2t³). I haven't yet found the formula to calculate the parameter using the ratio.

And last but not least, elastic out. I didn't like the default elastic one I saw everywhere because the initial overshoot was too large, so I made my own function (the total number of times it bounces back and forth is adjustable, it defaults to 6):

Code: Select all

function ease_elastic(t, times)
  return (1 - (math.exp(-12*t) + math.exp(-6*t) * math.cos(((times or 6) + 0.5)
* math.pi * t))/2)/0.99999692789382332858
end
Last edited by pgimeno on Fri Jul 01, 2016 8:51 am, edited 1 time in total.

User avatar
CrackedP0t
Citizen
Posts: 69
Joined: Wed May 07, 2014 4:01 am
Contact:

Re: Mini Functions Repository

Post by CrackedP0t » Sat Apr 30, 2016 8:52 pm

Here's a nice function that turns any value except for threads, functions, and (probably) userdata into Lua and human-readable strings:

Code: Select all

function bettertostring(value, depth, indent)
	depth = depth or 0
	indent = indent or "\t"
	local s = ""
	if type(value) == "table" then
		s = s .. "{\n"
		for k, v in pairs(value) do
			s = s .. string.rep(indent, depth + 1) .. "[" .. bettertostring(k) .. "]" .. " = " .. bettertostring(v, depth + 1) .. ",\n"
		end
		s = s .. string.rep(indent, depth) .. "}"
		s = s:reverse():gsub("^(.-),", "%1"):reverse()
	elseif type(value) == "string" then
		s = "\"" .. tostring(value) .. "\""
	else
		s = tostring(value)
	end

	return s
end
Last edited by CrackedP0t on Tue May 03, 2016 12:12 am, edited 1 time in total.
/人 ◕‿‿◕ 人\
Here, have an umlaut. Ö

User avatar
zorg
Party member
Posts: 2745
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Mini Functions Repository

Post by zorg » Sat Apr 30, 2016 9:20 pm

undef wrote:I wrote this fancy left-pad function:
I see what you did there : 3
Here's the pair of that, right-pad:

Code: Select all

function rightpad( str, len, ch )  ch = ch or " "
    return str .. string.rep( ch, len - #str )
end
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.

User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Mini Functions Repository

Post by Inny » Sun May 01, 2016 12:06 am

leftpad, rightpad, luarocks, breaking the internet
While hilarious, let me just set the record straight that string.format is what you want to use for your string padding. The format codes are a little weird at first and I always have to look them up myself: https://en.wikipedia.org/wiki/Printf_fo ... cification

So, just a few examples:

Code: Select all

local input_number, input_string = 27, "Hi Mom!"
 -- "        27"
local left_padded_number = string.format("%10i", input_number)

 -- "27        "
local right_padded_number = string.format("%-10i", input_number)

 -- "   Hi Mom!"
local left_padded_string = string.format("%10s", input_string)

 -- "0000000027"
local left_padded_leading_zeros_number = string.format("%010i", input_number)

 -- "     27.00"
local left_padded_fixed_point = string.format("%10.2f", input_number)

-- also you can use the object-method form it.
local left_padded = ("%20i"):format(input_number)

User avatar
pgimeno
Party member
Posts: 1942
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Mini Functions Repository

Post by pgimeno » Sun May 01, 2016 2:29 am

CrackedP0t wrote:Here's a nice function that turns any value except for threads, functions, and (probably) userdata into Lua and human-readable strings:

Code: Select all

[...]
			s = s .. string.rep(indent, depth + 1) .. "[" .. stostring(k) .. "]" .. " = " .. stostring(v, depth + 1) .. ",\n"
What is stostring?

User avatar
Doctory
Party member
Posts: 441
Joined: Fri Dec 27, 2013 4:53 pm

Re: Mini Functions Repository

Post by Doctory » Sun May 01, 2016 9:56 am

pgimeno wrote:
CrackedP0t wrote:-snip-
[/code]
What is stostring?
Most likely a typo, meant to be tostring.
"This is what videogames should be like; they should have meaning and triple machine guns"
- Jan Willem Nijman, 2013
github

User avatar
undef
Party member
Posts: 438
Joined: Mon Jun 10, 2013 3:09 pm
Location: Berlin
Contact:

Re: Mini Functions Repository

Post by undef » Sun May 01, 2016 1:39 pm

Inny wrote:snip
Ah, that's how I zero pad or space pad numbers, but for some reason I didn't think of using it for strings, and I didn't know it's possible to right align like that as well.
Thanks :)

I also chose this implementation because just like the npm version it allows padding with any character (which is of course a much rarer case).
If there's a way to repeat strings in string.format I've been missing so far, please let my know by the way... so far I create format strings with a lot of repetition in them with string.rep.
twitter | steam | indieDB

Check out quadrant on Steam!

Post Reply

Who is online

Users browsing this forum: o3a and 24 guests