Skip to content
Draft
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
5 changes: 5 additions & 0 deletions Core/GameEngine/Include/Common/GameDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
#define PRIORITIZE_TEXTURES_BY_SIZE (1)
#endif

// Enable drawing particles as a terrain-conforming mesh.
#ifndef ENABLE_TERRAIN_CONFORMING_PARTICLES
#define ENABLE_TERRAIN_CONFORMING_PARTICLES (1)
#endif

// Enable obsolete code. This mainly refers to code that existed in Generals but was removed in GeneralsMD.
// Disable and remove this when Generals and GeneralsMD are merged.
#if RTS_GENERALS
Expand Down
8 changes: 8 additions & 0 deletions Core/GameEngine/Include/GameClient/ParticleSys.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#pragma once

#include "Common/AsciiString.h"
#include "Common/GameDefines.h"
#include "Common/GameMemory.h"
#include "Common/GameType.h"
#include "Common/Snapshot.h"
Expand Down Expand Up @@ -516,6 +517,11 @@ class ParticleSystemTemplate : public MemoryPoolObject, protected ParticleSystem
void validate();

const AsciiString& getName() const { return m_name; }
#if ENABLE_TERRAIN_CONFORMING_PARTICLES
Bool getIsTerrainConforming() const { return m_isTerrainConforming; }
#else
Bool getIsTerrainConforming() const { return FALSE; }
#endif

// This function was made const because of update modules' module data being all const.
ParticleSystem *createSlaveSystem( Bool createSlaves = TRUE ) const ; ///< if returns non-null, it is a slave system for use
Expand Down Expand Up @@ -543,6 +549,7 @@ class ParticleSystemTemplate : public MemoryPoolObject, protected ParticleSystem

// This has to be mutable because of the delayed initialization thing in createSlaveSystem
mutable const ParticleSystemTemplate *m_slaveTemplate; ///< if non-null, use this to create a slave system
Bool m_isTerrainConforming; ///< render ground-aligned particles conforming to the terrain

// template attribute data inherited from ParticleSystemInfo class
};
Expand Down Expand Up @@ -615,6 +622,7 @@ class ParticleSystem : public MemoryPoolObject,
UnsignedInt getVolumeParticleDepth() const { return m_volumeParticleDepth; }

Bool shouldBillboard() const { return !m_isGroundAligned; }
Bool isTerrainConforming() const { return m_template->getIsTerrainConforming(); }

ParticleShaderType getShaderType() const { return m_shaderType; }

Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngine/Source/GameClient/System/ParticleSys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2757,6 +2757,7 @@ const FieldParse ParticleSystemTemplate::m_fieldParseTable[] =

{ "IsHollow", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isEmissionVolumeHollow ) },
{ "IsGroundAligned", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isGroundAligned ) },
{ "IsTerrainConforming", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isTerrainConforming ) },
{ "IsEmitAboveGroundOnly", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isEmitAboveGroundOnly) },
{ "IsParticleUpTowardsEmitter", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isParticleUpTowardsEmitter) },

Expand Down Expand Up @@ -2862,6 +2863,7 @@ ParticleSystemTemplate::ParticleSystemTemplate( const AsciiString &name ) :
m_name(name)
{
m_slaveTemplate = nullptr;
m_isTerrainConforming = FALSE;
}

