Math types

Vec2 and Vec3 are float32 numpy.ndarray subclasses: they work with every NumPy operation and add named accessors and convenience methods. Quat holds rotations. Angles are radians throughout.

Vec2

2D vector (ndarray subclass, shape (2,), float32):

from simvx.core import Vec2

v = Vec2(3, 4)
v.length()          # 5.0
v.normalized()      # Vec2(0.6, 0.8)
v.dot(Vec2(1, 0))   # 3.0

# Arithmetic (numpy broadcasting)
a + b, a - b, a * 2, a / 3
Vec2(1)              # Vec2(1, 1): scalar broadcast

# NumPy interop
import numpy as np
np.linalg.norm(v)    # 5.0, because a Vec2 is an ndarray

Vec3

3D vector (ndarray subclass, shape (3,), float32):

from simvx.core import Vec3

v = Vec3(1, 2, 3)
v.length()           # 3.742
v.length_squared()   # 14
v.normalized()       # unit vector
v.dot(other)         # dot product
v.cross(other)       # cross product

# Indexing
v[0], v[1], v[2]     # x, y, z
v.x, v.y, v.z        # named access
Vec3(1)              # Vec3(1, 1, 1): scalar broadcast

Quat

Unit quaternion for 3D rotations. All angle arguments use radians.

import math
from simvx.core import Quat

# Construction
q = Quat()                                              # identity
q = Quat.from_euler(pitch=math.radians(45), yaw=math.radians(90))
q = Quat.from_axis_angle((0, 1, 0), math.pi / 2)       # axis-angle
q = Quat.look_at((0, 0, -1))                            # look direction

# Operations
q.inverse()                          # conjugate (inverse for unit quaternion)
q.euler_angles()                     # Vec3(pitch, yaw, roll) in radians
q.slerp(other, 0.5)                 # spherical interpolation
q.rotate((0, 1, 0), math.pi / 6)   # compose with additional rotation
q.to_mat4()                          # convert to 4x4 numpy matrix

# Rotate a vector
rotated = q * Vec3(1, 0, 0)

Rotation convention

All SimVX internal rotation APIs use radians. This applies to:

  • Quat.from_euler(), Quat.from_axis_angle(), Quat.rotate()

  • Node3D.rotate(axis, angle), Node2D.rotate(radians) (Node2D.rotate_deg(degrees) takes degrees)

  • euler_angles() return values

  • Matrix functions: perspective(fov), rotate(axis, angle)

The editor’s inspector shows degrees and converts.

Matrix functions

Pure NumPy matrix utilities in simvx.core.math:

from simvx.core import perspective, look_at, translate, rotate, scale, identity

# Projection (fov in radians)
proj = perspective(fov=math.radians(60), aspect=16/9, near=0.1, far=100.0)

# View
view = look_at(eye=(0, 5, 10), center=(0, 0, 0), up=(0, 1, 0))

# Model transforms (angle in radians)
model = translate((1, 2, 3)) @ rotate((0, 1, 0), angle=math.pi/2) @ scale((2, 2, 2))

All matrices are NumPy arrays in row-major order. Vulkan shaders expect column-major, so the renderer transposes them at the GPU boundary.

Edge case safety

Matrix functions guard against degenerate inputs that would otherwise produce NaN or infinity:

  • look_at(): when the forward direction is nearly parallel to the up vector (e.g. looking straight up/down), a fallback up vector is chosen automatically to avoid a zero cross product.

  • perspective(): the field of view is clamped to 1-179 degrees, aspect ratio floors at a small positive value, and near/far are validated to prevent division by zero.

  • orthographic(): degenerate bounds (left == right, top == bottom, near == far) are corrected to avoid division by zero.

  • Quat.slerp(): handles both nearly-identical quaternions (< 0.03 degrees apart) and nearly-opposite quaternions (~180 degrees apart) by falling back to normalised linear interpolation.

Utility functions

from simvx.core import normalize, length, dot, cross, mix, clamp, slerp

normalize(v)         # unit vector
length(v)            # magnitude
dot(a, b)            # dot product
cross(a, b)          # cross product (Vec3 only)
mix(a, b, 0.5)       # linear interpolation (slerp for Quat)
clamp(x, 0.0, 1.0)  # clamp scalar
slerp(q1, q2, 0.5)  # quaternion slerp

API reference

See simvx.core.math.types for the complete math API.