JurassicParkTrespasser/jp2_pc/Source/Lib/Renderer/Particles.cpp
2020-04-01 21:49:18 +02:00

1541 lines
40 KiB
C++

/***********************************************************************************************
*
* Copyright © DreamWorks Interactive, 1998.
*
* Implementation of Particles.hpp.
*
* To do:
* Don't even create particles whose origin is out of the camera view.
*
***********************************************************************************************
*
* $Log:: /JP2_PC/Source/Lib/Renderer/Particles.cpp $
*
* 45 10/03/98 5:02p Pkeet
* Removed the normalized camera bias.
*
* 44 98/10/03 4:43 Speter
* Oops. Default gravity should be 5, not 0 to 5 range.
*
* 43 98.09.30 10:38p Mmouni
* Added support bUseTerrainColour.
*
* 42 98/09/28 2:46 Speter
* Removed clamp on particle effect intensity.
*
* 41 98.09.24 1:40a Mmouni
* Shrunk CRenderPolygon by a large amount.
* Multiple list depth sort stuff is now on a compile switch.
*
* 40 98/09/23 15:09 Speter
* Corrected projection code to take account of depth. Reverted v3Position() to old behaviour
* that causes tail to exactly follow head.
*
* 39 98/09/23 0:12 Speter
* Turned down wind. Stricter screen size limits.
*
* 38 98/09/19 14:47 Speter
* Changed method of randomising particle params, for more control. Added a wind component. Dust
* is now constant screen size.
*
* 37 98/09/17 3:42 Speter
* Made dust trajectory asymptotic, controlled by fAccelStuck; fAccelUnstuck now enables regular
* gravity. Added screen min and max sizes to fCamSize, so particles are always shown. Shrink
* solid particles with age.Removed fScreenSize() functions.
*
* 36 9/08/98 10:25p Pkeet
* Added a dust particle type.
*
* 35 9/08/98 7:09p Pkeet
* Fixed sorting for fluid particles.
*
* 34 9/08/98 6:31p Pkeet
* Fixed bug of solid particles not sorting against the terrain.
*
* 33 98/09/05 23:24 Speter
* Added max particle limit.
*
* 32 98/08/28 19:50 Speter
* Added CParticlesCreate::fIntensity(); Create() now takes a strength param in event units,
* rather than an interp param.
*
* 31 8/27/98 1:51p Asouth
* one change for MSL (vs. HP STL); statics scoped to the class that defines them
*
* 30 98/08/27 1:01 Speter
* Put a clamp on particle effect intensity (2.0 times nominal max). Fixed bug involving small
* step times.
*
* 29 98/08/25 19:05 Speter
* Replaced sStickTime with fLength parameter, which calculates former. Increased Z depth sort
* bias. Again fixed acceleration calc.
*
* 28 8/21/98 11:07p Rwycko
* Fixed several bugs in text prop loading.
*
* 27 98/08/21 18:05 Speter
* Added text prop loading. Moved fAccelStuck and fAccelUnstuck to SParticleParams. Added
* fThreshold, bLogScale. Added default constructors. Fixed stuck acceleration calculation.
* Added bias to screen Z to make sorting better.
*
**********************************************************************************************/
//
// Defines and pragmas.
//
#include "Common.hpp"
#include "Particles.hpp"
#include "Lib/W95/Direct3D.hpp"
#include "Lib/W95/WinInclude.hpp"
#include "D3DTypes.h"
#include "Lib/GeomDBase/PartitionPriv.hpp"
#include "PipeLine.hpp"
#include "Lib/Sys/W95/Render.hpp"
#include "Lib/Std/PrivSelf.hpp"
#include "RenderDefs.hpp"
#include "Camera.hpp"
#include "ScreenRender.hpp"
#include "PipeLineHeap.hpp"
#include "Lib/Math/FloatDef.hpp"
#include "Lib/Sys/W95/Render.hpp"
#include "Lib/Sys/Profile.hpp"
#include "Lib/Math/FastTrig.hpp"
#include "Lib/Std/Random.hpp"
#include "Lib/Transform/Matrix2.hpp"
#include "Lib/Groff/VTParse.hpp"
#include "Lib/GeomDBase/Terrain.hpp"
#include "Lib/EntityDBase/Query/QTerrain.hpp"
#include <map>
#include <math.h>
#include <memory.h>
#define bDEBUG_PARTICLES (1 && BUILDVER_MODE != MODE_FINAL)
#if bDEBUG_PARTICLES
#include "Lib/Sys/DebugConsole.hpp"
#endif // bDEBUG_DIRECT3D
//
// Macros.
//
//
// Module specific constants.
//
// A really long time for a particle.
const TSec sReallyOld = 1000.0f;
const int iMAX_PARTICLES = 300;
// The default wind velocity vector.
const CVector3<> v3Wind (0.25f, 0.0f, 0.0f);
// Near clip values for normalized camera space.
const float fNearClipNorm = 0.1f / 1000.0f;
const uint32 u4NearClipNorm = u4FromFloat(fNearClipNorm);
// Screen size limits (pixels).
const float fScrSizeMin = 1.0f;
const float fScrSizeMax = 5.0f;
// Points for an equilateral triangle.
CVector3<> v3TriA = CVector3<>( 0.0f, 0.0f, 1.0f);
CVector3<> v3TriB = CVector3<>( 0.866f, 0.0f, -0.5f);
CVector3<> v3TriC = CVector3<>(-0.866f, 0.0f, -0.5f);
CProfileStat psParticlesUpdate("Particles Upd", &proProfile.psRender),
psParticlesRender("Particles Ren", &proProfile.psRender);
//
// Module specific variables.
//
// Random number for the module.
static CRandom rndParticles;
//
// Internal class definitions.
//
//******************************************************************************************
//
class CTextureColours : public std::map< TD3DPixel, CTexture*, std::less<TD3DPixel> >
//
// Less than comparison class for texture colours.
//
// Prefix: texcol
//
//**************************************
{
public:
//******************************************************************************************
//
// Constructors and destructors.
//
// Default constructor.
CTextureColours()
{
}
// Destructor.
~CTextureColours()
{
Reset();
}
//******************************************************************************************
//
// Member functions.
//
//******************************************************************************************
//
void Reset()
{
iterator it = begin();
for (; it != end(); ++it)
{
CTexture* ptex = (*it).second;
delete ptex;
}
erase(begin(), end());
}
//
// Releases all shared surfaces and removes them from the STL map.
//
//**************************************
//******************************************************************************************
//
CTexture* ptexFind
(
CColour clr
)
//
// Returns a pointer to a shared surface if it has one with the given hash value, otherwise
// returns zero.
//
//**************************************
{
iterator it = find(clr.d3dcolGetD3DColour());
if (it == end())
{
CTexture* ptex = new CTexture();
// Add the colour to the texture for software and hardware.
ptex->tpSolid = prasMainScreen->pixFromColour(clr);
ptex->d3dpixColour = clr.d3dcolGetD3DColour();
// Add the texture to the map.
#ifdef __MWERKS__
insert(pair<const TD3DPixel, CTexture*>(clr.d3dcolGetD3DColour(), ptex));
#else
insert(std::pair<TD3DPixel, CTexture*>(clr.d3dcolGetD3DColour(), ptex));
#endif
// Find the colour back.
it = find(clr.d3dcolGetD3DColour());
}
return (*it).second;
}
};
class CCreateParticles::CPriv : public CCreateParticles
{
public:
//*************************************************************************************
//
void SetTexture
(
CTextureColours* ptexcol
)
//
// Set the texture based on the colour.
//
//**************************
{
Assert(ptexcol);
// Get the texture associated with the colour.
ptexTexture = ptexcol->ptexFind(clrColour);
}
//*********************************************************************************************
//
int iCreateNumParticles
(
TSec s_period = 1.0 // Time scaling for number of particles created.
) const
//
// Returns:
// A random integral number of particles, based on min and max ranges, and period.
//
//**************************
{
// Randomise the number of particles created based on ranges and s_period.
float f_num = rndParticles(ppMin.fNumParticles, ppMax.fNumParticles) * s_period;
// Convert float to an int, and randomly apply the remainder.
int i_num = iPosFloatCast(f_num);
if (rndParticles(0.0, 1.0) < f_num - i_num)
i_num++;
return i_num;
}
};
//*********************************************************************************************
//
class CParticleBase
//
// Object describes a single renderable particle.
//
// Prefix: prt
//
// Notes:
// The class contains a prebuilt polygon with its own vertex list as a means of
// simplifying and speeding up construction of polygons to add to the polygon list.
//
//**************************
{
public:
TSec sAge; // Amount of time the particle is alive.
TSec sLifespan; // Amount of time the particle will last.
TSec sStickTime; // Amount of time the particle will 'stick.'
bool bRendered; // Set to 'true' if the particle is rendered at least once.
bool bOneFrame; // Allow the particle to exist for only one frame.
float fAngle; // Angle of particle.
float fAngularVelocity; // Angular velocity of particle.
float fAccelStuck;
float fAccelUnstuck;
float fSize;
CVector3<> v3StartPosition; // Starting position of the particle.
CVector3<> v3StartVelocity; // Starting velocity of the particle.
float fStartVelocity;
float fFinalDist; // For dust.
SRenderVertex rvVertices[4];
SRenderVertex* prvVertices[4];
CRenderPolygon rpolyPolygon;
CTexture* ptexTexture;
static float fScreenWidth;
static float fScreenHeight;
public:
//*****************************************************************************************
//
// Constructors.
//
// Default constructor.
CParticleBase()
: ptexTexture(0)
{
SetPolygon();
}
// Copy constructor.
CParticleBase(const CParticleBase& particle)
{
memcpy(this, &particle, sizeof(particle));
SetPolygon();
}
// Parameter constructor.
CParticleBase(const CVector3<>& v3_origin, const CDir3<>& d3_dir, const CCreateParticles& crt);
//*****************************************************************************************
//
// Member functions.
//
//*****************************************************************************************
//
void Update
(
TSec sec
)
//
// Updates the particles by physically moving them.
//
//**************************
{
sAge += sec;
}
//*****************************************************************************************
//
virtual bool bAdd
(
const CCamera& cam,
const CTransform3<>& tf3_to_norm_cam
)
//
// Prepares the particle to add to the renderer.
//
//**************************
{
Assert(0);
return false;
}
//*****************************************************************************************
//
bool bRetired
(
)
//
// Returns 'true' if the particle is too old to live.
//
//**************************
{
if (bOneFrame)
{
return bRendered;
}
return sAge >= sLifespan;
}
//*****************************************************************************************
//
virtual CVector3<> v3Position
(
TSec sec // Time to determine the position of the particle for.
) const;
//
// Returns the position of the particle at the given time.
//
//**************************
protected:
//*****************************************************************************************
bool bNotInScreen();
//*****************************************************************************************
bool bNotInCamera();
//*****************************************************************************************
virtual float fCamSize(const CCamera& cam, float f_cam_y) const;
//*****************************************************************************************
void SetPolygon();
#if bDEBUG_PARTICLES
//*****************************************************************************************
void Validate();
#endif // bDEBUG_PARTICLES
};
//*********************************************************************************************
//
class CParticleFluid : public CParticleBase
//
// Object describes a single renderable particle.
//
// Prefix: prt
//
// Notes:
// The class contains a prebuilt polygon with its own vertex list as a means of
// simplifying and speeding up construction of polygons to add to the polygon list.
//
//**************************
{
public:
//*****************************************************************************************
//
// Constructors.
//
// Default constructor.
CParticleFluid()
: CParticleBase()
{
Initialize();
}
// Copy constructor.
CParticleFluid(const CParticleFluid& particle)
: CParticleBase(particle)
{
Initialize();
}
// Parameter constructor.
CParticleFluid(const CVector3<>& v3_origin, const CDir3<>& d3_dir, const CCreateParticles& crt)
: CParticleBase(v3_origin, d3_dir, crt)
{
Initialize();
}
//*****************************************************************************************
//
// Member functions.
//
//*****************************************************************************************
//
virtual bool bAdd
(
const CCamera& cam,
const CTransform3<>& tf3_to_norm_cam
);
//
// Prepares the particle to add to the renderer.
//
//**************************
protected:
//*****************************************************************************************
void Initialize();
};
//*********************************************************************************************
//
class CParticleSolid : public CParticleBase
//
// Object describes a single renderable particle.
//
// Prefix: prt
//
// To do:
// Implement particle rotation if required.
//
//**************************
{
public:
//*****************************************************************************************
//
// Constructors.
//
// Default constructor.
CParticleSolid()
: CParticleBase()
{
Initialize();
}
// Copy constructor.
CParticleSolid(const CParticleSolid& particle)
: CParticleBase(particle)
{
Initialize();
}
// Parameter constructor.
CParticleSolid(const CVector3<>& v3_origin, const CDir3<>& d3_dir, const CCreateParticles& crt)
: CParticleBase(v3_origin, d3_dir, crt)
{
Initialize();
}
//*****************************************************************************************
//
// Member functions.
//
//*****************************************************************************************
//
virtual bool bAdd
(
const CCamera& cam,
const CTransform3<>& tf3_to_norm_cam
);
//
// Prepares the particle to add to the renderer.
//
//**************************
protected:
//*****************************************************************************************
void Initialize();
};
//*********************************************************************************************
//
class CParticleDust : public CParticleSolid
//
// Object describes a single renderable dust particle.
//
// Prefix: prt
//
// To do:
// Implement particle rotation if required.
//
//**************************
{
public:
//*****************************************************************************************
//
// Constructors.
//
// Default constructor.
CParticleDust()
: CParticleSolid()
{
Setup();
}
// Copy constructor.
CParticleDust(const CParticleSolid& particle)
: CParticleSolid(particle)
{
Setup();
}
// Parameter constructor.
CParticleDust(const CVector3<>& v3_origin, const CDir3<>& d3_dir, const CCreateParticles& crt)
: CParticleSolid(v3_origin, d3_dir, crt)
{
Setup();
}
//*****************************************************************************************
//
// Overrides.
//
//*****************************************************************************************
virtual CVector3<> v3Position(TSec sec) const;
protected:
//*****************************************************************************************
void Setup();
//*****************************************************************************************
virtual float fCamSize(const CCamera& cam, float f_cam_y) const
{
// fSize refers to screen pixels, so convert to camera space.
return fSize * f_cam_y / (fScreenWidth * 0.5f);
}
};
//*********************************************************************************************
//
class CParticleList : public std::list<CParticleBase>
//
// Object describes a list of particles.
//
// Prefix: pprt
//
// Notes:
// This is a trivial class to allow a simplified interface into the main particle system
// class.
//
//**************************
{
public:
};
//
// Class implementations.
//
//*********************************************************************************************
//
// Class CParticleBase implementation.
//
//*****************************************************************************************
CParticleBase::CParticleBase(const CVector3<>& v3_origin, const CDir3<>& d3_dir, const CCreateParticles& crt)
: ptexTexture(crt.ptexTexture), sAge(0.0f), bRendered(false), bOneFrame(false)
{
Assert(ptexTexture);
SetPolygon();
// Determine the lifespan of the particle.
bOneFrame = crt.ppMax.sLifespan < 0.01f;
// Get random parameters.
//SParticleParams pp = crt.ppMin.ppRandom(crt.ppMax);
SParticleParams pp = crt.ppMin.ppInterpolate(crt.ppMax, rndParticles(0.0, 1.0));
fSize = pp.fSize;
sLifespan = pp.sLifespan;
// Set the initial position of the particle.
v3StartPosition = v3_origin;
// Set the 'stick' time from length. Either velocity and/or stuck acceleration
// can produce length. Take the min of the 2, while ensuring that we don't
// divide by 0.
sStickTime = Min
(
pp.fLength / Max(pp.fVelocity, .0001f),
fSqrt(2.0 * pp.fLength / Max(pp.fAccelStuck, .0001f))
);
fAccelStuck = pp.fAccelStuck;
fAccelUnstuck = pp.fAccelUnstuck;
if (bOneFrame)
{
v3StartVelocity = CVector3<>(0.0f, 0.0f, 0.0f);
}
else
{
// Get a randomly determined velocity.
v3StartVelocity = d3_dir;
v3StartVelocity *= pp.fVelocity;
Update(rndParticles(0.005f, Max(0.005f, crt.sBeginAge)));
}
}
//*****************************************************************************************
CVector3<> CParticleBase::v3Position(TSec sec) const
{
CVector3<> v3_s = v3StartPosition;
// Clamp the time.
if (sec < 0.0f)
{
return v3_s;
}
//
// A little bit of Newton for the 'Z' component:
//
// s1 = s0 + v * t + 0.5 * (a' * t' * t' + a * t * t)
//
// And for the 'X' and 'Y' components:
//
// s1 = s0 + v * t
//
v3_s += v3StartVelocity * sec;
// Apply stuck and unstuck acceleration.
if (sAge <= sStickTime)
{
// Still stuck. This is the forward position; apply stuck acceleration.
Assert(sec >= 0.0f);
v3_s.tZ -= 0.5f * fAccelStuck * Sqr(sec);
}
else
{
// Now unstuck; apply stuck and unstuck acceleration.
v3_s.tZ -= 0.5f * fAccelStuck * Sqr(sStickTime);
v3_s.tZ -= 0.5f * fAccelUnstuck * Sqr(sec - sStickTime);
}
return v3_s;
}
//*****************************************************************************************
//
float CParticleBase::fCamSize
(
const CCamera& cam,
float f_cam_y
) const
//
// Returns the size of the particle in screenspace.
//
//**************************
{
float f_far = cam.campropGetProperties().rFarClipPlaneDist;
// Calculate the dimension scaled for normalized camera space.
float f_size = fSize / f_far;
// Clamp the size to appropriate screen values.
float f_to_cam = f_cam_y / (fScreenWidth * 0.5f);
SetMinMax(f_size, fScrSizeMin * f_to_cam, fScrSizeMax * f_to_cam);
// Return the calculated dimension.
return f_size;
}
//*****************************************************************************************
//
bool CParticleBase::bNotInScreen
(
)
//
// Returns 'true' if the particle is touching a screen edge or entirely off the screen.
//
//**************************
{
// Test vertices.
for (int i = 0; i < rpolyPolygon.paprvPolyVertices.uLen; ++i)
{
CVector3<> v3 = rvVertices[i].v3Screen;
if (v3.tX < 1.0f)
return true;
if (v3.tX > fScreenWidth)
return true;
if (v3.tY < 1.0f)
return true;
if (v3.tY > fScreenHeight)
return true;
if (v3.tZ <= 0.0f)
return true;
}
return false;
}
//*****************************************************************************************
//
bool CParticleBase::bNotInCamera
(
)
//
// Returns 'true' if the particle is touching or outside of the camera frustrum.
//
//**************************
{
// Test vertices.
for (int i = 0; i < rpolyPolygon.paprvPolyVertices.uLen; ++i)
{
CVector3<> v3 = rvVertices[i].v3Cam;
// Get an integer representation of y and test for a negative value.
uint32 u4_y = u4FromFloat(v3.tY);
// Fail if behind or on the camera.
if (u4_y & 0x80000000)
return true;
if (u4_y < u4NearClipNorm)
return true;
// Fail if x or z is out of bounds.
if ((u4FromFloat(v3.tX) & 0x7FFFFFFF) >= u4_y)
return true;
if ((u4FromFloat(v3.tZ) & 0x7FFFFFFF) >= u4_y)
return true;
}
return false;
}
//*****************************************************************************************
//
void CParticleBase::SetPolygon
(
)
//
// Class sets up the required polygon structures.
//
//**************************
{
rvVertices[0].cvIntensity = 1.0f;
rvVertices[0].tcTex.tX = 0.0f;
rvVertices[0].tcTex.tY = 0.0f;
rvVertices[1].cvIntensity = 1.0f;
rvVertices[1].tcTex.tX = 1.0f;
rvVertices[1].tcTex.tY = 0.0f;
rvVertices[2].cvIntensity = 1.0f;
rvVertices[2].tcTex.tX = 1.0f;
rvVertices[2].tcTex.tY = 1.0f;
rvVertices[3].cvIntensity = 1.0f;
rvVertices[3].tcTex.tX = 0.0f;
rvVertices[3].tcTex.tY = 1.0f;
prvVertices[0] = &rvVertices[0];
prvVertices[1] = &rvVertices[1];
prvVertices[2] = &rvVertices[2];
prvVertices[3] = &rvVertices[3];
rpolyPolygon.paprvPolyVertices.atArray = prvVertices;
rpolyPolygon.paprvPolyVertices.uLen = 4;
rpolyPolygon.seterfFace = Set(erfTRAPEZOIDS);
rpolyPolygon.ptexTexture = ptexTexture;
rpolyPolygon.cvFace = 1.0f;
rpolyPolygon.iFogBand = 0;
rpolyPolygon.rPlaneTolerance = 0.001;
rpolyPolygon.iMipLevel = 0;
rpolyPolygon.bAccept = true;
rpolyPolygon.eamAddressMode = eamTileNone;
rpolyPolygon.bPrerasterized = false;
#if (VER_DEBUG)
rpolyPolygon.pshOwningShape = 0;
#endif
if (d3dDriver.bUseD3D())
{
rpolyPolygon.ehwHardwareFlags = ehwFill;
rpolyPolygon.bFullHardware = true;
}
else
{
rpolyPolygon.ehwHardwareFlags = ehwSoftware;
rpolyPolygon.bFullHardware = false;
}
}
#if bDEBUG_PARTICLES
//*****************************************************************************************
//
void CParticleBase::Validate
(
)
//
// Rigorous asserts to verify the particle's data.
//
//**************************
{
uint u = 0;
uint u_len = rpolyPolygon.paprvPolyVertices.uLen;
AlwaysAssert(u_len == 3 || u_len == 4);
for (u = 0; u < u_len; ++u)
{
SRenderVertex& rv = rvVertices[u];
AlwaysAssert(rv.v3Screen.tZ > 0.0f);
AlwaysAssert(rv.v3Cam.tY > 0.0f);
}
}
#endif // bDEBUG_PARTICLES
//*********************************************************************************************
//
// CParticleFluid implementation.
//
//*****************************************************************************************
bool CParticleFluid::bAdd(const CCamera& cam, const CTransform3<>& tf3_to_norm_cam)
{
// Set the rendered flag.
bRendered = true;
CVector3<> v3_a = v3Position(sAge - sStickTime) * tf3_to_norm_cam;
CVector3<> v3_b = v3Position(sAge) * tf3_to_norm_cam;
// Special case of a particle that has not moved yet.
if (bOneFrame)
v3_a = v3_b;
// Extend along the length of the particle, in screen space.
CVector3<> v3_dir = v3_b - v3_a;
v3_dir.tY = 0.0f;
float f_len = v3_dir.tLen();
if (f_len <= 0.0f)
v3_dir = CVector3<>(0.0f, 0.0f, 1.0f);
else
v3_dir /= f_len;
float f_size = fCamSize(cam, v3_b.tY);
// Vertices along the direction.
rvVertices[0].v3Cam = v3_b + v3_dir * f_size;
rvVertices[2].v3Cam = v3_a - v3_dir * f_size;
// Vertices on the perpendicular.
Swap(v3_dir.tX, v3_dir.tZ);
v3_dir.tZ *= -1.0f;
rvVertices[3].v3Cam = v3_b + v3_dir * f_size;
rvVertices[1].v3Cam = v3_b - v3_dir * f_size;
// Project the points because the vertices are not in the regular pipeline.
rvVertices[0].v3Screen = cam.ProjectPoint(rvVertices[0].v3Cam);
rvVertices[1].v3Screen = cam.ProjectPoint(rvVertices[1].v3Cam);
rvVertices[2].v3Screen = cam.ProjectPoint(rvVertices[2].v3Cam);
rvVertices[3].v3Screen = cam.ProjectPoint(rvVertices[3].v3Cam);
// For efficiency in depth sorting, set all camera Y's and screen Z's equal.
rvVertices[3].v3Cam.tY = rvVertices[2].v3Cam.tY = rvVertices[1].v3Cam.tY = rvVertices[0].v3Cam.tY;
rvVertices[3].v3Screen.tZ = rvVertices[2].v3Screen.tZ = rvVertices[1].v3Screen.tZ = rvVertices[0].v3Screen.tZ;
//
// If this particle is touching or off the screen, kill it.
//
if (bNotInScreen())
{
sAge = sReallyOld;
return false;
}
// Set appropriate flags.
Initialize();
rpolyPolygon.bPrerasterized = false;
#if bDEBUG_PARTICLES
Validate();
#endif
return true;
}
//*****************************************************************************************
//
void CParticleFluid::Initialize
(
)
//
// Class sets up the required structures.
//
//**************************
{
if (rpolyPolygon.bFullHardware)
{
if (d3dDriver.bUseAlpha())
{
rpolyPolygon.ehwHardwareFlags = ehwFluid;
}
}
}
//*********************************************************************************************
//
// CParticleSolid implementation.
//
//*****************************************************************************************
bool CParticleSolid::bAdd(const CCamera& cam, const CTransform3<>& tf3_to_norm_cam)
{
// Set the rendered flag.
bRendered = true;
// Get the position of the particle in normalized camera space.
CVector3<> v3_norm_cam = v3Position(sAge) * tf3_to_norm_cam;
// Get the size of the particle in normalized camera space.
float f_size = fCamSize(cam, v3_norm_cam.tY);
// Shrink the particle over time.
f_size *= (sLifespan - sAge) / sLifespan;
// Extend outward from the centroid of the particle.
rvVertices[0].v3Cam = v3_norm_cam + v3TriC * f_size;
rvVertices[1].v3Cam = v3_norm_cam + v3TriB * f_size;
rvVertices[2].v3Cam = v3_norm_cam + v3TriA * f_size;
/*
// If this particle is touching or off the screen, kill it.
if (bNotInCamera())
{
sAge = sReallyOld;
return false;
}
*/
// Project the points because the vertices are not in the regular pipeline.
rvVertices[0].v3Screen = cam.ProjectPoint(rvVertices[0].v3Cam);
rvVertices[1].v3Screen = cam.ProjectPoint(rvVertices[1].v3Cam);
rvVertices[2].v3Screen = cam.ProjectPoint(rvVertices[2].v3Cam);
// If this particle is touching or off the screen, kill it.
if (bNotInScreen())
{
sAge = sReallyOld;
return false;
}
// Set appropriate flags.
Initialize();
// Set appropriate flags.
rpolyPolygon.bPrerasterized = false;
#if bDEBUG_PARTICLES
Validate();
#endif
return true;
}
//*****************************************************************************************
//
void CParticleSolid::Initialize
(
)
//
// Class sets up the required structures.
//
//**************************
{
rvVertices[3].v3Screen = CVector3<>(1.0f, 1.0f, 0.1f);
rpolyPolygon.paprvPolyVertices.uLen = 3;
if (rpolyPolygon.bFullHardware)
{
rpolyPolygon.ehwHardwareFlags = ehwFill;
}
}
//*********************************************************************************************
//
// CParticleDust implementation.
//
//*****************************************************************************************
CVector3<> CParticleDust::v3Position(TSec sec) const
{
CVector3<> v3_s = v3StartPosition;
// Clamp the time.
if (sec <= 0.0f)
return v3_s;
// Wacky computation of distance.
TSec s_effective = sec * fFinalDist / (fStartVelocity * sec + fFinalDist);
v3_s += v3StartVelocity * s_effective;
// Add regular acceleration.
v3_s.tZ -= 0.5f * fAccelUnstuck * Sqr(sec);
// Add wind velocity. Scale it according to how "light" the particle is.
// The closer fAccelUnstuck is to real gravity, the heavier the particle).
v3_s += v3Wind * (1.0f - fAccelUnstuck * 0.1f) * sec;
return v3_s;
}
//*****************************************************************************************
void CParticleDust::Setup()
{
AlwaysAssert(fAccelStuck != 0.0f);
// Get the velocity and direction of the particle.
fStartVelocity = v3StartVelocity.tLen();
AlwaysAssert(fStartVelocity != 0.0f);
CVector3<> v3_dir = v3StartVelocity;
v3_dir /= fStartVelocity;
fFinalDist = fSqrt(fStartVelocity / fAccelStuck);
}
//*****************************************************************************************
//
// Struct SParticleParams implementation.
//
//*************************************************************************************
//
// Member functions implementations.
//
//*************************************************************************************
SParticleParams::SParticleParams()
{
fSpinRadPerSec = dDegreesToRadians(360.0f);
fNumParticles = 8.0f;
fVelocity = 4.0f;
sLifespan = 1.0f;
fSize = 0.01f;
fLength = 0.04f;
fSpreadRadians = dDegreesToRadians(60.0f);
fThreshold = 1.0f;
fAccelStuck = 1.0f;
fAccelUnstuck = 5.0f;
}
//*************************************************************************************
SParticleParams SParticleParams::ppInterpolate(const SParticleParams& pp, float f_t) const
{
SParticleParams pp_ret;
float f_s = 1.0f - f_t;
pp_ret.fNumParticles = f_s * fNumParticles + f_t * pp.fNumParticles;
pp_ret.fVelocity = f_s * fVelocity + f_t * pp.fVelocity;
pp_ret.sLifespan = f_s * sLifespan + f_t * pp.sLifespan;
pp_ret.fLength = f_s * fLength + f_t * pp.fLength;
pp_ret.fAccelStuck = f_s * fAccelStuck + f_t * pp.fAccelStuck;
pp_ret.fAccelUnstuck = f_s * fAccelUnstuck + f_t * pp.fAccelUnstuck;
pp_ret.fSize = f_s * fSize + f_t * pp.fSize;
pp_ret.fSpinRadPerSec = f_s * fSpinRadPerSec + f_t * pp.fSpinRadPerSec;
pp_ret.fSpreadRadians = f_s * fSpreadRadians + f_t * pp.fSpreadRadians;
return pp_ret;
}
//*************************************************************************************
SParticleParams SParticleParams::operator *(float f_t) const
{
SParticleParams pp_ret;
pp_ret.fNumParticles = f_t * fNumParticles;
pp_ret.fVelocity = f_t * fVelocity;
pp_ret.sLifespan = f_t * sLifespan;
pp_ret.fLength = f_t * fLength;
pp_ret.fAccelStuck = f_t * fAccelStuck;
pp_ret.fAccelUnstuck = f_t * fAccelUnstuck;
pp_ret.fSize = f_t * fSize;
pp_ret.fSpinRadPerSec = f_t * fSpinRadPerSec;
pp_ret.fSpreadRadians = f_t * fSpreadRadians;
return pp_ret;
}
//*************************************************************************************
SParticleParams SParticleParams::ppRandom(const SParticleParams& pp) const
{
SParticleParams pp_ret;
float f_age_size = rndParticles(0.0, 1.0);
pp_ret.fNumParticles = rndParticles(0.0, 1.0) * (pp.fNumParticles - fNumParticles) + fNumParticles;
pp_ret.fVelocity = rndParticles(0.0, 1.0) * (pp.fVelocity - fVelocity) + fVelocity;
pp_ret.sLifespan = f_age_size * (pp.sLifespan - sLifespan) + sLifespan;
pp_ret.fSize = f_age_size * (pp.fSize - fSize) + fSize;
pp_ret.fLength = rndParticles(0.0, 1.0) * (pp.fLength - fLength) + fLength;
pp_ret.fAccelStuck = rndParticles(0.0, 1.0) * (pp.fAccelStuck - fAccelStuck) + fAccelStuck;
pp_ret.fAccelUnstuck = f_age_size * (pp.fAccelUnstuck - fAccelUnstuck) + fAccelUnstuck;
pp_ret.fSpreadRadians = rndParticles(0.0, 1.0) * (pp.fSpreadRadians - fSpreadRadians) + fSpreadRadians;
pp_ret.fSpinRadPerSec = rndParticles(0.0, 1.0) * (pp.fSpinRadPerSec - fSpinRadPerSec) + fSpinRadPerSec;
return pp_ret;
}
//*****************************************************************************************
//
// Class CCreateParticles implementation.
//
//*************************************************************************************
//
// Class CCreateParticles constructors.
//
//*************************************************************************************
CCreateParticles::CCreateParticles(CColour clr)
: clrColour(clr), ptexTexture(0), bUseTerrainColour(false)
{
eptParticleType = eptFluid;
bLogScale = true;
iPriority = 0;
fTopMin = 0.25f;
// Set most min values to 0 by default.
ppMin = ppMax * 0.0f;
ppMin.fAccelStuck = 1.0f;
ppMin.fAccelUnstuck = 5.0f;
}
//*************************************************************************************
CCreateParticles::CCreateParticles
(
CLoadWorld* pload, // The loader.
const CHandle& h_object, // handle of object in value table.
CValueTable* pvtable // Value Table.
)
{
// First set everything to defaults.
eptParticleType = eptFluid;
bLogScale = true;
iPriority = 0;
fTopMin = 0.25f;
bUseTerrainColour = false;
// Set default minimum params to all be some fraction of max params.
ppMin = ppMax * 0.0f;
ppMin.fAccelStuck = 1.0f;
ppMin.fAccelUnstuck = 5.0f;
SETUP_TEXT_PROCESSING(pvtable, pload)
SETUP_OBJECT_HANDLE(h_object)
{
bool b;
if (bFILL_BOOL(b, esSolid))
eptParticleType = b ? eptSolid : eptFluid;
if (bFILL_BOOL(b, esDust))
eptParticleType = b ? eptDust : eptParticleType;
bFILL_BOOL(bLogScale, esLog);
bFILL_INT(iPriority, esPriority);
int i_red, i_green, i_blue;
if (bFILL_INT(i_red, esR) && bFILL_INT(i_green, esG) && bFILL_INT(i_blue, esB))
{
clrColour = CColour(i_red, i_green, i_blue);
}
// Load the min and max sub-objects.
for (ESymbol es = esMin; es <= esMax; es = ESymbol((int)es + 1))
{
IF_SETUP_OBJECT_SYMBOL(es)
{
SParticleParams* ppp = es == esMin ? &ppMin : &ppMax;
bFILL_FLOAT(ppp->fNumParticles, esNumber);
bFILL_FLOAT(ppp->fVelocity, esVelocity);
bFILL_FLOAT(ppp->sLifespan, esLifespan);
bFILL_FLOAT(ppp->fAccelUnstuck, esGravity);
if (!bFILL_FLOAT(ppp->fAccelStuck, esStuckGravity))
ppp->fAccelStuck = ppp->fAccelUnstuck;
bFILL_FLOAT(ppp->fSize, esSize);
bFILL_FLOAT(ppp->fLength, esLength);
if (bFILL_FLOAT(ppp->fSpreadRadians,esSpread))
// It was actually given in degrees, so convert.
ppp->fSpreadRadians *= dDEGREES;
bFILL_FLOAT(ppp->fThreshold, esThreshold);
}
END_OBJECT;
}
}
END_OBJECT;
END_TEXT_PROCESSING;
}
//*************************************************************************************
//
// Class CCreateParticles member functions.
//
CColour clrScaleSat(CColour clr, float f_scale)
{
return CColour
(
Min(iRound(clr.u1Red * f_scale), 255),
Min(iRound(clr.u1Green * f_scale), 255),
Min(iRound(clr.u1Blue * f_scale), 255)
);
}
//*************************************************************************************
void CCreateParticles::Create(CVector3<> v3_origin, CDir3<> d3_dir, TSec s_period)
{
// Set the particle colour based on terrain colour if desired.
if (bUseTerrainColour)
{
// Get the terrain pointer.
CTerrain* ptrr = CWDbQueryTerrain().tGet();
if (ptrr)
{
// Get the colour from the terrain base texture at this point.
CColour clrTerrain = ptrr->clrGetBaseTextureColour(v3_origin.tX, v3_origin.tY);
clrColour = clrTerrain + CColour(80,80,80);
}
}
// For angle, ignore min spread, just use max.
priv_self.SetTexture(Particles.ptexcolTextures);
sBeginAge = Particles.sPreviousStep;
// Construct a rotation.
CMatrix3<> mx3 = CRotate3<>(d3ZAxis, d3_dir);
int i_num_particles = priv_selfc.iCreateNumParticles(s_period);
for (int i_new_particle = 0; i_new_particle < i_num_particles; ++i_new_particle)
{
// Enforce a reasonable maximum.
if (Particles.pprtParticleList->size() >= iMAX_PARTICLES)
break;
// Create a random rotation.
float f_phi = rndParticles(0.0, Min(ppMax.fSpreadRadians, dPI));
float f_theta = rndParticles(0.0, d2_PI);
float f_sin_phi = sin(f_phi);
CDir3<> d3_dir(cos(f_theta) * f_sin_phi, sin(f_theta) * f_sin_phi, cos(f_phi), true);
// Construct a particle.
d3_dir *= mx3;
switch (eptParticleType)
{
case eptSolid:
Particles.pprtParticleList->push_back(CParticleSolid(v3_origin, d3_dir, *this));
break;
case eptDust:
Particles.pprtParticleList->push_back(CParticleDust(v3_origin, d3_dir, *this));
break;
case eptFluid:
Particles.pprtParticleList->push_back(CParticleFluid(v3_origin, d3_dir, *this));
break;
default:
Assert(0);
}
}
}
//*********************************************************************************************
void CCreateParticles::Create(CVector3<> v3_origin, CDir3<> d3_dir, TSec s_period, float f_strength)
{
float f_interp = fIntensity(f_strength);
if (f_interp < 0.0f)
return;
//SetMin(f_interp, 1.0f);
// Create a new maximum param set by interpolating between the min and max values,
// using fTopMin as a base. Retain the minimum param set.
CCreateParticles crt = *this;
f_interp = f_interp * (1.0f - fTopMin) + fTopMin;
crt.ppMax = ppMin.ppInterpolate(ppMax, f_interp);
crt.Create(v3_origin, d3_dir, s_period);
}
//*************************************************************************************
float CCreateParticles::fIntensity(float f_strength) const
{
return (f_strength - ppMin.fThreshold) /
(ppMax.fThreshold - ppMin.fThreshold);
}
//*************************************************************************************
CCreateParticles CCreateParticles::crtInterpolate(const CCreateParticles& crt, float f_t) const
{
CCreateParticles crt_ret = *this;
crt_ret.ppMin = ppMin.ppInterpolate(crt.ppMin, f_t);
crt_ret.ppMax = ppMax.ppInterpolate(crt.ppMax, f_t);
return crt_ret;
}
//*************************************************************************************
CCreateParticles CCreateParticles::operator *(float f_t) const
{
CCreateParticles crt_ret = *this;
crt_ret.ppMin = ppMin * f_t;
crt_ret.ppMax = ppMax * f_t;
return crt_ret;
}
//*********************************************************************************************
//
// Class CParticles implementation.
//
//*****************************************************************************************
//
// CParticles constructor and destructor.
//
//*****************************************************************************************
CParticles::CParticles()
: sPreviousStep(0.0f)
{
pprtParticleList = new CParticleList();
ptexcolTextures = new CTextureColours();
}
//*****************************************************************************************
CParticles::~CParticles()
{
Reset();
ptexcolTextures->Reset();
delete pprtParticleList;
delete ptexcolTextures;
}
//*****************************************************************************************
//
// CParticles member function.
//
//*****************************************************************************************
void CParticles::Reset()
{
ptexcolTextures->Reset();
pprtParticleList->erase(pprtParticleList->begin(), pprtParticleList->end());
}
//*****************************************************************************************
void CParticles::Update(TSec sec)
{
CCycleTimer ctmr;
CParticleList::iterator it;
// Filter large delta times.
SetMinMax(sec, 0.025f, 0.25f);
// Update particles.
for (it = pprtParticleList->begin(); it != pprtParticleList->end(); ++it)
{
(*it).Update(sec);
}
// Remove old particles.
for (it = pprtParticleList->begin(); it != pprtParticleList->end();)
{
CParticleList::iterator it_remove = it;
++it;
if ((*it_remove).bRetired())
pprtParticleList->erase(it_remove);
}
// Store the step time.
sPreviousStep = sec;
psParticlesUpdate.Add(ctmr(), pprtParticleList->size());
}
//*****************************************************************************************
void CParticles::Add(const CCamera& cam, CPipelineHeap& rplhHeap)
{
CCycleTimer ctmr;
Assert(prasMainScreen.ptGet());
// Set the width and the height of the particle screen.
CParticleBase::fScreenWidth = float(prasMainScreen->iWidth - 1);
CParticleBase::fScreenHeight = float(prasMainScreen->iHeight - 1);
// Get the world to normalized camera space transform.
CTransform3<> tf3_to_norm_cam(cam.tf3ToNormalisedCamera());
// Iterate through the particle list and render them.
CParticleList::iterator it = pprtParticleList->begin();
for (; it != pprtParticleList->end(); ++it)
{
if ((*it).bAdd(cam, tf3_to_norm_cam))
{
CRenderPolygon& rpoly = *rplhHeap.darpolyPolygons.paAlloc(1);
rpoly = (*it).rpolyPolygon;
rpoly.paprvPolyVertices = (*it).rpolyPolygon.paprvPolyVertices;
}
}
psParticlesRender.Add(ctmr(), pprtParticleList->size());
}
//
// Global variables.
//
CParticles Particles;
float CParticleBase::fScreenWidth = 0.0f;
float CParticleBase::fScreenHeight = 0.0f;