pygame icon indicating copy to clipboard operation
pygame copied to clipboard

Lerp and Inverse Lerp functions in pygame.math

Open JamesC01 opened this issue 3 years ago • 9 comments
trafficstars

I saw on the discord that you should open issues to talk about api-changing features, so I'm making this. I plan on contributing a lerp and inverse lerp function to the pygame.math module. I went over similar points in my pygame.math.clamp PR, but I'll mention them again:

  • Most other frameworks implement their own lerp functions (MonoGame, Unity, Godot, even the C++ standard library)
  • There don't seem to be many common python libraries that have lerp and inverse lerp.
  • Functions like lerp (and clamp, which is a current PR I have) are pretty common for game development, and implementing them in C would boost the performance. (for the clamp function, the C implementation is 2.8x faster than if it was implemented in python)

I believe these functions fit quite well into the pygame.math module, and they're relatively small, and should be easy to maintain. If the org members give the okay, I'll be happy to implement these functions!

By the way, I'm not talking about Vector lerp functions, I'm talking about regular lerp functions that take floats.

JamesC01 avatar Jul 23 '22 15:07 JamesC01

I use lerp quite often in my games.

Pondering on this, should we also support some of the traditional easing options. e.g.

  • smoothstep
  • quadratic ease in
  • quadratic ease out

etc

see: https://github.com/Michaelangel007/easing

for some examples and nice graphs.

Though that might be drifting into too many options.

MyreMylar avatar Jul 23 '22 17:07 MyreMylar

Godot, Unity and MonoGame all have smoothstep (as well as lerp), so that also seems like a common function in game math libraries, so that sounds like a good choice. After a little bit of searching, I haven't seen quadratic ease in/out in any of the common game frameworks/engines, so I'm not sure about those, they seem less common. I think targeting the most common ones might be a good idea, rather than adding in a load of them.

JamesC01 avatar Jul 23 '22 18:07 JamesC01

I personally dig the idea. Always nice to have frequently used functions in pygame C api

itzpr3d4t0r avatar Jul 23 '22 20:07 itzpr3d4t0r

@MyreMylar Do you think I should implement the functions (lerp, inverse_lerp, smoothstep)? Or wait for other contributors opinions?

JamesC01 avatar Jul 24 '22 13:07 JamesC01

Lerp is a really handy piece of functionality to include in base pygame, especially in the C API

PurityLake avatar Jul 30 '22 22:07 PurityLake

@MyreMylar Do you think I should implement the functions (lerp, inverse_lerp, smoothstep)? Or wait for other contributors opinions?

Apologies for the slow response, I was on holiday in Sweden.

I think a float lerp function would be the best way to start out as it should be a pretty easy PR C wise. Though more attention might need to be put on things like documentation and examples to introduce the concept of lerping in games to an audience who might never have heard of it. 'Linear interpolation' sounds like a scary thing that is actually simple and very useful (health bars spring to mind).

Since it is an easy-ish thing to implement in python perhaps also a performance comparison versus a python implementation would be a good thing to put in the PR. Python implementation would also be a good thing to test against in the tests. After all if the C version isn't faster we are not adding much other than docs.

def lerp(a: float, b: float, percentage: float) -> float:
    if 0.0 > d > 1.0:
        raise ValueError("Percentage value must be between 0.0 and 1.0")
    return a + (b-a) * percentage 

If the lerp PR is a success we could consider adding a couple of others.

MyreMylar avatar Jul 31 '22 13:07 MyreMylar

Most of the lerp implementations I've seen allow for percent ranges outside of 0 and 1. One of the only ones that doesn't allow values outside that range is unity, which just clamps them for you. This is mentioned in the godot documentation for lerp: "However, values outside this range are allowed and can be used to perform extrapolation."

Also, why only floats? It wouldn't be hard to make it work like the python implementation you gave, and could potentially be more performant, because it wouldn't have to convert to c types. As long as the object supports add, subtract and multiply, it would work, and would be consistent with clamp. Or do you think it's best to just stick with floats for now? I guess starting with floats would allow the option to extend it to more types later, without breaking anything (I think.)

Also, I timed the c implementation with floats vs the python implementation you gave, and the C version is ~84% faster (unless I wrote it wrong!) Edit: The C implementation using any type, not just floats is actually almost as slow as the python version, so it's probably better to just support floats, especially since Color and Vector types already have lerp.

This does feel a bit inconsistent with clamp, though. I wonder if we should document clamp as only taking floats too, and just leave the fact that it can take any type as a behind-the-scenes implementation detail. That would make it more consistent with lerp. Clamp is only really avoiding floats because it performs better that way.

JamesC01 avatar Jul 31 '22 18:07 JamesC01

Hmm, I don't know if lerp is super performance sensitive, at least I've not seen many games where performance hinges on thousands of lerp calls a frame.

Ignoring performance, I would lean towards supporting all python single number types for this lerp function - but not putting in extra work for things like Vectors and Colours, because as you say the colour and Vector types in pygame already have their own lerp function. This function would really just be covering the float/int case in actual practicality/95% of cases, yes complex numbers also exist but almost nobody really uses them in games. I guess somebody could build their own number class, but if they are doing that they can probably happily make their own lerp function for it as well.

If doing this in C ends up slower than the basic float function I outlined above then perhaps it is easier just to let people define their own lerp functions for what they need them for. If it is a bit faster or the same then good because it saves people effort and it's very existence in pygame provides a little bit of documentation on how to make games in itself.

You could also point people towards the other lerp methods for the relevant pygame classes in the documentation. You could also comment in the lerp code why it has limits on the types if you like.

In conclusion -

  1. lerp should support at least int and float. If it supports more than that, great.
  2. I'm somewhat indifferent on extrapolation as I've never used it in a lerp myself. Whichever way you go should be documented.
  3. performance less important than ease of use IMHO as I would rate lerp as only 'medium' on the giant chart of performance sensitivity, but should ideally still be better than python code otherwise not much point as the function is relatively simple.

That is my thoughts, however others may disagree :)

MyreMylar avatar Aug 01 '22 06:08 MyreMylar

I made the PR. It supports int and float, and is around 50% faster than the python one. I haven't written much documentation for it yet, though, so as more people give their feedback, I'll update it.

JamesC01 avatar Aug 01 '22 07:08 JamesC01