Skip to content

Commit 19a17c9

Browse files
bilhoxMyreMylarankith26
authored
invlerp and remap implementation (#2654)
* invlerp & remap impl --------- Co-authored-by: Dan Lawrence <[email protected]> Co-authored-by: Ankith <[email protected]>
1 parent 92ee77a commit 19a17c9

File tree

5 files changed

+238
-0
lines changed

5 files changed

+238
-0
lines changed

buildconfig/stubs/pygame/math.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ class Vector3(_GenericVector):
334334

335335

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

339341

docs/reST/ref/math.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,37 @@ Multiple coordinates can be set using slices or swizzling
7979

8080
.. ## math.lerp ##
8181
82+
.. function:: invlerp
83+
84+
| :sl:`returns value inverse interpolated between a and b`
85+
| :sg:`invlerp(a, b, value, /) -> float`
86+
87+
Returns a number which is an inverse interpolation between ``a``
88+
and ``b``. The third parameter ``value`` is the result of the linear interpolation
89+
between a and b with a certain coefficient. In other words, this coefficient
90+
will be the result of this function.
91+
If ``b - a`` is equal to 0, it raises a ``ZeroDivisionError``.
92+
93+
The formula is:
94+
95+
``(v - a)/(b - a)``.
96+
97+
This is an example explaining what is above :
98+
99+
.. code-block:: python
100+
101+
> a = 10
102+
> b = 20
103+
> pygame.math.invlerp(10, 20, 11.5)
104+
> 0.15
105+
> pygame.math.lerp(10, 20, 0.15)
106+
> 11.5
107+
108+
109+
.. versionadded:: 2.5.0
110+
111+
.. ## math.invlerp ##
112+
82113
.. function:: smoothstep
83114

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

103134
.. ## math.smoothstep ##
104135
136+
.. function:: remap
137+
138+
| :sl:`remaps value from i_range to o_range`
139+
| :sg:`remap(i_min, i_max, o_min, o_max, value, /) -> float`
140+
141+
Returns a number which is the value remapped from ``i_range`` to
142+
``o_range``.
143+
If ``i_max - i_min`` is equal to 0, it raises a ``ZeroDivisionError``.
144+
145+
Example:
146+
147+
.. code-block:: python
148+
149+
> value = 50
150+
> pygame.math.remap(0, 100, 0, 200, value)
151+
> 100.0
152+
153+
154+
.. versionadded:: 2.5.0
155+
156+
.. ## math.remap ##
157+
105158
.. class:: Vector2
106159

107160
| :sl:`a 2-Dimensional Vector`

src_c/doc/math_doc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
#define DOC_MATH "pygame module for vector classes"
33
#define DOC_MATH_CLAMP "clamp(value, min, max, /) -> float\nreturns value clamped to min and max."
44
#define DOC_MATH_LERP "lerp(a, b, value, do_clamp=True, /) -> float\nreturns value linearly interpolated between a and b"
5+
#define DOC_MATH_INVLERP "invlerp(a, b, value, /) -> float\nreturns value inverse interpolated between a and b"
56
#define DOC_MATH_SMOOTHSTEP "smoothstep(a, b, value, /) -> float\nreturns value smoothly interpolated between a and b."
7+
#define DOC_MATH_REMAP "remap(i_min, i_max, o_min, o_max, value, /) -> float\nremaps value from i_range to o_range"
68
#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"
79
#define DOC_MATH_VECTOR2_DOT "dot(Vector2, /) -> float\ncalculates the dot- or scalar-product with the other vector"
810
#define DOC_MATH_VECTOR2_CROSS "cross(Vector2, /) -> float\ncalculates the cross- or vector-product"

src_c/math.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4192,6 +4192,18 @@ vector_elementwise(pgVector *vec, PyObject *_null)
41924192
return (PyObject *)proxy;
41934193
}
41944194

