Skip to content

invlerp and remap implementation #2654

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1975b07
invlerp & remap impl
bilhox Jan 4, 2024
3e6fe92
remove test code
bilhox Jan 4, 2024
4f9ca04
potential fix
bilhox Jan 4, 2024
1b196b8
clang fix
bilhox Jan 4, 2024
7e20fb8
docs
bilhox Jan 4, 2024
66c817f
docs fix
bilhox Jan 4, 2024
cf51bf6
docs fix 2
bilhox Jan 4, 2024
a291038
stub fix
bilhox Jan 4, 2024
f5524ef
stub fix 2
bilhox Jan 4, 2024
f557983
revert lerp & overflow prevention
bilhox Jan 4, 2024
5f41e95
unittest & stub fix & error message
bilhox Jan 6, 2024
0f552cd
test removal
bilhox Jan 6, 2024
3f83b7f
format fix
bilhox Jan 6, 2024
d2b053b
period
bilhox Jan 6, 2024
969b2b0
period
bilhox Jan 6, 2024
3868b04
space moments
bilhox Jan 6, 2024
386ab80
Merge branch 'pygame-community:main' into bigwhoops_impl
bilhox Jan 20, 2024
99d0682
Improve inverse lerp tests
MyreMylar Feb 11, 2024
93aa1a9
Correcting test
MyreMylar Feb 11, 2024
43dc68f
Improve test.
MyreMylar Feb 11, 2024
849b739
Merge branch 'pygame-community:main' into bigwhoops_impl
bilhox Feb 11, 2024
8960d02
uncap invlerp value
bilhox Feb 11, 2024
a542c8f
update test
bilhox Feb 11, 2024
3078c72
Merge branch 'pygame-community:main' into bigwhoops_impl
bilhox Feb 25, 2024
dad6326
Merge branch 'pygame-community:main' into bigwhoops_impl
bilhox May 25, 2024
5bcdf35
temp changes
bilhox May 25, 2024
1ce7019
Merge branch 'bigwhoops_impl' of https://github.com/bilhox/pygame-ce …
bilhox May 25, 2024
cbf66ef
separate arg error part 1
bilhox May 26, 2024
bbcdeef
fix numcheck macro
MyreMylar May 30, 2024
64461ed
Apply suggestions from code review
MyreMylar May 30, 2024
a87b274
formatting
MyreMylar May 30, 2024
a27e44d
fix missing semi-colon and test assert type
MyreMylar May 30, 2024
fce1693
small fix
bilhox Jun 1, 2024
ff7dc3d
Merge branch 'bigwhoops_impl' of https://github.com/bilhox/pygame-ce …
bilhox Jun 1, 2024
85e64e3
last fixes
bilhox Jun 1, 2024
964fdbd
test fix + removed useless code
bilhox Jun 1, 2024
9d483ea
match errors to var names in signature (docs and type hints)
MyreMylar Jun 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/math.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ class Vector3(_GenericVector):


def lerp(a: float, b: float, weight: float, do_clamp: bool = True, /) -> float: ...
def invlerp(a: float, b: float, weight: float, /) -> float: ...
def remap(i_min: float, i_max: float, o_min: float, o_max: float, value: float, /) -> float: ...
def smoothstep(a: float, b: float, weight: float, /) -> float: ...


Expand Down
40 changes: 40 additions & 0 deletions docs/reST/ref/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ Multiple coordinates can be set using slices or swizzling

.. ## math.lerp ##

.. function:: invlerp

| :sl:`returns value inverse interpolated between a and b`
| :sg:`invlerp(a, b, value, /) -> float`

Returns a number which is an inverse interpolation between ``a``
and ``b``. The third parameter determines how far between ``a`` and
``b`` the result is going to be.
If ``b - a`` is equal to 0, it raises a ``ZeroDivisionError``.

The formula is:

``(v - a)/(b - a)``.

.. versionadded:: 2.5.0

.. ## math.invlerp ##

.. function:: smoothstep

| :sl:`returns value smoothly interpolated between a and b.`
Expand All @@ -102,6 +120,28 @@ Multiple coordinates can be set using slices or swizzling

.. ## math.smoothstep ##

.. function:: remap

| :sl:`remaps value from i_range to o_range`
| :sg:`remap(i_min, i_max, o_min, o_max, value, /) -> float`

