Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Core/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ set(GAMEENGINE_SRC
Include/GameClient/ClientInstance.h
Include/GameClient/ClientRandomValue.h
Include/GameClient/Color.h
Include/GameClient/GlobalLightingModifier.h
# Include/GameClient/CommandXlat.h
# Include/GameClient/ControlBar.h
# Include/GameClient/ControlBarResizer.h
Expand Down Expand Up @@ -700,6 +701,7 @@ set(GAMEENGINE_SRC
Source/Common/WorkerProcess.cpp
Source/GameClient/ClientInstance.cpp
Source/GameClient/Color.cpp
Source/GameClient/GlobalLightingModifier.cpp
Source/GameClient/Credits.cpp
# Source/GameClient/Display.cpp
Source/GameClient/DisplayString.cpp
Expand Down
63 changes: 63 additions & 0 deletions Core/GameEngine/Include/GameClient/GlobalLightingModifier.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// FILE: GlobalLightingModifier.h ////////////////////////////////////////////////////////////////
// Client-side aggregator for "global lighting modifier" contributors (e.g. objects that darken /
// colorize / brighten the whole map's lighting while active). Contributors register themselves;
// the renderer asks for the combined multiply+add each frame and applies it to the scene lighting.
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "Lib/BaseType.h"
#include "Common/STLTypedefs.h"

//-------------------------------------------------------------------------------------------------
/** Anything that contributes to the global lighting modifier implements this. The contribution is
returned pre-scaled by the contributor's own fade weight / intensity: a multiply color (default
white = no change) and an additive color (default black = no change). */
//-------------------------------------------------------------------------------------------------
class LightingModifierContributor
{
public:
virtual ~LightingModifierContributor() {}
virtual void getLightingContribution( RGBColor& outMul, RGBColor& outAdd ) const = 0;
};

//-------------------------------------------------------------------------------------------------
/** Global registry + combiner. Presentation-only; not part of the deterministic sim. */
//-------------------------------------------------------------------------------------------------
class GlobalLightingModifierManager
{
public:
static GlobalLightingModifierManager& get( void );

void registerContributor( const LightingModifierContributor* c );
void unregisterContributor( const LightingModifierContributor* c );

/** Combined multiply (component-wise product) and additive (sum) across all contributors.
Returns identity (mul = 1,1,1 add = 0,0,0) when there are none. */
void computeCombined( RGBColor& outMul, RGBColor& outAdd ) const;

Bool hasActiveContributors( void ) const { return !m_contributors.empty(); }

private:
GlobalLightingModifierManager() {}

std::vector<const LightingModifierContributor*> m_contributors;
};
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ static PoolSizeRec PoolSizes[] =
{ "FireWeaponAdvancedUpdate", 32, 32 },
{ "FlammableUpdate", 512, 256 },
{ "FloatUpdate", 512, 128 },
{ "GlobalLightingModifierUpdate", 16, 16 },
{ "TensileFormationUpdate", 256, 32 },
{ "GarrisonContain", 256, 32 },
{ "HealCrateCollide", 32, 32 },
Expand Down
83 changes: 83 additions & 0 deletions Core/GameEngine/Source/GameClient/GlobalLightingModifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// FILE: GlobalLightingModifier.cpp //////////////////////////////////////////////////////////////

#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine

#include "GameClient/GlobalLightingModifier.h"

//-------------------------------------------------------------------------------------------------
/*static*/ GlobalLightingModifierManager& GlobalLightingModifierManager::get( void )
{
static GlobalLightingModifierManager theInstance;
return theInstance;
}

//-------------------------------------------------------------------------------------------------
void GlobalLightingModifierManager::registerContributor( const LightingModifierContributor* c )
{
if( c == nullptr )
return;
// avoid duplicate registration
for( std::vector<const LightingModifierContributor*>::const_iterator it = m_contributors.begin();
it != m_contributors.end(); ++it )
{
if( *it == c )
return;
}
m_contributors.push_back( c );
}

//-------------------------------------------------------------------------------------------------
void GlobalLightingModifierManager::unregisterContributor( const LightingModifierContributor* c )
{
for( std::vector<const LightingModifierContributor*>::iterator it = m_contributors.begin();
it != m_contributors.end(); ++it )
{
if( *it == c )
{
m_contributors.erase( it );
return;
}
}
}

//-------------------------------------------------------------------------------------------------
void GlobalLightingModifierManager::computeCombined( RGBColor& outMul, RGBColor& outAdd ) const
{
outMul.red = outMul.green = outMul.blue = 1.0f; // identity multiply
outAdd.red = outAdd.green = outAdd.blue = 0.0f; // identity add

for( std::vector<const LightingModifierContributor*>::const_iterator it = m_contributors.begin();
it != m_contributors.end(); ++it )
{
RGBColor mul, add;
mul.red = mul.green = mul.blue = 1.0f;
add.red = add.green = add.blue = 0.0f;
(*it)->getLightingContribution( mul, add );

// multiplies compound (darker stacks darker), additives sum (brighten/colorize stack)
outMul.red *= mul.red;
outMul.green *= mul.green;
outMul.blue *= mul.blue;
outAdd.red += add.red;
outAdd.green += add.green;
outAdd.blue += add.blue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class HeightMapRenderObjClass : public BaseHeightMapRenderObjClass
///update vertex buffers associated with the given rectangle
void initDestAlphaLUT(void); ///<initialize water depth LUT stored in m_destAlphaTexture
void renderTerrainPass(CameraClass *pCamera); ///< renders additional terrain pass.
void renderLightingModifierOverlay(void); ///< draw-time global lighting tint over the terrain footprint (no re-bake).
Int getNumExtraBlendTiles(Bool visible) { return visible?m_numVisibleExtraBlendTiles:m_numExtraBlendTiles;}
void freeIndexVertexBuffers(void);
void renderExtraBlendTiles(void); ///< render 3-way blend tiles that have blend of 3 textures.
Expand Down
96 changes: 96 additions & 0 deletions Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
#include "GameClient/TerrainVisual.h"
#include "GameClient/View.h"
#include "GameClient/Water.h"
#include "GameClient/GlobalLightingModifier.h"

#include "GameLogic/AIPathfind.h"
#include "GameLogic/TerrainLogic.h"
Expand Down Expand Up @@ -2102,6 +2103,11 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo)
if (TheTerrainTracksRenderObjClassSystem)
TheTerrainTracksRenderObjClassSystem->flush();

// Phase 2: draw-time global lighting tint over the terrain footprint (terrain + roads + scorches +
// bridges). Skipped on the reflection pass. Drawn before the shroud so fog-of-war stays black.
if (!ShaderClass::Is_Backface_Culling_Inverted() && GlobalLightingModifierManager::get().hasActiveContributors())
renderLightingModifierOverlay();

if (m_shroud && rinfo.Additional_Pass_Count())
{
rinfo.Peek_Additional_Pass(0)->Install_Materials();
Expand Down Expand Up @@ -2131,6 +2137,96 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo)



//-------------------------------------------------------------------------------------------------
static UnsignedInt packLightingColor01( Real r, Real g, Real b )
{
Int ir = REAL_TO_INT( r * 255.0f + 0.5f ); if (ir < 0) ir = 0; if (ir > 255) ir = 255;
Int ig = REAL_TO_INT( g * 255.0f + 0.5f ); if (ig < 0) ig = 0; if (ig > 255) ig = 255;
Int ib = REAL_TO_INT( b * 255.0f + 0.5f ); if (ib < 0) ib = 0; if (ib > 255) ib = 255;
return 0xff000000 | (((UnsignedInt)ir) << 16) | (((UnsignedInt)ig) << 8) | ((UnsignedInt)ib);
}

//-------------------------------------------------------------------------------------------------
/** Draw-time global lighting tint: re-draws the terrain tiles as flat-color overlay passes that
multiply and/or add the combined lighting-modifier color over whatever is already in the frame
buffer within the terrain footprint (terrain + roads + scorches + bridges). Cheap (no vertex
re-bake); only runs when a modifier is active. Depth test is ALWAYS so on-terrain features at a
slight z-offset are tinted too; z-write is off and objects render afterwards, so objects are
unaffected (they get their own tint from the object light-environment path). */
//-------------------------------------------------------------------------------------------------
void HeightMapRenderObjClass::renderLightingModifierOverlay(void)
{
RGBColor mul, add;
GlobalLightingModifierManager::get().computeCombined( mul, add );

const Bool doMul = (mul.red < 0.999f || mul.green < 0.999f || mul.blue < 0.999f);
const Bool doAdd = (add.red > 0.001f || add.green > 0.001f || add.blue > 0.001f);
if (!doMul && !doAdd)
return;

// build the multiply / additive overlay shaders once (untextured solid, depth-test always, no z-write)
static Bool s_init = false;
static ShaderClass s_mulShader;
static ShaderClass s_addShader;
if (!s_init)
{
s_mulShader = ShaderClass::_PresetOpaqueSolidShader;
s_mulShader.Set_Depth_Mask( ShaderClass::DEPTH_WRITE_DISABLE );
s_mulShader.Set_Depth_Compare( ShaderClass::PASS_ALWAYS );
s_mulShader.Set_Src_Blend_Func( ShaderClass::SRCBLEND_ZERO );
s_mulShader.Set_Dst_Blend_Func( ShaderClass::DSTBLEND_SRC_COLOR );

s_addShader = s_mulShader;
s_addShader.Set_Src_Blend_Func( ShaderClass::SRCBLEND_ONE );
s_addShader.Set_Dst_Blend_Func( ShaderClass::DSTBLEND_ONE );

s_init = true;
}

Matrix3D tm(Transform);
DX8Wrapper::Set_Texture(0, nullptr);
DX8Wrapper::Set_Texture(1, nullptr);
m_stageTwoTexture->restore();
ShaderClass::Invalidate();
DX8Wrapper::Set_Material(m_vertexMaterialClass);
DX8Wrapper::Set_Transform(D3DTS_WORLD, tm);
DX8Wrapper::Set_Index_Buffer(m_indexBuffer, 0);

for (Int passIndex = 0; passIndex < 2; passIndex++)
{
if (passIndex == 0 && !doMul) continue;
if (passIndex == 1 && !doAdd) continue;

DX8Wrapper::Set_Shader( passIndex == 0 ? s_mulShader : s_addShader );
DX8Wrapper::Set_Texture(0, nullptr);
DX8Wrapper::Apply_Render_State_Changes();

// force stage 0 to emit a flat constant color from TFACTOR (ignore vertex diffuse / textures)
DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_COLOROP, D3DTOP_DISABLE);

UnsignedInt col = (passIndex == 0) ? packLightingColor01(mul.red, mul.green, mul.blue)
: packLightingColor01(add.red, add.green, add.blue);
DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, col);

for (Int j = 0; j < m_numVBTilesY; j++)
for (Int i = 0; i < m_numVBTilesX; i++)
{
DX8Wrapper::Set_Vertex_Buffer(getVertexBufferTile(i, j));
if (Is_Hidden() == 0)
DX8Wrapper::Draw_Triangles(0, HEIGHTMAP_POLYGON_NUM, 0, HEIGHTMAP_VERTEX_NUM);
}
}

// leave the pipeline in a sane state for subsequent draws
DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0xffffffff);
DX8Wrapper::Set_Texture(0, nullptr);
DX8Wrapper::Set_Texture(1, nullptr);
ShaderClass::Invalidate();
}