// ------------------------------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngineDevice/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ set(GAMEENGINEDEVICE_SRC
Include/W3DDevice/GameClient/W3DSnow.h
# Include/W3DDevice/GameClient/W3DStatusCircle.h
Include/W3DDevice/GameClient/W3DTerrainBackground.h
Include/W3DDevice/GameClient/W3DTerrainParticle.h
Include/W3DDevice/GameClient/W3DTerrainTracks.h
Include/W3DDevice/GameClient/W3DTerrainVisual.h
Include/W3DDevice/GameClient/W3DTreeBuffer.h
Expand Down Expand Up @@ -171,6 +172,7 @@ set(GAMEENGINEDEVICE_SRC
Source/W3DDevice/GameClient/W3DSnow.cpp
# Source/W3DDevice/GameClient/W3DStatusCircle.cpp
Source/W3DDevice/GameClient/W3DTerrainBackground.cpp
Source/W3DDevice/GameClient/W3DTerrainParticle.cpp
Source/W3DDevice/GameClient/W3DTerrainTracks.cpp
Source/W3DDevice/GameClient/W3DTerrainVisual.cpp
Source/W3DDevice/GameClient/W3DTreeBuffer.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#pragma once

#include "GameClient/ParticleSys.h"
#include "W3DDevice/GameClient/W3DTerrainParticle.h"
#include "WW3D2/pointgr.h"
#include "WW3D2/streak.h"
#include "WW3D2/rinfo.h"
Expand All @@ -50,15 +51,16 @@ class W3DParticleSystemManager : public ParticleSystemManager
virtual Int getOnScreenParticleCount() override { return m_onScreenParticleCount; }

private:
Bool finishedBatch(const ParticleSystem& system, const RefCountPtr<TextureClass>& texture);
void initializeBatch(const ParticleSystem& system, const RefCountPtr<TextureClass>& texture);
Bool finishedBatch(const ParticleSystem&system, const RefCountPtr<TextureClass>&texture);
void initializeBatch(const ParticleSystem& system, const RefCountPtr<TextureClass>& texture, const AABoxClass& bbox);
void flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount);

enum { MAX_POINTS_PER_GROUP = 512 };

RefCountPtr<TextureClass> m_batchTexture; ///< the texture used as the drawing surface for batched particle draws
PointGroupClass *m_pointGroup; ///< the point group that contains all of the particles
StreakLineClass *m_streakLine; ///< the streak class that contains all of the streaks
W3DTerrainParticle *m_terrainParticles; ///< the terrain particles renderer that contains all of the terrain conforming particles
ShareBufferClass<Vector3> *m_posBuffer; ///< array of particle positions
ShareBufferClass<Vector4> *m_RGBABuffer; ///< array of particle color and alpha
ShareBufferClass<float> *m_sizeBuffer; ///< array of particle sizes
Expand All @@ -67,4 +69,6 @@ class W3DParticleSystemManager : public ParticleSystemManager
ParticleSystemInfo::ParticleShaderType m_batchShaderType;
Bool m_readyToRender; ///< if true, it is OK to render
Bool m_batchBillboard;
Bool m_batchIsConforming;
AABoxClass m_batchBoundingBox;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 TheSuperHackers
**
** 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/>.
*/

#pragma once

#include <vector>

#include "Lib/BaseType.h"
#include "WW3D2/shader.h"
#include "WWLib/sharebuf.h"
#include "WWMath/vector3.h"
#include "WWMath/vector4.h"

class AABoxClass;
class TextureClass;
class WorldHeightMap;
struct VertexFormatXYZNDUV2;

// Renders particles as meshes that follow the shape of the terrain below them.
class W3DTerrainParticle
{
public:
W3DTerrainParticle();
~W3DTerrainParticle();

void setTexture(TextureClass* texture);
void setShader(ShaderClass shader);
void setArrays(ShareBufferClass<Vector3>* locs,
ShareBufferClass<Vector4>* diffuse = nullptr,
ShareBufferClass<Real>* sizes = nullptr,
ShareBufferClass<UnsignedByte>* orientations = nullptr,
Int activePointCount = -1);
void setBoundingBox(const AABoxClass& worldBoundingBox);
void render();

private:
struct ParticleContext;

void drawRegion(WorldHeightMap& map, const ParticleContext& particle, const IRegion2D& bounds);
void drawQuad(WorldHeightMap& map, const ParticleContext& particle, const IRegion2D& bounds, Bool flipped);
UnsignedShort addVertex(WorldHeightMap& map, const ParticleContext& particle, Int x, Int y);
void addTriangle(UnsignedShort a, UnsignedShort b, UnsignedShort c);
void resetVertexLookup();
void flushBatch();
IRegion2D calcTerrainBounds(WorldHeightMap& map, const Vector3& loc, Real projectedRadius) const;
void updateSettings();

std::vector<VertexFormatXYZNDUV2> m_vertexData; ///< Vertices of the current batch.
std::vector<UnsignedShort> m_indexData; ///< Indices defining the triangles of the current batch.
std::vector<UnsignedByte> m_outcodes; ///< UV outcodes indexed by batch vertex.
std::vector<UnsignedShort> m_vertexLookup; ///< Particle grid vertices mapped to current batch indices.
UnsignedShort m_numVertices; ///< Number of vertices used in m_vertexData.
UnsignedShort m_numIndices; ///< Number of indices used in m_indexData.

TextureClass* m_texture;
ShaderClass m_shader;

ShareBufferClass<Vector3>* m_pointLoc; ///< World space point locations.
ShareBufferClass<Vector4>* m_pointDiffuse; ///< RGBA values (nullptr if not used).
ShareBufferClass<Real>* m_pointSize; ///< Size override table (nullptr if not used).
ShareBufferClass<UnsignedByte>* m_pointOrientation; ///< Orientation indices (nullptr if not used).
Int m_pointCount; ///< Total point count.
IRegion2D m_terrainInViewBounds; ///< Bounding box of which terrain cell indices are on the screen.

Real m_defaultPointSize; ///< Point size (size array overrides if present).
Vector3 m_defaultPointColor; ///< Point color (color array overrides if present).
Real m_defaultPointAlpha; ///< Point alpha (alpha array overrides if present).
UnsignedByte m_defaultPointOrientation; ///< Point orientation (orientation array overrides if present).
UnsignedInt m_defaultDiffuse; ///< Diffuse built from m_defaultPointColor and m_defaultPointAlpha.
};
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ class WorldHeightMap : public RefCountClass,

