srctools.math

This module implements three classes for representing vectors, Euler angles and rotation matrices, following Valve conventions. Vec represents an XYZ position and Angle represents a pitch-yaw-roll angle (in degrees). Matrix represents a rotation also, but in a format that can be manipulated properly. Angle will automatically compute a matrix when required, but it is more efficient to perform repeated operations on a matrix.

These classes will be replaced by Cython-optimized versions where possible, which are interchangable if pickled.

Rotations are performed via the matrix-multiplication operator @, where the left is rotated by the right. Vectors can be rotated by matrices and angles and matrices can be rotated by angles, but not vice-versa.

  • Vec @ AngleVec

  • Vec @ MatrixVec

  • tuple[x, y, z] @ AngleVec

  • Angle @ AngleAngle

  • Angle @ MatrixAngle

  • Matrix @ MatrixMatrix

Implicit Tolerances

Repeated calculations, especially calculations involving rotation will inevitably acculumulate error, making exact comparison unreliable. However, it is quite useful to compare to values like (0, 0, 1), so to allow this comparison operations will treat a difference of less than 10-6 as equal. This precision was chosen since it is the number of decimal points permitted in SMD files. If exact comparisons are required, direct attribute comparisons can be used. To allow use as dictionary keys, Vec.as_tuple() and Angle.as_tuple() round to the same precision also.

Free Functions

srctools.math.parse_vec_str(
val: str | VecBase | AngleBase,
x: T1 | float = 0.0,
y: T2 | float = 0.0,
z: T3 | float = 0.0,
) Tuple[T1 | float, T2 | float, T3 | float]

Convert a string in the form (4 6 -4) into a set of floats.

If the string is unparsable or an invalid type, this uses the defaults x, y, z. The string can be surrounded by any of the (), {}, [], <> bracket types, which are simply ignored.

If the ‘string’ is already a VecBase or AngleBase, this will be passed through. If you do want a specific class, use VecBase.from_str(), AngleBase.from_str() or MatrixBase.from_angstr().

srctools.math.lerp(
x: float,
in_min: float,
in_max: float,
out_min: float,
out_max: float,
) float

Linearly interpolate from in to out.

Raises:

ZeroDivisionError – If both in values are the same.

srctools.math.to_matrix(
value: Angle | FrozenAngle | Matrix | FrozenMatrix | VecBase | Vec_tuple | Tuple[float, float, float] | None,
) Matrix | FrozenMatrix

Convert various values to a rotation matrix.

Vec will be treated as angles, and None as the identity.

srctools.math.quickhull(
vertexes: Iterable[Vec],
) List[Tuple[Vec, Vec, Vec]]

Use the quickhull algorithm to construct a convex hull around the provided points.

This is only available when the C extension is compiled.

Vectors

class srctools.math.VecBase(*args: Any, **kwargs: Any)

Internal Base class for 3D vectors, implementing common code.

__hash__ = None
INV_AXIS = {'x': ('y', 'z'), 'y': ('x', 'z'), 'z': ('x', 'y'), ('x', 'y'): 'z', ('x', 'z'): 'y', ('y', 'x'): 'z', ('y', 'z'): 'x', ('z', 'x'): 'y', ('z', 'y'): 'x'}

This is a dictionary containing complementary axes. INV_AXIS["x", "y"] gives "z", and INV_AXIS["y"] returns ("x", "z").

__init__(
*args: Any,
**kwargs: Any,
) None

VecBase cannot be instantiated.

property x: float

Return the X axis.

property y: float

Return the Y axis.

property z: float

Return the Z axis.

copy() VecT

Implemented by subclasses.

classmethod from_str(
val: str | VecBase,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
) VecT

Convert a string in the form (4 6 -4) into a Vector.

If the string is unparsable, this uses the defaults (x,y,z). The string can be surrounded by any of the (), {}, [], <> bracket types, which are simply ignored.

If the value is already a vector, a copy will be returned. To only do parsing, use parse_vec_str().

classmethod with_axes(
*args: str | int | float | SupportsFloat | SupportsIndex | VecBase,
**kwargs: str | int | float | SupportsFloat | SupportsIndex | VecBase,
) VecT

