|
|
//===== Copyright � 1996-2007, Valve Corporation, All rights reserved. ======//
//
// Purpose: see CParticleSnapshot declaration (in particles.h)
//
//===========================================================================//
#include "tier0/platform.h"
#include "particles/particles.h"
#include "filesystem.h"
#include "tier2/tier2.h"
#include "tier2/fileutils.h"
#include "tier1/utlbuffer.h"
#include "tier1/UtlStringMap.h"
#include "tier1/strtools.h"
#include "dmxloader/dmxloader.h"
#include "dmxloader/utlsoacontainer_serialization.h"
#include "tier1/lzmaDecoder.h"
#include "tier0/vprof.h"
#include "particles_internal.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// This macro is used to process the attribute mapping parameters, for both vararg versions of Init():
#define ATTRIBUTE_MAPPING_LOOP( _lastarg_ ) \
va_list args; \ va_start( args, _lastarg_ ); \ for(;;) \ { \ int nFieldNumber = va_arg( args, int ); \ if ( nFieldNumber == -1 ) \ break; \ /* Associate nParticleAttribute with nFieldNumber */ \ int nParticleAttribute = va_arg( args, int );
//-----------------------------------------------------------------------------
// Purge, clear the container back to its initial state
//-----------------------------------------------------------------------------
void CParticleSnapshot::Purge( void ) { m_Container.Purge(); m_pContainer = NULL; for ( int i = 0; i < ARRAYSIZE( m_ParticleAttributeToContainerAttribute ); i++ ) m_ParticleAttributeToContainerAttribute[ i ] = -1; for ( int i = 0; i < ARRAYSIZE( m_ContainerAttributeToParticleAttribute ); i++ ) m_ContainerAttributeToParticleAttribute[ i ] = -1; }
//-----------------------------------------------------------------------------
// AddAttributeMapping, add a new field/attribute pair
//-----------------------------------------------------------------------------
bool CParticleSnapshot::AddAttributeMapping( int nFieldNumber, int nParticleAttribute, const char *pFunc ) { if ( ( ( m_ParticleAttributeToContainerAttribute[ nParticleAttribute ] != -1 ) && ( m_ParticleAttributeToContainerAttribute[ nParticleAttribute ] != nFieldNumber ) ) || ( ( m_ContainerAttributeToParticleAttribute[ nFieldNumber ] != -1 ) && ( m_ContainerAttributeToParticleAttribute[ nFieldNumber ] != nParticleAttribute ) ) ) { Warning( "CParticleSnapshot::%s - Invalid attribute mapping specified (must be one-to-one)!\n", pFunc ); Assert( 0 ); Purge(); return false; } m_ParticleAttributeToContainerAttribute[ nParticleAttribute ] = nFieldNumber; m_ContainerAttributeToParticleAttribute[ nFieldNumber ] = nParticleAttribute; return true; }
//-----------------------------------------------------------------------------
// ValidateAttributeMapping, check datatypes for a field/attribute pair
//-----------------------------------------------------------------------------
bool CParticleSnapshot::ValidateAttributeMapping( int nFieldNumber, int nParticleAttribute, const char *pFunc ) { // Check the presence/datatype of each specified container field
// TODO: support unallocated attributes (requires different 'copy' implementations in operators - can't use memcpy!)
EAttributeDataType nExpectedDataType = g_pParticleSystemMgr->GetParticleAttributeDataType( nParticleAttribute ); if ( ( m_pContainer->GetAttributeType( nFieldNumber ) != nExpectedDataType ) || !m_pContainer->HasAllocatedMemory( nFieldNumber ) ) { Warning( "CParticleSnapshot::%s - Invalid attribute mapping specified for the provided container!\n", pFunc ); if ( m_pContainer->GetAttributeType( nFieldNumber ) != nExpectedDataType ) Warning( " (data type of container field %d does not match particle attribute %s)\n", nFieldNumber, g_pParticleSystemMgr->GetParticleAttributeName( nParticleAttribute ) ); if ( !m_pContainer->HasAllocatedMemory( nFieldNumber ) ) Warning( " (container field %d has no allocated data)\n", nFieldNumber ); Assert( 0 ); Purge(); return false; } return true; }
//-----------------------------------------------------------------------------
// Init, creating a new container with the specified attribute mapping
//-----------------------------------------------------------------------------
bool CParticleSnapshot::Init( int nX, int nY, int nZ, const AttributeMapVector &attributeMaps ) { Assert( attributeMaps.Count() > 0 ); if ( attributeMaps.Count() <= 0 ) return false; Assert( ( nX >= 1 ) && ( nY >= 1 ) && ( nZ >= 1 ) ); if ( ( nX < 1 ) || ( nY < 1 ) || ( nZ < 1 ) ) return false;
Purge(); m_pContainer = &m_Container;
for ( int i = 0; i < attributeMaps.Count(); i++ ) { int nFieldNumber = attributeMaps[ i ].m_nContainerAttribute; int nParticleAttribute = attributeMaps[ i ].m_nParticleAttribute;
if ( !AddAttributeMapping( nFieldNumber, nParticleAttribute, "Init" ) ) return false;
// Set the datatype of each specified container field
EAttributeDataType nDataType = g_pParticleSystemMgr->GetParticleAttributeDataType( nParticleAttribute ); m_Container.SetAttributeType( nFieldNumber, nDataType ); } m_Container.AllocateData( nX, nY, nZ );
return true; }
//-----------------------------------------------------------------------------
// Init, creating a new container with the specified attribute mapping
//-----------------------------------------------------------------------------
bool CParticleSnapshot::Init( int nX, int nY, int nZ, ... ) { AttributeMapVector attributeMaps; ATTRIBUTE_MAPPING_LOOP( nZ ) //{
attributeMaps.AddToTail( AttributeMap( nFieldNumber, nParticleAttribute ) ); } return Init( nX, nY, nZ, attributeMaps ); }
//-----------------------------------------------------------------------------
// Init using an existing container, with the specified attribute mapping
//-----------------------------------------------------------------------------
bool CParticleSnapshot::InitExternal( CSOAContainer *pContainer, const AttributeMapVector &attributeMaps ) { Assert( attributeMaps.Count() > 0 ); if ( attributeMaps.Count() <= 0 ) return false;
Purge(); m_pContainer = pContainer;
for ( int i = 0; i < attributeMaps.Count(); i++ ) { int nFieldNumber = attributeMaps[ i ].m_nContainerAttribute; int nParticleAttribute = attributeMaps[ i ].m_nParticleAttribute;
if ( !AddAttributeMapping( nFieldNumber, nParticleAttribute, "InitExternal" ) || !ValidateAttributeMapping( nFieldNumber, nParticleAttribute, "InitExternal" ) ) return false; }
return true; }
//-----------------------------------------------------------------------------
// Init using an existing container, with the specified attribute mapping
//-----------------------------------------------------------------------------
bool CParticleSnapshot::InitExternal( CSOAContainer *pContainer, ... ) { AttributeMapVector attributeMaps; ATTRIBUTE_MAPPING_LOOP( pContainer ) //{
attributeMaps.AddToTail( AttributeMap( nFieldNumber, nParticleAttribute ) ); } return InitExternal( pContainer, attributeMaps ); }
//-----------------------------------------------------------------------------
// Unpack structure for CParticleSnapshot
//-----------------------------------------------------------------------------
BEGIN_DMXELEMENT_UNPACK( CParticleSnapshot ) DMXELEMENT_UNPACK_FIELD_ARRAY( "particle_attribute_to_container_attribute", "-1", int, m_ParticleAttributeToContainerAttribute ) DMXELEMENT_UNPACK_FIELD_ARRAY( "container_attribute_to_particle_attribute", "-1", int, m_ContainerAttributeToParticleAttribute ) END_DMXELEMENT_UNPACK( CParticleSnapshot, s_pParticleSnapshotUnpack )
//-----------------------------------------------------------------------------
// Check whether the particle system's defined attributes have changed
//-----------------------------------------------------------------------------
void CParticleSnapshot::CheckParticleAttributesForChanges( void ) { // If this doesn't compile, then we need to bump the file version and perform fixup on files saved w/ the old attribute definitions:
// TODO: store out an array of attribute names (g_pParticleSystemMgr->GetParticleAttributeName()) with the data so the fixup can be automatic and general
COMPILE_TIME_ASSERT( MAX_PARTICLE_ATTRIBUTES == 24 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_XYZ == 0 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_LIFE_DURATION == 1 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_PREV_XYZ == 2 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_RADIUS == 3 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_ROTATION == 4 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_ROTATION_SPEED == 5 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_TINT_RGB == 6 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_ALPHA == 7 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_CREATION_TIME == 8 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER == 9 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_TRAIL_LENGTH == 10 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_PARTICLE_ID == 11 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_YAW == 12 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1 == 13 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_HITBOX_INDEX == 14 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_HITBOX_RELATIVE_XYZ == 15 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_ALPHA2 == 16 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_SCRATCH_VEC == 17 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_SCRATCH_FLOAT == 18 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_UNUSED == 19 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_PITCH == 20 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_NORMAL == 21 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_GLOW_RGB == 22 ); COMPILE_TIME_ASSERT( PARTICLE_ATTRIBUTE_GLOW_ALPHA == 23 ); }
//-----------------------------------------------------------------------------
// Init from a DMX (.psf) file
//-----------------------------------------------------------------------------
bool CParticleSnapshot::Unserialize( const char *pFullPath ) { DECLARE_DMX_CONTEXT();
CheckParticleAttributesForChanges();
CDmxElement *pRootElement = NULL; bool bTextMode = true, bBinaryMode = false; if ( !UnserializeDMX( pFullPath, "GAME", bTextMode, &pRootElement ) && !UnserializeDMX( pFullPath, "GAME", bBinaryMode, &pRootElement ) ) // TODO: shouldn't UnserializeDMX automatically detect text mode?
{ Warning( "ERROR: CParticleSnapshot::Unserialize - could not load file %s!\n", pFullPath ); return false; }
bool bSuccess = true;
CDmxElement *pParticleSnapshotElement = pRootElement->GetValue< CDmxElement * >( "particle_snapshot" ); if ( !pParticleSnapshotElement ) { Warning( "ERROR: CParticleSnapshot::Unserialize - %s is not a particle snapshot (.psf) file!\n", pFullPath ); bSuccess = false; }
int nVersion = -1; if ( bSuccess ) { // Read the version number
nVersion = pParticleSnapshotElement->GetValue( "version", -1 ); if ( nVersion == -1 ) { Warning( "ERROR: CParticleSnapshot::Unserialize - missing version field in file %s\n", pFullPath ); bSuccess = false; } }
if ( bSuccess ) { // Read the CParticleSnapshot structure
Purge(); pParticleSnapshotElement->UnpackIntoStructure( this, s_pParticleSnapshotUnpack ); // Unserialize the embedded CSOAContainer:
CDmxElement *pContainerElement = pParticleSnapshotElement->GetValue< CDmxElement * >( "container" ); if ( !UnserializeCSOAContainer( &m_Container, pContainerElement ) ) { Warning( "ERROR: CParticleSnapshot::Unserialize - error reading embedded CSOAContainer in file %s\n", pFullPath ); bSuccess = false; } }
if ( bSuccess ) { // Update files saved in old versions
switch( nVersion ) { case PARTICLE_SNAPSHOT_DMX_VERSION: // Up to date - nothing to do.
break; default: // The DMX unpack structure will set reasonable defaults or flag stuff that needs fixing up
// TODO: add code when versions are bumped and fixup needs to happen
bSuccess = false; break; } }
if ( bSuccess ) { m_pContainer = &m_Container;
// Validate the attribute mapping (re-'add' it, in both directions, to be paranoid)
int nForwardMaps = 0, nReverseMaps = 0; for ( int i = 0; i < ARRAYSIZE( m_ParticleAttributeToContainerAttribute ); i++ ) { int nFieldNumber = m_ParticleAttributeToContainerAttribute[ i ]; if ( nFieldNumber == -1 ) continue; if ( !AddAttributeMapping( nFieldNumber, i, "Unserialize" ) || !ValidateAttributeMapping( nFieldNumber, i, "Unserialize" ) ) { bSuccess = false; break; } nForwardMaps++; } for ( int i = 0; i < ARRAYSIZE( m_ContainerAttributeToParticleAttribute ); i++ ) { int nParticleAttribute = m_ContainerAttributeToParticleAttribute[ i ]; if ( nParticleAttribute == -1 ) continue; if ( !AddAttributeMapping( i, nParticleAttribute, "Unserialize" ) || !ValidateAttributeMapping( i, nParticleAttribute, "Unserialize" ) ) { bSuccess = false; break; } nReverseMaps++; }
if ( bSuccess && ( !nForwardMaps || !nReverseMaps ) ) { bSuccess = false; Warning( "ERROR: CParticleSnapshot::Unserialize - error in data in file %s (no attribute mapping specified)\n", pFullPath ); Assert( 0 ); } }
if ( !bSuccess ) { // Leave a beautiful corpse
Purge(); }
CleanupDMX( pRootElement );
return bSuccess; }
//-----------------------------------------------------------------------------
// Write out to a DMX (.psf) file
//-----------------------------------------------------------------------------
bool CParticleSnapshot::Serialize( const char *pFullPath, bool bTextMode ) { DECLARE_DMX_CONTEXT();
if ( !IsValid() ) { Warning( "ERROR: CParticleSnapshot::Serialize - cannot serialize an uninitialized CParticleSnapshot! (%s)\n", pFullPath ); return false; } const char *pExtension = V_GetFileExtension( pFullPath ); if ( !pExtension || Q_stricmp( pExtension, "psf" ) ) { Warning( "ERROR: CParticleSnapshot::Serialize - file extension should be '.psf' (%s)\n", pFullPath ); return false; }
bool bSuccess = true; CDmxElement *pRootElement = CreateDmxElement( "CDmeElement" ); { CDmxElementModifyScope modifyRoot( pRootElement );
// Write the version number first
CDmxElement *pParticleSnapshotElement = CreateDmxElement( "CDmeParticleSnapshot" ); pRootElement->SetValue( "particle_snapshot", pParticleSnapshotElement ); int nDmxVersion = PARTICLE_SNAPSHOT_DMX_VERSION; pParticleSnapshotElement->SetValue( "version", nDmxVersion );
// Then all our member variables
pParticleSnapshotElement->AddAttributesFromStructure( this, s_pParticleSnapshotUnpack );
// Then the embedded container
CDmxElement *pContainerElement = CreateDmxElement( "CDmeSOAContainer" ); pParticleSnapshotElement->SetValue( "container", pContainerElement ); if ( !SerializeCSOAContainer( m_pContainer, pContainerElement ) ) { Warning( "ERROR: CParticleSnapshot::Serialize - error serializing embedded CSOAContainer for file %s\n", pFullPath ); bSuccess = false; } }
if ( bSuccess ) { // Write out the file
bSuccess = SerializeDMX( pFullPath, "GAME", bTextMode, pRootElement ); }
CleanupDMX( pRootElement );
return bSuccess; }
|