Torque3D Documentation / _generateds / tsLastDetail.cpp

tsLastDetail.cpp

Engine/source/ts/tsLastDetail.cpp

More...

Public Functions

DefineEngineFunction(tsUpdateImposterImages , void , (bool forceUpdate) , (false) , "tsUpdateImposterImages( bool forceupdate )" )
GFXImplementVertexFormat(ImposterState )

Detailed Description

Public Functions

AFTER_MODULE_INIT(Sim )

DefineEngineFunction(tsUpdateImposterImages , void , (bool forceUpdate) , (false) , "tsUpdateImposterImages( bool forceupdate )" )

GFXImplementVertexFormat(ImposterState )

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "ts/tsLastDetail.h"
 26
 27#include "renderInstance/renderPassManager.h"
 28#include "ts/tsShapeInstance.h"
 29#include "scene/sceneManager.h"
 30#include "scene/sceneRenderState.h"
 31#include "lighting/lightInfo.h"
 32#include "renderInstance/renderImposterMgr.h"
 33#include "gfx/gfxTransformSaver.h"
 34#include "gfx/bitmap/ddsFile.h"
 35#include "gfx/bitmap/imageUtils.h"
 36#include "gfx/gfxTextureManager.h"
 37#include "math/mRandom.h"
 38#include "core/stream/fileStream.h"
 39#include "util/imposterCapture.h"
 40#include "materials/materialManager.h"
 41#include "materials/materialFeatureTypes.h"
 42#include "console/consoleTypes.h"
 43#include "console/engineAPI.h"
 44
 45
 46GFXImplementVertexFormat( ImposterState )
 47{
 48   addElement( "POSITION", GFXDeclType_Float4 );
 49
 50   addElement( "ImposterParams", GFXDeclType_Float2, 0 );
 51
 52   addElement( "ImposterUpVec", GFXDeclType_Float3, 1 );
 53   addElement( "ImposterRightVec", GFXDeclType_Float3, 2 );
 54};
 55
 56
 57Vector<TSLastDetail*> TSLastDetail::smLastDetails;
 58
 59bool TSLastDetail::smCanShadow = true;
 60
 61
 62AFTER_MODULE_INIT( Sim )
 63{
 64   Con::addVariable( "$pref::imposter::canShadow", TypeBool, &TSLastDetail::smCanShadow,
 65      "User preference which toggles shadows from imposters.  Defaults to true.\n"
 66      "@ingroup Rendering\n" );
 67}
 68
 69
 70TSLastDetail::TSLastDetail(   TSShape *shape,
 71                              const String &cachePath,
 72                              U32 numEquatorSteps,
 73                              U32 numPolarSteps,
 74                              F32 polarAngle,
 75                              bool includePoles,
 76                              S32 dl, S32 dim )
 77{
 78   mNumEquatorSteps = getMax( numEquatorSteps, (U32)1 );
 79   mNumPolarSteps = numPolarSteps;
 80   mPolarAngle = polarAngle;
 81   mIncludePoles = includePoles;
 82   mShape = shape;
 83   mDl = dl;
 84   mDim = getMax( dim, (S32)32 );
 85
 86   mRadius = mShape->mRadius;
 87   mCenter = mShape->center;
 88
 89   mCachePath = cachePath;
 90
 91   mMaterial = NULL;
 92   mMatInstance = NULL;
 93
 94   // Store this in the static list.
 95   smLastDetails.push_back( this );  
 96}
 97
 98TSLastDetail::~TSLastDetail()
 99{
100   SAFE_DELETE( mMatInstance );
101   if ( mMaterial )
102      mMaterial->deleteObject();
103
104   // Remove ourselves from the list.
105   Vector<TSLastDetail*>::iterator iter = T3D::find( smLastDetails.begin(), smLastDetails.end(), this );
106   smLastDetails.erase( iter );
107}
108
109void TSLastDetail::render( const TSRenderState &rdata, F32 alpha )
110{
111   // Early out if we have nothing to render.
112   if (  alpha < 0.01f || 
113         !mMatInstance ||
114         mMaterial->mImposterUVs.size() == 0 )
115      return;
116
117   const MatrixF &mat = GFX->getWorldMatrix();
118
119   // Post a render instance for this imposter... the special
120   // imposter render manager will do the magic!
121   RenderPassManager *renderPass = rdata.getSceneState()->getRenderPass();
122
123   ImposterRenderInst *ri = renderPass->allocInst<ImposterRenderInst>();
124   ri->mat = rdata.getSceneState()->getOverrideMaterial( mMatInstance );
125
126   ri->state.alpha = alpha;
127
128   // Store the up and right vectors of the rotation
129   // and we'll generate the up vector in the shader.
130   //
131   // This is faster than building a quat on the
132   // CPU and then rebuilding the matrix on the GPU.
133   //
134   // NOTE: These vector include scale.
135   //
136   mat.getColumn( 2, &ri->state.upVec );
137   mat.getColumn( 0, &ri->state.rightVec );
138
139   // We send the unscaled size and the vertex shader
140   // will use the orientation vectors above to scale it.
141   ri->state.halfSize = mRadius;
142
143   // We use the center of the object bounds for
144   // the center of the billboard quad.
145   mat.mulP( mCenter, &ri->state.center );
146
147   // We sort by the imposter type first so that RIT_Imposter and s
148   // RIT_ImposterBatches do not get mixed together.
149   //
150   // We then sort by material.
151   //
152   ri->defaultKey = 1;
153   ri->defaultKey2 = ri->mat->getStateHint();
154
155   renderPass->addInst( ri );   
156}
157
158void TSLastDetail::update( bool forceUpdate )
159{
160   // This should never be called on a dedicated server or
161   // anywhere else where we don't have a GFX device!
162   AssertFatal( GFXDevice::devicePresent(), "TSLastDetail::update() - Cannot update without a GFX device!" );
163
164   // Clear the materialfirst.
165   SAFE_DELETE( mMatInstance );
166   if ( mMaterial )
167   {
168      mMaterial->deleteObject();
169      mMaterial = NULL;
170   }
171
172   // Make sure imposter textures have been flushed (and not just queued for deletion)
173   TEXMGR->cleanupCache();
174
175   // Get the real path to the source shape for doing modified time
176   // comparisons... this might be different if the DAEs have been 
177   // deleted from the install.
178   String shapeFile( mCachePath );
179   if ( !Platform::isFile( shapeFile ) )
180   {
181      Torque::Path path(shapeFile);
182      path.setExtension("cached.dts");
183      shapeFile = path.getFullPath();
184      if ( !Platform::isFile( shapeFile ) )  
185      {
186         Con::errorf( "TSLastDetail::update - '%s' could not be found!", mCachePath.c_str() );
187         return;
188      }
189   }
190
191   // Do we need to update the imposter?
192   const String diffuseMapPath = _getDiffuseMapPath();
193   if (  forceUpdate || 
194         Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) <= 0 )
195      _update();
196
197   // If the time check fails now then the update must have not worked.
198   if ( Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) < 0 )
199   {
200      Con::errorf( "TSLastDetail::update - Failed to create imposters for '%s'!", mCachePath.c_str() );
201      return;
202   }
203
204   // Figure out what our vertex format will be.
205   //
206   // If we're on SM 3.0 we can do multiple vertex streams
207   // and the performance win is big as we send 3x less data
208   // on each imposter instance.
209   //
210   // The problem is SM 2.0 won't do this, so we need to
211   // support fallback to regular single stream imposters.
212   //
213   //mImposterVertDecl.copy( *getGFXVertexFormat<ImposterCorner>() );
214   //mImposterVertDecl.append( *getGFXVertexFormat<ImposterState>(), 1 );
215   //mImposterVertDecl.getDecl();   
216   mImposterVertDecl.clear();
217   mImposterVertDecl.copy( *getGFXVertexFormat<ImposterState>() );
218
219   // Setup the material for this imposter.
220   mMaterial = MATMGR->allocateAndRegister( String::EmptyString );
221   mMaterial->mAutoGenerated = true;
222   mMaterial->mDiffuseMapFilename[0] = diffuseMapPath;
223   mMaterial->mNormalMapFilename[0] = _getNormalMapPath();
224   mMaterial->mImposterLimits.set( (mNumPolarSteps * 2) + 1, mNumEquatorSteps, mPolarAngle, mIncludePoles );
225   mMaterial->mTranslucent = true;
226   mMaterial->mTranslucentBlendOp = Material::None;
227   mMaterial->mTranslucentZWrite = true;
228   mMaterial->mDoubleSided = true;
229   mMaterial->mAlphaTest = true;
230   mMaterial->mAlphaRef = 84;
231
232   // Create the material instance.
233   FeatureSet features = MATMGR->getDefaultFeatures();
234   features.addFeature( MFT_ImposterVert );
235   mMatInstance = mMaterial->createMatInstance();
236   if ( !mMatInstance->init( features, &mImposterVertDecl ) )
237   {
238      delete mMatInstance;
239      mMatInstance = NULL;
240   }
241
242   // Get the diffuse texture and from its size and
243   // the imposter dimensions we can generate the UVs.
244   GFXTexHandle diffuseTex( diffuseMapPath, &GFXStaticTextureSRGBProfile, String::EmptyString );
245   Point2I texSize( diffuseTex->getWidth(), diffuseTex->getHeight() );
246
247   _validateDim();
248
249   S32 downscaledDim = mDim >> GFXTextureManager::getTextureDownscalePower(&GFXStaticTextureSRGBProfile);
250
251   // Ok... pack in bitmaps till we run out.
252   Vector<RectF> imposterUVs;
253   for ( S32 y=0; y+downscaledDim <= texSize.y; )
254   {
255      for ( S32 x=0; x+downscaledDim <= texSize.x; )
256      {
257         // Store the uv for later lookup.
258         RectF info;
259         info.point.set( (F32)x / (F32)texSize.x, (F32)y / (F32)texSize.y );
260         info.extent.set( (F32)downscaledDim / (F32)texSize.x, (F32)downscaledDim / (F32)texSize.y );
261         imposterUVs.push_back( info );
262         
263         x += downscaledDim;
264      }
265
266      y += downscaledDim;
267   }
268
269   AssertFatal( imposterUVs.size() != 0, "hey" );
270
271   mMaterial->mImposterUVs = imposterUVs;
272}
273
274void TSLastDetail::_validateDim()
275{
276   // Loop till they fit.
277   S32 newDim = mDim;
278   while ( true )
279   {
280      S32 maxImposters = ( smMaxTexSize / newDim ) * ( smMaxTexSize / newDim );
281      S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 );
282      if ( imposterCount <= maxImposters )
283         break;
284
285      // There are too many imposters to fit a single 
286      // texture, so we fail.  These imposters are for
287      // rendering small distant objects.  If you need
288      // a really high resolution imposter or many images
289      // around the equator and poles, maybe you need a
290      // custom solution.
291
292      newDim /= 2;
293   }
294
295   if ( newDim != mDim )
296   {
297      Con::printf( "TSLastDetail::_validateDim - '%s' detail dimensions too big! Reduced from %d to %d.", 
298         mCachePath.c_str(),
299         mDim, newDim );
300
301      mDim = newDim;
302   }
303}
304
305void TSLastDetail::_update()
306{
307   // We're gonna render... make sure we can.
308   bool sceneBegun = GFX->canCurrentlyRender();
309   if ( !sceneBegun )
310      GFX->beginScene();
311
312   _validateDim();
313
314   Vector<GBitmap*> bitmaps;
315   Vector<GBitmap*> normalmaps;
316
317   // We need to create our own instance to render with.
318   TSShapeInstance *shape = new TSShapeInstance( mShape, true );
319
320   // Animate the shape once.
321   shape->animate( mDl );
322
323   // So we don't have to change it everywhere.
324   const GFXFormat format = GFXFormatR8G8B8A8;  
325
326   S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 );
327
328   // Figure out the optimal texture size.
329   Point2I texSize( smMaxTexSize, smMaxTexSize );
330   while ( true )
331   {
332      Point2I halfSize( texSize.x / 2, texSize.y / 2 );
333      U32 count = ( halfSize.x / mDim ) * ( halfSize.y / mDim );
334      if ( count < imposterCount )
335      {
336         // Try half of the height.
337         count = ( texSize.x / mDim ) * ( halfSize.y / mDim );
338         if ( count >= imposterCount )
339            texSize.y = halfSize.y;
340         break;
341      }
342
343      texSize = halfSize;
344   }
345
346   GBitmap *imposter = NULL;
347   GBitmap *normalmap = NULL;
348   GBitmap destBmp( texSize.x, texSize.y, true, format );
349   GBitmap destNormal( texSize.x, texSize.y, true, format );
350
351   U32 mipLevels = destBmp.getNumMipLevels();
352
353   ImposterCapture *imposterCap = new ImposterCapture();
354
355   F32 equatorStepSize = M_2PI_F / (F32)mNumEquatorSteps;
356
357   static const MatrixF topXfm( EulerF( -M_PI_F / 2.0f, 0, 0 ) );
358   static const MatrixF bottomXfm( EulerF( M_PI_F / 2.0f, 0, 0 ) );
359
360   MatrixF angMat;
361
362   F32 polarStepSize = 0.0f;
363   if ( mNumPolarSteps > 0 )
364      polarStepSize = -( 0.5f * M_PI_F - mDegToRad( mPolarAngle ) ) / (F32)mNumPolarSteps;
365
366   PROFILE_START(TSLastDetail_snapshots);
367
368   S32 currDim = mDim;
369   for ( S32 mip = 0; mip < mipLevels; mip++ )
370   {
371      if ( currDim < 1 )
372         currDim = 1;
373      
374      dMemset( destBmp.getWritableBits(mip), 0, destBmp.getWidth(mip) * destBmp.getHeight(mip) * GFXFormat_getByteSize( format ) );
375      dMemset( destNormal.getWritableBits(mip), 0, destNormal.getWidth(mip) * destNormal.getHeight(mip) * GFXFormat_getByteSize( format ) );
376
377      bitmaps.clear();
378      normalmaps.clear();
379
380      F32 rotX = 0.0f;
381      if ( mNumPolarSteps > 0 )
382         rotX = -( mDegToRad( mPolarAngle ) - 0.5f * M_PI_F );
383
384      // We capture the images in a particular order which must
385      // match the order expected by the imposter renderer.
386
387      imposterCap->begin( shape, mDl, currDim, mRadius, mCenter );
388
389      for ( U32 j=0; j < (2 * mNumPolarSteps + 1); j++ )
390      {
391         F32 rotZ = -M_PI_F / 2.0f;
392
393         for ( U32 k=0; k < mNumEquatorSteps; k++ )
394         {            
395            angMat.mul( MatrixF( EulerF( rotX, 0, 0 ) ),
396                        MatrixF( EulerF( 0, 0, rotZ ) ) );
397
398            imposterCap->capture( angMat, &imposter, &normalmap );
399
400            bitmaps.push_back( imposter );
401            normalmaps.push_back( normalmap );
402
403            rotZ += equatorStepSize;
404         }
405
406         rotX += polarStepSize;
407
408         if ( mIncludePoles )
409         {
410            imposterCap->capture( topXfm, &imposter, &normalmap );
411
412            bitmaps.push_back(imposter);
413            normalmaps.push_back( normalmap );
414
415            imposterCap->capture( bottomXfm, &imposter, &normalmap );
416
417            bitmaps.push_back( imposter );
418            normalmaps.push_back( normalmap );
419         }         
420      }
421
422      imposterCap->end();
423
424      Point2I atlasSize( destBmp.getWidth(mip), destBmp.getHeight(mip) );
425
426      // Ok... pack in bitmaps till we run out.
427      for ( S32 y=0; y+currDim <= atlasSize.y; )
428      {
429         for ( S32 x=0; x+currDim <= atlasSize.x; )
430         {
431            // Copy the next bitmap to the dest texture.
432            GBitmap* cell = bitmaps.first();
433            bitmaps.pop_front();
434            destBmp.copyRect(cell, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip );
435            delete cell;
436
437            // Copy the next normal to the dest texture.
438            GBitmap* cellNormalmap = normalmaps.first();
439            normalmaps.pop_front();
440            destNormal.copyRect(cellNormalmap, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip );
441            delete cellNormalmap;
442
443            // Did we finish?
444            if ( bitmaps.empty() )
445               break;
446
447            x += currDim;
448         }
449
450         // Did we finish?
451         if ( bitmaps.empty() )
452            break;
453
454         y += currDim;
455      }
456
457      // Next mip...
458      currDim /= 2;
459   }
460
461   PROFILE_END(); // TSLastDetail_snapshots
462
463   delete imposterCap;
464   delete shape;   
465   
466   
467   // Should we dump the images?
468   if ( Con::getBoolVariable( "$TSLastDetail::dumpImposters", false ) )
469   {
470      String imposterPath = mCachePath + ".imposter.png";
471      String normalsPath = mCachePath + ".imposter_normals.png";
472
473      FileStream stream;
474      if ( stream.open( imposterPath, Torque::FS::File::Write  ) )
475         destBmp.writeBitmap( "png", stream );
476      stream.close();
477
478      if ( stream.open( normalsPath, Torque::FS::File::Write ) )
479         destNormal.writeBitmap( "png", stream );
480      stream.close();
481   }
482
483   // DEBUG: Some code to force usage of a test image.
484   //GBitmap* tempMap = GBitmap::load( "./forest/data/test1234.png" );
485   //tempMap->extrudeMipLevels();
486   //mTexture.set( tempMap, &GFXStaticTextureSRGBProfile, false );
487   //delete tempMap;
488
489   DDSFile *ddsDest = DDSFile::createDDSFileFromGBitmap( &destBmp );
490   ImageUtil::ddsCompress( ddsDest, GFXFormatBC2 );
491
492   DDSFile *ddsNormals = DDSFile::createDDSFileFromGBitmap( &destNormal );
493   ImageUtil::ddsCompress( ddsNormals, GFXFormatBC3 );
494
495   // Finally save the imposters to disk.
496   FileStream fs;
497   if ( fs.open( _getDiffuseMapPath(), Torque::FS::File::Write ) )
498   {
499      ddsDest->write( fs );
500      fs.close();
501   }
502   if ( fs.open( _getNormalMapPath(), Torque::FS::File::Write ) )
503   {
504      ddsNormals->write( fs );
505      fs.close();
506   }
507
508   delete ddsDest;
509   delete ddsNormals;
510
511   // If we did a begin then end it now.
512   if ( !sceneBegun )
513      GFX->endScene();
514}
515
516void TSLastDetail::deleteImposterCacheTextures()
517{
518   const String diffuseMap = _getDiffuseMapPath();
519   if ( diffuseMap.length() )
520      dFileDelete( diffuseMap );
521
522   const String normalMap = _getNormalMapPath();
523   if ( normalMap.length() )
524      dFileDelete( normalMap );
525}
526
527void TSLastDetail::updateImposterImages( bool forceUpdate )
528{
529   // Can't do it without GFX!
530   if ( !GFXDevice::devicePresent() )
531      return;
532
533   //D3DPERF_SetMarker( D3DCOLOR_RGBA( 0, 255, 0, 255 ), L"TSLastDetail::makeImposter" );
534
535   bool sceneBegun = GFX->canCurrentlyRender();
536   if ( !sceneBegun )
537      GFX->beginScene();
538
539   Vector<TSLastDetail*>::iterator iter = smLastDetails.begin();
540   for ( ; iter != smLastDetails.end(); iter++ )
541      (*iter)->update( forceUpdate );
542
543   if ( !sceneBegun )
544      GFX->endScene();
545}
546
547DefineEngineFunction( tsUpdateImposterImages, void, (bool forceUpdate), (false), "tsUpdateImposterImages( bool forceupdate )")
548{
549   TSLastDetail::updateImposterImages(forceUpdate);
550}
551
552
553