Create a Vector, given a number of axes and corresponding values.

This is a convenience for doing the following::

vec = Vec() vec[axis1] = val1 vec[axis2] = val2 vec[axis3] = val3

The magnitudes can also be Vectors, in which case the matching axis will be used from the vector.

classmethod bbox(
*points: Iterable[VecBase] | VecBase,
) Tuple[VecT, VecT]

Compute the bounding box for a set of points.

Pass either several Vecs, or an iterable of Vecs. Returns a (min, max) tuple.

classmethod iter_grid(
min_pos: VecBase,
max_pos: VecBase,
stride: int = 1,
) Iterator[VecT]

Loop over points in a bounding box. All coordinates should be integers.

Both borders will be included.

iter_line(
end: VecBase,
stride: int = 1,
) Iterator[VecT]

Yield points in a line (including both endpoints).

Parameters:
  • stride – This specifies the distance between each point.

  • end – The other end of the line.

If the distance is less than the stride, only end-points will be yielded. If they are the same, that point will be yielded.

axis() Literal['x', 'y', 'z']

For an axis-aligned vector, return the axis it is on.

Raises:

ValueError – If the vector is not on-axis.

to_angle(
roll: float = 0,
) Angle

Convert a normal to a Source Engine angle.

The angle will point its +x axis in the direction of this vector. The inverse of this is Vec(x=1) @ Angle(pitch, yaw, roll).

Parameters:

roll – The roll is not affected by the direction of the vector, so it can be provided separately.

__abs__() VecT

Performing abs() on a Vec takes the absolute value of all axes.

__add__(
other: Vec | tuple | float,
)

+ operation.

This additionally works on scalars (adds to all axes).

__radd__(
other: VecBase | tuple | float,
)

+ operation with reversed operands.

This additionally works on scalars (adds to all axes).

__sub__(
other: Vec | tuple | float,
)

- operation.

This additionally works on scalars (adds to all axes).

__rsub__(
other: VecBase | tuple | float,
)

- operation with reversed operands.

This additionally works on scalars (adds to all axes).

__mul__(other: float)

Vector * scalar operation.

__rmul__(other: float)

scalar * Vector operation.

__truediv__(other: float)

Vector / scalar operation.

__rtruediv__(other: float)

scalar / Vector operation.

__floordiv__(other: float)

Vector // scalar operation.

__rfloordiv__(other: float)

scalar // Vector operation.

__mod__(other: float)

Vector % scalar operation.

__rmod__(other: float)

scalar % Vector operation.

__divmod__(
other: float,
) Tuple[VecT, VecT]

Divide the vector by a scalar, returning the result and remainder.

__rdivmod__(
other: float,
) Tuple[VecT, VecT]

Divide a scalar by a vector, returning the result and remainder.

__matmul__(
other: AngleBase | MatrixBase,
) VecT

Rotate this vector by an angle or matrix.

__bool__() bool

Vectors are True if any axis is non-zero.

__eq__(other: object) bool

Equality test.

Two Vectors are compared based on the axes. A Vector can be compared with a 3-tuple as if it was a Vector also. A tolerance of 1e-6 is accounted for automatically.

__ne__(other: object) bool

Inequality test.

Two Vectors are compared based on the axes. A Vector can be compared with a 3-tuple as if it was a Vector also. A tolerance of 1e-6 is accounted for automatically.

__lt__(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) bool

A<B test.

Two Vectors are compared based on the axes. A Vector can be compared with a 3-tuple as if it was a Vector also. A tolerance of 1e-6 is accounted for automatically.

__le__(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) bool

A<=B test.

Two Vectors are compared based on the axes. A Vector can be compared with a 3-tuple as if it was a Vector also. A tolerance of 1e-6 is accounted for automatically.

__gt__(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) bool

A>B test.

Two Vectors are compared based on the axes. A Vector can be compared with a 3-tuple as if it was a Vector also. A tolerance of 1e-6 is accounted for automatically.

__ge__(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) bool

A>=B test.