4195+
inline double
4196+
lerp(double a, double b, double v)
4197+
{
4198+
return a + (b - a) * v;
4199+
}
4200+
4201+
inline double
4202+
invlerp(double a, double b, double v)
4203+
{
4204+
return (v - a) / (b - a);
4205+
}
4206+
41954207
static PyObject *
41964208
math_clamp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
41974209
{
@@ -4233,6 +4245,72 @@ math_clamp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
42334245
return value;
42344246
}
42354247

4248+
#define RAISE_ARG_TYPE_ERROR(var) \
4249+
if (PyErr_Occurred()) { \
4250+
return RAISE(PyExc_TypeError, \
4251+
"The argument '" var "' must be a real number"); \
4252+
}
4253+
4254+
static PyObject *
4255+
math_invlerp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
4256+
{
4257+
if (nargs != 3)
4258+
return RAISE(PyExc_TypeError,
4259+
"invlerp requires exactly 3 numeric arguments");
4260+
4261+
double a = PyFloat_AsDouble(args[0]);
4262+
RAISE_ARG_TYPE_ERROR("a")
4263+
double b = PyFloat_AsDouble(args[1]);
4264+
RAISE_ARG_TYPE_ERROR("b")
4265+
double t = PyFloat_AsDouble(args[2]);
4266+
RAISE_ARG_TYPE_ERROR("value")
4267+
4268+
if (PyErr_Occurred())
4269+
return RAISE(PyExc_ValueError,
4270+
"invalid argument values passed to invlerp, numbers "
4271+
"might be too small or too big");
4272+
4273+
if (b - a == 0)
4274+
return RAISE(PyExc_ValueError,
4275+
"the result of b - a needs to be different from zero");
4276+
4277+
return PyFloat_FromDouble(invlerp(a, b, t));
4278+
}
4279+
4280+
#
4281+
4282+
static PyObject *
4283+
math_remap(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
4284+
{
4285+
if (nargs != 5)
4286+
return RAISE(PyExc_TypeError,
4287+
"remap requires exactly 5 numeric arguments");
4288+
4289+
PyObject *i_min = args[0];
4290+
PyObject *i_max = args[1];
4291+
PyObject *o_min = args[2];
4292+
PyObject *o_max = args[3];
4293+
PyObject *value = args[4];
4294+
4295+
double v = PyFloat_AsDouble(value);
4296+
RAISE_ARG_TYPE_ERROR("value")
4297+
double a = PyFloat_AsDouble(i_min);
4298+
RAISE_ARG_TYPE_ERROR("i_min")
4299+
double b = PyFloat_AsDouble(i_max);
4300+
RAISE_ARG_TYPE_ERROR("i_max")
4301+
double c = PyFloat_AsDouble(o_min);
4302+
RAISE_ARG_TYPE_ERROR("o_min")
4303+
double d = PyFloat_AsDouble(o_max);
4304+
RAISE_ARG_TYPE_ERROR("o_max")
4305+
4306+
if (b - a == 0)
4307+
return RAISE(
4308+
PyExc_ValueError,
4309+
"the result of i_max - i_min needs to be different from zero");
4310+
4311+
return PyFloat_FromDouble(lerp(c, d, invlerp(a, b, v)));
4312+
}
4313+
42364314
static PyObject *
42374315
math_lerp(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
42384316
{
@@ -4343,6 +4421,8 @@ math_disable_swizzling(pgVector *self, PyObject *_null)
43434421
static PyMethodDef _math_methods[] = {
43444422
{"clamp", (PyCFunction)math_clamp, METH_FASTCALL, DOC_MATH_CLAMP},
43454423
{"lerp", (PyCFunction)math_lerp, METH_FASTCALL, DOC_MATH_LERP},
4424+
{"invlerp", (PyCFunction)math_invlerp, METH_FASTCALL, DOC_MATH_INVLERP},
4425+
{"remap", (PyCFunction)math_remap, METH_FASTCALL, DOC_MATH_REMAP},
43464426
{"smoothstep", (PyCFunction)math_smoothstep, METH_FASTCALL,
43474427
DOC_MATH_SMOOTHSTEP},
43484428
{"enable_swizzling", (PyCFunction)math_enable_swizzling, METH_NOARGS,

test/math_test.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,107 @@ def test_lerp(self):
8686
b = 2
8787
pygame.math.lerp(a, b, Vector2(0, 0))
8888

89+
def test_invlerp(self):
90+
a = 0.0
91+
b = 10.0
92+
self.assertEqual(pygame.math.invlerp(a, b, 5.0), 0.5)
93+
94+
a = 0.0
95+
b = 10.0
96+
self.assertEqual(pygame.math.invlerp(a, b, 0.1), 0.01)
97+
98+
a = -10.0
99+
b = 10.0
100+
self.assertEqual(pygame.math.invlerp(a, b, 0.5), 0.525)
101+
102+
a = -10.0
103+
b = 10.0
104+
self.assertEqual(pygame.math.invlerp(a, b, 1.5), 0.575)
105+
106+
a = 0.0
107+
b = 100.0
108+
self.assertEqual(pygame.math.invlerp(a, b, 0.25), 0.0025)
109+
110+
with self.assertRaises(TypeError):
111+
a = Vector2(0, 0)
112+
b = Vector2(10.0, 10.0)
113+
pygame.math.invlerp(a, b, 0.5)
114+
115+
with self.assertRaises(TypeError):
116+
a = 1
117+
b = 2
118+
pygame.math.invlerp(a, b, Vector2(0, 0))
119+
120+
with self.assertRaises(ValueError):
121+
a = 5
122+
b = 5
123+
pygame.math.invlerp(a, b, 5)
124+
125+
with self.assertRaises(TypeError):
126+
a = 12**300
127+
b = 11**30
128+
pygame.math.invlerp(a, b, 1)
129+
130+
def test_remap(self):
131+
a = 0.0
132+
b = 10.0
133+
c = 0.0
134+
d = 100.0
135+
self.assertEqual(pygame.math.remap(a, b, c, d, 1.0), 10.0)
136+
137+
a = 0.0
138+
b = 10.0
139+
c = 0.0
140+
d = 100.0
141+
self.assertEqual(pygame.math.remap(a, b, c, d, -1.0), -10.0)
142+
143+
a = -10.0
144+
b = 10.0
145+
c = -20.0
146+
d = 20.0
147+
self.assertEqual(pygame.math.remap(a, b, c, d, 0.0), 0.0)
148+
149+
a = -10.0
150+
b = 10.0
151+
c = 10.0
152+
d = 110.0
153+
self.assertEqual(pygame.math.remap(a, b, c, d, -8.0), 20.0)
154+
155+
with self.assertRaises(TypeError):
156+
a = Vector2(0, 0)
157+
b = "fish"
158+
c = "durk"
159+
d = Vector2(100, 100)
160+
pygame.math.remap(a, b, c, d, 10)
161+
162+
with self.assertRaises(TypeError):
163+
a = 1
164+
b = 2
165+
c = 10
166+
d = 20
167+
pygame.math.remap(a, b, c, d, Vector2(0, 0))
168+
169+
with self.assertRaises(ValueError):
170+
a = 5
171+
b = 5
172+
c = 0
173+
d = 100
174+
pygame.math.remap(a, b, c, d, 10)
175+
176+
with self.assertRaises(TypeError):
177+
a = 12**300
178+
b = 11**30
179+
c = 20
180+
d = 30
181+
pygame.math.remap(a, b, c, d, 100 * 50)
182+
183+
with self.assertRaises(TypeError):
184+
a = 12j
185+
b = 11j
186+
c = 10j
187+
d = 9j
188+
pygame.math.remap(a, b, c, d, 50j)
189+
89190
def test_smoothstep(self):
90191
a = 0.0
91192
b = 10.0

0 commit comments

Comments
 (0)