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
11 changes: 10 additions & 1 deletion 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 @@ -515,7 +516,12 @@ class ParticleSystemTemplate : public MemoryPoolObject, protected ParticleSystem

void validate();

AsciiString getName() const { return m_name; }
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 @@ -607,13 +614,15 @@ class ParticleSystem : public MemoryPoolObject,
void setInitialDelay( UnsignedInt delay ) { m_delayLeft = delay; }

const AsciiString& getParticleTypeName() const { return m_particleTypeName; } ///< return the name of the particles
const Bool isUsingParticles() const { return m_particleType == PARTICLE; }
const Bool isUsingDrawables() const { return m_particleType == DRAWABLE; }
const Bool isUsingStreak() const { return m_particleType == STREAK; }
const Bool isUsingSmudge() const { return m_particleType == SMUDGE; }
const Bool isUsingVolumeParticles() const { return m_particleType == VOLUME_PARTICLE; }
const UnsignedInt getVolumeParticleDepth() const { return m_volumeParticleDepth; }

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

ParticleShaderType getShaderType() { 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,13 +51,23 @@ class W3DParticleSystemManager : public ParticleSystemManager
virtual Int getOnScreenParticleCount() override { return m_onScreenParticleCount; }

private:
void initializeBatch(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
ShareBufferClass<uint8> *m_angleBuffer; ///< array of particle orientations

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,80 @@
/*
** 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 "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:

void processParticle(WorldHeightMap& map, const Vector3& loc, UnsignedInt diffuse, Real size, UnsignedByte orientation);
void drawQuad(const Vector3& loc, UnsignedInt diffuse, Real size, Real cosine, Real sine, Real height, const Vector3& normal);
void drawTerrainConformingMesh(WorldHeightMap& map, const Vector3& loc, const IRegion2D& bounds, UnsignedInt diffuse, Real size, Real cosine, Real sine);
void addTriangle(UnsignedShort baseVertex, UnsignedShort offsetA, UnsignedShort offsetB, UnsignedShort offsetC);
void flushBatch();
IRegion2D calcBounds(WorldHeightMap& map, const Vector3& loc, Real projectedRadius) const;
void updateSettings();

VertexFormatXYZNDUV2* m_vertexData; ///< Vertices of the current batch.
UnsignedShort* m_indexData; ///< Indices defining the triangles of the current batch.
UnsignedByte* m_outcodes; ///< UV outcodes to keep track which triangles are fully transparent.
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
Loading
Loading