Two Vectors are compared based on the axes. A Vector can be compared with a 3-tuple as if it was a Vector also. A tolerance of 1e-6 is accounted for automatically.

classmethod lerp(
x: float,
in_min: float,
in_max: float,
out_min: VecBase,
out_max: VecBase,
) VecT

Linerarly interpolate between two vectors.

Raises:

ZeroDivisionError – If in_min and in_max are the same.

clamped(
*args: VecBase | Vec_tuple | Tuple[float, float, float],
mins: VecBase | Vec_tuple | Tuple[float, float, float] | None = None,
maxs: VecBase | Vec_tuple | Tuple[float, float, float] | None = None,
) VecT

Return a copy of this vector, constrained by the given min/max values.

Either both can be provided positionally, or at least one can be provided by keyword.

__round__(
ndigits: int = 0,
) VecT

Performing round() on a vector rounds each axis.

mag() float

Compute the distance from the vector and the origin.

join(delim: str = ', ') str

Return a string with all numbers joined by the passed delimiter.

This strips off the .0 if no decimal portion exists.

__str__() str

Return the values, separated by spaces.

This is the main format in Valve’s file formats. This strips off the .0 if no decimal portion exists.

__format__(format_spec: str) str

Control how the text is formatted.

This returns each axis formatted with the provided specification, joined by spaces.

__iter__() Iterator[float]

Iterating through the vector yields each axis in order.

__reversed__() Iterator[float]

Allow iterating through the dimensions, in reverse.

__getitem__(ind: str | int) float

Allow reading values by index instead of name if desired.

This accepts either 0, 1, 2 or x, y, z to read values. Useful in conjunction with a loop to apply commands to all values.

in_bbox(
a: VecBase | Vec_tuple | Tuple[float, float, float],
b: VecBase | Vec_tuple | Tuple[float, float, float],
) bool

Check if this point is inside the specified bounding box.

static bbox_intersect(
min1: VecBase,
max1: VecBase,
min2: VecBase,
max2: VecBase,
) bool

Check if the (min1, max1) bounding box intersects the (min2, max2) bounding box.

other_axes(
axis: str,
) Tuple[float, float]

Get the values for the other two axes.

as_tuple() Vec_tuple

Return the Vector as a tuple.

len_sq() float

Return the magnitude squared, which is slightly faster.

__len__() int

The len() of a vector is always 3.

__contains__(val: float) bool

Check to see if an axis is set to the given value.

__neg__() VecT

The inverted form of a Vector has inverted axes.

__pos__() VecT

+ on a Vector simply copies it.

norm() VecT

Normalise the Vector.

This is done by transforming it to have a magnitude of 1 but the same direction. The vector is left unchanged if it is equal to (0, 0, 0), instead of raising.

dot(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) float

Return the dot product of both Vectors.

Tip: using this in the form Vec.dot(a, b) may be more readable.

cross(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) VecT

Return the cross product of both Vectors.

If this is called as a method (a.cross(b)), the result will have the same type as a. Otherwise, if called as Vec.cross(a, b) or FrozenVec.cross(a, b), the type of the class takes priority.

norm_mask(
normal: Vec | FrozenVec,
) VecT

Subtract the components of this vector not in the direction of the normal.

If the normal is axis-aligned, this will zero out the other axes. If not axis-aligned, it will do the equivalent.

len() float

Compute the distance from the vector and the origin.

mag_sq() float

Return the magnitude squared, which is slightly faster.

class srctools.math.FrozenVec(
x: int | float | VecBase | Iterable[float] = 0.0,
y: float = 0.0,
z: float = 0.0,
)

Immutable vector class. This cannot be changed once created, but is hashable.

static __new__(
cls,
x: int | float | VecBase | Iterable[float] = 0.0,
y: float = 0.0,
z: float = 0.0,
) FrozenVec

Create a FrozenVec.

All values are converted to floats automatically. If no value is given, that axis will be set to 0. An iterable can be passed in (as the x argument), which will be used for x, y, and z.

classmethod with_axes(
axis1: str,
val1: int | float | SupportsFloat | SupportsIndex | VecBase,
axis2: str | None = None,
val2: int | float | SupportsFloat | SupportsIndex | VecBase = 0.0,
axis3: str | None = None,
val3: int | float | SupportsFloat | SupportsIndex | VecBase = 0.0,
) FrozenVec

