From 03364d1d7098e12b63c54ebcf7248ff58c54a6b5 Mon Sep 17 00:00:00 2001 From: Andi Date: Fri, 14 Aug 2026 11:28:22 +0200 Subject: [PATCH 1/2] Phase 1 implemented --- Core/GameEngine/CMakeLists.txt | 2 + .../GameClient/GlobalLightingModifier.h | 63 ++++++ .../System/GameMemoryInitPools_GeneralsMD.inl | 1 + .../GameClient/GlobalLightingModifier.cpp | 83 ++++++++ GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../Module/GlobalLightingModifierUpdate.h | 74 +++++++ .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../Update/GlobalLightingModifierUpdate.cpp | 189 ++++++++++++++++++ .../Source/W3DDevice/GameClient/W3DScene.cpp | 56 ++++++ 9 files changed, 472 insertions(+) create mode 100644 Core/GameEngine/Include/GameClient/GlobalLightingModifier.h create mode 100644 Core/GameEngine/Source/GameClient/GlobalLightingModifier.cpp create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 4de35b21a25..cc84e9a899b 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -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 @@ -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 diff --git a/Core/GameEngine/Include/GameClient/GlobalLightingModifier.h b/Core/GameEngine/Include/GameClient/GlobalLightingModifier.h new file mode 100644 index 00000000000..bf541d41fee --- /dev/null +++ b/Core/GameEngine/Include/GameClient/GlobalLightingModifier.h @@ -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 . +*/ + +// 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 m_contributors; +}; diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index dda88f88f06..2bd5458d31f 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -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 }, diff --git a/Core/GameEngine/Source/GameClient/GlobalLightingModifier.cpp b/Core/GameEngine/Source/GameClient/GlobalLightingModifier.cpp new file mode 100644 index 00000000000..396f4e2449e --- /dev/null +++ b/Core/GameEngine/Source/GameClient/GlobalLightingModifier.cpp @@ -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 . +*/ + +// 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_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::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_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; + } +} diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index a4693497647..c847f8c8ea1 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -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 @@ -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 diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h new file mode 100644 index 00000000000..b9810d1b7d9 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h @@ -0,0 +1,74 @@ +/* +** 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 . +*/ + +// 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_DARKEN = 0, ///< multiply lighting toward TargetColor (use a dark color) + LIGHTINGMOD_COLORIZE, ///< multiply lighting toward TargetColor (tint) + LIGHTINGMOD_BRIGHTEN ///< add TargetColor to lighting + }; + + RGBColor m_targetColor; ///< the color to multiply/add toward + Int m_blendMode; ///< LightingBlendMode + Real m_intensity; ///< 0..1 (or higher for BRIGHTEN) strength at full fade + 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 computeActive( void ) const; ///< is this instance currently contributing (before fade)? + + Real m_weight; ///< current fade weight 0..1 +}; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 5942b811a89..d520be35b95 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -134,6 +134,7 @@ #include "GameLogic/Module/FireWeaponAdvancedUpdate.h" #include "GameLogic/Module/FlammableUpdate.h" #include "GameLogic/Module/FloatUpdate.h" +#include "GameLogic/Module/GlobalLightingModifierUpdate.h" #include "GameLogic/Module/TensileFormationUpdate.h" #include "GameLogic/Module/HackInternetAIUpdate.h" #include "GameLogic/Module/DeployStyleAIUpdate.h" @@ -466,6 +467,7 @@ void ModuleFactory::init( void ) addModule( FireWeaponAdvancedUpdate ); addModule( FlammableUpdate ); addModule( FloatUpdate ); + addModule( GlobalLightingModifierUpdate ); addModule( TensileFormationUpdate ); addModule( HeightDieUpdate ); addModule( ScatterShotUpdate ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp new file mode 100644 index 00000000000..058c1cefd5a --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp @@ -0,0 +1,189 @@ +/* +** 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 . +*/ + +// FILE: GlobalLightingModifierUpdate.cpp //////////////////////////////////////////////////////// + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#include "Common/Player.h" +#include "Common/Upgrade.h" +#include "Common/Xfer.h" +#include "GameLogic/Object.h" +#include "GameLogic/Module/GlobalLightingModifierUpdate.h" + +//------------------------------------------------------------------------------------------------- +static const char* const TheLightingBlendModeNames[] = +{ + "DARKEN", + "COLORIZE", + "BRIGHTEN", + nullptr +}; + +//------------------------------------------------------------------------------------------------- +GlobalLightingModifierUpdateModuleData::GlobalLightingModifierUpdateModuleData() +{ + m_targetColor.red = m_targetColor.green = m_targetColor.blue = 0.0f; + m_blendMode = LIGHTINGMOD_DARKEN; + m_intensity = 1.0f; + m_fadeInFrames = 1; + m_fadeOutFrames = 1; + // m_requiredUpgrade defaults empty -> always active while alive +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void GlobalLightingModifierUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpdateModuleData::buildFieldParse( p ); + + static const FieldParse dataFieldParse[] = + { + { "TargetColor", INI::parseRGBColor, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_targetColor ) }, + { "BlendMode", INI::parseIndexList, TheLightingBlendModeNames, offsetof( GlobalLightingModifierUpdateModuleData, m_blendMode ) }, + { "Intensity", INI::parseReal, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_intensity ) }, + { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_fadeInFrames ) }, + { "FadeOutTime", INI::parseDurationUnsignedInt, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_fadeOutFrames ) }, + { "RequiredUpgrade", INI::parseAsciiString, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_requiredUpgrade ) }, + { nullptr, nullptr, nullptr, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +GlobalLightingModifierUpdate::GlobalLightingModifierUpdate( Thing *thing, const ModuleData* moduleData ) : + UpdateModule( thing, moduleData ), + m_weight( 0.0f ) // start faded out; ramps in +{ + GlobalLightingModifierManager::get().registerContributor( this ); +} + +//------------------------------------------------------------------------------------------------- +GlobalLightingModifierUpdate::~GlobalLightingModifierUpdate( void ) +{ + GlobalLightingModifierManager::get().unregisterContributor( this ); +} + +//------------------------------------------------------------------------------------------------- +Bool GlobalLightingModifierUpdate::computeActive( void ) const +{ + const Object* obj = getObject(); + if( obj == nullptr || obj->isEffectivelyDead() ) + return FALSE; + + const GlobalLightingModifierUpdateModuleData* d = getGlobalLightingModifierUpdateModuleData(); + if( d->m_requiredUpgrade.isNotEmpty() ) + { + const UpgradeTemplate* ut = TheUpgradeCenter->findUpgrade( d->m_requiredUpgrade ); + if( ut == nullptr ) + return FALSE; + if( obj->hasUpgrade( ut ) ) + return TRUE; + const Player* p = obj->getControllingPlayer(); + if( p != nullptr && p->getCompletedUpgradeMask().testForAll( ut->getUpgradeMask() ) ) + return TRUE; + return FALSE; + } + + return TRUE; +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime GlobalLightingModifierUpdate::update( void ) +{ + const GlobalLightingModifierUpdateModuleData* d = getGlobalLightingModifierUpdateModuleData(); + + Real target = computeActive() ? 1.0f : 0.0f; + + if( m_weight < target ) + { + Real step = ( d->m_fadeInFrames > 0 ) ? ( 1.0f / (Real)d->m_fadeInFrames ) : 1.0f; + m_weight += step; + if( m_weight > target ) + m_weight = target; + } + else if( m_weight > target ) + { + Real step = ( d->m_fadeOutFrames > 0 ) ? ( 1.0f / (Real)d->m_fadeOutFrames ) : 1.0f; + m_weight -= step; + if( m_weight < target ) + m_weight = target; + } + + return UPDATE_SLEEP_NONE; +} + +//------------------------------------------------------------------------------------------------- +void GlobalLightingModifierUpdate::getLightingContribution( RGBColor& outMul, RGBColor& outAdd ) const +{ + const GlobalLightingModifierUpdateModuleData* d = getGlobalLightingModifierUpdateModuleData(); + + Real s = d->m_intensity * m_weight; + if( s < 0.0f ) + s = 0.0f; + + outMul.red = outMul.green = outMul.blue = 1.0f; + outAdd.red = outAdd.green = outAdd.blue = 0.0f; + + const RGBColor& c = d->m_targetColor; + switch( d->m_blendMode ) + { + case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_BRIGHTEN: + // additive: add the target color scaled by strength + outAdd.red = c.red * s; + outAdd.green = c.green * s; + outAdd.blue = c.blue * s; + break; + + case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_DARKEN: + case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_COLORIZE: + default: + // multiplicative: lerp each channel from 1 (no change) toward the target color + outMul.red = 1.0f + ( c.red - 1.0f ) * s; + outMul.green = 1.0f + ( c.green - 1.0f ) * s; + outMul.blue = 1.0f + ( c.blue - 1.0f ) * s; + break; + } +} + +//------------------------------------------------------------------------------------------------- +void GlobalLightingModifierUpdate::crc( Xfer *xfer ) +{ + UpdateModule::crc( xfer ); +} + +//------------------------------------------------------------------------------------------------- +/** Xfer method + * Version Info: + * 1: Initial version */ +//------------------------------------------------------------------------------------------------- +void GlobalLightingModifierUpdate::xfer( Xfer *xfer ) +{ + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + UpdateModule::xfer( xfer ); + + xfer->xferReal( &m_weight ); +} + +//------------------------------------------------------------------------------------------------- +void GlobalLightingModifierUpdate::loadPostProcess( void ) +{ + UpdateModule::loadPostProcess(); +} diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 51263250c6d..684f6bfd12d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -47,6 +47,7 @@ #include "GameClient/ParticleSys.h" #include "GameClient/Color.h" #include "GameClient/View.h" +#include "GameClient/GlobalLightingModifier.h" #include "W3DDevice/GameClient/HeightMap.h" #include "W3DDevice/GameClient/W3DScene.h" #include "W3DDevice/GameClient/W3DDynamicLight.h" @@ -988,11 +989,66 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) { if (m_customPassMode == SCENE_PASS_DEFAULT) { + // Global lighting modifier (Phase 1: object lighting). If any modifier objects are active, + // tint the global directional lights + scene ambient for this frame's object lighting, then + // restore afterward. Terrain (baked) is not affected in Phase 1. + GlobalLightingModifierManager& lmMgr = GlobalLightingModifierManager::get(); + Bool lmActive = lmMgr.hasActiveContributors(); + Vector3 lmSavedAmbient; + Vector3 lmSavedDiffuse[LightEnvironmentClass::MAX_LIGHTS]; + Vector3 lmSavedLightAmbient[LightEnvironmentClass::MAX_LIGHTS]; + if (lmActive) + { + RGBColor lmMul, lmAdd; + lmMgr.computeCombined(lmMul, lmAdd); + + lmSavedAmbient = Get_Ambient_Light(); + Vector3 amb = lmSavedAmbient; + amb.X = amb.X * lmMul.red + lmAdd.red; if (amb.X < 0.0f) amb.X = 0.0f; + amb.Y = amb.Y * lmMul.green + lmAdd.green; if (amb.Y < 0.0f) amb.Y = 0.0f; + amb.Z = amb.Z * lmMul.blue + lmAdd.blue; if (amb.Z < 0.0f) amb.Z = 0.0f; + Set_Ambient_Light(amb); + + for (Int lmi = 0; lmi < m_numGlobalLights; lmi++) + { + LightClass* lmL = m_globalLight[lmi]; + if (lmL == NULL) + continue; + lmL->Get_Diffuse(&lmSavedDiffuse[lmi]); + lmL->Get_Ambient(&lmSavedLightAmbient[lmi]); + + Vector3 d = lmSavedDiffuse[lmi]; + d.X = d.X * lmMul.red + lmAdd.red; if (d.X < 0.0f) d.X = 0.0f; + d.Y = d.Y * lmMul.green + lmAdd.green; if (d.Y < 0.0f) d.Y = 0.0f; + d.Z = d.Z * lmMul.blue + lmAdd.blue; if (d.Z < 0.0f) d.Z = 0.0f; + lmL->Set_Diffuse(d); + + Vector3 a = lmSavedLightAmbient[lmi]; + a.X = a.X * lmMul.red + lmAdd.red; if (a.X < 0.0f) a.X = 0.0f; + a.Y = a.Y * lmMul.green + lmAdd.green; if (a.Y < 0.0f) a.Y = 0.0f; + a.Z = a.Z * lmMul.blue + lmAdd.blue; if (a.Z < 0.0f) a.Z = 0.0f; + lmL->Set_Ambient(a); + } + } + //Regular rendering pass with no effects updatePlayerColorPasses();///@todo: this probably doesn't need to be done each frame. updateFixedLightEnvironments(rinfo); Customized_Render(rinfo); Flush(rinfo); + + if (lmActive) + { + Set_Ambient_Light(lmSavedAmbient); + for (Int lmi = 0; lmi < m_numGlobalLights; lmi++) + { + LightClass* lmL = m_globalLight[lmi]; + if (lmL == NULL) + continue; + lmL->Set_Diffuse(lmSavedDiffuse[lmi]); + lmL->Set_Ambient(lmSavedLightAmbient[lmi]); + } + } } else if (m_customPassMode == SCENE_PASS_ALPHA_MASK) { From 07cef8edbfcde5839ce42a7305d66ff4d7fcf13f Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 16 Aug 2026 11:11:26 +0200 Subject: [PATCH 2/2] update module --- .../Include/W3DDevice/GameClient/HeightMap.h | 1 + .../Source/W3DDevice/GameClient/HeightMap.cpp | 96 +++++++++++++++++++ .../Module/GlobalLightingModifierUpdate.h | 13 ++- .../Update/GlobalLightingModifierUpdate.cpp | 66 ++++++++++--- 4 files changed, 159 insertions(+), 17 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/HeightMap.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/HeightMap.h index 6352058dcf1..a7758d19615 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/HeightMap.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/HeightMap.h @@ -113,6 +113,7 @@ class HeightMapRenderObjClass : public BaseHeightMapRenderObjClass ///update vertex buffers associated with the given rectangle void initDestAlphaLUT(void); ///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(); @@ -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) { diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h index b9810d1b7d9..499a8b632f4 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GlobalLightingModifierUpdate.h @@ -34,14 +34,15 @@ class GlobalLightingModifierUpdateModuleData : public UpdateModuleData public: enum LightingBlendMode CPP_11(: Int) { - LIGHTINGMOD_DARKEN = 0, ///< multiply lighting toward TargetColor (use a dark color) - LIGHTINGMOD_COLORIZE, ///< multiply lighting toward TargetColor (tint) - LIGHTINGMOD_BRIGHTEN ///< add TargetColor to lighting + 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 BRIGHTEN) strength at full fade + 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) @@ -68,7 +69,9 @@ class GlobalLightingModifierUpdate : public UpdateModule, public LightingModifie protected: - Bool computeActive( void ) const; ///< is this instance currently contributing (before fade)? + 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) }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp index 058c1cefd5a..2e76e865166 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/GlobalLightingModifierUpdate.cpp @@ -23,15 +23,15 @@ #include "Common/Player.h" #include "Common/Upgrade.h" #include "Common/Xfer.h" +#include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/Module/GlobalLightingModifierUpdate.h" //------------------------------------------------------------------------------------------------- static const char* const TheLightingBlendModeNames[] = { - "DARKEN", - "COLORIZE", - "BRIGHTEN", + "MULTIPLY", + "ADDITIVE", nullptr }; @@ -39,8 +39,10 @@ static const char* const TheLightingBlendModeNames[] = GlobalLightingModifierUpdateModuleData::GlobalLightingModifierUpdateModuleData() { m_targetColor.red = m_targetColor.green = m_targetColor.blue = 0.0f; - m_blendMode = LIGHTINGMOD_DARKEN; + m_blendMode = LIGHTINGMOD_MULTIPLY; m_intensity = 1.0f; + m_initialDelayFrames = 0; // no delay + m_durationFrames = 0; // infinite (stays active until gate drops) m_fadeInFrames = 1; m_fadeOutFrames = 1; // m_requiredUpgrade defaults empty -> always active while alive @@ -56,6 +58,8 @@ GlobalLightingModifierUpdateModuleData::GlobalLightingModifierUpdateModuleData() { "TargetColor", INI::parseRGBColor, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_targetColor ) }, { "BlendMode", INI::parseIndexList, TheLightingBlendModeNames, offsetof( GlobalLightingModifierUpdateModuleData, m_blendMode ) }, { "Intensity", INI::parseReal, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_intensity ) }, + { "InitialDelay", INI::parseDurationUnsignedInt, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_initialDelayFrames ) }, + { "Duration", INI::parseDurationUnsignedInt, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_durationFrames ) }, { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_fadeInFrames ) }, { "FadeOutTime", INI::parseDurationUnsignedInt, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_fadeOutFrames ) }, { "RequiredUpgrade", INI::parseAsciiString, nullptr, offsetof( GlobalLightingModifierUpdateModuleData, m_requiredUpgrade ) }, @@ -67,7 +71,9 @@ GlobalLightingModifierUpdateModuleData::GlobalLightingModifierUpdateModuleData() //------------------------------------------------------------------------------------------------- GlobalLightingModifierUpdate::GlobalLightingModifierUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ), - m_weight( 0.0f ) // start faded out; ramps in + m_weight( 0.0f ), // start faded out; ramps in + m_triggered( FALSE ), + m_triggerFrame( 0 ) { GlobalLightingModifierManager::get().registerContributor( this ); } @@ -79,7 +85,7 @@ GlobalLightingModifierUpdate::~GlobalLightingModifierUpdate( void ) } //------------------------------------------------------------------------------------------------- -Bool GlobalLightingModifierUpdate::computeActive( void ) const +Bool GlobalLightingModifierUpdate::computeConditionMet( void ) const { const Object* obj = getObject(); if( obj == nullptr || obj->isEffectivelyDead() ) @@ -107,7 +113,37 @@ UpdateSleepTime GlobalLightingModifierUpdate::update( void ) { const GlobalLightingModifierUpdateModuleData* d = getGlobalLightingModifierUpdateModuleData(); - Real target = computeActive() ? 1.0f : 0.0f; + // Determine the timed activation target (0 or 1). + Real target; + if( !computeConditionMet() ) + { + // gate not satisfied (dead, or required upgrade lost): fade out and allow a future re-trigger + m_triggered = FALSE; + target = 0.0f; + } + else + { + UnsignedInt now = TheGameLogic->getFrame(); + if( !m_triggered ) + { + m_triggered = TRUE; + m_triggerFrame = now; // start of InitialDelay + } + + UnsignedInt elapsed = now - m_triggerFrame; + if( elapsed < d->m_initialDelayFrames ) + { + target = 0.0f; // still delayed + } + else + { + UnsignedInt activeElapsed = elapsed - d->m_initialDelayFrames; + if( d->m_durationFrames == 0 || activeElapsed < d->m_durationFrames ) + target = 1.0f; // active + else + target = 0.0f; // duration expired -> fade out (stays done while the gate holds) + } + } if( m_weight < target ) { @@ -142,15 +178,14 @@ void GlobalLightingModifierUpdate::getLightingContribution( RGBColor& outMul, RG const RGBColor& c = d->m_targetColor; switch( d->m_blendMode ) { - case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_BRIGHTEN: + case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_ADDITIVE: // additive: add the target color scaled by strength outAdd.red = c.red * s; outAdd.green = c.green * s; outAdd.blue = c.blue * s; break; - case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_DARKEN: - case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_COLORIZE: + case GlobalLightingModifierUpdateModuleData::LIGHTINGMOD_MULTIPLY: default: // multiplicative: lerp each channel from 1 (no change) toward the target color outMul.red = 1.0f + ( c.red - 1.0f ) * s; @@ -169,17 +204,24 @@ void GlobalLightingModifierUpdate::crc( Xfer *xfer ) //------------------------------------------------------------------------------------------------- /** Xfer method * Version Info: - * 1: Initial version */ + * 1: Initial version + * 2: Added timed activation state (m_triggered, m_triggerFrame) */ //------------------------------------------------------------------------------------------------- void GlobalLightingModifierUpdate::xfer( Xfer *xfer ) { - XferVersion currentVersion = 1; + XferVersion currentVersion = 2; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); UpdateModule::xfer( xfer ); xfer->xferReal( &m_weight ); + + if( version >= 2 ) + { + xfer->xferBool( &m_triggered ); + xfer->xferUnsignedInt( &m_triggerFrame ); + } } //-------------------------------------------------------------------------------------------------