Source code for xecs._internal.vec2

import typing
from typing import Self, TypeAlias

import numpy as np
import numpy.typing as npt

from xecs._internal.struct import Struct
from xecs.xecs import ArrayViewIndices, Float32

if typing.TYPE_CHECKING:
    from xecs.xecs import Float32Rhs


[docs] class Vec2(Struct): """ A set of 2D vector values. """ x: Float32 """The x values.""" y: Float32 """The y values."""
[docs] @staticmethod def from_numpy(array: npt.NDArray[np.float32]) -> "Vec2": """ Create the vectors from a NumPy array. Parameters: array: The NumPy array. Returns: The vectors. """ obj = Vec2.__new__(Vec2) if isinstance(array, np.ndarray) and array.ndim == 2: indices = ArrayViewIndices.with_capacity(len(array[0])) indices.spawn(len(array[0])) obj.x = Float32.p_from_numpy(array[0]).p_new_view_with_indices( indices ) obj.y = Float32.p_from_numpy(array[1]).p_new_view_with_indices( indices ) else: indices = ArrayViewIndices.with_capacity(len(array)) obj.x = Float32.p_from_numpy(array).p_new_view_with_indices( indices ) obj.y = Float32.p_from_numpy(array).p_new_view_with_indices( indices ) obj._indices = indices return obj
[docs] @staticmethod def from_xy(x: float, y: float, num: int) -> "Vec2": """ Create the vectors from x and y values. Parameters: x: The x value of the vectors. y: The y value of the vectors. num: The number of vectors to create. Returns: The vectors. """ obj = Vec2.__new__(Vec2) obj._init(Float32.p_from_value(x, num), Float32.p_from_value(y, num)) return obj
[docs] def clamp_length(self, min: float, max: float) -> Self: """ Ensure lengths are no less than `min` and no more than `max`. Parameters: min: The minimum length. max: The maximum length. Returns: Itself. """ length_sq = self.length_squared() too_small = length_sq < (min * min) too_small_self = self[too_small] too_large = length_sq > (max * max) too_large_self = self[too_large] too_small_length = length_sq[too_small] np.sqrt(too_small_length, out=too_small_length) too_small_self.x /= too_small_length too_small_self.x *= min too_small_self.y /= too_small_length too_small_self.y *= min too_large_length = length_sq[too_large] np.sqrt(too_large_length, out=too_large_length) too_large_self.x /= too_large_length too_large_self.x *= max too_large_self.y /= too_large_length too_large_self.y *= max return self
[docs] def dot_xy(self, x: float, y: float) -> npt.NDArray[np.float32]: """ Get the dot products between the vectors and another vector. Parameters: x: The x value of the other vector. y: The y value of the other vector. Returns: The dot products. """ tmp = self.numpy() np.multiply(tmp, [[x], [y]], out=tmp) return np.sum(tmp, axis=0, out=tmp[0])
[docs] def dot_vec2(self, other: "Vec2") -> npt.NDArray[np.float32]: """ Get the element wise dot products with some other vectors. Parameters: other: The other vectors. Returns: The dot products. """ tmp = self.numpy() np.multiply(tmp, other.numpy(), out=tmp) return np.sum(tmp, axis=0, out=tmp[0])
[docs] def perp_dot_xy(self, x: float, y: float) -> npt.NDArray[np.float32]: """ Get the perpendicular dot products with the vectors and another. Also known as the 2D cross product. Parameters: x: The x value of the other vector. y: The y value of the other vector. Returns: The perpendicular dot products. """ return np.cross(self.numpy(), [x, y], axisa=0, axisb=0)
[docs] def perp_dot_vec2(self, other: "Vec2") -> npt.NDArray[np.float32]: """ Get the perpendicular dot products with some other vectors. Also known as the 2D cross product. Parameters: other: The other vectors. Returns: The perpendicular dot products. """ return np.cross(self.numpy(), other.numpy(), axisa=0, axisb=0)
[docs] def angle_between_xy(self, x: float, y: float) -> npt.NDArray[np.float32]: """ Return the angles between another vector. The returned angles are in radians and in the range ``[-pi, pi]``. Parameters: x: The x value of the other vector. y: The y value of the other vector. Returns: The angles. """ tmp = self.length_squared() np.multiply(tmp, x * x + y * y, out=tmp) np.sqrt(tmp, out=tmp) tmp2 = self.dot_xy(x, y) tmp2 /= tmp np.arccos(tmp2, out=tmp2) perp_dot = self.perp_dot_xy(x, y) return np.multiply(tmp2, np.sign(perp_dot, out=perp_dot), out=tmp2)
[docs] def angle_between_vec2(self, other: "Vec2") -> npt.NDArray[np.float32]: """ Return the angles between some other vectors. The returned angles are in radians and in the range ``[-pi, pi]``. Parameters: other: The other vectors. Returns: The angles. """ tmp = self.length_squared() np.multiply(tmp, other.length_squared(), out=tmp) np.sqrt(tmp, out=tmp) tmp2 = self.dot_vec2(other) tmp2 /= tmp np.arccos(tmp2, out=tmp2) perp_dot = self.perp_dot_vec2(other) return np.multiply(tmp2, np.sign(perp_dot, out=perp_dot), out=tmp2)
[docs] def length_squared(self) -> npt.NDArray[np.float32]: """ Get the squared lengths of the vectors. Returns: The squared lengths. """ values = self.numpy() np.multiply(values, values, out=values) return np.sum(values, axis=0, out=values[0])
[docs] def numpy(self) -> npt.NDArray[np.float32]: """ Return a copy of the vectors as a NumPy array. Returns: The vectors. """ return np.array([self.x.numpy(), self.y.numpy()], dtype=np.float32)
[docs] def fill(self, value: "Float32Rhs") -> None: """ Set the values of the vectors. Parameters: value (float | list[float]): The values to set the vectors to. """ if isinstance(value, np.ndarray) and value.ndim == 2: self.x.fill(value[0]) self.y.fill(value[1]) else: self.x.fill(value) self.y.fill(value)
def _init(self, x: Float32, y: Float32) -> None: self.x = x self.y = y def __add__(self, other: "Rhs") -> npt.NDArray[np.float32]: if isinstance(other, Vec2 | Float32): other = other.numpy() result = self.numpy() result += other return result def __iadd__(self, other: "Rhs") -> Self: if isinstance(other, int | float | Float32 | list | tuple): self.x += other self.y += other elif isinstance(other, np.ndarray) and other.ndim == 2: self.x += other[0] self.y += other[1] elif isinstance(other, np.ndarray): self.x += other self.y += other else: self.x += other.x self.y += other.y return self def __sub__(self, other: "Rhs") -> npt.NDArray[np.float32]: if isinstance(other, Vec2 | Float32): other = other.numpy() result = self.numpy() result -= other return result def __isub__(self, other: "Rhs") -> Self: if isinstance(other, int | float | Float32 | list | tuple): self.x -= other self.y -= other elif isinstance(other, np.ndarray) and other.ndim == 2: self.x -= other[0] self.y -= other[1] elif isinstance(other, np.ndarray): self.x -= other self.y -= other else: self.x -= other.x self.y -= other.y return self def __mul__(self, other: "Rhs") -> npt.NDArray[np.float32]: if isinstance(other, Vec2 | Float32): other = other.numpy() result = self.numpy() result *= other return result def __imul__(self, other: "Rhs") -> Self: if isinstance(other, int | float | Float32 | list | tuple): self.x *= other self.y *= other elif isinstance(other, np.ndarray) and other.ndim == 2: self.x *= other[0] self.y *= other[1] elif isinstance(other, np.ndarray): self.x *= other self.y *= other else: self.x *= other.x self.y *= other.y return self def __truediv__(self, other: "Rhs") -> npt.NDArray[np.float32]: if isinstance(other, Vec2 | Float32): other = other.numpy() result = self.numpy() result /= other return result def __itruediv__(self, other: "Rhs") -> Self: if isinstance(other, int | float | Float32 | list | tuple): self.x /= other self.y /= other elif isinstance(other, np.ndarray) and other.ndim == 2: self.x /= other[0] self.y /= other[1] elif isinstance(other, np.ndarray): self.x /= other self.y /= other else: self.x /= other.x self.y /= other.y return self def __floordiv__(self, other: "Rhs") -> npt.NDArray[np.float32]: if isinstance(other, Vec2 | Float32): other = other.numpy() result = self.numpy() result //= other return result def __ifloordiv__(self, other: "Rhs") -> Self: if isinstance(other, int | float | Float32 | list | tuple): self.x //= other self.y //= other elif isinstance(other, np.ndarray) and other.ndim == 2: self.x //= other[0] self.y //= other[1] elif isinstance(other, np.ndarray): self.x //= other self.y //= other else: self.x //= other.x self.y //= other.y return self def __mod__(self, other: "Rhs") -> npt.NDArray[np.float32]: if isinstance(other, Vec2 | Float32): other = other.numpy() result = self.numpy() result %= other return result def __imod__(self, other: "Rhs") -> Self: if isinstance(other, int | float | Float32 | list | tuple): self.x %= other self.y %= other elif isinstance(other, np.ndarray) and other.ndim == 2: self.x %= other[0] self.y %= other[1] elif isinstance(other, np.ndarray): self.x %= other self.y %= other else: self.x %= other.x self.y %= other.y return self def __pow__(self, other: "Rhs") -> npt.NDArray[np.float32]: if isinstance(other, Vec2 | Float32): other = other.numpy() result = self.numpy() result **= other return result def __ipow__(self, other: "Rhs") -> Self: if isinstance(other, int | float | Float32 | list | tuple): self.x **= other self.y **= other elif isinstance(other, np.ndarray) and other.ndim == 2: self.x **= other[0] self.y **= other[1] elif isinstance(other, np.ndarray): self.x **= other self.y **= other else: self.x **= other.x self.y **= other.y return self
Rhs: TypeAlias = ( Vec2 | Float32 | npt.NDArray[np.float32] | float | list[float] | tuple[float, ...] )