Create a Vector, given a number of axes and corresponding values.

This is a convenience for doing the following:

vec = Vec()
vec[axis1] = val1
vec[axis2] = val2
vec[axis3] = val3

The magnitudes can also be Vectors, in which case the matching axis will be used from the vector.

copy() FrozenVec

FrozenVec is immutable.

__copy__() FrozenVec

FrozenVec is immutable.

__deepcopy__(
memodict: Dict[str, Any] | None = None,
) FrozenVec

FrozenVec is immutable.

__repr__() str

Code required to reproduce this vector.

__reduce__() Tuple[Callable[[float, float, float], FrozenVec], Tuple[float, float, float]]

Pickling support.

This redirects to a global function, so C/Python versions interoperate.

__hash__() int

Hashing a frozen vec is the same as hashing the tuple form.

cross(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) FrozenVec

Return the cross product of both Vectors.

If this is called as a method (a.cross(b)), the result will have the same type as a. Otherwise, if called as Vec.cross(a, b) or FrozenVec.cross(a, b), the type of the class takes priority.

thaw() Vec

Return a mutable copy of this vector.

class srctools.math.Vec(
x: int | float | VecBase | Iterable[float] = 0.0,
y: float = 0.0,
z: float = 0.0,
)

A 3D Vector. This has most standard Vector functions.

>>> Vec(1, 2, z=3)  # Positional or vec, defaults to 0.
Vec(1, 2, 3)
>> Vec(range(3))  # Any 1,2 or 3 long iterable
Vec(0, 1, 2)
>>> Vec(1, 2, 3) * 2
Vec(2, 4, 6)
>>> Vec.from_str('<4 2 -45>')  # Parse strings.
Vec(4, 2, -45)

Operators and comparisons will treat 3-tuples interchangably with vectors, which is more convenient when specifying constant values. >>> Vec(3, 8, 7) - (0, 3, 4) Vec(3, 5, 3)

Addition/subtraction can be performed between either vectors or scalar values (applying equally to all axes). Multiplication/division must be performed between a vector and scalar to scale - use Vec.dot() or Vec.cross() for those operations.

Values can be modified by either setting/getting x, y and z attributes. In addition, the following indexes are allowed (case-insensitive): * 0 1 2 * “x”, “y”, “z”

__init__(
x: int | float | VecBase | Iterable[float] = 0.0,
y: float = 0.0,
z: float = 0.0,
) None

Create a Vector.

All values are converted to floats automatically. If no value is given, that axis will be set to 0. An iterable can be passed in (as the x argument), which will be used for x, y, and z.

property x: float

The X axis of the vector.

property y: float

The Y axis of the vector.

property z: float

The Z axis of the vector.

classmethod with_axes(
axis1: str,
val1: int | float | SupportsFloat | SupportsIndex | VecBase,
axis2: str | None = None,
val2: int | float | SupportsFloat | SupportsIndex | VecBase = 0.0,
axis3: str | None = None,
val3: int | float | SupportsFloat | SupportsIndex | VecBase = 0.0,
) Vec

Create a Vector, given a number of axes and corresponding values.

This is a convenience for doing the following:

vec = Vec()
vec[axis1] = val1
vec[axis2] = val2
vec[axis3] = val3

The magnitudes can also be Vectors, in which case the matching axis will be used from the vector.

__repr__() str

Code required to reproduce this vector.

freeze() FrozenVec

Return an immutable version of this vector.

copy() Vec

Create a duplicate of this vector.

__copy__() Vec

Create a duplicate of this vector.

cross(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) Vec

Return the cross product of both Vectors.

If this is called as a method (a.cross(b)), the result will have the same type as a. Otherwise, if called as Vec.cross(a, b) or FrozenVec.cross(a, b), the type of the class takes priority.

__iadd__(
other: VecBase | tuple | float,
)

+= operation.

Like the normal one except without duplication.

__isub__(
other: VecBase | tuple | float,
)

-= operation.

