diff --git a/Generals/Code/CompatLib/Include/d3dx8math.h b/Generals/Code/CompatLib/Include/d3dx8math.h index b3d1b431c46..d66440fbf72 100644 --- a/Generals/Code/CompatLib/Include/d3dx8math.h +++ b/Generals/Code/CompatLib/Include/d3dx8math.h @@ -52,6 +52,7 @@ typedef struct D3DXVECTOR4 #define D3DX_PI 3.141592654f +D3DXMATRIX *WINAPI D3DXMatrixIdentity(D3DXMATRIX *pOut); D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM); D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz); D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z); diff --git a/GeneralsMD/Code/CompatLib/CMakeLists.txt b/GeneralsMD/Code/CompatLib/CMakeLists.txt index 0466a3d8859..33223963616 100644 --- a/GeneralsMD/Code/CompatLib/CMakeLists.txt +++ b/GeneralsMD/Code/CompatLib/CMakeLists.txt @@ -39,6 +39,10 @@ target_include_directories(d3dx8 PUBLIC ${CMAKE_SOURCE_DIR}/Core/Libraries/Source/WWVegas/WWLib ) +if(SAGE_USE_DETERMINISTIC_MATH) + target_link_libraries(d3dx8 PUBLIC gamemath) +endif() + # Link d3dx8 to d3d8lib to get DirectX8 headers (min-dx8-sdk or DXVK) target_link_libraries(d3dx8 PUBLIC d3d8lib) diff --git a/GeneralsMD/Code/CompatLib/Include/d3dx8math.h b/GeneralsMD/Code/CompatLib/Include/d3dx8math.h index b3d1b431c46..d66440fbf72 100644 --- a/GeneralsMD/Code/CompatLib/Include/d3dx8math.h +++ b/GeneralsMD/Code/CompatLib/Include/d3dx8math.h @@ -52,6 +52,7 @@ typedef struct D3DXVECTOR4 #define D3DX_PI 3.141592654f +D3DXMATRIX *WINAPI D3DXMatrixIdentity(D3DXMATRIX *pOut); D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM); D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz); D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z); diff --git a/GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp b/GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp index baf4c99f9e9..5d6acc3524e 100644 --- a/GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp +++ b/GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp @@ -1,147 +1,190 @@ -// GLM_ENABLE_EXPERIMENTAL is passed via target_compile_definitions in CMakeLists.txt. -// Do NOT redefine it here -- that causes -Wmacro-redefined with Clang. - -#ifdef _WIN32 -#include -#else -#include "windows_compat.h" -#endif -#include "d3dx8core.h" - -#include "d3dx8math.h" - -#include -#include - - -static void ConvertGLMToD3DX (const glm::mat4x4 &glm, D3DXMATRIX &d3dx) -{ - d3dx._11 = glm[0][0]; - d3dx._12 = glm[1][0]; - d3dx._13 = glm[2][0]; - d3dx._14 = glm[3][0]; - - d3dx._21 = glm[0][1]; - d3dx._22 = glm[1][1]; - d3dx._23 = glm[2][1]; - d3dx._24 = glm[3][1]; - - d3dx._31 = glm[0][2]; - d3dx._32 = glm[1][2]; - d3dx._33 = glm[2][2]; - d3dx._34 = glm[3][2]; - - d3dx._41 = glm[0][3]; - d3dx._42 = glm[1][3]; - d3dx._43 = glm[2][3]; - d3dx._44 = glm[3][3]; -} - -static void ConvertD3DXToGLM (const D3DXMATRIX &d3dx, glm::mat4x4 &glm) -{ - glm[0][0] = d3dx._11; - glm[1][0] = d3dx._12; - glm[2][0] = d3dx._13; - glm[3][0] = d3dx._14; - - glm[0][1] = d3dx._21; - glm[1][1] = d3dx._22; - glm[2][1] = d3dx._23; - glm[3][1] = d3dx._24; - - glm[0][2] = d3dx._31; - glm[1][2] = d3dx._32; - glm[2][2] = d3dx._33; - glm[3][2] = d3dx._34; - - glm[0][3] = d3dx._41; - glm[1][3] = d3dx._42; - glm[2][3] = d3dx._43; - glm[3][3] = d3dx._44; -} - -D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM) -{ - glm::mat4x4 m; - ConvertD3DXToGLM(*pM, m); - - if (pDeterminant) - *pDeterminant = glm::determinant(m); - - glm::mat4x4 inv = glm::inverse(m); - ConvertGLMToD3DX(inv, *pOut); - - return pOut; -} - -D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz) -{ - glm::mat4x4 m = glm::scale(glm::mat4x4(1.0f), glm::vec3(sx, sy, sz)); - ConvertGLMToD3DX(m, *pOut); - return pOut; -} - -D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z) -{ - glm::mat4x4 m = glm::translate(glm::mat4x4(1.0f), glm::vec3(x, y, z)); - ConvertGLMToD3DX(m, *pOut); - return pOut; -} - -D3DXMATRIX *WINAPI D3DXMatrixMultiply(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM1, CONST D3DXMATRIX *pM2) -{ - glm::mat4x4 m1, m2; - ConvertD3DXToGLM(*pM1, m1); - ConvertD3DXToGLM(*pM2, m2); - - glm::mat4x4 m = m1 * m2; - ConvertGLMToD3DX(m, *pOut); - return pOut; -} - -D3DXVECTOR4 *WINAPI D3DXVec3Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR3 *pV, CONST D3DXMATRIX *pM) -{ - glm::vec4 v(pV->x, pV->y, pV->z, 1.0f); - glm::mat4x4 m; - ConvertD3DXToGLM(*pM, m); - - glm::vec4 result = m * v; - pOut->x = result.x; - pOut->y = result.y; - pOut->z = result.z; - pOut->w = result.w; - return pOut; -} - -D3DXMATRIX *WINAPI D3DXMatrixTranspose(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM) -{ - glm::mat4x4 m; - ConvertD3DXToGLM(*pM, m); - - glm::mat4x4 mTransposed; - mTransposed = glm::transpose(m); - - ConvertGLMToD3DX(mTransposed, *pOut); - return pOut; -} - -D3DXMATRIX *WINAPI D3DXMatrixRotationZ(D3DXMATRIX *pOut, FLOAT Angle) -{ - glm::mat4x4 m = glm::rotate(glm::mat4x4(1.0f), Angle, glm::vec3(0.0f, 0.0f, 1.0f)); - ConvertGLMToD3DX(m, *pOut); - return pOut; -} - -D3DXVECTOR4 *WINAPI D3DXVec4Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR4 *pV, CONST D3DXMATRIX *pM) -{ - glm::vec4 v(pV->x, pV->y, pV->z, pV->w); - glm::mat4x4 m; - ConvertD3DXToGLM(*pM, m); - - glm::vec4 result = m * v; - pOut->x = result.x; - pOut->y = result.y; - pOut->z = result.z; - pOut->w = result.w; - return pOut; -} \ No newline at end of file +// GLM_ENABLE_EXPERIMENTAL is passed via target_compile_definitions in CMakeLists.txt. +// Do NOT redefine it here -- that causes -Wmacro-redefined with Clang. + +#ifdef _WIN32 +#include +#else +#include "windows_compat.h" +#endif +#include "d3dx8core.h" + +#include "d3dx8math.h" + +#if defined(__has_include) && __has_include("gmath.h") +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmacro-redefined" +#endif +#include "gmath.h" +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#define USE_DETERMINISTIC_MATH (1) +#endif + +#include +#include + +// GeneralsX @bugfix fbraz 27/09/2026 Direct D3DX row-major matrix operations to fix cloud shadows, UV animation, and transformations + +static void ConvertGLMToD3DX (const glm::mat4x4 &glm, D3DXMATRIX &d3dx) +{ + d3dx._11 = glm[0][0]; + d3dx._12 = glm[1][0]; + d3dx._13 = glm[2][0]; + d3dx._14 = glm[3][0]; + + d3dx._21 = glm[0][1]; + d3dx._22 = glm[1][1]; + d3dx._23 = glm[2][1]; + d3dx._24 = glm[3][1]; + + d3dx._31 = glm[0][2]; + d3dx._32 = glm[1][2]; + d3dx._33 = glm[2][2]; + d3dx._34 = glm[3][2]; + + d3dx._41 = glm[0][3]; + d3dx._42 = glm[1][3]; + d3dx._43 = glm[2][3]; + d3dx._44 = glm[3][3]; +} + +static void ConvertD3DXToGLM (const D3DXMATRIX &d3dx, glm::mat4x4 &glm) +{ + glm[0][0] = d3dx._11; + glm[1][0] = d3dx._12; + glm[2][0] = d3dx._13; + glm[3][0] = d3dx._14; + + glm[0][1] = d3dx._21; + glm[1][1] = d3dx._22; + glm[2][1] = d3dx._23; + glm[3][1] = d3dx._24; + + glm[0][2] = d3dx._31; + glm[1][2] = d3dx._32; + glm[2][2] = d3dx._33; + glm[3][2] = d3dx._34; + + glm[0][3] = d3dx._41; + glm[1][3] = d3dx._42; + glm[2][3] = d3dx._43; + glm[3][3] = d3dx._44; +} + +D3DXMATRIX *WINAPI D3DXMatrixIdentity(D3DXMATRIX *pOut) +{ + if (!pOut) return nullptr; + pOut->m[0][0] = 1.0f; pOut->m[0][1] = 0.0f; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f; + pOut->m[1][0] = 0.0f; pOut->m[1][1] = 1.0f; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f; + pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = 1.0f; pOut->m[2][3] = 0.0f; + pOut->m[3][0] = 0.0f; pOut->m[3][1] = 0.0f; pOut->m[3][2] = 0.0f; pOut->m[3][3] = 1.0f; + return pOut; +} + +D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM) +{ + if (!pOut || !pM) return nullptr; + glm::mat4x4 m; + ConvertD3DXToGLM(*pM, m); + + if (pDeterminant) + *pDeterminant = glm::determinant(m); + + glm::mat4x4 inv = glm::inverse(m); + ConvertGLMToD3DX(inv, *pOut); + + return pOut; +} + +D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz) +{ + if (!pOut) return nullptr; + pOut->m[0][0] = sx; pOut->m[0][1] = 0.0f; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f; + pOut->m[1][0] = 0.0f; pOut->m[1][1] = sy; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f; + pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = sz; pOut->m[2][3] = 0.0f; + pOut->m[3][0] = 0.0f; pOut->m[3][1] = 0.0f; pOut->m[3][2] = 0.0f; pOut->m[3][3] = 1.0f; + return pOut; +} + +D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z) +{ + if (!pOut) return nullptr; + pOut->m[0][0] = 1.0f; pOut->m[0][1] = 0.0f; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f; + pOut->m[1][0] = 0.0f; pOut->m[1][1] = 1.0f; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f; + pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = 1.0f; pOut->m[2][3] = 0.0f; + pOut->m[3][0] = x; pOut->m[3][1] = y; pOut->m[3][2] = z; pOut->m[3][3] = 1.0f; + return pOut; +} + +D3DXMATRIX *WINAPI D3DXMatrixMultiply(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM1, CONST D3DXMATRIX *pM2) +{ + if (!pOut || !pM1 || !pM2) return nullptr; + D3DXMATRIX temp; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + temp.m[i][j] = pM1->m[i][0] * pM2->m[0][j] + + pM1->m[i][1] * pM2->m[1][j] + + pM1->m[i][2] * pM2->m[2][j] + + pM1->m[i][3] * pM2->m[3][j]; + } + } + *pOut = temp; + return pOut; +} + +D3DXVECTOR4 *WINAPI D3DXVec3Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR3 *pV, CONST D3DXMATRIX *pM) +{ + if (!pOut || !pV || !pM) return nullptr; + D3DXVECTOR4 temp; + temp.x = pV->x * pM->m[0][0] + pV->y * pM->m[1][0] + pV->z * pM->m[2][0] + pM->m[3][0]; + temp.y = pV->x * pM->m[0][1] + pV->y * pM->m[1][1] + pV->z * pM->m[2][1] + pM->m[3][1]; + temp.z = pV->x * pM->m[0][2] + pV->y * pM->m[1][2] + pV->z * pM->m[2][2] + pM->m[3][2]; + temp.w = pV->x * pM->m[0][3] + pV->y * pM->m[1][3] + pV->z * pM->m[2][3] + pM->m[3][3]; + *pOut = temp; + return pOut; +} + +D3DXMATRIX *WINAPI D3DXMatrixTranspose(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM) +{ + if (!pOut || !pM) return nullptr; + D3DXMATRIX temp; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + temp.m[i][j] = pM->m[j][i]; + } + } + *pOut = temp; + return pOut; +} + +D3DXMATRIX *WINAPI D3DXMatrixRotationZ(D3DXMATRIX *pOut, FLOAT Angle) +{ + if (!pOut) return nullptr; +#if defined(USE_DETERMINISTIC_MATH) + FLOAT fSin = gm_sinf(Angle); + FLOAT fCos = gm_cosf(Angle); +#else + FLOAT fSin = sinf(Angle); + FLOAT fCos = cosf(Angle); +#endif + + pOut->m[0][0] = fCos; pOut->m[0][1] = fSin; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f; + pOut->m[1][0] = -fSin; pOut->m[1][1] = fCos; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f; + pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = 1.0f; pOut->m[2][3] = 0.0f; + pOut->m[3][0] = 0.0f; pOut->m[3][1] = 0.0f; pOut->m[3][2] = 0.0f; pOut->m[3][3] = 1.0f; + return pOut; +} + +D3DXVECTOR4 *WINAPI D3DXVec4Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR4 *pV, CONST D3DXMATRIX *pM) +{ + if (!pOut || !pV || !pM) return nullptr; + D3DXVECTOR4 temp; + temp.x = pV->x * pM->m[0][0] + pV->y * pM->m[1][0] + pV->z * pM->m[2][0] + pV->w * pM->m[3][0]; + temp.y = pV->x * pM->m[0][1] + pV->y * pM->m[1][1] + pV->z * pM->m[2][1] + pV->w * pM->m[3][1]; + temp.z = pV->x * pM->m[0][2] + pV->y * pM->m[1][2] + pV->z * pM->m[2][2] + pV->w * pM->m[3][2]; + temp.w = pV->x * pM->m[0][3] + pV->y * pM->m[1][3] + pV->z * pM->m[2][3] + pV->w * pM->m[3][3]; + *pOut = temp; + return pOut; +} \ No newline at end of file diff --git a/docs/WORKLOG/2026-09-DIARY.md b/docs/WORKLOG/2026-09-DIARY.md index 3f3bde6c7c5..b38815c46b8 100644 --- a/docs/WORKLOG/2026-09-DIARY.md +++ b/docs/WORKLOG/2026-09-DIARY.md @@ -4,6 +4,30 @@ > **AI-Generated Content Disclosure**: This worklog is automatically generated and maintained by AI coding agents to document daily progress, debugging sessions, and technical decisions. ## 27/09/2026 +### Fix Cloud Shadows Animation & D3DX Row-Major Matrix Operations +- **Context**: In Command & Conquer: Generals and Zero Hour on Windows, animated cloud shadows scroll across the terrain when the cloud shadow setting (`UseCloudMap`) is enabled. On macOS and Linux, cloud shadows were completely static or invisible despite the option being enabled in `Options.ini` and Game LOD settings. +- **Root Cause**: + - In `GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp`, `D3DXMatrixTranslation`, `D3DXMatrixMultiply`, `D3DXMatrixRotationZ`, `D3DXVec3Transform`, and `D3DXVec4Transform` were implemented via GLM matrix functions assuming OpenGL column-vector conventions ($v' = M \cdot v$). + - In GLM, `glm::translate` places translation offsets $(x, y, z)$ into column 3 (`m[3][0]=x, m[3][1]=y, m[3][2]=z`). When converted to `D3DXMATRIX`, translation was mapped to column 4 (`_14, _24, _34`) instead of row 4 (`_41, _42, _43`) required by DirectX row-vector conventions ($v' = v \cdot M$). + - In addition, `ConvertD3DXToGLM` and `ConvertGLMToD3DX` caused `m1 * m2` in GLM to evaluate $(M_1^T \cdot M_2^T)^T = M_2 \cdot M_1$, reversing matrix multiplication order. + - When `TerrainShader2Stage::updateNoise1` calculated `*destMatrix = *curViewInverse * scale; *destMatrix *= offset;`, the combined matrix had 0 offset in `_41` and `_42`. In DXVK's fixed function vertex shader generator (`d3d9_fixed_function.cpp`), `D3DTS_TEXTURE2` transforms camera-space position into texture coordinates $(u, v)$ using row-major multiplication. Because `_41` and `_42` were zero, the animated sliding offset $(m\_xOffset, m\_yOffset)$ was completely lost. + - `D3DXMatrixRotationZ` also inverted rotation direction in `pointgr.cpp` particle rendering due to column-major vs row-major sine sign mapping. +- **Changes**: + - **Direct Row-Major D3DX Implementations (`GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp`)**: + - Replaced GLM helper delegation in `D3DXMatrixTranslation`, `D3DXMatrixScaling`, `D3DXMatrixMultiply`, `D3DXMatrixTranspose`, `D3DXMatrixRotationZ`, `D3DXVec3Transform`, and `D3DXVec4Transform` with direct, row-major DirectX implementations. + - Implemented `D3DXMatrixIdentity` directly. + - `D3DXMatrixTranslation` explicitly sets `m[3][0] = x`, `m[3][1] = y`, `m[3][2] = z`, `m[3][3] = 1.0f`. + - `D3DXMatrixMultiply` explicitly computes standard row-major multiplication into a temporary matrix to safely handle in-place multiplication (`*destMatrix *= offset`). + - Retained `D3DXMatrixInverse` via GLM since matrix inversion and determinant are algebraic invariants under transpose mapping. + - Updated `D3DXMatrixRotationZ` to utilize deterministic GameMath (`gm_sinf`, `gm_cosf`) when `USE_DETERMINISTIC_MATH` is enabled, linking `gamemath` in `GeneralsMD/Code/CompatLib/CMakeLists.txt` to enforce cross-platform replay determinism. + - **Header Synchronization**: + - Added `D3DXMatrixIdentity` declaration to `GeneralsMD/Code/CompatLib/Include/d3dx8math.h` and `Generals/Code/CompatLib/Include/d3dx8math.h` for full parity with base game and `W3DWater.cpp`. +- **Validation**: + - Built `d3dx8` static library cleanly with Clang. + - Built `GeneralsXZH` (Zero Hour) and `GeneralsX` (base game) targets cleanly with exit code 0. + - Deployed binaries and dylibs to `~/GeneralsX/GeneralsZH/` and `~/GeneralsX/Generals/`. + - Executed matrix transform verification suite confirming that `destMatrix` translation row `_41` and `_42` correctly receive scaled world offsets and animated sliding values (`m_xOffset`, `m_yOffset`), whereas old GLM code lost offsets into column 4. + ### Render OS Brand Glyphs in Lobby & Resolve CodeRabbit Review Findings (#338) - **Context**: Players in online multiplayer lobbies display operating system tags (`[WIN]`, `[MAC]`, `[LNX]`). To improve visual polish in the lobby roster, player names are formatted to replace text OS tags with Font Awesome 6 Brands glyphs (`0xF179` Apple, `0xF17A` Windows, `0xF17C` Linux). A dedicated brand fallback mechanism was introduced via `AlternateBrandFont` in `FontCharsClass`. - **CodeRabbit & Security Architecture Review Triage**: