From 2e5b04448bcab2605f2d3667f4c2d7b4655b8ad2 Mon Sep 17 00:00:00 2001 From: DragonMoffon Date: Fri, 29 Mar 2024 17:49:28 +1300 Subject: [PATCH 1/2] Updating arcade.math changed lerp_vec to lerp_2d created lerp_3d, and quaternion_rotation methods. Also updated any methods which used lerp_vec --- arcade/math.py | 76 +++++++++++++++++++++++++++++++++++++++-- arcade/paths.py | 4 +-- tests/unit/test_math.py | 6 ++-- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index fc964112d..68ceba97e 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -2,6 +2,8 @@ import math import random +from typing import Tuple, List, Union +from pyglet.math import Vec2, Vec3 from arcade.types import Point, Vector _PRECISION = 2 @@ -11,7 +13,8 @@ "round_fast", "clamp", "lerp", - "lerp_vec", + "lerp_2d", + "lerp_3d", "lerp_angle", "rand_in_rect", "rand_in_circle", @@ -25,6 +28,7 @@ "rotate_point", "get_angle_degrees", "get_angle_radians", + "quaternion_rotation" ] @@ -64,13 +68,25 @@ def lerp(v1: float, v2: float, u: float) -> float: return v1 + ((v2 - v1) * u) -def lerp_vec(v1: Vector, v2: Vector, u: float) -> Vector: +V_2D = Union[Vec2, Tuple[float, float], List[float]] +V_3D = Union[Vec3, Tuple[float, float, float], List[float]] + + +def lerp_2d(v1: V_2D, v2: V_2D, u: float) -> Tuple[float, float]: 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]: + 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, @@ -160,7 +176,7 @@ def rand_on_line(pos1: Point, pos2: Point) -> Point: :return: A random point on the line """ u = random.uniform(0.0, 1.0) - return lerp_vec(pos1, pos2, u) + return lerp_2d(pos1, pos2, u) def rand_angle_360_deg() -> float: @@ -354,3 +370,57 @@ def get_angle_radians(x1: float, y1: float, x2: float, y2: float) -> float: x_diff = x2 - x1 y_diff = y2 - y1 return math.atan2(x_diff, y_diff) + + +def quaternion_rotation(axis: Tuple[float, float, float], + vector: Tuple[float, float, float], + angle: float) -> Tuple[float, float, float]: + """ + Rotate a 3-dimensional vector of any length clockwise around a 3-dimensional unit length vector. + + This method of vector rotation is immune to rotation-lock, however it takes a little more effort + to find the axis of rotation rather than 3 angles of rotation. + Ref: https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html. + + Example: + import arcade + from arcade.camera.controllers import quaternion_rotation + + + # Rotating a sprite around a point + sprite = arcade.Sprite(center_x=0.0, center_y=10.0) + rotation_point = (0.0, 0.0) + + # Find the relative vector between the sprite and point to rotate. (Must be a 3D vector) + relative_position = sprite.center_x - rotation_point[0], sprite.center_y - rotation_point[1], 0.0 + + # Because arcade uses the X and Y axis for 2D co-ordinates the Z-axis becomes the rotation axis. + rotation_axis = (0.0, 0.0, 1.0) + + # Rotate the vector 45 degrees clockwise. + new_relative_position = quaternion_rotation(rotation_axis, relative_position, 45) + + + sprite.position = ( + rotation_point[0] + new_relative_position[0], + rotation_point[1] + new_relative_position[1] + ) + + :param axis: The unit length vector that will be rotated around + :param vector: The 3-dimensional vector to be rotated + :param angle: The angle in degrees to rotate the vector clock-wise by + :return: A rotated 3-dimension vector with the same length as the argument vector. + """ + _rotation_rads = -math.radians(angle) + p1, p2, p3 = vector + _c2, _s2 = math.cos(_rotation_rads / 2.0), math.sin(_rotation_rads / 2.0) + + q0, q1, q2, q3 = _c2, _s2 * axis[0], _s2 * axis[1], _s2 * axis[2] + q0_2, q1_2, q2_2, q3_2 = q0 ** 2, q1 ** 2, q2 ** 2, q3 ** 2 + q01, q02, q03, q12, q13, q23 = q0 * q1, q0 * q2, q0 * q3, q1 * q2, q1 * q3, q2 * q3 + + _x = p1 * (q0_2 + q1_2 - q2_2 - q3_2) + 2.0 * (p2 * (q12 - q03) + p3 * (q02 + q13)) + _y = p2 * (q0_2 - q1_2 + q2_2 - q3_2) + 2.0 * (p1 * (q03 + q12) + p3 * (q23 - q01)) + _z = p3 * (q0_2 - q1_2 - q2_2 + q3_2) + 2.0 * (p1 * (q13 - q02) + p2 * (q01 + q23)) + + return _x, _y, _z diff --git a/arcade/paths.py b/arcade/paths.py index 13ec8267c..5ce1af0f1 100644 --- a/arcade/paths.py +++ b/arcade/paths.py @@ -19,7 +19,7 @@ check_for_collision_with_list, get_sprites_at_point ) -from arcade.math import get_distance, lerp_vec +from arcade.math import get_distance, lerp_2d from arcade.types import Point __all__ = [ @@ -360,7 +360,7 @@ def has_line_of_sight(observer: Point, for step in range(steps + 1): step_distance = step * check_resolution u = step_distance / distance - midpoint = lerp_vec(observer, target, u) + midpoint = lerp_2d(observer, target, u) if step_distance > max_distance: return False sprite_list = get_sprites_at_point(midpoint, walls) diff --git a/tests/unit/test_math.py b/tests/unit/test_math.py index 7d4dd0aa0..b43b9786c 100644 --- a/tests/unit/test_math.py +++ b/tests/unit/test_math.py @@ -15,11 +15,11 @@ def test_lerp(): assert lerp(2.0, 4.0, 0.75) == approx(3.5) -def test_lerp_vec(): - vec = lerp_vec((0.0, 2.0), (8.0, 4.0), 0.25) +def test_lerp_2d(): + vec = lerp_2d((0.0, 2.0), (8.0, 4.0), 0.25) assert vec[0] == approx(2.0) assert vec[1] == approx(2.5) - vec = lerp_vec((0.0, 2.0), (8.0, 4.0), -0.25) + vec = lerp_2d((0.0, 2.0), (8.0, 4.0), -0.25) assert vec[0] == approx(-2.0) assert vec[1] == approx(1.5) From eb9fdcbe7fde6e827ff08c0084f2c8c70cfeb887 Mon Sep 17 00:00:00 2001 From: DragonMoffon Date: Fri, 29 Mar 2024 18:04:52 +1300 Subject: [PATCH 2/2] remove example from arcade.math.quaternion_rotation --- arcade/math.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/arcade/math.py b/arcade/math.py index 68ceba97e..636cafb6e 100644 --- a/arcade/math.py +++ b/arcade/math.py @@ -382,30 +382,6 @@ def quaternion_rotation(axis: Tuple[float, float, float], to find the axis of rotation rather than 3 angles of rotation. Ref: https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html. - Example: - import arcade - from arcade.camera.controllers import quaternion_rotation - - - # Rotating a sprite around a point - sprite = arcade.Sprite(center_x=0.0, center_y=10.0) - rotation_point = (0.0, 0.0) - - # Find the relative vector between the sprite and point to rotate. (Must be a 3D vector) - relative_position = sprite.center_x - rotation_point[0], sprite.center_y - rotation_point[1], 0.0 - - # Because arcade uses the X and Y axis for 2D co-ordinates the Z-axis becomes the rotation axis. - rotation_axis = (0.0, 0.0, 1.0) - - # Rotate the vector 45 degrees clockwise. - new_relative_position = quaternion_rotation(rotation_axis, relative_position, 45) - - - sprite.position = ( - rotation_point[0] + new_relative_position[0], - rotation_point[1] + new_relative_position[1] - ) - :param axis: The unit length vector that will be rotated around :param vector: The 3-dimensional vector to be rotated :param angle: The angle in degrees to rotate the vector clock-wise by