Like the normal one except without duplication.

__imul__(other: float)

*= operation.

Like the normal one except without duplication.

__itruediv__(other: float)

/= operation.

Like the normal one except without duplication.

__ifloordiv__(other: float)

//= operation.

Like the normal one except without duplication.

__imod__(other: float)

%= operation.

Like the normal one except without duplication.

__imatmul__(
other: AngleBase | MatrixBase,
) Vec

We need to define this, so it’s in-place.

__reduce__() Tuple[Callable[[float, float, float], Vec], Tuple[float, float, float]]

Pickling support.

This redirects to a global function, so C/Python versions interoperate.

__setitem__(
ind: str | int,
val: int | float | SupportsFloat | SupportsIndex,
) None

Allow editing values by index instead of name if desired.

This accepts either 0, 1, 2 or x, y, z to edit values. Useful in conjunction with a loop to apply commands to all values.

rotate(
pitch: float = 0.0,
yaw: float = 0.0,
roll: float = 0.0,
round_vals: bool = True,
) Vec

Old method to rotate a vector by a Source rotational angle.

Deprecated:

do Vec(...) @ Angle(...) instead.

If round is True, all values will be rounded to 6 decimals (since these calculations always have small inprecision.)

rotate_by_str(
ang: str,
pitch: float = 0.0,
yaw: float = 0.0,
roll: float = 0.0,
round_vals: bool = True,
) Vec

Rotate a vector, using a string instead of a vector.

Deprecated:

use Vec(…) @ Angle.from_str(…) instead.

to_angle_roll(
z_norm: Vec | FrozenVec,
stride: int = 0,
) Angle

Produce a Source Engine angle with roll.

Deprecated:

Use MatrixBase.from_basis() and then MatrixBase.to_angle(). from_basis() can take any two direction pairs.

Parameters:
  • z_norm – This must be at right angles to this vector. The resulting angle’s +z axis will point in this direction.

  • stride – is no longer used, it defined the roll angles to try.

rotation_around(
rot: float = 90,
) Angle

For an axis-aligned normal, return the angles which rotate around it.

Deprecated:

Use MatrixBase.axis_angle() and then MatrixBase.to_angle(). axis_angle() works for any arbitary axis.

max(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) None

Set this vector’s values to the maximum of the two vectors.

min(
other: VecBase | Vec_tuple | Tuple[float, float, float],
) None

Set this vector’s values to be the minimum of the two vectors.

localise(
origin: VecBase | Vec_tuple | Tuple[float, float, float],
angles: Angle | FrozenAngle | Matrix | FrozenMatrix | None = None,
) None

Shift this point to be local to the given position and angles.

This effectively translates local-space offsets to a global location, given the parent’s origin and angles. This is an in-place version of self @ angles + origin.

transform() Iterator[Matrix]

Perform rotations on this Vector efficiently.

Used as a context manager, which returns a matrix. When the body is exited safely, the matrix is applied to the angle.


class srctools.math.Vec_tuple(x: float, y: float, z: float)

An immutable tuple, useful for dictionary keys.

x: float

Alias for field number 0

y: float

Alias for field number 1

z: float

Alias for field number 2

Euler Angles

class srctools.math.AngleBase(*args: Any, **kwargs: Any)

Internal base class for Euler angles, implements common code.

__hash__ = None
__init__(
*args: Any,
**kwargs: Any,
) None

AngleBase cannot be instantiated.

property pitch: float

The Y-axis rotation, performed second.

property yaw: float

The Z-axis rotation, performed last.

property roll: float

The X-axis rotation, performed first.

classmethod from_str(
val: str | AngleBase,
pitch: float = 0.0,
yaw: float = 0.0,
roll: float = 0.0,
) AngleT

Convert a string in the form (4 6 -4) into an Angle.

If the string is unparsable, the provided default values are used instead. The string can be surrounded by any of the (), {}, [], <> bracket types, which are simply ignored.

If the value is already an Angle, a copy will be returned. To only do parsing, use parse_vec_str().

classmethod from_basis(
*,
x: Vec | FrozenVec | None = None,
y: Vec | FrozenVec | None = None,
z: Vec | FrozenVec | None = None,
) AngleT

