Skip to content

Commit 4edcf7e

Browse files
authored
More generic math methods. (#2431)
* generic `rescale_relative_to_point` and point2 unrolling * generic `rotate_around_point` method.
1 parent 38644c0 commit 4edcf7e

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

arcade/math.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from pyglet.math import Vec2, Vec3
88

9-
from arcade.types import HasAddSubMul, Point, Point2, SupportsRichComparison
9+
from arcade.types import AsFloat, HasAddSubMul, Point, Point2, SupportsRichComparison
1010
from arcade.types.rect import Rect
1111
from arcade.types.vector_like import Point3
1212

@@ -365,6 +365,73 @@ def rotate_point(
365365
return x, y
366366

367367

368+
# scale around point
369+
370+
371+
def rescale_relative_to_point(source: Point2, target: Point2, factor: AsFloat | Point2) -> Point2:
372+
"""
373+
Calculate where a point should be when scaled by the factor realtive to the source point.
374+
375+
Args:
376+
source: Where to scaled from.
377+
target: The point being scaled.
378+
factor: How much to scale by. If factor is less than one, target approaches source.
379+
Otherwise it moves away. A factor of zero returns source.
380+
381+
Returns:
382+
The rescaled point.
383+
"""
384+
385+
if isinstance(factor, (float, int)):
386+
if factor == 1.0:
387+
return target
388+
scale_x = scale_y = factor
389+
else:
390+
try:
391+
scale_x, scale_y = factor
392+
if scale_x == 1.0 and scale_y == 1.0:
393+
return target
394+
except ValueError:
395+
raise ValueError(
396+
"factor must be a float, int, or tuple-like "
397+
"which unpacks as two float-like values"
398+
)
399+
except TypeError:
400+
raise TypeError(
401+
"factor must be a float, int, or tuple-like unpacks as two float-like values"
402+
)
403+
404+
dx = target[0] - source[0]
405+
dy = target[1] - source[1]
406+
407+
return source[0] + dx * scale_x, source[1] + dy * scale_y
408+
409+
410+
def rotate_around_point(source: Point2, target: Point2, angle: float):
411+
"""
412+
Rotate a point around another point clockwise.
413+
414+
Args:
415+
source: The point to rotate around
416+
target: The point to rotate
417+
angle: The degrees to rotate the target by.
418+
"""
419+
420+
if source == target or angle % 360.0 == 0.0:
421+
return target
422+
423+
diff_x = target[0] - source[0]
424+
diff_y = target[1] - source[1]
425+
r = math.radians(angle)
426+
427+
c, s = math.cos(r), math.sin(r)
428+
429+
dx = diff_x * c - diff_y * s
430+
dy = diff_x * s + diff_y * c
431+
432+
return target[0] + dx, target[1] + dy
433+
434+
368435
def get_angle_degrees(x1: float, y1: float, x2: float, y2: float) -> float:
369436
"""
370437
Get the angle in degrees between two points.

arcade/utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from pathlib import Path
1313
from typing import Any, Callable, Generator, Generic, Iterable, Sequence, Type, TypeVar
1414

15+
from arcade.types import AsFloat, Point2
16+
1517
__all__ = [
1618
"as_type",
1719
"type_name",
@@ -285,3 +287,35 @@ def get_raspberry_pi_info() -> tuple[bool, str, str]:
285287
pass
286288

287289
return False, "", ""
290+
291+
292+
def unpack_asfloat_or_point(value: AsFloat | Point2) -> Point2:
293+
"""
294+
A utility method that converts a float or int into a Point2, or
295+
validates that an iterable is a Point2.
296+
297+
.. note:: This should be inlined in hot code paths
298+
299+
Args:
300+
value: The value to test.
301+
302+
Returns:
303+
A Point2 that is either equal to value, or is equal to (value, value)
304+
"""
305+
306+
if isinstance(value, (float, int)):
307+
x = y = value
308+
else:
309+
try:
310+
x, y = value
311+
except ValueError:
312+
raise ValueError(
313+
"value must be a float, int, or tuple-like "
314+
"which unpacks as two float-like values"
315+
)
316+
except TypeError:
317+
raise TypeError(
318+
"value must be a float, int, or tuple-like unpacks as two float-like values"
319+
)
320+
321+
return x, y

0 commit comments

Comments
 (0)