Skip to content

Math Types

The SDK ships a small math layer used across the runtime and gRPC APIs:

  • Vector3 / Quaternion: lightweight 3D vector and rotation types (provided by pyglm)
  • Transform / Pose / AABB: convenience wrappers for common spatial data
  • Helpers for conversion and geometry operations

Units

TongSIM Lite follows Unreal Engine conventions: positions are in centimeters (UU).


Key Types

Type What it represents Typical usage
Vector3 3D vector positions, extents, directions
Quaternion rotation orientation for cameras/actors
Transform location + rotation + scale gRPC transforms, coordinate conversion
Pose location + rotation simple pose passing
AABB axis-aligned bounding box bounding/overlap checks in Python

Helper functions

  • dot, cross, normalize, length, lerp
  • degrees_to_radians, radians_to_degrees
  • euler_to_quaternion, quaternion_to_euler
  • calc_camera_look_at_rotation

API References

tongsim.math.geometry.type.Pose

A lightweight container that groups location and rotation as a pose.

Source code in src/tongsim/math/geometry/type.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Pose:
    """
    A lightweight container that groups `location` and `rotation` as a pose.
    """

    __slots__ = ("location", "rotation")

    def __init__(
        self, location: Vector3 | None = None, rotation: Quaternion | None = None
    ):
        self.location = location if location is not None else Vector3(0.0, 0.0, 0.0)
        self.rotation = (
            rotation if rotation is not None else Quaternion(1.0, 0.0, 0.0, 0.0)
        )

    def __repr__(self) -> str:
        return f"Pose(location={self.location}, rotation={self.rotation})"

    def copy(self) -> "Pose":
        """
        Return a deep copy of this pose.
        """
        return Pose(Vector3(self.location), Quaternion(self.rotation))

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, Pose)
            and self.location == other.location
            and self.rotation == other.rotation
        )

    def to_transform(self) -> "Transform":
        """
        Convert this pose to a `Transform`.
        """
        return Transform(self.location, self.rotation, Vector3(1.0, 1.0, 1.0))

copy

copy() -> Pose

Return a deep copy of this pose.

Source code in src/tongsim/math/geometry/type.py
34
35
36
37
38
def copy(self) -> "Pose":
    """
    Return a deep copy of this pose.
    """
    return Pose(Vector3(self.location), Quaternion(self.rotation))

to_transform

to_transform() -> Transform

Convert this pose to a Transform.

Source code in src/tongsim/math/geometry/type.py
47
48
49
50
51
def to_transform(self) -> "Transform":
    """
    Convert this pose to a `Transform`.
    """
    return Transform(self.location, self.rotation, Vector3(1.0, 1.0, 1.0))

tongsim.math.geometry.type.Transform

A spatial transform with location, rotation, and scale.

This structure is aligned with Unreal Engine's Transform concept.