Return the rotation which results in the specified local axes.

At least two must be specified, with the third computed if necessary.

classmethod with_axes(
*args: str | float | AngleBase,
**kwargs: str | float | AngleBase,
) AngleT

Create an Angle, given a number of axes and corresponding values.

This is a convenience for doing the following:

ang = Angle()
ang[axis1] = val1
ang[axis2] = val2
ang[axis3] = val3

The magnitudes can also be Angles, in which case the matching axis will be used from the angle.

join(delim: str = ', ') str

Return a string with all numbers joined by the passed delimiter.

This strips off the .0 if no decimal portion exists.

__str__() str

Return the values, separated by spaces.

This is the main format in Valve’s file formats, though identical to vectors. This strips off the .0 if no decimal portion exists.

__format__(format_spec: str) str

Control how the text is formatted.

as_tuple() Tuple[float, float, float]

Return the Angle as a tuple.

__len__() int

The length of an Angle is always 3.

__iter__() Iterator[float]

Iterating over the angles returns each value in turn.

__reversed__() Iterator[float]

Iterating over the angles returns each value in turn.

__getitem__(ind: str | int) float

Allow reading values by index instead of name if desired.

This accepts the following indexes to read values: - 0, 1, 2 - "pitch", "yaw", "roll" - "pit", "yaw", "rol" - "p", "y", "r" Useful in conjunction with a loop to apply commands to all values.

__eq__(other: object) bool

== test.

Two Angles are equal if all three axes are the same. An Angle can be compared with a 3-tuple as if it was an Angle also. A tolerance of 1e-6 is accounted for automatically.

__ne__(other: object) bool

!= test.

Two Angles are equal if all three axes are the same. An Angle can be compared with a 3-tuple as if it was an Angle also. A tolerance of 1e-6 is accounted for automatically.

__mul__(
other: int | float,
) AngleT

Angle * float multiplies each value.

__rmul__(
other: int | float,
) AngleT

Angle * float multiplies each value.

__matmul__(
other: AngleBase | MatrixBase,
) AngleT

Angle @ Angle or Angle @ Matrix rotates the first by the second.

__rmatmul__(
other: AngleBase | Tuple[float, float, float] | VecT,
) Vec | FrozenVec | VecT | AngleT

Vec @ Angle rotates the first by the second.

class srctools.math.FrozenAngle(
pitch: int | float | Iterable[int | float] = 0.0,
yaw: int | float = 0.0,
roll: int | float = 0.0,
)

Represents an immutable pitch-yaw-roll Euler angle.

static __new__(
cls,
pitch: int | float | Iterable[int | float] = 0.0,
yaw: int | float = 0.0,
roll: int | float = 0.0,
) FrozenAngle

Create a FrozenAngle.

All values are converted to Floats automatically. If no value is given, that axis will be set to 0. An iterable can be passed in (as the pitch argument), which will be used for pitch, yaw, and roll. This includes Vectors and other Angles.

classmethod with_axes(
axis1: str,
val1: float | AngleBase,
axis2: str | None = None,
val2: float | AngleBase = 0.0,
axis3: str | None = None,
val3: float | AngleBase = 0.0,
) FrozenAngle

Create an Angle, given a number of axes and corresponding values.

This is a convenience for doing the following:

ang = Angle()
ang[axis1] = val1
ang[axis2] = val2
ang[axis3] = val3

The magnitudes can also be Angles, in which case the matching axis will be used from the angle.

__reduce__() Tuple[Callable[[float, float, float], FrozenAngle], Tuple[float, float, float]]

Pickling support.

This redirects to a global function, so C/Python versions interoperate.

__hash__() int

Hashing a frozen angle is the same as hashing the tuple form.

thaw() Angle

Return a mutable copy of this angle.

copy() FrozenAngle

FrozenAngle is immutable.

__copy__() FrozenAngle

FrozenAngle is immutable.

__deepcopy__(
memodict: Dict[str, Any] | None = None,
) FrozenAngle

FrozenAngle is immutable.

__repr__() str

Return repr(self).

