simvx.core.math.matrices¶
Pure NumPy matrix utilities.
All matrices use NumPy’s native row-major layout. When sending to GLSL, matrices must be transposed (GLSL expects column-major); this transpose happens only at GPU boundary points, keeping the rest of the engine free of row-major vs column-major ambiguity.
Module Contents¶
Functions¶
Convert a quaternion to a 4x4 rotation matrix. |
|
Create 4x4 identity matrix. |
|
Create a reverse-Z perspective projection matrix. |
|
Replace proj’s near clip plane with an arbitrary world-space plane. |
|
Create view matrix using look-at vectors. |
|
Create translation matrix. |
|
Create rotation matrix using axis-angle representation. |
|
Create scale matrix. |
|
Create orthographic projection matrix. |
|
Build model matrix from position, rotation quaternion, and scale. |
|
Build N model matrices from arrays of positions, quaternions, and scales. |
|
Convert mat4 to bytes for GPU upload (64 bytes, row-major float32). |
|
Sub-pixel jitter offset in [-0.5, 0.5] from the Halton(base_x, base_y) sequence. |
|
Return a copy of |
Data¶
API¶
- simvx.core.math.matrices.log¶
‘getLogger(…)’
- simvx.core.math.matrices.__all__¶
[‘identity’, ‘perspective’, ‘oblique_near_plane’, ‘look_at’, ‘translate’, ‘rotate’, ‘scale’, ‘orthog…
- simvx.core.math.matrices.quat_to_mat4(q, dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Convert a quaternion to a 4x4 rotation matrix.
Args: q: Quaternion: any object with .w, .x, .y, .z attributes (e.g. Quat) or a 4-element array [w, x, y, z]. dtype: NumPy data type (default float32)
Returns: 4x4 rotation matrix as numpy array
- simvx.core.math.matrices.identity(dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Create 4x4 identity matrix.
Args: dtype: NumPy data type (default float32)
Returns: 4x4 identity matrix as numpy array
- simvx.core.math.matrices.perspective(fov: float, aspect: float, near: float, far: float, dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Create a reverse-Z perspective projection matrix.
Args: fov: Field of view in radians aspect: Aspect ratio (width / height) near: Near clipping plane far: Far clipping plane dtype: NumPy data type (default float32)
Returns: 4x4 perspective projection matrix
Note: Depth maps to
z_ndcin [0, 1] with the near plane at 1 and the far plane at 0, matching :func:orthographic. Two things follow, and the engine’s pipelines depend on both: the depth attachment clears to 0.0, and every camera-space depth test comparesGREATERorGREATER_OR_EQUAL.The reversal is what makes a float depth buffer usable over a wide range. A float's exponent crowds its precision near zero, and a perspective divide crowds depth near the near plane; forward-mapping puts both concentrations in the same place and wastes them, while reversing lines the float's fine steps up with the far field where the projection's own steps are coarsest. The older OpenGL ``[-1, 1]`` form is not merely a different spelling here. The hardware clip volume is ``0 <= z_clip <= w_clip`` on both backends, so that form put the effective near plane at ``2 f n / (f + n)``: a camera asking for ``near = 0.1`` really clipped at ``0.1998``, and nothing said so. Does NOT include Y-flip for Vulkan. Caller must do: proj[1, 1] *= -1 # Flip Y-axis for Vulkan
- simvx.core.math.matrices.oblique_near_plane(proj: numpy.ndarray, view: numpy.ndarray, plane: tuple[float, float, float, float] | numpy.ndarray, dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Replace proj’s near clip plane with an arbitrary world-space plane.
The oblique-frustum technique (Lengyel, “Oblique View Frustum Depth Projection and Clipping”): the projection’s z row is rewritten so one of the GPU’s two fixed depth clip planes coincides with plane, scaled so the opposite plane still touches the corner of the original frustum that lies deepest on the kept side. Used by planar reflections: the mirrored camera must not render geometry behind the reflection plane.
The engine is reverse-Z, so the near clip is
z_clip <= w_cliprather thanz_clip >= 0and the substituted row isw_row - alpha * planerather than Lengyel’salpha * plane. Both forms clip identically; only the reversed one leaves depth running near-to-far the way the pipelines’GREATERcomparison expects, so the textbook form would silently invert depth testing inside every planar-reflection pass.Args: proj: 4x4 reverse-Z projection matrix (row-major): hardware clip volume
0 <= z_clip <= w_clipwith the near plane atz_clip = w_clip. Both backends and both of this module’s projections qualify. view: 4x4 view matrix of the camera the projection belongs to. plane: World-space plane(nx, ny, nz, d)with the equationn . x + d = 0; geometry on then . x + d > 0side is kept.Returns: A new 4x4 projection matrix (input is not modified). Depth values are redistributed (inherent to the technique); depth testing within the pass stays consistent. Returns proj unchanged when the plane does not face any part of the view frustum (degenerate input).
- simvx.core.math.matrices.look_at(eye: numpy.ndarray | tuple[float, float, float], center: numpy.ndarray | tuple[float, float, float], up: numpy.ndarray | tuple[float, float, float], dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Create view matrix using look-at vectors.
Args: eye: Camera position center: Point to look at up: Up vector (should be normalized) dtype: NumPy data type (default float32)
Returns: 4x4 view matrix
- simvx.core.math.matrices.translate(t: numpy.ndarray | tuple[float, float, float], dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Create translation matrix.
Args: t: Translation vector (x, y, z) dtype: NumPy data type (default float32)
Returns: 4x4 translation matrix
- simvx.core.math.matrices.rotate(axis: numpy.ndarray | tuple[float, float, float], angle: float, dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Create rotation matrix using axis-angle representation.
Args: axis: Rotation axis (should be normalized) angle: Rotation angle in radians dtype: NumPy data type (default float32)
Returns: 4x4 rotation matrix
Uses Rodrigues’ rotation formula.
- simvx.core.math.matrices.scale(s: numpy.ndarray | tuple[float, float, float] | float, dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Create scale matrix.
Args: s: Scale factors (x, y, z) or uniform scale factor dtype: NumPy data type (default float32)
Returns: 4x4 scale matrix
- simvx.core.math.matrices.orthographic(left: float, right: float, bottom: float, top: float, near: float, far: float, dtype: numpy.dtype = np.float32) numpy.ndarray[source]¶
Create orthographic projection matrix.
Args: left: Left plane right: Right plane bottom: Bottom plane top: Top plane near: Near plane far: Far plane dtype: NumPy data type (default float32)
Returns: 4x4 orthographic projection matrix
Note: Depth maps to
z_ndcin [0, 1], the clip volume both backends (Vulkan, WebGPU) actually enforce, with the near plane at 1 and the far plane at 0 so an orthographic camera shares the reverse-Z convention :func:perspectiveuses and the same clear value and depth comparison serve both.An OpenGL-style [-1, 1] ortho would put half the depth range at negative ``z_clip`` and the hardware would clip away half the scene: unlike the perspective case there is no non-linearity to hide it. Reversal buys an orthographic camera no precision, since its depth is linear in distance. It is here for uniformity: one clear value and one compare op across both projections is what keeps the two from needing separate pipelines. Does NOT include the Y-flip for Vulkan. Caller must do: proj[1, 1] *= -1
- simvx.core.math.matrices.mat4_from_trs(pos: tuple[float, float, float] | numpy.ndarray, rot, scl: tuple[float, float, float] | numpy.ndarray) numpy.ndarray[source]¶
Build model matrix from position, rotation quaternion, and scale.
Args: pos: Position (x, y, z) rot: Rotation quaternion: Quat or any object with .w/.x/.y/.z scl: Scale (x, y, z)
Returns: 4x4 model matrix as numpy array (Translate * Rotate * Scale)
- simvx.core.math.matrices.batch_mat4_from_trs(positions: numpy.ndarray, rotations: numpy.ndarray, scales: numpy.ndarray) numpy.ndarray[source]¶
Build N model matrices from arrays of positions, quaternions, and scales.
Args: positions: (N, 3) float32 positions rotations: (N, 4) float32 quaternions [w, x, y, z] scales: (N, 3) float32 scale factors
Returns: (N, 4, 4) float32 model matrices (Translate * Rotate * Scale)
- simvx.core.math.matrices.mat4_to_bytes(m: numpy.ndarray) bytes[source]¶
Convert mat4 to bytes for GPU upload (64 bytes, row-major float32).
- simvx.core.math.matrices.halton_jitter(index: int, base_x: int = 2, base_y: int = 3) tuple[float, float][source]¶
Sub-pixel jitter offset in [-0.5, 0.5] from the Halton(base_x, base_y) sequence.
Used for TAA: each frame samples a different sub-pixel location so the temporal accumulation reconstructs detail below one pixel.
indexis the frame counter (callers typically cycle it modulo the history length, e.g. 8). Mirrors the web runtime’sTAAPass.haltonJitterexactly so desktop and web jitter identically.Returns:
(jx, jy)offsets in pixels, each in[-0.5, 0.5].
- simvx.core.math.matrices.apply_jitter(proj: numpy.ndarray, jx: float, jy: float, width: float, height: float) numpy.ndarray[source]¶
Return a copy of
projtranslated by a sub-pixel jitter in clip space.projis a row-major Vulkan projection matrix (post Y-flip). The pixel-space jitter(jx, jy)is converted to NDC (2 / dimper pixel) and added to the clip-x/clip-y rows of the third column (proj[0, 2]/proj[1, 2]), the same slots the renderer transposes to the GPU’s column-2 rows 0/1. The original matrix is left untouched so callers keep an unjittered copy for culling and motion-vector reprojection.Under an orthographic projection
wis a constant 1 rather than-z_eye, so a third-column offset would shear with depth instead of translating. The offset moves to the translation column, negated to land on the same NDC shift the perspective path produces (+p02divided byw = -z_eye); the TAA resolve inverts the same signed jitter for both.