Source code in src/tongsim/math/geometry/type.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
class Transform:
    """
    A spatial transform with `location`, `rotation`, and `scale`.

    This structure is aligned with Unreal Engine's Transform concept.
    """

    __slots__ = ("location", "rotation", "scale")

    def __init__(
        self,
        location: Vector3 | None = None,
        rotation: Quaternion | None = None,
        scale: Vector3 | None = None,
    ):
        self.location = location if location is not None else Vector3(0.0, 0.0, 0.0)
        self.rotation = (
            rotation if rotation is not None else Quaternion(1.0, 0.0, 0.0, 0.0)
        )
        self.scale = scale if scale is not None else Vector3(1.0, 1.0, 1.0)

    def __repr__(self) -> str:
        return (
            f"Transform(location={self.location}, "
            f"rotation={self.rotation}, scale={self.scale})"
        )

    def copy(self) -> "Transform":
        """
        Return a deep copy of this transform.
        """
        return Transform(
            Vector3(self.location),
            Quaternion(self.rotation),
            Vector3(self.scale),
        )

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, Transform)
            and self.location == other.location
            and self.rotation == other.rotation
            and self.scale == other.scale
        )

    def __mul__(self, other: "Transform") -> "Transform":
        """
        Compose two transforms (right-multiplication) and return a new transform.

        This is equivalent to applying `other` first, then `self`.

        Args:
            other (Transform): The other transform to compose with.

        Returns:
            Transform: The composed transform.
        """
        if not isinstance(other, Transform):
            return NotImplemented

        # Multiply transform matrices.
        m = self.to_matrix() * other.to_matrix()
        # print (m)

        # Extract translation from the matrix.
        loc = Vector3(m[3].x, m[3].y, m[3].z)

        # Extract scale.
        sx = _glm.length(Vector3(m[0].x, m[0].y, m[0].z))
        sy = _glm.length(Vector3(m[1].x, m[1].y, m[1].z))
        sz = _glm.length(Vector3(m[2].x, m[2].y, m[2].z))
        scale = Vector3(sx, sy, sz)

        # Extract rotation (remove scale first).
        rot_mat = Mat4(m)
        rot_mat[0] /= sx
        rot_mat[1] /= sy
        rot_mat[2] /= sz
        rot = _glm.quat_cast(rot_mat)

        return Transform(loc, rot, scale)

    def to_matrix(self) -> Mat4:
        """
        Return the 4x4 affine transformation matrix for this transform.

        The effective order is: scale → rotate → translate.
        """

        t = translate(Mat4(1.0), self.location)
        r = mat4_cast(self.rotation)
        s = glm_scale(Mat4(1.0), self.scale)
        return t * r * s  # Note the right-multiplication order.

    def transform_vector3(self, point: Vector3) -> Vector3:
        """
        Apply this transform to a 3D point and return the transformed result.
        """
        m = self.to_matrix()
        p = m * _glm.vec4(point, 1.0)  # Use homogeneous coordinates.
        return Vector3(p.x, p.y, p.z)

    def inverse(self) -> "Transform":
        """
        Return the inverse of this transform.

        Note: invert scale first, then rotation, then translation.
        """
        if self.scale.x == 0 or self.scale.y == 0 or self.scale.z == 0:
            raise ValueError(f"Cannot invert Transform with zero scale: {self.scale}")
        inv_scale = Vector3(
            1.0 / self.scale.x,
            1.0 / self.scale.y,
            1.0 / self.scale.z,
        )
        inv_rot = _glm.inverse(self.rotation)
        inv_loc = -(inv_rot * (inv_scale * self.location))
        return Transform(inv_loc, inv_rot, inv_scale)

copy

copy() -> Transform

Return a deep copy of this transform.

Source code in src/tongsim/math/geometry/type.py
81
82
83
84
85
86
87
88
89
def copy(self) -> "Transform":
    """
    Return a deep copy of this transform.
    """
    return Transform(
        Vector3(self.location),
        Quaternion(self.rotation),
        Vector3(self.scale),
    )

to_matrix

to_matrix() -> mat4

Return the 4x4 affine transformation matrix for this transform.

The effective order is: scale → rotate → translate.

Source code in src/tongsim/math/geometry/type.py
136
137
138
139
140
141
142
143
144
145
146
def to_matrix(self) -> Mat4:
    """
    Return the 4x4 affine transformation matrix for this transform.

    The effective order is: scale → rotate → translate.
    """

    t = translate(Mat4(1.0), self.location)
    r = mat4_cast(self.rotation)
    s = glm_scale(Mat4(1.0), self.scale)
    return t * r * s  # Note the right-multiplication order.

transform_vector3

transform_vector3(point: vec3) -> vec3

Apply this transform to a 3D point and return the transformed result.

Source code in src/tongsim/math/geometry/type.py
148
149
150
151
152
153
154
def transform_vector3(self, point: Vector3) -> Vector3:
    """
    Apply this transform to a 3D point and return the transformed result.
    """
    m = self.to_matrix()
    p = m * _glm.vec4(point, 1.0)  # Use homogeneous coordinates.
    return Vector3(p.x, p.y, p.z)

inverse

inverse() -> Transform

Return the inverse of this transform.

Note: invert scale first, then rotation, then translation.

