Skip to content

Transform builders and vector application

Import matrix builders/methods from gem.matrix; the raw vector transform kernel and Vector methods are in gem.vector. Source, transform tests, pivot/shear.

All matrix methods here postmultiply the receiver: result=M*builder. Returning methods allocate fresh Matrix/ctypes storage; i_ methods replace rows and ctypes and return self. Inputs remain unchanged. Wrapper scale/rotate/translate require a Vector argument and otherwise raise TypeError. Raw builders use indexable coordinate sequences, not Vector wrappers. Angles are degrees unless explicitly marked radians. Positive +Z rotation maps +X toward +Y; this is not an enforced application-wide world frame.

Axis-angle builders use Rodrigues rotation: for normalized axis n, the row-vector matrix is cos(theta)I+(1−cos(theta))nn^T−sin(theta)[n]_cross, where [n]_cross*v=n×v in column notation. This is the transpose of the conventional column-vector active matrix.

Matrix builders and methods

Exact source declaration Parameters, result and behavior
scale(size, value) Raw value: coordinate scale factors; fresh diagonal size×size list. First up to three diagonal entries use value[x]; entries x>=3 are 1.0, preserving homogeneous W for size 4.
shearXY3(x, y) Numeric x,y; fresh 3×3 rows implementing Z′=Z+xX+yY; other coordinates unchanged.
shearYZ3(y, z) Numeric y,z; fresh 3×3 rows implementing X′=X+yY+zZ; other coordinates unchanged.
shearXZ3(x, z) Numeric x,z; fresh 3×3 rows implementing Y′=Y+xX+zZ; other coordinates unchanged.
shearXY4(x, y) Numeric x,y; fresh 4×4 rows implementing Z′=Z+xX+yY; other coordinates unchanged, homogeneous W preserved.
shearYZ4(y, z) Numeric y,z; fresh 4×4 rows implementing X′=X+yY+zZ; other coordinates unchanged, homogeneous W preserved.
shearXZ4(x, z) Numeric x,z; fresh 4×4 rows implementing Y′=Y+xX+zZ; other coordinates unchanged, homogeneous W preserved.
translate2(vector) Raw vector XY sequence → fresh homogeneous 3×3 translation; last row [x,y,1].
translate3(vector) Raw vector XYZ sequence → fresh 3×3 with last row [x,y,z]. Historical linear-row replacement, not general 3D affine translation.
translate4(vector) Raw vector XYZ (extra W ignored) → fresh homogeneous 4×4; last row [x,y,z,1].
rotate2(point, theta) Raw 2D point pivot; theta degrees; fresh 3×3 homogeneous p′=pivot+(p−pivot)R. Pivot stationary; W=0 directions receive origin-only linear rotation.
rotate3(axis, theta) Raw 3D axis, theta degrees; temporary normalized axis; fresh 3×3 Rodrigues row matrix. Exact zero axis raises ZeroDivisionError.
rotate4(axis, theta) Raw 3D axis, theta degrees; temporary normalization; fresh 4×4 Rodrigues row matrix preserving W. Zero axis raises ZeroDivisionError.
rotate_origin2(theta) theta radians; fresh homogeneous 3×3 counterclockwise XY rotation about origin.
Matrix.i_scale(self, value) value: Vector with enough coordinate scale factors; postmultiply by the scale diagonal. Replace rows/ctypes and return self. No zero-factor rejection; size 4 preserves W.
Matrix.scale(self, value) value: Vector with enough coordinate scale factors; postmultiply by the scale diagonal. Fresh Matrix. No zero-factor rejection; size 4 preserves W.
Matrix.i_rotate(self, axis, theta) axis: Vector; theta: degrees. Size 2 uses the linear block of rotate2, ignoring pivot translation (origin-only); size 3/4 uses normalized axis-angle builder. Other sizes NotImplementedError. Replace rows/ctypes, return self. Nonzero 3D axis prerequisite; zero raises ZeroDivisionError.
Matrix.rotate(self, axis, theta) axis: Vector; theta: degrees. Size 2 uses the linear block of rotate2, ignoring pivot translation (origin-only); size 3/4 uses normalized axis-angle builder. Other sizes NotImplementedError. Fresh Matrix. Nonzero 3D axis prerequisite; zero raises ZeroDivisionError.
Matrix.i_translate(self, vecA) vecA: Vector. Matrix3/Vector2 → affine 2D; Matrix3/Vector3 → legacy last-row replacement; Matrix4/Vector3 or Vector4 → final-row XYZ translation. Matrix2/other receiver sizes NotImplementedError. Replace rows/ctypes and return self. Unsupported argument dimensions have historical, nonuniform errors.
Matrix.translate(self, vecA) vecA: Vector. Matrix3/Vector2 → affine 2D; Matrix3/Vector3 → legacy last-row replacement; Matrix4/Vector3 or Vector4 → final-row XYZ translation. Matrix2/other receiver sizes NotImplementedError. Fresh Matrix. Unsupported argument dimensions have historical, nonuniform errors.
Matrix.shearXY(self, x, y) Numeric x,y; postmultiply Z′=Z+xX+yY shear for sizes 3/4 (size 4 preserves W), otherwise NotImplementedError. Fresh Matrix.
Matrix.i_shearXY(self, x, y) Numeric x,y; postmultiply Z′=Z+xX+yY shear for sizes 3/4 (size 4 preserves W), otherwise NotImplementedError. Replace rows/ctypes, return self.
Matrix.shearYZ(self, y, z) Numeric y,z; postmultiply X′=X+yY+zZ shear for sizes 3/4 (size 4 preserves W), otherwise NotImplementedError. Fresh Matrix.
Matrix.i_shearYZ(self, y, z) Numeric y,z; postmultiply X′=X+yY+zZ shear for sizes 3/4 (size 4 preserves W), otherwise NotImplementedError. Replace rows/ctypes, return self.
Matrix.shearXZ(self, x, z) Numeric x,z; postmultiply Y′=Y+xX+zZ shear for sizes 3/4 (size 4 preserves W), otherwise NotImplementedError. Fresh Matrix.
Matrix.i_shearXZ(self, x, z) Numeric x,z; postmultiply Y′=Y+xX+zZ shear for sizes 3/4 (size 4 preserves W), otherwise NotImplementedError. Replace rows/ctypes, return self.

