Skip to content
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

invlerp and remap implementation #2654

Merged
merged 37 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
bilhox marked this conversation as resolved.
Show resolved Hide resolved
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`
bilhox marked this conversation as resolved.
Show resolved Hide resolved

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``.
bilhox marked this conversation as resolved.
Show resolved Hide resolved

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
89 changes: 89 additions & 0 deletions src_c/math.c
bilhox marked this conversation as resolved.
Show resolved Hide resolved
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;
}
bilhox marked this conversation as resolved.
Show resolved Hide resolved

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,81 @@ math_clamp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
return value;
}

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];
PyObject *max = args[1];
PyObject *value = args[2];

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improvement suggestion: instead of checking this explicitly, we can instead call PyFloat_AsDouble directly, and then handle the error on a per-argument basis? That we can also tell which of the arguments is incorrect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So If I understood, I should write something like this ? :

double v = PyFloat_AsDouble(value);
if (PyErr_Occurred())
    return RAISE(PyExc_ValueError,
                 "invalid `arg` values passed to remap, the value might "
                 "be too small or too big");
double a = PyFloat_AsDouble(i_min);
// same here
double b = PyFloat_AsDouble(i_max);
// same here
double c = PyFloat_AsDouble(o_min);
// same here
double d = PyFloat_AsDouble(o_max);
// same here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we could do a simple return NULL as the error is already set. But the error won't be really specific


double t = PyFloat_AsDouble(value);
bilhox marked this conversation as resolved.
Show resolved Hide resolved

if (t < 0)
t = 0.0;
else if (t > 1)
t = 1.0;
bilhox marked this conversation as resolved.
Show resolved Hide resolved

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");
bilhox marked this conversation as resolved.
Show resolved Hide resolved

if (b - a == 0)
return RAISE(PyExc_ZeroDivisionError,
"the result of b - a needs to be different from zero");
bilhox marked this conversation as resolved.
Show resolved Hide resolved

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);
bilhox marked this conversation as resolved.
Show resolved Hide resolved
double a = PyFloat_AsDouble(i_min);
double b = PyFloat_AsDouble(i_max);
double c = PyFloat_AsDouble(o_min);
double d = PyFloat_AsDouble(o_max);
bilhox marked this conversation as resolved.
Show resolved Hide resolved

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_ZeroDivisionError,
"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 +4430,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, 0.5), 0.05)

MyreMylar marked this conversation as resolved.
Show resolved Hide resolved
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.55)
MyreMylar marked this conversation as resolved.
Show resolved Hide resolved

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(ZeroDivisionError):
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"
ankith26 marked this conversation as resolved.
Show resolved Hide resolved
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(ZeroDivisionError):
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
Loading