Page 2 of 3

Re: Tiled world with smooth movement transitions

Posted: Wed Aug 31, 2022 7:15 am
by darkfrei
So we can use the floatPosition where it's just a summary of previous floatPosition and dt*floatVelocity.
The stepPosition (in pixels) can be just integer as math.floor (floatPosition+0.5) or in tiles (where tileSize is 64 pixels) fine fraction as math.floor (floatPosition*64+0.5)/64. It looks smooth as float, but precise as integer.

Re: Tiled world with smooth movement transitions

Posted: Wed Aug 31, 2022 11:06 am
by pgimeno
RNavega wrote: Wed Aug 31, 2022 4:34 am @pgimeno thanks a lot for the insights on the variants of LERP. If I understood you right, it's okay to freely use the optimized form (a + (b-a) * t) in your game, provided that you:
1) Don't assume that the result will have the exact values of A or B, even if t is 0 or 1. It'll be extremely close (to the point of not being noticeable by the viewer), just not floating-point equal.
When t is 0 there's no problem at all: (b-a)*0 is guaranteed to be 0, and a+0 is guaranteed to be a. The problem appears only when t is 1, because b - a + a is not guaranteed to be b.

If you floor the result (which is pretty common when using it for graphics), the coordinate may be off by 1 pixel, and that will probably be noticeable. Fortunately it's not very likely that b - a + a does not equal b when both a and b are integer; that would only happen for very big a, in the order of 2^54 or more, and comparatively small b.

Re: Tiled world with smooth movement transitions

Posted: Wed Aug 31, 2022 1:54 pm
by RNavega
pgimeno wrote: Wed Aug 31, 2022 11:06 am If you floor the result (which is pretty common when using it for graphics), the coordinate may be off by 1 pixel, and that will probably be noticeable.
Ah, I see what you mean, thanks man.
If you have like a pixel art game, and you set b to 10 but the interpolated result is 9.999(...) and gets truncated to 9, that will be noticeable.

The only solution I can think of would be to have the interpolation code special-case to return b directly -- the condition being if t >= 1.0, or if the interp. speed is positive (so the destination of t is 1) and its state is considered "finished".

Re: Tiled world with smooth movement transitions

Posted: Wed Aug 31, 2022 4:06 pm
by pgimeno
RNavega wrote: Wed Aug 31, 2022 1:54 pm The only solution I can think of would be to have the interpolation code special-case to return b directly -- the condition being if t >= 1.0, or if the interp. speed is positive (so the destination of t is 1) and its state is considered "finished".
That's the fourth formulation I suggested:

Code: Select all

return t == 1 and b or a + (b - a) * t
The third one is thoroughly tested for violations of monotonicity in the transition from t=0.49999999999999994 (the number just before 0.5) to t=0.5 and none was discovered. This one gives the exact values when t = 0 and t = 1.

Code: Select all

return t < 0.5 and a + (b - a) * t or b - (b - a) * (1 - t)
As for limiting t, that may not be desirable in some applications. Lerp is often used for tweening, where the easing is provided by a function that takes a linear t in 0..1 and transforms it to non-linear for lerping. For example:

Code: Select all

local function ease_smoothstep(t)
  return 3*t^2-2*t^3
end

function tween.smoothstep(a, b, t)
  return lerp(a, b, ease_smoothstep(t))
end

local function ease_outback(t)
  t = 1-t
  return 1-t*t*(t+(t-1)*1.701540198866824)
end

function tween.outback(a, b, t)
  return lerp(a, b, ease_outback(t))
end
The last one, outback, goes a bit past b and then comes back to land on b (that weird magic number is adjusted so that it goes exactly 10% past b); to do that, the easing function produces, and thus the lerp function receives, t > 1.

Re: Tiled world with smooth movement transitions

Posted: Tue Sep 06, 2022 2:37 pm
by milon
pgimeno wrote: Wed Aug 31, 2022 4:06 pm
RNavega wrote: Wed Aug 31, 2022 1:54 pm The only solution I can think of would be to have the interpolation code special-case to return b directly -- the condition being if t >= 1.0, or if the interp. speed is positive (so the destination of t is 1) and its state is considered "finished".
That's the fourth formulation I suggested:

Code: Select all

return t == 1 and b or a + (b - a) * t
Is there any particular reason to use == instead of >= in the above? I would think >= is more robust, depending on implementation. It's probably got the exact same computational expense too, but correct me if I'm wrong.

Re: Tiled world with smooth movement transitions

