blob: 10a13e6c0c656ff1b9bfaeea72e72fa2a8fdb76e [file] [log] [blame]
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mathfu/matrix.h"
#include "mathfu/matrix_4x4.h"
#include "mathfu/quaternion.h"
#include "mathfu/utilities.h"
#include "mathfu/vector.h"
#include <cmath>
#include <sstream>
#include <string>
#include "gtest/gtest.h"
#include "precision.h"
static const float kUnProjectFloatPrecision = 0.0012f;
static const double kLookAtDoublePrecision = 1e-8;
class MatrixTests : public ::testing::Test {
protected:
virtual void SetUp() {}
virtual void TearDown() {}
};
template <class T, int rows, int columns>
struct MatrixExpectation {
const char* description;
mathfu::Matrix<T, rows, columns> calculated;
mathfu::Matrix<T, rows, columns> expected;
};
// This will automatically generate tests for each template parameter.
#define TEST_ALL_F(MY_TEST, FLOAT_PRECISION_VALUE, DOUBLE_PRECISION_VALUE) \
TEST_F(MatrixTests, MY_TEST##_float_2) { \
MY_TEST##_Test<float, 2>(FLOAT_PRECISION_VALUE); \
} \
TEST_F(MatrixTests, MY_TEST##_double_2) { \
MY_TEST##_Test<double, 2>(DOUBLE_PRECISION_VALUE); \
} \
TEST_F(MatrixTests, MY_TEST##_float_3) { \
MY_TEST##_Test<float, 3>(FLOAT_PRECISION_VALUE); \
} \
TEST_F(MatrixTests, MY_TEST##_double_3) { \
MY_TEST##_Test<double, 3>(DOUBLE_PRECISION_VALUE); \
} \
TEST_F(MatrixTests, MY_TEST##_float_4) { \
MY_TEST##_Test<float, 4>(FLOAT_PRECISION_VALUE); \
} \
TEST_F(MatrixTests, MY_TEST##_double_4) { \
MY_TEST##_Test<double, 4>(DOUBLE_PRECISION_VALUE); \
}
// This will automatically generate tests for each scalar template parameter.
#define TEST_SCALAR_F(MY_TEST, FLOAT_PRECISION_VALUE, DOUBLE_PRECISION_VALUE) \
TEST_F(MatrixTests, MY_TEST##_float) { \
MY_TEST##_Test<float>(FLOAT_PRECISION_VALUE); \
} \
TEST_F(MatrixTests, MY_TEST##_double) { \
MY_TEST##_Test<double>(DOUBLE_PRECISION_VALUE); \
}
// This will test initialization by passing in values. The template paramter d
// corresponds to the number of rows and columns.
template <class T, int d>
void Initialize_Test(const T& precision) {
// This will test initialization of the matrix using a random single value.
// The expected result is that all entries equal the given value.
mathfu::Matrix<T, d> matrix_splat(static_cast<T>(3.1));
for (int i = 0; i < d * d; ++i) {
EXPECT_NEAR(3.1, matrix_splat[i], precision);
}
// This will verify that the value is correct when using the (i, j) form
// of indexing.
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(3.1, matrix_splat(i, j), precision);
}
}
// This will test initialization of the matrix using a c style array of
// values.
T x[d * d];
for (int i = 0; i < d * d; ++i) {
x[i] = rand() / static_cast<T>(RAND_MAX) * 100.f;
}
mathfu::Matrix<T, d> matrix_arr(x);
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(x[i + d * j], matrix_arr(i, j), precision);
}
}
// This will test the copy constructor making sure that the new matrix
// equals the old one.
mathfu::Matrix<T, d> matrix_copy(matrix_arr);
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(x[i + d * j], matrix_copy(i, j), precision);
}
}
// This will verify that the copy was deep and changing the values of the
// copied matrix does not effect the original.
matrix_copy = matrix_copy - mathfu::Matrix<T, d>(1);
EXPECT_NE(matrix_copy(0, 0), matrix_arr(0, 0));
// This will test creation of the identity matrix.
mathfu::Matrix<T, d> identity(mathfu::Matrix<T, d>::Identity());
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(i == j ? 1 : 0, identity(i, j), precision);
}
}
}
TEST_ALL_F(Initialize, FLOAT_PRECISION, DOUBLE_PRECISION);
// This will test initialization by specifying all values explicitly.
template <class T>
void InitializePerDimension_Test(const T& precision) {
mathfu::Matrix<T, 2> matrix_f2x2(static_cast<T>(4.5), static_cast<T>(3.4),
static_cast<T>(2.6), static_cast<T>(9.8));
EXPECT_NEAR(4.5, matrix_f2x2(0, 0), precision);
EXPECT_NEAR(3.4, matrix_f2x2(1, 0), precision);
EXPECT_NEAR(2.6, matrix_f2x2(0, 1), precision);
EXPECT_NEAR(9.8, matrix_f2x2(1, 1), precision);
mathfu::Matrix<T, 3> matrix_f3x3(
static_cast<T>(3.7), static_cast<T>(2.4), static_cast<T>(6.4),
static_cast<T>(1.1), static_cast<T>(5.2), static_cast<T>(6.4),
static_cast<T>(2.7), static_cast<T>(7.4), static_cast<T>(0.1));
EXPECT_NEAR(3.7, matrix_f3x3(0, 0), precision);
EXPECT_NEAR(2.4, matrix_f3x3(1, 0), precision);
EXPECT_NEAR(6.4, matrix_f3x3(2, 0), precision);
EXPECT_NEAR(1.1, matrix_f3x3(0, 1), precision);
EXPECT_NEAR(5.2, matrix_f3x3(1, 1), precision);
EXPECT_NEAR(6.4, matrix_f3x3(2, 1), precision);
EXPECT_NEAR(2.7, matrix_f3x3(0, 2), precision);
EXPECT_NEAR(7.4, matrix_f3x3(1, 2), precision);
EXPECT_NEAR(0.1, matrix_f3x3(2, 2), precision);
mathfu::Matrix<T, 4> matrix_f4x4(
static_cast<T>(4.1), static_cast<T>(8.4), static_cast<T>(7.2),
static_cast<T>(4.8), static_cast<T>(0.9), static_cast<T>(7.8),
static_cast<T>(5.6), static_cast<T>(8.7), static_cast<T>(2.3),
static_cast<T>(4.2), static_cast<T>(6.1), static_cast<T>(2.7),
static_cast<T>(0.1), static_cast<T>(1.4), static_cast<T>(9.4),
static_cast<T>(3.6));
EXPECT_NEAR(4.1, matrix_f4x4(0, 0), precision);
EXPECT_NEAR(8.4, matrix_f4x4(1, 0), precision);
EXPECT_NEAR(7.2, matrix_f4x4(2, 0), precision);
EXPECT_NEAR(4.8, matrix_f4x4(3, 0), precision);
EXPECT_NEAR(0.9, matrix_f4x4(0, 1), precision);
EXPECT_NEAR(7.8, matrix_f4x4(1, 1), precision);
EXPECT_NEAR(5.6, matrix_f4x4(2, 1), precision);
EXPECT_NEAR(8.7, matrix_f4x4(3, 1), precision);
EXPECT_NEAR(2.3, matrix_f4x4(0, 2), precision);
EXPECT_NEAR(4.2, matrix_f4x4(1, 2), precision);
EXPECT_NEAR(6.1, matrix_f4x4(2, 2), precision);
EXPECT_NEAR(2.7, matrix_f4x4(3, 2), precision);
EXPECT_NEAR(0.1, matrix_f4x4(0, 3), precision);
EXPECT_NEAR(1.4, matrix_f4x4(1, 3), precision);
EXPECT_NEAR(9.4, matrix_f4x4(2, 3), precision);
EXPECT_NEAR(3.6, matrix_f4x4(3, 3), precision);
}
TEST_SCALAR_F(InitializePerDimension, FLOAT_PRECISION, DOUBLE_PRECISION)
// Test initialization of a matrix from an array of packed vectors.
template <class T, int d>
void InitializePacked_Test(const T& precision) {
(void)precision;
mathfu::VectorPacked<T, d> packed[d];
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
packed[i].data[j] = static_cast<T>((i * d) + j);
}
}
mathfu::Matrix<T, d> matrix(packed);
for (int i = 0; i < d * d; ++i) {
EXPECT_NEAR(packed[i / d].data[i % d], matrix[i], static_cast<T>(0))
<< "Element " << i;
}
}
TEST_ALL_F(InitializePacked, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test serialization to a packed array of vectors.
template <class T, int d>
void PackedSerialization_Test(const T& precision) {
(void)precision;
mathfu::Matrix<T, d> matrix;
for (int i = 0; i < d * d; ++i) {
matrix[i] = static_cast<T>(i);
}
mathfu::VectorPacked<T, d> packed[d];
matrix.Pack(packed);
for (int i = 0; i < d * d; ++i) {
EXPECT_NEAR(matrix[i], packed[i / d].data[i % d], static_cast<T>(0))
<< "Element " << i;
}
}
TEST_ALL_F(PackedSerialization, FLOAT_PRECISION, DOUBLE_PRECISION);
// This will test the Addition and Subtraction of matrices. The template
// parameter d corresponds to the number of rows and columns.
template <class T, int d>
void AddSub_Test(const T& precision) {
T x1[d * d], x2[d * d];
for (int i = 0; i < d * d; ++i) {
x1[i] = rand() / static_cast<T>(RAND_MAX) * 100.f;
}
for (int i = 0; i < d * d; ++i) {
x2[i] = rand() / static_cast<T>(RAND_MAX) * 100.f;
}
mathfu::Matrix<T, d> matrix1(x1), matrix2(x2);
// This will test the negation of a matrix and verify that each element
// is negated.
mathfu::Matrix<T, d> neg_mat1(-matrix1);
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(-x1[i + d * j], neg_mat1(i, j), precision);
}
}
// This will test the addition of two matrices and verify that each element
// equal to the sum of the input values.
mathfu::Matrix<T, d> matrix_add(matrix1 + matrix2);
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(x1[i + d * j] + x2[i + d * j], matrix_add(i, j), precision);
}
}
// This will test the subtraction of two matrices and verify that each
// element equal to the difference of the input values.
mathfu::Matrix<T, d> matrix_sub(matrix1 - matrix2);
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(x1[i + d * j] - x2[i + d * j], matrix_sub(i, j), precision);
}
}
}
TEST_ALL_F(AddSub, FLOAT_PRECISION, DOUBLE_PRECISION);
// This will test the multiplication of matrices by matrices, vectors,
// and scalars. The template parameter d corresponds to the number of rows and
// columns.
template <class T, int d>
void Mult_Test(const T& precision) {
T x1[d * d], x2[d * d];
for (int i = 0; i < d * d; ++i) x1[i] = rand() / static_cast<T>(RAND_MAX);
for (int i = 0; i < d * d; ++i) x2[i] = rand() / static_cast<T>(RAND_MAX);
mathfu::Matrix<T, d> matrix1(x1), matrix2(x2);
// This will test scalar matrix multiplication and verify that each element
// is equal to multiplication by the scalar.
mathfu::Matrix<T, d> matrix_mults(matrix1 * static_cast<T>(1.1));
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(x1[i + d * j] * 1.1, matrix_mults(i, j), precision);
}
}
T v[d];
for (int i = 0; i < d; ++i) v[i] = rand() / static_cast<T>(RAND_MAX);
mathfu::Vector<T, d> vector(v);
// This will test matrix vector multiplication and verify that the resulting
// vector is mathematically correct.
mathfu::Vector<T, d> vector_multv(matrix1 * vector);
for (int i = 0; i < d; ++i) {
T v1[d];
for (int k = 0; k < d; ++k) v1[k] = matrix1(i, k);
mathfu::Vector<T, d> vec1(v1);
T dot = mathfu::Vector<T, d>::DotProduct(vec1, vector);
EXPECT_NEAR(dot, vector_multv[i], precision);
}
// This will test matrix multiplication and verify that the resulting
// matrix is mathematically correct.
mathfu::Matrix<T, d> matrix_multm(matrix1 * matrix2);
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
T v1[d], v2[d];
for (int k = 0; k < d; ++k) v1[k] = matrix1(i, k);
for (int k = 0; k < d; ++k) v2[k] = matrix2(k, j);
mathfu::Vector<T, d> vec1(v1), vec2(v2);
T dot = mathfu::Vector<T, d>::DotProduct(vec1, vec2);
EXPECT_NEAR(dot, matrix_multm(i, j), precision);
}
}
}
TEST_ALL_F(Mult, FLOAT_PRECISION, DOUBLE_PRECISION);
// This will test the outer product of two vectors. The template parameter d
// corresponds to the number of rows and columns.
template <class T, int d>
void OuterProduct_Test(const T& precision) {
T x1[d], x2[d];
for (int i = 0; i < d; ++i) x1[i] = rand() / static_cast<T>(RAND_MAX);
for (int i = 0; i < d; ++i) x2[i] = rand() / static_cast<T>(RAND_MAX);
mathfu::Vector<T, d> vector1(x1), vector2(x2);
mathfu::Matrix<T, d> matrix(
mathfu::Matrix<T, d>::OuterProduct(vector1, vector2));
// This will verify that each element is mathematically correct.
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(vector1[i] * vector2[j], matrix(i, j), precision);
}
}
}
TEST_ALL_F(OuterProduct, FLOAT_PRECISION, DOUBLE_PRECISION);
// Print the specified matrix to output_string in the form.
template <class T, int rows, int columns>
std::string MatrixToString(const mathfu::Matrix<T, rows, columns>& matrix) {
std::stringstream ss;
ss.flags(std::ios::fixed);
ss.precision(4);
for (int col = 0; col < columns; ++col) {
for (int row = 0; row < rows; ++row) {
ss << (T)matrix(col, row) << " ";
}
ss << "\n";
}
return ss.str();
}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4127) // conditional expression is constant
#endif // _MSC_VER
// Test the inverse of a set of noninvertible matrices.
template <class T, int d>
void InverseNonInvertible_Test(const T& precision) {
(void)precision;
T m[d * d];
const size_t matrix_size = sizeof(m) / sizeof(m[0]);
static const T kDeterminantThreshold =
mathfu::Constants<T>::GetDeterminantThreshold();
static const T kDeterminantThresholdInverse = 1 / kDeterminantThreshold;
static const T kDeterminantThresholdSmall =
kDeterminantThresholdInverse / 100;
static const T kDeterminantThresholdInverseLarge =
kDeterminantThresholdInverse * 100;
// Create a matrix with all zeros.
for (size_t i = 0; i < matrix_size; ++i) m[i] = 0;
// Verify that it's not possible to invert the matrix.
{
mathfu::Matrix<T, d> matrix(m);
mathfu::Matrix<T, d> inverse_matrix;
EXPECT_FALSE(matrix.InverseWithDeterminantCheck(&inverse_matrix));
}
// Check a matrix with all elements at the determinant threshold.
for (size_t i = 0; i < matrix_size; ++i) m[i] = kDeterminantThreshold;
{
mathfu::Matrix<T, d> matrix(m);
mathfu::Matrix<T, d> inverse_matrix;
EXPECT_FALSE(matrix.InverseWithDeterminantCheck(&inverse_matrix));
}
// Check a matrix with all very large elements.
for (size_t i = 0; i < matrix_size - 1; ++i) {
m[i] = kDeterminantThresholdInverse;
}
m[matrix_size - 1] = kDeterminantThresholdInverseLarge;
// NOTE: Due to precision, this case will pass the determinant check with a
// 2x2 matrix since the determinant of the matrix will be calculated as 1.
if (d != 2) {
// Create a matrix with all elements at the determinant threshold and one
// large value in the matrix.
for (size_t i = 0; i < matrix_size - 1; ++i) {
m[i] = kDeterminantThresholdSmall;
}
m[matrix_size - 1] = kDeterminantThresholdInverseLarge;
{
mathfu::Matrix<T, d> matrix(m);
mathfu::Matrix<T, d> inverse_matrix;
EXPECT_FALSE(matrix.InverseWithDeterminantCheck(&inverse_matrix));
}
}
}
TEST_ALL_F(InverseNonInvertible, FLOAT_PRECISION, DOUBLE_PRECISION);
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
// This will test calculating the inverse of a matrix. The template parameter d
// corresponds to the number of rows and columns.
template <class T, int d>
void Inverse_Test(const T& precision) {
T x[d * d];
for (int iterations = 0; iterations < 1000; ++iterations) {
// NOTE: This assumes that matrices generated here are invertible since
// there is a tiny probability that a randomly generated matrix will be
// noninvertible. This does mean that this test can be flakey by
// occasionally generating noninvertible matrices.
for (int i = 0; i < mathfu::Matrix<T, d>::kElements; ++i) {
x[i] = mathfu::RandomRange<T>(1);
}
mathfu::Matrix<T, d> matrix(x);
std::string error_string = MatrixToString(matrix);
error_string += "\n";
mathfu::Matrix<T, d> inverse_matrix(matrix.Inverse());
mathfu::Matrix<T, d> identity_matrix(matrix * inverse_matrix);
error_string += MatrixToString(inverse_matrix);
error_string += "\n";
error_string += MatrixToString(identity_matrix);
// This will verify that M * Minv is equal to the identity.
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(i == j ? 1 : 0, identity_matrix(i, j), 100 * precision)
<< error_string << " row=" << i << " column=" << j;
}
}
}
}
// Due to the number of operations involved and the random numbers used to
// generate the test matrices, the precision the inverse matrix is calculated
// to is relatively low.
TEST_ALL_F(Inverse, 1e-4f, 1e-8);
// This will test converting from a translation into a matrix and back again.
template <class T>
void TranslationVector3D_Test(const T& precision) {
(void)precision;
const mathfu::Vector<T, 3> trans(static_cast<T>(-100.0), static_cast<T>(0.0),
static_cast<T>(0.00003));
const mathfu::Matrix<T, 4> trans_matrix =
mathfu::Matrix<T, 4>::FromTranslationVector(trans);
const mathfu::Vector<T, 3> trans_back = trans_matrix.TranslationVector3D();
// This will verify that the translation vector has not changed.
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(trans[i], trans_back[i]);
}
}
TEST_SCALAR_F(TranslationVector3D, FLOAT_PRECISION, DOUBLE_PRECISION);
// This will test converting from a translation into a matrix and back again.
template <class T>
void TranslationVector2D_Test(const T& precision) {
(void)precision;
const mathfu::Vector<T, 2> trans(static_cast<T>(-100.0),
static_cast<T>(0.00003));
const mathfu::Matrix<T, 3> trans_matrix =
mathfu::Matrix<T, 3>::FromTranslationVector(trans);
const mathfu::Vector<T, 2> trans_back = trans_matrix.TranslationVector2D();
// This will verify that the translation vector has not changed.
for (int i = 0; i < 2; ++i) {
EXPECT_EQ(trans[i], trans_back[i]);
}
}
TEST_SCALAR_F(TranslationVector2D, FLOAT_PRECISION, DOUBLE_PRECISION);
// This will test converting from a scale into a matrix, then multiply by
// a vector of 1's, which should produce the original scale again.
template <class T, int d>
void FromScaleVector_Test(const T& precision) {
mathfu::Vector<T, d> ones(static_cast<T>(1));
mathfu::Vector<T, d - 1> v;
// Tests that the scale vector is placed in the correct order in the matrix.
for (int i = 0; i < d - 1; ++i) {
v[i] = static_cast<T>(i + 10);
}
mathfu::Matrix<T, d> m = mathfu::Matrix<T, d>::FromScaleVector(v);
// Ensure that the v is on the diagonal.
for (int i = 0; i < d - 1; ++i) {
EXPECT_NEAR(v[i], m(i, i), precision);
}
// Ensure that the last diagonal element is one.
EXPECT_NEAR(m(d - 1, d - 1), 1, precision);
// Ensure that all non-diagonal elements are zero.
for (int i = 0; i < d - 1; ++i) {
for (int j = 0; j < d - 1; ++j) {
if (i == j) continue;
EXPECT_NEAR(m(i, j), 0, precision);
}
}
}
// Precision is zero. Results must be perfect for this test.
TEST_ALL_F(FromScaleVector, 0.0f, 0.0);
// Compare a set of Matrix<T, rows, columns> with expected values.
template <class T, int rows, int columns>
void VerifyMatrixExpectations(
const MatrixExpectation<T, rows, columns>* test_cases,
const size_t number_of_test_cases, const T& precision) {
for (size_t i = 0; i < number_of_test_cases; ++i) {
const MatrixExpectation<T, rows, columns>& test = test_cases[i];
for (int j = 0; j < mathfu::Matrix<T, rows, columns>::kElements; ++j) {
const mathfu::Matrix<T, rows, columns>& calculated = test.calculated;
const mathfu::Matrix<T, rows, columns>& expected = test.expected;
EXPECT_NEAR(calculated[j], expected[j], precision)
<< "element " << j << " (" << (j / columns) << ", " << (j % columns)
<< "), case " << test.description << "\n"
<< MatrixToString(calculated) << "vs expected\n"
<< MatrixToString(expected);
}
}
}
// Test perspective matrix calculation.
template <class T>
void Perspective_Test(const T& precision) {
// clang-format off
static const MatrixExpectation<T, 4, 4> kTestCases[] = {
{
"normalized handedness=1",
mathfu::Matrix<T, 4>::Perspective(
atan(static_cast<T>(1)) * 2, 1, 0, 1, 1),
mathfu::Matrix<T, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, -1, -1,
0, 0, 0, 0),
},
{
"normalized handedness=-1",
mathfu::Matrix<T, 4>::Perspective(
atan(static_cast<T>(1)) * 2, 1, 0, 1, -1),
mathfu::Matrix<T, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 0),
},
{
"widefov",
mathfu::Matrix<T, 4>::Perspective(
atan(static_cast<T>(2)) * 2, 1, 0, 1, 1),
mathfu::Matrix<T, 4>(0.5, 0, 0, 0,
0, 0.5, 0, 0,
0, 0, -1, -1,
0, 0, 0, 0),
},
{
"narrowfov",
mathfu::Matrix<T, 4>::Perspective(
atan(static_cast<T>(0.1)) * 2, 1, 0, 1, 1),
mathfu::Matrix<T, 4>(10, 0, 0, 0,
0, 10, 0, 0,
0, 0, -1, -1,
0, 0, 0, 0),
},
{
"2:1 aspect ratio",
mathfu::Matrix<T, 4>::Perspective(
atan(static_cast<T>(1)) * 2, 0.5, 0, 1, 1),
mathfu::Matrix<T, 4>(2, 0, 0, 0,
0, 1, 0, 0,
0, 0, -1, -1,
0, 0, 0, 0),
},
{
"deeper view frustrum",
mathfu::Matrix<T, 4>::Perspective(
atan(static_cast<T>(1)) * 2, 1, -2, 2, 1),
mathfu::Matrix<T, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, -0.5, -1,
0, 0, 2, 0),
},
};
// clang-format on
VerifyMatrixExpectations(
kTestCases, sizeof(kTestCases) / sizeof(kTestCases[0]), precision);
}
TEST_SCALAR_F(Perspective, FLOAT_PRECISION, DOUBLE_PRECISION * 10);
// Test orthographic matrix calculation.
template <class T>
void Ortho_Test(const T& precision) {
// clang-format off
static const MatrixExpectation<T, 4, 4> kTestCases[] = {
{
"normalized",
mathfu::Matrix<T, 4, 4>::Ortho(0, 2, 0, 2, 2, 0),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, -1, 1, 1),
},
{
"normalized RH",
mathfu::Matrix<T, 4, 4>::Ortho(0, 2, 0, 2, 2, 0, 1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, -1, 1, 1),
},
{
"narrow RH",
mathfu::Matrix<T, 4, 4>::Ortho(1, 3, 0, 2, 2, 0, 1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-2, -1, 1, 1),
},
{
"squat RH",
mathfu::Matrix<T, 4, 4>::Ortho(0, 2, 1, 3, 2, 0, 1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, -2, 1, 1),
},
{
"deep RH",
mathfu::Matrix<T, 4, 4>::Ortho(0, 2, 0, 2, 3, 1, 1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, -1, 2, 1),
},
{
"normalized LH",
mathfu::Matrix<T, 4, 4>::Ortho(0, 2, 0, 2, 2, 0, -1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, -1, 0,
-1, -1, 1, 1),
},
{
"Canonical LH",
mathfu::Matrix<T, 4, 4>::Ortho(1, 3, 1, 3, 1, 3, -1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-2,-2,-2, 1),
},
};
// clang-format on
VerifyMatrixExpectations(
kTestCases, sizeof(kTestCases) / sizeof(kTestCases[0]), precision);
}
TEST_SCALAR_F(Ortho, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test look-at matrix calculation.
template <class T>
void LookAt_Test(const T& precision) {
// clang-format off
static const MatrixExpectation<T, 4, 4> kTestCases[] = {
{
"origin along z",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(0, 0, 1), mathfu::Vector<T, 3>(0, 0, 0),
mathfu::Vector<T, 3>(0, 1, 0)),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1),
},
{
"origin along diagonal",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(0, 0, 0), mathfu::Vector<T, 3>(1, 1, 1),
mathfu::Vector<T, 3>(0, 1, 0), -1),
mathfu::Matrix<T, 4, 4>(
static_cast<T>(-0.707106781), static_cast<T>(-0.408248290),
static_cast<T>(-0.577350269), 0,
0, static_cast<T>(0.816496580), static_cast<T>(-0.577350269), 0,
static_cast<T>(0.707106781), static_cast<T>(-0.408248290),
static_cast<T>(-0.577350269), 0,
0, 0, static_cast<T>(1.732050808), 1),
},
{
"origin along z",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(0, 0, 2), mathfu::Vector<T, 3>(0, 0, 0),
mathfu::Vector<T, 3>(0, 1, 0), -1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1),
},
{
"origin along x",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(1, 0, 0), mathfu::Vector<T, 3>(0, 0, 0),
mathfu::Vector<T, 3>(0, 1, 0), -1),
mathfu::Matrix<T, 4, 4>(0, 0, 1, 0,
0, 1, 0, 0,
-1, 0, 0, 0,
0, 0, 0, 1),
},
{
"origin along y",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(0, 1, 0), mathfu::Vector<T, 3>(0, 0, 0),
mathfu::Vector<T, 3>(1, 0, 0), -1),
mathfu::Matrix<T, 4, 4>(0, 1, 0, 0,
0, 0, 1, 0,
1, 0, 0, 0,
0, 0, 0, 1),
},
{
"translated eye, looking along z",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(1, 1, 2), mathfu::Vector<T, 3>(1, 1, 1),
mathfu::Vector<T, 3>(0, 1, 0), -1),
mathfu::Matrix<T, 4, 4>(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, -1, -1, 1),
},
{
"right-handed diagonal along diagonal",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(0, 0, 0), mathfu::Vector<T, 3>(1, 1, 1),
mathfu::Vector<T, 3>(0, 1, 0), 1),
mathfu::Matrix<T, 4, 4>(
static_cast<T>(0.707106781), static_cast<T>(-0.408248290),
static_cast<T>(0.577350269), 0,
0, static_cast<T>(0.816496581), static_cast<T>(0.577350269), 0,
static_cast<T>(-0.707106781), static_cast<T>(-0.408248290),
static_cast<T>(0.577350269), 0,
0, 0, static_cast<T>(-1.732050808), 1),
},
{
"right-handed origin along z",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(0, 0, 1), mathfu::Vector<T, 3>(0, 0, 0),
mathfu::Vector<T, 3>(0, 1, 0), 1),
mathfu::Matrix<T, 4, 4>(-1, 0, 0, 0,
0, 1, 0, 0,
0, 0, -1,0,
0, 0, 0, 1),
},
{
"right-handed origin along x",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(1, 0, 0), mathfu::Vector<T, 3>(0, 0, 0),
mathfu::Vector<T, 3>(0, 1, 0), 1),
mathfu::Matrix<T, 4, 4>(0, 0, -1, 0,
0, 1, 0, 0,
1, 0, 0, 0,
0, 0, 0, 1),
},
{
"right-handed origin along y",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(0, 1, 0), mathfu::Vector<T, 3>(0, 0, 0),
mathfu::Vector<T, 3>(1, 0, 0), 1),
mathfu::Matrix<T, 4, 4>(0, 1, 0, 0,
0, 0, -1, 0,
-1, 0, 0, 0,
0, 0, 0, 1),
},
{
"right-handed translated eye along x",
mathfu::Matrix<T, 4, 4>::LookAt(
mathfu::Vector<T, 3>(2, 1, 1), mathfu::Vector<T, 3>(1, 1, 1),
mathfu::Vector<T, 3>(0, 1, 0), 1),
mathfu::Matrix<T, 4, 4>(0, 0, -1, 0,
0, 1, 0, 0,
1, 0, 0, 0,
-1,-1, 1, 1),
},
};
// clang-format on
VerifyMatrixExpectations(
kTestCases, sizeof(kTestCases) / sizeof(kTestCases[0]), precision);
}
TEST_SCALAR_F(LookAt, FLOAT_PRECISION, kLookAtDoublePrecision);
// Test UnProject calculation.
template <class T>
void UnProject_Test(const T& precision) {
// clang-format off
mathfu::Matrix<T, 4, 4> modelView =
mathfu::Matrix<T, 4, 4>(-1, 0, 0, 0,
0, 1, 0, 0,
0, 0, -1, 0,
0, 0, static_cast<T>(-10), 1);
mathfu::Matrix<T, 4, 4> projection =
mathfu::Matrix<T, 4, 4>(
static_cast<T>(1.81066), 0, 0, 0,
0, static_cast<T>(2.41421342), 0, 0,
0, 0, static_cast<T>(-1.00001991), -1,
0, 0, static_cast<T>(-0.200001985), 0);
// clang-format on
mathfu::Vector<T, 3> result = mathfu::Matrix<T, 4, 4>::UnProject(
mathfu::Vector<T, 3>(754, 1049, 1), modelView, projection, 1600, 1200);
EXPECT_NEAR(result.x, 319.00242400912055, 300.0 * precision);
EXPECT_NEAR(result.y, 3113.7409399625253, 3000.0 * precision);
EXPECT_NEAR(result.z, 10035.303114023569, 10000.0 * precision);
}
TEST_SCALAR_F(UnProject, kUnProjectFloatPrecision, DOUBLE_PRECISION);
// Test matrix transposition.
template <class T, int d>
void Transpose_Test(const T& precision) {
(void)precision;
mathfu::Matrix<T, d> matrix;
for (int i = 0; i < d * d; ++i) {
matrix[i] = static_cast<T>(i);
}
mathfu::Matrix<T, d> transpose = matrix.Transpose();
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(matrix(i, j), transpose(j, i), static_cast<T>(0));
}
}
}
TEST_ALL_F(Transpose, FLOAT_PRECISION, DOUBLE_PRECISION);
// Return one of the numbers:
// offset, offset + width/d, offset + 2*width/d, ... , offset + (d-1)*width/d
// For each i=0..d-1, the number returned is different.
//
// The order of the numbers is determined by 'prime' which should be a prime
// number > d.
template <class T, int d>
static T WellSpacedNumber(int i, int prime, T width, T offset) {
// Reorder the i's from 0..(d-1) by using a prime number.
// The numbers get reordered (i.e. no duplicates) because 'd' and 'prime' are
// relatively prime.
const int remapped = ((i + 1) * prime) % d;
// Map to [0,1) range. That is, inclusive of 0, exclusive of 1.
const T zero_to_one = static_cast<T>(remapped) / static_cast<T>(d);
// Map to [offset, offset + width) range.
const T offset_and_scaled = zero_to_one * width + offset;
return offset_and_scaled;
}
// Generate a (probably) invertable matrix. I haven't gone through the math
// to verify that it's actually invertable for all d, but for small d it is.
//
// We adjusting each element of an identity matrix by a small amount.
// The amount must be small so that the matrix stays reasonably close to
// the identity. Matrices become progressively less numerically stable the
// further the get from identity.
template <class T, int d>
static mathfu::Matrix<T, d> InvertableMatrix() {
mathfu::Matrix<T, d> invertable = mathfu::Matrix<T, d>::Identity();
for (int i = 0; i < d; ++i) {
// The width and offset constants are arbitrary. We do want to keep the
// pseudo-random values centered near 0 though.
const T rand_i = WellSpacedNumber<T, d>(i, 7, 0.8f, -0.33f);
for (int j = 0; j < d; ++j) {
const T rand_j = WellSpacedNumber<T, d>(j, 13, 0.6f, -0.4f);
invertable(i, j) += rand_i * rand_j;
}
}
return invertable;
}
template <class T, int d>
static void ExpectEqualMatrices(const mathfu::Matrix<T, d>& a,
const mathfu::Matrix<T, d>& b, T precision) {
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
EXPECT_NEAR(a(i, j), b(i, j), precision);
}
}
}
// Test matrix operator*() by multiplying an invertable matrix by its
// inverse. Should end up with identity.
template <class T, int d>
void MultiplyOperatorInverse_Test(const T& precision) {
typedef typename mathfu::Matrix<T, d> Mat;
const Mat identity = Mat::Identity();
const Mat invertable = InvertableMatrix<T, d>();
// Use operator*() to go way from the identity and then get back to it.
Mat product = identity;
product *= invertable;
product *= invertable.Inverse();
// Test that operator*() gets is back to where we need to go.
ExpectEqualMatrices(product, identity, precision);
}
TEST_ALL_F(MultiplyOperatorInverse, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test static operator*(a, b) by multiplying an invertable matrix by its
// inverse. Should end up with identity.
template <class T, int d>
void ExternalMultiplyOperatorInverse_Test(const T& precision) {
typedef typename mathfu::Matrix<T, d> Mat;
const Mat identity = Mat::Identity();
const Mat invertable = InvertableMatrix<T, d>();
// Use operator*() to go way from the identity and then get back to it.
Mat product = identity;
product = product * invertable;
product = product * invertable.Inverse();
// Test that operator*() gets is back to where we need to go.
ExpectEqualMatrices(product, identity, precision);
}
TEST_ALL_F(ExternalMultiplyOperatorInverse, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test matrix operator*() by multiplying a non-zero matrix by identity.
// Should be no change.
template <class T, int d>
void MultiplyOperatorIdentity_Test(const T& precision) {
typedef typename mathfu::Matrix<T, d> Mat;
const Mat identity = Mat::Identity();
const Mat invertable = InvertableMatrix<T, d>();
// Use operator*() to multipy by the identity.
Mat product = invertable;
product *= identity;
// Test that operator*() didn't change anything when multiplying by identity.
ExpectEqualMatrices(product, invertable, precision);
}
TEST_ALL_F(MultiplyOperatorIdentity, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test static operator*(a, b) by multiplying a non-zero matrix by identity.
// Should be no change.
template <class T, int d>
void ExternalMultiplyOperatorIdentity_Test(const T& precision) {
typedef typename mathfu::Matrix<T, d> Mat;
const Mat identity = Mat::Identity();
const Mat invertable = InvertableMatrix<T, d>();
// Use operator*() to multipy by the identity.
const Mat product = invertable * identity;
// Test that operator*() didn't change anything when multiplying by identity.
ExpectEqualMatrices(product, invertable, precision);
}
TEST_ALL_F(ExternalMultiplyOperatorIdentity, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test matrix operator*() by multiplying a non-zero matrix by zero.
// Should be no change.
template <class T, int d>
void MultiplyOperatorZero_Test(const T& precision) {
typedef typename mathfu::Matrix<T, d> Mat;
const Mat zero(static_cast<T>(0));
const Mat invertable = InvertableMatrix<T, d>();
// Use operator*() to multipy by the zero.
Mat product = invertable;
product *= zero;
// Test that operator*() didn't change anything when multiplying by zero.
ExpectEqualMatrices(product, zero, precision);
}
TEST_ALL_F(MultiplyOperatorZero, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test static operator*(a, b) by multiplying a non-zero matrix by zero.
// Should be no change.
template <class T, int d>
void ExternalMultiplyOperatorZero_Test(const T& precision) {
typedef typename mathfu::Matrix<T, d> Mat;
const Mat zero(static_cast<T>(0));
const Mat invertable = InvertableMatrix<T, d>();
// Use operator*() to multipy by the zero.
const Mat product = invertable * zero;
// Test that operator*() didn't change anything when multiplying by zero.
ExpectEqualMatrices(product, zero, precision);
}
TEST_ALL_F(ExternalMultiplyOperatorZero, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test Matrix<>::ToAffineTransform().
template <class T>
void Mat4ToAffine_Test(const T&) {
typedef typename mathfu::Matrix<T, 4> Mat4;
typedef typename mathfu::Matrix<T, 4, 3> Affine;
const Mat4 indices4(0, 1, 2, 0, 4, 5, 6, 0, 8, 9, 10, 0, 12, 13, 14, 1);
const Affine indices_affine(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14);
const Affine to_affine = Mat4::ToAffineTransform(indices4);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
EXPECT_EQ(to_affine(i, j), indices_affine(i, j));
}
}
}
TEST_SCALAR_F(Mat4ToAffine, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test Matrix<>::FromAffineTransform().
template <class T>
void Mat4FromAffine_Test(const T&) {
typedef typename mathfu::Matrix<T, 4> Mat4;
typedef typename mathfu::Matrix<T, 4, 3> Affine;
const Mat4 indices4(0, 1, 2, 0, 4, 5, 6, 0, 8, 9, 10, 0, 12, 13, 14, 1);
const Affine indices_affine(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14);
const Mat4 to_mat4 = Mat4::FromAffineTransform(indices_affine);
ExpectEqualMatrices(to_mat4, indices4, static_cast<T>(0));
}
TEST_SCALAR_F(Mat4FromAffine, FLOAT_PRECISION, DOUBLE_PRECISION);
// Test converting back and forth via Matrix<>::To/FromAffineTransform().
template <class T>
void Mat4ToAndFromAffine_Test(const T&) {
typedef typename mathfu::Matrix<T, 4> Mat4;
typedef typename mathfu::Matrix<T, 4, 3> Affine;
const Mat4 indices4(0, 1, 2, 0, 4, 5, 6, 0, 8, 9, 10, 0, 12, 13, 14, 1);
const Affine indices_affine(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14);
// Convert to/from AffineTransform and check to ensure the result is the same
// as the original.
const Mat4 converted =
Mat4::FromAffineTransform(Mat4::ToAffineTransform(indices4));
ExpectEqualMatrices(indices4, converted, static_cast<T>(0));
// Perform a normal mat4 * mat4 multiplication and compare its result with
// multiplications involving conversions.
const Mat4 mat4_multiplication = indices4 * indices4;
const Mat4 affine_multiplication = Mat4::FromAffineTransform(indices_affine) *
Mat4::FromAffineTransform(indices_affine);
const Mat4 affine_and_mat4_multiplication =
indices4 * Mat4::FromAffineTransform(indices_affine);
ExpectEqualMatrices(mat4_multiplication, affine_multiplication,
static_cast<T>(0));
ExpectEqualMatrices(mat4_multiplication, affine_and_mat4_multiplication,
static_cast<T>(0));
// Ensure that the result from the multiplication produces the expected
// AffineTransform result.
const Affine expected_result(20, 68, 116, 176, 23, 83, 143, 216, 26, 98, 170,
256);
const Affine affine_result = Mat4::ToAffineTransform(affine_multiplication);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
EXPECT_EQ(expected_result(i, j), affine_result(i, j));
}
}
}
TEST_SCALAR_F(Mat4ToAndFromAffine, FLOAT_PRECISION, DOUBLE_PRECISION);
// This will test converting from a translation into a matrix and back again.
// Test the compilation of basic matrix operations given in the sample file.
// This will test transforming a vector with a matrix.
TEST_F(MatrixTests, MatrixSample) {
using namespace mathfu;
/// @doxysnippetstart Chapter04_Matrices.md Matrix_Sample
Vector<float, 3> trans(3.f, 2.f, 8.f);
Vector<float, 3> rotation(0.4f, 1.4f, 0.33f);
Vector<float, 3> vector(4.f, 8.f, 1.f);
Quaternion<float> rotQuat = Quaternion<float>::FromEulerAngles(rotation);
Matrix<float, 3> rotMatrix = rotQuat.ToMatrix();
Matrix<float, 4> transMatrix = Matrix<float, 4>::FromTranslationVector(trans);
Matrix<float, 4> rotHMatrix = Matrix<float, 4>::FromRotationMatrix(rotMatrix);
Matrix<float, 4> matrix = transMatrix * rotHMatrix;
Vector<float, 3> rotatedVector = matrix * vector;
/// @doxysnippetend
const float precision = 1e-2f;
EXPECT_NEAR(5.14f, rotatedVector[0], precision);
EXPECT_NEAR(10.11f, rotatedVector[1], precision);
EXPECT_NEAR(4.74f, rotatedVector[2], precision);
}
// Simple class that represents a possible compatible type for a vector.
// That is, it's just an array of T of length d, so can be loaded and
// stored from mathfu::Vector<T,d> using ToType() and FromType().
template <class T, int d>
struct SimpleMatrix {
T values[d * d];
};
// This will test the FromType() conversion functions.
template <class T, int d>
void FromType_Test(const T& precision) {
typedef SimpleMatrix<T, d> CompatibleT;
typedef mathfu::Matrix<T, d> MatrixT;
CompatibleT compatible;
for (int i = 0; i < d * d; ++i) {
compatible.values[i] = static_cast<T>(i * precision);
}
#ifdef MATHFU_COMPILE_WITH_PADDING
// With padding, vec3s take up 4-floats worth of memory, so byte-wise
// conversion won't work.
if (sizeof(CompatibleT) != sizeof(MatrixT)) {
EXPECT_EQ(d, 3);
return;
}
#endif // MATHFU_COMPILE_WITH_PADDING
const MatrixT matrix = MatrixT::FromType(compatible);
for (int i = 0; i < d * d; ++i) {
EXPECT_EQ(compatible.values[i], matrix[i]);
}
}
TEST_ALL_F(FromType, 0.0f, 0.0);
// This will test the ToType() conversion functions.
template <class T, int d>
void ToType_Test(const T& precision) {
typedef SimpleMatrix<T, d> CompatibleT;
typedef mathfu::Matrix<T, d> MatrixT;
MatrixT matrix;
for (int i = 0; i < d * d; ++i) {
matrix[i] = static_cast<T>(i * precision);
}
#ifdef MATHFU_COMPILE_WITH_PADDING
// With padding, vec3s take up 4-floats worth of memory, so byte-wise
// conversion won't work.
if (sizeof(CompatibleT) != sizeof(MatrixT)) {
EXPECT_EQ(d, 3);
return;
}
#endif // MATHFU_COMPILE_WITH_PADDING
const CompatibleT compatible = MatrixT::template ToType<CompatibleT>(matrix);
for (int i = 0; i < d * d; ++i) {
EXPECT_EQ(compatible.values[i], matrix[i]);
}
}
TEST_ALL_F(ToType, 0.0f, 0.0);
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
printf("%s (%s)\n", argv[0], MATHFU_BUILD_OPTIONS_STRING);
return RUN_ALL_TESTS();
}