Repository: Zuzu-Typ/PyGLM (language: python)

Issue:
Possible to apply transformation matrix to each vec2 in an array?
I'm not sure if what I'm asking for is possible, nor if it *should* be possible.

I'm using pyglm to efficiently scale, rotate, and translate polygons for the purposes of collision detection.  I'm hoping that pyglm is faster than pure python.  But ultimately, the calculations will never be used for rendering, only for checking polygon-polygon collisions on the CPU.

I'm trying to keep as much as possible within PyGLM arrays so that nothing needs to be unboxed into python.  However, I cannot seem to apply a transformation matrix to an array of vec2 points.  I'm not sure if this should be possible.  The transformation is applied to the first vec2 correctly, then that value is repeated multiple times in the resulting array.

```python
    # Square AABB hitbox from 0,0 to 1,1
    hitbox = glm.array(
        glm.vec2(0., 1.),
        glm.vec2(1., 1.),
        glm.vec2(1., 0.),
        glm.vec2(0., 0.),
    )
    rotation = glm.radians(90)
    scale_x = 1.
    scale_y = 2.

    cos_rotation = glm.cos(rotation)
    sin_rotation = glm.sin(rotation)

    rotation_scale_matrix = glm.mat2x2(
        scale_x * cos_rotation, -scale_x * sin_rotation,
        scale_y * sin_rotation, scale_y * cos_rotation
    )
    hitbox_rotated = hitbox * rotation_scale_matrix

    print(repr(hitbox))
    print(repr(hitbox_rotated))

    # I expect to see the points rotated 90 degrees, approximately:
    # array(vec2(-1, 0), vec2(-1, 1), vec2(0, 1), vec2(0, 0))

    # Instead, I see four repeats of the first item:
    # array(vec2(-1, 0), vec2(-1, 0), vec2(-1, 0), vec2(-1, 0))

    # The zeros are actually 1.22465e-16 but I assume that's fine, just a precision thing
```
Possible to apply transformation matrix to each vec2 in an array?
I'm not sure if what I'm asking for is possible, nor if it *should* be possible.

I'm using pyglm to efficiently scale, rotate, and translate polygons for the purposes of collision detection.  I'm hoping that pyglm is faster than pure python.  But ultimately, the calculations will never be used for rendering, only for checking polygon-polygon collisions on the CPU.

I'm trying to keep as much as possible within PyGLM arrays so that nothing needs to be unboxed into python.  However, I cannot seem to apply a transformation matrix to an array of vec2 points.  I'm not sure if this should be possible.  The transformation is applied to the first vec2 correctly, then that value is repeated multiple times in the resulting array.

```python
    # Square AABB hitbox from 0,0 to 1,1
    hitbox = glm.array(
        glm.vec2(0., 1.),
        glm.vec2(1., 1.),
        glm.vec2(1., 0.),
        glm.vec2(0., 0.),
    )
    rotation = glm.radians(90)
    scale_x = 1.
    scale_y = 2.

    cos_rotation = glm.cos(rotation)
    sin_rotation = glm.sin(rotation)

    rotation_scale_matrix = glm.mat2x2(
        scale_x * cos_rotation, -scale_x * sin_rotation,
        scale_y * sin_rotation, scale_y * cos_rotation
    )
    hitbox_rotated = hitbox * rotation_scale_matrix

    print(repr(hitbox))
    print(repr(hitbox_rotated))

    # I expect to see the points rotated 90 degrees, approximately:
    # array(vec2(-1, 0), vec2(-1, 1), vec2(0, 1), vec2(0, 0))

    # Instead, I see four repeats of the first item:
    # array(vec2(-1, 0), vec2(-1, 0), vec2(-1, 0), vec2(-1, 0))

    # The zeros are actually 1.22465e-16 but I assume that's fine, just a precision thing
```
Possible to apply transformation matrix to each vec2 in an array?
I'm not sure if what I'm asking for is possible, nor if it *should* be possible.

I'm using pyglm to efficiently scale, rotate, and translate polygons for the purposes of collision detection.  I'm hoping that pyglm is faster than pure python.  But ultimately, the calculations will never be used for rendering, only for checking polygon-polygon collisions on the CPU.

I'm trying to keep as much as possible within PyGLM arrays so that nothing needs to be unboxed into python.  However, I cannot seem to apply a transformation matrix to an array of vec2 points.  I'm not sure if this should be possible.  The transformation is applied to the first vec2 correctly, then that value is repeated multiple times in the resulting array.