///Performs additional terrain rendering pass, blending in the black shroud texture.
void HeightMapRenderObjClass::renderTerrainPass(CameraClass *pCamera)
{
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/FlightDeckBehavior.h
Include/GameLogic/Module/DroneCarrierAIUpdate.h
Include/GameLogic/Module/FloatUpdate.h
Include/GameLogic/Module/GlobalLightingModifierUpdate.h
Include/GameLogic/Module/FXListDie.h
Include/GameLogic/Module/GarrisonContain.h
Include/GameLogic/Module/GenerateMinefieldBehavior.h
Expand Down Expand Up @@ -1085,6 +1086,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Update/FireWeaponAdvancedUpdate.cpp
Source/GameLogic/Object/Update/FlammableUpdate.cpp
Source/GameLogic/Object/Update/FloatUpdate.cpp
Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp
Source/GameLogic/Object/Update/HeightDieUpdate.cpp
Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp
Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// FILE: GlobalLightingModifierUpdate.h //////////////////////////////////////////////////////////
// Desc: While active, this module contributes a darken/colorize/brighten shift to the GLOBAL map
// lighting (Phase 1: object lighting). Multiple instances aggregate. Fades in/out on
// activation. Presentation-only: it registers with the client-side lighting manager and
// never feeds back into the deterministic sim.
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "GameLogic/Module/UpdateModule.h"
#include "GameClient/GlobalLightingModifier.h"

//-------------------------------------------------------------------------------------------------
class GlobalLightingModifierUpdateModuleData : public UpdateModuleData
{
public:
enum LightingBlendMode CPP_11(: Int)
{
LIGHTINGMOD_MULTIPLY = 0, ///< multiply lighting toward TargetColor (darken / tint)
LIGHTINGMOD_ADDITIVE ///< add TargetColor to lighting (brighten / additive tint)
};

RGBColor m_targetColor; ///< the color to multiply/add toward
Int m_blendMode; ///< LightingBlendMode
Real m_intensity; ///< 0..1 (or higher for ADDITIVE) strength at full fade
UnsignedInt m_initialDelayFrames; ///< after the gate is met, wait this long before activating (0 = none)
UnsignedInt m_durationFrames; ///< once activated, stay active this long then fade out (0 = infinite)
UnsignedInt m_fadeInFrames; ///< frames to ramp in when activated
UnsignedInt m_fadeOutFrames; ///< frames to ramp out when deactivated
AsciiString m_requiredUpgrade; ///< if set, only active while this upgrade is owned (else always active while alive)

GlobalLightingModifierUpdateModuleData();
static void buildFieldParse(MultiIniFieldParse& p);
};

//-------------------------------------------------------------------------------------------------
class GlobalLightingModifierUpdate : public UpdateModule, public LightingModifierContributor
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( GlobalLightingModifierUpdate, "GlobalLightingModifierUpdate" )
MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( GlobalLightingModifierUpdate, GlobalLightingModifierUpdateModuleData )

public:

GlobalLightingModifierUpdate( Thing *thing, const ModuleData* moduleData );
// virtual destructor prototype provided by memory pool declaration

virtual UpdateSleepTime update();

// LightingModifierContributor
virtual void getLightingContribution( RGBColor& outMul, RGBColor& outAdd ) const;

protected:

Bool computeConditionMet( void ) const; ///< is the activation gate satisfied right now (alive + upgrade)?

Real m_weight; ///< current fade weight 0..1
Bool m_triggered; ///< has the gate been met and the timed cycle started?
UnsignedInt m_triggerFrame; ///< frame the gate was first met (start of InitialDelay)
};
Loading
Loading