diff --git a/Engine/Graphics/3DRenderer.cpp b/Engine/Graphics/3DRenderer.cpp index db5ef86..6851577 100644 --- a/Engine/Graphics/3DRenderer.cpp +++ b/Engine/Graphics/3DRenderer.cpp @@ -5,8 +5,11 @@ #include "../World/DbgCube.hpp" #include "../World/Tank.hpp" +#define SF_COLOR_4CHEX(h) sf::Color((uint32_t)h) + //#define DISABLE_AABB_CLIPPING //#define DISABLE_TRIANGLE_CLIPPING +//#define DISABLE_WIREFRAME_MODE // Rendering pipeline: @@ -58,7 +61,7 @@ Graphic3DRenderer::~Graphic3DRenderer() {} void Graphic3DRenderer::SetRTSize(unsigned int w, unsigned int h) { mRTSize.x = w; mRTSize.y = h; - mMainCamera->SetFrustrum(90.0f, mRTSize.x/mRTSize.y, 1.0f, 100.f); + mMainCamera->SetFrustrum(75.0f, mRTSize.x/mRTSize.y, 1.0f, 100.f); } void Graphic3DRenderer::UpdateCamera(CAMERA_MOVE type, const float value) { @@ -107,13 +110,16 @@ void Graphic3DRenderer::Draw(sf::RenderTexture& context) { M3D_BoundingFrustum camFrustrum(projMat, false); camFrustrum.Transform(camFrustrum, invViewMat); + const float sgRatio = ComputeSGRatio(); // -= Draw the sky =- // To avoid unfilled pixels on screen, the "sky-plane" will be rendered // all over the screen. // It's will be useless to use and compute a specific rectangle from the // size of the screen! // The sky have an infinite z-depth (any objects will be rendered over). - context.clear(sf::Color::Cyan); +#ifdef DISABLE_WIREFRAME_MODE + context.clear(SF_COLOR_4CHEX(0x00B5E2FF)); +#endif // -= Draw the ground =- // A simple rectangle shape is used to draw the ground over the sky-plane. @@ -121,17 +127,26 @@ void Graphic3DRenderer::Draw(sf::RenderTexture& context) { // Depending of the camera pitch, the ratio sky/ground on screen vary. // Like the sky, the ground have an infinite z-depth (any objects will // be rendered over). - const float sgRatio = ComputeSGRatio(); +#ifdef DISABLE_WIREFRAME_MODE sf::RectangleShape gndRect; if (mMainCamera->GetPos3f().y >= 0) { gndRect.setSize(sf::Vector2f(mRTSize.x, mRTSize.y * sgRatio)); - gndRect.setPosition(sf::Vector2f(0, mRTSize.y * (1.f - sgRatio))); + gndRect.setPosition(sf::Vector2f(0, mRTSize.y * (1.f - sgRatio) - 1)); } else { gndRect.setSize(sf::Vector2f(mRTSize.x, mRTSize.y * (1.f - sgRatio))); gndRect.setPosition(sf::Vector2f(0, 0)); } - gndRect.setFillColor(sf::Color::Green); + gndRect.setFillColor(SF_COLOR_4CHEX(0x009A17FF)); + //gndRect.setFillColor(SF_COLOR_4CHEX(0xD5C2A5FF)); context.draw(gndRect, sRS); +#else + sf::Vertex gndLine[2]; + gndLine[0].position = sf::Vector2f(0, mRTSize.y * (1.f - sgRatio)); + gndLine[0].color = sf::Color::White; + gndLine[1].position = sf::Vector2f(mRTSize.x - 1, mRTSize.y * (1.f - sgRatio)); + gndLine[1].color = sf::Color::White; + context.draw(gndLine, 2, sf::Lines); +#endif // Process scene's objects for (auto& obj : mRenderList) { @@ -209,8 +224,11 @@ void Graphic3DRenderer::Draw(sf::RenderTexture& context) { v_tri[1].position = sf::Vector2f(M3D_V4GetX(V2), M3D_V4GetY(V2)); v_tri[2].position = sf::Vector2f(M3D_V4GetX(V3), M3D_V4GetY(V3)); v_tri[3] = v_tri[0]; - //context.draw(v_tri, 4, sf::LineStrip, sRS); - context.draw(v_tri, 3, sf::Triangles, sRS); +#ifdef DISABLE_WIREFRAME_MODE + context.draw(v_tri, 4, sf::Triangles, sRS); +#else + context.draw(v_tri, 4, sf::LineStrip, sRS); +#endif } } } @@ -233,21 +251,41 @@ void Graphic3DRenderer::UpdateInternalTestObjects() { } // Compute the screen ratio between the ground and the sky (aka. Line of Horizon) -inline float Graphic3DRenderer::ComputeSGRatio() { - // FoV angle for Y axis is recovered using frustrum FoV and apply RT screen ratio to it - const float fovYAngleDiv2 = M3D_Deg2Rad(mMainCamera->GetFoV()) * 0.5f; - // Get the camera pitch angle over camera FoV ratio - const float theta = M3D_ScalarASinEst(-mMainCamera->GetLook3f().y); - // Get the camera altitude from the ground - const float altitude = mMainCamera->GetPos3f().y; +float Graphic3DRenderer::ComputeSGRatio() { + static double sgRatio = 0.5f; + static float fovCos = 0.f; + static float fovSin = 0.f; + static float thetaCos = 0.f; + static float fovThetaSin = 0.f; + const bool isCamMoved = mMainCamera->IsCameraMoved(); + const bool isFUpdated = mMainCamera->IsFrustrumUpdated(); + if (isCamMoved || isFUpdated) { + mMainCamera->ResetUpdateFlags(); + + // FoV angle for Y axis is recovered using frustrum FoV and apply RT screen ratio to it + const float fovYAngleDiv2 = M3D_Deg2Rad(mMainCamera->GetFoV()) * 0.5f; + // Get the camera pitch angle over camera FoV ratio + const float theta = M3D_ScalarASinEst(-mMainCamera->GetLook3f().y); + // Get the camera altitude from the ground + const float altitude = mMainCamera->GetPos3f().y; + + fovThetaSin = M3D_ScalarSinEst(fovYAngleDiv2 + theta); + if (isCamMoved) + thetaCos = M3D_ScalarCosEst(theta); + + if (isFUpdated) { + fovCos = M3D_ScalarCosEst(fovYAngleDiv2); + fovSin = M3D_ScalarSinEst(fovYAngleDiv2); + } - // Ground/Sky screen ratio calculation using "simple" trigonometric properties of the - // pinhole (frustrum) camera model. - // The triangle made by the ground plane intersection with the frustum. This intersection - // cross the far plane at some point. Instead of computing the coordinate of the point, we - // directly use the far plane length to get the corresponding ratio for the screen. - double sgRatio = -(altitude * M3D_ScalarCosEst(fovYAngleDiv2) - mMainCamera->GetFarZ() * M3D_ScalarSinEst(fovYAngleDiv2 + theta)) - / (2.f * mMainCamera->GetFarZ() * M3D_ScalarSinEst(fovYAngleDiv2) * M3D_ScalarCosEst(theta)); + // Ground/Sky screen ratio calculation using "simple" trigonometric properties of the + // pinhole (frustrum) camera model. + // The triangle made by the ground plane intersection with the frustum. This intersection + // cross the far plane at some point. Instead of computing the coordinate of the point, we + // directly use the far plane length to get the corresponding ratio for the screen. + sgRatio = -(altitude * fovCos - mMainCamera->GetFarZ() * fovThetaSin) + / (2.f * mMainCamera->GetFarZ() * fovSin * thetaCos); + } // Clamp if (sgRatio > 1.f) diff --git a/Engine/Graphics/Camera.cpp b/Engine/Graphics/Camera.cpp index 3cd6a2c..86716ff 100644 --- a/Engine/Graphics/Camera.cpp +++ b/Engine/Graphics/Camera.cpp @@ -11,6 +11,7 @@ void Camera::SetFrustrum(float fov, float r, float zn, float zf) { this->zf = zf; M3D_MATRIX pMat = M3D_TransformMatrixFrustrumFovLH(M3D_Deg2Rad(fov), r, zn, zf); M3D_V4StoreF4x4(&mProjMat, pMat); + frustrumUpdated = true; } void Camera::UpdateCamView() { @@ -19,6 +20,7 @@ void Camera::UpdateCamView() { M3D_VECTOR U = M3D_V4LoadF3(&mUp); M3D_V4StoreF4x4(&mViewMat, M3D_TransformMatrixCamLookToLH(P, L, U)); + camMoved = true; } void Camera::LookAt(M3D_VECTOR pos, M3D_VECTOR target, M3D_VECTOR worldUp) { diff --git a/Engine/Graphics/Camera.hpp b/Engine/Graphics/Camera.hpp index 6d83e61..7f3f2d1 100644 --- a/Engine/Graphics/Camera.hpp +++ b/Engine/Graphics/Camera.hpp @@ -15,6 +15,8 @@ public: float GetFoV() const { return fov; } float GetFarZ() const { return zf; } + const bool IsFrustrumUpdated() const { return frustrumUpdated; } + const bool IsCameraMoved() const { return camMoved; } M3D_VECTOR GetPos() const { return M3D_V4LoadF3(&mPos); } M3D_F3 GetPos3f() const { return mPos; } M3D_VECTOR GetLook() const { return M3D_V4LoadF3(&mLook); } @@ -39,10 +41,15 @@ public: void Pitch(float angle); void Yaw(float angle); + void ResetUpdateFlags() { frustrumUpdated = false; camMoved = false;} //TODO: need a proper way to manage this flags + private: float fov; // It's the Y-FoV! float zf; + bool frustrumUpdated = true; + bool camMoved = true; + M3D_F4X4 mProjMat = M3D_MIdentity4x4(); M3D_F4X4 mViewMat = M3D_MIdentity4x4(); diff --git a/Engine/Utils/3DMaths.hpp b/Engine/Utils/3DMaths.hpp index b2a2e21..74d0334 100644 --- a/Engine/Utils/3DMaths.hpp +++ b/Engine/Utils/3DMaths.hpp @@ -605,6 +605,7 @@ float M3D_ScalarASin(float Value) noexcept; float M3D_ScalarASinEst(float Value) noexcept; float M3D_ScalarACos(float Value) noexcept; float M3D_ScalarACosEst(float Value) noexcept; +void M3D_ScalarSinCos(float* pSin, float* pCos, float Value) noexcept; // // Common values for vector/matrix manipulation diff --git a/Engine/Utils/3DMaths_sc.inl b/Engine/Utils/3DMaths_sc.inl index 5f4e02e..9fd1466 100644 --- a/Engine/Utils/3DMaths_sc.inl +++ b/Engine/Utils/3DMaths_sc.inl @@ -163,4 +163,36 @@ inline float M3D_ScalarACosEst(float Value) noexcept { // acos(x) = pi - acos(-x) when x < 0 return (nonnegative ? result : M3D_PI - result); -} \ No newline at end of file +} + +inline void M3D_ScalarSinCos(float* pSin, float* pCos, float Value) noexcept { + // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. + float quotient = M3D_1DIV2PI * Value; + if (Value >= 0.0f) + quotient = static_cast(static_cast(quotient + 0.5f)); + else + quotient = static_cast(static_cast(quotient - 0.5f)); + + float y = Value - M3D_2PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(Value). + float sign; + if (y > M3D_PIDIV2) { + y = M3D_PI - y; + sign = -1.0f; + } else if (y < -M3D_PIDIV2) { + y = -M3D_PI - y; + sign = -1.0f; + } else { + sign = +1.0f; + } + + float y2 = y * y; + + // 11-degree minimax approximation + *pSin = (((((-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f) * y2 + 0.0083333310f) * y2 - 0.16666667f) * y2 + 1.0f) * y; + + // 10-degree minimax approximation + float p = ((((-2.6051615e-07f * y2 + 2.4760495e-05f) * y2 - 0.0013888378f) * y2 + 0.041666638f) * y2 - 0.5f) * y2 + 1.0f; + *pCos = sign * p; +} diff --git a/Engine/Utils/3DMaths_vec.inl b/Engine/Utils/3DMaths_vec.inl index ba107a7..24bcb1d 100644 --- a/Engine/Utils/3DMaths_vec.inl +++ b/Engine/Utils/3DMaths_vec.inl @@ -1,38 +1,6 @@ #pragma once -inline void M3D_ScalarSinCos(float* pSin, float* pCos, float Value) noexcept { - // Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder. - float quotient = M3D_1DIV2PI * Value; - if (Value >= 0.0f) - quotient = static_cast(static_cast(quotient + 0.5f)); - else - quotient = static_cast(static_cast(quotient - 0.5f)); - - float y = Value - M3D_2PI * quotient; - - // Map y to [-pi/2,pi/2] with sin(y) = sin(Value). - float sign; - if (y > M3D_PIDIV2) { - y = M3D_PI - y; - sign = -1.0f; - } else if (y < -M3D_PIDIV2) { - y = -M3D_PI - y; - sign = -1.0f; - } else { - sign = +1.0f; - } - - float y2 = y * y; - - // 11-degree minimax approximation - *pSin = (((((-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f) * y2 + 0.0083333310f) * y2 - 0.16666667f) * y2 + 1.0f) * y; - - // 10-degree minimax approximation - float p = ((((-2.6051615e-07f * y2 + 2.4760495e-05f) * y2 - 0.0013888378f) * y2 + 0.041666638f) * y2 - 0.5f) * y2 + 1.0f; - *pCos = sign * p; -} - namespace M3D_Internal { #ifdef DISABLE_INTRINSICS // Round to nearest (even) a.k.a. banker's rounding diff --git a/README.md b/README.md index 0fa33da..d379ae6 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,31 @@ It (should be) a "very dirty" 80s-style arcade game with tanks. I like big-canon, ballistics, retro and simulation, and that's where the initial concept came from. -The similarity with Battlezone (Atari, 1980) or 3D Tank Duel (ZX Spectrum, 1984) only became apparent to me later in the development. +The similarity with Battlezone (Atari, 1980), 3D Tank Duel (ZX Spectrum, 1984) and other tank-like arcade +games, their influence on development is limited to the graphic reference. In fact, I was only able to discover +some of them during my research phase. The game itself isn't very polished. Mainly focused on game engine design, state-of-the-art rendering, and CPU-accelerated computing techniques. This code has been written with experimentation and education in mind. It is subject to the MIT license. -I've used some libs for backend "boiler plates" like: +I've used some libs for backend boilerplate like: - SFML (window management) - tinyobjloader (OBJ file loading) -- Tweaked DirectXMath (Heavy math duty) \ No newline at end of file +- Tweaked DirectXMath (Heavy math duty) + +## Why ... ? + +Why using SFML? I've really wanted to focus on the projection stuff and geometry constructs in first place. +SFML is a light library, giving a drawing window access and pretty easy to use, nothing more. I'll look on +a direct interface for window management (X11 or gdi32) later... + +Why using DirectX for math? Yeah I know GLM exist, but it lacks of SIMD ready functions. And with a MIT license, +the question of the proprietary code issue was quickly answered! + +## About + +*Actually, 72 coffees have been used to produce this code.* + +Feel free to ask question about the code: jackcartersmith@jcsmith.fr +