Returns a number which is the value remapped from ``i_range`` to
``o_range``.
If ``i_max - i_min`` is equal to 0, it raises a ``ZeroDivisionError``.

Example:

.. code-block:: python

> value = 50
> pygame.math.remap(0, 100, 0, 200, value)
> 100.0


.. versionadded:: 2.5.0

.. ## math.remap ##

.. class:: Vector2

| :sl:`a 2-Dimensional Vector`
Expand Down
2 changes: 2 additions & 0 deletions src_c/doc/math_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
#define DOC_MATH "pygame module for vector classes"
#define DOC_MATH_CLAMP "clamp(value, min, max, /) -> float\nreturns value clamped to min and max."
#define DOC_MATH_LERP "lerp(a, b, value, do_clamp=True, /) -> float\nreturns value linearly interpolated between a and b"
#define DOC_MATH_INVLERP "invlerp(a, b, value, /) -> float\nreturns value inverse interpolated between a and b"
#define DOC_MATH_SMOOTHSTEP "smoothstep(a, b, value, /) -> float\nreturns value smoothly interpolated between a and b."
#define DOC_MATH_REMAP "remap(i_min, i_max, o_min, o_max, value, /) -> float\nremaps value from i_range to o_range"
#define DOC_MATH_VECTOR2 "Vector2() -> Vector2(0, 0)\nVector2(int) -> Vector2\nVector2(float) -> Vector2\nVector2(Vector2) -> Vector2\nVector2(x, y) -> Vector2\nVector2((x, y)) -> Vector2\na 2-Dimensional Vector"
#define DOC_MATH_VECTOR2_DOT "dot(Vector2, /) -> float\ncalculates the dot- or scalar-product with the other vector"
#define DOC_MATH_VECTOR2_CROSS "cross(Vector2, /) -> float\ncalculates the cross- or vector-product"
Expand Down
95 changes: 95 additions & 0 deletions src_c/math.c
Original file line number Diff line number Diff line change
Expand Up @@ -4192,6 +4192,18 @@ vector_elementwise(pgVector *vec, PyObject *_null)
return (PyObject *)proxy;
}

inline double
lerp(double a, double b, double v)
{
return a + (b - a) * v;
}

inline double
invlerp(double a, double b, double v)
{
return (v - a) / (b - a);
}

static PyObject *
math_clamp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
Expand Down Expand Up @@ -4233,6 +4245,87 @@ math_clamp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
return value;
}

#define RAISE_ARG_TYPE_ERROR(py_num_obj, var) \
if (!PyNumber_Check(py_num_obj)) { \
return RAISE(PyExc_TypeError, \
"The argument '" var "' must be a real number"); \
}

static PyObject *
math_invlerp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
if (nargs != 3)
return RAISE(PyExc_TypeError,
"invlerp requires exactly 3 numeric arguments");

PyObject *min = args[0];
RAISE_ARG_TYPE_ERROR(min, "min")
PyObject *max = args[1];
RAISE_ARG_TYPE_ERROR(max, "max")
PyObject *value = args[2];
RAISE_ARG_TYPE_ERROR(value, "value")

// if (!PyNumber_Check(min) || !PyNumber_Check(max) ||
// !PyNumber_Check(value))
// return RAISE(PyExc_TypeError,
// "invlerp requires all the arguments to be numbers");

double t = PyFloat_AsDouble(value);
double a = PyFloat_AsDouble(min);
double b = PyFloat_AsDouble(max);

if (PyErr_Occurred())
return RAISE(PyExc_ValueError,
"invalid argument values passed to invlerp, numbers "
"might be too small or too big");

if (b - a == 0)
return RAISE(PyExc_ValueError,
"the result of b - a needs to be different from zero");

return PyFloat_FromDouble(invlerp(a, b, t));
}

#

static PyObject *
math_remap(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
if (nargs != 5)
return RAISE(PyExc_TypeError,
"remap requires exactly 5 numeric arguments");

PyObject *i_min = args[0];
PyObject *i_max = args[1];
PyObject *o_min = args[2];
PyObject *o_max = args[3];
PyObject *value = args[4];

if (!PyNumber_Check(value) || !PyNumber_Check(i_min) ||
!PyNumber_Check(i_max) || !PyNumber_Check(o_min) ||
!PyNumber_Check(o_max))
return RAISE(PyExc_TypeError,
"remap requires all the arguments to be numbers");

double v = PyFloat_AsDouble(value);
double a = PyFloat_AsDouble(i_min);
double b = PyFloat_AsDouble(i_max);
double c = PyFloat_AsDouble(o_min);
double d = PyFloat_AsDouble(o_max);

if (PyErr_Occurred())
return RAISE(PyExc_ValueError,
"invalid argument values passed to remap, numbers might "
"be too small or too big");

if (b - a == 0)
return RAISE(
PyExc_ValueError,
"the result of i_max - i_min needs to be different from zero");

return PyFloat_FromDouble(lerp(c, d, invlerp(a, b, v)));
}