Source code in src/tongsim/math/geometry/type.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def inverse(self) -> "Transform":
    """
    Return the inverse of this transform.

    Note: invert scale first, then rotation, then translation.
    """
    if self.scale.x == 0 or self.scale.y == 0 or self.scale.z == 0:
        raise ValueError(f"Cannot invert Transform with zero scale: {self.scale}")
    inv_scale = Vector3(
        1.0 / self.scale.x,
        1.0 / self.scale.y,
        1.0 / self.scale.z,
    )
    inv_rot = _glm.inverse(self.rotation)
    inv_loc = -(inv_rot * (inv_scale * self.location))
    return Transform(inv_loc, inv_rot, inv_scale)

tongsim.math.geometry.type.AABB

Axis-Aligned Bounding Box (AABB) in 3D space.

Attributes:

Name Type Description
min vec3

Minimum corner (smallest x, y, z).

max vec3

Maximum corner (largest x, y, z).

Source code in src/tongsim/math/geometry/type.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
class AABB:
    """
    Axis-Aligned Bounding Box (AABB) in 3D space.

    Attributes:
        min (Vector3): Minimum corner (smallest x, y, z).
        max (Vector3): Maximum corner (largest x, y, z).
    """

    __slots__ = ("max", "min")

    def __init__(self, min: Vector3, max: Vector3):
        self.min = min
        self.max = max

    def __repr__(self) -> str:
        return f"AABB(min={self.min}, max={self.max})"

    def deepcopy(self) -> "AABB":
        """
        Return a deep copy of this AABB.
        """
        return AABB(Vector3(self.min), Vector3(self.max))

    def center(self) -> Vector3:
        """
        Return the center point of the AABB.
        """
        return (self.min + self.max) * 0.5

    def extent(self) -> Vector3:
        """
        Return the size of the AABB (width, height, depth).
        """
        return self.max - self.min

    def contains_point(self, point: Vector3) -> bool:
        """
        Check whether a point lies inside the AABB.

        Args:
            point (Vector3): The point to test.

        Returns:
            bool: True if the point is inside; otherwise False.
        """
        return (
            self.min.x <= point.x <= self.max.x
            and self.min.y <= point.y <= self.max.y
            and self.min.z <= point.z <= self.max.z
        )

deepcopy

deepcopy() -> AABB

Return a deep copy of this AABB.

Source code in src/tongsim/math/geometry/type.py
192
193
194
195
196
def deepcopy(self) -> "AABB":
    """
    Return a deep copy of this AABB.
    """
    return AABB(Vector3(self.min), Vector3(self.max))

center

center() -> vec3

Return the center point of the AABB.

Source code in src/tongsim/math/geometry/type.py
198
199
200
201
202
def center(self) -> Vector3:
    """
    Return the center point of the AABB.
    """
    return (self.min + self.max) * 0.5

extent

extent() -> vec3

Return the size of the AABB (width, height, depth).

Source code in src/tongsim/math/geometry/type.py
204
205
206
207
208
def extent(self) -> Vector3:
    """
    Return the size of the AABB (width, height, depth).
    """
    return self.max - self.min

contains_point

contains_point(point: vec3) -> bool

Check whether a point lies inside the AABB.

Parameters:

Name Type Description Default
point vec3

The point to test.

required

Returns:

Name Type Description
bool bool

True if the point is inside; otherwise False.

Source code in src/tongsim/math/geometry/type.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def contains_point(self, point: Vector3) -> bool:
    """
    Check whether a point lies inside the AABB.

    Args:
        point (Vector3): The point to test.

    Returns:
        bool: True if the point is inside; otherwise False.
    """
    return (
        self.min.x <= point.x <= self.max.x
        and self.min.y <= point.y <= self.max.y
        and self.min.z <= point.z <= self.max.z
    )

tongsim.math.geometry.geometry.degrees_to_radians

degrees_to_radians(
    value: float | Vector3,
) -> float | Vector3

Convert degrees to radians.

Parameters:

Name Type Description Default
value float | Vector3

A scalar degree value or a degree-based Vector3.

required

Returns:

Type Description
float | Vector3

float | Vector3: The value converted to radians.

