Structs continued

Basic functions finished and tested for matrix and vector.
dev-ui
Piotr Dziwinski 2012-05-01 20:05:48 +02:00
parent 7369b10a87
commit 2513f6556e
9 changed files with 629 additions and 301 deletions

View File

@ -697,7 +697,7 @@ FILE_PATTERNS = *.h \
# should be searched for input files as well. Possible values are YES and NO.
# If left blank NO is used.
RECURSIVE = NO
RECURSIVE = YES
# The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a
@ -705,7 +705,7 @@ RECURSIVE = NO
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE =
EXCLUDE = "src/old" "src/metafile"
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded

View File

@ -14,15 +14,18 @@
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// math/const.h
/* Math constants */
/** @defgroup MathConstModule math/const.h
Contains the math constants used in math functions.
*/
#pragma once
// Math module namespace
namespace Math
{
/* @{ */ // start of group
//! Tolerance level -- minimum accepted float value
const float TOLERANCE = 1e-6f;
@ -41,4 +44,6 @@ namespace Math
const float DEG_TO_RAD = 0.01745329251994329547f;
//! Radians to degrees multiplier
const float RAD_TO_DEG = 57.29577951308232286465f;
};
/* @} */ // end of group
}; // namespace Math

View File

@ -14,9 +14,9 @@
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// math/func.h
/* Common math functions */
/** @defgroup MathFuncModule math/func.h
Contains common math functions.
*/
#pragma once
@ -26,9 +26,13 @@
#include <cmath>
#include <cstdlib>
// Math module namespace
namespace Math
{
/* @{ */ // start of group
//! Compares \a a and \a b within \a tolerance
inline bool IsEqual(float a, float b, float tolerance = Math::TOLERANCE)
{
@ -254,5 +258,6 @@ inline float Bounce(float progress, float middle, float bounce)
}
}
}; // namespace Math
/* @} */ // end of group
}; // namespace Math

View File