class srctools.math.Angle(
pitch: int | float | Iterable[int | float] = 0.0,
yaw: int | float = 0.0,
roll: int | float = 0.0,
)

Represents a pitch-yaw-roll Euler angle.

>>> Angle(45, 0, z=-90)  # Positional or vec, defaults to 0.
Angle(45, 0, 270)
>> Vec(range(0, 270, 90))  # Any 1,2 or 3 long iterable
Vec(0, 90, 180)
>>> Vec(1, 2, 3) @ Angle(0, 0, 45)
Vec(1, -0.707107, 3.53553)
>>> Angle.from_str('(45 90 0)')  # Parse strings.
Angle(45, 90, 0)

Addition and subtraction can be performed between angles, while division/multiplication must be between an angle and scalar (to scale).

Like vectors, each axis can be accessed by getting/setting pitch/yaw and roll attributes. In addition, the following indexes are allowed (case-insensitive):

  • 0, 1, 2

  • "p", "y", r

  • "pitch", "yaw", "roll"

  • "pit", "yaw", "rol"

All values are remapped to between 0-360 when set.

__init__(
pitch: int | float | Iterable[int | float] = 0.0,
yaw: int | float = 0.0,
roll: int | float = 0.0,
) None

Create an Angle.

All values are converted to :external:py:class`float`s automatically. If no value is given, that axis will be set to 0. An iterable can be passed in (as the pitch argument), which will be used for pitch, yaw, and roll. This includes Vectors and other Angles.

copy() Angle

Create a duplicate of this angle.

__copy__() Angle

Create a duplicate of this angle.

__reduce__() Tuple[Callable[[float, float, float], Angle], Tuple[float, float, float]]

Pickling support.

This redirects to a global function, so C/Python versions interoperate.

freeze() FrozenAngle

Return an immutable copy of this angle.

property pitch: float

The Y-axis rotation, performed second.

property yaw: float

The Z-axis rotation, performed last.

property roll: float

The X-axis rotation, performed first.

__repr__() str

Return repr(self).

classmethod with_axes(
axis1: str,
val1: float | AngleBase,
axis2: str | None = None,
val2: float | AngleBase = 0.0,
axis3: str | None = None,
val3: float | AngleBase = 0.0,
) Angle

Create an Angle, given a number of axes and corresponding values.

This is a convenience for doing the following:

ang = Angle()
ang[axis1] = val1
ang[axis2] = val2
ang[axis3] = val3

The magnitudes can also be Angles, in which case the matching axis will be used from the angle.

__setitem__(
ind: str | int,
value: int | float | SupportsFloat | SupportsIndex,
) None

Allow editing values by index instead of name if desired.

This accepts the following indexes to edit values: - 0, 1, 2 - "pitch", "yaw", "roll" - "pit", "yaw", "rol" - "p", "y", "r" Useful in conjunction with a loop to apply commands to all values.

__imul__(
other: int | float,
) Angle

Angle *= float multiplies each value.

__imatmul__(
other: AngleBase | MatrixBase,
) Angle

Angle @ Angle or Angle @ Matrix rotates the first by the second.

transform() Iterator[Matrix]

Perform transformations on this angle.

Used as a context manager, which returns a matrix. When the body is exited safely, the matrix is applied to the angle.

Rotation Matrices

class srctools.math.MatrixBase(
matrix: MatrixBase | None = None,
)

Common code for both matrix versions.

__init__(
matrix: MatrixBase | None = None,
) None

MatrixBase cannot be instantiated.

__hash__ = None
__eq__(other: object) bool

Return self==value.

__repr__() str

Return repr(self).

copy() MatrixT

Duplicate this matrix.

classmethod from_pitch(
pitch: float,
) MatrixT

Return the matrix representing a pitch rotation (Y axis).

classmethod from_yaw(
yaw: float,
) MatrixT

Return the matrix representing a yaw rotation (Z axis).

classmethod from_roll(
roll: float,
) MatrixT

Return the matrix representing a roll rotation (X axis).

classmethod from_angle(
pitch: AngleBase | float,
yaw: float | None = None,
roll: float | None = None,
) MatrixT

