diff --git a/demos/demo2-blender-import/src/main.cpp b/demos/demo2-blender-import/src/main.cpp index de75970..84c231d 100644 --- a/demos/demo2-blender-import/src/main.cpp +++ b/demos/demo2-blender-import/src/main.cpp @@ -10,7 +10,7 @@ int main() { std::shared_ptr engine(new GBAEngine()); - engine.get()->setRenderer(new WiredRenderer()); + engine.get()->setRenderer(new RasterizerRenderer()); MonkeyScene* startScene = new MonkeyScene(engine); engine->setScene(startScene); diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index e890e9b..1bad965 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -23,7 +23,7 @@ add_library(${PROJECT_NAME} src/renderer/pixelrenderer.cpp src/renderer/wirerenderer.cpp src/math.cpp - src/sound_control.cpp src/scene.cpp src/timer.cpp src/vectorfx.cpp src/mesh.cpp) + src/sound_control.cpp src/scene.cpp src/timer.cpp src/vectorfx.cpp src/mesh.cpp src/renderer/raserizerrenderer.cpp) target_include_directories(${PROJECT_NAME} PUBLIC $ diff --git a/engine/include/libgba-sprite-engine/math.h b/engine/include/libgba-sprite-engine/math.h index a8616e2..6bbedec 100644 --- a/engine/include/libgba-sprite-engine/math.h +++ b/engine/include/libgba-sprite-engine/math.h @@ -8,9 +8,9 @@ #include #ifdef CODE_COMPILED_AS_PART_OF_TEST #include - #else +#else #include - #endif +#endif #include #include @@ -37,8 +37,13 @@ INLINE FIXED fxsin(FIXED fxrad); INLINE FIXED fxcos(FIXED fxrad); INLINE float rnd(float val); INLINE std::string fstr(FIXED which); +INLINE FIXED interpolate(FIXED min, FIXED max, FIXED gradient); // ---- impl +INLINE FIXED interpolate(FIXED min, FIXED max, FIXED gradient) { + return min + fxmul((max - min), CLAMP(gradient, 0, ONE)); +} + INLINE float rnd(float val) { return (float) ((std::floor(val * 100) + .5) / 100); } diff --git a/engine/include/libgba-sprite-engine/renderer/gba_engine.h b/engine/include/libgba-sprite-engine/renderer/gba_engine.h index 07088d1..2e3f2ac 100644 --- a/engine/include/libgba-sprite-engine/renderer/gba_engine.h +++ b/engine/include/libgba-sprite-engine/renderer/gba_engine.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include "libgba-sprite-engine/scene.h" @@ -71,10 +70,11 @@ public: void setRenderer(Renderer* r) { renderer = std::unique_ptr(r); }; - VectorPx project(const VectorFx &coord, const MatrixFx &transMat); + VectorFx project(const VectorFx &coord, const MatrixFx &transMat); void plotPixel(int x, int y, u8 clrId); - void plotPixel(const VectorPx &pixel, u8 clrId); - void plotLine(const VectorPx &point0, const VectorPx &point1, u8 clrId); + void plotPixel(const VectorFx &pixel, u8 clrId); + void plotLine(const VectorFx &point0, const VectorFx &point1, u8 clrId); + void plotLine(int p0x, int p0y, int p1x, int p1y, u8 clrId); }; diff --git a/engine/include/libgba-sprite-engine/renderer/renderer.h b/engine/include/libgba-sprite-engine/renderer/renderer.h index 66856e3..2d1f7a8 100644 --- a/engine/include/libgba-sprite-engine/renderer/renderer.h +++ b/engine/include/libgba-sprite-engine/renderer/renderer.h @@ -21,13 +21,20 @@ public: class PixelRenderer : public Renderer { public: void render(const MatrixFx &transformationMatrix, const Mesh* mesh) override; - }; class WiredRenderer : public Renderer { public: void render(const MatrixFx &transformationMatrix, const Mesh* mesh) override; +}; +class RasterizerRenderer : public Renderer { +private: + void plotTriangle(const VectorFx& p1, const VectorFx& p2, const VectorFx& p3, COLOR color); + void processScanLine(FIXED y, const VectorFx& pa, const VectorFx& pb, const VectorFx& pc, const VectorFx& pd, COLOR color); + +public: + void render(const MatrixFx &transformationMatrix, const Mesh* mesh) override; }; #endif //GBA_BITMAP_ENGINE_PROJECT_RENDERER_H diff --git a/engine/include/libgba-sprite-engine/vectorfx.h b/engine/include/libgba-sprite-engine/vectorfx.h index 95becc2..e7bdc36 100644 --- a/engine/include/libgba-sprite-engine/vectorfx.h +++ b/engine/include/libgba-sprite-engine/vectorfx.h @@ -88,6 +88,20 @@ public: v.z = fxmul(v.z, num); } + inline static FIXED gradient(FIXED y, const VectorFx& pa, const VectorFx &pb) { + if(pa.y() == pb.y()) { + return int2fx(1); + } + return fxdiv((y - pa.y()), (pb.y() - pa.y())); + } + + inline static FIXED slope(const VectorFx &p1, const VectorFx &p2) { + if(p2.y() - p1.y() > 0) { + return fxdiv(p2.x() - p1.x(), p2.y() - p1.y()); + } + return 0; + } + inline FIXED x() const { return v.x; } inline float floatX() const { return fx2float(v.x); } inline void setX(FIXED x) { v.x = x; } diff --git a/engine/include/libgba-sprite-engine/vectorpx.h b/engine/include/libgba-sprite-engine/vectorpx.h deleted file mode 100644 index 4fecd3b..0000000 --- a/engine/include/libgba-sprite-engine/vectorpx.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Created by Wouter Groeneveld on 10/07/20. -// - -#ifndef GBA_BITMAP_ENGINE_PROJECT_VECTOR2_H -#define GBA_BITMAP_ENGINE_PROJECT_VECTOR2_H - -// a 2D vector with pixel-based ints - -class VectorPx { -private: - int v_x, v_y; - -public: - VectorPx(int theX, int theY) : v_x(theX), v_y(theY) {} - - static inline VectorPx fromFx(FIXED x, FIXED y) { return VectorPx(fx2int(x), fx2int(y)); } - - inline int x() const { return v_x; } - inline int y() const { return v_y; } -}; - -#endif //GBA_BITMAP_ENGINE_PROJECT_VECTOR2_H diff --git a/engine/src/gba/tonc_bmp8.cpp b/engine/src/gba/tonc_bmp8.cpp index 4daaa07..e3891ba 100644 --- a/engine/src/gba/tonc_bmp8.cpp +++ b/engine/src/gba/tonc_bmp8.cpp @@ -163,7 +163,7 @@ void bmp8_line(int x1, int y1, int x2, int y2, u32 clr, // with mask-flips // NOTE: (mask>>31) is equivalent to (x&1) ? 0 : 1 - if(dx>=dy) // Diagonal, slope <= 1 + if(dx>=dy) // Diagonal, slopeFx <= 1 { dd= 2*dy - dx; @@ -180,7 +180,7 @@ void bmp8_line(int x1, int y1, int x2, int y2, u32 clr, mask = ~mask; } } - else // # Diagonal, slope > 1 + else // # Diagonal, slopeFx > 1 { dd= 2*dx - dy; diff --git a/engine/src/renderer/gba_engine.cpp b/engine/src/renderer/gba_engine.cpp index a81bc61..7cdce11 100644 --- a/engine/src/renderer/gba_engine.cpp +++ b/engine/src/renderer/gba_engine.cpp @@ -162,21 +162,29 @@ void GBAEngine::flipPage() { REG_DISPCNT ^= DCNT_PAGE; } -void GBAEngine::plotPixel(const VectorPx &pixel, u8 clrId) { - m4_plot(pixel.x(), pixel.y(), clrId); +void GBAEngine::plotPixel(int x, int y, u8 clrId) { + m4_plot(x, y, clrId); } -void GBAEngine::plotLine(const VectorPx &point0, const VectorPx &point1, u8 clrId) { - // uses tonc's optimalization tricks to get 10 FPS extra compared to standard bline algorithms - m4_line(point0.x(), point0.y(), point1.x(), point1.y(), clrId); +void GBAEngine::plotPixel(const VectorFx &pixel, u8 clrId) { + plotPixel(fx2int(pixel.x()), fx2int(pixel.y()), clrId); } -VectorPx GBAEngine::project(const VectorFx &coord, const MatrixFx &transMat) { +// uses tonc's optimalization tricks to get 10 FPS extra compared to standard bline algorithms +void GBAEngine::plotLine(int p0x, int p0y, int p1x, int p1y, u8 clrId) { + m4_line(p0x, p0y, p1x, p1y, clrId); +} + +void GBAEngine::plotLine(const VectorFx &point0, const VectorFx &point1, u8 clrId) { + plotLine(fx2int(point0.x()), fx2int(point0.y()), fx2int(point1.x()), fx2int(point1.y()), clrId); +} + +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, TWO); auto y = fxmul(-point.y(), GBA_SCREEN_HEIGHT_FX) + fxdiv(GBA_SCREEN_HEIGHT_FX, TWO); - return VectorPx::fromFx(x, y); + return VectorFx(x, y, point.z()); } // does the mesh rendering - to write mem before flipping diff --git a/engine/src/renderer/raserizerrenderer.cpp b/engine/src/renderer/raserizerrenderer.cpp new file mode 100644 index 0000000..fc1d202 --- /dev/null +++ b/engine/src/renderer/raserizerrenderer.cpp @@ -0,0 +1,82 @@ +// +// Created by Wouter Groeneveld on 11/07/20. +// + +#include +#include +#include +#include + +// drawing line between 2 points from left to right +// papb -> pcpd +// pa, pb, pc, pd must then be sorted before +void RasterizerRenderer::processScanLine(FIXED y, const VectorFx& pa, const VectorFx& pb, const VectorFx& pc, const VectorFx& pd, COLOR color) { + FIXED gradient1 = VectorFx::gradient(y, pa, pb), gradient2 = VectorFx::gradient(y, pc, pd); + + int sx = fx2int(interpolate(pa.x(), pb.x(), gradient1)); + int ex = fx2int(interpolate(pc.x(), pd.x(), gradient2)); + int yy = fx2int(y); + + engine.get()->plotLine(sx, yy, ex, yy, color); +} + +void RasterizerRenderer::plotTriangle(const VectorFx& pt1, const VectorFx& pt2, const VectorFx& pt3, COLOR color) { + // Sorting the points in order to always have this order on screen p1, p2 & p3 + // with p1 always up (thus having the Y the lowest possible to be near the top screen) + // then p2 between p1 & p3 + auto p1 = &pt1, p2 = &pt2, p3 = &pt3; + + if (p1->y() > p2->y()) { + auto temp = p2; + p2 = p1; + p1 = temp; + } + + if (p2->y() > p3->y()) { + auto temp = p2; + p2 = p3; + p3 = temp; + } + + if (p1->y() > p2->y()) { + auto temp = p2; + p2 = p1; + p1 = temp; + } + + FIXED dP1P2 = VectorFx::slope(*p1, *p2), dP1P3 = VectorFx::slope(*p1, *p3); + if (dP1P2 > dP1P3) { + for (auto y = p1->y(); y <= p3->y(); y+=ONE) { + if (y < p2->y()) { + processScanLine(y, *p1, *p3, *p1, *p2, color); + } else { + processScanLine(y, *p1, *p3, *p2, *p3, color); + } + } + } else { + for (auto y = p1->y(); y <= p3->y(); y+=ONE) { + if (y < p2->y()) { + processScanLine(y, *p1, *p2, *p1, *p3, color); + } else { + processScanLine(y, *p2, *p3, *p1, *p3, color); + } + } + } +} + +void RasterizerRenderer::render(const MatrixFx &transformationMatrix, const Mesh *mesh) { + bool colorSwitch = false; + for (auto &face : mesh->faces()) { + auto &vertexA = mesh->vertices()[face.a]; + auto &vertexB = mesh->vertices()[face.b]; + auto &vertexC = mesh->vertices()[face.c]; + + auto pixelA = engine->project(*vertexA.get(), transformationMatrix); + auto pixelB = engine->project(*vertexB.get(), transformationMatrix); + auto pixelC = engine->project(*vertexC.get(), transformationMatrix); + + plotTriangle(pixelA, pixelB, pixelC, colorSwitch ? 2 : 1); + colorSwitch = !colorSwitch; + } + +} \ No newline at end of file diff --git a/engine/src/vectorfx.cpp b/engine/src/vectorfx.cpp index d1931c4..da26c92 100644 --- a/engine/src/vectorfx.cpp +++ b/engine/src/vectorfx.cpp @@ -42,7 +42,7 @@ std::deque VectorFx::bresenhamLineTo(VECTOR dest) { y += step.y; } } else if(delta.x >= delta.y) { - // Diagonal, slope <= 1 + // Diagonal, slopeFx <= 1 dd = 2 * delta.y - delta.x; for(ii = 0; ii <= delta.x; ii++) { @@ -55,7 +55,7 @@ std::deque VectorFx::bresenhamLineTo(VECTOR dest) { x += step.x; } } else { - // Diagonal, slope > 1 + // Diagonal, slopeFx > 1 dd = 2 * delta.x - delta.y; for(ii = 0; ii <= delta.y; ii++) { diff --git a/test/fixedpoinmathtest.cpp b/test/fixedpoinmathtest.cpp index 2e5a5d5..fa69c02 100644 --- a/test/fixedpoinmathtest.cpp +++ b/test/fixedpoinmathtest.cpp @@ -17,6 +17,11 @@ protected: } }; +TEST_F(FpSuite, InterpolateTests) { + ASSERT_FLOAT_EQ(fx2float(interpolate(ONE, int2fx(10), float2fx(0))), 1); + ASSERT_FLOAT_EQ(fx2float(interpolate(ONE, int2fx(10), ONE)), 9.9648438); + ASSERT_FLOAT_EQ(fx2float(interpolate(ONE, int2fx(10), float2fx(0.1))), 1.8789062); +} // from LUT doxygen: //! Look-up a cosine value (2π = 0x10000) diff --git a/test/matrixfxtest.cpp b/test/matrixfxtest.cpp index bc33c02..ba40910 100644 --- a/test/matrixfxtest.cpp +++ b/test/matrixfxtest.cpp @@ -9,6 +9,7 @@ #include #include #include +#include class MatrixFxSuite : public ::testing::Test { protected: diff --git a/test/vectorfxtest.cpp b/test/vectorfxtest.cpp index 33227ec..2577a10 100644 --- a/test/vectorfxtest.cpp +++ b/test/vectorfxtest.cpp @@ -34,6 +34,49 @@ void assertVector(VectorFx expected, VectorFx actual) { ASSERT_EQ(rnd2(fx2float(actual.z())), rnd2(fx2float(actual.z()))) << "z incorrect: (act, exp) " << str; } +TEST_F(VectorFxSuite, GradientTest) { + int y = int2fx(88); + /* + * pa: + * x: 202.9834560692982 +y: 84.93338221282914 +z: 1.0091180828593247 + pb: + x: x: 211.95655997027734 +y: 104.46714141689979 +z: 1.0090892519150232 + gradient: 0.1569906619168215 + sx: 204 + */ + auto pa = VectorFx::fromInt(203, 85, 1), pb = VectorFx::fromInt(212, 104, 1); + auto gradient = VectorFx::gradient(y, pa, pb); + ASSERT_FLOAT_EQ(fx2float(gradient), 0.15625); +} + +TEST_F(VectorFxSuite, GradientThenInterpolateTest) { + int y = int2fx(88); + auto pa = VectorFx::fromInt(203, 85, 1), pb = VectorFx::fromInt(212, 104, 1); + + auto gradient = VectorFx::gradient(y, pa, pb); + auto interpolated = interpolate(pa.x(), pb.x(), gradient); + ASSERT_EQ(fx2int(interpolated), 204); +} + +TEST_F(VectorFxSuite, ComputeSlope) { + /* p2: + * x: 210.58517158313248 + y: 88.7224853699867 + z: 1.0090972814678028 + p1: + x: 202.9834560692982 + y: 84.93338221282914 + z: 1.0091180828593247 + */ + auto p2 = VectorFx::fromInt(210, 88, 1), p1 = VectorFx::fromInt(203, 85, 1); + auto result = VectorFx::slope(p1, p2); + ASSERT_FLOAT_EQ(fx2float(result), 2.3320312); +} + TEST_F(VectorFxSuite, ToAndFromFixedIntWorks) { auto zAxis = VectorFx::fromInt(0, 0, -10); ASSERT_EQ(zAxis.y(), 0);