blob: a71deef5e8e8ac7c4b73256de00f056df9af001a [file] [log] [blame]
#include "shared.rsh"
#define EXPECT(row, col, a, b) \
do { \
if (fabs((a) - (b)) > 0.00001f) { \
failed = true; \
rsDebug("Matrix operation FAILED at line", __LINE__); \
rsDebug(" row: ", row); \
rsDebug(" col: ", col); \
rsDebug(" " #a, (a)); \
rsDebug(" " #b, (b)); \
} \
} while (0)
static bool testMatrixSetAndGet() {
bool failed = false;
rsDebug("Testing MatrixSetAndGet", 0);
rs_matrix2x2 m2;
rs_matrix3x3 m3;
rs_matrix4x4 m4;
// Set each cell to 100 * row + col
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
rsMatrixSet(&m2, col, row, row * 100.f + col);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
rsMatrixSet(&m3, col, row, row * 100.f + col);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
rsMatrixSet(&m4, col, row, row * 100.f + col);
}
}
// Verify these values.
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2, col, row), row * 100.f + col);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3, col, row), row * 100.f + col);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), row * 100.f + col);
}
}
return failed;
}
static bool testMatrixLoadFromArray() {
bool failed = false;
rsDebug("Testing MatrixLoadFromArray", 0);
rs_matrix2x2 m2;
rs_matrix3x3 m3;
rs_matrix4x4 m4;
// Matrix are loaded by rsMatrixLoad in a column-major format.
const float m2Values[] = { 11.f, 21.f,
12.f, 22.f };
const float m3Values[] = { 11.f, 21.f, 31.f,
12.f, 22.f, 32.f,
13.f, 23.f, 33.f };
const float m4Values[] = { 11.f, 21.f, 31.f, 41.f,
12.f, 22.f, 32.f, 42.f,
13.f, 23.f, 33.f, 43.f,
14.f, 24.f, 34.f, 44.f };
// Test loading from arrays.
rsMatrixLoad(&m2, m2Values);
rsMatrixLoad(&m3, m3Values);
rsMatrixLoad(&m4, m4Values);
// Rows and columns are 0 indexed.
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2, col, row), (row + 1) * 10 + (col + 1));
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3, col, row), (row + 1) * 10 + (col + 1));
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), (row + 1) * 10 + (col + 1));
}
}
return failed;
}
/* Load the matrix with the values of v, where the values are in row major.
* This makes the tests below easier to read than if they were column major,
* as rsLoadMatrix does.
*/
static void loadByRow2(rs_matrix2x2* m2, const float* v) {
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
rsMatrixSet(m2, col, row, v[2 * row + col]);
}
}
}
static void loadByRow3(rs_matrix3x3* m3, const float* v) {
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
rsMatrixSet(m3, col, row, v[3 * row + col]);
}
}
}
static void loadByRow4(rs_matrix4x4* m4, const float* v) {
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
rsMatrixSet(m4, col, row, v[4 * row + col]);
}
}
}
static bool testMatrixLoadFromMatrix() {
bool failed = false;
rsDebug("Testing MatrixLoadFromMatrix", 0);
rs_matrix2x2 m2;
rs_matrix3x3 m3;
rs_matrix4x4 m4;
const float m2Values[] = { 11.f, 12.f,
21.f, 22.f };
const float m3Values[] = { 11.f, 12.f, 13.f,
21.f, 22.f, 23.f,
31.f, 32.f, 33.f };
const float m4Values[] = { 11.f, 12.f, 13.f, 14.f,
21.f, 22.f, 23.f, 24.f,
31.f, 32.f, 33.f, 34.f,
41.f, 42.f, 43.f, 44.f };
loadByRow2(&m2, m2Values);
loadByRow3(&m3, m3Values);
loadByRow4(&m4, m4Values);
rs_matrix2x2 m2CopyOfM2;
rs_matrix3x3 m3CopyOfM3;
rs_matrix4x4 m4CopyOfM2;
rs_matrix4x4 m4CopyOfM3;
rs_matrix4x4 m4CopyOfM4;
rsMatrixLoad(&m2CopyOfM2, &m2);
rsMatrixLoad(&m3CopyOfM3, &m3);
rsMatrixLoad(&m4CopyOfM2, &m2);
rsMatrixLoad(&m4CopyOfM3, &m3);
rsMatrixLoad(&m4CopyOfM4, &m4);
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2CopyOfM2, col, row), m2Values[row * 2 + col]);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3CopyOfM3, col, row), m3Values[row * 3 + col]);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
if (col < 2 && row < 2) {
EXPECT(row, col, rsMatrixGet(&m4CopyOfM2, col, row), m2Values[row * 2 + col]);
} else {
EXPECT(row, col, rsMatrixGet(&m4CopyOfM2, col, row), row == col ? 1.f : 0.f);
}
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
if (col < 3 && row < 3) {
EXPECT(row, col, rsMatrixGet(&m4CopyOfM3, col, row), m3Values[row * 3 + col]);
} else {
EXPECT(row, col, rsMatrixGet(&m4CopyOfM3, col, row), row == col ? 1.f : 0.f);
}
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4CopyOfM4, col, row), m4Values[row * 4 + col]);
}
}
return failed;
}
static bool testMatrixIdentity() {
bool failed = false;
rsDebug("Testing MatrixIdentity", 0);
rs_matrix2x2 m2;
rs_matrix3x3 m3;
rs_matrix4x4 m4;
rsMatrixLoadIdentity(&m2);
rsMatrixLoadIdentity(&m3);
rsMatrixLoadIdentity(&m4);
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2, col, row), row == col ? 1.f : 0.f);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3, col, row), row == col ? 1.f : 0.f);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), row == col ? 1.f : 0.f);
}
}
return failed;
}
static bool testMatrixVectorMultiply() {
bool failed = false;
rsDebug("Testing MatrixVectorMultiply", 0);
rs_matrix2x2 m2;
rs_matrix3x3 m3;
rs_matrix4x4 m4;
const float m2Values[] = { 11.f, 12.f,
21.f, 22.f };
const float m3Values[] = { 11.f, 12.f, 13.f,
21.f, 22.f, 23.f,
31.f, 32.f, 33.f };
const float m4Values[] = { 11.f, 12.f, 13.f, 14.f,
21.f, 22.f, 23.f, 24.f,
31.f, 32.f, 33.f, 34.f,
41.f, 42.f, 43.f, 44.f };
loadByRow2(&m2, m2Values);
loadByRow3(&m3, m3Values);
loadByRow4(&m4, m4Values);
const float2 f2 = {-2.f, 3.f};
const float3 f3 = {-7.f, 6.f, 2.f};
const float4 f4 = {4.f, -2.f, 6.f, 5.f};
const float2 f2r = rsMatrixMultiply(&m2, f2);
const float3 f3r = rsMatrixMultiply(&m3, f3);
const float4 f4r = rsMatrixMultiply(&m4, f4);
const float4 f3m4r = rsMatrixMultiply(&m4, f3);
// rsMatrixMultiply returns (matrix * vector)
const float2 f2rExpectedValues = { 14.f, 24.f };
const float3 f3rExpectedValues = { 21.f, 31.f, 41.f };
const float4 f4rExpectedValues = {168.f, 298.f, 428.f, 558.f};
const float4 f3m4rExpectedValues = {35.0, 55.0, 75.0, 95.0};
for (int row = 0; row < 2; row++) {
EXPECT(row, 0, f2r[row], f2rExpectedValues[row]);
}
for (int row = 0; row < 3; row++) {
EXPECT(row, 0, f3r[row], f3rExpectedValues[row]);
}
for (int row = 0; row < 4; row++) {
EXPECT(row, 0, f4r[row], f4rExpectedValues[row]);
}
for (int row = 0; row < 4; row++) {
EXPECT(row, 0, f3m4r[row], f3m4rExpectedValues[row]);
}
return failed;
}
static bool testMatrixMultiply() {
bool failed = false;
rsDebug("Testing MatrixMultiply", 0);
const float m2LeftValues[] = { 3.f, -2.f,
7.f, 4.f };
const float m2RightValues[] = { -2.f, 3.f,
-7.f, 6.f };
const float m2LeftTimesRightValues[] = { 8.f, -3.f,
-42.f, 45.f };
const float m3LeftValues[] = { 3.f, -2.f, 8.f,
7.f, 4.f, -9.f,
5.f, -3.f, 6.f };
const float m3RightValues[] = { -2.f, 3.f, 7.f,
-7.f, 6.f, -5.f,
4.f, -4.f, 2.f };
const float m3LeftTimesRightValues[] = { 40.f, -35.f, 47.f,
-78.f, 81.f, 11.f,
35.f, -27.f, 62.f };
const float m4LeftValues[] = { 3.f, -2.f, 8.f, -5.f,
7.f, 4.f, -9.f, -4.f,
5.f, -3.f, 6.f, -6.f,
2.f, -8.f, 1.f, -1.f };
const float m4RightValues[] = { -2.f, 3.f, 7.f, -6.f,
-7.f, 6.f, -5.f, 7.f,
4.f, -4.f, 2.f, 5.f,
-1.f, 1.f, 8.f, -8.f };
const float m4LeftTimesRightValues[] = { 45.f, -40.f, 7.f, 48.f,
-74.f, 77.f, -21.f, -27.f,
41.f, -33.f, 14.f, 27.f,
57.f, -47.f, 48.f, -55.f };
rs_matrix2x2 m2, m2l, m2r;
rs_matrix3x3 m3, m3l, m3r;
rs_matrix4x4 m4, m4l, m4r;
loadByRow2(&m2l, m2LeftValues);
loadByRow2(&m2r, m2RightValues);
loadByRow3(&m3l, m3LeftValues);
loadByRow3(&m3r, m3RightValues);
loadByRow4(&m4l, m4LeftValues);
loadByRow4(&m4r, m4RightValues);
// Test the two versions of multiply.
rsMatrixLoadMultiply (&m2, &m2l, &m2r);
rsMatrixLoadMultiply (&m3, &m3l, &m3r);
rsMatrixLoadMultiply (&m4, &m4l, &m4r);
rsMatrixMultiply (&m2l, &m2r);
rsMatrixMultiply (&m3l, &m3r);
rsMatrixMultiply (&m4l, &m4r);
// rsMatrixMultiply returns (left * right).
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2, col, row), m2LeftTimesRightValues[row * 2 + col]);
EXPECT(row, col, rsMatrixGet(&m2l, col, row), m2LeftTimesRightValues[row * 2 + col]);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3, col, row), m3LeftTimesRightValues[row * 3 + col]);
EXPECT(row, col, rsMatrixGet(&m3l, col, row), m3LeftTimesRightValues[row * 3 + col]);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), m4LeftTimesRightValues[row * 4 + col]);
EXPECT(row, col, rsMatrixGet(&m4l, col, row), m4LeftTimesRightValues[row * 4 + col]);
}
}
// Verify that rsLoadMultiply can store the result in its inputs.
loadByRow2(&m2l, m2LeftValues);
loadByRow2(&m2r, m2RightValues);
loadByRow3(&m3l, m3LeftValues);
loadByRow3(&m3r, m3RightValues);
loadByRow4(&m4l, m4LeftValues);
loadByRow4(&m4r, m4RightValues);
rsMatrixLoadMultiply (&m2r, &m2l, &m2r);
rsMatrixLoadMultiply (&m3r, &m3l, &m3r);
rsMatrixLoadMultiply (&m4r, &m4l, &m4r);
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2r, col, row), m2LeftTimesRightValues[row * 2 + col]);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3r, col, row), m3LeftTimesRightValues[row * 3 + col]);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4r, col, row), m4LeftTimesRightValues[row * 4 + col]);
}
}
loadByRow2(&m2l, m2LeftValues);
loadByRow2(&m2r, m2RightValues);
loadByRow3(&m3l, m3LeftValues);
loadByRow3(&m3r, m3RightValues);
loadByRow4(&m4l, m4LeftValues);
loadByRow4(&m4r, m4RightValues);
rsMatrixLoadMultiply (&m2l, &m2l, &m2r);
rsMatrixLoadMultiply (&m3l, &m3l, &m3r);
rsMatrixLoadMultiply (&m4l, &m4l, &m4r);
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2l, col, row), m2LeftTimesRightValues[row * 2 + col]);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3l, col, row), m3LeftTimesRightValues[row * 3 + col]);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4l, col, row), m4LeftTimesRightValues[row * 4 + col]);
}
}
return failed;
}
static bool testMatrixTranspose() {
bool failed = false;
rsDebug("Testing MatrixTranspose", 0);
rs_matrix2x2 m2;
rs_matrix3x3 m3;
rs_matrix4x4 m4;
const float m2Values[] = { 11.f, 12.f,
21.f, 22.f };
const float m3Values[] = { 11.f, 12.f, 13.f,
21.f, 22.f, 23.f,
31.f, 32.f, 33.f };
const float m4Values[] = { 11.f, 12.f, 13.f, 14.f,
21.f, 22.f, 23.f, 24.f,
31.f, 32.f, 33.f, 34.f,
41.f, 42.f, 43.f, 44.f };
loadByRow2(&m2, m2Values);
loadByRow3(&m3, m3Values);
loadByRow4(&m4, m4Values);
rsMatrixTranspose(&m4);
rsMatrixTranspose(&m3);
rsMatrixTranspose(&m2);
const float m2ExpectedValues[] = { 11.f, 21.f,
12.f, 22.f };
const float m3ExpectedValues[] = { 11.f, 21.f, 31.f,
12.f, 22.f, 32.f,
13.f, 23.f, 33.f };
const float m4ExpectedValues[] = { 11.f, 21.f, 31.f, 41.f,
12.f, 22.f, 32.f, 42.f,
13.f, 23.f, 33.f, 43.f,
14.f, 24.f, 34.f, 44.f };
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
EXPECT(row, col, rsMatrixGet(&m2, col, row), m2ExpectedValues[row * 2 + col]);
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
EXPECT(row, col, rsMatrixGet(&m3, col, row), m3ExpectedValues[row * 3 + col]);
}
}
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), m4ExpectedValues[row * 4 + col]);
}
}
return failed;
}
static bool testMatrixInverse() {
bool failed = false;
rsDebug("Testing MatrixInverse", 0);
rs_matrix4x4 m4;
const float m4Values[] = { 3.f, -2.f, 8.f, -5.f,
7.f, 4.f, -9.f, -4.f,
5.f, -3.f, 6.f, -6.f,
2.f, -8.f, 1.f, -1.f };
const float m4Inverse[] = { -585.f / 16.f, -135.f / 16.f, 602.f / 16.f, -147.f / 16.f,
-91.f / 16.f, -21.f / 16.f, 94.f / 16.f, -25.f / 16.f,
-207.f / 16.f, -49.f / 16.f, 214.f / 16.f, -53.f / 16.f,
-649.f / 16.f, -151.f / 16.f, 666.f / 16.f, -163.f / 16.f };
const float m4InverseTranspose[] = { -585.f / 16.f, -91.f / 16.f, -207.f / 16.f, -649.f / 16.f,
-135.f / 16.f, -21.f / 16.f, -49.f / 16.f, -151.f / 16.f,
602.f / 16.f, 94.f / 16.f, 214.f / 16.f, 666.f / 16.f,
-147.f / 16.f, -25.f / 16.f, -53.f / 16.f, -163.f / 16.f };
loadByRow4(&m4, m4Values);
rsMatrixInverse(&m4);
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), m4Inverse[row * 4 + col]);
}
}
loadByRow4(&m4, m4Values);
rsMatrixInverseTranspose(&m4);
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), m4InverseTranspose[row * 4 + col]);
}
}
return failed;
}
static bool testMatrixScale() {
bool failed = false;
rsDebug("Testing MatrixScale", 0);
// Build a scaling matrix
const float x = 2.0f;
const float y = 0.5f;
const float z = 3.0f;
rs_matrix4x4 m4ScalingMatrix;
rsMatrixLoadScale(&m4ScalingMatrix, x, y, z);
const float m4ScalingMatrixExpectedValues[] = { x, 0.f, 0.f, 0.f,
0.f, y, 0.f, 0.f,
0.f, 0.f, z, 0.f,
0.f, 0.f, 0.f, 1.f };
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4ScalingMatrix, col, row),
m4ScalingMatrixExpectedValues[row * 4 + col]);
}
}
// Check that the scaling does what we expect.
const float3 f3 = {4.f, -2.f, 6.f};
const float4 f4r = rsMatrixMultiply(&m4ScalingMatrix, f3);
const float4 f4rExpectedValues = {4.f * x, -2.f * y, 6.f * z, 1.f};
for (int row = 0; row < 4; row++) {
EXPECT(row, 0, f4r[row], f4rExpectedValues[row]);
}
// Test combining scale with another transformation.
rs_matrix4x4 m4;
const float m4Values[] = { 11.f, 12.f, 13.f, 14.f,
21.f, 22.f, 23.f, 24.f,
31.f, 32.f, 33.f, 34.f,
41.f, 42.f, 43.f, 44.f };
loadByRow4(&m4, m4Values);
// Method 1:
rs_matrix4x4 m4s;
rsMatrixLoadMultiply(&m4s, &m4, &m4ScalingMatrix);
// Method 2:
rsMatrixScale(&m4, x, y, z);
// Verify the results
/* We are creating a matrix that scales first then does the remaining operation.
* It's a bit inverse of what I would have expected.
*/
const float expectedValues[] = { x * 11.f, y * 12.f, z * 13.f, 14.f,
x * 21.f, y * 22.f, z * 23.f, 24.f,
x * 31.f, y * 32.f, z * 33.f, 34.f,
x * 41.f, y * 42.f, z * 43.f, 44.f };
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), expectedValues[row * 4 + col]);
EXPECT(row, col, rsMatrixGet(&m4s, col, row), expectedValues[row * 4 + col]);
}
}
return failed;
}
static bool testMatrixTranslate() {
bool failed = false;
rsDebug("Testing MatrixTranslate", 0);
// Build a translating matrix
rs_matrix4x4 m4TranslatingMatrix;
const float x = 2.0f;
const float y = 0.5f;
const float z = 3.0f;
rsMatrixLoadTranslate(&m4TranslatingMatrix, x, y, z);
const float m4TranslatingMatrixExpectedValues[] = { 1.f, 0.f, 0.f, x,
0.f, 1.f, 0.f, y,
0.f, 0.f, 1.f, z,
0.f, 0.f, 0.f, 1.f };
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4TranslatingMatrix, col, row),
m4TranslatingMatrixExpectedValues[row * 4 + col]);
}
}
// Check that the translation does what we expect.
const float3 f3 = {4.f, -2.f, 6.f};
const float4 f4r = rsMatrixMultiply(&m4TranslatingMatrix, f3);
const float4 f4rExpectedValues = {4.f + x, -2.f + y, 6.f + z, 1.f};
for (int row = 0; row < 4; row++) {
EXPECT(row, 0, f4r[row], f4rExpectedValues[row]);
}
// Test combining translate with another transformation.
rs_matrix4x4 m4;
const float m4Values[] = { 11.f, 12.f, 13.f, 14.f,
21.f, 22.f, 23.f, 24.f,
31.f, 32.f, 33.f, 34.f,
41.f, 42.f, 43.f, 44.f };
loadByRow4(&m4, m4Values);
// Method 1:
rs_matrix4x4 m4s;
rsMatrixLoadMultiply(&m4s, &m4, &m4TranslatingMatrix);
// Method 2:
rsMatrixTranslate(&m4, x, y, z);
// Verify the results
/* We are creating a matrix that translates first then does the remaining operation.
* It's a bit inverse of what I would have expected.
*/
float m4ExpectedValues[] = { 11.f, 12.f, 13.f, x * 11.f + y * 12.f + z * 13.f + 14.f,
21.f, 22.f, 23.f, x * 21.f + y * 22.f + z * 23.f + 24.f,
31.f, 32.f, 33.f, x * 31.f + y * 32.f + z * 33.f + 34.f,
41.f, 42.f, 43.f, x * 41.f + y * 42.f + z * 43.f + 44.f };
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), m4ExpectedValues[row * 4 + col]);
EXPECT(row, col, rsMatrixGet(&m4s, col, row), m4ExpectedValues[row * 4 + col]);
}
}
return failed;
}
static bool testMatrixTranslateAndScale() {
bool failed = false;
rsDebug("Testing MatrixTranslateAndScale", 0);
/* Create a matrix that will scale by (4, -2, 3) then translate by (2, 3, -1).
* Applied to the vector (5, 6, 7), we should get:
* step 1: (20, -12, 21)
* step 2: (22, -9, 20)
*/
const float xs = 4.f;
const float ys = -2.f;
const float zs = 3.f;
const float xt = 2.f;
const float yt = 3.f;
const float zt = -1.f;
rs_matrix4x4 m4;
// Start by the second operation (yes, it's counter intuitive)
rsMatrixLoadTranslate(&m4, xt, yt, zt);
rsMatrixScale(&m4, xs, ys, zs);
// Check that the transformation does what we expect.
const float3 f3 = {5.f, 6.f, 7.f};
const float4 f4 = rsMatrixMultiply(&m4, f3);
const float4 f4ExpectedValues = {22.f, -9.f, 20.f, 1.f};
for (int row = 0; row < 4; row++) {
EXPECT(row, 0, f4[row], f4ExpectedValues[row]);
}
return failed;
}
static bool testMatrixRotate() {
bool failed = false;
rsDebug("Testing MatrixRotate", 0);
// Build a rotating matrix.
rs_matrix4x4 m4RotatingMatrix;
const float rotation = 90.f; // Rotation is in degrees.
const float x = 1.f;
const float y = 4.f;
const float z = 8.f;
rsMatrixLoadRotate(&m4RotatingMatrix, rotation, x, y, z);
/* See http://en.wikipedia.org/wiki/Rotation_matrix
*/
const float inRadians = radians(rotation); // rotation * float(M_PI) / 180.0f;
const float co = cos(inRadians);
const float si = sin(inRadians);
const float ux = 1.f / 9.f;
const float uy = 4.f / 9.f;
const float uz = 8.f / 9.f;
const float m4RotatingMatrixExpectedValues[] = {
co + ux * ux * (1.f - co), ux * uy * (1.f - co) - uz * si, ux * uz * (1.f - co) + uy * si, 0.f,
uy * ux * (1.f - co) + uz * si, co + uy * uy * (1.f - co), uy * uz * (1.f - co) - ux * si, 0.f,
uz * ux * (1.f - co) - uy * si, uz * uy * (1.f - co) + ux * si, co + uz * uz * (1.f - co), 0.f,
0.f, 0.f, 0.f, 1.f
};
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4RotatingMatrix, col, row), m4RotatingMatrixExpectedValues[row * 4 + col]);
}
}
// Test combining rotate with another transformation.
rs_matrix4x4 m4;
const float m4Values[] = { 11.f, 12.f, 13.f, 14.f,
21.f, 22.f, 23.f, 24.f,
31.f, 32.f, 33.f, 34.f,
41.f, 42.f, 43.f, 44.f };
loadByRow4(&m4, m4Values);
// Method 1:
rs_matrix4x4 m4s;
rsMatrixLoadMultiply(&m4s, &m4, &m4RotatingMatrix);
// Method 2:
rsMatrixRotate(&m4, rotation, x, y, z);
// Verify the results
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), rsMatrixGet(&m4s, col, row));
}
}
// Verify that rsMatrixRotate does what we expect on something simple to understand.
rsMatrixLoadRotate(&m4RotatingMatrix, 60.f, 1.f, 0.f, 1.f);
const float3 f3 = {0.f, 1.f, 0.f};
const float4 f4r = rsMatrixMultiply(&m4RotatingMatrix, f3);
const float k = sqrt(3.f) * sqrt(2.f) / 4.f;
const float4 f4rExpectedValues = {-k, 0.5f, k, 1.f};
for (int row = 0; row < 4; row++) {
EXPECT(row, 0, f4r[row], f4rExpectedValues[row]);
}
return failed;
}
static bool testMatrixProjections() {
bool failed = false;
rsDebug("Testing MatrixProjections", 0);
rs_matrix4x4 m4;
const float left = -2.f;
const float right = 4.f;
const float bottom = -3.f;
const float top = 5.f;
const float near = 2.f;
const float far = 7.f;
const float fovy = 60.f;
const float aspect = 2.f;
/* Orthographic projection that remaps to the unit cube. See
* http://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/
*/
rsMatrixLoadOrtho(&m4, left, right, bottom, top, near, far);
const float orthoExpectedValues[] = {
2.f / (right - left), 0.f, 0.f, -(right + left) / (right - left),
0.f, 2.f / (top - bottom), 0.f, -(top + bottom) / (top - bottom),
0.f, 0.f, -2.f / (far - near), -(far + near) / (far - near),
0.f, 0.f, 0.f, 1.f,
};
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), orthoExpectedValues[row * 4 + col]);
}
}
/* Frustum projection. See
* http://www.scratchapixel.com/lessons/3d-advanced-lessons/perspective-and-orthographic-projection-matrix/opengl-perspective-projection-matrix/
*/
rsMatrixLoadFrustum(&m4, left, right, bottom, top, near, far);
const float frustumExpectedValues[] = {
2.f * near / (right - left), 0.f, (right + left) / (right - left), 0.f,
0.f, 2.f * near / (top - bottom), (top + bottom) / (top - bottom), 0.f,
0.f, 0.f, -(far + near) / (far - near), -2.f * far * near / (far - near),
0.f, 0.f, -1.f, 0.f,
};
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), frustumExpectedValues[row * 4 + col]);
}
}
/* Perspective projection. See
* http://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/
*/
rsMatrixLoadPerspective(&m4, fovy, aspect, near, far);
const float invTan = 1.f / tan(radians(fovy / 2.f));
const float perspectiveExpectedValues[] = {
invTan / aspect, 0.f, 0.f, 0.f,
0.f, invTan, 0.f, 0.f,
0.f, 0.f, (near + far) / (near - far), 2.f * far * near / (near - far),
0.f, 0.f, -1.f, 0.f,
};
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
EXPECT(row, col, rsMatrixGet(&m4, col, row), perspectiveExpectedValues[row * 4 + col]);
}
}
return failed;
}
void matrixTests() {
bool failed = false;
failed |= testMatrixSetAndGet();
failed |= testMatrixLoadFromArray();
failed |= testMatrixLoadFromMatrix();
failed |= testMatrixIdentity();
failed |= testMatrixVectorMultiply();
failed |= testMatrixMultiply();
failed |= testMatrixTranspose();
failed |= testMatrixInverse();
failed |= testMatrixScale();
failed |= testMatrixTranslate();
failed |= testMatrixTranslateAndScale();
failed |= testMatrixRotate();
failed |= testMatrixProjections();
if (failed) {
rsSendToClientBlocking(RS_MSG_TEST_FAILED);
} else {
rsSendToClientBlocking(RS_MSG_TEST_PASSED);
}
}