Posted: Tue Sep 06, 2022 7:28 pm
by pgimeno
milon wrote: Tue Sep 06, 2022 2:37 pm Is there any particular reason to use == instead of >= in the above? I would think >= is more robust, depending on implementation. It's probably got the exact same computational expense too, but correct me if I'm wrong.
See the post immediately before yours. Sometimes it's desirable that t > 1.

Re: Tiled world with smooth movement transitions

Posted: Tue Sep 06, 2022 9:22 pm
by RNavega
milon wrote: Tue Sep 06, 2022 2:37 pm Is there any particular reason to use == instead of >= in the above? I would think >= is more robust, depending on implementation. It's probably got the exact same computational expense too, but correct me if I'm wrong.
What Pedro meant with that example is that it's important for some effects for t to under/overshoot the [0, 1] range, like the "ease in/out back" easings:

Image

However, if you must keep your t in the [0, 1] range, you can still have these out-of-range easings if you use another internal t that only your interpolation helper knows about, with rules like these:
- The input t is expected to be in the [0, 1] range, and anything outside of that is undefined behavior / error.
- The internal t (t' if you will) can have any value, it can under/overshoot etc. and is the result of the input t being interpolated with some easing function.
- When checking if the lerp is finished, test the input t. To calculate the lerp, use t'.

Re: Tiled world with smooth movement transitions

Posted: Wed Sep 07, 2022 1:06 pm
by milon
pgimeno wrote: Tue Sep 06, 2022 7:28 pm
milon wrote: Tue Sep 06, 2022 2:37 pm Is there any particular reason to use == instead of >= in the above? I would think >= is more robust, depending on implementation. It's probably got the exact same computational expense too, but correct me if I'm wrong.
See the post immediately before yours. Sometimes it's desirable that t > 1.
Facepalm
Guess I'm still learning how to read. :crazy:
Thanks!

RNavega wrote: Tue Sep 06, 2022 9:22 pm
milon wrote: Tue Sep 06, 2022 2:37 pm Is there any particular reason to use == instead of >= in the above? I would think >= is more robust, depending on implementation. It's probably got the exact same computational expense too, but correct me if I'm wrong.
What Pedro meant with that example is that it's important for some effects for t to under/overshoot the [0, 1] range, like the "ease in/out back" easings...
Thanks for the detailed reply. I honestly just missed the second half of his post somehow. But it's a nice explanation that beautifully shows the concept! :)

Re: Tiled world with smooth movement transitions

Posted: Wed Sep 07, 2022 2:28 pm
by pgimeno
This is an example of use of the actual outback function I had in mind (the formula I posted above). It has humorous uses; you can imagine an accompanying screeching tyre sound. In this post I give a formula to calculate a different outback constant given a percent of overshoot: viewtopic.php?p=198129#p198129 (see calc_outback_param).

Code: Select all

local timer = 0
local total_time = 2
local stop_time = 1

local start_x, end_x, y = 0, 650, 300

local function lerp(a, b, t)
  return t < 0.5 and a + (b - a) * t or b + (a - b) * (1 - t)
end

local function ease_outback(t)
  t = 1-t
  return 1-t*t*(t+(t-1)*1.701540198866824)
end

local function tween_outback(a, b, t)
  return lerp(a, b, ease_outback(t))
end

local frame=0
function love.update(dt)
--  dt = i==0 and 0 or 1/15; frame=frame+1;love.graphics.captureScreenshot(("screenshot%02d.png"):format(frame))
  timer = timer + dt
  if timer >= total_time then
    timer = timer - total_time
  end
end

function love.draw()
  local t = timer / stop_time
  if t > 1 then t = 1 end
  love.graphics.circle("fill", tween_outback(start_x, end_x, t), y, 15)

  love.graphics.rectangle("fill", start_x, y + 20, 1, 20)
  love.graphics.print("Start\nposition", start_x, y + 50)
  love.graphics.rectangle("fill", end_x, y + 20, 1, 20)
  love.graphics.print("Target\nposition", end_x, y + 50)
end

Re: Tiled world with smooth movement transitions

Posted: Wed Sep 07, 2022 3:29 pm
by RNavega
@pgimeno this function of yours here to calculate the magic factor:
pgimeno wrote: Fri Apr 29, 2016 1:43 am

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
Do you know the reasoning behind it? When I was researching this a while ago I only found a Japanese blog post explaining how it's the factor for a polynomial curve, something like that. It was too complicated for me so I used an online web plotter like Desmos to calibrate the parameter (adjusting the overshoot), a graph like this: https://www.desmos.com/calculator/7x8fwjgnpt