```python
    # Square AABB hitbox from 0,0 to 1,1
    hitbox = glm.array(
        glm.vec2(0., 1.),
        glm.vec2(1., 1.),
        glm.vec2(1., 0.),
        glm.vec2(0., 0.),
    )
    rotation = glm.radians(90)
    scale_x = 1.
    scale_y = 2.

    cos_rotation = glm.cos(rotation)
    sin_rotation = glm.sin(rotation)

    rotation_scale_matrix = glm.mat2x2(
        scale_x * cos_rotation, -scale_x * sin_rotation,
        scale_y * sin_rotation, scale_y * cos_rotation
    )
    hitbox_rotated = hitbox * rotation_scale_matrix

    print(repr(hitbox))
    print(repr(hitbox_rotated))

    # I expect to see the points rotated 90 degrees, approximately:
    # array(vec2(-1, 0), vec2(-1, 1), vec2(0, 1), vec2(0, 0))

    # Instead, I see four repeats of the first item:
    # array(vec2(-1, 0), vec2(-1, 0), vec2(-1, 0), vec2(-1, 0))

    # The zeros are actually 1.22465e-16 but I assume that's fine, just a precision thing
```


Discussion / hints:
Hi there @cspotcode 

This seems to be a bug in the array type. This is certainly not supposed to happen.
The calculation should happen per element, as you would have expected.
I wasn't able to locate the source of the bug yet.

For the time being, I recommend you to use NumPy for the calculations. 
NumPy is great at performing the same operation on a lot of data, because it uses special CPU instructions, which PyGLM is not able to utilize, therefore it will also be faster than PyGLM's array would.

To do this, you would edit your code as follows:
```Python
import glm
import numpy

# Square AABB hitbox from 0,0 to 1,1
hitbox = glm.array(
	glm.vec2(0., 1.),
	glm.vec2(1., 1.),
	glm.vec2(1., 0.),
	glm.vec2(0., 0.),
)
rotation = glm.radians(90)
scale_x = 1.
scale_y = 2.

cos_rotation = glm.cos(rotation)
sin_rotation = glm.sin(rotation)

rotation_scale_matrix = glm.mat2x2(
	scale_x * cos_rotation, -scale_x * sin_rotation,
	scale_y * sin_rotation, scale_y * cos_rotation
)

hitbox_rotated = glm.array(numpy.array(hitbox) * numpy.matrix(rotation_scale_matrix))

print(repr(hitbox))
print(repr(hitbox_rotated))
```

