Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
162 changes: 104 additions & 58 deletions arcade/math.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import math
import random
from typing import TypeVar
from typing import TypeVar, overload

from pyglet.math import Vec2, Vec3

Expand Down Expand Up @@ -311,51 +311,83 @@ def rand_vec_magnitude(
return vel.x, vel.y


def get_distance(x1: float, y1: float, x2: float, y2: float) -> float:
"""
Get the distance between two points.
@overload
def get_distance(p1: Point2, p2: Point2) -> float: ...

Args:
x1 (float): x coordinate of the first point
y1 (float): y coordinate of the first point
x2 (float): x coordinate of the second point
y2 (float): y coordinate of the second point
@overload
def get_distance(x1: float, y1: float, x2: float, y2: float) -> float: ...



def get_distance(*args) -> float:
"""
get distance between two points.
args:
can be called as:
get_distance(p1, p2) with point2 args
get_distance(x1, y1, x2, y2) with float args

get distance with x1 y1 can be removed later down the line for now it is for backwards compatiblity
"""
return math.hypot(x1 - x2, y1 - y2)
if len(args) == 2:
return math.hypot(args[0][0] - args[1][0], args[0][1] - args[1][1])
elif len(args) == 4:
return math.hypot(args[0] - args[2], args[1] - args[3])
else:
raise ValueError("get_distance() takes 2 or 4 arguments")


@overload
def rotate_point(
x: float,
y: float,
cx: float,
cy: float,
angle_degrees: float,
) -> Point2:
"""
Rotate a point around a center.

Args:
x (float): x value of the point you want to rotate
y (float): y value of the point you want to rotate
cx (float): x value of the center point you want to rotate around
cy (float): y value of the center point you want to rotate around
angle_degrees (float): Angle, in degrees, to rotate
"""
temp_x = x - cx
temp_y = y - cy
) -> Point2: ...

# now apply rotation
angle_radians = math.radians(angle_degrees)
cos_angle = math.cos(angle_radians)
sin_angle = math.sin(angle_radians)
rotated_x = temp_x * cos_angle + temp_y * sin_angle
rotated_y = -temp_x * sin_angle + temp_y * cos_angle

# translate back
x = round(rotated_x + cx, _PRECISION)
y = round(rotated_y + cy, _PRECISION)

return x, y
@overload
def rotate_point(
p1: Point2 , p2: Point2,
angle_degrees: float,
) -> Point2: ...


def rotate_point(*args) -> Point2:

if len(args) == 3:
p1, p2, angle_degrees = args
temp_x = p1[0] - p2[0]
temp_y = p1[1] - p2[1]
angle_radians = math.radians(angle_degrees)
cos_angle = math.cos(angle_radians)
sin_angle = math.sin(angle_radians)
rotated_x = temp_x * cos_angle + temp_y * sin_angle
rotated_y = -temp_x * sin_angle + temp_y * cos_angle

# translate back
x = round(rotated_x + p2[0], _PRECISION)
y = round(rotated_y + p2[1], _PRECISION)

return x, y
elif len(args) == 5:
x, y, cx, cy, angle_degrees = args
temp_x = x - cx
temp_y = y - cy
angle_radians = math.radians(angle_degrees)
cos_angle = math.cos(angle_radians)
sin_angle = math.sin(angle_radians)
rotated_x = temp_x * cos_angle + temp_y * sin_angle
rotated_y = -temp_x * sin_angle + temp_y * cos_angle

# translate back
x = round(rotated_x + cx, _PRECISION)
y = round(rotated_y + cy, _PRECISION)

return x, y
else:
raise ValueError("rotate_point() takes 2 or 4 arguments")



# scale around point
Expand Down Expand Up @@ -424,35 +456,49 @@ def rotate_around_point(source: Point2, target: Point2, angle: float):
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.
@overload
def get_angle_degrees(x1: float, y1: float, x2: float, y2: float) -> float: ...

Args:
x1 (float): x coordinate of the first point
y1 (float): y coordinate of the first point
x2 (float): x coordinate of the second point
y2 (float): y coordinate of the second point
"""
x_diff = x2 - x1
y_diff = y2 - y1

@overload
def get_angle_degrees(p1: Point2, p2: Point2) -> float: ...


def get_angle_degrees(*args):
"""same as other function can take arguments as p1,p2 [point range] or float args x1 y2.."""

if len(args) == 2:
p1, p2 = args
x_diff = p2[0] - p1[0]
y_diff = p2[1] - p1[1]
elif len(args) == 4:
x1, y1, x2, y2 = args
x_diff = x2 - x1
y_diff = y2 - y1
else:
raise ValueError("get_angle_degrees() takes 2 or 4 arguments")
return -math.degrees(math.atan2(y_diff, x_diff))


def get_angle_radians(x1: float, y1: float, x2: float, y2: float) -> float:
"""
Get the angle in radians between two points.
@overload
def get_angle_radians(x1: float, y1: float, x2: float, y2: float) -> float: ...

@overload
def get_angle_radians(p1: Point2, p2: Point2) -> float: ...

Args:
x1 (float): x coordinate of the first point
y1 (float): y coordinate of the first point
x2 (float): x coordinate of the second point
y2 (float): y coordinate of the second point
"""
x_diff = x2 - x1
y_diff = y2 - y1
return math.atan2(x_diff, y_diff)

def get_angle_radians(*args):
if len(args) == 4:
x1, y1, x2, y2 = args
x_diff = x2 - x1
y_diff = y2 - y1
elif len(args) == 2:
p1, p2 = args
x_diff = p2[0] - p1[0]
y_diff = p2[1] - p1[1]
else:
raise ValueError("get_angle_radians() takes 2 or 4 arguments")
return math.atan2(x_diff, y_diff)

def quaternion_rotation(axis: Point3, vector: Point3, angle: float) -> tuple[float, float, float]:
"""
Expand Down
54 changes: 54 additions & 0 deletions tests/unit/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,57 @@ def test_rand_vec_spread_deg():
def test_rand_vec_magnitude():
"""Smoke test"""
rand_vec_magnitude(30.5, 3.3, 4.4)


#adding more test for getdistance with floats of point2 and x1 type

def test_get_distance_with_floats():
"""Test get_distance with x1, y1, x2, y2 parameters"""
assert get_distance(0, 0, 3, 4) == approx(5.0)
assert get_distance(1, 1, 4, 5) == approx(5.0)

def test_get_distance_with_point2():
"""Test get_distance with Point2 parameters"""
assert get_distance((0, 0), (3, 4)) == approx(5.0)
assert get_distance((1, 1), (4, 5)) == approx(5.0)

def test_rotate_point_with_floats():
"""Test rotate_point with x, y, cx, cy, angle parameters"""
# Rotate (1, 0) around origin by 90 degrees
result = rotate_point(1, 0, 0, 0, 90)
assert result[0] == approx(0.0, abs=0.01)
assert result[1] == approx(-1.0, abs=0.01)


def test_rotate_point_with_point2():
"""Test rotate_point with Point2 parameters"""
# Rotate (1, 0) around origin (0, 0) by 90 degrees
result = rotate_point((1, 0), (0, 0), 90)
assert result[0] == approx(0.0, abs=0.01)
assert result[1] == approx(-1.0, abs=0.01)


def test_get_angle_degrees_with_floats():
"""Test get_angle_degrees with x1, y1, x2, y2 parameters"""
# Angle from (0,0) to (1,0) should be 0 degrees
assert get_angle_degrees(0, 0, 1, 0) == approx(0.0)
# Angle from (0,0) to (0,1) should be -90 degrees
assert get_angle_degrees(0, 0, 0, 1) == approx(-90.0)


def test_get_angle_degrees_with_point2():
"""Test get_angle_degrees with Point2 parameters"""
assert get_angle_degrees((0, 0), (1, 0)) == approx(0.0)
assert get_angle_degrees((0, 0), (0, 1)) == approx(-90.0)


def test_get_angle_radians_with_floats():
"""Test get_angle_radians with x1, y1, x2, y2 parameters"""
import math
assert get_angle_radians(0, 0, 1, 0) == approx(math.pi / 2)


def test_get_angle_radians_with_point2():
"""Test get_angle_radians with Point2 parameters"""
import math
assert get_angle_radians((0, 0), (1, 0)) == approx(math.pi / 2)