@ -14,9 +14,9 @@
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// math/matrix.h
/* Matrix struct and functions */
/** @defgroup MathMatrixModule math/matrix.h
Contains the Matrix struct and related functions.
*/
#pragma once
@ -27,35 +27,39 @@
#include <cmath>
#include <cassert>
// Math module namespace
namespace Math
{
/** 4x4 matrix
/* @{ */ // start of group
/** \struct Matrix math/matrix.h
\brief 4x4 matrix
Represents an universal 4x4 matrix that can be used in OpenGL and DirectX engines.
Contains the required methods for operating on matrices (inverting, multiplying, etc.).
The internal representation is a 16-value table in column-major order, thus:
m[0 ] m[4 ] m[8 ] m[12]
m[1 ] m[5 ] m[9 ] m[13]
m[2 ] m[6 ] m[10] m[14]
m[3 ] m[7 ] m[11] m[15]
\verbatim m[0 ] m[4 ] m[8 ] m[12]
m[1 ] m[5 ] m[9 ] m[13]
m[2 ] m[6 ] m[10] m[14]
m[3 ] m[7 ] m[11] m[15] \endverbatim
This representation is native to OpenGL; DirectX requires transposing the matrix.
The order of multiplication of matrix and vector is also OpenGL-native
(see the method Vector::MultiplyMatrix).
(see the function MatrixVectorMultiply).
All methods are made inline to maximize optimization.
TODO test
Unit tests for the structure and related functions are in module: math/test/matrix_test.cpp.
**/
struct Matrix
{
//! Matrix values in column-major format
//! Matrix values in column-major order
float m[16];
//! Creates the indentity matrix
@ -64,14 +68,28 @@ struct Matrix
LoadIdentity();
}
//! Creates the matrix from given values
/** \a m values in column-major format */
//! Creates the matrix from 1D array
/** \a m matrix values in column-major order */
inline Matrix(const float (&m)[16])
{
for (int i = 0; i < 16; ++i)
this->m[i] = m[i];
}
//! Creates the matrix from 2D array
/** The array's first index is row, second is column.
\a m array with values */
inline Matrix(const float (&m)[4][4])
{
for (int c = 0; c < 4; ++c)
{
for (int r = 0; r < 4; ++r)
{
this->m[4*c+r] = m[r][c];
}
}
}
//! Loads the zero matrix
inline void LoadZero()
{
@ -83,7 +101,10 @@ struct Matrix
inline void LoadIdentity()
{
LoadZero();
m[0] = m[5] = m[10] = m[15] = 1.0f;
/* (1,1) */ m[0 ] = 1.0f;
/* (2,2) */ m[5 ] = 1.0f;
/* (3,3) */ m[10] = 1.0f;
/* (4,4) */ m[15] = 1.0f;
}
//! Transposes the matrix
@ -114,7 +135,7 @@ struct Matrix
//! Calculates the cofactor of the matrix
/** \a r row (0 to 3)
\a c column (0 to 3)
\returns the cofactor or 0.0f if invalid r, c given*/
\returns the cofactor */
inline float Cofactor(int r, int c) const
{
assert(r >= 0 && r <= 3);
@ -293,38 +314,48 @@ struct Matrix
return result;
}
//! Inverts the matrix
inline void Invert()
//! Calculates the inverse matrix
/** The determinant of the matrix must not be zero.
\returns the inverted matrix */
inline Matrix Inverse() const
{
float d = Det();
assert(! IsZero(d));
Matrix temp = *this;
float result[16] = { 0.0f };
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
m[4*r+c] = (1.0f / d) * temp.Cofactor(r, c);
// Already transposed!
result[4*r+c] = (1.0f / d) * Cofactor(r, c);
}
}
return Matrix(result);
}
//! Multiplies the matrix with the given matrix
/** \a right right-hand matrix */
inline void Multiply(const Matrix &right)
//! Calculates the multiplication of this matrix * given matrix
/** \a right right-hand matrix
\returns multiplication result */
inline Matrix Multiply(const Matrix &right) const
{
Matrix left = *this;
float result[16] = { 0.0f };
for (int c = 0; c < 4; ++c)
{
for (int r = 0; r < 4; ++r)
{
m[4*c+r] = 0.0f;
result[4*c+r] = 0.0f;
for (int i = 0; i < 4; ++i)
{
m[4*c+r] += left.m[4*i+r] * right.m[4*c+i];
result[4*c+r] += m[4*i+r] * right.m[4*c+i];
}
}
}
return Matrix(result);
}
//! Loads view matrix from the given vectors
@ -374,56 +405,122 @@ struct Matrix
// Start building the matrix. The first three rows contains the basis
// vectors used to rotate the view to point at the lookat point
LoadIdentity();
m[0 ] = right.x; m[1 ] = up.x; m[2 ] = view.x;
m[4 ] = right.y; m[5 ] = up.y; m[6 ] = view.y;
m[8 ] = right.z; m[9 ] = up.z; m[10] = view.z;
/* (1,1) */ m[0 ] = right.x;
/* (2,1) */ m[1 ] = up.x;
/* (3,1) */ m[2 ] = view.x;
/* (1,2) */ m[4 ] = right.y;
/* (2,2) */ m[5 ] = up.y;
/* (3,2) */ m[6 ] = view.y;
/* (1,3) */ m[8 ] = right.z;
/* (2,3) */ m[9 ] = up.z;
/* (3,3) */ m[10] = view.z;
// Do the translation values (rotations are still about the eyepoint)
m[12] = - DotProduct(from, right);
m[13] = - DotProduct(from, up);
m[14] = - DotProduct(from, view);
/* (1,4) */ m[12] = -DotProduct(from, right);
/* (2,4) */ m[13] = -DotProduct(from, up);
/* (3,4) */ m[14] = -DotProduct(from, view);
}
//! Loads a perspective projection matrix
/** \a fov field of view in radians
\a aspect aspect ratio (width / height)
\a nearPlane distance to near cut plane
\a farPlane distance to far cut plane */
inline void LoadProjection(float fov = 1.570795f, float aspect = 1.0f,
float nearPlane = 1.0f, float farPlane = 1000.0f)
{
// TODO
assert(fabs(farPlane - nearPlane) >= 0.01f);
assert(fabs(sin(fov / 2)) >= 0.01f);
float w = aspect * (cosf(fov / 2) / sinf(fov / 2));
float h = 1.0f * (cosf(fov / 2) / sinf(fov / 2));
float q = farPlane / (farPlane - nearPlane);
LoadZero();
/* (1,1) */ m[0 ] = w;
/* (2,2) */ m[5 ] = h;
/* (3,3) */ m[10] = q;
/* (3,4) */ m[14] = 1.0f;
/* (4,3) */ m[11] = -q * nearPlane;
}
//! Loads a translation matrix from given vector
/** \a trans vector of translation*/
inline void LoadTranslation(const Vector &trans)
{
LoadIdentity();
m[12] = trans.x;
m[13] = trans.y;
m[14] = trans.z;
/* (1,4) */ m[12] = trans.x;
/* (2,4) */ m[13] = trans.y;
/* (3,4) */ m[14] = trans.z;
}
//! Loads a scaling matrix fom given vector
/** \a scale vector with scaling factors for X, Y, Z */
inline void LoadScale(const Vector &scale)
{
LoadIdentity();
m[0] = scale.x;
m[5] = scale.y;
m[10] = scale.z;
/* (1,1) */ m[0 ] = scale.x;
/* (2,2) */ m[5 ] = scale.y;
/* (3,3) */ m[10] = scale.z;
}
//! Loads a rotation matrix along the X axis
/** \a angle angle in radians */
inline void LoadRotationX(float angle)
{
// TODO
LoadIdentity();
/* (2,2) */ m[5 ] = cosf(angle);
/* (3,2) */ m[6 ] = sinf(angle);
/* (2,3) */ m[9 ] = -sinf(angle);
/* (3,3) */ m[10] = cosf(angle);
}
//! Loads a rotation matrix along the Y axis
/** \a angle angle in radians */
inline void LoadRotationY(float angle)
{
// TODO
LoadIdentity();
/* (1,1) */ m[0 ] = cosf(angle);
/* (3,1) */ m[2 ] = -sinf(angle);
/* (1,3) */ m[8 ] = sinf(angle);
/* (3,3) */ m[10] = cosf(angle);
}
//! Loads a rotation matrix along the Z axis
/** \a angle angle in radians */
inline void LoadRotationZ(float angle)
{
// TODO
LoadIdentity();
/* (1,1) */ m[0 ] = cosf(angle);
/* (2,1) */ m[1 ] = sinf(angle);
/* (1,2) */ m[4 ] = -sinf(angle);
/* (2,2) */ m[5 ] = cosf(angle);
}
//! Loads a rotation matrix along the given axis
/** \a dir axis of rotation
\a angle angle in radians */
inline void LoadRotation(const Vector &dir, float angle)
{
// TODO
float cos = cosf(angle);
float sin = sinf(angle);
Vector v = Math::Normalize(dir);
LoadIdentity();
/* (1,1) */ m[0 ] = (v.x * v.x) * (1.0f - cos) + cos;
/* (2,1) */ m[1 ] = (v.x * v.y) * (1.0f - cos) - (v.z * sin);
/* (3,1) */ m[2 ] = (v.x * v.z) * (1.0f - cos) + (v.y * sin);
/* (1,2) */ m[4 ] = (v.y * v.x) * (1.0f - cos) + (v.z * sin);
/* (2,2) */ m[5 ] = (v.y * v.y) * (1.0f - cos) + cos ;
/* (3,2) */ m[6 ] = (v.y * v.z) * (1.0f - cos) - (v.x * sin);
/* (1,3) */ m[8 ] = (v.z * v.x) * (1.0f - cos) - (v.y * sin);
/* (2,3) */ m[9 ] = (v.z * v.y) * (1.0f - cos) + (v.x * sin);
/* (3,3) */ m[10] = (v.z * v.z) * (1.0f - cos) + cos;
}
//! Calculates the matrix to make three rotations in the order X, Z and Y
@ -453,32 +550,38 @@ struct Matrix
}
};
//! Convenience function for inverting a matrix
/** \a m input matrix
\a result result -- inverted matrix */
inline void InvertMatrix(const Matrix &m, Matrix &result)
//! Convenience function for getting transposed matrix
inline Matrix Transpose(const Matrix &m)
{
result = m;
result.Invert();
Matrix result = m;
result.Transpose();
return result;
}
//! Convenience function for multiplying a matrix
/** \a left left-hand matrix
\a right right-hand matrix
\a result result -- multiplied matrices */
inline void MultiplyMatrices(const Matrix &left, const Matrix &right, Matrix &result)
\returns multiplied matrices */
inline Matrix MultiplyMatrices(const Matrix &left, const Matrix &right)
{
result = left;
result.Multiply(right);
return left.Multiply(right);
}
//! Calculates the result of multiplying m * v
/** The multiplication is performed thus:
\verbatim [ m.m[0 ] m.m[4 ] m.m[8 ] m.m[12] ] [ v.x ]
[ m.m[1 ] m.m[5 ] m.m[9 ] m.m[13] ] [ v.y ]
[ m.m[2 ] m.m[6 ] m.m[10] m.m[14] ] * [ v.z ]
[ m.m[3 ] m.m[7 ] m.m[11] m.m[15] ] [ 1 ] \endverbatim
The result, a 4x1 vector is then converted to 3x1 by dividing
x,y,z coords by the fourth coord (w). */
inline Vector MatrixVectorMultiply(const Matrix &m, const Vector &v)
{
float x = v.x * m.m[0 ] + v.y * m.m[4 ] + v.z * m.m[8 ] + m.m[12];
float y = v.x * m.m[1 ] + v.y * m.m[5 ] + v.z * m.m[9 ] + m.m[13];
float z = v.x * m.m[2 ] + v.y * m.m[6 ] + v.z * m.m[10] + m.m[14];
float w = v.x * m.m[4 ] + v.y * m.m[7 ] + v.z * m.m[11] + m.m[15];
float w = v.x * m.m[4 ] + v.y * m.m[7 ] + v.z * m.m[11] + m.m[15];
if (IsZero(w))
return Vector(x, y, z);
@ -503,4 +606,6 @@ inline bool MatricesEqual(const Math::Matrix &m1, const Math::Matrix &m2,
return true;
}
/* @} */ // end of group
}; // namespace Math

