simvx.core.math.types

Lightweight math types – Vec2, Vec3, Quat.

Vec2/Vec3 subclass np.ndarray for zero-copy interop with NumPy and GPU pipelines: v = Vec2(1, 2) np.dot(v, v) # works – it IS a numpy array v.normalized() # returns Vec2, not ndarray

Quat uses slots for memory efficiency (different semantics than vectors). All angles are in radians.

Module Contents

Classes

Vec2

2D vector (np.ndarray subclass, shape (2,), float32).

Vec3

3D vector (np.ndarray subclass, shape (3,), float32).

Quat

Quaternion (w, x, y, z) – identity by default.

Curve2D

2D curve with cubic Bezier interpolation.

Curve3D

3D curve with cubic Bezier interpolation.

Functions

normalize

Return normalized copy of vector.

length

Return length of vector.

dot

Dot product of two vectors.

cross

Cross product of two Vec3.

mix

Linear interpolation between a and b.

clamp

Clamp a scalar value between lo and hi.

slerp

Spherical linear interpolation between two quaternions.

Data

API

simvx.core.math.types.log

‘getLogger(…)’

simvx.core.math.types.__all__

[‘Vec2’, ‘Vec3’, ‘Quat’, ‘Curve2D’, ‘Curve3D’, ‘normalize’, ‘length’, ‘dot’, ‘cross’, ‘mix’, ‘clamp’…

class simvx.core.math.types.Vec2(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)[source]

Bases: numpy.ndarray

2D vector (np.ndarray subclass, shape (2,), float32).

Components are stored as the nearest float32, and that is the guarantee. It is what lets a vector be handed to the GPU without a copy or a conversion, which is the whole reason these are ndarray subclasses rather than a pair of Python floats. The consequence is that a float64 value does not survive a store unchanged, and the nearest float32 may lie on either side of it: after v.x = 0.1, v.x <= 0.1 is not guaranteed.

So an exact boundary does not survive a round trip. Code enforcing a spatial bound must compare with a tolerance, or clamp to a value inside the bound (limit * (1 - 1e-6)), rather than expect clamp followed by <= to hold. There is deliberately no round-trip-safe clamp_to helper: a helper would have to pick a tolerance on the caller’s behalf, and the caller is the only one who knows what the bound means.

Initialization

__new__(x=0.0, y=None)[source]
__array_finalize__(obj)[source]
__array_ufunc__(ufunc, method, *inputs, **kwargs)[source]
property x: float[source]
property y: float[source]
__eq__(other)[source]
__ne__(other)[source]
__hash__()[source]
__repr__()[source]
__mod__(other)[source]
__bool__()[source]
length() float[source]

Return the magnitude of this vector.

length_squared() float[source]

Return the squared magnitude (avoids sqrt).

normalized() simvx.core.math.types.Vec2[source]

Return a unit-length copy, or zero vector if length is near zero.

dot(other) float[source]

Return the dot product with another vector.

distance_to(other) float[source]

Return the distance to another vector.

direction_to(other) simvx.core.math.types.Vec2[source]

Return the normalized direction toward another vector.

angle_to(other) float[source]

Return the angle between this vector and other (radians).

move_toward(target, delta: float) simvx.core.math.types.Vec2[source]

Move toward target by at most delta distance.

lerp(other, t: float) simvx.core.math.types.Vec2[source]

Linear interpolation between self and other.

snapped(step: float) simvx.core.math.types.Vec2[source]

Round each component to the nearest multiple of step.

clamped(min_v, max_v) simvx.core.math.types.Vec2[source]

Return this vector with each component clamped between the matching components of min_v and max_v (component-wise).

rotated(angle: float) simvx.core.math.types.Vec2[source]

Return this vector rotated by angle (radians).

angle() float[source]

Return the angle of this vector in radians (atan2(y, x)).

reflect(normal) simvx.core.math.types.Vec2[source]

Return this vector reflected off a surface with the given normal.

bounce(normal) simvx.core.math.types.Vec2[source]

Return the bounce vector (negated reflect).

slide(normal) simvx.core.math.types.Vec2[source]

Return this vector slid along a plane defined by normal.

abs() simvx.core.math.types.Vec2[source]

Return vector with absolute values of each component.

cross(other) float[source]

Return the 2D cross product (scalar z-component).

classmethod from_angle(radians: float) simvx.core.math.types.Vec2[source]

Return a unit vector pointing in the given direction (radians from +X axis).

__abs__()
__add__(value)
__and__(value)
__array__(dtype=None)
__array_wrap__(obj)
__contains__(key)
__copy__()
__deepcopy__(memo)
__divmod__(value)
__float__()
__floordiv__()
__ge__(value)
__getitem__(key)
__gt__(value)
__iadd__(value)
__iand__(value)
__ifloordiv__(value)
__ilshift__(value)
__imod__(value)
__imul__(value)
__int__()
__invert__()
__ior__(value)
__ipow__(value)
__irshift__(value)
__isub__(value)
__itruediv__(value)
__ixor__(value)
__le__(value)
__len__()
__lshift__(value)
__lt__(value)
__matmul__(value)
__mul__(value)
__neg__()
__or__(value)
__pos__()
__pow__()
__rshift__()
__setitem__(key, value)
__str__()
__sub__(value)
__truediv__(value)
__xor__(value)
all(axis=None, out=None, keepdims=False)
any(axis=None, out=None, keepdims=False)
argmax(axis=None, out=None)
argmin(axis=None, out=None)
argpartition(kth, axis=-1, kind='introselect', order=None)
argsort(axis=-1, kind='quicksort', order=None)
astype(dtype, order='K', casting='unsafe', subok=True, copy=True)
byteswap(inplace=False)
choose(choices, out=None, mode='raise')
clip(min=None, max=None, out=None)
compress(condition, axis=None, out=None)
conj()
conjugate()
copy(order='C')
cumprod(axis=None, dtype=None, out=None)
cumsum(axis=None, dtype=None, out=None)
diagonal(offset=0, axis1=0, axis2=1)
dump(file)
dumps()
fill(value)
flatten(order='C')
getfield(dtype, offset=0)
item(*args)
itemset(*args)
max(axis=None, out=None)
mean(axis=None, dtype=None, out=None, keepdims=False)
min(axis=None, out=None, keepdims=False)
newbyteorder(new_order='S')
nonzero()
partition(kth, axis=-1, kind='introselect', order=None)
prod(axis=None, dtype=None, out=None, keepdims=False)
ptp(axis=None, out=None)
put(indices, values, mode='raise')
ravel(order='C')
repeat(repeats, axis=None)
reshape(shape, order='C')
resize(new_shape, refcheck=True)
round(decimals=0, out=None)
searchsorted(v, side='left', sorter=None)
setfield(val, dtype, offset=0)
setflags(write=None, align=None, uic=None)
sort(axis=-1, kind='quicksort', order=None)
squeeze(axis=None)
std(axis=None, dtype=None, out=None, ddof=0, keepdims=False)
sum(axis=None, dtype=None, out=None, keepdims=False)
swapaxes(axis1, axis2)
take(indices, axis=None, out=None, mode='raise')
tobytes(order='C')
tofile(fid, sep='', format='%s')
tolist()
tostring(order='C')
trace(offset=0, axis1=0, axis2=1, dtype=None, out=None)
transpose(*axes)
var(axis=None, dtype=None, out=None, ddof=0, keepdims=False)
view(dtype=None, type=None)
classmethod __class_getitem__(value)
simvx.core.math.types.builtins_abs

None

class simvx.core.math.types.Vec3(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)[source]

Bases: numpy.ndarray

3D vector (np.ndarray subclass, shape (3,), float32).

Components are stored as the nearest float32, on the same terms as

Class:

Vec2: it is what makes a vector zero-copy at the GPU boundary, and it means an exact bound does not survive a store. clamp(x, r) followed by position.x <= r is not guaranteed. Compare with a tolerance, or clamp to a value inside the bound. See :class:Vec2 for the full statement and for why there is no clamped_length helper.

Initialization

__new__(x=0.0, y=None, z=None)[source]
__array_finalize__(obj)[source]
__array_ufunc__(ufunc, method, *inputs, **kwargs)[source]
property x: float[source]
property y: float[source]
property z: float[source]
__eq__(other)[source]
__ne__(other)[source]
__hash__()[source]
__repr__()[source]
__mod__(other)[source]
__bool__()[source]
length() float[source]

Return the magnitude of this vector.

length_squared() float[source]

Return the squared magnitude (avoids sqrt).

normalized() simvx.core.math.types.Vec3[source]

Return a unit-length copy, or zero vector if length is near zero.

dot(other) float[source]

Return the dot product with another vector.

cross(other) simvx.core.math.types.Vec3[source]

Return the cross product with another vector.

distance_to(other) float[source]

Return the distance to another vector.

direction_to(other) simvx.core.math.types.Vec3[source]

Return the normalized direction toward another vector.

angle_to(other) float[source]

Return the angle between this vector and other (radians).

move_toward(target, delta: float) simvx.core.math.types.Vec3[source]

Move toward target by at most delta distance.

lerp(other, t: float) simvx.core.math.types.Vec3[source]

Linear interpolation between self and other.

snapped(step: float) simvx.core.math.types.Vec3[source]

Round each component to the nearest multiple of step.

clamped(min_v, max_v) simvx.core.math.types.Vec3[source]

Return this vector with each component clamped between the matching components of min_v and max_v (component-wise).

__abs__()
__add__(value)
__and__(value)
__array__(dtype=None)
__array_wrap__(obj)
__contains__(key)
__copy__()
__deepcopy__(memo)
__divmod__(value)
__float__()
__floordiv__()
__ge__(value)
__getitem__(key)
__gt__(value)
__iadd__(value)
__iand__(value)
__ifloordiv__(value)
__ilshift__(value)
__imod__(value)
__imul__(value)
__int__()
__invert__()
__ior__(value)
__ipow__(value)
__irshift__(value)
__isub__(value)
__itruediv__(value)
__ixor__(value)
__le__(value)
__len__()
__lshift__(value)
__lt__(value)
__matmul__(value)
__mul__(value)
__neg__()
__or__(value)
__pos__()
__pow__()
__rshift__()
__setitem__(key, value)
__str__()
__sub__(value)
__truediv__(value)
__xor__(value)
all(axis=None, out=None, keepdims=False)
any(axis=None, out=None, keepdims=False)
argmax(axis=None, out=None)
argmin(axis=None, out=None)
argpartition(kth, axis=-1, kind='introselect', order=None)
argsort(axis=-1, kind='quicksort', order=None)
astype(dtype, order='K', casting='unsafe', subok=True, copy=True)
byteswap(inplace=False)
choose(choices, out=None, mode='raise')
clip(min=None, max=None, out=None)
compress(condition, axis=None, out=None)
conj()
conjugate()
copy(order='C')
cumprod(axis=None, dtype=None, out=None)
cumsum(axis=None, dtype=None, out=None)
diagonal(offset=0, axis1=0, axis2=1)
dump(file)
dumps()
fill(value)
flatten(order='C')
getfield(dtype, offset=0)
item(*args)
itemset(*args)
max(axis=None, out=None)
mean(axis=None, dtype=None, out=None, keepdims=False)
min(axis=None, out=None, keepdims=False)
newbyteorder(new_order='S')
nonzero()
partition(kth, axis=-1, kind='introselect', order=None)
prod(axis=None, dtype=None, out=None, keepdims=False)
ptp(axis=None, out=None)
put(indices, values, mode='raise')
ravel(order='C')
repeat(repeats, axis=None)
reshape(shape, order='C')
resize(new_shape, refcheck=True)
round(decimals=0, out=None)
searchsorted(v, side='left', sorter=None)
setfield(val, dtype, offset=0)
setflags(write=None, align=None, uic=None)
sort(axis=-1, kind='quicksort', order=None)
squeeze(axis=None)
std(axis=None, dtype=None, out=None, ddof=0, keepdims=False)
sum(axis=None, dtype=None, out=None, keepdims=False)
swapaxes(axis1, axis2)
take(indices, axis=None, out=None, mode='raise')
tobytes(order='C')
tofile(fid, sep='', format='%s')
tolist()
tostring(order='C')
trace(offset=0, axis1=0, axis2=1, dtype=None, out=None)
transpose(*axes)
var(axis=None, dtype=None, out=None, ddof=0, keepdims=False)
view(dtype=None, type=None)
classmethod __class_getitem__(value)
class simvx.core.math.types.Quat(w=1.0, x=0.0, y=0.0, z=0.0)[source]

Quaternion (w, x, y, z) – identity by default.

Rotation convention: YXZ intrinsic, applied as yaw about Y, then pitch about X, then roll about Z. This puts the two orientations Euler angles cannot name at straight up and straight down, where a camera controller already clamps, rather than at a compass heading.

All angle inputs/outputs in radians.

Initialization

__slots__

(‘w’, ‘x’, ‘y’, ‘z’)

classmethod from_euler(pitch: float = 0.0, yaw: float = 0.0, roll: float = 0.0) simvx.core.math.types.Quat[source]

Create quaternion from Euler angles in radians (YXZ intrinsic order).

Args: pitch: Rotation around X axis (radians) yaw: Rotation around Y axis (radians) roll: Rotation around Z axis (radians)

classmethod from_axis_angle(axis: simvx.core.math.types.Vec3 | tuple, angle: float) simvx.core.math.types.Quat[source]

Create quaternion from axis-angle rotation.

Args: axis: Rotation axis (will be normalized) angle: Rotation angle in radians

classmethod look_at(direction: simvx.core.math.types.Vec3 | tuple, up: simvx.core.math.types.Vec3 | tuple = None) simvx.core.math.types.Quat[source]

Create quaternion looking in given direction (right-handed).

This builds a whole frame from up, so it also decides the roll. To turn one direction onto another by the smallest angle, leaving the roll alone, use :meth:shortest_arc.

Args: direction: Forward direction (will be normalized) up: Up vector (default: Y-up)

classmethod shortest_arc(from_direction: simvx.core.math.types.Vec3 | tuple, to_direction: simvx.core.math.types.Vec3 | tuple) simvx.core.math.types.Quat[source]

Rotation that turns one direction onto another by the smallest angle.

Reach for this when what matters is the pairing of two directions and not the roll around them: swinging a turret from the way its barrel already points to the way it should point, orienting a projectile along its velocity, or laying a decal by turning :attr:Vec3.UP onto a surface normal. Only the directions are used; the lengths of both arguments are ignored.

Meth:

look_at also returns a rotation that points a given way, but a different one. It builds a frame from a world up vector and so fixes the roll as well, which flips by half a turn as the direction passes that up vector, where the arc turns smoothly through. Use look_at for a camera or anything that must stay upright; use this to turn one vector onto another.

Two exactly opposite directions have no shortest arc, since every perpendicular axis turns one onto the other. A half turn about a perpendicular derived from the input is returned, so repeated calls on the same pair agree. A zero-length direction names no rotation at all: identity is returned and the cause is logged once.

Args: from_direction: Direction to rotate from (will be normalized) to_direction: Direction to rotate onto (will be normalized)

inverse() simvx.core.math.types.Quat[source]

Return conjugate (inverse for unit quaternion).

euler_angles() simvx.core.math.types.Vec3[source]

Euler angles (pitch, yaw, roll) in radians, the inverse of :meth:from_euler.

Straight up and straight down are the two orientations an Euler triple cannot name: there, yaw and roll turn about the same world axis and only their sum is determined. Both are reported as roll zero and the whole turn as yaw, which is the reading that recomposes to the orientation asked about and the one an inspector should show, since a camera looking down has a heading and no roll.

slerp(other: simvx.core.math.types.Quat, t: float) simvx.core.math.types.Quat[source]

Spherical linear interpolation between self and other.

rotate(axis: simvx.core.math.types.Vec3 | tuple, angle: float) simvx.core.math.types.Quat[source]

Apply additional rotation around axis (radians).

to_mat4() numpy.ndarray[source]

Convert to 4x4 rotation matrix (numpy, row-major).

__mul__(other)[source]

Hamilton product (Quat * Quat) or rotate vector (Quat * Vec3).

__eq__(other)[source]
__repr__()[source]
__hash__()[source]
__iter__()[source]
class simvx.core.math.types.Curve2D(bake_interval: float = 5.0)[source]

2D curve with cubic Bezier interpolation.

Each point has an optional in-handle and out-handle for smooth curves. Handles are relative offsets from the point position.

Initialization

__slots__

(‘_points’, ‘_baked_points’, ‘_baked_length’, ‘_bake_dirty’, ‘_bake_interval’)

property point_count: int[source]
add_point(position: simvx.core.math.types.Vec2 | tuple, handle_in: simvx.core.math.types.Vec2 | tuple = None, handle_out: simvx.core.math.types.Vec2 | tuple = None, index: int = -1)[source]

Add a point to the curve. Handles are relative offsets from position.

remove_point(index: int)[source]

Remove a point by index.

get_point_position(index: int) simvx.core.math.types.Vec2[source]
set_point_position(index: int, position: simvx.core.math.types.Vec2 | tuple)[source]
get_point_in(index: int) simvx.core.math.types.Vec2[source]
get_point_out(index: int) simvx.core.math.types.Vec2[source]
sample(t: float) simvx.core.math.types.Vec2[source]

Sample the curve at parameter t (0.0 to 1.0). Returns position via cubic Bezier.

sample_baked(offset: float) simvx.core.math.types.Vec2[source]

Sample by distance along the baked curve (0 to baked_length).

sample_baked_with_rotation(offset: float) tuple[simvx.core.math.types.Vec2, float][source]

Sample position and rotation angle (radians) at distance along curve.

get_baked_length() float[source]

Total arc length of the baked curve.

get_baked_points() list[simvx.core.math.types.Vec2][source]

Return list of baked (pre-tessellated) points.

clear()[source]

Remove all points.

class simvx.core.math.types.Curve3D(bake_interval: float = 0.2)[source]

3D curve with cubic Bezier interpolation.

Each point has an optional in-handle and out-handle for smooth curves. Handles are relative offsets from the point position.

Initialization

__slots__

(‘_points’, ‘_baked_points’, ‘_baked_length’, ‘_bake_dirty’, ‘_bake_interval’, ‘_tilts’)

property point_count: int[source]
add_point(position: simvx.core.math.types.Vec3 | tuple, handle_in: simvx.core.math.types.Vec3 | tuple = None, handle_out: simvx.core.math.types.Vec3 | tuple = None, index: int = -1, tilt: float = 0.0)[source]

Add a point to the curve. Handles are relative offsets from position.

remove_point(index: int)[source]

Remove a point by index.

get_point_position(index: int) simvx.core.math.types.Vec3[source]
set_point_position(index: int, position: simvx.core.math.types.Vec3 | tuple)[source]
get_point_in(index: int) simvx.core.math.types.Vec3[source]
get_point_out(index: int) simvx.core.math.types.Vec3[source]
get_point_tilt(index: int) float[source]
set_point_tilt(index: int, tilt: float)[source]
sample(t: float) simvx.core.math.types.Vec3[source]

Sample the curve at parameter t (0.0 to 1.0). Returns position via cubic Bezier.

sample_baked(offset: float) simvx.core.math.types.Vec3[source]

Sample by distance along the baked curve (0 to baked_length).

sample_baked_with_rotation(offset: float, up: simvx.core.math.types.Vec3 = None) tuple[simvx.core.math.types.Vec3, simvx.core.math.types.Vec3][source]

Sample position and forward direction at distance along curve.

Returns (position, forward_direction) where forward is a unit Vec3.

get_baked_length() float[source]
get_baked_points() list[simvx.core.math.types.Vec3][source]
clear()[source]

Remove all points.

simvx.core.math.types.normalize(v: simvx.core.math.types.Vec2 | simvx.core.math.types.Vec3) simvx.core.math.types.Vec2 | simvx.core.math.types.Vec3[source]

Return normalized copy of vector.

simvx.core.math.types.length(v: simvx.core.math.types.Vec2 | simvx.core.math.types.Vec3) float[source]

Return length of vector.

simvx.core.math.types.dot(a: simvx.core.math.types.Vec2 | simvx.core.math.types.Vec3, b: simvx.core.math.types.Vec2 | simvx.core.math.types.Vec3) float[source]

Dot product of two vectors.

simvx.core.math.types.cross(a: simvx.core.math.types.Vec3, b: simvx.core.math.types.Vec3) simvx.core.math.types.Vec3[source]

Cross product of two Vec3.

simvx.core.math.types.mix(a, b, t: float)[source]

Linear interpolation between a and b.

Works with scalars, Vec2, Vec3, and Quat (uses slerp for Quat).

simvx.core.math.types.clamp(v: float, lo: float, hi: float) float[source]

Clamp a scalar value between lo and hi.

simvx.core.math.types.slerp(a: simvx.core.math.types.Quat, b: simvx.core.math.types.Quat, t: float) simvx.core.math.types.Quat[source]

Spherical linear interpolation between two quaternions.