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 @ Angle -> Vec
  • Vec @ Matrix -> Vec
  • 3-tuple @ Angle -> Vec
  • Angle @ Angle -> Angle
  • Angle @ Matrix -> Angle
  • Matrix @ Matrix -> Matrix

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.


srctools.math.parse_vec_str(
val: Union[str, Vec, Angle],
x: Union[T1, float] = 0.0,
y: Union[T2, float] = 0.0,
z: Union[T3, float] = 0.0,
) Tuple[Union[T1, float], Union[T2, float], Union[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 Vec or Angle, this will be passed through. If you do want a specific class, use Vec.from_str(), Angle.from_str() or Matrix.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() Matrix

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.


class srctools.math.Vec(
x: Union[int, float, Vec, 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”

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

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

__init__(
x: Union[int, float, Vec, 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.

copy() Vec

Create a duplicate of this vector.

__copy__() Vec

Create a duplicate of this vector.

__reduce__() tuple

Pickling support.

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

classmethod from_str(
val: Union[str, Vec],
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
) Vec

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(axis1: str, val1: Union[float, Vec]) Vec
classmethod with_axes(
axis1: str,
val1: Union[float, Vec],
axis2: str,
val2: Union[float, Vec],
) Vec
classmethod with_axes(
axis1: str,
val1: Union[float, Vec],
axis2: str,
val2: Union[float, Vec],
axis3: str,
val3: Union[float, Vec],
) 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.

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.
static bbox(__point: Iterable[Vec]) Tuple[Vec, Vec]
static bbox(*points: Vec) Tuple[Vec, Vec]

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: Vec,
max_pos: Vec,
stride: int = 1,
) Iterator[Vec]

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

Both borders will be included.

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

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() str

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.
to_angle_roll(z_norm: Vec, stride: int = 0) Angle

Produce a Source Engine angle with roll.

Deprecated:

Use Matrix.from_basis() and then Matrix.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 Matrix.axis_angle() and then Matrix.to_angle(). axis_angle() works for any arbitary axis.
__abs__() Vec

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

__add__(other: Union[Vec, tuple, float])

+ operation.

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

__radd__(other: Union[Vec, tuple, float])

+ operation with reversed operands.

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

__iadd__(other: Union[Vec, tuple, float])

+= operation.

Like the normal one except without duplication.

__sub__(other: Union[Vec, tuple, float])

- operation.

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

__rsub__(other: Union[Vec, tuple, float])

- operation with reversed operands.

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

__isub__(other: Union[Vec, tuple, float])

-= operation.

Like the normal one except without duplication.

__mul__(other: float)

Vector * scalar operation.

__rmul__(other: float)

scalar * Vector operation.

__imul__(other: float)

*= operation.

Like the normal one except without duplication.

__truediv__(other: float)

Vector / scalar operation.

__rtruediv__(other: float)

scalar / Vector operation.

__itruediv__(other: float)

/= operation.

Like the normal one except without duplication.

__floordiv__(other: float)

Vector // scalar operation.

__rfloordiv__(other: float)

scalar // Vector operation.

__ifloordiv__(other: float)

//= operation.

Like the normal one except without duplication.

__mod__(other: float)

Vector % scalar operation.

__rmod__(other: float)

scalar % Vector operation.

__imod__(other: float)

%= operation.

Like the normal one except without duplication.

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

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

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

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

__matmul__(other: Union[Angle, Matrix]) Vec

Rotate this vector by an angle or matrix.

__imatmul__(other: Union[Angle, Matrix]) Vec

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

__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__() 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__() 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__() 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__() 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.

max() None

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

min() None

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

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

Linerarly interpolate between two vectors.

Raises:ZeroDivisionError – If in_min and in_max are the same.
__round__() Any
__round__(ndigits: int) Vec

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 formated with the provided specification, joined by spaces.

__repr__() str

Produce the code required to reproduce this vector.

__iter__() Iterator[float]

Iterating through the vector yields each axis in order.

__reversed__() Iterator[float]

Allow iterating through the dimensions, in reverse.

__getitem__(ind: Union[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.

__setitem__(ind: Union[str, int], val: float) 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.

in_bbox() bool

Check if this point is inside the specified bounding box.

static bbox_intersect(
min1: Vec,
max1: Vec,
min2: Vec,
max2: Vec,
) 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__() Vec

The inverted form of a Vector has inverted axes.

__pos__() Vec

+ on a Vector simply copies it.

norm() Vec

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() float

Return the dot product of both Vectors.

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

cross() Vec

Return the cross product of both Vectors.

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

localise(
origin: Union[Vec, Tuple[float, float, float]],
angles: Optional[Union[Angle, Matrix]] = 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.

norm_mask(normal: Vec) Vec

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.

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.

__hash__ = None

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


class srctools.math.Angle(
pitch: Union[int, float, Iterable[Union[int, float]]] = 0.0,
yaw: Union[int, float] = 0.0,
roll: Union[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: Union[int, float, Iterable[Union[int, float]]] = 0.0,
yaw: Union[int, float] = 0.0,
roll: Union[int, float] = 0.0,
) None

Create an Angle.

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.

copy() Angle

Create a duplicate of this angle.

__copy__() Angle

Create a duplicate of this angle.

__reduce__() tuple

Pickling support.

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

classmethod from_str(
val: Union[str, Angle],
pitch: float = 0.0,
yaw: float = 0.0,
roll: float = 0.0,
) Angle

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().

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.

__hash__ = None
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.

__repr__() str

Return repr(self).

__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.

classmethod with_axes(axis1: str, val1: Union[float, Angle]) Angle
classmethod with_axes(
axis1: str,
val1: Union[float, Angle],
axis2: str,
val2: Union[float, Angle],
) Angle
classmethod with_axes(
axis1: str,
val1: Union[float, Angle],
axis2: str,
val2: Union[float, Angle],
axis3: str,
val3: Union[float, Angle],
) 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.

classmethod from_basis(*, x: Vec, y: Vec, z: Vec) Angle
classmethod from_basis(*, x: Vec, y: Vec) Angle
classmethod from_basis(*, y: Vec, z: Vec) Angle
classmethod from_basis(*, x: Vec, z: Vec) Angle

Return the rotation which results in the specified local axes.

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

__getitem__(ind: Union[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.

__setitem__(ind: Union[str, int], val: float) 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.

__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 a 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 a Angle also. A tolerance of 1e-6 is accounted for automatically.

__mul__(other: Union[int, float]) Angle

Angle * float multiplies each value.

__rmul__(other: Union[int, float]) Angle

Angle * float multiplies each value.

__matmul__() Angle

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

__imatmul__() Angle

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

__rmatmul__(other: Angle) Angle
__rmatmul__(other: Tuple[float, float, float]) Vec
__rmatmul__(other: Vec) Vec

Vec @ Angle 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.


class srctools.math.Matrix

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__() None

Create a matrix set to the identity transform.

__eq__(other: object) bool

Return self==value.

__repr__() str

Return repr(self).

copy() Matrix

Duplicate this matrix.

__reduce__() tuple

Pickling support.

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

classmethod from_pitch(pitch: float) Matrix

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

classmethod from_yaw(yaw: float) Matrix

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

classmethod from_roll(roll: float) Matrix

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

classmethod from_angle(__angle: Angle) Matrix
classmethod from_angle(pitch: float, yaw: float, roll: float) Matrix

Return the rotation representing an Euler angle.

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

classmethod from_angstr(
val: Union[str, Angle],
pitch: float = 0.0,
yaw: float = 0.0,
roll: float = 0.0,
) Matrix

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

This is equivalent to combining Matrix.from_angle() and Angle.from_str(), except more efficient.

classmethod axis_angle(
axis: Union[Vec, Tuple[float, float, float]],
angle: float,
) Matrix

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).

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

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

__iter__ = None

Iteration doesn’t make much sense.

to_angle() Angle

Return an Euler angle replicating this rotation.

transpose() Matrix

Return the transpose of this matrix.

classmethod from_basis(*, x: Vec, y: Vec, z: Vec) Matrix
classmethod from_basis(*, x: Vec, y: Vec) Matrix
classmethod from_basis(*, y: Vec, z: Vec) Matrix
classmethod from_basis(*, x: Vec, z: Vec) Matrix

Construct a matrix from at least two basis vectors.

The third is computed, if not provided.

__hash__ = None