Return the rotation representing an Euler angle.

Either an Angle can be passed, or the raw pitch/yaw/roll angles.

classmethod from_angstr(
val: str | Angle,
pitch: float = 0.0,
yaw: float = 0.0,
roll: float = 0.0,
) MatrixT

Parse a string of the form “pitch yaw roll”, then convert to a Matrix.

This is equivalent to combining MatrixBase.from_angle() and AngleBase.from_str(), except more efficient.

classmethod axis_angle(
axis: Vec | Tuple[float, float, float],
angle: float,
) MatrixT

Compute the rotation matrix forming a rotation around an axis by a specific angle.

forward(
mag: float = 1.0,
) Vec

Return a vector with the given magnitude pointing along the X axis.

left(mag: float = 1.0) Vec

Return a vector with the given magnitude pointing along the Y axis.

up(mag: float = 1.0) Vec

Return a vector with the given magnitude pointing along the Z axis.

__getitem__(
item: Tuple[int, int],
) float

Retrieve an individual matrix value by x, y position (0-2).

__iter__: None = None

Iteration doesn’t make much sense.

to_angle() Angle

Return an Euler angle replicating this rotation.

transpose() MatrixT

Return the transpose of this matrix.

inverse() MatrixT

Return the inverse of this matrix.

Raises:

ArithmeticError – If the matrix does not have an inverse.

classmethod from_basis(
*,
x: Vec | FrozenVec | None = None,
y: Vec | FrozenVec | None = None,
z: Vec | FrozenVec | None = None,
) MatrixT

Construct a matrix from existing basis vectors.

If at least two are provided, only one result is possible. Otherwise, the result is insufficiently constrained, so multiple results are valid. In those cases one of the remaining axes will be positioned on the horizontal plane. With no parameters, this returns the identity matrix (but just call the constructor for that).

class srctools.math.FrozenMatrix(
matrix: MatrixBase | None = None,
)

Represents an immutable rotation via a transformation matrix.

When performing multiple rotations, it is more efficient to create one of these instead of using an Angle directly. To construct a rotation, use one of the several classmethods available depending on what rotation is desired.

static __new__(
cls,
matrix: MatrixBase | None = None,
) FrozenMatrix

Create a new matrix.

If an existing matrix is supplied, it will be copied. Otherwise, an identity matrix is produced.

thaw() Matrix

Return a mutable copy of this matrix.

copy() FrozenMatrix

Frozen matrices are immutable.

__copy__() FrozenMatrix

Frozen matrices are immutable.

__deepcopy__(
memodict: Dict[str, Any] | None = None,
) FrozenMatrix

Frozen matrices are immutable.

__reduce__() Tuple[Callable[[float, float, float, float, float, float, float, float, float], FrozenMatrix], Tuple[float, float, float, float, float, float, float, float, float]]

Pickling support.

This redirects to a global function, so C/Python versions interoperate.

__weakref__

list of weak references to the object (if defined)

class srctools.math.Matrix(matrix: MatrixBase | None = None)

Represents a rotation via a transformation matrix.

When performing multiple rotations, it is more efficient to create one of these instead of using an Angle directly. To construct a rotation, use one of the several classmethods available depending on what rotation is desired.

__init__(
matrix: MatrixBase | None = None,
) None

Create a new matrix.

If an existing matrix is supplied, it will be copied. Otherwise, an identity matrix is produced.

freeze() FrozenMatrix

Return a frozen copy of this matrix.

copy() Matrix

Duplicate this matrix.

__copy__() Matrix

Duplicate this matrix.

__deepcopy__(
memodict: Dict[str, Any] | None = None,
) Matrix

Duplicate this matrix.

__reduce__() Tuple[Callable[[float, float, float, float, float, float, float, float, float], Matrix], Tuple[float, float, float, float, float, float, float, float, float]]

Pickling support.

This redirects to a global function, so C/Python versions interoperate.

__setitem__(
item: Tuple[int, int],
value: int | float | SupportsFloat | SupportsIndex,
) None

Set an individual matrix value by x, y position (0-2).

__weakref__

list of weak references to the object (if defined)