View File

@ -14,26 +14,49 @@
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// math/point.h
/* Point struct and functions */
/** @defgroup MathPointModule math/point.h
Contains the Point struct and related functions.
*/
#pragma once
#include <cmath>
/* TODO
FPOINT RotatePoint(FPOINT center, float angle, FPOINT p);
FPOINT RotatePoint(float angle, FPOINT p);
FPOINT RotatePoint(float angle, float dist);
void RotatePoint(float cx, float cy, float angle, float &px, float &py);
void RotatePoint(D3DVECTOR center, float angleH, float angleV, D3DVECTOR &p);
void RotatePoint2(D3DVECTOR center, float angleH, float angleV, D3DVECTOR &p);
float RotateAngle(float x, float y);
float RotateAngle(FPOINT center, FPOINT p1, FPOINT p2);
float MidPoint(FPOINT a, FPOINT b, float px);
BOOL IsInsideTriangle(FPOINT a, FPOINT b, FPOINT c, FPOINT p);
BOOL LineFunction(FPOINT p1, FPOINT p2, float &a, float &b);
float IsInsideTriangle(FPOINT a, FPOINT b, FPOINT c);
*/
// Math module namespace
namespace Math
{
/** 2D Point
/* @{ */ // start of group
/** \struct Point math/point.h
\brief 2D point
Represents a 2D point (x, y).
Contains the required methods for operating on points.
All methods are made inline to maximize optimization.
TODO test
*/
struct Point
{
@ -42,43 +65,38 @@ struct Point
//! Y coord
float y;
//! Constructs a zero point: (0,0)
inline Point()
{
LoadZero();
}
//! Constructs a point from given coords: (x,y)
inline Point(float x, float y)
{
this->x = x;
this->y = y;
}
//! Sets the zero point: (0,0)
inline void LoadZero()
{
x = y = 0.0f;
}
//! Returns the distance from (0,0) to the point (x,y)
inline float Length()
{
return std::sqrt(x*x + y*y);
}
};
/* TODO
FPOINT RotatePoint(FPOINT center, float angle, FPOINT p);
FPOINT RotatePoint(float angle, FPOINT p);
FPOINT RotatePoint(float angle, float dist);
void RotatePoint(float cx, float cy, float angle, float &px, float &py);
void RotatePoint(D3DVECTOR center, float angleH, float angleV, D3DVECTOR &p);
void RotatePoint2(D3DVECTOR center, float angleH, float angleV, D3DVECTOR &p);
float Length(FPOINT a, FPOINT b);
float RotateAngle(float x, float y);
float RotateAngle(FPOINT center, FPOINT p1, FPOINT p2);
float MidPoint(FPOINT a, FPOINT b, float px);
BOOL IsInsideTriangle(FPOINT a, FPOINT b, FPOINT c, FPOINT p);
//! Returns the distance between two points
inline float Distance(const Point &a, const Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}
BOOL LineFunction(FPOINT p1, FPOINT p2, float &a, float &b);
/* @} */ // end of group
*/
};
}; // namespace Math

