From 8af1be2a8fb7ae9973b054994682bccb129b0e98 Mon Sep 17 00:00:00 2001 From: Pirulax Date: Sun, 16 Jul 2023 23:46:12 +0200 Subject: [PATCH] Part of `CCoronas` (#583) --- .gitmodules | 4 + source/Base.h | 8 +- source/extensions/FixedFloat.hpp | 12 +- source/extensions/FixedVector.hpp | 5 +- source/game_sa/Camera.h | 6 + source/game_sa/Core/Vector2D.cpp | 8 - source/game_sa/Core/Vector2D.h | 4 +- source/game_sa/Coronas.cpp | 510 +++++++++++++++++++++++++++- source/game_sa/Coronas.h | 24 +- source/game_sa/PathFind.cpp | 5 +- source/game_sa/RegisteredCorona.cpp | 19 +- source/game_sa/RegisteredCorona.h | 10 +- source/game_sa/RenderBuffer.cpp | 10 +- source/game_sa/RenderBuffer.hpp | 4 +- source/game_sa/Sprite.cpp | 12 +- source/game_sa/Sprite.h | 6 +- source/game_sa/TxdStore.h | 16 + source/game_sa/World.h | 2 +- source/game_sa/common.h | 3 +- 19 files changed, 605 insertions(+), 63 deletions(-) diff --git a/.gitmodules b/.gitmodules index 40e9b8c185..a8c97a7c2b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,7 @@ [submodule "libs/tracy"] path = libs/tracy url = git@github.com:wolfpld/tracy.git +[submodule "libs/spdlog"] + path = libs/spdlog + url = git@github.com:gabime/spdlog.git + \ No newline at end of file diff --git a/source/Base.h b/source/Base.h index 415037c101..8bb0a4fbf7 100644 --- a/source/Base.h +++ b/source/Base.h @@ -135,9 +135,15 @@ template * @tparam T The type of the variable * @param Addr The address of it */ +template +T& StaticRef(uintptr addr) { + return *reinterpret_cast(addr); +} + +// TODO: Replace this with the one above template T& StaticRef() { - return *reinterpret_cast(Addr); + return StaticRef(Addr); } #define _IGNORED_ diff --git a/source/extensions/FixedFloat.hpp b/source/extensions/FixedFloat.hpp index 53dea32018..4b771574d5 100644 --- a/source/extensions/FixedFloat.hpp +++ b/source/extensions/FixedFloat.hpp @@ -1,17 +1,15 @@ #pragma once //! Fixed point number (With implicit conversion to float) -template - requires std::is_integral_v +template class FixedFloat { T value{}; public: constexpr FixedFloat() = default; - constexpr FixedFloat(float X) : value(static_cast(X * CompressValue)) {} - explicit constexpr FixedFloat(T X) : value(X) {} + constexpr FixedFloat(float v) : value(static_cast(v * CompressValue)) {} + template + constexpr FixedFloat(Y x) : value(x) {} - constexpr operator float() const { - return static_cast(value) / CompressValue; - } + constexpr operator float() const { return static_cast(value) / CompressValue; } }; diff --git a/source/extensions/FixedVector.hpp b/source/extensions/FixedVector.hpp index f9db269e57..1f1a5bda82 100644 --- a/source/extensions/FixedVector.hpp +++ b/source/extensions/FixedVector.hpp @@ -5,9 +5,8 @@ template struct FixedVector { constexpr FixedVector() = default; - constexpr FixedVector(float X, float Y, float Z) : x(X), y(Y), z(Z) {} constexpr FixedVector(CVector v3d) : x(v3d.x), y(v3d.y), z(v3d.z) {} - explicit constexpr FixedVector(T X, T Y, T Z) : x(X), y(Y), z(Z) {} + constexpr FixedVector(T X, T Y, T Z) : x(X), y(Y), z(Z) {} constexpr operator CVector() const { return CVector{ x, y, z }; } @@ -20,7 +19,7 @@ struct FixedVector2D { constexpr FixedVector2D() = default; constexpr FixedVector2D(float X, float Y) : x(X), y(Y) {} constexpr FixedVector2D(CVector2D v2d) : FixedVector2D{v2d.x, v2d.y} {} - explicit constexpr FixedVector2D(T X, T Y) : FixedVector2D{ x, y } {} + constexpr FixedVector2D(T X, T Y) : FixedVector2D{ x, y } {} constexpr operator CVector2D() const { return { x, y }; } diff --git a/source/game_sa/Camera.h b/source/game_sa/Camera.h index 2b157c6d01..ca7375a052 100644 --- a/source/game_sa/Camera.h +++ b/source/game_sa/Camera.h @@ -34,6 +34,12 @@ enum class eSwitchType : uint16 { JUMPCUT }; +/* todo: + LOOKING_BEHIND = 0x0, + LOOKING_LEFT = 0x1, + LOOKING_RIGHT = 0x2, + LOOKING_FORWARD = 0x3, +*/ enum eLookingDirection { LOOKING_DIRECTION_UNKNOWN_1 = 0, LOOKING_DIRECTION_BEHIND = 1, diff --git a/source/game_sa/Core/Vector2D.cpp b/source/game_sa/Core/Vector2D.cpp index 85d7976c5d..e7201aa885 100644 --- a/source/game_sa/Core/Vector2D.cpp +++ b/source/game_sa/Core/Vector2D.cpp @@ -48,11 +48,3 @@ CVector2D CVector2D::RotatedBy(float rad) const { x * s - y * c, }; } - -CVector2D CVector2D::GetPerpRight() const { - return { y, -x }; // `RotatedBy(-PI / 2)` done manually, rotate by +PI/2 would be `{-y, x}` -} - -CVector2D CVector2D::GetPerpLeft() const { - return { -y, x }; -} diff --git a/source/game_sa/Core/Vector2D.h b/source/game_sa/Core/Vector2D.h index 8a2d122dbc..fb887c71f5 100644 --- a/source/game_sa/Core/Vector2D.h +++ b/source/game_sa/Core/Vector2D.h @@ -144,11 +144,11 @@ class CVector2D : public RwV2d { //! Get vector perpendicular to `*this` on the right side (Same direction `*this` rotated by -90) //! Also see `GetPerpLeft` and `RotatedBy` //! (This sometimes is also called a 2D cross product https://stackoverflow.com/questions/243945 ) - CVector2D GetPerpRight() const; + CVector2D GetPerpRight() const { return { y, -x }; } //! Get vector perpendicular to `*this` on the left side (Same direction `*this` rotated by 90) //! Also see `GetPerpRight` and `RotatedBy` - CVector2D GetPerpLeft() const; + CVector2D GetPerpLeft() const { return { -y, x }; } /*! * @notsa diff --git a/source/game_sa/Coronas.cpp b/source/game_sa/Coronas.cpp index 5f28a0a387..2267124fe0 100644 --- a/source/game_sa/Coronas.cpp +++ b/source/game_sa/Coronas.cpp @@ -2,8 +2,6 @@ #include "Coronas.h" -float& CCoronas::SunScreenX = *(float*)0xC3E028; -float& CCoronas::SunScreenY = *(float*)0xC3E02C; //bool& CCoronas::SunBlockedByClouds = *(bool*)0x0; bool& CCoronas::bChangeBrightnessImmediately = *(bool*)0xC3E034; uint32& CCoronas::NumCoronas = *(uint32*)0xC3E038; @@ -14,34 +12,118 @@ CRegisteredCorona(&CCoronas::aCoronas)[MAX_NUM_CORONAS] = *(CRegisteredCorona(*) uint16(&CCoronas::ms_aEntityLightsOffsets)[8] = *(uint16(*)[8])0x8D5028; -char (&coronaTexturesAlphaMasks)[260] = *(char (*)[260])0x8D4A58; +auto& aCoronastar = StaticRef, 0x8D4950>(); +auto& coronaTexturesAlphaMasks = StaticRef, 0x8D4A58>(); + +struct CFlareDefinition +{ + float Position; + float Size; + FixedVector ColorMult; + FixedFloat IntensityMult; + int16 Sprite; // Only used for array-end checking +}; +constexpr CFlareDefinition HeadLightsFlareDef[]{ + { 4.00f, 5.0f, { 60, 60, 60 }, 200, 4 }, + { 3.00f, 7.0f, { 40, 40, 40 }, 200, 4 }, + { 2.00f, 5.0f, { 40, 40, 40 }, 200, 4 }, + { 1.50f, 6.0f, { 90, 90, 90 }, 200, 4 }, + { 1.25f, 5.0f, { 40, 40, 40 }, 200, 4 }, + { 0.80f, 14.f, { 60, 60, 60 }, 200, 4 }, + { 0.60f, 4.0f, { 40, 40, 40 }, 200, 4 }, + { 0.25f, 10.f, { 60, 60, 60 }, 200, 4 }, + { 0.10f, 6.0f, { 30, 30, 30 }, 200, 4 }, + { 0.05f, 14.f, { 50, 50, 50 }, 200, 4 }, + { -0.03f, 3.0f, { 30, 30, 30 }, 200, 4 }, + { -0.10f, 6.0f, { 60, 60, 60 }, 200, 4 }, + { -0.30f, 5.0f, { 30, 30, 30 }, 200, 4 }, + { -0.40f, 60.f, { 30, 30, 30 }, 200, 4 }, + { -0.55f, 4.0f, { 40, 40, 40 }, 200, 4 }, + { -0.75f, 14.f, { 50, 50, 50 }, 200, 4 }, + { -0.90f, 5.2f, { 35, 35, 35 }, 200, 4 }, + { -1.00f, 11.f, { 55, 55, 55 }, 200, 4 }, + { -1.20f, 3.5f, { 35, 35, 35 }, 200, 4 }, + { -1.35f, 9.0f, { 50, 50, 50 }, 200, 4 }, + { -1.70f, 54.f, { 35, 35, 35 }, 200, 4 }, + { -2.00f, 5.0f, { 50, 50, 50 }, 200, 4 }, + { -2.50f, 4.5f, { 35, 35, 35 }, 200, 4 }, + { -3.00f, 14.f, { 50, 50, 50 }, 200, 4 }, + { -6.00f, 24.f, { 70, 70, 70 }, 200, 4 }, + { -9.00f, 14.f, { 70, 50, 70 }, 200, 4 }, + { 0.00f, 0.0f, { 255, 255, 255 }, 255, 0 } +}; + +constexpr CFlareDefinition SunFlareDef[]{ + { 4.00f, 8.00f, { 36, 30, 24 }, 200, 4 }, + { 3.00f, 11.2f, { 24, 18, 15 }, 200, 4 }, + { 2.00f, 8.00f, { 24, 12, 12 }, 200, 4 }, + { 1.50f, 9.60f, { 54, 54, 48 }, 200, 4 }, + { 1.25f, 8.00f, { 24, 24, 18 }, 200, 4 }, + { 0.80f, 22.4f, { 36, 30, 24 }, 200, 4 }, + { 0.60f, 6.40f, { 24, 15, 12 }, 200, 4 }, + { 0.25f, 16.0f, { 36, 30, 30 }, 200, 4 }, + { 0.10f, 9.60f, { 18, 18, 18 }, 200, 4 }, + { 0.05f, 22.4f, { 36, 30, 24 }, 200, 4 }, + { -0.03f, 4.80f, { 18, 18, 18 }, 200, 4 }, + { -0.10f, 9.60f, { 42, 42, 42 }, 200, 4 }, + { -0.30f, 8.00f, { 18, 6, 6 }, 200, 4 }, + { -0.40f, 96.0f, { 18, 12, 9 }, 200, 4 }, + { -0.55f, 6.40f, { 18, 18, 12 }, 200, 4 }, + { -0.75f, 22.4f, { 42, 24, 18 }, 200, 4 }, + { -0.90f, 8.32f, { 21, 12, 18 }, 200, 4 }, + { -1.00f, 17.6f, { 42, 13, 18 }, 200, 4 }, + { -1.20f, 5.60f, { 21, 12, 12 }, 200, 4 }, + { -1.35f, 14.4f, { 42, 42, 24 }, 200, 4 }, + { -1.70f, 86.8f, { 21, 15, 15 }, 200, 4 }, + { -2.00f, 8.00f, { 48, 30, 30 }, 200, 4 }, + { -2.50f, 7.20f, { 21, 15, 12 }, 200, 4 }, + { -3.00f, 22.4f, { 42, 30, 24 }, 200, 4 }, + { -6.00f, 38.4f, { 42, 42, 30 }, 200, 4 }, + { -9.00f, 22.4f, { 42, 30, 36 }, 200, 4 }, + { 0.00f, 0.00f, { 255, 255, 255 }, 255, 0 } +}; void CCoronas::InjectHooks() { RH_ScopedClass(CCoronas); RH_ScopedCategoryGlobal(); - RH_ScopedInstall(Init, 0x6FAA70, { .reversed = false }); - RH_ScopedInstall(Shutdown, 0x6FAB00, { .reversed = false }); + RH_ScopedInstall(Init, 0x6FAA70); + RH_ScopedInstall(Shutdown, 0x6FAB00); RH_ScopedInstall(Update, 0x6FADF0, { .reversed = false }); - RH_ScopedInstall(Render, 0x6FAEC0, { .reversed = false }); - RH_ScopedInstall(RenderReflections, 0x6FB630, { .reversed = false }); - RH_ScopedInstall(RenderSunReflection, 0x6FBAA0, { .reversed = false }); + RH_ScopedInstall(Render, 0x6FAEC0); + RH_ScopedInstall(RenderReflections, 0x6FB630); + RH_ScopedInstall(RenderSunReflection, 0x6FBAA0); RH_ScopedOverloadedInstall(RegisterCorona, "type", 0x6FC180, void(*)(uint32, CEntity*, uint8, uint8, uint8, uint8, const CVector&, float, float, RwTexture*, eCoronaFlareType, bool, bool, int32, float, bool, float, uint8, float, bool, bool reflectionDelay), { .reversed = false }); RH_ScopedOverloadedInstall(RegisterCorona, "texture", 0x6FC580, void(*)(uint32, CEntity*, uint8, uint8, uint8, uint8, const CVector&, float, float, eCoronaType, eCoronaFlareType, bool, bool, int32, float, bool, float, uint8, float, bool, bool reflectionDelay), { .reversed = false }); + RH_ScopedInstall(UpdateCoronaCoors, 0x6FC4D0, { .reversed = false }); - RH_ScopedInstall(DoSunAndMoon, 0x6FC5A0, { .reversed = false }); + RH_ScopedInstall(DoSunAndMoon, 0x6FC5A0); } // Initialises coronas // 0x6FAA70 void CCoronas::Init() { - plugin::Call<0x6FAA70>(); + { + CTxdStore::ScopedTXDSlot txd{"particle"}; + //for (auto&& [tex, name, maskName] : rng::zip_view{ gpCoronaTexture, aCoronastar, coronaTexturesAlphaMasks }) { // TODO: C++23 + // if (!tex) { + // tex = RwTextureRead(name, maskName); + // } + //} + for (auto i = 0; i < CORONA_TEXTURES_COUNT; i++) { + auto& tex = gpCoronaTexture[i]; + if (!tex) { + tex = RwTextureRead(aCoronastar[i], coronaTexturesAlphaMasks[i]); + } + } + } + rng::fill(aCoronas, CRegisteredCorona{}); } // Terminates coronas // 0x6FAB00 void CCoronas::Shutdown() { - plugin::Call<0x6FAB00>(); + rng::for_each(gpCoronaTexture, RwTextureDestroy); } // Updates coronas @@ -50,26 +132,377 @@ void CCoronas::Update() { ZoneScoped; plugin::Call<0x6FADF0>(); + + /** + * NOTE: Unfinished and untested + *** + + LightsMult = std::min(CTimer::GetTimeStep() * 0.03f * LightsMult, 1.f); + + struct CamLook { + bool unused : 4{}, left : 1{}, right : 1{}, behind : 1{}, forward : 1{}; // Have to initialize the msb 4 bits too, otherwise it wont compare equal to the original code's value + } &LastCamLook = StaticRef(); // NOTE/TODO: I'm not sure if foward is really forward + + const auto c = TheCamera.GetActiveCam(); + const CamLook currLook{ + .left = c.m_bLookingLeft, + .right = c.m_bLookingRight, + .behind = c.m_bLookingBehind, + .forward = TheCamera.GetLookDirection() != 0, + }; + + if (currLook == LastCamLook) { + + } + */ } -// Renders coronas // 0x6FAEC0 void CCoronas::Render() { ZoneScoped; - plugin::Call<0x6FAEC0>(); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + + const auto raster = RwCameraGetRaster(Scene.m_pRwCamera); + const auto rasterSize = CVector2D{ (float)RwRasterGetWidth(raster), (float)RwRasterGetHeight(raster) }; + const auto rasterRect = CRect{ + 0.f, 0.f, + rasterSize.x, rasterSize.y + }; + + bool zTestEnable = true; + for (auto& c : aCoronas) { + if (!c.m_dwId) { + continue; + } + + if (!c.m_FadedIntensity && !c.m_Color.a) { + continue; + } + + const auto covidPos = c.GetPosition(); + + //< 0x6FB009 - Get on-screen position, and check if it's on it + CVector onScrPos; + CVector2D onScrSize; + if (c.m_bOffScreen = !CSprite::CalcScreenCoors(covidPos, &onScrPos, &onScrSize.x, &onScrSize.y, true, true)) { + continue; + } + + c.m_bOffScreen = !rasterRect.IsPointInside(CVector2D{ onScrPos }); // Seems like a useless check + + // Already faded out + if (c.m_FadedIntensity == 0) { + continue; + } + + // If outside farclip range, just ignore + if (onScrPos.z >= c.m_fFarClip) { + continue; + } + + const auto rz = 1.f / onScrPos.z; + + //< 0x6FB0A7 - Start fading out at half the far clip distance + const auto intensity = (int16)((float)c.m_FadedIntensity * c.CalculateIntensity(onScrPos.z, c.m_fFarClip)); + + //< 0x6FB0D9 - Enable/disable Z test if necessary + if (c.m_bCheckObstacles == zTestEnable) { + zTestEnable = !zTestEnable; + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(zTestEnable)); + } + + const auto LerpColorC = [](uint8 cc, float t) { + return (uint8)((float)cc * t); + }; + + //< 0x6FB131 - Render with texture + if (c.m_pTexture) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(c.m_pTexture))); + + //< 0x6FB122 + const auto scale = std::min(40.f, onScrPos.z) * CWeather::Foggyness / 40.f + 1.f; + + // NOP - The coordinates are later overwritten + //if (c.m_dwId == 1) { + // scrPos.z = RwCameraGetFarClipPlane(Scene.m_pRwCamera) * 0.95f; + //} + + //< 0x6FB24E + if (CSprite::CalcScreenCoors( + covidPos - (covidPos - TheCamera.GetPosition()).Normalized() * c.m_fNearClip, + &onScrPos, // NOTE/BUG: Yeah, overwrites the previously calculated value. Not sure if it's intentional. + &onScrSize.x, &onScrSize.y, + true, + true + )) { + CSprite::RenderOneXLUSprite_Rotate_Aspect( + onScrPos, + onScrSize * c.m_fSize * CVector2D{1.f, scale}, + LerpColorC(c.m_Color.r, 1.f / scale), + LerpColorC(c.m_Color.g, 1.f / scale), + LerpColorC(c.m_Color.b, 1.f / scale), + intensity, + rz * 20.f, + 0.f, + 255 + ); + } + } + + //< 0x6FB2F3 - Render flare + if (c.m_nFlareType != FLARETYPE_NONE) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[0]))); + + //< 0x6FB35B + const auto colorVariationMult = CGeneral::GetRandomNumberInRange(0.7f, 1.f) * (float)c.m_FadedIntensity; + + //< 0x6FB2FC [Moved here] + auto it = [&] { + switch (c.m_nFlareType) { + case FLARETYPE_SUN: return &SunFlareDef[0]; + case FLARETYPE_HEADLIGHTS: return &HeadLightsFlareDef[0]; + default: NOTSA_UNREACHABLE(); + } + }(); + + //< 0x6FB46C + for (; it->Sprite; it++) { + CEntity* hitEntity; + CColPoint hitCP; + if (!CWorld::ProcessLineOfSight( + covidPos, + TheCamera.GetPosition(), + hitCP, + hitEntity, + false, + true, + true, + false, + false, + false, + false, + true + )) { //< 0x6FB409 + auto color = c.m_Color * colorVariationMult; + color.a = 255; + CSprite::RenderBufferedOneXLUSprite2D( + lerp(rasterSize / 2.f, CVector2D{ onScrPos }, it->Position), + CVector2D{ it->Size, it->Size } * 4.f, + color, + 255, + 255 + ); + } + } + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + + //< 0x6FB483 + if (c.m_nFlareType == FLARETYPE_HEADLIGHTS && CWeather::HeadLightsSpectrum != 0.f && CGame::CanSeeOutSideFromCurrArea()) { + for (auto it = HeadLightsFlareDef; it->Sprite; it++) { + const auto RenderFlareSprite = [ + &, + spriteIntensity = (int16)((float)intensity * it->IntensityMult) + ](RwRGBA clr, float posOffset) { + CSprite::RenderBufferedOneXLUSprite2D( + lerp(rasterSize / 2.f, CVector2D{ onScrPos }, it->Position + posOffset), + CVector2D{ it->Size, it->Size }, + clr, + spriteIntensity, + 255 + ); + }; + RenderFlareSprite({ LerpColorC(c.m_Color.r, it->ColorMult.x * CWeather::HeadLightsSpectrum), 0, 0, 255 }, +0.05f); // 0x6FB561 + RenderFlareSprite({ 0, 0, LerpColorC(c.m_Color.b, it->ColorMult.z * CWeather::HeadLightsSpectrum), 255 }, -0.05f); // 0x6FB5EA + } + } + } + } + CSprite::FlushSpriteBuffer(); + + /* NOTE/BUG: Renderstates not restored? */ } -// Renders coronas reflections on a wet ground // 0x6FB630 void CCoronas::RenderReflections() { - plugin::Call<0x6FB630>(); + if (s_DebugSettings.DisableWetRoadReflections) { + return; + } + + if (!s_DebugSettings.AlwaysRenderWetRoadReflections) { + // Check if the roads are wet enough + if (CWeather::WetRoads <= 0.f) { + for (auto& c : aCoronas) { + c.m_bHasValidHeightAboveGround = false; + } + return; + } + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[3]))); + + const auto camPos = TheCamera.GetPosition(); + for (auto&& [i, c] : notsa::enumerate(aCoronas)) { + const auto covidPos = c.GetPosition(); + if (!c.m_bHasValidHeightAboveGround || ((i & 0xFF) + (CTimer::GetFrameCounter() & 0xFF) % 16) == 0) { //< Simplified code + bool bGroundFound; + const auto groundZ = CWorld::FindGroundZFor3DCoord(covidPos, &bGroundFound); + if (bGroundFound) { // NOTE/BUG: Weird.. Why not set it according to `bGroundFound` directly? + c.m_bHasValidHeightAboveGround = true; + c.m_fHeightAboveGround = covidPos.z - groundZ; + } + } + if (!c.m_bHasValidHeightAboveGround) { + continue; + } + if (c.m_bHasValidHeightAboveGround >= 20.f) { + continue; + } + if (covidPos.z - c.m_fHeightAboveGround >= camPos.z) { + continue; + } + CVector onScrPos; + CVector2D onScrSize; + if (!CSprite::CalcScreenCoors(covidPos - CVector{0.f, 0.f, 2 * c.m_fHeightAboveGround }, & onScrPos, & onScrSize.x, & onScrSize.y, true, true)) { + continue; + } + const auto clampedFarClip = std::min(55.f, c.m_fFarClip * 0.75f); + if (onScrPos.z >= clampedFarClip) { + continue; + } + const auto LerpColorC = [ + t = (s_DebugSettings.AlwaysRenderWetRoadReflections ? 1.f : CWeather::WetRoads) + * c.CalculateIntensity(onScrPos.z, clampedFarClip) + * invLerp(20.f, 0.f, c.m_fHeightAboveGround) + * 230.f + ](uint8 cc) { + return (uint8)((uint16)((float)cc * t) >> 8 & 0xFF); // divide by 256 + }; + CSprite::RenderBufferedOneXLUSprite( + { onScrPos.x, onScrPos.y, notsa::IsFixBugs() ? onScrPos.z : RwIm2DGetNearScreenZMacro() }, + onScrSize * CVector2D{0.75f, 2.f} * c.m_fSize, + LerpColorC(c.m_Color.r), + LerpColorC(c.m_Color.g), + LerpColorC(c.m_Color.b), + 128, + 1.f / RwCameraGetNearClipPlane(Scene.m_pRwCamera), + 255 + ); + } + CSprite::FlushSpriteBuffer(); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDINVSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); } -// Renders sun reflection on water // 0x6FBAA0 void CCoronas::RenderSunReflection() { - plugin::Call<0x6FBAA0>(); + constexpr auto REFLECTION_SIZE = 60.f; + constexpr auto REFLECTION_PERIOD = 2048; // Code is faster if this is a power-of-2 + + const auto vecToSun3D = CTimeCycle::m_VectorToSun[CTimeCycle::m_CurrentStoredValue]; + + if (vecToSun3D.z <= -0.05f) { + return; + } + + const auto camPos = TheCamera.GetPosition(); + + auto t = + invLerp(0.3f, 0.f, std::abs(vecToSun3D.z - 0.25f)) + * (1.f - CWeather::CloudCoverage) + * (1.f - CWeather::Foggyness) + * (1.f - CWeather::Wind); + if (t <= 0.f) { + return; + } + for (const auto& v : std::initializer_list{ // TODO: Magic numberz + { 611.0f, 875.0f, 0.f }, + { -929.0f, 2364.f, 0.f }, + { -1034.f, 2640.f, 0.f }, + { 2372.f, -1854.f, 0.f }, + { -1633.f, 106.0f, 0.f } + }) { + t *= std::clamp(invLerp(0.f, 100.f, (camPos - v).Magnitude2D() - 250.f), 0.f, 1.f); + } + t *= 0.25f; + + const auto center2D = CVector2D{ vecToSun3D } * 40.f + CVector2D{ camPos }; + const auto vecToSun2D = CVector2D{ vecToSun3D }.Normalized(); + const auto vecToSunCore2D = vecToSun2D * (REFLECTION_SIZE / 2.f); + const auto vecToSunCorona2D = vecToSun2D * REFLECTION_SIZE; + + #define CalcColorC(_color) \ + (uint8)((float)(CTimeCycle::m_CurrentColours.m_nSunCorona##_color + CTimeCycle::m_CurrentColours.m_nSunCore##_color) * t) + const auto PushVertex = [ + &, + color = CRGBA{ CalcColorC(Red), CalcColorC(Green), CalcColorC(Blue), 255 }, + posZ = CWeather::Wind * 0.5f + 0.1f + ](CVector2D offsetToCenter, CVector2D uv) { + RenderBuffer::PushVertex(CVector{ center2D + offsetToCenter, posZ }, uv, color); + }; + #undef CalcColorC + + RenderBuffer::ClearRenderBuffer(); + + // The part closer to the camera + { + RenderBuffer::PushIndices({ 2, 1, 0, 2, 3, 1 }, false); + + PushVertex(vecToSunCore2D.GetPerpRight(), { 0.0f, 1.0f }); + PushVertex(vecToSunCore2D.GetPerpLeft(), { 1.0f, 1.0f }); + PushVertex(vecToSunCorona2D + vecToSunCore2D.GetPerpRight(), { 0.0f, 0.5f }); + PushVertex(vecToSunCorona2D + vecToSunCore2D.GetPerpLeft(), { 1.0f, 0.5f }); + } + + //< 0x6FBF36 - The part that is changing position [To imitate how the water is flowing] + const auto time = CTimer::GetTimeInMS(); + for (auto i = 0u; i < 20u; i++) { + RenderBuffer::PushIndices({ 0, -1, -2, 0, 1, -1 }, true); // Same as the above indices, but with 2 subtracted from each + + const auto forward = vecToSun2D * ( + std::sin((float)((time + 900 * i) % REFLECTION_PERIOD) / (float)REFLECTION_PERIOD * TWO_PI) * 10.f + + (float)(i * 970 / 20 + 30) + ); + const auto offset = vecToSun2D * (float)(i * 1440 / 20 + 60); + + PushVertex(offset + forward.GetPerpRight(), { 0.0f, 0.5f }); + PushVertex(offset + forward.GetPerpLeft(), { 1.0f, 0.5f }); + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEFOGTYPE, RWRSTATE(rwFOGTYPELINEAR)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[4]))); + + RenderBuffer::RenderStuffInBuffer(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDINVSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(FALSE)); } // Creates corona by texture @@ -94,5 +527,46 @@ void CCoronas::UpdateCoronaCoors(uint32 id, const CVector& posn, float farClip, void CCoronas::DoSunAndMoon() { ZoneScoped; - plugin::Call<0x6FC5A0>(); + if (!CGame::CanSeeOutSideFromCurrArea()) { + return; + } + + const auto vecToSun = CTimeCycle::GetVectorToSun(); + + if (vecToSun.z >= -0.1f) { + const auto DoRegisterCorona = [ + coronaPos = vecToSun * (CDraw::GetFarClipZ() * 0.95f) + TheCamera.GetPosition() + ](uint32 id, eCoronaFlareType ftype, float radiusMult) { + const auto& cc = CTimeCycle::m_CurrentColours; + RegisterCorona( + id, + nullptr, + (uint8)cc.m_nSunCoreRed, + (uint8)cc.m_nSunCoreGreen, + (uint8)cc.m_nSunCoreBlue, + 255u, + &coronaPos, + cc.m_fSunSize * radiusMult, + 999999.88f, + gpCoronaTexture[0], + ftype, + false, + false, + 0, + 0.f, + false, + 1.5f, + 0, + 15.f, + false, + false + ); + }; + DoRegisterCorona(1, FLARETYPE_NONE, 2.7335f); + if (vecToSun.z >= 0.f) { // Removed redudant check + DoRegisterCorona(2, FLARETYPE_SUN, 6.f); + } + } + + // Dead code here } diff --git a/source/game_sa/Coronas.h b/source/game_sa/Coronas.h index 2420ca3d4a..5d7b8f01fd 100644 --- a/source/game_sa/Coronas.h +++ b/source/game_sa/Coronas.h @@ -10,9 +10,6 @@ class CCoronas { public: - // sun 2d position - static float& SunScreenX; - static float& SunScreenY; // are there any obstacles between sun and camera static bool& SunBlockedByClouds; // change coronas brightness immediately @@ -29,15 +26,36 @@ class CCoronas { static uint16 (&ms_aEntityLightsOffsets)[8]; + inline static struct { // NOTSA + bool DisableWetRoadReflections; + bool AlwaysRenderWetRoadReflections; // Ignored if if `DisableReflections == false` + } s_DebugSettings{}; + public: static void InjectHooks(); static void Init(); static void Shutdown(); static void Update(); + + /*! + * @addr 0x6FAEC0 + * Renders the registered coronas + */ static void Render(); + + /*! + * @addr 0x6FB630 + * Renders registered coronas reflections on a wet roads ground + */ static void RenderReflections(); + + /*! + * @addr 0x6FBAA0 + * Renders sun's reflection on the water [sea] + */ static void RenderSunReflection(); + static void RegisterCorona(uint32 id, CEntity* attachTo, uint8 red, uint8 green, uint8 blue, uint8 alpha, const CVector& posn, float radius, float farClip, RwTexture* texture, eCoronaFlareType flareType, bool enableReflection, bool checkObstacles, int32 _param_not_used, float angle, bool longDistance, float nearClip, uint8 fadeState, float fadeSpeed, bool onlyFromBelow, bool reflectionDelay); diff --git a/source/game_sa/PathFind.cpp b/source/game_sa/PathFind.cpp index 2ac01a6a12..734e6f16ae 100644 --- a/source/game_sa/PathFind.cpp +++ b/source/game_sa/PathFind.cpp @@ -232,7 +232,7 @@ auto CPathFind::FindIntersection(const CNodeAddress& startNodeAddress, const CNo return nullptr; } - const auto& startNode = m_pPathNodes[startNodeAddress.m_wAreaId][startNodeAddress.m_wNodeId]; + const auto& startNode = *GetPathNode(startNodeAddress); const auto& nodeLinks = m_pNodeLinks[startNodeAddress.m_wAreaId]; for (auto i = 0u; i < startNode.m_nNumLinks; i++) { const auto linkedNodeIdx = startNode.m_wBaseLinkId + i; @@ -586,6 +586,7 @@ void CPathFind::SwitchRoadsOffInAreaForOneRegion(float xMin, float xMax, float y // NOTSA CPathNode* CPathFind::GetPathNode(CNodeAddress address) { assert(address.IsValid()); + assert(IsAreaNodesAvailable(address)); return &m_pPathNodes[address.m_wAreaId][address.m_wNodeId]; } @@ -877,6 +878,8 @@ CNodeAddress CPathFind::FindNearestExteriorNodeToInteriorNode(int32 interiorId) // 0x44E000 void CPathFind::AddDynamicLinkBetween2Nodes_For1Node(CNodeAddress first, CNodeAddress second) { + assert(IsAreaNodesAvailable(first)); + auto& firstPathInfo = m_pPathNodes[first.m_wAreaId][first.m_wNodeId]; auto numAddresses = m_anNumAddresses[first.m_wAreaId]; diff --git a/source/game_sa/RegisteredCorona.cpp b/source/game_sa/RegisteredCorona.cpp index a15c7a792d..62ec0eafce 100644 --- a/source/game_sa/RegisteredCorona.cpp +++ b/source/game_sa/RegisteredCorona.cpp @@ -1 +1,18 @@ -#include "StdInc.h" \ No newline at end of file +#include "StdInc.h" + +#include "RegisteredCorona.h" + +//! Calculate the position to use for rendering +auto CRegisteredCorona::GetPosition() const -> CVector { + if (!m_pAttachedTo) { + return m_vPosn; + } + if (m_pAttachedTo->GetType() == ENTITY_TYPE_VEHICLE && m_pAttachedTo->AsVehicle()->IsSubBike()) { + return m_pAttachedTo->AsBike()->m_mLeanMatrix * m_vPosn; + } + return m_pAttachedTo->GetMatrix() * m_vPosn; +} + +auto CRegisteredCorona::CalculateIntensity(float scrZ, float farClip) const -> float { + return std::clamp(invLerp(farClip, farClip / 2.f, scrZ), 0.f, 1.f); +} diff --git a/source/game_sa/RegisteredCorona.h b/source/game_sa/RegisteredCorona.h index a8f9833ed9..c4d9e4ea8b 100644 --- a/source/game_sa/RegisteredCorona.h +++ b/source/game_sa/RegisteredCorona.h @@ -32,7 +32,7 @@ enum eCoronaFlareType : uint8 { FLARETYPE_NONE, FLARETYPE_SUN, FLARETYPE_HEADLIG class CRegisteredCorona { public: CVector m_vPosn; - uint32 m_dwId; // Should be unique for each corona. Address or something + uint32 m_dwId{}; // Should be unique for each corona. Address or something RwTexture* m_pTexture; // Pointer to the actual texture to be rendered float m_fSize; float m_fAngle; // left from III&VC @@ -41,7 +41,7 @@ class CRegisteredCorona { float m_fHeightAboveGround; float m_fFadeSpeed; // The speed the corona fades in and out CRGBA m_Color; - uint8 m_nFadeState; // Intensity that lags behind the given intenisty and fades out if the LOS is blocked + uint8 m_FadedIntensity; // Intensity that lags behind the given intenisty and fades out if the LOS is blocked uint8 m_bRegisteredThisFrame; // Has this corona been registered by game code this frame eCoronaFlareType m_nFlareType; uint8 m_bUsesReflection; @@ -50,12 +50,16 @@ class CRegisteredCorona { uint8 m_bJustCreated; // If this corona has been created this frame we won't delete it (It hasn't had the time to get its OffScreen cleared) uint8 m_bFlashWhileFading : 1; // Does the corona fade out when closer to cam uint8 m_bOnlyFromBelow : 1; // This corona is only visible if the camera is below it - uint8 m_bReflectionDelay : 1; // this corona Has Valid Height Above Ground + uint8 m_bHasValidHeightAboveGround : 1; // this corona Has Valid Height Above Ground uint8 m_bDrawWithWhiteCore : 1; // This corona rendered with a small white core. uint8 m_bAttached : 1; // This corona is attached to an entity. CEntity* m_pAttachedTo; + CRegisteredCorona() = default; + void Update(); + auto GetPosition() const -> CVector; + auto CalculateIntensity(float scrZ, float farClip) const -> float; }; VALIDATE_SIZE(CRegisteredCorona, 0x3C); diff --git a/source/game_sa/RenderBuffer.cpp b/source/game_sa/RenderBuffer.cpp index 73a412d721..b29fe3d2ef 100644 --- a/source/game_sa/RenderBuffer.cpp +++ b/source/game_sa/RenderBuffer.cpp @@ -93,12 +93,16 @@ void PushVertex(CVector pos, CVector2D uv, CRGBA color) { } // notsa -void PushIndex(RwImVertexIndex idx, bool useCurrentVtxAsBase) { - aTempBufferIndices[uiTempBufferIndicesStored++] = useCurrentVtxAsBase ? uiTempBufferVerticesStored + idx : idx; +void PushIndex(int32 idx, bool useCurrentVtxAsBase) { + idx = useCurrentVtxAsBase + ? (int32)uiTempBufferVerticesStored + idx + : idx; + assert(idx >= 0); + aTempBufferIndices[uiTempBufferIndicesStored++] = idx; } // notsa -void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase) { +void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase) { for (auto idx : idxs) { PushIndex(idx, useCurrentVtxAsBase); } diff --git a/source/game_sa/RenderBuffer.hpp b/source/game_sa/RenderBuffer.hpp index 6c31a59a47..06179bbbf2 100644 --- a/source/game_sa/RenderBuffer.hpp +++ b/source/game_sa/RenderBuffer.hpp @@ -61,12 +61,12 @@ void PushVertex(CVector pos, CVector2D uv, CRGBA color); * @addr notsa * @brief Push an index into the buffer. Not to be used with `StartStoring`! */ -void PushIndex(RwImVertexIndex idx, bool useCurrentVtxAsBase); +void PushIndex(int32 idx, bool useCurrentVtxAsBase); /*! * @addr notsa * @brief Push multiple indices into the buffer. Not to be used with `StartStoring`! */ -void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase); +void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase); }; // namespace RenderBuffer diff --git a/source/game_sa/Sprite.cpp b/source/game_sa/Sprite.cpp index f63eee86ed..1a549d679a 100644 --- a/source/game_sa/Sprite.cpp +++ b/source/game_sa/Sprite.cpp @@ -120,8 +120,8 @@ void CSprite::RenderOneXLUSprite_Triangle(float, float, float, float, float, flo } // 0x70D490 -void CSprite::RenderOneXLUSprite_Rotate_Aspect(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8) { - assert(false); +void CSprite::RenderOneXLUSprite_Rotate_Aspect(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float rz, float rotation, uint8 alpha) { + plugin::Call<0x70D490>(pos, size, r, g, b, intensity, rz, rotation, alpha); } // Android @@ -148,8 +148,8 @@ void CSprite::RenderOneXLUSprite2D_Rotate_Dimension(float, float, float, float, /* --- Buffered XLU Sprite --- */ // 0x70E4A0 -void CSprite::RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11) { - plugin::Call<0x70E4A0, float, float, float, float, float, uint8, uint8, uint8, int16, float, uint8>(x, y, z, w, h, r, g, b, intensity, recipNearZ, a11); +void CSprite::RenderBufferedOneXLUSprite(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11) { + plugin::Call<0x70E4A0>(pos, size, r, g, b, intensity, recipNearZ, a11); } // 0x70E780 @@ -168,8 +168,8 @@ void CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(float, float, float, fl } // 0x70F440 -void CSprite::RenderBufferedOneXLUSprite2D(float, float, float, float, const RwRGBA&, int16, uint8) { - assert(false); +void CSprite::RenderBufferedOneXLUSprite2D(CVector2D pos, CVector2D size, const RwRGBA& color, int16 intensity, uint8 alpha) { + plugin::Call<0x70F440>(pos, size, &color, intensity, alpha); } // unused diff --git a/source/game_sa/Sprite.h b/source/game_sa/Sprite.h index fd4710c7fa..a705bdc19c 100644 --- a/source/game_sa/Sprite.h +++ b/source/game_sa/Sprite.h @@ -33,16 +33,16 @@ class CSprite { static void RenderOneXLUSprite(float x, float y, float z, float halfWidth, float halfHeight, uint8 r, uint8 g, uint8 b, int16 a, float rhw, uint8 intensity, uint8 udir, uint8 vdir); static void RenderOneXLUSprite_Triangle(float, float, float, float, float, float, float, uint8, uint8, uint8, int16, float, uint8); - static void RenderOneXLUSprite_Rotate_Aspect(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8); + static void RenderOneXLUSprite_Rotate_Aspect(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float rz, float rotation, uint8 alpha); static void RenderOneXLUSprite_Rotate_Dimension(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8); static void RenderOneXLUSprite_Rotate_2Colours(float, float, float, float, float, uint8, uint8, uint8, uint8, uint8, uint8, float, float, float, float, uint8); static void RenderOneXLUSprite2D(float, float, float, float, const RwRGBA&, int16, uint8); static void RenderOneXLUSprite2D_Rotate_Dimension(float, float, float, float, const RwRGBA&, int16, float, uint8); - static void RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11); + static void RenderBufferedOneXLUSprite(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11); static void RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, float angle, uint8 a12); static void RenderBufferedOneXLUSprite_Rotate_Dimension(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8); static void RenderBufferedOneXLUSprite_Rotate_2Colours(float, float, float, float, float, uint8, uint8, uint8, uint8, uint8, uint8, float, float, float, float, uint8); - static void RenderBufferedOneXLUSprite2D(float, float, float, float, const RwRGBA&, int16, uint8); + static void RenderBufferedOneXLUSprite2D(CVector2D pos, CVector2D size, const RwRGBA& color, int16 intensity, uint8 alpha); static void RenderBufferedOneXLUSprite2D_Rotate_Dimension(float, float, float, float, const RwRGBA&, int16, float, uint8); }; diff --git a/source/game_sa/TxdStore.h b/source/game_sa/TxdStore.h index c7fd81db92..fc38c0eb2b 100644 --- a/source/game_sa/TxdStore.h +++ b/source/game_sa/TxdStore.h @@ -24,6 +24,22 @@ static inline int32& ms_txdPluginOffset = *reinterpret_cast(0xC88018); typedef CPool CTxdPool; class CTxdStore { +public: + struct ScopedTXDSlot { + ScopedTXDSlot(uint32 id) { + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(id); + } + + ScopedTXDSlot(const char* txd) : + ScopedTXDSlot{ CTxdStore::FindTxdSlot(txd) } + { + } + + ~ScopedTXDSlot() { + CTxdStore::PopCurrentTxd(); + } + }; public: static CTxdPool*& ms_pTxdPool; static RwTexDictionary*& ms_pStoredTxd; diff --git a/source/game_sa/World.h b/source/game_sa/World.h index 318a34096c..af585d62d0 100644 --- a/source/game_sa/World.h +++ b/source/game_sa/World.h @@ -153,7 +153,7 @@ class CWorld { static void FindMissionEntitiesIntersectingCube(const CVector& cornerA, const CVector& cornerB, int16* outCount, int16 maxCount, CEntity** outEntities, bool vehicles, bool peds, bool objects); static CEntity* FindNearestObjectOfType(int32 modelId, const CVector& point, float radius, bool b2D, bool buildings, bool vehicles, bool peds, bool objects, bool dummies); static float FindGroundZForCoord(float x, float y); - static float FindGroundZFor3DCoord(CVector coord, bool* outResult, CEntity** outEntity); + static float FindGroundZFor3DCoord(CVector coord, bool* outResult = nullptr, CEntity** outEntity = nullptr); static float FindRoofZFor3DCoord(float x, float y, float z, bool* outResult); static float FindLowestZForCoord(float x, float y); static void RepositionOneObject(CEntity* object); diff --git a/source/game_sa/common.h b/source/game_sa/common.h index db5e168889..bcbfae5cc8 100644 --- a/source/game_sa/common.h +++ b/source/game_sa/common.h @@ -155,10 +155,11 @@ constexpr float RadiansToDegrees(float angleInRadians) { template auto lerp(const T& from, const T& to, float t) { + // Same as from + (to - from) * t return to * t + from * (1.f - t); } -inline const float invLerp(float fMin, float fMax, float fVal) { +constexpr float invLerp(float fMin, float fMax, float fVal) { return (fVal - fMin) / (fMax - fMin); }