Int getXExtent() const {return m_width;} ///<number of vertices in x
Int getYExtent() const {return m_height;} ///<number of vertices in y
IRegion2D getLogicalBounds() const
{
IRegion2D bounds;
bounds.lo.x = -m_borderSize;
bounds.lo.y = -m_borderSize;
bounds.hi.x = m_width - m_borderSize;
bounds.hi.y = m_height - m_borderSize;
return bounds;
}

Region2D getDrawRegion2D();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
#include "W3DDevice/GameClient/HeightMap.h"
#include "W3DDevice/GameClient/W3DSmudge.h"
#include "W3DDevice/GameClient/W3DSnow.h"
#include "W3DDevice/GameClient/W3DTerrainParticle.h"
#include "WW3D2/camera.h"


//------------------------------------------------------------------------------ Performance Timers
//#include "Common/PerfMetrics.h"
//#include "Common/PerfTimer.h"
Expand All @@ -51,6 +51,7 @@ W3DParticleSystemManager::W3DParticleSystemManager()

m_pointGroup = nullptr;
m_streakLine = nullptr;
m_terrainParticles = nullptr;
m_posBuffer = nullptr;
m_RGBABuffer = nullptr;
m_sizeBuffer = nullptr;
Expand All @@ -62,6 +63,7 @@ W3DParticleSystemManager::W3DParticleSystemManager()
m_pointGroup = NEW PointGroupClass();
//m_streakLine = nullptr;
m_streakLine = NEW StreakLineClass();
m_terrainParticles = NEW W3DTerrainParticle();

m_posBuffer = NEW_REF( ShareBufferClass<Vector3>, (MAX_POINTS_PER_GROUP, "W3DParticleSystemManager::m_posBuffer") );
m_RGBABuffer = NEW_REF( ShareBufferClass<Vector4>, (MAX_POINTS_PER_GROUP, "W3DParticleSystemManager::m_RGBABuffer") );
Expand All @@ -80,6 +82,9 @@ W3DParticleSystemManager::~W3DParticleSystemManager()
REF_PTR_RELEASE(m_streakLine);
}

delete m_terrainParticles;
m_terrainParticles = nullptr;

REF_PTR_RELEASE(m_posBuffer);
REF_PTR_RELEASE(m_RGBABuffer);
REF_PTR_RELEASE(m_sizeBuffer);
Expand Down Expand Up @@ -222,7 +227,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo)
// setup a new particle batch texture if prior batch was flushed.
if (canBatch && m_batchTexture == nullptr)
{
initializeBatch(*sys, texture);
initializeBatch(*sys, texture, bbox);
}

UnsignedInt startCount = pointCount;
Expand Down Expand Up @@ -277,7 +282,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo)
// This prevents particles being dropped. Bank the stats first as the flush resets count to 0.
m_onScreenParticleCount += (pointCount - startCount);
flushParticleBatch(rinfo, pointCount);
initializeBatch(*sys, texture);
initializeBatch(*sys, texture, bbox);
startCount = 0;
}
}
Expand Down Expand Up @@ -370,7 +375,6 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo)
pointCount = startCount;
}