Source code in src/tongsim/math/geometry/geometry.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def degrees_to_radians(value: float | Vector3) -> float | Vector3:
    """
    Convert degrees to radians.

    Args:
        value (float | Vector3): A scalar degree value or a degree-based `Vector3`.

    Returns:
        float | Vector3: The value converted to radians.
    """
    if isinstance(value, Vector3):
        return Vector3(
            math.radians(value.x),
            math.radians(value.y),
            math.radians(value.z),
        )
    return math.radians(value)

tongsim.math.geometry.geometry.radians_to_degrees

radians_to_degrees(
    value: float | Vector3,
) -> float | Vector3

Convert radians to degrees.

Parameters:

Name Type Description Default
value float | Vector3

A scalar radian value or a radian-based Vector3.

required

Returns:

Type Description
float | Vector3

float | Vector3: The value converted to degrees.

Source code in src/tongsim/math/geometry/geometry.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def radians_to_degrees(value: float | Vector3) -> float | Vector3:
    """
    Convert radians to degrees.

    Args:
        value (float | Vector3): A scalar radian value or a radian-based `Vector3`.

    Returns:
        float | Vector3: The value converted to degrees.
    """
    if isinstance(value, Vector3):
        return Vector3(
            math.degrees(value.x),
            math.degrees(value.y),
            math.degrees(value.z),
        )
    return math.degrees(value)

tongsim.math.geometry.geometry.euler_to_quaternion

euler_to_quaternion(
    euler: Vector3, is_degree: bool = False
) -> Quaternion

Convert Euler angles (roll, pitch, yaw) to a quaternion.

Uses Unreal Engine's ZYX rotation order: - roll: rotate around X axis - pitch: rotate around Y axis - yaw: rotate around Z axis

Parameters:

Name Type Description Default
euler Vector3

Euler angles in (roll, pitch, yaw).

required
is_degree bool

Whether the input is in degrees. Defaults to False (radians).

False

Returns:

Name Type Description
Quaternion Quaternion

The corresponding rotation quaternion.

Source code in src/tongsim/math/geometry/geometry.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def euler_to_quaternion(euler: Vector3, is_degree: bool = False) -> Quaternion:
    """
    Convert Euler angles (roll, pitch, yaw) to a quaternion.

    Uses Unreal Engine's ZYX rotation order:
    - roll: rotate around X axis
    - pitch: rotate around Y axis
    - yaw: rotate around Z axis

    Args:
        euler (Vector3): Euler angles in (roll, pitch, yaw).
        is_degree (bool): Whether the input is in degrees. Defaults to False (radians).

    Returns:
        Quaternion: The corresponding rotation quaternion.
    """
    if is_degree:
        euler = degrees_to_radians(euler)

    roll, pitch, yaw = euler.x, euler.y, euler.z

    cy = math.cos(yaw * 0.5)
    sy = math.sin(yaw * 0.5)
    cp = math.cos(pitch * 0.5)
    sp = math.sin(pitch * 0.5)
    cr = math.cos(roll * 0.5)
    sr = math.sin(roll * 0.5)

    w = cr * cp * cy + sr * sp * sy
    x = sr * cp * cy - cr * sp * sy
    y = cr * sp * cy + sr * cp * sy
    z = cr * cp * sy - sr * sp * cy

    return Quaternion(w, x, y, z)

tongsim.math.geometry.geometry.quaternion_to_euler

quaternion_to_euler(
    q: Quaternion, is_degree: bool = False
) -> Vector3

Convert a quaternion to Euler angles (roll, pitch, yaw).

Uses Unreal Engine's coordinate convention and ZYX rotation order: - Roll (X): rotate around X axis - Pitch (Y): rotate around Y axis - Yaw (Z): rotate around Z axis

Parameters:

Name Type Description Default
q Quaternion

Input quaternion.

required
is_degree bool

Whether to return degrees. Defaults to False (radians).

False

Returns:

Name Type Description
Vector3 Vector3

Euler angles in (roll, pitch, yaw).

