Skip to content

Commit

Permalink
More generic math methods. (#2431)
Browse files Browse the repository at this point in the history
* generic `rescale_relative_to_point` and point2 unrolling

* generic `rotate_around_point` method.
  • Loading branch information
DragonMoffon authored Oct 22, 2024
1 parent 38644c0 commit 4edcf7e
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 1 deletion.
69 changes: 68 additions & 1 deletion arcade/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from pyglet.math import Vec2, Vec3

from arcade.types import HasAddSubMul, Point, Point2, SupportsRichComparison
from arcade.types import AsFloat, HasAddSubMul, Point, Point2, SupportsRichComparison
from arcade.types.rect import Rect
from arcade.types.vector_like import Point3

Expand Down Expand Up @@ -365,6 +365,73 @@ def rotate_point(
return x, y


# scale around point


def rescale_relative_to_point(source: Point2, target: Point2, factor: AsFloat | Point2) -> Point2:
"""
Calculate where a point should be when scaled by the factor realtive to the source point.
Args:
source: Where to scaled from.
target: The point being scaled.
factor: How much to scale by. If factor is less than one, target approaches source.
Otherwise it moves away. A factor of zero returns source.
Returns:
The rescaled point.
"""

if isinstance(factor, (float, int)):
if factor == 1.0:
return target
scale_x = scale_y = factor
else:
try:
scale_x, scale_y = factor
if scale_x == 1.0 and scale_y == 1.0:
return target
except ValueError:
raise ValueError(
"factor must be a float, int, or tuple-like "
"which unpacks as two float-like values"
)
except TypeError:
raise TypeError(
"factor must be a float, int, or tuple-like unpacks as two float-like values"
)

dx = target[0] - source[0]
dy = target[1] - source[1]

return source[0] + dx * scale_x, source[1] + dy * scale_y


def rotate_around_point(source: Point2, target: Point2, angle: float):
"""
Rotate a point around another point clockwise.
Args:
source: The point to rotate around
target: The point to rotate
angle: The degrees to rotate the target by.
"""

if source == target or angle % 360.0 == 0.0:
return target

diff_x = target[0] - source[0]
diff_y = target[1] - source[1]
r = math.radians(angle)

c, s = math.cos(r), math.sin(r)

dx = diff_x * c - diff_y * s
dy = diff_x * s + diff_y * c

return target[0] + dx, target[1] + dy


def get_angle_degrees(x1: float, y1: float, x2: float, y2: float) -> float:
"""
Get the angle in degrees between two points.
Expand Down
34 changes: 34 additions & 0 deletions arcade/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from pathlib import Path
from typing import Any, Callable, Generator, Generic, Iterable, Sequence, Type, TypeVar

from arcade.types import AsFloat, Point2

__all__ = [
"as_type",
"type_name",
Expand Down Expand Up @@ -285,3 +287,35 @@ def get_raspberry_pi_info() -> tuple[bool, str, str]:
pass

return False, "", ""


def unpack_asfloat_or_point(value: AsFloat | Point2) -> Point2:
"""
A utility method that converts a float or int into a Point2, or
validates that an iterable is a Point2.
.. note:: This should be inlined in hot code paths
Args:
value: The value to test.
Returns:
A Point2 that is either equal to value, or is equal to (value, value)
"""

if isinstance(value, (float, int)):
x = y = value
else:
try:
x, y = value
except ValueError:
raise ValueError(
"value must be a float, int, or tuple-like "
"which unpacks as two float-like values"
)
except TypeError:
raise TypeError(
"value must be a float, int, or tuple-like unpacks as two float-like values"
)

return x, y

0 comments on commit 4edcf7e

Please sign in to comment.