static PyObject *
math_lerp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
Expand Down Expand Up @@ -4343,6 +4436,8 @@ math_disable_swizzling(pgVector *self, PyObject *_null)
static PyMethodDef _math_methods[] = {
{"clamp", (PyCFunction)math_clamp, METH_FASTCALL, DOC_MATH_CLAMP},
{"lerp", (PyCFunction)math_lerp, METH_FASTCALL, DOC_MATH_LERP},
{"invlerp", (PyCFunction)math_invlerp, METH_FASTCALL, DOC_MATH_INVLERP},
{"remap", (PyCFunction)math_remap, METH_FASTCALL, DOC_MATH_REMAP},
{"smoothstep", (PyCFunction)math_smoothstep, METH_FASTCALL,
DOC_MATH_SMOOTHSTEP},
{"enable_swizzling", (PyCFunction)math_enable_swizzling, METH_NOARGS,
Expand Down
101 changes: 101 additions & 0 deletions test/math_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,107 @@ def test_lerp(self):
b = 2
pygame.math.lerp(a, b, Vector2(0, 0))

def test_invlerp(self):
a = 0.0
b = 10.0
self.assertEqual(pygame.math.invlerp(a, b, 5.0), 0.5)

a = 0.0
b = 10.0
self.assertEqual(pygame.math.invlerp(a, b, 0.1), 0.01)

a = -10.0
b = 10.0
self.assertEqual(pygame.math.invlerp(a, b, 0.5), 0.525)

a = -10.0
b = 10.0
self.assertEqual(pygame.math.invlerp(a, b, 1.5), 0.575)

a = 0.0
b = 100.0
self.assertEqual(pygame.math.invlerp(a, b, 0.25), 0.0025)

with self.assertRaises(TypeError):
a = Vector2(0, 0)
b = Vector2(10.0, 10.0)
pygame.math.invlerp(a, b, 0.5)

with self.assertRaises(TypeError):
a = 1
b = 2
pygame.math.invlerp(a, b, Vector2(0, 0))

with self.assertRaises(ValueError):
a = 5
b = 5
pygame.math.invlerp(a, b, 5)

with self.assertRaises(ValueError):
a = 12**300
b = 11**30
pygame.math.invlerp(a, b, 1)

def test_remap(self):
a = 0.0
b = 10.0
c = 0.0
d = 100.0
self.assertEqual(pygame.math.remap(a, b, c, d, 1.0), 10.0)

a = 0.0
b = 10.0
c = 0.0
d = 100.0
self.assertEqual(pygame.math.remap(a, b, c, d, -1.0), -10.0)

a = -10.0
b = 10.0
c = -20.0
d = 20.0
self.assertEqual(pygame.math.remap(a, b, c, d, 0.0), 0.0)

a = -10.0
b = 10.0
c = 10.0
d = 110.0
self.assertEqual(pygame.math.remap(a, b, c, d, -8.0), 20.0)

with self.assertRaises(TypeError):
a = Vector2(0, 0)
b = "fish"
c = "durk"
d = Vector2(100, 100)
pygame.math.remap(a, b, c, d, 10)

with self.assertRaises(TypeError):
a = 1
b = 2
c = 10
d = 20
pygame.math.remap(a, b, c, d, Vector2(0, 0))

with self.assertRaises(ValueError):
a = 5
b = 5
c = 0
d = 100
pygame.math.remap(a, b, c, d, 10)

with self.assertRaises(ValueError):
a = 12**300
b = 11**30
c = 20
d = 30
pygame.math.remap(a, b, c, d, 100 * 50)

with self.assertRaises(ValueError):
a = 12j
b = 11j
c = 10j
d = 9j
pygame.math.remap(a, b, c, d, 50j)

def test_smoothstep(self):
a = 0.0
b = 10.0
Expand Down