/// @todo lorenzen sez: this should be debug only:
//add particle count to total
m_onScreenParticleCount += (pointCount - startCount);
Expand Down Expand Up @@ -416,46 +420,64 @@ Bool W3DParticleSystemManager::finishedBatch(const ParticleSystem& system, const
{
return texture.Peek() != m_batchTexture.Peek() ||
system.getShaderType() != m_batchShaderType ||
system.shouldBillboard() != m_batchBillboard;
system.shouldBillboard() != m_batchBillboard ||
system.isTerrainConforming() != m_batchIsConforming;
}

void W3DParticleSystemManager::initializeBatch(const ParticleSystem& system, const RefCountPtr<TextureClass>& texture)
void W3DParticleSystemManager::initializeBatch(const ParticleSystem& system, const RefCountPtr<TextureClass>& texture, const AABoxClass& bbox)
{
m_batchTexture = texture;
m_batchShaderType = system.getShaderType();
m_batchBillboard = system.shouldBillboard();
m_batchIsConforming = !system.shouldBillboard() && !system.isUsingVolumeParticles() && system.isTerrainConforming();
m_batchBoundingBox = bbox;
}

void W3DParticleSystemManager::flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount)
{
if (pointCount > 0)
if(pointCount > 0)
{
m_pointGroup->Set_Texture(m_batchTexture.Peek());

switch (m_batchShaderType)
ShaderClass shader;
switch(m_batchShaderType )
{
case ParticleSystemInfo::ADDITIVE:
m_pointGroup->Set_Shader(ShaderClass::_PresetAdditiveSpriteShader);
break;
case ParticleSystemInfo::ALPHA:
m_pointGroup->Set_Shader(ShaderClass::_PresetAlphaSpriteShader);
break;
case ParticleSystemInfo::ALPHA_TEST:
m_pointGroup->Set_Shader(ShaderClass::_PresetATestSpriteShader);
break;
case ParticleSystemInfo::MULTIPLY:
m_pointGroup->Set_Shader(ShaderClass::_PresetMultiplicativeSpriteShader);
break;
case ParticleSystemInfo::ADDITIVE:
shader = ShaderClass::_PresetAdditiveSpriteShader;
break;
case ParticleSystemInfo::ALPHA:
shader = ShaderClass::_PresetAlphaSpriteShader;
break;
case ParticleSystemInfo::ALPHA_TEST:
shader = ShaderClass::_PresetATestSpriteShader;
break;
case ParticleSystemInfo::MULTIPLY:
shader = ShaderClass::_PresetMultiplicativeSpriteShader;
break;
}

m_pointGroup->Set_Flag(PointGroupClass::TRANSFORM, true);
m_pointGroup->Set_Point_Mode(PointGroupClass::QUADS);
m_pointGroup->Set_Arrays(m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount);
m_pointGroup->Set_Billboard(m_batchBillboard);
m_pointGroup->Set_Point_Frame(0);
m_pointGroup->Render(rinfo);
if (m_batchIsConforming)
{
m_terrainParticles->setTexture(m_batchTexture.Peek());
m_terrainParticles->setShader( shader );
m_terrainParticles->setArrays( m_posBuffer, m_RGBABuffer, m_sizeBuffer, m_angleBuffer, pointCount );
m_terrainParticles->setBoundingBox( m_batchBoundingBox );
m_terrainParticles->render();
}
else // draw regular point group
{
m_pointGroup->Set_Texture(m_batchTexture.Peek());
m_pointGroup->Set_Shader(shader);
m_pointGroup->Set_Flag(PointGroupClass::TRANSFORM, true);
m_pointGroup->Set_Point_Mode(PointGroupClass::QUADS);
m_pointGroup->Set_Arrays(m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount);
m_pointGroup->Set_Billboard(m_batchBillboard);
m_pointGroup->Set_Point_Frame(0);
m_pointGroup->Render(rinfo);
}

pointCount = 0;
m_batchBillboard = false;
m_batchIsConforming = false;
m_batchShaderType = ParticleSystemInfo::INVALID_SHADER;
}

m_batchTexture.Clear();
Expand Down
Loading
Loading