//===== Copyright � 1996-2006, Valve Corporation, All rights reserved. ======// // // Purpose: sheet code for particles and other sprite functions // //===========================================================================// #include "bitmap/psheet.h" #include "tier1/UtlStringMap.h" #include "tier1/utlbuffer.h" #include "tier2/fileutils.h" // MOC_TODO: These probably shouldn't be here - maybe we should put CSheetExtended somewhere else, like materialsystem? #include "materialsystem/imesh.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" #include "materialsystem/itexture.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" CSheet::CSheet( void ) { } CSheet::CSheet( CUtlBuffer &buf ) { // lets read a sheet buf.ActivateByteSwappingIfBigEndian(); int nVersion = buf.GetInt(); // version# int nNumCoordsPerFrame = (nVersion)?MAX_IMAGES_PER_FRAME_ON_DISK:1; int nNumSequences = buf.GetInt(); int nNumToAllocate = nNumSequences; // The old hardcoded arrays were 64. // We will allocate 64 for now to handle content issues with // rendering system trying to get at empty sequences. if ( nNumToAllocate < 64 ) nNumToAllocate = 64; m_SheetInfo.EnsureCapacity( nNumSequences ); for ( int i = 0; i < nNumToAllocate; ++i ) { m_SheetInfo.AddToTail(); m_SheetInfo[i].m_pSamples = NULL; m_SheetInfo[i].m_SeqFlags = 0; m_SheetInfo[i].m_bSequenceIsCopyOfAnotherSequence = 0; m_SheetInfo[i].m_nNumFrames = 0; m_SheetInfo[i].m_flFrameSpan = 0.0f; } while ( nNumSequences-- ) { int nSequenceIndex = buf.GetInt(); if ( nSequenceIndex < 0 ) { Warning( "Invalid sequence number (%d)!!!\n", nSequenceIndex ); return; } else if ( nSequenceIndex >= m_SheetInfo.Count() ) { // This can happen if users delete intermediate sequences. // For example you can wind up with n sequences if you delete a sequence between 0 and n // In this case we will pad out the vector. int i = -1; while ( i < nSequenceIndex ) { i = m_SheetInfo.AddToTail(); m_SheetInfo[i].m_pSamples = NULL; m_SheetInfo[i].m_SeqFlags = 0; m_SheetInfo[i].m_bSequenceIsCopyOfAnotherSequence = 0; m_SheetInfo[i].m_nNumFrames = 0; m_SheetInfo[i].m_flFrameSpan = 0.0f; } } m_SheetInfo[nSequenceIndex].m_SeqFlags = (uint8)(0xFF & buf.GetInt()); // reading an int, but not worth storing it all int nFrameCount = buf.GetInt(); // Save off how many frames we have for this sequence m_SheetInfo[nSequenceIndex].m_nNumFrames = nFrameCount; Assert( nFrameCount >= 0 ); bool bSingleFrameSequence = ( nFrameCount == 1 ); int nTimeSamples = bSingleFrameSequence ? 1 : SEQUENCE_SAMPLE_COUNT; if ( m_SheetInfo[nSequenceIndex].m_pSamples ) { Warning( "Invalid particle sheet sequence index. There are more than one items with a sequence index of %d. We are only using the last one we found..\n", nSequenceIndex ); delete[] m_SheetInfo[nSequenceIndex].m_pSamples; } m_SheetInfo[nSequenceIndex].m_pSamples = new SheetSequenceSample_t[ nTimeSamples ]; int fTotalSequenceTime = ( int )buf.GetFloat(); float InterpKnot[SEQUENCE_SAMPLE_COUNT]; float InterpValue[SEQUENCE_SAMPLE_COUNT]; SheetSequenceSample_t Samples[SEQUENCE_SAMPLE_COUNT]; float fCurTime = 0.; for( int nFrm = 0 ; nFrm < nFrameCount; nFrm++ ) { float fThisDuration = buf.GetFloat(); InterpValue[ nFrm ] = nFrm; InterpKnot [ nFrm ] = SEQUENCE_SAMPLE_COUNT*( fCurTime/ fTotalSequenceTime ); SheetSequenceSample_t &seq = Samples[ nFrm ]; seq.m_fBlendFactor = 0.0f; for(int nImage = 0 ; nImage< nNumCoordsPerFrame; nImage++ ) { SequenceSampleTextureCoords_t &s=seq.m_TextureCoordData[nImage]; s.m_fLeft_U0 = buf.GetFloat(); s.m_fTop_V0 = buf.GetFloat(); s.m_fRight_U0 = buf.GetFloat(); s.m_fBottom_V0 = buf.GetFloat(); } if ( nNumCoordsPerFrame == 1 ) seq.CopyFirstFrameToOthers(); fCurTime += fThisDuration; m_SheetInfo[nSequenceIndex].m_flFrameSpan = fCurTime; } // now, fill in the whole table for( int nIdx = 0; nIdx < nTimeSamples; nIdx++ ) { float flIdxA, flIdxB, flInterp; GetInterpolationData( InterpKnot, InterpValue, nFrameCount, SEQUENCE_SAMPLE_COUNT, nIdx, ! ( m_SheetInfo[nSequenceIndex].m_SeqFlags & SEQ_FLAG_CLAMP ), &flIdxA, &flIdxB, &flInterp ); SheetSequenceSample_t sA = Samples[(int) flIdxA]; SheetSequenceSample_t sB = Samples[(int) flIdxB]; SheetSequenceSample_t &oseq = m_SheetInfo[nSequenceIndex].m_pSamples[nIdx]; oseq.m_fBlendFactor = flInterp; for(int nImage = 0 ; nImage< MAX_IMAGES_PER_FRAME_IN_MEMORY; nImage++ ) { SequenceSampleTextureCoords_t &src0=sA.m_TextureCoordData[nImage]; SequenceSampleTextureCoords_t &src1=sB.m_TextureCoordData[nImage]; SequenceSampleTextureCoords_t &o=oseq.m_TextureCoordData[nImage]; o.m_fLeft_U0 = src0.m_fLeft_U0; o.m_fTop_V0 = src0.m_fTop_V0; o.m_fRight_U0 = src0.m_fRight_U0; o.m_fBottom_V0 = src0.m_fBottom_V0; o.m_fLeft_U1 = src1.m_fLeft_U0; o.m_fTop_V1 = src1.m_fTop_V0; o.m_fRight_U1 = src1.m_fRight_U0; o.m_fBottom_V1 = src1.m_fBottom_V0; } } } // now, fill in all unseen sequences with copies of the first seen sequence to prevent crashes // while editing int nFirstSequence = -1; for(int i= 0 ; i < m_SheetInfo.Count(); i++) { if ( m_SheetInfo[i].m_pSamples ) { nFirstSequence = i; break; } } if ( nFirstSequence != -1 ) { for(int i=0 ; i < m_SheetInfo.Count(); i++) { if ( m_SheetInfo[i].m_pSamples == NULL ) { m_SheetInfo[i].m_pSamples = m_SheetInfo[nFirstSequence].m_pSamples; m_SheetInfo[i].m_SeqFlags = m_SheetInfo[nFirstSequence].m_SeqFlags; m_SheetInfo[i].m_nNumFrames = m_SheetInfo[nFirstSequence].m_nNumFrames; Assert( m_SheetInfo[i].m_nNumFrames >= 1 ); m_SheetInfo[i].m_bSequenceIsCopyOfAnotherSequence = true; } } } } CSheet::~CSheet( void ) { for( int i = 0; i < m_SheetInfo.Count(); i++ ) { if ( m_SheetInfo[i].m_pSamples && ( !m_SheetInfo[i].m_bSequenceIsCopyOfAnotherSequence ) ) { delete[] m_SheetInfo[i].m_pSamples; } } } const SheetSequenceSample_t *CSheet::GetSampleForSequence( float flAge, float flAgeScale, int nSequence, bool bForceLoop ) { if ( m_SheetInfo[nSequence].m_nNumFrames == 1 ) return (const SheetSequenceSample_t *) &m_SheetInfo[nSequence].m_pSamples[0]; flAge *= flAgeScale; unsigned int nFrame = ( int )flAge; if ( ( m_SheetInfo[nSequence].m_SeqFlags & SEQ_FLAG_CLAMP ) && !bForceLoop ) { nFrame = MIN( nFrame, SEQUENCE_SAMPLE_COUNT-1 ); } else { nFrame &= SEQUENCE_SAMPLE_COUNT-1; } return (const SheetSequenceSample_t *) &m_SheetInfo[nSequence].m_pSamples[nFrame]; } ////////////////////////////////////////////////////////////////////////// CSheetExtended::CSheetExtended( IMaterial* pMaterial ) { m_Material.Init( pMaterial ); m_pSheetData = NULL; LoadFromMaterial(pMaterial); } CSheetExtended::~CSheetExtended() { delete m_pSheetData; } void CSheetExtended::LoadFromMaterial( IMaterial* pMaterial ) { if ( pMaterial == NULL ) return; bool bFound = false; IMaterialVar *pVar = pMaterial->FindVar( "$basetexture", &bFound ); if ( !pVar || !bFound || !pVar->IsDefined() ) return; ITexture *pTex = pVar->GetTextureValue(); if ( !pTex || pTex->IsError() ) return; size_t nBytes; void const *pSheetData = pTex->GetResourceData( VTF_RSRC_SHEET, &nBytes ); if ( pSheetData ) { CUtlBuffer bufLoad( pSheetData, nBytes, CUtlBuffer::READ_ONLY ); LoadFromBuffer( bufLoad ); } } void CSheetExtended::LoadFromBuffer( CUtlBuffer& buf ) { m_pSheetData = new CSheet(buf); } int CSheetExtended::GetSheetSequenceCount() { if ( m_pSheetData == NULL ) { return 0; } int nUniqueSequences = 0; for ( int i = 0; i < m_pSheetData->m_SheetInfo.Count(); ++i ) { if ( !m_pSheetData->m_SheetInfo[i].m_bSequenceIsCopyOfAnotherSequence ) { nUniqueSequences++; } } return nUniqueSequences; } int CSheetExtended::GetNthSequenceIndex( int nSequenceNumber ) { if ( m_pSheetData == NULL ) { return 0; } int nCountValidSequences = 0; for ( int i = 0; i < m_pSheetData->m_SheetInfo.Count(); ++i ) { if ( !m_pSheetData->m_SheetInfo[i].m_bSequenceIsCopyOfAnotherSequence ) { if ( nCountValidSequences == nSequenceNumber ) { return i; } else { nCountValidSequences++; } } } return 0; } static SheetSequenceSample_t s_DefaultSheetSequence = { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, }; const SheetSequenceSample_t *CSheetExtended::GetSampleForSequence( float flAge, float flAgeScale, int nSequence, bool bForceLoop ) { if ( m_pSheetData == NULL ) return &s_DefaultSheetSequence; return m_pSheetData->GetSampleForSequence( flAge, flAgeScale, nSequence, bForceLoop ); } float CSheetExtended::GetSequenceTimeSpan( int nSequenceIndex ) { if ( m_pSheetData == NULL ) { return 0.f; } return m_pSheetData->m_SheetInfo[nSequenceIndex].m_flFrameSpan; } bool CSheetExtended::ValidSheetData() { return (m_pSheetData != NULL); } bool CSheetExtended::SequenceHasAlphaData( int nSequenceIndex ) { return !(m_pSheetData->m_SheetInfo[nSequenceIndex].m_SeqFlags & SEQ_FLAG_NO_ALPHA); } bool CSheetExtended::SequenceHasColorData( int nSequenceIndex ) { return !(m_pSheetData->m_SheetInfo[nSequenceIndex].m_SeqFlags & SEQ_FLAG_NO_COLOR); } inline void TexCoords0( CMeshBuilder& meshBuilder, int nChannel, const SequenceSampleTextureCoords_t* pSample ) { meshBuilder.TexCoord4f( nChannel, pSample->m_fLeft_U0, pSample->m_fTop_V0, pSample->m_fRight_U0, pSample->m_fBottom_V0 ); } inline void TexCoords1( CMeshBuilder& meshBuilder, int nChannel, const SequenceSampleTextureCoords_t* pSample ) { meshBuilder.TexCoord4f( nChannel, pSample->m_fLeft_U1, pSample->m_fTop_V1, pSample->m_fRight_U1, pSample->m_fBottom_V1 ); } inline void SpriteCardVert( CMeshBuilder& meshBuilder, const Vector &vCenter, const float flRadius, const SheetSequenceSample_t * pSample, const SequenceSampleTextureCoords_t *pSample0, const SequenceSampleTextureCoords_t *pSecondTexture0, const SheetSequenceSample_t *pSample1Data, const SequenceSampleTextureCoords_t *pSample1, float flChannel3U, float flChannel3V ) { meshBuilder.Position3fv( vCenter.Base() ); TexCoords0( meshBuilder, 0, pSample0 ); TexCoords1( meshBuilder, 1, pSample0 ); meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, 0.0f, flRadius, 0.0f ); meshBuilder.TexCoord2f( 3, flChannel3U, flChannel3V ); TexCoords0( meshBuilder, 4, pSecondTexture0 ); if ( pSample1 ) { TexCoords0( meshBuilder, 5, pSample1 ); TexCoords1( meshBuilder, 6, pSample1 ); meshBuilder.TexCoord4f( 7, pSample1Data->m_fBlendFactor, 0, 0, 0 ); } } void CSheetExtended::DrawSheet( IMesh *pMesh, const Vector &vCenter, float flRadius, int nSheetSequence, float flAge, float flSheetPreviewSpeed, bool bLoopSheetPreview, int nSecondarySequence, bool bOverrideSpriteCard ) { // nSecondarySequence bool bSpriteCardMaterial = false; bSpriteCardMaterial = !bOverrideSpriteCard && m_Material && m_Material->IsSpriteCard(); const SheetSequenceSample_t *pSample = GetSampleForSequence( flAge, flSheetPreviewSpeed, nSheetSequence, bLoopSheetPreview ); const SequenceSampleTextureCoords_t *pSample0 = &(pSample->m_TextureCoordData[0]); const SequenceSampleTextureCoords_t *pSecondTexture0 = &(pSample->m_TextureCoordData[1]); const SheetSequenceSample_t *pSample1Data = NULL; const SequenceSampleTextureCoords_t *pSample1 = NULL; if ( (nSecondarySequence != -1) && IsMaterialDualSequence( m_Material ) ) { const float SECONDARY_AGE_MULTIPLIER = 0.1f; // hardcoded 'best guess' at relative speed, since we don't want a whole UI for a second speed float flSecondaryAge = flAge * SECONDARY_AGE_MULTIPLIER; pSample1Data = GetSampleForSequence( flSecondaryAge, flSheetPreviewSpeed, nSecondarySequence, bLoopSheetPreview ); pSample1 = &(pSample1Data->m_TextureCoordData[0]); } CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); if ( bSpriteCardMaterial ) { SpriteCardVert( meshBuilder, vCenter, flRadius, pSample, pSample0, pSecondTexture0, pSample1Data, pSample1, 0, 0 ); } else { meshBuilder.Position3fv( (vCenter + Vector(-flRadius,-flRadius,0)).Base() ); meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0 ); } meshBuilder.Color4ub( 255, 255, 255, 255 ); meshBuilder.AdvanceVertex(); if ( bSpriteCardMaterial ) { SpriteCardVert( meshBuilder, vCenter, flRadius, pSample, pSample0, pSecondTexture0, pSample1Data, pSample1, 1, 0 ); } else { meshBuilder.Position3fv( (vCenter + Vector(+flRadius,-flRadius,0)).Base() ); meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fTop_V0 ); } meshBuilder.Color4ub( 255, 255, 255, 255 ); meshBuilder.AdvanceVertex(); if ( bSpriteCardMaterial ) { SpriteCardVert( meshBuilder, vCenter, flRadius, pSample, pSample0, pSecondTexture0, pSample1Data, pSample1, 1, 1 ); } else { meshBuilder.Position3fv( (vCenter + Vector(+flRadius,+flRadius,0)).Base() ); meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); } meshBuilder.Color4ub( 255, 255, 255, 255 ); meshBuilder.AdvanceVertex(); if ( bSpriteCardMaterial ) { SpriteCardVert( meshBuilder, vCenter, flRadius, pSample, pSample0, pSecondTexture0, pSample1Data, pSample1, 0, 1 ); } else { meshBuilder.Position3fv( (vCenter + Vector(-flRadius,+flRadius,0)).Base() ); meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fBottom_V0 ); } meshBuilder.Color4ub( 255, 255, 255, 255 ); meshBuilder.AdvanceVertex(); meshBuilder.End(); pMesh->Draw(); } bool CSheetExtended::IsMaterialDualSequence( IMaterial* pMat ) { if ( !pMat ) { return false; } bool bFound = false; IMaterialVar *pVar = pMat->FindVar( "$DUALSEQUENCE", &bFound ); return ( pVar && bFound && pVar->IsDefined() && pVar->GetIntValue() ); } bool CSheetExtended::IsMaterialSeparateAlphaColorMaterial( IMaterial* pMat ) { if ( !pMat || !IsMaterialDualSequence(pMat) ) { return false; } bool bFound = false; IMaterialVar *pVar = pMat->FindVar( "$SEQUENCE_BLEND_MODE", &bFound ); if ( !pVar || !bFound || !pVar->IsDefined() ) return false; return (pVar->GetIntValue() == 1); }