View File

@ -4,11 +4,22 @@ set(CMAKE_BUILD_TYPE debug)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -g -O0")
add_executable(matrix_test matrix_test.cpp)
add_executable(vector_test vector_test.cpp)
enable_testing()
add_test(matrix_test ./matrix_test)
add_test(vector_test ./vector_test)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS matrix_test)
# 'make check' will compile the required test programs
# Note that 'make test' will still fail without compiled programs
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS matrix_test vector_test)
add_custom_target(distclean COMMAND rm -rf ./matrix_test CMakeFiles Testing cmake_install.cmake CMakeCache.txt CTestTestfile.cmake Makefile)
# Files to be removed in distclean
set(REMOVE_FILES
CMakeFiles Testing cmake_install.cmake CMakeCache.txt CTestTestfile.cmake Makefile
./matrix_test
./vector_test
)
add_custom_target(distclean COMMAND rm -rf ${REMOVE_FILES})

View File

@ -18,8 +18,8 @@
/* Unit tests for Matrix struct
Test data was randomly generated and the expected
results calculated using GNU Octave.
Test data was randomly generated and the expected results
calculated using GNU Octave.
*/
@ -30,53 +30,77 @@
using namespace std;
const float TEST_TOLERANCE = 1e-5;
const float TEST_TOLERANCE = 1e-6;
int TestCofactor()
{
const float TEST_MATRIX[16] =
{
-0.306479,
-0.520207,
0.127906,
0.632922,
const Math::Matrix mat1(
(float[4][4])
{
{ 0.610630320796245, 1.059932357918312, -1.581674311378210, 1.782214448453331 },
{ 0.191028848211526, -0.813898708757524, 1.516114203870644, 0.395202639476002 },
{ 0.335142750345279, -0.346586619596529, 0.545382042472336, -0.879268918923072 },
{ 1.417588151657198, 1.450841789070141, 0.219080104196171, 0.378724047481655 }
}
);
-0.782876,
0.015264,
0.337479,
1.466013,
0.072725,
-0.315123,
1.613198,
-0.577377,
0.962397,
-1.320724,
1.467588,
0.579020
};
const float EXPECTED_RESULTS[4][4] =
{
{ 2.791599, -0.249952, 1.065075, -1.356570 },
{ 3.715943, -1.537511, 0.094812, -0.074520 },
{ 1.034500, -0.731752, -0.920756, -0.196235 },
{ 1.213928, -1.236857, 0.779741, -0.678482 }
};
Math::Matrix mat(TEST_MATRIX);
const Math::Matrix expectedCofactors1(
(float[4][4])
{
{ -2.402679369186782, 2.282452509293019, 1.722732204057644, -0.746939701104385 },
{ -0.687677756877654, 1.168949180331164, -0.985354966837796, -1.334071111592705 },
{ -5.115621958424845, 4.229724770159009, 2.529000630782808, 1.481632618355891 },
{ 0.147480897398694, -2.140677680337111, -1.207189492265546, 0.151236920408051 }
}
);
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
float ret = mat.Cofactor(r, c);
float exp = EXPECTED_RESULTS[r][c];
float ret = mat1.Cofactor(r, c);
float exp = expectedCofactors1.m[4*c+r];
if (! Math::IsEqual(ret, exp, TEST_TOLERANCE))
{
fprintf(stderr, "Cofactor r=%d, c=%d, %f (returned) != %f (expected)\n", r, c, ret, exp);
return 4*c+r;
fprintf(stderr, "Cofactors 1 mismatch!\n");
fprintf(stderr, "r=%d, c=%d, %f (returned) != %f (expected)\n", r, c, ret, exp);
return __LINE__;
}
}
}
const Math::Matrix mat2(
(float[4][4])
{
{ 0.9845099464982393, -0.9091233416532389, -0.6272243714245945, 0.4645001858944354 },
{ -0.1333308471483736, 0.9128181433725897, -1.0937461393836190, 0.3180936795928376 },
{ -0.0654324396846289, 0.1014641705415945, 1.5107709042683430, -0.0240560430414690 },
{ 0.0179638644093347, -1.0695585982782767, -0.1741250853101032, 1.0803106709464336 }
}
);
const Math::Matrix expectedCofactors2(
(float[4][4])
{
{ 2.0861102207614466, 0.2989010779528912, 0.0746276150537432, 0.2732659822656097 },
{ 0.6850002886584565, 1.5513169659641379, -0.0503743176545917, 1.5163672441575642 },
{ 1.2385556680997216, 1.1827709562505695, 1.2282813085138962, 1.3483789679871401 },
{ -1.0710790241539783, -0.5589604503588883, 0.0100959837872308, 1.1897872684455839 }
}
);
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
float ret = mat2.Cofactor(r, c);
float exp = expectedCofactors2.m[4*c+r];
if (! Math::IsEqual(ret, exp, TEST_TOLERANCE))
{
fprintf(stderr, "Cofactors 2 mismatch!\n");
fprintf(stderr, "r=%d, c=%d, %f (returned) != %f (expected)\n", r, c, ret, exp);
return __LINE__;
}
}
}
@ -86,96 +110,105 @@ int TestCofactor()
int TestDet()
{
const float TEST_MATRIX[16] =
const Math::Matrix mat1(
(float[4][4])
{
{ -0.95880162984708284, 0.24004047608997131, -0.78172309932665407, -0.11604124457222834 },
{ -0.36230592086261376, -0.75778166876017261, 0.33041059404631740, -1.06001391941094836 },
{ 0.00260215210936187, 1.27485610196385113, -0.26149859846418033, -0.59669701186364876 },
{ 0.36899429848485432, 3.01720896813933104, 2.10311476609438719, -1.68627076626448269 }
}
);
const float expectedDet1 = 4.07415413729671;
float ret1 = mat1.Det();
if (! Math::IsEqual(ret1, expectedDet1, TEST_TOLERANCE))
{
0.85554,
0.11624,
1.30411,
0.81467,
fprintf(stderr, "Det mismatch!\n");
fprintf(stderr, "%f (returned) != %f (expected)\n", ret1, expectedDet1);
return __LINE__;
}
0.49692,
-1.92483,
-1.33543,
0.85042,
const Math::Matrix mat2(
(float[4][4])
{
{ -1.0860073221346871, 0.9150354098189495, -0.2723201933559999, 0.2922832160271507 },
{ -1.0248331304801788, -2.5081237461125205, -1.0277123574586633, -0.2254690663329798 },
{ -1.4227635282899367, -0.0403846809122684, 0.9216148477171653, 1.2517067488015878 },
{ -0.1160254467152022, 0.8270675274393656, 1.0327218739781614, -0.3674886870220400 }
}
);
-0.16775,
0.35344,
1.40673,
0.13961,
const float expectedDet2 = -6.35122307880942;
1.40709,
0.11731,
0.69042,
0.91216
};
const float EXPECTED_RESULT = 0.084360;
float ret = Math::Matrix(TEST_MATRIX).Det();
if (! Math::IsEqual(ret, EXPECTED_RESULT, TEST_TOLERANCE))
float ret2 = mat2.Det();
if (! Math::IsEqual(ret2, expectedDet2, TEST_TOLERANCE))
{
fprintf(stderr, "Det %f (returned) != %f (expected)\n", ret, EXPECTED_RESULT);
return 1;
fprintf(stderr, "Det mismatch!\n");
fprintf(stderr, "%f (returned) != %f (expected)\n", ret2, expectedDet2);
return __LINE__;
}
return 0;
}
int TestInvert()
int TestInverse()
{
const float TEST_MATRIX[16] =
const Math::Matrix mat1(
(float[4][4])
{
{ -2.2829352811514658, -0.9103222363187888, 0.2792976509411680, -0.7984393573193174 },
{ 2.4823665798689589, -0.0599056759070980, 0.3832364352926366, -1.6404257204372739 },
{ -0.3841952272526398, -0.8377700696457873, -0.3416328338427138, 1.1746577275723329 },
{ 0.1746031241954947, -0.4952532117949962, 0.2155084379835037, -1.6586460437329220 }
}
);
const Math::Matrix expectedInverse1(
(float[4][4])
{
{ -0.119472603171041, 0.331675963276297, 0.187516809009720, -0.137720814290806 },
{ -0.387591686166085, -0.487284946727583, -0.798527541290274, 0.102991635972060 },
{ 2.601905603425902, 2.606899016264679, -0.528006148839176, -4.204703326522837 },
{ 0.441220327151392, 0.519128136207318, 0.189567009205522, -1.194469716136194 }
}
);
Math::Matrix inverse1 = mat1.Inverse();
if (! Math::MatricesEqual(inverse1, expectedInverse1, TEST_TOLERANCE))
{
1.4675123,
-0.2857923,
-0.0496217,
-1.2825408,
fprintf(stderr, "Inverse 1 mismatch!\n");
return __LINE__;
}
-0.2804135,
-0.0826255,
-0.6825495,
1.1661259,
const Math::Matrix mat2(
(float[4][4])
{
{ -0.05464332404298505, -0.64357755258235749, -0.13017671677619302, -0.56742332785888006 },
{ 0.29048383600458222, -0.91517047043724875, 0.84517524415561684, 0.51628195547960565 },
{ 0.00946488004480186, -0.89077382212689293, 0.73565573766341397, -0.15932513521840930 },
{ -1.01244718912499132, -0.27840911963972276, -0.39189681211309862, 1.18315064340192055 }
}
);
0.0032798,
0.5999200,
-1.8359883,
-1.1894424,
const Math::Matrix expectedInverse2(
(float[4][4])
{
{ 0.771302711132012, 1.587542278361995, -2.003075114445104, -0.592574156227379 },
{ -1.208929259769431, -0.786598967848473, 0.607335305808052, -0.154759693303324 },
{ -1.500037668208218, -0.774300278997914, 1.917800427261255, -0.123268572651291 },
{ -0.121314770937944, 0.916925149209746, -0.935924950785014, 0.260875394250671 }
}
);
-1.1501538,
-2.8792485,
0.0299345,
0.3730919
};
Math::Matrix inverse2 = mat2.Inverse();
const float EXPECTED_RESULT[16] =
if (! Math::MatricesEqual(inverse2, expectedInverse2, TEST_TOLERANCE))
{
0.685863,
0.562274,
-0.229722,
-0.132079,
-0.266333,
-0.139862,
0.054211,
-0.305568,
-0.130817,
-0.494076,
-0.358226,
-0.047477,
0.069486,
0.693649,
-0.261074,
-0.081200
};
Math::Matrix mat(TEST_MATRIX);
mat.Invert();
if (! Math::MatricesEqual(mat, EXPECTED_RESULT, TEST_TOLERANCE))
{
fprintf(stderr, "Invert mismatch\n");
return 1;
fprintf(stderr, "Inverse 2 mismatch!\n");
return __LINE__;
}
return 0;
@ -183,81 +216,78 @@ int TestInvert()
int TestMultiply()
{
const float TEST_MATRIX_A[16] =
const Math::Matrix mat1A(
(float[4][4])
{
{ 0.6561727049162027, -1.4180263627131411, -0.8271026046117423, 2.3919331748512578 },
{ -0.6035665535146352, 0.0150827348790615, -0.7090794192822540, 0.9057604704594814 },
{ -0.9871045001223655, -0.4980646811455065, 0.3806177002298990, 0.1520583649240934 },
{ -0.2721911170792712, 0.7627928194552067, -0.1504091336784158, 0.9747545351840121 }
}
);
const Math::Matrix mat1B(
(float[4][4])
{
{ -0.2643735892448818, -0.7542994492819621, 0.6082322350568750, 0.0581733424861419 },
{ 1.0293246070431237, 0.1979285388251341, -0.2932031385332818, 0.8838407179018929 },
{ 0.3448687251553114, 0.5031654871245456, 0.7554693012922442, -0.4845315903845708 },
{ -1.8662838497278593, -0.7843850624747805, 0.1389026096476257, -1.3686415408300689 }
}
);
const Math::Matrix expectedMultiply1(
(float[4][4])
{
{ -6.382352236417988, -3.067984733682130, 0.522270304251466, -4.088079444498280 },
{ -1.759853366848825, -0.608994052024491, -0.781406179437379, -0.917870775786188 },
{ -0.404226802169062, 0.718232546720114, -0.145688356880835, -0.890167707987175 },
{ -1.013918490922430, -0.483971504099758, -0.367442194643757, -0.602858486133615 }
}
);
Math::Matrix multiply1 = Math::MultiplyMatrices(mat1A, mat1B);
if (! Math::MatricesEqual(multiply1, expectedMultiply1, TEST_TOLERANCE ) )
{
-1.931420,
0.843410,
0.476929,
-0.493435,
1.425659,
-0.176331,
0.129096,
0.551081,
-0.543530,
-0.190783,
-0.084744,
1.379547,
-0.473377,
1.643398,
0.400539,
0.702937
};
fprintf(stderr, "Multiply 1 mismath!\n");
return __LINE__;
}
const float TEST_MATRIX_B[16] =
const Math::Matrix mat2A(
(float[4][4])
{
{ 0.8697203025776754, 2.1259475710644935, 1.7856691009707812, -2.1563963348328126 },
{ 1.5888074489288735, -0.0794849733953615, 0.7307782768677457, 0.7943129159612630 },
{ 0.2859761537233830, -0.6231231890384962, -0.0496743172880377, -0.8137857518646087 },
{ 1.2670547229512983, -0.5305171374831831, -0.4987412674062375, -1.1257327113869595 }
}
);
const Math::Matrix mat2B(
(float[4][4])
{
{ 1.1321105701165317, 0.1759563504574463, -2.0675778912000418, 1.4840339814245538 },
{ -1.5117280888829916, -0.0933013188828093, -0.2079262944351640, 0.9575727579539316 },
{ 0.3615378398970173, 1.2465163589027248, 1.1326150997082589, 0.9921208694352303 },
{ -0.7357104529373861, -0.4774022005969588, -0.2118739096676499, 1.1427567093270703 }
}
);
const Math::Matrix expectedMultiply2(
(float[4][4])
{
{ 0.00283516267056338, 3.21001319965989307, 0.23910503934370686, 2.63380716363006107 },
{ 1.59868505822469742, 0.81869715594617765, -2.60905981088293570, 3.91445839239110294 },
{ 1.84650099286297942, 0.43504079532852930, -0.34555619012424243, -1.15152951542451487 },
{ 2.88434318563174585, 0.18818239851585700, -2.83579436909308980, -0.40890672198610400 }
}
);
Math::Matrix multiply2 = Math::MultiplyMatrices(mat2A, mat2B);
if (! Math::MatricesEqual(multiply2, expectedMultiply2, TEST_TOLERANCE ) )
{
0.3517561,
1.3903778,
-0.8048254,
-0.4090024,
-1.5542159,
-0.6798636,
1.6003393,
-0.1467117,
0.5043620,
-0.0068779,
2.0697285,
-0.0463650,
0.9605451,
-0.4620149,
1.2525952,
-1.3409909
};
const float EXPECTED_RESULT[16] =
{
1.933875,
-0.467099,
0.251638,
-0.805156,
1.232207,
-1.737383,
-1.023401,
2.496859,
-2.086953,
-0.044468,
0.045688,
2.570036,
-2.559921,
-1.551155,
-0.244802,
0.056808
};
Math::Matrix matA(TEST_MATRIX_A);
Math::Matrix matB(TEST_MATRIX_B);
Math::Matrix mat;
Math::MultiplyMatrices(matA, matB, mat);
if (! Math::MatricesEqual(mat, Math::Matrix(EXPECTED_RESULT), TEST_TOLERANCE ) )
{
fprintf(stderr, "Multiply mismath!\n");
return 1;
fprintf(stderr, "Multiply 2 mismath!\n");
return __LINE__;
}
return 0;
@ -265,23 +295,25 @@ int TestMultiply()
int main()
{
// Functions to test
int (*TESTS[])() =
{
TestCofactor,
TestDet,
TestInverse,
TestMultiply
};
const int TESTS_SIZE = sizeof(TESTS) / sizeof(*TESTS);
int result = 0;
for (int i = 0; i < TESTS_SIZE; ++i)
{
result = TESTS[i]();
if (result != 0)
return result;
}
result = TestCofactor();
if (result != 0)
return result;
fprintf(stderr, "All tests successful\n");
result = TestDet();
if (result != 0)
return result;
result = TestInvert();
if (result != 0)
return result;
result = TestMultiply();
if (result != 0)
return result;
return result;
return 0;
}

