123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platform/platform.h"
- #include "ts/tsLastDetail.h"
- #include "renderInstance/renderPassManager.h"
- #include "ts/tsShapeInstance.h"
- #include "scene/sceneManager.h"
- #include "scene/sceneRenderState.h"
- #include "lighting/lightInfo.h"
- #include "renderInstance/renderImposterMgr.h"
- #include "gfx/gfxTransformSaver.h"
- #include "gfx/bitmap/ddsFile.h"
- #include "gfx/bitmap/imageUtils.h"
- #include "gfx/gfxTextureManager.h"
- #include "math/mRandom.h"
- #include "core/stream/fileStream.h"
- #include "util/imposterCapture.h"
- #include "materials/materialManager.h"
- #include "materials/materialFeatureTypes.h"
- #include "console/consoleTypes.h"
- #include "console/engineAPI.h"
- GFXImplementVertexFormat( ImposterState )
- {
- addElement( "POSITION", GFXDeclType_Float4 );
- addElement( "ImposterParams", GFXDeclType_Float2, 0 );
- addElement( "ImposterUpVec", GFXDeclType_Float3, 1 );
- addElement( "ImposterRightVec", GFXDeclType_Float3, 2 );
- };
- Vector<TSLastDetail*> TSLastDetail::smLastDetails;
- bool TSLastDetail::smCanShadow = true;
- AFTER_MODULE_INIT( Sim )
- {
- Con::addVariable( "$pref::imposter::canShadow", TypeBool, &TSLastDetail::smCanShadow,
- "User preference which toggles shadows from imposters. Defaults to true.\n"
- "@ingroup Rendering\n" );
- }
- TSLastDetail::TSLastDetail( TSShape *shape,
- const String &cachePath,
- U32 numEquatorSteps,
- U32 numPolarSteps,
- F32 polarAngle,
- bool includePoles,
- S32 dl, S32 dim )
- {
- mNumEquatorSteps = getMax( numEquatorSteps, (U32)1 );
- mNumPolarSteps = numPolarSteps;
- mPolarAngle = polarAngle;
- mIncludePoles = includePoles;
- mShape = shape;
- mDl = dl;
- mDim = getMax( dim, (S32)32 );
- mRadius = mShape->mRadius;
- mCenter = mShape->center;
- mCachePath = cachePath;
- mDiffusePath = mCachePath + "_imposter.dds";
- mNormalPath = mCachePath + "_imposter_normals.dds";
- mMaterial = NULL;
- mMatInstance = NULL;
- // Store this in the static list.
- smLastDetails.push_back( this );
- }
- TSLastDetail::TSLastDetail(TSShape* shape,
- const String& cachePath,
- const String& diffusePath,
- const String& normalPath,
- U32 numEquatorSteps,
- U32 numPolarSteps,
- F32 polarAngle,
- bool includePoles,
- S32 dl, S32 dim)
- {
- mNumEquatorSteps = getMax(numEquatorSteps, (U32)1);
- mNumPolarSteps = numPolarSteps;
- mPolarAngle = polarAngle;
- mIncludePoles = includePoles;
- mShape = shape;
- mDl = dl;
- mDim = getMax(dim, (S32)32);
- mRadius = mShape->mRadius;
- mCenter = mShape->center;
- mCachePath = cachePath;
- mDiffusePath = diffusePath;
- mNormalPath = normalPath;
- mMaterial = NULL;
- mMatInstance = NULL;
- // Store this in the static list.
- smLastDetails.push_back(this);
- }
- TSLastDetail::~TSLastDetail()
- {
- SAFE_DELETE( mMatInstance );
- if ( mMaterial )
- mMaterial->deleteObject();
- // Remove ourselves from the list.
- Vector<TSLastDetail*>::iterator iter = T3D::find( smLastDetails.begin(), smLastDetails.end(), this );
- smLastDetails.erase( iter );
- }
- void TSLastDetail::render( const TSRenderState &rdata, F32 alpha )
- {
- // Early out if we have nothing to render.
- if ( alpha < 0.01f ||
- !mMatInstance ||
- mMaterial->mImposterUVs.size() == 0 )
- return;
- const MatrixF &mat = GFX->getWorldMatrix();
- // Post a render instance for this imposter... the special
- // imposter render manager will do the magic!
- RenderPassManager *renderPass = rdata.getSceneState()->getRenderPass();
- ImposterRenderInst *ri = renderPass->allocInst<ImposterRenderInst>();
- ri->mat = rdata.getSceneState()->getOverrideMaterial( mMatInstance );
- ri->state.alpha = alpha;
- // Store the up and right vectors of the rotation
- // and we'll generate the up vector in the shader.
- //
- // This is faster than building a quat on the
- // CPU and then rebuilding the matrix on the GPU.
- //
- // NOTE: These vector include scale.
- //
- mat.getColumn( 2, &ri->state.upVec );
- mat.getColumn( 0, &ri->state.rightVec );
- // We send the unscaled size and the vertex shader
- // will use the orientation vectors above to scale it.
- ri->state.halfSize = mRadius;
- // We use the center of the object bounds for
- // the center of the billboard quad.
- mat.mulP( mCenter, &ri->state.center );
- // We sort by the imposter type first so that RIT_Imposter and s
- // RIT_ImposterBatches do not get mixed together.
- //
- // We then sort by material.
- //
- ri->defaultKey = 1;
- ri->defaultKey2 = ri->mat->getStateHint();
- renderPass->addInst( ri );
- }
- void TSLastDetail::update( bool forceUpdate )
- {
- // This should never be called on a dedicated server or
- // anywhere else where we don't have a GFX device!
- AssertFatal( GFXDevice::devicePresent(), "TSLastDetail::update() - Cannot update without a GFX device!" );
- // Clear the materialfirst.
- SAFE_DELETE( mMatInstance );
- if ( mMaterial )
- {
- mMaterial->deleteObject();
- mMaterial = NULL;
- }
- // Make sure imposter textures have been flushed (and not just queued for deletion)
- TEXMGR->cleanupCache();
- // Get the real path to the source shape for doing modified time
- // comparisons... this might be different if the DAEs have been
- // deleted from the install.
- String shapeFile( mCachePath );
- if ( !Torque::FS::IsFile( shapeFile ) )
- {
- Torque::Path path(shapeFile);
- path.setExtension("cached.dts");
- shapeFile = path.getFullPath();
- if ( !Torque::FS::IsFile( shapeFile ) )
- {
- Con::errorf( "TSLastDetail::update - '%s' could not be found!", mCachePath.c_str() );
- return;
- }
- }
- // Do we need to update the imposter?
- const String diffuseMapPath = _getDiffuseMapPath();
- bool isFile = Platform::isFile(diffuseMapPath.c_str());
- if ( forceUpdate || !Platform::isFile(diffuseMapPath.c_str()) ||
- Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) <= 0 )
- _update();
- // If the time check fails now then the update must have not worked.
- if ( Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) < 0 )
- {
- Con::errorf( "TSLastDetail::update - Failed to create imposters for '%s'!", mCachePath.c_str() );
- return;
- }
- // Figure out what our vertex format will be.
- //
- // If we're on SM 3.0 we can do multiple vertex streams
- // and the performance win is big as we send 3x less data
- // on each imposter instance.
- //
- // The problem is SM 2.0 won't do this, so we need to
- // support fallback to regular single stream imposters.
- //
- //mImposterVertDecl.copy( *getGFXVertexFormat<ImposterCorner>() );
- //mImposterVertDecl.append( *getGFXVertexFormat<ImposterState>(), 1 );
- //mImposterVertDecl.getDecl();
- mImposterVertDecl.clear();
- mImposterVertDecl.copy( *getGFXVertexFormat<ImposterState>() );
- // Setup the material for this imposter.
- mMaterial = MATMGR->allocateAndRegister( String::EmptyString );
- mMaterial->mAutoGenerated = true;
- mMaterial->setDiffuseMapFile(diffuseMapPath, 0);
- mMaterial->setNormalMapFile(_getNormalMapPath(), 0);
- mMaterial->mImposterLimits.set( (mNumPolarSteps * 2) + 1, mNumEquatorSteps, mPolarAngle, mIncludePoles );
- mMaterial->mTranslucent = true;
- mMaterial->mTranslucentBlendOp = Material::None;
- mMaterial->mTranslucentZWrite = true;
- mMaterial->mDoubleSided = true;
- mMaterial->mAlphaTest = true;
- mMaterial->mAlphaRef = 84;
- // Create the material instance.
- FeatureSet features = MATMGR->getDefaultFeatures();
- features.addFeature( MFT_ImposterVert );
- mMatInstance = mMaterial->createMatInstance();
- if ( !mMatInstance->init( features, &mImposterVertDecl ) )
- {
- delete mMatInstance;
- mMatInstance = NULL;
- }
- // Get the diffuse texture and from its size and
- // the imposter dimensions we can generate the UVs.
- GFXTexHandle diffuseTex( diffuseMapPath, &GFXStaticTextureSRGBProfile, String::EmptyString );
- Point2I texSize( diffuseTex->getWidth(), diffuseTex->getHeight() );
- _validateDim();
- S32 downscaledDim = mDim >> GFXTextureManager::getTextureDownscalePower(&GFXStaticTextureSRGBProfile);
- // Ok... pack in bitmaps till we run out.
- Vector<RectF> imposterUVs;
- for ( S32 y=0; y+downscaledDim <= texSize.y; )
- {
- for ( S32 x=0; x+downscaledDim <= texSize.x; )
- {
- // Store the uv for later lookup.
- RectF info;
- info.point.set( (F32)x / (F32)texSize.x, (F32)y / (F32)texSize.y );
- info.extent.set( (F32)downscaledDim / (F32)texSize.x, (F32)downscaledDim / (F32)texSize.y );
- imposterUVs.push_back( info );
-
- x += downscaledDim;
- }
- y += downscaledDim;
- }
- AssertFatal( imposterUVs.size() != 0, "hey" );
- mMaterial->mImposterUVs = imposterUVs;
- }
- void TSLastDetail::_validateDim()
- {
- // Loop till they fit.
- S32 newDim = mDim;
- while ( true )
- {
- S32 maxImposters = ( smMaxTexSize / newDim ) * ( smMaxTexSize / newDim );
- S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 );
- if ( imposterCount <= maxImposters )
- break;
- // There are too many imposters to fit a single
- // texture, so we fail. These imposters are for
- // rendering small distant objects. If you need
- // a really high resolution imposter or many images
- // around the equator and poles, maybe you need a
- // custom solution.
- newDim /= 2;
- }
- if ( newDim != mDim )
- {
- Con::printf( "TSLastDetail::_validateDim - '%s' detail dimensions too big! Reduced from %d to %d.",
- mCachePath.c_str(),
- mDim, newDim );
- mDim = newDim;
- }
- }
- void TSLastDetail::_update()
- {
- // We're gonna render... make sure we can.
- bool sceneBegun = GFX->canCurrentlyRender();
- if ( !sceneBegun )
- GFX->beginScene();
- _validateDim();
- Vector<GBitmap*> bitmaps;
- Vector<GBitmap*> normalmaps;
- // We need to create our own instance to render with.
- TSShapeInstance *shape = new TSShapeInstance( mShape, true );
- // Animate the shape once.
- shape->animate( mDl );
- // So we don't have to change it everywhere.
- const GFXFormat format = GFXFormatR8G8B8A8;
- S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 );
- // Figure out the optimal texture size.
- Point2I texSize( smMaxTexSize, smMaxTexSize );
- while ( true )
- {
- Point2I halfSize( texSize.x / 2, texSize.y / 2 );
- U32 count = ( halfSize.x / mDim ) * ( halfSize.y / mDim );
- if ( count < imposterCount )
- {
- // Try half of the height.
- count = ( texSize.x / mDim ) * ( halfSize.y / mDim );
- if ( count >= imposterCount )
- texSize.y = halfSize.y;
- break;
- }
- texSize = halfSize;
- }
- GBitmap *imposter = NULL;
- GBitmap *normalmap = NULL;
- GBitmap destBmp( texSize.x, texSize.y, true, format );
- GBitmap destNormal( texSize.x, texSize.y, true, format );
- U32 mipLevels = destBmp.getNumMipLevels();
- ImposterCapture *imposterCap = new ImposterCapture();
- F32 equatorStepSize = M_2PI_F / (F32)mNumEquatorSteps;
- static const MatrixF topXfm( EulerF( -M_PI_F / 2.0f, 0, 0 ) );
- static const MatrixF bottomXfm( EulerF( M_PI_F / 2.0f, 0, 0 ) );
- MatrixF angMat;
- F32 polarStepSize = 0.0f;
- if ( mNumPolarSteps > 0 )
- polarStepSize = -( 0.5f * M_PI_F - mDegToRad( mPolarAngle ) ) / (F32)mNumPolarSteps;
- PROFILE_START(TSLastDetail_snapshots);
- S32 currDim = mDim;
- for ( S32 mip = 0; mip < mipLevels; mip++ )
- {
- if ( currDim < 1 )
- currDim = 1;
-
- dMemset( destBmp.getWritableBits(mip), 0, destBmp.getWidth(mip) * destBmp.getHeight(mip) * GFXFormat_getByteSize( format ) );
- dMemset( destNormal.getWritableBits(mip), 0, destNormal.getWidth(mip) * destNormal.getHeight(mip) * GFXFormat_getByteSize( format ) );
- bitmaps.clear();
- normalmaps.clear();
- F32 rotX = 0.0f;
- if ( mNumPolarSteps > 0 )
- rotX = -( mDegToRad( mPolarAngle ) - 0.5f * M_PI_F );
- // We capture the images in a particular order which must
- // match the order expected by the imposter renderer.
- imposterCap->begin( shape, mDl, currDim, mRadius, mCenter );
- for ( U32 j=0; j < (2 * mNumPolarSteps + 1); j++ )
- {
- F32 rotZ = -M_PI_F / 2.0f;
- for ( U32 k=0; k < mNumEquatorSteps; k++ )
- {
- angMat.mul( MatrixF( EulerF( rotX, 0, 0 ) ),
- MatrixF( EulerF( 0, 0, rotZ ) ) );
- imposterCap->capture( angMat, &imposter, &normalmap );
- bitmaps.push_back( imposter );
- normalmaps.push_back( normalmap );
- rotZ += equatorStepSize;
- }
- rotX += polarStepSize;
- if ( mIncludePoles )
- {
- imposterCap->capture( topXfm, &imposter, &normalmap );
- bitmaps.push_back(imposter);
- normalmaps.push_back( normalmap );
- imposterCap->capture( bottomXfm, &imposter, &normalmap );
- bitmaps.push_back( imposter );
- normalmaps.push_back( normalmap );
- }
- }
- imposterCap->end();
- Point2I atlasSize( destBmp.getWidth(mip), destBmp.getHeight(mip) );
- // Ok... pack in bitmaps till we run out.
- for ( S32 y=0; y+currDim <= atlasSize.y; )
- {
- for ( S32 x=0; x+currDim <= atlasSize.x; )
- {
- // Copy the next bitmap to the dest texture.
- GBitmap* cell = bitmaps.first();
- bitmaps.pop_front();
- destBmp.copyRect(cell, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip );
- delete cell;
- // Copy the next normal to the dest texture.
- GBitmap* cellNormalmap = normalmaps.first();
- normalmaps.pop_front();
- destNormal.copyRect(cellNormalmap, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip );
- delete cellNormalmap;
- // Did we finish?
- if ( bitmaps.empty() )
- break;
- x += currDim;
- }
- // Did we finish?
- if ( bitmaps.empty() )
- break;
- y += currDim;
- }
- // Next mip...
- currDim /= 2;
- }
- PROFILE_END(); // TSLastDetail_snapshots
- delete imposterCap;
- delete shape;
-
-
- // Should we dump the images?
- if ( Con::getBoolVariable( "$TSLastDetail::dumpImposters", false ) )
- {
- String imposterPath = _getDiffuseMapPath();
- String normalsPath = _getNormalMapPath();
- FileStream stream;
- if ( stream.open( imposterPath, Torque::FS::File::Write ) )
- destBmp.writeBitmap( "png", stream );
- stream.close();
- if ( stream.open( normalsPath, Torque::FS::File::Write ) )
- destNormal.writeBitmap( "png", stream );
- stream.close();
- }
- // DEBUG: Some code to force usage of a test image.
- //GBitmap* tempMap = GBitmap::load( "./forest/data/test1234.png" );
- //tempMap->extrudeMipLevels();
- //mTexture.set( tempMap, &GFXStaticTextureSRGBProfile, false );
- //delete tempMap;
- DDSFile *ddsDest = DDSFile::createDDSFileFromGBitmap( &destBmp );
- ImageUtil::ddsCompress( ddsDest, GFXFormatBC2 );
- DDSFile *ddsNormals = DDSFile::createDDSFileFromGBitmap( &destNormal );
- ImageUtil::ddsCompress( ddsNormals, GFXFormatBC3 );
- // Finally save the imposters to disk.
- FileStream fs;
- if ( fs.open( _getDiffuseMapPath(), Torque::FS::File::Write ) )
- {
- ddsDest->write( fs );
- fs.close();
- }
- if ( fs.open( _getNormalMapPath(), Torque::FS::File::Write ) )
- {
- ddsNormals->write( fs );
- fs.close();
- }
- delete ddsDest;
- delete ddsNormals;
- // If we did a begin then end it now.
- if ( !sceneBegun )
- GFX->endScene();
- }
- void TSLastDetail::deleteImposterCacheTextures()
- {
- const String diffuseMap = _getDiffuseMapPath();
- if ( diffuseMap.length() )
- dFileDelete( diffuseMap );
- const String normalMap = _getNormalMapPath();
- if ( normalMap.length() )
- dFileDelete( normalMap );
- }
- void TSLastDetail::updateImposterImages( bool forceUpdate )
- {
- // Can't do it without GFX!
- if ( !GFXDevice::devicePresent() )
- return;
- //D3DPERF_SetMarker( D3DCOLOR_RGBA( 0, 255, 0, 255 ), L"TSLastDetail::makeImposter" );
- bool sceneBegun = GFX->canCurrentlyRender();
- if ( !sceneBegun )
- GFX->beginScene();
- Vector<TSLastDetail*>::iterator iter = smLastDetails.begin();
- for ( ; iter != smLastDetails.end(); iter++ )
- (*iter)->update( forceUpdate );
- if ( !sceneBegun )
- GFX->endScene();
- }
- DefineEngineFunction( tsUpdateImposterImages, void, (bool forceUpdate), (false), "tsUpdateImposterImages( bool forceupdate )")
- {
- TSLastDetail::updateImposterImages(forceUpdate);
- }
|