From 3f5559a42a0d2e2dbd387cc5cfde1e0d5295f93f Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 07:42:11 -0400 Subject: [PATCH 01/17] Add Lerpable protocol --- arcade/types/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arcade/types/__init__.py b/arcade/types/__init__.py index c890a525b..dd5b88f72 100644 --- a/arcade/types/__init__.py +++ b/arcade/types/__init__.py @@ -26,7 +26,8 @@ # flake8: noqa: E402 import sys from pathlib import Path -from typing import NamedTuple, Union, TYPE_CHECKING, TypeVar, Iterable +from typing import NamedTuple, Union, TYPE_CHECKING, TypeVar, Iterable, Protocol +from typing_extensions import Self from pytiled_parser import Properties @@ -124,6 +125,7 @@ "Box", "LRBTNF", "XYZWHD", + "Lerpable", "RGB", "RGBA", "RGBOrA", @@ -206,6 +208,13 @@ def annotated2(argument: OneOrIterableOf[MyType] | None = tuple()): # --- End potentially obsolete annotations --- +class Lerpable(Protocol): + """Matches types which work with :py:func:`arcade.math.lerp`.""" + def __add__(self, other) -> Self: ... + def __sub__(self, other) -> Self: ... + def __mul__(self, other) -> Self: ... + + # Path handling PathLike = Union[str, Path, bytes] _POr = TypeVar("_POr") # Allows PathOr[TypeNameHere] syntax From ad8330672a14cb542a20115cd314642448a6d3d9 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:00:28 -0400 Subject: [PATCH 02/17] Add support for arbitary lerpable arguments to arcade.math.lerp --- arcade/math.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index 31e8390c1..5544757f4 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -2,11 +2,11 @@ import math import random -from typing import Sequence, Union +from typing import Sequence, Union, TypeVar from pyglet.math import Vec2 -from arcade.types import AsFloat, Point, Point2 +from arcade.types import AsFloat, Point, Point2, Lerpable from arcade.types.rect import Rect from arcade.types.vector_like import Point3 @@ -35,6 +35,7 @@ ] + def clamp(a, low: float, high: float) -> float: """Clamp a number between a range. @@ -50,13 +51,16 @@ def clamp(a, low: float, high: float) -> float: V_3D = Union[tuple[AsFloat, AsFloat, AsFloat], Sequence[AsFloat]] -def lerp(v1: AsFloat, v2: AsFloat, u: float) -> float: +L = TypeVar('L', bound=Lerpable) + + +def lerp(v1: L, v2: L, u: float) -> L: """linearly interpolate between two values Args: - v1 (float): The first value - v2 (float): The second value - u (float): The interpolation value `(0.0 to 1.0)` + v1: The first value + v2: The second value + u: The interpolation value `(0.0 to 1.0)` """ return v1 + ((v2 - v1) * u) @@ -85,6 +89,7 @@ def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u), lerp(v1[2], v2[2], u)) + def lerp_angle(start_angle: float, end_angle: float, u: float) -> float: """ Linearly interpolate between two angles in degrees, From e40a1679ddfde6227634262d2b5cc41c70f471f8 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:42:51 -0400 Subject: [PATCH 03/17] Docstring cleanup --- arcade/math.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index 5544757f4..4d5abb4e0 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -35,7 +35,6 @@ ] - def clamp(a, low: float, high: float) -> float: """Clamp a number between a range. @@ -55,16 +54,25 @@ def clamp(a, low: float, high: float) -> float: def lerp(v1: L, v2: L, u: float) -> L: - """linearly interpolate between two values + """Linearly interpolate two :py:class:`~arcade.types.Lerpable` values. + + .. tip:: For angles, use :py:func:`lerp_angle`. + + The values must be of compatible types: + + * Two scalar values such as :py:class:`float` + * A two :py:mod:`pyglet.math` vectors of the same size + * A custom :py:class:`Lerpable` object Args: - v1: The first value - v2: The second value + v1 (Lerpable): The first value + v2 (Lerpable): The second value u: The interpolation value `(0.0 to 1.0)` """ return v1 + ((v2 - v1) * u) + def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> tuple[float, float]: """ Linearly interpolate between two 2D points. @@ -89,7 +97,6 @@ def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u), lerp(v1[2], v2[2], u)) - def lerp_angle(start_angle: float, end_angle: float, u: float) -> float: """ Linearly interpolate between two angles in degrees, From f020977786d94a99297ed126ff74d242799d2a14 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:43:12 -0400 Subject: [PATCH 04/17] Formatting --- arcade/math.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index 4d5abb4e0..d7781a002 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -2,11 +2,11 @@ import math import random -from typing import Sequence, Union, TypeVar +from typing import Sequence, TypeVar, Union from pyglet.math import Vec2 -from arcade.types import AsFloat, Point, Point2, Lerpable +from arcade.types import AsFloat, Lerpable, Point, Point2 from arcade.types.rect import Rect from arcade.types.vector_like import Point3 @@ -50,7 +50,7 @@ def clamp(a, low: float, high: float) -> float: V_3D = Union[tuple[AsFloat, AsFloat, AsFloat], Sequence[AsFloat]] -L = TypeVar('L', bound=Lerpable) +L = TypeVar("L", bound=Lerpable) def lerp(v1: L, v2: L, u: float) -> L: @@ -72,7 +72,6 @@ def lerp(v1: L, v2: L, u: float) -> L: return v1 + ((v2 - v1) * u) - def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> tuple[float, float]: """ Linearly interpolate between two 2D points. From e35f00a77168ef649c85b1f582ebdde22496014f Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:01:25 -0400 Subject: [PATCH 05/17] Zero pyright errors on lerpables? --- arcade/math.py | 11 +++++++++-- arcade/types/__init__.py | 13 +++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index d7781a002..256b26c8b 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -81,7 +81,10 @@ def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> tuple[float, float]: v2 (tuple[float, float]): The second point u (float): The interpolation value `(0.0 to 1.0)` """ - return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u)) + return ( + v1[0] + ((v2[0] - v1[0]) * u), + v1[1] + ((v2[1] - v1[1]) * u), + ) def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: @@ -93,7 +96,11 @@ def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: v2 (tuple[float, float, float]): The second point u (float): The interpolation value `(0.0 to 1.0)` """ - return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u), lerp(v1[2], v2[2], u)) + return ( + v1[0] + ((v2[0] - v1[0]) * u), + v1[1] + ((v2[1] - v1[1]) * u), + v1[2] + ((v2[2] - v1[2]) * u), + ) def lerp_angle(start_angle: float, end_angle: float, u: float) -> float: diff --git a/arcade/types/__init__.py b/arcade/types/__init__.py index dd5b88f72..0c9b06faf 100644 --- a/arcade/types/__init__.py +++ b/arcade/types/__init__.py @@ -208,11 +208,16 @@ def annotated2(argument: OneOrIterableOf[MyType] | None = tuple()): # --- End potentially obsolete annotations --- -class Lerpable(Protocol): +_T_contra = TypeVar("_T_contra", contravariant=True) +_R_co = TypeVar("_R_co", covariant=True) + + +class Lerpable(Protocol[_T_contra, _R_co]): """Matches types which work with :py:func:`arcade.math.lerp`.""" - def __add__(self, other) -> Self: ... - def __sub__(self, other) -> Self: ... - def __mul__(self, other) -> Self: ... + + def __add__(self, value: _T_contra, /) -> _R_co: ... + def __sub__(self, value: _T_contra, /) -> _R_co: ... + def __mul__(self, value: _T_contra, /) -> _R_co: ... # Path handling From 774c7de9473cf4d07154452958a6a594bbc0ae6b Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:03:23 -0400 Subject: [PATCH 06/17] More formatting --- arcade/math.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index 256b26c8b..de71c96e0 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -84,7 +84,7 @@ def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> tuple[float, float]: return ( v1[0] + ((v2[0] - v1[0]) * u), v1[1] + ((v2[1] - v1[1]) * u), - ) + ) def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: @@ -96,7 +96,7 @@ def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: v2 (tuple[float, float, float]): The second point u (float): The interpolation value `(0.0 to 1.0)` """ - return ( + return ( v1[0] + ((v2[0] - v1[0]) * u), v1[1] + ((v2[1] - v1[1]) * u), v1[2] + ((v2[2] - v1[2]) * u), From b3d01e77d516f6ae073e82f70e43a423c88cd864 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:07:31 -0400 Subject: [PATCH 07/17] Explain why the Lerpable protocol is so specific --- arcade/types/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arcade/types/__init__.py b/arcade/types/__init__.py index 0c9b06faf..5aa1d0e58 100644 --- a/arcade/types/__init__.py +++ b/arcade/types/__init__.py @@ -215,6 +215,9 @@ def annotated2(argument: OneOrIterableOf[MyType] | None = tuple()): class Lerpable(Protocol[_T_contra, _R_co]): """Matches types which work with :py:func:`arcade.math.lerp`.""" + # The / is necessary for pyright to be happy since float's + # implementations of these functions are positional only. + # See https://peps.python.org/pep-0570/ def __add__(self, value: _T_contra, /) -> _R_co: ... def __sub__(self, value: _T_contra, /) -> _R_co: ... def __mul__(self, value: _T_contra, /) -> _R_co: ... From 4f7e844ea4b0ff4c97928c7a101ce9db21ffaa59 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:11:10 -0400 Subject: [PATCH 08/17] Revert inlining --- arcade/math.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index de71c96e0..2b72a8ae8 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -81,10 +81,7 @@ def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> tuple[float, float]: v2 (tuple[float, float]): The second point u (float): The interpolation value `(0.0 to 1.0)` """ - return ( - v1[0] + ((v2[0] - v1[0]) * u), - v1[1] + ((v2[1] - v1[1]) * u), - ) + return (lerp(v1[0], v2[0], u), lerp(v2[1], v2[1], u)) def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: @@ -96,11 +93,7 @@ def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: v2 (tuple[float, float, float]): The second point u (float): The interpolation value `(0.0 to 1.0)` """ - return ( - v1[0] + ((v2[0] - v1[0]) * u), - v1[1] + ((v2[1] - v1[1]) * u), - v1[2] + ((v2[2] - v1[2]) * u), - ) + return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u), lerp(v1[2], v2[2], u)) def lerp_angle(start_angle: float, end_angle: float, u: float) -> float: From bb2410a7452259a6347370b5cf8d2eee0b2516d4 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:14:33 -0400 Subject: [PATCH 09/17] Revert typo --- arcade/math.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/math.py b/arcade/math.py index 2b72a8ae8..d7781a002 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -81,7 +81,7 @@ def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> tuple[float, float]: v2 (tuple[float, float]): The second point u (float): The interpolation value `(0.0 to 1.0)` """ - return (lerp(v1[0], v2[0], u), lerp(v2[1], v2[1], u)) + return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u)) def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: From 2bdcc29725ca2994cc431a278846c8a285425198 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:19:49 -0400 Subject: [PATCH 10/17] Remove unused import --- arcade/types/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/arcade/types/__init__.py b/arcade/types/__init__.py index 5aa1d0e58..85b0b08bb 100644 --- a/arcade/types/__init__.py +++ b/arcade/types/__init__.py @@ -27,7 +27,6 @@ import sys from pathlib import Path from typing import NamedTuple, Union, TYPE_CHECKING, TypeVar, Iterable, Protocol -from typing_extensions import Self from pytiled_parser import Properties From c6a7d691dc170574a0391d4d210ba1b4a88334b4 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:24:54 -0400 Subject: [PATCH 11/17] Rephrase comment --- arcade/types/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arcade/types/__init__.py b/arcade/types/__init__.py index 85b0b08bb..4186f5d13 100644 --- a/arcade/types/__init__.py +++ b/arcade/types/__init__.py @@ -214,8 +214,8 @@ def annotated2(argument: OneOrIterableOf[MyType] | None = tuple()): class Lerpable(Protocol[_T_contra, _R_co]): """Matches types which work with :py:func:`arcade.math.lerp`.""" - # The / is necessary for pyright to be happy since float's - # implementations of these functions are positional only. + # The / matches float and similar operations to keep pyright + # happy since built-in arithmetic makes them positional only. # See https://peps.python.org/pep-0570/ def __add__(self, value: _T_contra, /) -> _R_co: ... def __sub__(self, value: _T_contra, /) -> _R_co: ... From a3699ea3528c7beda638a13ecc27a66c9d9c101a Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:47:02 -0400 Subject: [PATCH 12/17] Rename Lerpable to HasAddSubMul --- arcade/math.py | 8 ++++---- arcade/types/__init__.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index d7781a002..aeacf3314 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -6,7 +6,7 @@ from pyglet.math import Vec2 -from arcade.types import AsFloat, Lerpable, Point, Point2 +from arcade.types import AsFloat, HasAddSubMul, Point, Point2 from arcade.types.rect import Rect from arcade.types.vector_like import Point3 @@ -50,7 +50,7 @@ def clamp(a, low: float, high: float) -> float: V_3D = Union[tuple[AsFloat, AsFloat, AsFloat], Sequence[AsFloat]] -L = TypeVar("L", bound=Lerpable) +L = TypeVar("L", bound=HasAddSubMul) def lerp(v1: L, v2: L, u: float) -> L: @@ -65,8 +65,8 @@ def lerp(v1: L, v2: L, u: float) -> L: * A custom :py:class:`Lerpable` object Args: - v1 (Lerpable): The first value - v2 (Lerpable): The second value + v1 (HasAddSubMul): The first value + v2 (HasAddSubMul): The second value u: The interpolation value `(0.0 to 1.0)` """ return v1 + ((v2 - v1) * u) diff --git a/arcade/types/__init__.py b/arcade/types/__init__.py index 4186f5d13..db88165e9 100644 --- a/arcade/types/__init__.py +++ b/arcade/types/__init__.py @@ -124,7 +124,7 @@ "Box", "LRBTNF", "XYZWHD", - "Lerpable", + "HasAddSubMul", "RGB", "RGBA", "RGBOrA", @@ -211,7 +211,7 @@ def annotated2(argument: OneOrIterableOf[MyType] | None = tuple()): _R_co = TypeVar("_R_co", covariant=True) -class Lerpable(Protocol[_T_contra, _R_co]): +class HasAddSubMul(Protocol[_T_contra, _R_co]): """Matches types which work with :py:func:`arcade.math.lerp`.""" # The / matches float and similar operations to keep pyright From 0e42c9cd02a517bdab35afbbff95640f91216d52 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:55:58 -0400 Subject: [PATCH 13/17] Convert lerp_2d and lerp_3d to return Vec types --- arcade/math.py | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index aeacf3314..8b8b897a1 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -4,7 +4,7 @@ import random from typing import Sequence, TypeVar, Union -from pyglet.math import Vec2 +from pyglet.math import Vec2, Vec3 from arcade.types import AsFloat, HasAddSubMul, Point, Point2 from arcade.types.rect import Rect @@ -54,15 +54,23 @@ def clamp(a, low: float, high: float) -> float: def lerp(v1: L, v2: L, u: float) -> L: - """Linearly interpolate two :py:class:`~arcade.types.Lerpable` values. + """Linearly interpolate two values which support arithmetic operators. - .. tip:: For angles, use :py:func:`lerp_angle`. + Both ``v1`` and ``v2`` must be of compatible types and support + the following operators: - The values must be of compatible types: + * ``+`` (:py:meth:`~object.__add__`) + * ``-`` (:py:meth:`~object.__sub__`) + * ``*`` (:py:meth:`~object.__mul__`) - * Two scalar values such as :py:class:`float` - * A two :py:mod:`pyglet.math` vectors of the same size - * A custom :py:class:`Lerpable` object + This means that in certain cases, you may want to use another + function: + + * For angles, use :py:func:`lerp_angle`. + * To convert points as arbitary sequences, use: + + * :py:func:`lerp_2d` + * :py:func:`lerp_3d` Args: v1 (HasAddSubMul): The first value @@ -72,28 +80,32 @@ def lerp(v1: L, v2: L, u: float) -> L: return v1 + ((v2 - v1) * u) -def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> tuple[float, float]: - """ - Linearly interpolate between two 2D points. +def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> Vec2: + """Linearly interpolate between two 2D points passed as sequences. + + .. tip:: This function returns a :py:class:`Vec2` you can use + with :py:func`lerp` . Args: - v1 (tuple[float, float]): The first point - v2 (tuple[float, float]): The second point + v1: The first point as a sequence of 2 values. + v2: The second point as a sequence of 2 values. u (float): The interpolation value `(0.0 to 1.0)` """ - return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u)) + return Vec2(lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u)) -def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> tuple[float, float, float]: - """ - Linearly interpolate between two 3D points. +def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> Vec3: + """Linearly interpolate between two 3D points passed as sequences. + + .. tip:: This function returns a :py:class:`Vec2` you can use + with :py:func`lerp`. Args: - v1 (tuple[float, float, float]): The first point - v2 (tuple[float, float, float]): The second point + v1: The first point as a sequence of 3 values. + v2: The second point as a sequence of 3 values. u (float): The interpolation value `(0.0 to 1.0)` """ - return (lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u), lerp(v1[2], v2[2], u)) + return Vec3(lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u), lerp(v1[2], v2[2], u)) def lerp_angle(start_angle: float, end_angle: float, u: float) -> float: From 0d67afaf62677352b14160333e1790093cda6032 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:57:19 -0400 Subject: [PATCH 14/17] Use Point2 and Point3 instead of local annotations --- arcade/math.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index 8b8b897a1..7b7d4a0ec 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -2,11 +2,11 @@ import math import random -from typing import Sequence, TypeVar, Union +from typing import TypeVar from pyglet.math import Vec2, Vec3 -from arcade.types import AsFloat, HasAddSubMul, Point, Point2 +from arcade.types import HasAddSubMul, Point, Point2 from arcade.types.rect import Rect from arcade.types.vector_like import Point3 @@ -46,10 +46,6 @@ def clamp(a, low: float, high: float) -> float: return high if a > high else max(a, low) -V_2D = Union[tuple[AsFloat, AsFloat], Sequence[AsFloat]] -V_3D = Union[tuple[AsFloat, AsFloat, AsFloat], Sequence[AsFloat]] - - L = TypeVar("L", bound=HasAddSubMul) @@ -80,7 +76,7 @@ def lerp(v1: L, v2: L, u: float) -> L: return v1 + ((v2 - v1) * u) -def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> Vec2: +def lerp_2d(v1: Point2, v2: Point2, u: float) -> Vec2: """Linearly interpolate between two 2D points passed as sequences. .. tip:: This function returns a :py:class:`Vec2` you can use @@ -94,7 +90,7 @@ def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> Vec2: return Vec2(lerp(v1[0], v2[0], u), lerp(v1[1], v2[1], u)) -def lerp_3d(v1: V_3D, v2: V_3D, u: float) -> Vec3: +def lerp_3d(v1: Point3, v2: Point3, u: float) -> Vec3: """Linearly interpolate between two 3D points passed as sequences. .. tip:: This function returns a :py:class:`Vec2` you can use From c78cf991f11457f54fb620f729d33e5db9872b11 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:59:44 -0400 Subject: [PATCH 15/17] Explain the local TypeVar --- arcade/math.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arcade/math.py b/arcade/math.py index 7b7d4a0ec..821598378 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -46,6 +46,8 @@ def clamp(a, low: float, high: float) -> float: return high if a > high else max(a, low) +# This TypeVar helps match v1 and v2 as the same type below in lerp's +# signature. If we used HasAddSubMul, they could be different. L = TypeVar("L", bound=HasAddSubMul) From 564227d0ca0f32d5439414e93a8b46be7a1acdf9 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Fri, 26 Jul 2024 18:02:31 -0400 Subject: [PATCH 16/17] Clarify the odd-looking type vars for the HasAddSubMul Protocol --- arcade/types/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/arcade/types/__init__.py b/arcade/types/__init__.py index db88165e9..753fef028 100644 --- a/arcade/types/__init__.py +++ b/arcade/types/__init__.py @@ -207,19 +207,21 @@ def annotated2(argument: OneOrIterableOf[MyType] | None = tuple()): # --- End potentially obsolete annotations --- -_T_contra = TypeVar("_T_contra", contravariant=True) -_R_co = TypeVar("_R_co", covariant=True) +# These are for the argument type + return type. They're separate TypeVars +# to handle cases which take tuple but return Vec2 (e.g. pyglet.math.Vec2). +_T_contra = TypeVar("_T_contra", contravariant=True) # Same or more general than T +_T_co = TypeVar("_T_co", covariant=True) # Same or more specific than T -class HasAddSubMul(Protocol[_T_contra, _R_co]): +class HasAddSubMul(Protocol[_T_contra, _T_co]): """Matches types which work with :py:func:`arcade.math.lerp`.""" # The / matches float and similar operations to keep pyright # happy since built-in arithmetic makes them positional only. # See https://peps.python.org/pep-0570/ - def __add__(self, value: _T_contra, /) -> _R_co: ... - def __sub__(self, value: _T_contra, /) -> _R_co: ... - def __mul__(self, value: _T_contra, /) -> _R_co: ... + def __add__(self, value: _T_contra, /) -> _T_co: ... + def __sub__(self, value: _T_contra, /) -> _T_co: ... + def __mul__(self, value: _T_contra, /) -> _T_co: ... # Path handling From 7fa3f6c5e52cc36dfb483a36bfbe6eb7468bb589 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Fri, 26 Jul 2024 18:12:02 -0400 Subject: [PATCH 17/17] Fix pyright by cleaning A* pathing annotations and imports --- arcade/paths.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/arcade/paths.py b/arcade/paths.py index 727b3708d..9e60af0e6 100644 --- a/arcade/paths.py +++ b/arcade/paths.py @@ -5,11 +5,10 @@ from __future__ import annotations import math -from typing import cast from arcade import Sprite, SpriteList, check_for_collision_with_list, get_sprites_at_point from arcade.math import get_distance, lerp_2d -from arcade.types import Point, Point2 +from arcade.types import Point2 __all__ = ["AStarBarrierList", "astar_calculate_path", "has_line_of_sight"] @@ -33,13 +32,13 @@ def _spot_is_blocked(position: Point2, moving_sprite: Sprite, blocking_sprites: return len(hit_list) > 0 -def _heuristic(start: Point, goal: Point) -> float: +def _heuristic(start: Point2, goal: Point2) -> float: """ Returns a heuristic value for the passed points. Args: - start (Point): The 1st point to compare - goal (Point): The 2nd point to compare + start (Point2): The 1st point to compare + goal (Point2): The 2nd point to compare Returns: float: The heuristic of the 2 points @@ -102,7 +101,7 @@ def __init__( else: self.movement_directions = (1, 0), (-1, 0), (0, 1), (0, -1) # type: ignore - def get_vertex_neighbours(self, pos: Point) -> list[tuple[float, float]]: + def get_vertex_neighbours(self, pos: Point2) -> list[tuple[float, float]]: """ Return neighbors for this point according to ``self.movement_directions`` @@ -123,7 +122,7 @@ def get_vertex_neighbours(self, pos: Point) -> list[tuple[float, float]]: n.append((x2, y2)) return n - def move_cost(self, a: Point, b: Point) -> float: + def move_cost(self, a: Point2, b: Point2) -> float: """ Returns a float of the cost to move @@ -224,12 +223,12 @@ def _AStarSearch(start: Point2, end: Point2, graph: _AStarGraph) -> list[Point2] return None -def _collapse(pos: Point, grid_size: float): +def _collapse(pos: Point2, grid_size: float) -> tuple[int, int]: """Makes Point pos smaller by grid_size""" return int(pos[0] // grid_size), int(pos[1] // grid_size) -def _expand(pos: Point, grid_size: float): +def _expand(pos: Point2, grid_size: float) -> tuple[int, int]: """Makes Point pos larger by grid_size""" return int(pos[0] * grid_size), int(pos[1] * grid_size) @@ -329,11 +328,11 @@ def recalculate(self): def astar_calculate_path( - start_point: Point, - end_point: Point, + start_point: Point2, + end_point: Point2, astar_barrier_list: AStarBarrierList, diagonal_movement: bool = True, -) -> list[Point] | None: +) -> list[Point2] | None: """ Calculates the path using AStarSearch Algorithm and returns the path @@ -371,13 +370,13 @@ def astar_calculate_path( # Currently 'result' is in grid locations. We need to convert them to pixel # locations. - revised_result = [_expand(p, grid_size) for p in result] - return cast(list[Point], revised_result) + revised_result: list[Point2] = [_expand(p, grid_size) for p in result] + return revised_result def has_line_of_sight( - observer: Point, - target: Point, + observer: Point2, + target: Point2, walls: SpriteList, max_distance: float = float("inf"), check_resolution: int = 2, @@ -429,7 +428,7 @@ def has_line_of_sight( # NOTE: Rewrite this -# def dda_step(start: Point, end: Point): +# def dda_step(start: Point2, end: Point2): # """ # Bresenham's line algorithm