Files
stepanalyser/.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.pyx
Christian Anetzberger a197de9456 initial
2026-01-22 20:23:51 +01:00

677 lines
22 KiB
Cython

# cython: language_level=3
# Copyright (c) 2020-2024, Manfred Moitzi
# License: MIT License
from typing import Sequence, Iterable, Tuple, TYPE_CHECKING, Iterator
from itertools import chain
import math
import numpy as np
import cython
from .vector cimport (
Vec2, Vec3, v3_normalize, v3_isclose, v3_cross, v3_dot,
)
from .vector import X_AXIS, Y_AXIS, Z_AXIS, NULLVEC
from libc.math cimport fabs, sin, cos, tan
if TYPE_CHECKING:
from ezdxf.math import UVec
cdef extern from "constants.h":
const double ABS_TOL
const double REL_TOL
cdef double[16] IDENTITY = [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
]
cdef void set_floats(double *m, object values) except *:
cdef int i = 0
for v in values:
if i < 16: # Do not write beyond array bounds
m[i] = v
i += 1
if i != 16:
raise ValueError("invalid argument count")
cdef class Matrix44:
def __cinit__(self, *args):
cdef int nargs = len(args)
if nargs == 0: # default constructor Matrix44(): fastest setup
self.m = IDENTITY # memcopy!
elif nargs == 1: # 16 numbers: slow setup
set_floats(self.m, args[0])
elif nargs == 4: # 4 rows of 4 numbers: slowest setup
set_floats(self.m, chain(*args))
else:
raise ValueError("invalid argument count: 4 row vectors or "
"iterable of 16 numbers")
def __reduce__(self):
return Matrix44, (tuple(self),)
def __getitem__(self, tuple index) -> float:
cdef int row = index[0]
cdef int col = index[1]
cdef int i = row * 4 + col
if 0 <= i < 16 and 0 <= col < 4:
return self.m[i]
else:
raise IndexError(f'index out of range: {index}')
def __setitem__(self, tuple index, double value):
cdef int row = index[0]
cdef int col = index[1]
cdef int i = row * 4 + col
if 0 <= i < 16 and 0 <= col < 4:
self.m[i] = value
else:
raise IndexError(f'index out of range: {index}')
def __iter__(self):
cdef int i
for i in range(16):
yield self.m[i]
def __repr__(self) -> str:
def format_row(row):
return "(%s)" % ", ".join(str(value) for value in row)
return "Matrix44(%s)" % \
", ".join(format_row(row) for row in self.rows())
def get_2d_transformation(self) -> Tuple[float, ...]:
cdef double *m = self.m
return m[0], m[1], 0.0, m[4], m[5], 0.0, m[12], m[13], 1.0
@staticmethod
def from_2d_transformation(components: Sequence[float]) -> Matrix44:
if len(components) != 6:
raise ValueError(
"First 2 columns of a 3x3 matrix required: m11, m12, m21, m22, m31, m32"
)
m44 = Matrix44()
m44.m[0] = components[0]
m44.m[1] = components[1]
m44.m[4] = components[2]
m44.m[5] = components[3]
m44.m[12] = components[4]
m44.m[13] = components[5]
return m44
def get_row(self, int row) -> Tuple[float, ...]:
cdef int index = row * 4
if 0 <= index < 13:
return self.m[index], self.m[index + 1], self.m[index + 2], self.m[
index + 3]
else:
raise IndexError(f'invalid row index: {row}')
def set_row(self, int row, values: Sequence[float]) -> None:
cdef Py_ssize_t count = len(values)
cdef Py_ssize_t start = row * 4
cdef Py_ssize_t i
if 0 <= row < 4:
if count > 4:
count = 4
for i in range(count):
self.m[start + i] = values[i]
else:
raise IndexError(f'invalid row index: {row}')
def get_col(self, int col) -> Tuple[float, ...]:
if 0 <= col < 4:
return self.m[col], self.m[col + 4], \
self.m[col + 8], self.m[col + 12]
else:
raise IndexError(f'invalid col index: {col}')
def set_col(self, int col, values: Sequence[float]):
cdef Py_ssize_t count = len(values)
cdef Py_ssize_t i
if 0 <= col < 4:
if count > 4:
count = 4
for i in range(count):
self.m[col + i * 4] = values[i]
else:
raise IndexError(f'invalid col index: {col}')
def rows(self) -> Iterator[Tuple[float, ...]]:
return (self.get_row(index) for index in (0, 1, 2, 3))
def columns(self) -> Iterator[Tuple[float, ...]]:
return (self.get_col(index) for index in (0, 1, 2, 3))
def copy(self) -> Matrix44:
cdef Matrix44 _copy = Matrix44()
_copy.m = self.m
return _copy
__copy__ = copy
@property
def origin(self) -> Vec3:
cdef Vec3 v = Vec3()
v.x = self.m[12]
v.y = self.m[13]
v.z = self.m[14]
return v
@origin.setter
def origin(self, v: UVec) -> None:
cdef Vec3 origin = Vec3(v)
self.m[12] = origin.x
self.m[13] = origin.y
self.m[14] = origin.z
@property
def ux(self) -> Vec3:
return self.get_ux()
cdef Vec3 get_ux(self):
cdef Vec3 v = Vec3()
v.x = self.m[0]
v.y = self.m[1]
v.z = self.m[2]
return v
@property
def uy(self) -> Vec3:
return self.get_uy()
cdef Vec3 get_uy(self):
cdef Vec3 v = Vec3()
v.x = self.m[4]
v.y = self.m[5]
v.z = self.m[6]
return v
@property
def uz(self) -> Vec3:
return self.get_uz()
cdef Vec3 get_uz(self):
cdef Vec3 v = Vec3()
v.x = self.m[8]
v.y = self.m[9]
v.z = self.m[10]
return v
@property
def is_cartesian(self) -> bool:
cdef Vec3 x_axis = v3_cross(self.get_uy(), self.get_uz())
return v3_isclose(x_axis, self.get_ux(), REL_TOL, ABS_TOL)
@property
def is_orthogonal(self) -> bool:
cdef Vec3 ux = v3_normalize(self.get_ux(), 1.0)
cdef Vec3 uy = v3_normalize(self.get_uy(), 1.0)
cdef Vec3 uz = v3_normalize(self.get_uz(), 1.0)
return fabs(v3_dot(ux, uy)) < 1e-9 and \
fabs(v3_dot(ux, uz)) < 1e-9 and \
fabs(v3_dot(uy, uz)) < 1e-9
@staticmethod
def scale(double sx, sy = None, sz = None) -> Matrix44:
cdef Matrix44 mat = Matrix44()
mat.m[0] = sx
mat.m[5] = sx if sy is None else sy
mat.m[10] = sx if sz is None else sz
return mat
@staticmethod
def translate(double dx, double dy, double dz) -> Matrix44:
cdef Matrix44 mat = Matrix44()
mat.m[12] = dx
mat.m[13] = dy
mat.m[14] = dz
return mat
@staticmethod
def x_rotate(double angle) -> Matrix44:
cdef Matrix44 mat = Matrix44()
cdef double cos_a = cos(angle)
cdef double sin_a = sin(angle)
mat.m[5] = cos_a
mat.m[6] = sin_a
mat.m[9] = -sin_a
mat.m[10] = cos_a
return mat
@staticmethod
def y_rotate(double angle) -> Matrix44:
cdef Matrix44 mat = Matrix44()
cdef double cos_a = cos(angle)
cdef double sin_a = sin(angle)
mat.m[0] = cos_a
mat.m[2] = -sin_a
mat.m[8] = sin_a
mat.m[10] = cos_a
return mat
@staticmethod
def z_rotate(double angle) -> Matrix44:
cdef Matrix44 mat = Matrix44()
cdef double cos_a = cos(angle)
cdef double sin_a = sin(angle)
mat.m[0] = cos_a
mat.m[1] = sin_a
mat.m[4] = -sin_a
mat.m[5] = cos_a
return mat
@staticmethod
def axis_rotate(axis: UVec, double angle) -> Matrix44:
cdef Matrix44 mat = Matrix44()
cdef double cos_a = cos(angle)
cdef double sin_a = sin(angle)
cdef double one_m_cos = 1.0 - cos_a
cdef Vec3 _axis = Vec3(axis).normalize()
cdef double x = _axis.x
cdef double y = _axis.y
cdef double z = _axis.z
mat.m[0] = x * x * one_m_cos + cos_a
mat.m[1] = y * x * one_m_cos + z * sin_a
mat.m[2] = x * z * one_m_cos - y * sin_a
mat.m[4] = x * y * one_m_cos - z * sin_a
mat.m[5] = y * y * one_m_cos + cos_a
mat.m[6] = y * z * one_m_cos + x * sin_a
mat.m[8] = x * z * one_m_cos + y * sin_a
mat.m[9] = y * z * one_m_cos - x * sin_a
mat.m[10] = z * z * one_m_cos + cos_a
return mat
@staticmethod
def xyz_rotate(double angle_x, double angle_y,
double angle_z) -> Matrix44:
cdef Matrix44 mat = Matrix44()
cdef double cx = cos(angle_x)
cdef double sx = sin(angle_x)
cdef double cy = cos(angle_y)
cdef double sy = sin(angle_y)
cdef double cz = cos(angle_z)
cdef double sz = sin(angle_z)
cdef double sxsy = sx * sy
cdef double cxsy = cx * sy
mat.m[0] = cy * cz
mat.m[1] = sxsy * cz + cx * sz
mat.m[2] = -cxsy * cz + sx * sz
mat.m[4] = -cy * sz
mat.m[5] = -sxsy * sz + cx * cz
mat.m[6] = cxsy * sz + sx * cz
mat.m[8] = sy
mat.m[9] = -sx * cy
mat.m[10] = cx * cy
return mat
@staticmethod
def shear_xy(double angle_x = 0, double angle_y = 0) -> Matrix44:
cdef Matrix44 mat = Matrix44()
cdef double tx = tan(angle_x)
cdef double ty = tan(angle_y)
mat.m[1] = ty
mat.m[4] = tx
return mat
@staticmethod
def perspective_projection(double left, double right, double top,
double bottom, double near,
double far) -> Matrix44:
cdef Matrix44 mat = Matrix44()
mat.m[0] = (2. * near) / (right - left)
mat.m[5] = (2. * near) / (top - bottom)
mat.m[8] = (right + left) / (right - left)
mat.m[9] = (top + bottom) / (top - bottom)
mat.m[10] = -((far + near) / (far - near))
mat.m[11] = -1
mat.m[14] = -((2. * far * near) / (far - near))
return mat
@staticmethod
def perspective_projection_fov(fov: float, aspect: float, near: float,
far: float) -> Matrix44:
vrange = near * math.tan(fov / 2.)
left = -vrange * aspect
right = vrange * aspect
bottom = -vrange
top = vrange
return Matrix44.perspective_projection(left, right, bottom, top, near,
far)
@staticmethod
def chain(*matrices: Matrix44) -> Matrix44:
cdef Matrix44 transformation = Matrix44()
for matrix in matrices:
transformation *= matrix
return transformation
def __imul__(self, Matrix44 other) -> Matrix44:
cdef double[16] m1 = self.m
cdef double *m2 = other.m
self.m[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8] + \
m1[3] * m2[12]
self.m[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9] + \
m1[3] * m2[13]
self.m[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10] + \
m1[3] * m2[14]
self.m[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + \
m1[3] * m2[15]
self.m[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8] + \
m1[7] * m2[12]
self.m[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9] + \
m1[7] * m2[13]
self.m[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10] + \
m1[7] * m2[14]
self.m[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + \
m1[7] * m2[15]
self.m[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8] + \
m1[11] * m2[12]
self.m[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9] + \
m1[11] * m2[13]
self.m[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10] + \
m1[11] * m2[14]
self.m[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + \
m1[11] * m2[15]
self.m[12] = m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + \
m1[15] * m2[12]
self.m[13] = m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + \
m1[15] * m2[13]
self.m[14] = m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + \
m1[15] * m2[14]
self.m[15] = m1[12] * m2[3] + m1[13] * m2[7] + m1[14] * m2[11] + \
m1[15] * m2[15]
return self
def __mul__(self, Matrix44 other) -> Matrix44:
cdef Matrix44 res_matrix = self.copy()
return res_matrix.__imul__(other)
# __matmul__ = __mul__ does not work!
def __matmul__(self, Matrix44 other) -> Matrix44:
cdef Matrix44 res_matrix = self.copy()
return res_matrix.__imul__(other)
def transpose(self) -> None:
swap(&self.m[1], &self.m[4])
swap(&self.m[2], &self.m[8])
swap(&self.m[3], &self.m[12])
swap(&self.m[6], &self.m[9])
swap(&self.m[7], &self.m[13])
swap(&self.m[11], &self.m[14])
def determinant(self) -> float:
cdef double *m = self.m
return m[0] * m[5] * m[10] * m[15] - m[0] * m[5] * m[11] * m[14] + \
m[0] * m[6] * m[11] * m[13] - m[0] * m[6] * m[9] * m[15] + \
m[0] * m[7] * m[9] * m[14] - m[0] * m[7] * m[10] * m[13] - \
m[1] * m[6] * m[11] * m[12] + m[1] * m[6] * m[8] * m[15] - \
m[1] * m[7] * m[8] * m[14] + m[1] * m[7] * m[10] * m[12] - \
m[1] * m[4] * m[10] * m[15] + m[1] * m[4] * m[11] * m[14] + \
m[2] * m[7] * m[8] * m[13] - m[2] * m[7] * m[9] * m[12] + \
m[2] * m[4] * m[9] * m[15] - m[2] * m[4] * m[11] * m[13] + \
m[2] * m[5] * m[11] * m[12] - m[2] * m[5] * m[8] * m[15] - \
m[3] * m[4] * m[9] * m[14] + m[3] * m[4] * m[10] * m[13] - \
m[3] * m[5] * m[10] * m[12] + m[3] * m[5] * m[8] * m[14] - \
m[3] * m[6] * m[8] * m[13] + m[3] * m[6] * m[9] * m[12]
def inverse(self) -> None:
cdef double[16] m = self.m # memcopy
cdef double det = self.determinant()
cdef double f = 1. / det
self.m[0] = (m[6] * m[11] * m[13] - m[7] * m[10] * m[13] + m[7] * m[9] *
m[14] - m[5] * m[11] * m[14] - m[6] * m[9] * m[15] + m[5] *
m[10] * m[15]) * f
self.m[1] = (m[3] * m[10] * m[13] - m[2] * m[11] * m[13] - m[3] * m[9] *
m[14] + m[1] * m[11] * m[14] + m[2] * m[9] * m[15] -
m[1] * m[10] * m[15]) * f
self.m[2] = (m[2] * m[7] * m[13] - m[3] * m[6] * m[13] + m[3] * m[5] *
m[14] - m[1] * m[7] * m[14] - m[2] * m[5] * m[15] +
m[1] * m[6] * m[15]) * f
self.m[3] = (m[3] * m[6] * m[9] - m[2] * m[7] * m[9] - m[3] * m[5] *
m[10] + m[1] * m[7] * m[10] + m[2] * m[5] * m[11] - m[1] *
m[6] * m[11]) * f
self.m[4] = (m[7] * m[10] * m[12] - m[6] * m[11] * m[12] - m[7] * m[8] *
m[14] + m[4] * m[11] * m[14] + m[6] * m[8] * m[15] -
m[4] * m[10] * m[15]) * f
self.m[5] = (m[2] * m[11] * m[12] - m[3] * m[10] * m[12] + m[3] * m[8] *
m[14] - m[0] * m[11] * m[14] - m[2] * m[8] * m[15] +
m[0] * m[10] * m[15]) * f
self.m[6] = (m[3] * m[6] * m[12] - m[2] * m[7] * m[12] - m[3] * m[4] *
m[14] + m[0] * m[7] * m[14] + m[2] * m[4] * m[15] -
m[0] * m[6] * m[15]) * f
self.m[7] = (m[2] * m[7] * m[8] - m[3] * m[6] * m[8] + m[3] * m[4] *
m[10] - m[0] * m[7] * m[10] - m[2] * m[4] * m[11] +
m[0] * m[6] * m[11]) * f
self.m[8] = (m[5] * m[11] * m[12] - m[7] * m[9] * m[12] + m[7] * m[8] *
m[13] - m[4] * m[11] * m[13] - m[5] * m[8] * m[15] +
m[4] * m[9] * m[15]) * f
self.m[9] = (m[3] * m[9] * m[12] - m[1] * m[11] * m[12] - m[3] *
m[8] * m[13] + m[0] * m[11] * m[13] + m[1] * m[8] *
m[15] - m[0] * m[9] * m[15]) * f
self.m[10] = (m[1] * m[7] * m[12] - m[3] * m[5] * m[12] + m[3] *
m[4] * m[13] - m[0] * m[7] * m[13] - m[1] * m[4] *
m[15] + m[0] * m[5] * m[15]) * f
self.m[11] = (m[3] * m[5] * m[8] - m[1] * m[7] * m[8] - m[3] * m[4] *
m[9] + m[0] * m[7] * m[9] + m[1] * m[4] * m[11] -
m[0] * m[5] * m[11]) * f
self.m[12] = (m[6] * m[9] * m[12] - m[5] * m[10] * m[12] - m[6] *
m[8] * m[13] + m[4] * m[10] * m[13] + m[5] * m[8] *
m[14] - m[4] * m[9] * m[14]) * f
self.m[13] = (m[1] * m[10] * m[12] - m[2] * m[9] * m[12] + m[2] *
m[8] * m[13] - m[0] * m[10] * m[13] - m[1] * m[8] *
m[14] + m[0] * m[9] * m[14]) * f
self.m[14] = (m[2] * m[5] * m[12] - m[1] * m[6] * m[12] - m[2] *
m[4] * m[13] + m[0] * m[6] * m[13] + m[1] * m[4] *
m[14] - m[0] * m[5] * m[14]) * f
self.m[15] = (m[1] * m[6] * m[8] - m[2] * m[5] * m[8] + m[2] * m[4] *
m[9] - m[0] * m[6] * m[9] - m[1] * m[4] * m[10] +
m[0] * m[5] * m[10]) * f
@staticmethod
def ucs(ux=X_AXIS, uy=Y_AXIS, uz=Z_AXIS, origin=NULLVEC) -> Matrix44:
cdef Matrix44 mat = Matrix44()
cdef Vec3 _ux = Vec3(ux)
cdef Vec3 _uy = Vec3(uy)
cdef Vec3 _uz = Vec3(uz)
cdef Vec3 _origin = Vec3(origin)
mat.m[0] = _ux.x
mat.m[1] = _ux.y
mat.m[2] = _ux.z
mat.m[4] = _uy.x
mat.m[5] = _uy.y
mat.m[6] = _uy.z
mat.m[8] = _uz.x
mat.m[9] = _uz.y
mat.m[10] = _uz.z
mat.m[12] = _origin.x
mat.m[13] = _origin.y
mat.m[14] = _origin.z
return mat
def transform(self, vector: UVec) -> Vec3:
cdef Vec3 res = Vec3(vector)
cdef double x = res.x
cdef double y = res.y
cdef double z = res.z
cdef double *m = self.m
res.x = x * m[0] + y * m[4] + z * m[8] + m[12]
res.y = x * m[1] + y * m[5] + z * m[9] + m[13]
res.z = x * m[2] + y * m[6] + z * m[10] + m[14]
return res
def transform_direction(self, vector: UVec, normalize=False) -> Vec3:
cdef Vec3 res = Vec3(vector)
cdef double x = res.x
cdef double y = res.y
cdef double z = res.z
cdef double *m = self.m
res.x = x * m[0] + y * m[4] + z * m[8]
res.y = x * m[1] + y * m[5] + z * m[9]
res.z = x * m[2] + y * m[6] + z * m[10]
if normalize:
return v3_normalize(res, 1.0)
else:
return res
ocs_to_wcs = transform_direction
def transform_vertices(self, vectors: Iterable[UVec]) -> Iterator[Vec3]:
cdef double *m = self.m
cdef Vec3 res
cdef double x, y, z
for vector in vectors:
res = Vec3(vector)
x = res.x
y = res.y
z = res.z
res.x = x * m[0] + y * m[4] + z * m[8] + m[12]
res.y = x * m[1] + y * m[5] + z * m[9] + m[13]
res.z = x * m[2] + y * m[6] + z * m[10] + m[14]
yield res
def fast_2d_transform(self, points: Iterable[UVec]) -> Iterator[Vec2]:
cdef double m0 = self.m[0]
cdef double m1 = self.m[1]
cdef double m4 = self.m[4]
cdef double m5 = self.m[5]
cdef double m12 = self.m[12]
cdef double m13 = self.m[13]
cdef double x, y
cdef Vec2 res
for pnt in points:
res = Vec2(pnt)
x = res.x
y = res.y
res.x = x * m0 + y * m4 + m12
res.y = x * m1 + y * m5 + m13
yield res
def transform_array_inplace(self, array: np.ndarray, ndim: int) -> None:
"""Transforms a numpy array inplace, the argument `ndim` defines the dimensions
to transform, this allows 2D/3D transformation on arrays with more columns
e.g. a polyline array which stores points as (x, y, start_width, end_width,
bulge) values.
"""
cdef int _ndim = ndim
if _ndim == 2:
assert array.shape[1] > 1
transform_2d_array_inplace(self.m, array, array.shape[0])
elif _ndim == 3:
assert array.shape[1] > 2
transform_3d_array_inplace(self.m, array, array.shape[0])
else:
raise ValueError("ndim has to be 2 or 3")
def transform_directions(
self, vectors: Iterable[UVec], normalize=False
) -> Iterator[Vec3]:
cdef double *m = self.m
cdef Vec3 res
cdef double x, y, z
cdef bint _normalize = normalize
for vector in vectors:
res = Vec3(vector)
x = res.x
y = res.y
z = res.z
res.x = x * m[0] + y * m[4] + z * m[8]
res.y = x * m[1] + y * m[5] + z * m[9]
res.z = x * m[2] + y * m[6] + z * m[10]
yield v3_normalize(res, 1.0) if _normalize else res
def ucs_vertex_from_wcs(self, wcs: Vec3) -> Vec3:
return self.ucs_direction_from_wcs(wcs - self.origin)
def ucs_direction_from_wcs(self, wcs: UVec) -> Vec3:
cdef double *m = self.m
cdef Vec3 res = Vec3(wcs)
cdef double x = res.x
cdef double y = res.y
cdef double z = res.z
res.x = x * m[0] + y * m[1] + z * m[2]
res.y = x * m[4] + y * m[5] + z * m[6]
res.z = x * m[8] + y * m[9] + z * m[10]
return res
ocs_from_wcs = ucs_direction_from_wcs
@cython.boundscheck(False)
@cython.wraparound(False)
cdef void transform_2d_array_inplace(double *m, double [:, ::1] array, Py_ssize_t size):
cdef double m0 = m[0]
cdef double m1 = m[1]
cdef double m4 = m[4]
cdef double m5 = m[5]
cdef double m12 = m[12]
cdef double m13 = m[13]
cdef double x, y
cdef Py_ssize_t i
for i in range(size):
x = array[i, 0]
y = array[i, 1]
array[i, 0] = x * m0 + y * m4 + m12
array[i, 1] = x * m1 + y * m5 + m13
@cython.boundscheck(False)
@cython.wraparound(False)
cdef void transform_3d_array_inplace(double *m, double [:, ::1] array, Py_ssize_t size):
cdef double x, y, z
cdef Py_ssize_t i
for i in range(size):
x = array[i, 0]
y = array[i, 1]
z = array[i, 2]
array[i, 0] = x * m[0] + y * m[4] + z * m[8] + m[12]
array[i, 1] = x * m[1] + y * m[5] + z * m[9] + m[13]
array[i, 2] = x * m[2] + y * m[6] + z * m[10] + m[14]