Thank you for reporting the bug (:

Cheers,
--Zuzu_Typ--
Thanks for taking a look, I'm glad to hear this is a bug instead of new API surface.

I actually did use numpy before attempting a switch to pyglm.  But since this math is dealing with 8-point hitboxes in a game engine, I guess the numpy overhead was too high for dealing with only 8 points at a time.  I think for these tiny arrays, *any* overhead at all makes it slower than pure python.

Right now the collision code is implemented in pure python, and 3.11 has so far been consistently faster than both pyglm and numpy.  But I keep experimenting to see if I can get a speedup with pyglm.
Hi there @cspotcode 

This seems to be a bug in the array type. This is certainly not supposed to happen.
The calculation should happen per element, as you would have expected.
I wasn't able to locate the source of the bug yet.

For the time being, I recommend you to use NumPy for the calculations. 
NumPy is great at performing the same operation on a lot of data, because it uses special CPU instructions, which PyGLM is not able to utilize, therefore it will also be faster than PyGLM's array would.

To do this, you would edit your code as follows:
```Python
import glm
import numpy

# Square AABB hitbox from 0,0 to 1,1
hitbox = glm.array(
	glm.vec2(0., 1.),
	glm.vec2(1., 1.),
	glm.vec2(1., 0.),
	glm.vec2(0., 0.),
)
rotation = glm.radians(90)
scale_x = 1.
scale_y = 2.

cos_rotation = glm.cos(rotation)
sin_rotation = glm.sin(rotation)

rotation_scale_matrix = glm.mat2x2(
	scale_x * cos_rotation, -scale_x * sin_rotation,
	scale_y * sin_rotation, scale_y * cos_rotation
)

hitbox_rotated = glm.array(numpy.array(hitbox) * numpy.matrix(rotation_scale_matrix))

print(repr(hitbox))
print(repr(hitbox_rotated))
```

Thank you for reporting the bug (:

Cheers,
--Zuzu_Typ--
Thanks for taking a look, I'm glad to hear this is a bug instead of new API surface.

I actually did use numpy before attempting a switch to pyglm.  But since this math is dealing with 8-point hitboxes in a game engine, I guess the numpy overhead was too high for dealing with only 8 points at a time.  I think for these tiny arrays, *any* overhead at all makes it slower than pure python.

Right now the collision code is implemented in pure python, and 3.11 has so far been consistently faster than both pyglm and numpy.  But I keep experimenting to see if I can get a speedup with pyglm.
Hi there @cspotcode 

This seems to be a bug in the array type. This is certainly not supposed to happen.
The calculation should happen per element, as you would have expected.
I wasn't able to locate the source of the bug yet.

For the time being, I recommend you to use NumPy for the calculations. 
NumPy is great at performing the same operation on a lot of data, because it uses special CPU instructions, which PyGLM is not able to utilize, therefore it will also be faster than PyGLM's array would.

To do this, you would edit your code as follows:
```Python
import glm
import numpy

# Square AABB hitbox from 0,0 to 1,1
hitbox = glm.array(
	glm.vec2(0., 1.),
	glm.vec2(1., 1.),
	glm.vec2(1., 0.),
	glm.vec2(0., 0.),
)
rotation = glm.radians(90)
scale_x = 1.
scale_y = 2.

cos_rotation = glm.cos(rotation)
sin_rotation = glm.sin(rotation)

rotation_scale_matrix = glm.mat2x2(
	scale_x * cos_rotation, -scale_x * sin_rotation,
	scale_y * sin_rotation, scale_y * cos_rotation
)

hitbox_rotated = glm.array(numpy.array(hitbox) * numpy.matrix(rotation_scale_matrix))

print(repr(hitbox))
print(repr(hitbox_rotated))
```

Thank you for reporting the bug (:

Cheers,
--Zuzu_Typ--
Thanks for taking a look, I'm glad to hear this is a bug instead of new API surface.

I actually did use numpy before attempting a switch to pyglm.  But since this math is dealing with 8-point hitboxes in a game engine, I guess the numpy overhead was too high for dealing with only 8 points at a time.  I think for these tiny arrays, *any* overhead at all makes it slower than pure python.

Right now the collision code is implemented in pure python, and 3.11 has so far been consistently faster than both pyglm and numpy.  But I keep experimenting to see if I can get a speedup with pyglm.

Fix patch:
diff --git a/PyGLM/type_methods/glmArray.h b/PyGLM/type_methods/glmArray.h
index 6d3e55f..6259d4f 100644
--- a/PyGLM/type_methods/glmArray.h
+++ b/PyGLM/type_methods/glmArray.h
@@ -5134,15 +5134,13 @@ static PyObject* glmArray_mul_T_MMUL(glmArray* arr1, glmArray* arr2) {
 	outArray->readonly = false;
 	outArray->reference = NULL;
 
-	Py_ssize_t n, arr1Stride, arr2Stride, trueShape1;
+	Py_ssize_t n, arr1Stride, arr2Stride;
 
 	if (arr1->glmType == PyGLM_TYPE_VEC) {
 		n = arr1->shape[0];
 		arr1Stride = 1;
 		arr2Stride = arr2->shape[1];
 
-		trueShape1 = 1;
-
 		outArray->glmType = PyGLM_TYPE_VEC;
 		outArray->shape[0] = arr2->shape[0];
 		outArray->shape[1] = 0;
@@ -5155,8 +5153,6 @@ static PyObject* glmArray_mul_T_MMUL(glmArray* arr1, glmArray* arr2) {
 		arr1Stride = arr1->shape[1];
 		arr2Stride = 0;
 
-		trueShape1 = arr1->shape[1];
-
 		outArray->glmType = PyGLM_TYPE_VEC;
 		outArray->shape[0] = arr1->shape[1];
 		outArray->shape[1] = 0;
@@ -5169,8 +5165,6 @@ static PyObject* glmArray_mul_T_MMUL(glmArray* arr1, glmArray* arr2) {
 		arr1Stride = arr1->shape[1];
 		arr2Stride = arr2->shape[1];
 
-		trueShape1 = arr1->shape[1];
-
 		outArray->glmType = PyGLM_TYPE_MAT;
 		outArray->shape[0] = arr2->shape[0];
 		outArray->shape[1] = arr1->shape[1];
@@ -5198,13 +5192,16 @@ static PyObject* glmArray_mul_T_MMUL(glmArray* arr1, glmArray* arr2) {
 		for (Py_ssize_t j = 0; j < outArrayRatio; j++) {
 			T result = (T)0;
 			for (Py_ssize_t k = 0; k < n; k++) {
-				T a = arr1DataPtr[k * arr1Stride + j % trueShape1];
-				T b = arr2DataPtr[k + (j / trueShape1) * arr2Stride];
+				T a = arr1DataPtr[k * arr1Stride + j % arr1Stride];
+				T b = arr2DataPtr[k + (j / arr1Stride) * arr2Stride];
 
 				result += a * b;
 			}
 			outArrayDataPtr[outArrayIndex++] = result;
 		}
+		// move pointers by one item
+		arr1DataPtr = reinterpret_cast<T*>(reinterpret_cast<char*>(arr1DataPtr) + arr1->itemSize);
+		arr2DataPtr = reinterpret_cast<T*>(reinterpret_cast<char*>(arr2DataPtr) + arr2->itemSize);
 	}
 
 	return (PyObject*)outArray;
@@ -5283,15 +5280,13 @@ static PyObject* glmArray_mulO_T(glmArray* arr, T* o, Py_ssize_t o_size, PyGLMTy
 		return (PyObject*)outArray;
 	}
 
-	Py_ssize_t n, arrStride, oStride, trueShape1;
+	Py_ssize_t n, arrStride, oStride;
 
 	if (arr->glmType == PyGLM_TYPE_VEC) {
 		n = arr->shape[0];
 		arrStride = 1;
 		oStride = pto->R;
 
-		trueShape1 = 1;
-
 		outArray->glmType = PyGLM_TYPE_VEC;
 		outArray->shape[0] = pto->C;
 		outArray->shape[1] = 0;
@@ -5304,8 +5299,6 @@ static PyObject* glmArray_mulO_T(glmArray* arr, T* o, Py_ssize_t o_size, PyGLMTy
 		arrStride = arr->shape[1];
 		oStride = 0;
 
-		trueShape1 = arr->shape[1];
-
 		outArray->glmType = PyGLM_TYPE_VEC;
 		outArray->shape[0] = arr->shape[1];
 		outArray->shape[1] = 0;
@@ -5318,8 +5311,6 @@ static PyObject* glmArray_mulO_T(glmArray* arr, T* o, Py_ssize_t o_size, PyGLMTy
 		arrStride = arr->shape[1];
 		oStride = pto->R;
 
-		trueShape1 = arr->shape[1];
-
 		outArray->glmType = PyGLM_TYPE_MAT;
 		outArray->shape[0] = pto->C;
 		outArray->shape[1] = arr->shape[1];
@@ -5346,13 +5337,14 @@ static PyObject* glmArray_mulO_T(glmArray* arr, T* o, Py_ssize_t o_size, PyGLMTy
 		for (Py_ssize_t j = 0; j < outArrayRatio; j++) {
 			T result = (T)0;
 			for (Py_ssize_t k = 0; k < n; k++) {
-				T a = arrDataPtr[k * arrStride + j % trueShape1];
-				T b = o[k + (j / trueShape1) * oStride];
+				T a = arrDataPtr[k * arrStride + j % arrStride];
+				T b = o[k + (j / arrStride) * oStride];
 
 				result = result + a * b;
 			}
 			outArrayDataPtr[outArrayIndex++] = result;
 		}
+		arrDataPtr = reinterpret_cast<T*>(reinterpret_cast<char*>(arrDataPtr) + arr->itemSize);
 	}
 
 	return (PyObject*)outArray;
@@ -5441,6 +5433,7 @@ static PyObject* glmArray_rmulO_T(glmArray* arr, T* o, Py_ssize_t o_size, PyGLMT
 			}
 			outArrayDataPtr[outArrayIndex++] = result;
 		}
+		arrDataPtr = reinterpret_cast<T*>(reinterpret_cast<char*>(arrDataPtr) + arr->itemSize);
 	}
 
 	return (PyObject*)outArray;


Test patch:
diff --git a/test/PyGLM_test.py b/test/PyGLM_test.py
index f815211..1d4b403 100644
--- a/test/PyGLM_test.py
+++ b/test/PyGLM_test.py
@@ -3643,7 +3643,36 @@ def test_mat2x2():
 ##/core_type_mat2x2 ##
 ###/GLM TESTS ###
 
-
+def test_array_matmul():
+    hitbox = glm.array(
+        glm.vec2(0., 1.),
+        glm.vec2(1., 1.),
+        glm.vec2(1., 0.),
+        glm.vec2(0., 0.),
+    )
+    rotation = glm.radians(90)
+    scale_x = 1.
+    scale_y = 2.
+
+    cos_rotation = glm.cos(rotation)
+    sin_rotation = glm.sin(rotation)
+
+    rotation_scale_matrix = glm.mat2x2(
+        scale_x * cos_rotation, -scale_y * sin_rotation,
+        scale_x * sin_rotation, scale_y * cos_rotation
+    )
+    hitbox_rotated = hitbox * rotation_scale_matrix
+
+    # I expect to see the points rotated 90 degrees, approximately:
+    # array(vec2(-2, 0), vec2(-2, 1), vec2(0, 1), vec2(0, 0))
+    assert glm.equal(hitbox_rotated[0].x, -2, 0.00001)
+    assert glm.equal(hitbox_rotated[0].y, 0, 0.00001)
+    assert glm.equal(hitbox_rotated[1].x, -2, 0.00001)
+    assert glm.equal(hitbox_rotated[1].y, 1, 0.00001)
+    assert glm.equal(hitbox_rotated[2].x, 0, 0.00001)
+    assert glm.equal(hitbox_rotated[2].y, 1, 0.00001)
+    assert glm.equal(hitbox_rotated[3].x, 0, 0.00001)
+    assert glm.equal(hitbox_rotated[3].y, 0, 0.00001)
 
 ### TEST TEST ###