Vector transform

Exact source declaration Parameters, result and behavior
transform(size, position, matrix) size: output dimension; raw position and nested matrix; fresh list row product. Same-size computes all components, including explicit W. Matrix of size+1 supplies local W=1 and returns size numerators. No perspective divide or input mutation.

Vector.transform(self, position, matrix) and Vector.i_transform use the receiver size and explicit raw position, not its stored coordinates; see Vector methods. General Matrix*Vector still requires matching dimensions. Local implicit promotion is intended for affine position transforms. With projective matrices, truncated numerators without output-W division are not perspective-correct coordinates; use project/unproject or explicit homogeneous processing.

from gem.matrix import Matrix, rotate2, shearXY4
from gem.vector import Vector, transform

pivot = [2, 3]
r = Matrix(3, rotate2(pivot, 90))
assert all(abs(a - b) < 1e-14 for a, b in zip((r * Vector(3, [3, 3, 1])).vector, [2, 4, 1]))
assert all(abs(a - b) < 1e-14 for a, b in zip((r * Vector(3, [2, 3, 1])).vector, [2, 3, 1]))
t = Matrix(4).translate(Vector(3, [2, -3, 4]))
assert transform(3, [1, 2, 3], t.matrix) == [3.0, -1.0, 7.0]
assert (t * Vector(4, [1, 2, 3, 0])).vector == [1.0, 2.0, 3.0, 0.0]
assert (Matrix(4, shearXY4(2, -1)) * Vector(4, [3, 4, 5, 7])).vector == [3.0, 4.0, 7.0, 7.0]
rot = Matrix(4).rotate(Vector(3, [0, 0, 1]), 90)
a = (t * rot) * Vector(4, [0, 0, 0, 1])
b = (rot * t) * Vector(4, [0, 0, 0, 1])
assert all(abs(x - y) < 1e-14 for x, y in zip(a.vector, [3, 2, 4, 1]))
assert b.vector == [2.0, -3.0, 4.0, 1.0]

Homogeneous pivot construction is not exposed as an additional Matrix overload. Unsupported translate dimensions and direct shape edits need a separate policy, not an invented exception guarantee. See decisions and index.

See the graphics gallery example for an executable visualization.