diff --git a/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index 5251fc4e244..782db55df3a 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -481,11 +481,8 @@ class DecalFXNugget : public FXNugget DecalFXNugget() { - m_templateName.set("GenericDecal"); // TODO - //m_templateName = AsciiString::TheEmptyString; - //m_textureName = AsciiString::TheEmptyString; - //m_opacity = 1.0; ///< value between 0 and 1 - //m_color = 0; ///< color in ARGB format. (Alpha is ignored). + // m_templateNames left empty; doFXPos falls back to "GenericDecal" if none listed. + m_scale.setRange(1.0f, 1.0f, GameClientRandomVariable::CONSTANT); // default = no scale variance m_lifetime = 0; /* m_fadeOutTime = 0; m_fadeInTime = 0; @@ -514,7 +511,12 @@ class DecalFXNugget : public FXNugget } } - Drawable* drawable = TheThingFactory->newDrawable(TheThingFactory->findTemplate(m_templateName)); + // pick one of the listed decal templates at random (fall back to GenericDecal if none listed) + AsciiString tmplName = m_templateNames.empty() + ? AsciiString("GenericDecal") + : m_templateNames[GameClientRandomValue(0, (Int)m_templateNames.size() - 1)]; + + Drawable* drawable = TheThingFactory->newDrawable(TheThingFactory->findTemplate(tmplName)); if (!drawable) return; @@ -531,6 +533,10 @@ class DecalFXNugget : public FXNugget if (m_randomAngle) drawable->setOrientation(GameClientRandomValueReal(0, PI * 2)); + // apply per-spawn random uniform scale variance (default range 1..1 = no change); + // W3DDecalDraw multiplies its decal size by the drawable's instance scale. + drawable->setInstanceScale(drawable->getInstanceScale() * m_scale.getValue()); + drawable->setExpirationDate(TheGameLogic->getFrame() + m_lifetime); } else @@ -555,7 +561,8 @@ class DecalFXNugget : public FXNugget { static const FieldParse myFieldParse[] = { - { "DecalName", INI::parseAsciiString, nullptr, offsetof(DecalFXNugget, m_templateName) }, + { "DecalName", INI::parseAsciiStringVectorAppend, nullptr, offsetof(DecalFXNugget, m_templateNames) }, + { "Scale", INI::parseGameClientRandomVariable, nullptr, offsetof(DecalFXNugget, m_scale) }, { "Lifetime", INI::parseDurationUnsignedInt, nullptr, offsetof(DecalFXNugget, m_lifetime) }, { "Offset", INI::parseCoord3D, nullptr, offsetof(DecalFXNugget, m_offset) }, { "Angle", INI::parseReal, nullptr, offsetof(DecalFXNugget, m_angle) }, @@ -571,7 +578,8 @@ class DecalFXNugget : public FXNugget } private: - AsciiString m_templateName; + std::vector m_templateNames; ///< one is picked at random per spawn ("DecalName", repeatable) + GameClientRandomVariable m_scale; ///< random uniform size factor per spawn ("Scale = low high") UnsignedInt m_lifetime; Coord3D m_offset; Real m_angle; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h index a7e781bf799..c203c809fbd 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h @@ -49,6 +49,7 @@ class W3DDecalDrawModuleData : public ModuleData ShadowType m_type; /// type of projection Real m_decalSizeX; /// 1/(world space extent of texture in x direction) Real m_decalSizeY; /// 1/(world space extent of texture in y direction) + Bool m_renderAboveWater; /// if true, this decal draws above water (else below, shadow-like) W3DDecalDrawModuleData(); ~W3DDecalDrawModuleData(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index 3027403ad7d..ce0c5768998 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -57,6 +57,7 @@ #include #include "Common/GlobalData.h" +#include "Common/MapData.h" #include "Common/PerfTimer.h" #include "Common/Xfer.h" @@ -602,10 +603,10 @@ void BaseHeightMapRenderObjClass::doTheLight(VERTEX_FORMAT *vb, Vector3*light, V // --------------------------------------------- // Height based ambient light factor (for water) // --------------------------------------------- - if (TheGlobalData && - TheGlobalData->m_terrainHeightAmbientLightHeightStart > 0 && - (TheGlobalData->m_terrainHeightAmbientLightHeight1 >= 0 || TheGlobalData->m_terrainHeightAmbientLightHeight2 >= 0) && - vb->z <= TheGlobalData->m_terrainHeightAmbientLightHeightStart + if (TheMapData && + TheMapData->m_terrainHeightAmbientLightHeightStart > 0 && + (TheMapData->m_terrainHeightAmbientLightHeight1 >= 0 || TheMapData->m_terrainHeightAmbientLightHeight2 >= 0) && + vb->z <= TheMapData->m_terrainHeightAmbientLightHeightStart ) { Real col1R, col1G, col1B, col2R, col2G, col2B; //Real factor1 = 0.0; @@ -618,33 +619,33 @@ void BaseHeightMapRenderObjClass::doTheLight(VERTEX_FORMAT *vb, Vector3*light, V Real colB; // case 1: only one color - if (TheGlobalData->m_terrainHeightAmbientLightHeight1 <= 0) + if (TheMapData->m_terrainHeightAmbientLightHeight1 <= 0) { - col1R = TheGlobalData->m_terrainHeightAmbientLightColor2.red; - col1G = TheGlobalData->m_terrainHeightAmbientLightColor2.green; - col1B = TheGlobalData->m_terrainHeightAmbientLightColor2.blue; - height1 = TheGlobalData->m_terrainHeightAmbientLightHeight2; + col1R = TheMapData->m_terrainHeightAmbientLightColor2.red; + col1G = TheMapData->m_terrainHeightAmbientLightColor2.green; + col1B = TheMapData->m_terrainHeightAmbientLightColor2.blue; + height1 = TheMapData->m_terrainHeightAmbientLightHeight2; } - else if (TheGlobalData->m_terrainHeightAmbientLightHeight2 <= 0) { - col1R = TheGlobalData->m_terrainHeightAmbientLightColor1.red; - col1G = TheGlobalData->m_terrainHeightAmbientLightColor1.green; - col1B = TheGlobalData->m_terrainHeightAmbientLightColor1.blue; - height1 = TheGlobalData->m_terrainHeightAmbientLightHeight1; + else if (TheMapData->m_terrainHeightAmbientLightHeight2 <= 0) { + col1R = TheMapData->m_terrainHeightAmbientLightColor1.red; + col1G = TheMapData->m_terrainHeightAmbientLightColor1.green; + col1B = TheMapData->m_terrainHeightAmbientLightColor1.blue; + height1 = TheMapData->m_terrainHeightAmbientLightHeight1; } else { // case 2: both colors - col1R = TheGlobalData->m_terrainHeightAmbientLightColor1.red; - col1G = TheGlobalData->m_terrainHeightAmbientLightColor1.green; - col1B = TheGlobalData->m_terrainHeightAmbientLightColor1.blue; - col2R = TheGlobalData->m_terrainHeightAmbientLightColor2.red; - col2G = TheGlobalData->m_terrainHeightAmbientLightColor2.green; - col2B = TheGlobalData->m_terrainHeightAmbientLightColor2.blue; - height1 = TheGlobalData->m_terrainHeightAmbientLightHeight1; - height2 = TheGlobalData->m_terrainHeightAmbientLightHeight2; + col1R = TheMapData->m_terrainHeightAmbientLightColor1.red; + col1G = TheMapData->m_terrainHeightAmbientLightColor1.green; + col1B = TheMapData->m_terrainHeightAmbientLightColor1.blue; + col2R = TheMapData->m_terrainHeightAmbientLightColor2.red; + col2G = TheMapData->m_terrainHeightAmbientLightColor2.green; + col2B = TheMapData->m_terrainHeightAmbientLightColor2.blue; + height1 = TheMapData->m_terrainHeightAmbientLightHeight1; + height2 = TheMapData->m_terrainHeightAmbientLightHeight2; } - bool multiply = !TheGlobalData->m_terrainHeightAmbientLightAdditive; + bool multiply = !TheMapData->m_terrainHeightAmbientLightAdditive; Real base; if (multiply) base = 1.0; @@ -652,7 +653,7 @@ void BaseHeightMapRenderObjClass::doTheLight(VERTEX_FORMAT *vb, Vector3*light, V base = 0.0; if (vb->z > height1) { - Real t = WWMath::Clamp(invLerp(TheGlobalData->m_terrainHeightAmbientLightHeightStart, height1, vb->z)); + Real t = WWMath::Clamp(invLerp(TheMapData->m_terrainHeightAmbientLightHeightStart, height1, vb->z)); colR = col1R * t + (1.0 - t) * base; colG = col1G * t + (1.0 - t) * base; colB = col1B * t + (1.0 - t) * base; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp index 5e06850a625..7c832358f14 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp @@ -48,6 +48,7 @@ //------------------------------------------------------------------------------------------------- W3DDecalDrawModuleData::W3DDecalDrawModuleData() { + m_renderAboveWater = FALSE; // default: below water (shadow-like) } //------------------------------------------------------------------------------------------------- @@ -69,6 +70,7 @@ void W3DDecalDrawModuleData::buildFieldParse(MultiIniFieldParse& p) { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof(W3DDecalDrawModuleData, m_fadeInTime) }, { "SizeX", INI::parseReal, nullptr, offsetof(W3DDecalDrawModuleData, m_decalSizeX) }, { "SizeY", INI::parseReal, nullptr, offsetof(W3DDecalDrawModuleData, m_decalSizeY) }, + { "RenderAboveWater", INI::parseBool, nullptr, offsetof(W3DDecalDrawModuleData, m_renderAboveWater) }, { nullptr, nullptr, nullptr, 0 } }; p.add(dataFieldParse); @@ -123,11 +125,31 @@ void W3DDecalDraw::init_shadow() strlcpy(shadowInfo.m_ShadowName, data->m_textureName.str(), ARRAY_SIZE(shadowInfo.m_ShadowName)); shadowInfo.allowUpdates = FALSE; //shadow image will never update shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects - shadowInfo.m_type = data->m_type; - shadowInfo.m_sizeX = data->m_decalSizeX; - shadowInfo.m_sizeY = data->m_decalSizeY; + + // W3DDecalDraw is a standalone FX decal, not a real object shadow. Only the decal-use blend types + // are valid here; SHADOW_DECAL (and other shadow/projection/volume types) route into object-shadow + // code paths that assume a shadow-casting object + runtime shadow texture, which crashes for stacked + // FX decals. Coerce anything else to SHADOW_ALPHA_DECAL. + ShadowType decalType; + if (data->m_type == SHADOW_ADDITIVE_DECAL) + decalType = SHADOW_ADDITIVE_DECAL; + else if (data->m_type == SHADOW_ALPHA_DECAL) + decalType = SHADOW_ALPHA_DECAL; + else + { + DEBUG_CRASH(("W3DDecalDraw: Style must be SHADOW_ALPHA_DECAL or SHADOW_ADDITIVE_DECAL (got 0x%x); " + "SHADOW_DECAL and shadow/projection types are not supported for FX decals and can crash. " + "Coercing to SHADOW_ALPHA_DECAL.", (Int)data->m_type)); + decalType = SHADOW_ALPHA_DECAL; + } + shadowInfo.m_type = decalType; + // honor the drawable's per-instance scale so FX-nugget scale variance affects decal size + Real scale = getDrawable()->getInstanceScale(); + shadowInfo.m_sizeX = data->m_decalSizeX * scale; + shadowInfo.m_sizeY = data->m_decalSizeY * scale; shadowInfo.m_offsetX = 0.0f; // TODO shadowInfo.m_offsetY = 0.0f; // TODO + shadowInfo.m_waterRenderMode = data->m_renderAboveWater ? SHADOW_WATER_ABOVE : SHADOW_WATER_BELOW; //shadowInfo.m_hasDynamicLength = FALSE; DEBUG_ASSERTCRASH(m_shadow == nullptr, ("m_shadow is not null")); @@ -149,8 +171,11 @@ void W3DDecalDraw::init_renderBox(const Matrix3D* transformMtx) { const W3DDecalDrawModuleData* data = getW3DDecalDrawModuleData(); + // honor the drawable's per-instance scale so FX-nugget scale variance affects decal size + Real scale = getDrawable()->getInstanceScale(); + Vector3 center = { 0, 0, 0 }; - Vector3 extent = { data->m_decalSizeX, data->m_decalSizeY, 1.0f }; + Vector3 extent = { data->m_decalSizeX * scale, data->m_decalSizeY * scale, 1.0f }; m_renderBox = NEW OBBoxRenderObjClass( OBBoxClass(center, extent) diff --git a/Generals/Code/GameEngine/Include/GameClient/Shadow.h b/Generals/Code/GameEngine/Include/GameClient/Shadow.h index cacf40256a8..29783b39ee8 100644 --- a/Generals/Code/GameEngine/Include/GameClient/Shadow.h +++ b/Generals/Code/GameEngine/Include/GameClient/Shadow.h @@ -62,6 +62,14 @@ static const char* const TheShadowNames[] = #define MAX_SHADOW_LIGHTS 1 //maximum number of shadow casting light sources in scene - support for more than 1 has been dropped from most code. +// Per-decal ordering relative to water (evaluated only by the Zero Hour render path; inert here). +enum ShadowWaterMode +{ + SHADOW_WATER_DEFAULT = 0, //follow the global RadiusDecalsAboveWater flag + SHADOW_WATER_ABOVE, //always draw above water + SHADOW_WATER_BELOW //always draw below water (shadow-like) +}; + class RenderObjClass; //forward reference class RenderCost; //forward reference @@ -83,6 +91,7 @@ class Shadow m_sizeY = 0.0f; m_offsetX = 0.0f; m_offsetY = 0.0f; + m_waterRenderMode = SHADOW_WATER_DEFAULT; } char m_ShadowName[64]; //when set, overrides the default model shadow (used mostly for Decals). @@ -93,9 +102,13 @@ class Shadow Real m_sizeY; //world size of decal projection Real m_offsetX; //world shift along x axis Real m_offsetY; //world shift along y axis + Int m_waterRenderMode; //ShadowWaterMode: above/below/default water ordering }; - Shadow(void) : m_diffuse(0xffffffff), m_color(0xffffffff), m_opacity (0x000000ff), m_localAngle(0.0f) {} + Shadow(void) : m_diffuse(0xffffffff), m_color(0xffffffff), m_opacity (0x000000ff), m_localAngle(0.0f), m_waterRenderMode(SHADOW_WATER_DEFAULT) {} + + void setWaterRenderMode(Int mode) { m_waterRenderMode = mode; } + Int getWaterRenderMode(void) const { return m_waterRenderMode; } ///> 8) & 0xff) * fvalue)) - |REAL_TO_INT(((Real)((m_color >> 16) & 0xff) * fvalue)); + // Premultiply each channel by opacity and pack into its correct byte (D3DCOLOR 0xAARRGGBB). + // Additive blend (ONE/ONE) ignores alpha, so fade is done by scaling RGB toward black. + Int r = REAL_TO_INT((Real)((m_color >> 16) & 0xff) * fvalue); + Int g = REAL_TO_INT((Real)((m_color >> 8) & 0xff) * fvalue); + Int b = REAL_TO_INT((Real)( m_color & 0xff) * fvalue); + m_diffuse = (r << 16) | (g << 8) | b; } } } @@ -194,9 +211,12 @@ inline void Shadow::setColor(Color value) if (m_type & SHADOW_ADDITIVE_DECAL) { Real fvalue=(Real)m_opacity/255.0f; - m_diffuse=REAL_TO_INT(((Real)(m_color & 0xff) * fvalue)) - |REAL_TO_INT(((Real)((m_color >> 8) & 0xff) * fvalue)) - |REAL_TO_INT(((Real)((m_color >> 16) & 0xff) * fvalue)); + // Premultiply each channel by opacity and pack into its correct byte (D3DCOLOR 0xAARRGGBB). + // Additive blend (ONE/ONE) ignores alpha, so fade is done by scaling RGB toward black. + Int r = REAL_TO_INT((Real)((m_color >> 16) & 0xff) * fvalue); + Int g = REAL_TO_INT((Real)((m_color >> 8) & 0xff) * fvalue); + Int b = REAL_TO_INT((Real)( m_color & 0xff) * fvalue); + m_diffuse = (r << 16) | (g << 8) | b; } } } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 9cff6db11f2..83e8adc2a5a 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -592,19 +592,12 @@ class GlobalData : public SubsystemInterface DeathTypeFlags m_defaultExcludedDeathTypes; Bool m_heightAboveTerrainIncludesWater; + Bool m_radiusDecalsAboveWater; ///< if true, radius decals (cursors) render over water instead of under it Bool m_hideScorchmarksAboveGround; Bool m_weaponScatterOnWaterSurfaceDefault; ///< default for WeaponTemplate ScatterOnWaterSurface when not set per-weapon Bool m_reverseMoveIgnoreAngleThreshold; ///< if true, a manual REVERSE_MOVE order reverses regardless of heading; if false, only when the goal is behind us Real m_smartGarrisonRange; ///< radius searched for additional transports by the Smart Garrison command - // Water depth lighting - RGBColor m_terrainHeightAmbientLightColor1; - RGBColor m_terrainHeightAmbientLightColor2; - Real m_terrainHeightAmbientLightHeightStart; - Real m_terrainHeightAmbientLightHeight1; - Real m_terrainHeightAmbientLightHeight2; - Bool m_terrainHeightAmbientLightAdditive; - // the trailing '\' is included! const AsciiString &getPath_UserData() const { return m_userDataDir; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MapData.h b/GeneralsMD/Code/GameEngine/Include/Common/MapData.h index 07a88767c96..5fc361de23f 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MapData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MapData.h @@ -25,7 +25,16 @@ class MapData : public SubsystemInterface Real m_HeightmapScale; Bool m_enableShips; + // Height based terrain ambient light (e.g. water depth lighting). Per-map. + RGBColor m_terrainHeightAmbientLightColor1; + RGBColor m_terrainHeightAmbientLightColor2; + Real m_terrainHeightAmbientLightHeightStart; + Real m_terrainHeightAmbientLightHeight1; + Real m_terrainHeightAmbientLightHeight2; + Bool m_terrainHeightAmbientLightAdditive; + private: + void setDefaults(); static const FieldParse s_MapDataFieldParseTable[]; }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Shadow.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Shadow.h index bbf012be983..9e1cb49b13d 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Shadow.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Shadow.h @@ -62,6 +62,14 @@ static const char* const TheShadowNames[] = #define MAX_SHADOW_LIGHTS 1 //maximum number of shadow casting light sources in scene - support for more than 1 has been dropped from most code. +// Per-decal ordering relative to water (evaluated only by the Zero Hour render path; inert in base Generals). +enum ShadowWaterMode +{ + SHADOW_WATER_DEFAULT = 0, //follow the global RadiusDecalsAboveWater flag + SHADOW_WATER_ABOVE, //always draw above water + SHADOW_WATER_BELOW //always draw below water (shadow-like) +}; + class RenderObjClass; //forward reference class RenderCost; //forward reference @@ -84,6 +92,7 @@ class Shadow m_offsetX = 0.0f; m_offsetY = 0.0f; m_hasDynamicLength = false; + m_waterRenderMode = SHADOW_WATER_DEFAULT; } char m_ShadowName[64]; //when set, overrides the default model shadow (used mostly for Decals). @@ -95,9 +104,13 @@ class Shadow Real m_offsetX; //world shift along x axis Real m_offsetY; //world shift along y axis Bool m_hasDynamicLength; ///< determines shadow angle based on object height + Int m_waterRenderMode; //ShadowWaterMode: above/below/default water ordering }; - Shadow(void) : m_diffuse(0xffffffff), m_color(0xffffffff), m_opacity (0x000000ff), m_localAngle(0.0f) {} + Shadow(void) : m_diffuse(0xffffffff), m_color(0xffffffff), m_opacity (0x000000ff), m_localAngle(0.0f), m_waterRenderMode(SHADOW_WATER_DEFAULT) {} + + void setWaterRenderMode(Int mode) { m_waterRenderMode = mode; } + Int getWaterRenderMode(void) const { return m_waterRenderMode; } ///> 8) & 0xff) * fvalue)) - |REAL_TO_INT(((Real)((m_color >> 16) & 0xff) * fvalue)); + // Premultiply each channel by opacity and pack into its correct byte (D3DCOLOR 0xAARRGGBB). + // Additive blend (ONE/ONE) ignores alpha, so fade is done by scaling RGB toward black. + Int r = REAL_TO_INT((Real)((m_color >> 16) & 0xff) * fvalue); + Int g = REAL_TO_INT((Real)((m_color >> 8) & 0xff) * fvalue); + Int b = REAL_TO_INT((Real)( m_color & 0xff) * fvalue); + m_diffuse = (r << 16) | (g << 8) | b; } } } @@ -199,9 +216,12 @@ inline void Shadow::setColor(Color value) if (m_type & SHADOW_ADDITIVE_DECAL) { Real fvalue=(Real)m_opacity/255.0f; - m_diffuse=REAL_TO_INT(((Real)(m_color & 0xff) * fvalue)) - |REAL_TO_INT(((Real)((m_color >> 8) & 0xff) * fvalue)) - |REAL_TO_INT(((Real)((m_color >> 16) & 0xff) * fvalue)); + // Premultiply each channel by opacity and pack into its correct byte (D3DCOLOR 0xAARRGGBB). + // Additive blend (ONE/ONE) ignores alpha, so fade is done by scaling RGB toward black. + Int r = REAL_TO_INT((Real)((m_color >> 16) & 0xff) * fvalue); + Int g = REAL_TO_INT((Real)((m_color >> 8) & 0xff) * fvalue); + Int b = REAL_TO_INT((Real)( m_color & 0xff) * fvalue); + m_diffuse = (r << 16) | (g << 8) | b; } } } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h index f8a6f69aad5..44f96e5569e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DrawBridgeUpdate.h @@ -6,13 +6,13 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "Common/KindOf.h" +#include "Common/AudioEventRTS.h" #include "GameLogic/Module/UpdateModule.h" // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class SpecialPowerModule; class ParticleSystem; class FXList; -class AudioEventRTS; enum CommandOption CPP_11(: Int); //------------------------------------------------------------------------------------------------- @@ -26,6 +26,13 @@ class DrawBridgeUpdateModuleData : public ModuleData Real m_openingPushForce; UnsignedInt m_closingDamageTime; + const FXList* m_openingFX; ///< played when the bridge starts opening + const FXList* m_openFX; ///< played when the bridge finishes opening + const FXList* m_closingFX; ///< played when the bridge starts closing + const FXList* m_closedFX; ///< played when the bridge finishes closing + AudioEventRTS m_openingAudio; ///< looped while the bridge is opening + AudioEventRTS m_closingAudio; ///< looped while the bridge is closing + DrawBridgeUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -62,10 +69,25 @@ class DrawBridgeUpdate : public UpdateModule void pushObjectsOnOpeningDrawbridge( void ); void destroyObjectsUnderClosingDrawbridge (void ); + void stopTransitionAudio( void ); ///< stop any looping opening/closing audio + + enum BridgeTransitionType + { + BRIDGE_TRANSITION_NONE = 0, + BRIDGE_TRANSITION_OPENING, + BRIDGE_TRANSITION_CLOSING, + }; + bool m_bridgeOpened; UnsignedInt m_nextReadyFrame; UnsignedInt m_openingFrame; ///< frame bridge started to open UnsignedInt m_closingDamageFrame; ///< frame damage will be applied when closing + + BridgeTransitionType m_transitionState; ///< opening/closing/none, drives the finish FX and looping audio + UnsignedInt m_transitionDoneFrame; ///< frame the current opening/closing finishes + + AudioEventRTS m_openingAudio; ///< runtime instance of the looping opening audio + AudioEventRTS m_closingAudio; ///< runtime instance of the looping closing audio }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 41ce14ca763..9cd47a63c29 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -580,15 +580,10 @@ GlobalData* GlobalData::m_theOriginal = nullptr; {"DefaultExcludedDeathTypes", INI::parseDeathTypeFlagsList, NULL, offsetof(GlobalData, m_defaultExcludedDeathTypes) }, {"HeightAboveTerrainIncludesWater", INI::parseBool, NULL, offsetof(GlobalData, m_heightAboveTerrainIncludesWater) }, + {"RadiusDecalsAboveWater", INI::parseBool, NULL, offsetof(GlobalData, m_radiusDecalsAboveWater) }, {"HideScorchmarksAboveGround", INI::parseBool, NULL, offsetof(GlobalData, m_hideScorchmarksAboveGround) }, - { "TerrainHeightAmbientLightColor1", INI::parseRGBColor, NULL, offsetof(GlobalData, m_terrainHeightAmbientLightColor1) }, - { "TerrainHeightAmbientLightColor2", INI::parseRGBColor, NULL, offsetof(GlobalData, m_terrainHeightAmbientLightColor2) }, - { "TerrainHeightAmbientLightStart", INI::parseReal, NULL, offsetof(GlobalData, m_terrainHeightAmbientLightHeightStart) }, - { "TerrainHeightAmbientLightHeight1", INI::parseReal, NULL, offsetof(GlobalData, m_terrainHeightAmbientLightHeight1) }, - { "TerrainHeightAmbientLightHeight2", INI::parseReal, NULL, offsetof(GlobalData, m_terrainHeightAmbientLightHeight2) }, - { "TerrainHeightAmbientLightAdditive", INI::parseBool, NULL, offsetof(GlobalData, m_terrainHeightAmbientLightAdditive) }, { nullptr, nullptr, nullptr, 0 } }; @@ -1182,20 +1177,7 @@ GlobalData::GlobalData() // m_chronoTintStatusType = TINT_STATUS_INVALID; m_heightAboveTerrainIncludesWater = false; - - m_terrainHeightAmbientLightColor1.red = 0; - m_terrainHeightAmbientLightColor1.green = 0; - m_terrainHeightAmbientLightColor1.blue = 0; - - m_terrainHeightAmbientLightColor2.red = 0; - m_terrainHeightAmbientLightColor2.green = 0; - m_terrainHeightAmbientLightColor2.blue = 0; - - m_terrainHeightAmbientLightHeightStart = -1; - m_terrainHeightAmbientLightHeight1 = -1; - m_terrainHeightAmbientLightHeight2 = -1; - - m_terrainHeightAmbientLightAdditive = false; + m_radiusDecalsAboveWater = false; } // end GlobalData diff --git a/GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp index 01114cd4643..41ef2fbffbc 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp @@ -18,24 +18,47 @@ MapData* TheWriteableMapData = NULL; ///< The current map data singleton { { "HeightMapScale", INI::parseReal, NULL, offsetof( MapData, m_HeightmapScale) }, { "EnableShips", INI::parseBool, NULL, offsetof( MapData, m_enableShips) }, + { "TerrainHeightAmbientLightColor1", INI::parseRGBColor, NULL, offsetof( MapData, m_terrainHeightAmbientLightColor1) }, + { "TerrainHeightAmbientLightColor2", INI::parseRGBColor, NULL, offsetof( MapData, m_terrainHeightAmbientLightColor2) }, + { "TerrainHeightAmbientLightStart", INI::parseReal, NULL, offsetof( MapData, m_terrainHeightAmbientLightHeightStart) }, + { "TerrainHeightAmbientLightHeight1", INI::parseReal, NULL, offsetof( MapData, m_terrainHeightAmbientLightHeight1) }, + { "TerrainHeightAmbientLightHeight2", INI::parseReal, NULL, offsetof( MapData, m_terrainHeightAmbientLightHeight2) }, + { "TerrainHeightAmbientLightAdditive",INI::parseBool, NULL, offsetof( MapData, m_terrainHeightAmbientLightAdditive) }, { NULL, NULL, NULL, 0 } // keep this last }; -MapData::MapData() : SubsystemInterface() +void MapData::setDefaults() { m_HeightmapScale = 1.0f; m_enableShips = false; + + m_terrainHeightAmbientLightColor1.red = 0; + m_terrainHeightAmbientLightColor1.green = 0; + m_terrainHeightAmbientLightColor1.blue = 0; + + m_terrainHeightAmbientLightColor2.red = 0; + m_terrainHeightAmbientLightColor2.green = 0; + m_terrainHeightAmbientLightColor2.blue = 0; + + m_terrainHeightAmbientLightHeightStart = -1; + m_terrainHeightAmbientLightHeight1 = -1; + m_terrainHeightAmbientLightHeight2 = -1; + + m_terrainHeightAmbientLightAdditive = false; +} + +MapData::MapData() : SubsystemInterface() +{ + setDefaults(); } void MapData::init() { - m_HeightmapScale = 1.0f; - m_enableShips = false; + setDefaults(); } void MapData::reset() { - m_HeightmapScale = 1.0f; - m_enableShips = false; + setDefaults(); } void MapData::parseMapDataDefinition(INI* ini) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index d2e38d18e3f..0bf2279aad5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -3598,6 +3598,10 @@ void Drawable::drawEnthusiastic(const IRegion2D* healthBarRegion) // // only display if have enthusiasm + // hardcoded fix to prevent projectiles that got the bonus from the launcher from showing the icon + if (obj->isKindOf(KINDOF_PROJECTILE)) + return; + if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ) == TRUE && healthBarRegion != nullptr ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp index 65fce065d3d..262af185e46 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DrawBridgeUpdate.cpp @@ -9,6 +9,7 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "Common/BitFlagsIO.h" +#include "Common/GameAudio.h" #include "Common/Radar.h" #include "Common/PlayerList.h" #include "Common/ThingTemplate.h" @@ -48,6 +49,11 @@ DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() m_closingDuration = 0U; m_openingPushForce = 0.0f; m_closingDamageTime = 0U; + + m_openingFX = nullptr; + m_openFX = nullptr; + m_closingFX = nullptr; + m_closedFX = nullptr; } //------------------------------------------------------------------------------------------------- @@ -61,6 +67,12 @@ DrawBridgeUpdateModuleData::DrawBridgeUpdateModuleData() { "ClosingDuration", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closingDuration)}, { "OpeningPushForce", INI::parseAccelerationReal, nullptr, offsetof(DrawBridgeUpdateModuleData, m_openingPushForce)}, { "ClosingDamageTime", INI::parseDurationUnsignedInt, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closingDamageTime)}, + { "BridgeOpeningFX", INI::parseFXList, nullptr, offsetof(DrawBridgeUpdateModuleData, m_openingFX)}, + { "BridgeOpenFX", INI::parseFXList, nullptr, offsetof(DrawBridgeUpdateModuleData, m_openFX)}, + { "BridgeClosingFX", INI::parseFXList, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closingFX)}, + { "BridgeClosedFX", INI::parseFXList, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closedFX)}, + { "BridgeOpeningAudio", INI::parseAudioEventRTS, nullptr, offsetof(DrawBridgeUpdateModuleData, m_openingAudio)}, + { "BridgeClosingAudio", INI::parseAudioEventRTS, nullptr, offsetof(DrawBridgeUpdateModuleData, m_closingAudio)}, { nullptr, nullptr, nullptr, 0 } }; p.add(dataFieldParse); @@ -74,12 +86,34 @@ DrawBridgeUpdate::DrawBridgeUpdate(Thing* thing, const ModuleData* moduleData) : m_nextReadyFrame = 0U; m_openingFrame = 0U; m_closingDamageFrame = 0U; + + const DrawBridgeUpdateModuleData* data = getDrawBridgeUpdateModuleData(); + m_transitionState = BRIDGE_TRANSITION_NONE; + m_transitionDoneFrame = 0U; + + // runtime copies of the configured looping audio, tied to this object + m_openingAudio = data->m_openingAudio; + m_openingAudio.setObjectID(getObject()->getID()); + m_closingAudio = data->m_closingAudio; + m_closingAudio.setObjectID(getObject()->getID()); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- DrawBridgeUpdate::~DrawBridgeUpdate(void) { + stopTransitionAudio(); +} + +//------------------------------------------------------------------------------------------------- +// Stop any looping opening/closing audio that is currently playing. +//------------------------------------------------------------------------------------------------- +void DrawBridgeUpdate::stopTransitionAudio(void) +{ + if (m_openingAudio.isCurrentlyPlaying()) + TheAudio->removeAudioEvent(m_openingAudio.getPlayingHandle()); + if (m_closingAudio.isCurrentlyPlaying()) + TheAudio->removeAudioEvent(m_closingAudio.getPlayingHandle()); } // ------------------------------------------------------------------------------------------------ @@ -87,6 +121,8 @@ DrawBridgeUpdate::~DrawBridgeUpdate(void) // ------------------------------------------------------------------------------------------------ void DrawBridgeUpdate::onDelete() { + stopTransitionAudio(); + // extend base class UpdateModule::onDelete(); @@ -133,12 +169,30 @@ bool DrawBridgeUpdate::setDrawBridgeState(bool opened, const Object* fromTower) obj->setGeometryInfo(openBridgeGeom); m_openingFrame = TheGameLogic->getFrame(); m_closingDamageFrame = 0U; + + // bridge starts opening: fire the opening FX and loop the opening audio until it finishes + stopTransitionAudio(); + if (data->m_openingFX) + FXList::doFXPos(data->m_openingFX, obj->getPosition()); + m_openingAudio.setPosition(obj->getPosition()); + m_openingAudio.setPlayingHandle(TheAudio->addAudioEvent(&m_openingAudio)); + m_transitionState = BRIDGE_TRANSITION_OPENING; + m_transitionDoneFrame = m_nextReadyFrame; } else { obj->clearAndSetModelConditionState(MODELCONDITION_DOOR_1_OPENING, MODELCONDITION_DOOR_1_CLOSING); obj->setGeometryInfo(obj->getTemplate()->getTemplateGeometryInfo()); m_openingFrame = 0U; // when rapid toggling is possible m_closingDamageFrame = TheGameLogic->getFrame() + data->m_closingDamageTime; + + // bridge starts closing: fire the closing FX and loop the closing audio until it finishes + stopTransitionAudio(); + if (data->m_closingFX) + FXList::doFXPos(data->m_closingFX, obj->getPosition()); + m_closingAudio.setPosition(obj->getPosition()); + m_closingAudio.setPlayingHandle(TheAudio->addAudioEvent(&m_closingAudio)); + m_transitionState = BRIDGE_TRANSITION_CLOSING; + m_transitionDoneFrame = m_nextReadyFrame; } } return true; @@ -150,6 +204,9 @@ void DrawBridgeUpdate::onBridgeDestroyed() { m_openingFrame = 0U; m_closingDamageFrame = 0U; + stopTransitionAudio(); + m_transitionState = BRIDGE_TRANSITION_NONE; + m_transitionDoneFrame = 0U; Object* obj = getObject(); obj->clearModelConditionFlags(MODELCONDITION_DOOR_1_OPENING); obj->clearModelConditionFlags(MODELCONDITION_DOOR_1_CLOSING); @@ -160,6 +217,9 @@ void DrawBridgeUpdate::onBridgeRepaired() { m_openingFrame = 0U; m_closingDamageFrame = 0U; + stopTransitionAudio(); + m_transitionState = BRIDGE_TRANSITION_NONE; + m_transitionDoneFrame = 0U; Object* obj = getObject(); obj->clearModelConditionFlags(MODELCONDITION_DOOR_1_OPENING); obj->clearModelConditionFlags(MODELCONDITION_DOOR_1_CLOSING); @@ -386,6 +446,17 @@ UpdateSleepTime DrawBridgeUpdate::update() m_closingDamageFrame = 0U; } } + + // The opening/closing animation just finished: fire the finished FX and stop the looping audio. + if (m_transitionState != BRIDGE_TRANSITION_NONE && TheGameLogic->getFrame() >= m_transitionDoneFrame) { + const DrawBridgeUpdateModuleData* data = getDrawBridgeUpdateModuleData(); + const FXList* finishedFX = (m_transitionState == BRIDGE_TRANSITION_OPENING) ? data->m_openFX : data->m_closedFX; + if (finishedFX) + FXList::doFXPos(finishedFX, getObject()->getPosition()); + stopTransitionAudio(); + m_transitionState = BRIDGE_TRANSITION_NONE; + m_transitionDoneFrame = 0U; + } return UPDATE_SLEEP_NONE; } @@ -403,12 +474,13 @@ void DrawBridgeUpdate::crc(Xfer* xfer) // Xfer method // Version Info: // 1: Initial version +// 2: Added opening/closing transition state for finish FX + looping audio //------------------------------------------------------------------------------------------------ void DrawBridgeUpdate::xfer(Xfer* xfer) { // version - XferVersion currentVersion = 1; + XferVersion currentVersion = 2; XferVersion version = currentVersion; xfer->xferVersion(&version, currentVersion); @@ -422,6 +494,12 @@ void DrawBridgeUpdate::xfer(Xfer* xfer) xfer->xferUnsignedInt(&m_openingFrame); xfer->xferUnsignedInt(&m_closingDamageFrame); + + if (version >= 2) + { + xfer->xferUser(&m_transitionState, sizeof(m_transitionState)); + xfer->xferUnsignedInt(&m_transitionDoneFrame); + } } //------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DProjectedShadow.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DProjectedShadow.h index 3fa7bc9b9d5..fad9d07c76d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DProjectedShadow.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DProjectedShadow.h @@ -57,6 +57,7 @@ class W3DProjectedShadowManager : public ProjectedShadowManager void shutdown(void); ///SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + m_pDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + m_pDev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + m_pDev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + m_pDev->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + m_pDev->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); + //Alpha Blended Shadows // m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); // m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); @@ -795,6 +809,21 @@ void testShadowDecal(void) */ #define BRIDGE_OFFSET_FACTOR 1.5f + +// Resolve a decal's effective "draw above water" choice. Per-decal mode wins; DEFAULT follows the +// global RadiusDecalsAboveWater flag. Used to split m_decalList between the pre-water and post-water passes. +static Bool decalDrawsAboveWater(const Shadow *shadow) +{ + if (shadow == nullptr) + return FALSE; + switch (shadow->getWaterRenderMode()) + { + case SHADOW_WATER_ABOVE: return TRUE; + case SHADOW_WATER_BELOW: return FALSE; + default: return TheGlobalData->m_radiusDecalsAboveWater; + } +} + /**Decals have a low poly count so its better to render large numbers at once. This system will queue them up until the buffers fill up. It will then flush the buffer (draw decals) and be ready for new decals. This is an optimized system that only uses the render objects bounding box to determine shadow visibility. @@ -996,10 +1025,21 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) Int numVerts = vertsPerRow *vertsPerColumn; //number of terrain vertices Int numIndex=(endX - startX) * (endY-startY)*6; //6 indices per terrain cell (2 triangles). + // Skip a pathologically large decal that can't fit a single buffer (would overrun the DISCARD lock). + if (numVerts > SHADOW_DECAL_VERTEX_SIZE || numIndex > SHADOW_DECAL_INDEX_SIZE) + return; + SHADOW_DECAL_VERTEX* pvVertices; UnsignedShort *pvIndices; - if (nShadowDecalVertsInBuf > (SHADOW_DECAL_VERTEX_SIZE-numVerts)) //check if room for model verts + // Decide a single flush for the whole decal: if EITHER the vertex or index buffer would overflow, + // flush and discard BOTH together. Checking them independently lets the two buffers desync (a decal + // uses ~6x more indices than verts, so the index buffer overflows first), which corrupts the shared + // batch bookkeeping and eventually makes DrawIndexedPrimitive read past the vertex buffer -> crash. + Bool needFlush = (nShadowDecalVertsInBuf > (SHADOW_DECAL_VERTEX_SIZE - numVerts)) || + (nShadowDecalIndicesInBuf > (SHADOW_DECAL_INDEX_SIZE - numIndex)); + + if (needFlush) { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0], shadow->m_type); if (shadowDecalVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DECAL_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) @@ -1040,7 +1080,7 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=__max((float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE,layerHeight); - if (TheGlobalData->m_heightAboveTerrainIncludesWater && TheTerrainLogic != nullptr) { + if ((TheGlobalData->m_heightAboveTerrainIncludesWater || decalDrawsAboveWater(shadow)) && TheTerrainLogic != nullptr) { if (Real waterZ = 0; TheTerrainLogic->isUnderwater(hmapVertex.X, hmapVertex.Y, &waterZ)) { if (waterZ > hmapVertex.Z) hmapVertex.Z = waterZ; } @@ -1066,7 +1106,7 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE+0.01f * MAP_XY_FACTOR; - if (TheGlobalData->m_heightAboveTerrainIncludesWater && TheTerrainLogic != nullptr) { + if ((TheGlobalData->m_heightAboveTerrainIncludesWater || decalDrawsAboveWater(shadow)) && TheTerrainLogic != nullptr) { if (Real waterZ = 0; TheTerrainLogic->isUnderwater(hmapVertex.X, hmapVertex.Y, &waterZ)) { if (waterZ > hmapVertex.Z) hmapVertex.Z = waterZ; } @@ -1085,16 +1125,15 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) shadowDecalVertexBufferD3D->Unlock(); - if (nShadowDecalIndicesInBuf > (SHADOW_DECAL_INDEX_SIZE-numIndex)) //check if room for model verts - { //flush the buffer by drawing the contents and re-locking again - flushDecals(shadow->m_shadowTexture[0], shadow->m_type); - + // Use the SAME flush decision as the vertex buffer above so both buffers reset in lockstep. + // flushDecals + the vertex/batch-counter resets already happened in the vertex block; here we + // only need to discard-lock the index buffer and reset the index counters. + if (needFlush) + { if (shadowDecalIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowDecalStartBatchIndex=0; - nShadowDecalPolysInBatch=0; //reset number of polys in texture batch - nShadowDecalVertsInBatch=0; nShadowDecalIndicesInBuf=0; } else @@ -1444,38 +1483,69 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) flushDecals(lastShadowDecalTexture,lastShadowType); //make sure there are not any unrendered decals left over. TheDX8MeshRenderer.Flush(); //draw all the shadow receiving objects } - if (m_decalList) + // Draw the below-water subset of the decal list here (before water). The above-water subset is + // drawn later, after the water pass, by a second renderDecals() call from RTS3DScene::Flush. + projectionCount += renderDecals(rinfo, false); + + return projectionCount; +} + +//------------------------------------------------------------------------------------------------- +/** Draw the decal list (m_decalList), limited to decals whose effective water ordering matches + aboveWaterPass. Called once before water (aboveWaterPass=false) and once after (true), so decals + can be ordered above or below water per-decal. */ +//------------------------------------------------------------------------------------------------- +Int W3DProjectedShadowManager::renderDecals(RenderInfoClass & rinfo, Bool aboveWaterPass) +{ + Int projectionCount=0; + + if (!TheTerrainRenderObject || !m_decalList) + return projectionCount; + + //According to Nvidia there's a D3D bug that happens if you don't start with a + //new dynamic VB each frame - so we force a DISCARD by overflowing the counter. + //(also needed because water was drawn between the shadow pass and here) + nShadowDecalVertsInBuf = 0xffff; + nShadowDecalIndicesInBuf = 0xffff; + + TheDX8MeshRenderer.Set_Camera(&rinfo.Camera); + + W3DProjectedShadow *shadow; + + //keep track of active decal texture so we can render all decals at once. + W3DShadowTexture *lastShadowDecalTexture=nullptr; + ShadowType lastShadowType = SHADOW_NONE; + + for( shadow = m_decalList; shadow; shadow = shadow->m_next ) { - //keep track of active decal texture so we can render all decals at once. - W3DShadowTexture *lastShadowDecalTexture=nullptr; - ShadowType lastShadowType = SHADOW_NONE; + // only draw the decals belonging to this pass (above vs below water) + if (decalDrawsAboveWater(shadow) != aboveWaterPass) + continue; - for( shadow = m_decalList; shadow; shadow = shadow->m_next ) + if (shadow->m_isEnabled && !shadow->m_isInvisibleEnabled) { - if (shadow->m_isEnabled && !shadow->m_isInvisibleEnabled) - { - if (lastShadowDecalTexture == nullptr) - lastShadowDecalTexture=m_decalList->m_shadowTexture[0]; - if (lastShadowType == SHADOW_NONE) - lastShadowType = m_decalList->m_type; - - if (shadow->m_shadowTexture[0] != lastShadowDecalTexture || - shadow->m_type != lastShadowType) - { flushDecals(lastShadowDecalTexture,lastShadowType); //switched to a new texture, need to render polys using last texture. - lastShadowDecalTexture=shadow->m_shadowTexture[0]; - lastShadowType=shadow->m_type; - } - ///@todo: may need to fix this if shadows are large enough to be seen while object is not visible - if (!(shadow->m_robj && !shadow->m_robj->Is_Really_Visible())) - { //queueSimpleDecal(shadow); - queueDecal(shadow); //only draw shadow if casting object is visible - projectionCount++; - } + if (lastShadowDecalTexture == nullptr) + lastShadowDecalTexture=m_decalList->m_shadowTexture[0]; + if (lastShadowType == SHADOW_NONE) + lastShadowType = m_decalList->m_type; + + if (shadow->m_shadowTexture[0] != lastShadowDecalTexture || + shadow->m_type != lastShadowType) + { flushDecals(lastShadowDecalTexture,lastShadowType); //switched to a new texture, need to render polys using last texture. + lastShadowDecalTexture=shadow->m_shadowTexture[0]; + lastShadowType=shadow->m_type; + } + ///@todo: may need to fix this if shadows are large enough to be seen while object is not visible + if (!(shadow->m_robj && !shadow->m_robj->Is_Really_Visible())) + { //queueSimpleDecal(shadow); + queueDecal(shadow); //only draw shadow if casting object is visible + projectionCount++; } } - - flushDecals(lastShadowDecalTexture,lastShadowType); //make sure there are not any unrendered decals left over. } + + flushDecals(lastShadowDecalTexture,lastShadowType); //make sure there are not any unrendered decals left over. + return projectionCount; } @@ -1540,6 +1610,7 @@ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) shadow->setTexture(0,st); ///@todo: Fix projected shadows to allow multiple lights shadow->m_type = shadowType; /// type of projection shadow->m_allowWorldAlign=allowWorldAlign; /// wrap shadow around world geometry - else align perpendicular to local z-axis. + shadow->setWaterRenderMode(shadowInfo->m_waterRenderMode); shadow->m_oowDecalSizeX = 1.0f/decalSizeX; //one over width shadow->m_oowDecalSizeY = 1.0f/decalSizeY; //one over height @@ -1647,6 +1718,7 @@ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::Shadow shadow->setTexture(0,st); ///@todo: Fix projected shadows to allow multiple lights shadow->m_type = shadowType; /// type of projection shadow->m_allowWorldAlign=allowWorldAlign; /// wrap shadow around world geometry - else align perpendicular to local z-axis. + shadow->setWaterRenderMode(shadowInfo->m_waterRenderMode); AABoxClass box; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp index e5f828619e4..fecb249f974 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp @@ -99,6 +99,16 @@ void DoShadows(RenderInfoClass & rinfo, Bool stencilPass) } +// Draw only the radius-decal list. Used to render radius decals AFTER the water pass so they +// appear over water (see TheGlobalData->m_radiusDecalsAboveWater). +// NOTE: does NOT gate on isShadowScene() - the stencil-shadow pass (DoShadows(...,true)) runs +// before water and resets that flag to FALSE, so it is already false by the time we get here. +void DoDecals(RenderInfoClass & rinfo) +{ + if (TheW3DProjectedShadowManager) + TheW3DProjectedShadowManager->renderDecals(rinfo, true); //above-water subset +} + W3DShadowManager::W3DShadowManager( void ) { DEBUG_ASSERTCRASH(TheW3DVolumetricShadowManager == nullptr && TheW3DProjectedShadowManager == nullptr, diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 4a56e2cb750..51263250c6d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -73,6 +73,7 @@ extern void PrepareShadows(); extern void DoTrees(RenderInfoClass & rinfo); extern void DoShadows(RenderInfoClass & rinfo, Bool stencilPass); +extern void DoDecals(RenderInfoClass & rinfo); extern void DoParticles(RenderInfoClass & rinfo); // No texturing, no zbuffer reading/writing, primary gradient, no @@ -876,6 +877,11 @@ void RTS3DScene::Flush(RenderInfoClass & rinfo) WW3D::Render_And_Clear_Static_Sort_Lists(rinfo); //draws things like water + //draw the above-water decal subset AFTER water so those decals show over it (still depth-tested, so + //objects stay on top). Which decals qualify is decided per-decal (global flag + per-decal water mode). + if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) + DoDecals(rinfo); + if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) flushTranslucentObjects(rinfo); //draw all translucent meshes which don't need per-polygon sorting.