diff --git a/demos/demo1-wireframes/src/wirescene.cpp b/demos/demo1-wireframes/src/wirescene.cpp index 67524b8..64fe95a 100644 --- a/demos/demo1-wireframes/src/wirescene.cpp +++ b/demos/demo1-wireframes/src/wirescene.cpp @@ -11,8 +11,6 @@ const unsigned short pal[4] __attribute__((aligned(4))) = { 0x0000, 0xFFFF, 0x3AE2 }; -const FIXED pointZeroOne = float2fx(0.01); - std::vector WireScene::meshes() { return { cube.get() }; } @@ -26,16 +24,31 @@ void WireScene::load() { backgroundPalette = std::unique_ptr(new BackgroundPaletteManager(pal, sizeof(pal))); cube = std::unique_ptr(new Mesh()); - cube->add(VectorFx(-1, 1, 1)); - cube->add(VectorFx(1, 1, 1)); - cube->add(VectorFx(-1, -1, 1)); - cube->add(VectorFx(-1, -1, -1)); - cube->add(VectorFx(-1, 1, -1)); - cube->add(VectorFx(1, 1, -1)); - cube->add(VectorFx(1, -1, 1)); - cube->add(VectorFx(-1, -1, -1)); + cube->add(VectorFx::fromInt(-1, 1, 1)); + cube->add(VectorFx::fromInt(1, 1, 1)); + cube->add(VectorFx::fromInt(-1, -1, 1)); + cube->add(VectorFx::fromInt(-1, -1, -1)); + cube->add(VectorFx::fromInt(-1, 1, -1)); + cube->add(VectorFx::fromInt(1, 1, -1)); + cube->add(VectorFx::fromInt(1, -1, 1)); + cube->add(VectorFx::fromInt(1, -1, -1)); + /* + * should be translated, with camera 0,0,10, and with GBA dimensions, to: + SoftEngine.js:50 drawing 163,123 + SoftEngine.js:50 drawing 155,115 + SoftEngine.js:50 drawing 155,44 + SoftEngine.js:50 drawing 84,44 + SoftEngine.js:50 drawing 76,123 + SoftEngine.js:50 drawing 84,115 + SoftEngine.js:50 drawing 163,36 + SoftEngine.js:50 drawing 76,36 + */ } void WireScene::tick(u16 keys) { - cube->rotate(pointZeroOne, pointZeroOne); + if(keys & KEY_START || keys & KEY_A) { + cube->rotate(5, 5); + } else if(keys & KEY_B) { + cube->resetRotation(); + } } diff --git a/engine/include/libgba-sprite-engine/gba_engine.h b/engine/include/libgba-sprite-engine/gba_engine.h index f3ee935..67d8116 100644 --- a/engine/include/libgba-sprite-engine/gba_engine.h +++ b/engine/include/libgba-sprite-engine/gba_engine.h @@ -32,6 +32,7 @@ private: Scene* sceneToTransitionTo; u16* vid_page; + MatrixFx projectionMatrix; static std::unique_ptr timer; static std::unique_ptr activeChannelA; @@ -50,7 +51,7 @@ private: void render(); void renderClear(); inline void plotPixel(int x, int y, u8 clrId); - inline VectorFx project(VectorFx coord, MatrixFx transMat); + inline VectorFx project(const VectorFx &coord, const MatrixFx &transMat); void flipPage(); public: diff --git a/engine/include/libgba-sprite-engine/math.h b/engine/include/libgba-sprite-engine/math.h index 25063c6..86b34b0 100644 --- a/engine/include/libgba-sprite-engine/math.h +++ b/engine/include/libgba-sprite-engine/math.h @@ -18,6 +18,7 @@ extern FIXED HALF; extern FIXED ONE; +extern FIXED TWO; #define FIX12_SHIFT 12 #define FIX12_SCALE ( 1< bresenhamLineTo(VECTOR dest); - VectorFx rotateAsCenter(VectorFx point, FIXED angle); // WHY all these inlines? performance reasons. inline static VectorFx up() { return VectorFx::fromInt(0, 1, 0); } diff --git a/engine/src/gba_engine.cpp b/engine/src/gba_engine.cpp index 80b59c9..944f74f 100644 --- a/engine/src/gba_engine.cpp +++ b/engine/src/gba_engine.cpp @@ -118,6 +118,7 @@ GBAEngine::GBAEngine() { REG_SNDDSCNT = 0; vid_page = vid_mem_back; + projectionMatrix = MatrixFx::perspectiveFovLH(float2fx(0.78), fxdiv(GBA_SCREEN_WIDTH_FX, GBA_SCREEN_HEIGHT_FX), float2fx(0.01), ONE); } void GBAEngine::update() { @@ -152,22 +153,19 @@ inline void GBAEngine::plotPixel(int x, int y, u8 clrId) { } } -inline VectorFx GBAEngine::project(VectorFx coord, MatrixFx transMat) { +inline VectorFx GBAEngine::project(const VectorFx &coord, const MatrixFx &transMat) { auto point = MatrixFx::transformCoordinates(coord, transMat); - auto x = fxmul(point.x(), GBA_SCREEN_WIDTH_FX) + fxdiv(GBA_SCREEN_WIDTH_FX, 2.0); - auto y = fxmul(-point.y(), GBA_SCREEN_HEIGHT_FX) + fxdiv(GBA_SCREEN_HEIGHT_FX, 2.0); + auto x = fxmul(point.x(), GBA_SCREEN_WIDTH_FX) + fxdiv(GBA_SCREEN_WIDTH_FX, TWO); + auto y = fxmul(-point.y(), GBA_SCREEN_HEIGHT_FX) + fxdiv(GBA_SCREEN_HEIGHT_FX, TWO); return VectorFx(x, y, 0); } // does the mesh rendering - to write mem before flipping void GBAEngine::render() { auto viewMatrix = MatrixFx::lookAtLH(currentCamera.getPosition(), currentCamera.getTarget(), VectorFx::up()); - // TODO float fxes and other stuff out of render loop? - auto projectionMatrix = MatrixFx::perspectiveFovLH(float2fx(0.78), fxdiv(GBA_SCREEN_WIDTH_FX, GBA_SCREEN_HEIGHT_FX), float2fx(0.01), ONE); for(auto& mesh :currentScene->meshes()) { - auto worldMatrix = MatrixFx::rotationYawPitchRoll(mesh->roty(), mesh->rotx(), mesh->rotz()) * MatrixFx::translation(mesh->position()); auto transformMatrix = worldMatrix * viewMatrix * projectionMatrix; @@ -176,6 +174,17 @@ void GBAEngine::render() { plotPixel(projectedPoint.x(), projectedPoint.y(), 1); } } + + /* + plotPixel(150, 40, 1); + plotPixel(60, 40, 1); + plotPixel(150, 140, 1); + plotPixel(93, 26, 1); + plotPixel(93, 115, 1); + plotPixel(172, 115, 1); + plotPixel(60, 140, 1); + plotPixel(93, 26, 1); + */ } void GBAEngine::renderClear() { diff --git a/engine/src/math.cpp b/engine/src/math.cpp index c5416b4..37a6966 100644 --- a/engine/src/math.cpp +++ b/engine/src/math.cpp @@ -6,3 +6,4 @@ FIXED HALF = float2fx(0.5); FIXED ONE = int2fx(1); +FIXED TWO = int2fx(2); diff --git a/engine/src/vectorfx.cpp b/engine/src/vectorfx.cpp index f3447ee..d1931c4 100644 --- a/engine/src/vectorfx.cpp +++ b/engine/src/vectorfx.cpp @@ -4,26 +4,6 @@ #include -VectorFx VectorFx::rotateAsCenter(VectorFx point, FIXED angle) { - auto center = this->v; - s32 centerx = center.x, centery = center.y; - s32 defaultx = point.x(), defaulty = point.y(); - - s32 cos = lu_cos(angle) >> 4; - s32 sin = lu_sin(angle) >> 4; - - // affine matriches are 8.8 fixed point numbers, so shift all input 8 spaces up and forth - // possibilities: instead of between [-1.0, 1.0] it's between [-256, +256] - // 90° rotation in inversed y-axis needs to flip sin sign - /* - return VectorFx({ - ( cos * (defaultx - centerx) + sin * (defaulty - centery) + (centerx << 8)) >> 8, - (-sin * (defaultx - centerx) + cos * (defaulty - centery) + (centery << 8)) >> 8}); - */ - return VectorFx({ - (fxmul(cos, (defaultx - centerx)) + fxmul(sin, (defaulty - centery)) + (centerx << 8)) >> 8, - (fxmul(-sin, (defaultx - centerx)) + fxmul(cos, (defaulty - centery) + (centery << 8))) >> 8}); -} std::deque VectorFx::bresenhamLineTo(VECTOR dest) { // https://www.coranac.com/tonc/text/bitmaps.htm - Bresenham's line algorithm with fixed points diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dd83357..7358567 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,6 +19,9 @@ SET(CMAKE_ASM_COMPILER gcc) SET(CMAKE_C_COMPILER gcc) SET(CMAKE_CXX_COMPILER g++) +# remove -03 optimization flag otherwise debugging will be annoying as hell +SET(CMAKE_CXX_FLAGS "-Wno-narrowing") + add_definitions(-DCODE_COMPILED_AS_PART_OF_TEST) include_directories(${GTEST_LIBRARY}/include) @@ -34,6 +37,7 @@ add_executable(unittest ../engine/src/palette/combined_palette.cpp ../engine/src/timer.cpp ../engine/src/math.cpp + ../engine/src/mesh.cpp ../engine/src/vectorfx.cpp timertest.cpp vectorfxtest.cpp matrixfxtest.cpp fixedpoinmathtest.cpp) diff --git a/test/matrixfxtest.cpp b/test/matrixfxtest.cpp index 33d0282..3a193b9 100644 --- a/test/matrixfxtest.cpp +++ b/test/matrixfxtest.cpp @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include class MatrixFxSuite : public ::testing::Test { protected: @@ -21,7 +24,7 @@ INLINE float rnd2(float val) { } -void assertMatrix(MatrixFx expected, MatrixFx actual) { +void assertMatrix(MatrixFx expected, MatrixFx actual, std::string matrixName) { for(int i = 0; i < MATRIX_DIMENSION; i++) { auto expect = expected.mAt(i); auto act = actual.mAt(i); @@ -30,7 +33,82 @@ void assertMatrix(MatrixFx expected, MatrixFx actual) { float expectFl = rnd2(fx2float(expect)); float actFl = rnd2(fx2float(act)); - ASSERT_EQ(expectFl, actFl) << "M[" << i << "] does not match: (exp, act) " << expect << ", " << act << " - floats: " << expectFl << ", " << actFl; + ASSERT_EQ(expectFl, actFl) << matrixName << "[" << i << "] does not match: (exp, act) " << expect << ", " << act << " - floats: " << expectFl << ", " << actFl; + } +} + +TEST_F(MatrixFxSuite, RotationMatriches) { + auto result = MatrixFx::rotationZ(0); + auto expectedIdMatrix = MatrixFx(float2fx(1.0), 0, 0, 0, 0, float2fx(1.0), 0, 0, 0, 0, float2fx(1.0), 0, 0, 0, 0, float2fx(1.0)); + assertMatrix(expectedIdMatrix, result, "rotz"); +} + +TEST_F(MatrixFxSuite, RotationYawPitchRoll) { + auto expectedIdMatrix = MatrixFx(float2fx(1.0), 0, 0, 0, 0, float2fx(1.0), 0, 0, 0, 0, float2fx(1.0), 0, 0, 0, 0, float2fx(1.0)); + auto rotYwaPitchRoll = MatrixFx::rotationYawPitchRoll(0, 0, 0); + assertMatrix(expectedIdMatrix, rotYwaPitchRoll, "rotywa"); +} + +TEST_F(MatrixFxSuite, MeshToTransformMatrix_IntegrationTest) { + // source: + Mesh cube; + cube.add(VectorFx(-1, 1, 1)); + cube.add(VectorFx(1, 1, 1)); + cube.add(VectorFx(-1, -1, 1)); + cube.add(VectorFx(-1, -1, -1)); + cube.add(VectorFx(-1, 1, -1)); + cube.add(VectorFx(1, 1, -1)); + cube.add(VectorFx(1, -1, 1)); + cube.add(VectorFx(-1, -1, -1)); + + auto currentCamera = Camera(VectorFx::fromInt(0, 0, 10), VectorFx::fromInt(0, 0, 0)); + auto viewMatrix = MatrixFx::lookAtLH(currentCamera.getPosition(), currentCamera.getTarget(), VectorFx::up()); + + auto projectionMatrix = MatrixFx::perspectiveFovLH(float2fx(0.78), fxdiv(GBA_SCREEN_WIDTH_FX, GBA_SCREEN_HEIGHT_FX), float2fx(0.01), ONE); + auto expectedProjectionMatrix = MatrixFx(float2fx(1.64f), 0, 0, 0, 0, float2fx(2.55f), 0, 0, 0, 0, float2fx(1.05), float2fx(1.05), 0, 0, float2fx(-0.05), 0); + assertMatrix(expectedProjectionMatrix, projectionMatrix, "project"); + + auto expectedIdMatrix = MatrixFx(float2fx(1.0), 0, 0, 0, 0, float2fx(1.0), 0, 0, 0, 0, float2fx(1.0), 0, 0, 0, 0, float2fx(1.0)); + auto rotYwaPitchRoll = MatrixFx::rotationYawPitchRoll(cube.roty(), cube.rotx(), cube.rotz()); + assertMatrix(expectedIdMatrix, rotYwaPitchRoll, "rotywa"); + auto translatedPos = MatrixFx::translation(cube.position()); + assertMatrix(expectedIdMatrix, translatedPos, "translpos"); + + auto worldMatrix = rotYwaPitchRoll * translatedPos; + assertMatrix(expectedIdMatrix, worldMatrix, "worldmatrix"); + + auto transformMatrix = worldMatrix * viewMatrix * projectionMatrix; + auto expectedTransformMatrix = MatrixFx(float2fx(-1.67), 0, 0, 0, 0, float2fx(2.55), 0, 0, 0, 0, float2fx(-1.0), float2fx(-1.0), 0, 0, float2fx(9.85), float2fx(9.75)); + assertMatrix(expectedTransformMatrix, transformMatrix, "transfomatrix"); + + auto coord = *cube.vertices()[0].get(); + auto point = MatrixFx::transformCoordinates(coord, transformMatrix); + ASSERT_EQ(fx2float(point.x()), 0.125f); + ASSERT_EQ(fx2float(point.y()), 0.25f); + ASSERT_EQ(fx2float(point.z()), 1.00f); + + auto x = fxmul(point.x(), GBA_SCREEN_WIDTH_FX) + fxdiv(GBA_SCREEN_WIDTH_FX, int2fx(2)); + auto y = fxmul(-point.y(), GBA_SCREEN_HEIGHT_FX) + fxdiv(GBA_SCREEN_HEIGHT_FX, int2fx(2)); + ASSERT_EQ(fx2float(x), 150); + ASSERT_EQ(fx2float(y), 40); + + // dest in Babylon - dest according to for loop below (should print something roughly similar) + /* + * 163, 36 - 150,40 + * 76, 36 - 60,40 + * 163, 123 - 150,140 + * 155, 115 - 93,26 + * 155, 44 - 93,115 + * 84, 44 - 172,115 + * 76, 123 - 60,140 + * 84, 115 - 93,26 + * + */ + for(auto& vertex : cube.vertices()) { + auto point = MatrixFx::transformCoordinates(*vertex.get(), transformMatrix); + auto x = fxmul(point.x(), GBA_SCREEN_WIDTH_FX) + fxdiv(GBA_SCREEN_WIDTH_FX, int2fx(2)); + auto y = fxmul(-point.y(), GBA_SCREEN_HEIGHT_FX) + fxdiv(GBA_SCREEN_HEIGHT_FX, int2fx(2)); + std::cout << "plotting (" << fx2int(x) << "," << fx2int(y) << ")" << std::endl; } } @@ -64,7 +142,7 @@ TEST_F(MatrixFxSuite, lookAtLH_TestData) { auto result = MatrixFx::lookAtLH(eye, target, up); auto expected = MatrixFx(-257, 0, 0, 0, 0, 256, 0, 0, 0, 0, -250, 0, 0, 0, 2500, 256); - assertMatrix(expected, result); + assertMatrix(expected, result, "M"); } @@ -92,5 +170,5 @@ TEST_F(MatrixFxSuite, PerspectiveFovLH_TestData) { auto result = MatrixFx::perspectiveFovLH(float2fx(0.78), float2fx(1.6), float2fx(0.01), float2fx(1)); auto expected = MatrixFx::fromFloat(1.565f, 0, 0, 0, 0, 2.505f, 0, 0, 0, 0, 1.005f, 1, 0, 0, -0.005f, 0); - assertMatrix(expected, result); + assertMatrix(expected, result, "M"); } diff --git a/test/vectorfxtest.cpp b/test/vectorfxtest.cpp index 34d6ecf..33227ec 100644 --- a/test/vectorfxtest.cpp +++ b/test/vectorfxtest.cpp @@ -144,42 +144,6 @@ TEST_F(VectorFxSuite, ScaleAVector) { ASSERT_EQ(result.z(), 6); } -uint grad2rad(uint grad) { - //return float2fx(grad*M_PI/180); - return fxdiv(fxmul(int2fx(grad), float2fx(M_PI)), int2fx(180)); -} - -TEST_F(VectorFxSuite, Rotate_FromBottomHalf_0_Degrees) { - auto bottomHalf = VectorFx::fromInt(120, 200, 0); - auto vector = VectorFx::fromInt(120, 80, 0); - auto result = vector.rotateAsCenter(bottomHalf, grad2rad(0)).toInt(); - ASSERT_EQ(120, result.x()); - ASSERT_EQ(200, result.y()); -} - -TEST_F(VectorFxSuite, Rotate_FromBottomHalf_90_Degrees) { - auto bottomHalf = VectorFx::fromInt(120, 200, 0); - auto vector = VectorFx::fromInt(120, 80, 0); - auto result = vector.rotateAsCenter(bottomHalf, grad2rad(90)).toInt(); - ASSERT_EQ(240, result.x()); - ASSERT_EQ(80, result.y()); -} - -TEST_F(VectorFxSuite, Rotate_FromBottomHalf_180_Degrees) { - auto bottomHalf = VectorFx::fromInt(120, 200, 0); - auto vector = VectorFx::fromInt(120, 80, 0); - auto result = vector.rotateAsCenter(bottomHalf, grad2rad(180)).toInt(); - ASSERT_EQ(120, result.x()); - ASSERT_EQ(-40, result.y()); -} - -TEST_F(VectorFxSuite, Rotate_FromBottomHalf_270_Degrees) { - auto bottomHalf = VectorFx::fromInt(120, 200, 0); - auto vector = VectorFx::fromInt(120, 80, 0); - auto result = vector.rotateAsCenter(bottomHalf, grad2rad(270)).toInt(); - ASSERT_EQ(0, result.x()); - ASSERT_EQ(80, result.y()); -} // ---- // TEST_F(VectorFxSuite, ToString) {