View File

@ -0,0 +1,126 @@
// * This file is part of the COLOBOT source code
// * Copyright (C) 2012, Polish Portal of Colobot (PPC)
// *
// * This program is free software: you can redistribute it and/or modify
// * it under the terms of the GNU General Public License as published by
// * the Free Software Foundation, either version 3 of the License, or
// * (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// math/test/vector_test.cpp
/* Unit tests for Vector struct
Test data was randomly generated and the expected results
calculated using GNU Octave.
*/
#include "../func.h"
#include "../vector.h"
#include <cstdio>
using namespace std;
const float TEST_TOLERANCE = 1e-6;
int TestLength()
{
Math::Vector vec(-1.288447945923275, 0.681452565308134, -0.633761098985957);
const float expectedLength = 1.58938001708428;
if (! Math::IsEqual(vec.Length(), expectedLength, TEST_TOLERANCE) )
{
fprintf(stderr, "Length mismatch!\n");
return __LINE__;
}
return 0;
}
int TestNormalize()
{
Math::Vector vec(1.848877241804398, -0.157262961268577, -1.963031403332377);
const Math::Vector expectedNormalized(0.6844609421393856, -0.0582193085618106, -0.7267212194481797);
vec.Normalize();
if (! Math::VectorsEqual(vec, expectedNormalized, TEST_TOLERANCE))
{
fprintf(stderr, "Normalize mismatch!\n");
return __LINE__;
}
return 0;
}
int TestDot()
{
Math::Vector vecA(0.8202190530968309, 0.0130926060162780, 0.2411914183883510);
Math::Vector vecB(-0.0524083951404069, 1.5564932716738220, -0.8971342631500536);
float expectedDot = -0.238988896477326;
if (! Math::IsEqual(Math::DotProduct(vecA, vecB), expectedDot, TEST_TOLERANCE) )
{
fprintf(stderr, "Dot product mismatch!\n");
return __LINE__;
}
return 0;
}
int TestCross()
{
Math::Vector vecA(1.37380499798567, 1.18054518384682, 1.95166361293121);
Math::Vector vecB(0.891657855926886, 0.447591335394532, -0.901604070087823);
Math::Vector expectedCross(-1.937932065431669, 2.978844370287636, -0.437739173833581);
Math::Vector expectedReverseCross = -expectedCross;
if (! Math::VectorsEqual(vecA.CrossMultiply(vecB), expectedCross, TEST_TOLERANCE) )
{
fprintf(stderr, "Cross product mismatch!\n");
return __LINE__;
}
if (! Math::VectorsEqual(vecB.CrossMultiply(vecA), expectedReverseCross, TEST_TOLERANCE) )
{
fprintf(stderr, "Reverse cross product mismatch!\n");
return __LINE__;
}
return 0;
}
int main()
{
// Functions to test
int (*TESTS[])() =
{
TestNormalize,
TestDot,
TestCross
};
const int TESTS_SIZE = sizeof(TESTS) / sizeof(*TESTS);
int result = 0;
for (int i = 0; i < TESTS_SIZE; ++i)
{
result = TESTS[i]();
if (result != 0)
return result;
}
fprintf(stderr, "All tests successful\n");
return 0;
}

