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
tuple[x, y, z] @ 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.
Free Functions
- srctools.math.parse_vec_str( ) 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
orAngleBase
, this will be passed through. If you do want a specific class, useVecBase.from_str()
,AngleBase.from_str()
orMatrixBase.from_angstr()
.
- srctools.math.lerp( ) 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,
Convert various values to a rotation matrix.
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"
, andINV_AXIS["y"]
returns("x", "z")
.
- classmethod from_str( ) 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,
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( ) 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( ) Iterator[VecT]
Loop over points in a bounding box. All coordinates should be integers.
Both borders will be included.
- iter_line( ) 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,
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 isVec(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.
- __add__( )
+
operation.This additionally works on scalars (adds to all axes).
- __radd__( )
+
operation with reversed operands.This additionally works on scalars (adds to all axes).
- __sub__( )
-
operation.This additionally works on scalars (adds to all axes).
- __rsub__( )
-
operation with reversed operands.This additionally works on scalars (adds to all axes).
- __divmod__(
- other: float,
Divide the vector by a scalar, returning the result and remainder.
- __rdivmod__(
- other: float,
Divide a scalar by a vector, returning the result and remainder.
- __matmul__(
- other: AngleBase | MatrixBase,
Rotate this vector by an angle or matrix.
- __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.
- classmethod lerp( ) VecT
Linerarly interpolate between two vectors.
- Raises:
ZeroDivisionError – If
in_min
andin_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,
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.
- 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.
- __getitem__(ind: str | int) float
Allow reading values by index instead of name if desired.
This accepts either
0
,1
,2
orx
,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],
Check if this point is inside the specified bounding box.
- static bbox_intersect( ) bool
Check if the
(min1, max1)
bounding box intersects the(min2, max2)
bounding box.
- 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( ) float
Return the dot product of both Vectors.
Tip: using this in the form
Vec.dot(a, b)
may be more readable.
- cross( ) 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 asa
. Otherwise, if called asVec.cross(a, b)
orFrozenVec.cross(a, b)
, the type of the class takes priority.
- class srctools.math.FrozenVec( )
Immutable vector class. This cannot be changed once created, but is hashable.
- static __new__( ) FrozenVec
Create a
FrozenVec
.All values are converted to
float
s automatically. If no value is given, that axis will be set to0
. An iterable can be passed in (as thex
argument), which will be used forx
,y
, andz
.
- 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,
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.
- __reduce__() Tuple[Callable[[float, float, float], FrozenVec], Tuple[float, float, float]]
Pickling support.
This redirects to a global function, so C/Python versions interoperate.
- class srctools.math.Vec( )
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__( ) None
Create a Vector.
All values are converted to
float
s automatically. If no value is given, that axis will be set to0
. An iterable can be passed in (as thex
argument), which will be used forx
,y
, andz
.
- 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,
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.
- cross( ) 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 asa
. Otherwise, if called asVec.cross(a, b)
orFrozenVec.cross(a, b)
, the type of the class takes priority.
- __iadd__( )
+=
operation.Like the normal one except without duplication.
- __isub__( )
-=
operation.Like the normal one except without duplication.
- __imatmul__(
- other: AngleBase | MatrixBase,
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,
Allow editing values by index instead of name if desired.
This accepts either
0
,1
,2
orx
,y
,z
to edit values. Useful in conjunction with a loop to apply commands to all values.
- rotate( ) 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( ) Vec
Rotate a vector, using a string instead of a vector.
- Deprecated:
use Vec(…) @ Angle.from_str(…) instead.
- to_angle_roll( ) Angle
Produce a Source Engine angle with roll.
- Deprecated:
Use
MatrixBase.from_basis()
and thenMatrixBase.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,
For an axis-aligned normal, return the angles which rotate around it.
- Deprecated:
Use
MatrixBase.axis_angle()
and thenMatrixBase.to_angle()
.axis_angle()
works for any arbitary axis.
- localise(
- origin: VecBase | Vec_tuple | Tuple[float, float, float],
- angles: Angle | FrozenAngle | Matrix | FrozenMatrix | 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
.
Euler Angles
- class srctools.math.AngleBase(*args: Any, **kwargs: Any)
Internal base class for Euler angles, implements common code.
- __hash__ = None
- classmethod from_str( ) 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,
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( ) 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.
- __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.
- __matmul__(
- other: AngleBase | MatrixBase,
Angle @ Angle or Angle @ Matrix rotates the first by the second.
- class srctools.math.FrozenAngle( )
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,
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,
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.
- copy() FrozenAngle
FrozenAngle is immutable.
- __copy__() FrozenAngle
FrozenAngle is immutable.
- class srctools.math.Angle( )
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
androll
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__( ) 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 thepitch
argument), which will be used forpitch
,yaw
, androll
. This includes Vectors and other Angles.
- __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.
- 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,
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,
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.
- __imatmul__(
- other: AngleBase | MatrixBase,
Angle
@
Angle or Angle@
Matrix rotates the first by the second.
Rotation Matrices
- class srctools.math.MatrixBase(
- matrix: MatrixBase | None = None,
Common code for both matrix versions.
- __init__(
- matrix: MatrixBase | None = None,
MatrixBase cannot be instantiated.
- __hash__ = None
- classmethod from_pitch(
- pitch: float,
Return the matrix representing a pitch rotation (Y axis).
- classmethod from_yaw(
- yaw: float,
Return the matrix representing a yaw rotation (Z axis).
- classmethod from_roll(
- roll: float,
Return the matrix representing a roll rotation (X axis).
- classmethod from_angle( ) MatrixT
Return the rotation representing an Euler angle.
Either an Angle can be passed, or the raw pitch/yaw/roll angles.
- classmethod from_angstr( ) MatrixT
Parse a string of the form “pitch yaw roll”, then convert to a Matrix.
This is equivalent to combining
MatrixBase.from_angle()
andAngleBase.from_str()
, except more efficient.
- classmethod axis_angle( ) MatrixT
Compute the rotation matrix forming a rotation around an axis by a specific angle.
- forward(
- mag: float = 1.0,
Return a vector with the given magnitude pointing along the X axis.
- 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,
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,
Create a new matrix.
If an existing matrix is supplied, it will be copied. Otherwise, an identity matrix is produced.
- copy() FrozenMatrix
Frozen matrices are immutable.
- __copy__() FrozenMatrix
Frozen matrices are immutable.
- __deepcopy__( ) 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,
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.
- __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,
Set an individual matrix value by x, y position (0-2).
- __weakref__
list of weak references to the object (if defined)