Source code in src/tongsim/math/geometry/geometry.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def quaternion_to_euler(q: Quaternion, is_degree: bool = False) -> Vector3:
    """
    Convert a quaternion to Euler angles (roll, pitch, yaw).

    Uses Unreal Engine's coordinate convention and ZYX rotation order:
    - Roll (X): rotate around X axis
    - Pitch (Y): rotate around Y axis
    - Yaw (Z): rotate around Z axis

    Args:
        q (Quaternion): Input quaternion.
        is_degree (bool): Whether to return degrees. Defaults to False (radians).

    Returns:
        Vector3: Euler angles in (roll, pitch, yaw).
    """
    w, x, y, z = q.w, q.x, q.y, q.z

    # pitch (Y axis)
    sinp = 2.0 * (w * y - z * x)
    pitch = math.copysign(math.pi / 2, sinp) if abs(sinp) >= 1 else math.asin(sinp)

    # yaw (Z axis)
    siny_cosp = 2.0 * (w * z + x * y)
    cosy_cosp = 1.0 - 2.0 * (y * y + z * z)
    yaw = math.atan2(siny_cosp, cosy_cosp)

    # roll (X axis)
    sinr_cosp = 2.0 * (w * x + y * z)
    cosr_cosp = 1.0 - 2.0 * (x * x + y * y)
    roll = math.atan2(sinr_cosp, cosr_cosp)

    result = Vector3(roll, pitch, yaw)
    return radians_to_degrees(result) if is_degree else result

tongsim.math.geometry.geometry.calc_camera_look_at_rotation

calc_camera_look_at_rotation(
    pos: Vector3, target: Vector3
) -> Quaternion

Compute the camera rotation quaternion required to look from pos to target.

Assumes the camera's local forward is +X and the world up is +Z.

Parameters:

Name Type Description Default
pos Vector3

Camera position.

required
target Vector3

Target position.

required

Returns:

Name Type Description
Quaternion Quaternion

The rotation that makes the camera look at the target.

Source code in src/tongsim/math/geometry/geometry.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def calc_camera_look_at_rotation(pos: Vector3, target: Vector3) -> Quaternion:
    """
    Compute the camera rotation quaternion required to look from `pos` to `target`.

    Assumes the camera's local forward is +X and the world up is +Z.

    Args:
        pos (Vector3): Camera position.
        target (Vector3): Target position.

    Returns:
        Quaternion: The rotation that makes the camera look at the target.
    """
    world_up = Vector3(0.0, 0.0, 1.0)

    forward = normalize(target - pos)  # Camera forward (+X)
    right = cross(world_up, forward)

    # Handle degenerate cases where forward is parallel (or anti-parallel) to world_up.
    if length(right) < 1e-6:
        right = normalize(Vector3(0.0, 1.0, 0.0))  # Pick any orthogonal direction.
    else:
        right = normalize(right)

    up = cross(forward, right)

    # Build rotation matrix (column-major, right-handed).
    m00, m01, m02 = forward.x, right.x, up.x
    m10, m11, m12 = forward.y, right.y, up.y
    m20, m21, m22 = forward.z, right.z, up.z

    trace = m00 + m11 + m22

    if trace > 0.0:
        s = math.sqrt(trace + 1.0) * 2.0
        w = 0.25 * s
        x = (m21 - m12) / s
        y = (m02 - m20) / s
        z = (m10 - m01) / s
    elif m00 > m11 and m00 > m22:
        s = math.sqrt(1.0 + m00 - m11 - m22) * 2.0
        w = (m21 - m12) / s
        x = 0.25 * s
        y = (m01 + m10) / s
        z = (m02 + m20) / s
    elif m11 > m22:
        s = math.sqrt(1.0 + m11 - m00 - m22) * 2.0
        w = (m02 - m20) / s
        x = (m01 + m10) / s
        y = 0.25 * s
        z = (m12 + m21) / s
    else:
        s = math.sqrt(1.0 + m22 - m00 - m11) * 2.0
        w = (m10 - m01) / s
        x = (m02 + m20) / s
        y = (m12 + m21) / s
        z = 0.25 * s

    return Quaternion(w, x, y, z)

tongsim.math.geometry.geometry.dot module-attribute

dot = dot

tongsim.math.geometry.geometry.cross module-attribute

cross = cross

tongsim.math.geometry.geometry.normalize module-attribute

normalize = normalize

tongsim.math.geometry.geometry.length module-attribute

length = length

tongsim.math.geometry.geometry.lerp module-attribute

lerp = lerp