View File

@ -14,9 +14,9 @@
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// math/vector.h
/* Vector struct and functions */
/** @defgroup MathVectorModule math/vector.h
Contains the Vector struct and related functions.
*/
#pragma once
@ -25,6 +25,7 @@
#include <cmath>
/*
TODO
@ -53,14 +54,17 @@ BOOL IsSamePlane(D3DVECTOR *plan1, D3DVECTOR *plan2);
namespace Math
{
/** 3x1 Vector
/* @{ */ // start of group
Represents an universal 3x1 vector that can be used in OpenGL and DirectX engines.
/** \struct Vector math/vector.h
\brief 3D (3x1) vector
Represents a universal 3x1 vector that can be used in OpenGL and DirectX engines.
Contains the required methods for operating on vectors.
All methods are made inline to maximize optimization.
TODO test
Unit tests for the structure and related functions are in module: math/test/vector_test.cpp.
*/
struct Vector
@ -102,22 +106,28 @@ struct Vector
inline void Normalize()
{
float l = Length();
if (Math::IsZero(l))
return;
x /= l;
y /= l;
z /= l;
}
//! Calculates the cross product with another vector
/** \a right right-hand side vector */
inline void CrossMultiply(const Vector &right)
/** \a right right-hand side vector
\returns the cross product*/
inline Vector CrossMultiply(const Vector &right) const
{
Vector left = *this;
x = left.y * right.z - left.z * right.y;
y = left.z * right.x - left.x * right.z;
z = left.x * right.y - left.y * right.x;
float px = y * right.z - z * right.y;
float py = z * right.x - x * right.z;
float pz = x * right.y - y * right.x;
return Vector(px, py, pz);
}
//! Calculates the dot product with another vector
/** \a right right-hand side vector
\returns the dot product */
inline float DotMultiply(const Vector &right) const
{
return x * right.x + y * right.y + z * right.z;
@ -208,6 +218,14 @@ struct Vector
}
};
//! Convenience function for getting normalized vector
inline Vector Normalize(const Vector &v)
{
Vector result = v;
result.Normalize();
return result;
}
//! Convenience function for calculating dot product
inline float DotProduct(const Vector &left, const Vector &right)
{
@ -217,9 +235,17 @@ inline float DotProduct(const Vector &left, const Vector &right)
//! Convenience function for calculating cross product
inline Vector CrossProduct(const Vector &left, const Vector &right)
{
Vector result = left;
result.CrossMultiply(right);
return result;
return left.CrossMultiply(right);
}
//! Checks if two vectors are equal within given \a tolerance
inline bool VectorsEqual(const Vector &a, const Vector &b, float tolerance = Math::TOLERANCE)
{
return Math::IsEqual(a.x, b.x, tolerance)
&& Math::IsEqual(a.y, b.y, tolerance)
&& Math::IsEqual(a.z, b.z, tolerance);
}
/* @} */ // end of group
}; // namespace Math