Game Development Community

dev|Pro Game Development Curriculum

TGEA 1.7.1 Extra Atlas Opacity Maps

by Matt Kronyak · 10/03/2008 (2:47 pm) · 19 comments

This resource demonstrates how to store a second opacity map directly in your Atlas terrain and how to load it so that the rendering code can make use of this data. This change requires no changes to the rendering code or any shaders.

A sample script written in Torque Script based off of the script generated by Grome is provided at the end of this resource.

Changes will be made to the following files:
atlas\editor\atlasConsoleGenerator.cpp
atlas\runtime\atlasInstance2.cpp
clipmap\clipMapBlenderCache.cpp
clipmap\clipMapBlenderCache.h

I will be providing the code for entire functions in this resource that can be easily copied and pasted over the code in the engine. I have noted areas that have been changed with the following comment:
//#CUSTOM#: Atlas Extra Opacity Map

This resource is extremely quick and easy to implement. On to the changes!

In atlas\editor\atlasConsoleGenerator.cpp:
Find:
ConsoleFunction(atlasGenerateBlenderTerrain, bool, 5, 20, "(outFile, inGeometryFile, inOpacityFile,  inLightmapFile, virtualTexSize, sourceImage0, sourceImage1, sourceImage2, ..., sourceImageN)")

Change this entire function to:
ConsoleFunction(atlasGenerateBlenderTerrain, bool, 5, 21, "(outFile, inGeometryFile, inOpacityFile, inOpacityFile2, inLightmapFile, virtualTexSize, sourceImage0, sourceImage1, sourceImage2, ..., sourceImageN)")	//#CUSTOM#: Atlas Extra Opacity Map
{
   Con::printf("atlasGenerateBlenderTerrain - Getting ready to produce blender terrain...");

   Resource<AtlasFile> inGeometryFile, inOpacityFile, inOpacityFile2, inLightmapFile;	//#CUSTOM#: Atlas Extra Opacity Map
	
   bool useSecondOpacityMap = false;	//#CUSTOM#: Atlas Extra Opacity Map

   // Try to load everything.
   Con::printf("   o Opening '%s' for geometry...", argv[2]);
   inGeometryFile = AtlasFile::load(argv[2]);
   if(inGeometryFile.isNull())
   {
      Con::errorf("atlasGenerateBlenderTerrain - unable to open '%s' for input!", argv[2]);
      return false;
   }

   Con::printf("   o Opening '%s' for opacity data...", argv[3]);
   inOpacityFile  = AtlasFile::load(argv[3]);
   if(inOpacityFile.isNull())
   {
      Con::errorf("atlasGenerateBlenderTerrain - unable to open '%s' for input!", argv[3]);
      return false;
   }

   //#CUSTOM#: Atlas Extra Opacity Map
   Con::printf("   o Opening '%s' for opacity data(2)...", argv[4]);
   inOpacityFile2  = AtlasFile::load(argv[4]);
   if(inOpacityFile2.isNull())
   {
	   //its ok to continue here so long as there's at least one opacity file
	  //since this is attempting to load a second opacity file, just output a warning if its not found
      Con::errorf("atlasGenerateBlenderTerrain - unable to open '%s' for input!", argv[4]);
   }

   //changed from 4 to 5
   Con::printf("   o Opening '%s' for lightmap data...", argv[5]);	
   inLightmapFile = AtlasFile::load(argv[5]);
   if(inLightmapFile.isNull())
   {
      Con::errorf("atlasGenerateBlenderTerrain - unable to open '%s' for input!", argv[5]);
      return false;
   }
   //#CUSTOM#: Atlas Extra Opacity Map

   Con::printf("   o Copying TOCs...");

   // Now create the new file.
   AtlasFile outFile;

   // Copy the TOCs into the new file.

   // Geometry first.
   AtlasResourceGeomTOC *originalArgtoc = NULL;
   if(!inGeometryFile->getTocBySlot(0, originalArgtoc))
   {
      Con::errorf("atlasGenerateBlenderTerrain - inGeometryFile does not have a Geometry TOC.");
      return false;
   }

   AtlasResourceGeomTOC *argtoc = new AtlasResourceGeomTOC;
   argtoc->copyFromTOC(originalArgtoc);
   outFile.registerTOC(argtoc);

   // Now do the textures...

   //#CUSTOM#: Atlas Extra Opacity Map
   // Opacity first.
   AtlasResourceTexTOC *originalArttocOM = NULL;
   if(!inOpacityFile->getTocBySlot(0, originalArttocOM))
   {
      Con::errorf("atlasGenerateBlenderTerrain - inOpacityFile does not have a Texture TOC.");
      return false;
   }

   //only use the second opacity file if it actually exists.
   //if not, just continue
   AtlasResourceTexTOC *originalArttocOM2 = NULL;
   if(!inOpacityFile2.isNull())
   {
	   if(!inOpacityFile2->getTocBySlot(0, originalArttocOM2))
	   {
		  Con::errorf("atlasGenerateBlenderTerrain - inOpacityFile2 does not have a Texture TOC.");
	   }
	   else
		   useSecondOpacityMap = true;
   }


   AtlasResourceTexTOC *arttocOM = new AtlasResourceTexTOC;
   arttocOM->copyFromTOC(originalArttocOM);
   outFile.registerTOC(arttocOM);
   	   
   AtlasResourceTexTOC *arttocOM2 = new AtlasResourceTexTOC;
   if(useSecondOpacityMap)	
   {
	   arttocOM2->copyFromTOC(originalArttocOM2);
	   outFile.registerTOC(arttocOM2);
   }
   //#CUSTOM#: Atlas Extra Opacity Map


   // Lightmap second.
   AtlasResourceTexTOC *originalArttocLM = NULL;
   if(!inLightmapFile->getTocBySlot(0, originalArttocLM))
   {
      Con::errorf("atlasGenerateBlenderTerrain - inLightmapFile does not have a Texture TOC.");
      return false;
   }

   AtlasResourceTexTOC *arttocLM = new AtlasResourceTexTOC;
   arttocLM->copyFromTOC(originalArttocLM);
   outFile.registerTOC(arttocLM);

   // We also need a config TOC.
   Con::printf("   o Generating config TOC...");
   AtlasResourceConfigTOC *arctoc = new AtlasResourceConfigTOC;
   arctoc->initialize(1);
   outFile.registerTOC(arctoc->castToResourceTOC());

   // And we're set, write it out.
   Con::printf("   o Outputting new .atlas file headers...");
   if(!outFile.createNew(argv[1]))
   {
      Con::errorf("atlasGenerateBlenderTerrain - unable to open '%s' for output!", argv[1]);
      return false;
   }

   // We have to copy the chunks, too. Start up threads so we can write.
   Con::printf("   o Starting loader threads...");
   outFile.startLoaderThreads();

   Con::printf("   o Copying geometry chunks...");
   originalArgtoc->copyChunksToTOC(argtoc);

   Con::printf("   o Copying opacity map chunks...");
   originalArttocOM->copyChunksToTOC(arttocOM);

   //#CUSTOM#: Atlas Extra Opacity Map
   if(useSecondOpacityMap)
   {   
	   Con::printf("   o Copying SECOND opacity map chunks...");   
	   originalArttocOM2->copyChunksToTOC(arttocOM2);
   }
   //#CUSTOM#: Atlas Extra Opacity Map

   Con::printf("   o Copying lightmap chunks...");
   originalArttocLM->copyChunksToTOC(arttocLM);

   // Finally, generate some configuration data.
   Con::printf("   o Generating config chunks...");
   AtlasConfigChunk *acc = new AtlasConfigChunk;
   acc->setEntry("type", "blender");
   acc->setEntry("opacityMapSlot", "0");
   //#CUSTOM#: Atlas Extra Opacity Map
   acc->setEntry("opacityMapSlot2", "1");
   acc->setEntry("shadowMapSlot",  "2");	//changed from 1 to 2
   //#CUSTOM#: Atlas Extra Opacity Map

   // Validate the texture size.
   S32 virtTexSize = dAtoi(argv[6]);	//changed from 5 to 6 //#CUSTOM#: Atlas Extra Opacity Map
   if(!isPow2(virtTexSize))
   {
      S32 nextLowest  = BIT(getBinLog2(U32(virtTexSize)));
      S32 nextHighest = BIT(getBinLog2(U32(virtTexSize))+1);

      Con::errorf("Virtual texture size specified was not a power of 2. Did you mean %d or %d?", nextLowest, nextHighest);
      Con::errorf("ABORTED!");
      return false;
   }

   acc->setEntry("virtualTexSize", argv[6]);	//changed from 5 to 6 //#CUSTOM#: Atlas Extra Opacity Map
   Con::printf("      - Virtual texture size is %d", virtTexSize);

   // How many source images are specified?
   S32 numSrcImages = argc - 7;	//changed from 6 to 7 //#CUSTOM#: Atlas Extra Opacity Map
   Con::printf("      - Found %d source images specified.", numSrcImages);

   acc->setEntry("sourceImageCount", avar("%d", numSrcImages));
   for(S32 i=0; i<numSrcImages; i++)
   {
      Con::printf("         * Source #%d = '%s'", i, argv[7+i]);	//changed from 6 to 7 //#CUSTOM#: Atlas Extra Opacity Map
      acc->setEntry(avar("sourceImage%d", i), argv[7 + i]);		//changed from 6 to 7 //#CUSTOM#: Atlas Extra Opacity Map
   }

   arctoc->addConfig("schema", acc);

   // Let everything serialize...
   outFile.waitForPendingWrites();
 
   // Successful, indicate such!
   Con::printf("   o Done!");
   return true;
}

Next, in atlas\runtime\atlasInstance2.cpp:

Find:
bool AtlasInstance::onAdd()

And replace it with:
bool AtlasInstance::onAdd()
{
   if(!Parent::onAdd())
      return false;

   mAtlasFile = AtlasFile::load(mAtlasFileName);

   if(mAtlasFile.isNull())
   {
      Con::errorf("AtlasInstance::onAdd - cannot open atlas file.");
      return false;
   }

   mAtlasFile->startLoaderThreads();

   // Bind the TOCs.
   AtlasResourceGeomTOC   *argtoc=NULL;
   AtlasResourceConfigTOC *arctoc=NULL;
   mAtlasFile->getTocBySlot(0, argtoc);
   mAtlasFile->getTocBySlot(0, arctoc);

   mGeomTOC = new AtlasInstanceGeomTOC;
   mGeomTOC->initializeTOC(argtoc);

   // Do configuration based initialization.
   if(arctoc)
   {
      if(isClientObject())
      {
         AtlasConfigChunk *acc = NULL;
         if(arctoc->getConfig("schema", acc))
         {
            // Is this a unique or a blender TOC?
            const char *schemaType = NULL;
            if(acc->getEntry("type", schemaType))
            {
               if(!dStricmp(schemaType, "unique"))
               {
                  // Grab the TOC - first texture TOC is what we want.
                  AtlasResourceTexTOC *arttoc;
                  if(mAtlasFile->getTocBySlot(0, arttoc))
                  {
                     // Set up a clipmap with appropriate settings.
                     mClipMap = new ClipMap();
                     mClipMap->mClipMapSize = Con::getIntVariable("Pref::Atlas::clipMapSize", 512);

                     // Do unique texture setup - this also initializes the texture size on clipmap.
                     AtlasClipMapImageSource *acmis = new AtlasClipMapImageSource();
                     acmis->setTOC(arttoc);
                     acmis->setUnique(true);
                     ClipMapUniqueCache *cmuc = new ClipMapUniqueCache(acmis);
                     mClipMap->setCache(cmuc);

                     // Don't forget to set the texture size!
                     mClipMap->mTextureSize = BIT(acmis->getMipLevelCount()-1);
                  }
                  else
                  {
                     Con::errorf("AtlasInstance2::onAdd - no texture TOC present, cannot initialize unique texturing!");
                  }

               }
               else if(!dStricmp(schemaType, "blender"))
               {
                  mIsBlended = true;
                  // Get what slots our TOCs are in.
                  const char *opacityTocSlot, *opacityTocSlot2, *shadowTocSlot;	//#CUSTOM#: Atlas Extra Opacity Map
                  bool gotTocSlotsOk = true;
				  bool useSecondOpacityMap = false; //#CUSTOM#: Atlas Extra Opacity Map
                  gotTocSlotsOk &= acc->getEntry("opacityMapSlot", opacityTocSlot);
				  useSecondOpacityMap = acc->getEntry("opacityMapSlot2", opacityTocSlot2);	//#CUSTOM#: Atlas Extra Opacity Map
                  gotTocSlotsOk &= acc->getEntry("shadowMapSlot", shadowTocSlot);

                  if(!gotTocSlotsOk)
                  {
                     Con::errorf("AtlasInstance2::onAdd - unable to get opacity or shadow TOC"
                                 " slot information from config block.");
                     goto endOfBlenderInitBlock;
                  }

                  // Grab TOCs.
                  bool tocInitOk;
                  AtlasResourceTexTOC *shadowToc;
                  AtlasResourceTexTOC *opacityToc;
				  AtlasResourceTexTOC *opacityToc2 = NULL;	//#CUSTOM#: Atlas Extra Opacity Map
                  tocInitOk = true;
                  shadowToc = NULL;                  
                  tocInitOk &= mAtlasFile->getTocBySlot(dAtoi(shadowTocSlot), shadowToc);					
				  tocInitOk &= mAtlasFile->getTocBySlot(dAtoi(opacityTocSlot), opacityToc);
				  
				  //#CUSTOM#: Atlas Extra Opacity Map
				  if(useSecondOpacityMap)
					  tocInitOk &= mAtlasFile->getTocBySlot(dAtoi(opacityTocSlot2), opacityToc2);
				  //#CUSTOM#: Atlas Extra Opacity Map                

                  if(!tocInitOk)
                  {
                     Con::errorf("AtlasInstance2::onAdd - unable to grab opacity or shadow TOC.");
                     goto endOfBlenderInitBlock;
                  }

                  // Initialize the clipmap.
                  mClipMap = new ClipMap();
                  mClipMap->mClipMapSize = Con::getIntVariable("Pref::Atlas::clipMapSize", 512);

                  // What size should the virtual texture be?
                  const char *virtualTexSize;
                  if(!acc->getEntry("virtualTexSize", virtualTexSize) || dAtoi(virtualTexSize) == 0)
                  {
                     Con::errorf("AtlasInstance2::onAdd - no virtual texture Size specified in config block or got zero size.");
                     goto endOfBlenderInitBlock;
                  }
                  else
                  {
                     mClipMap->mTextureSize = dAtoi(virtualTexSize);
                  }
                  
				  AtlasClipMapImageSource* acmis_opacity;
                  acmis_opacity = new AtlasClipMapImageSource();
                  acmis_opacity->setTOC(opacityToc);
                  acmis_opacity->setUnique(false);
				  
				  //#CUSTOM#: Atlas Extra Opacity Map
				  AtlasClipMapImageSource* acmis_opacity2;
				  //#CUSTOM#: Atlas Extra Opacity Map

                  AtlasClipMapImageSource *acmis_lightmap = new AtlasClipMapImageSource();
                  acmis_lightmap->setTOC(shadowToc);
                  acmis_opacity->setUnique(false);
				  //#CUSTOM#: Atlas Extra Opacity Map
				  if(useSecondOpacityMap)
				  {
					  Con::printf("AtlasInstance2::onAdd - Using TWO Opacity Maps!");
					  acmis_opacity2 = new AtlasClipMapImageSource();
					  acmis_opacity2->setTOC(opacityToc2);
					  acmis_opacity2->setUnique(false);
					  mAcmic_b = new ClipMapBlenderCache(acmis_opacity, acmis_opacity2, acmis_lightmap);
				  }
				  else
				  {
					  Con::printf("AtlasInstance2::onAdd - Using ONE Opacity Map!");
					  mAcmic_b = new ClipMapBlenderCache(acmis_opacity, acmis_lightmap);
				  }
				  //#CUSTOM#: Atlas Extra Opacity Map

				  mClipMap->setCache(mAcmic_b);

                  mLMChunkSize = acmis_lightmap->getChunkSize();

                  // And grab source images.
                  const char *srcImageCount;
                  if(!acc->getEntry("sourceImageCount", srcImageCount))
                  {
                     Con::errorf("AtlasInstance2::onAdd - no source image count specified.");
                     goto endOfBlenderInitBlock;
                  }

                  S32 srcCount = dAtoi(srcImageCount);
				  Con::printf("AtlasInstance2::onAdd - %d source images!", srcCount);	//#CUSTOM#: Atlas Extra Opacity Map
                  for(S32 i=0; i<srcCount; i++)
                  {
                     const char *srcImage;
                     if(!acc->getEntry(avar("sourceImage%d", i), srcImage))
                     {
                        Con::errorf("AtlasInstance2::onAdd - no source image specified at index %d!", i);
                        continue;
                     }

                     // AFX CODE BLOCK (atlas-strip-txr-path) <<
                     if (mStripTexturePath && mStripTexturePath[0] != '[[60f1bb35c3466]]')
                     {
                       S32 strip_len = dStrlen(mStripTexturePath);
                       if (dStrnicmp(mStripTexturePath, srcImage, strip_len) == 0)
                       {
                          srcImage += strip_len;
                          while (*srcImage == '/')
                            srcImage++;
                       }
                     }
                     // AFX CODE BLOCK (atlas-strip-txr-path) >>

                     char buff[1024];
                     Platform::makeFullPathName(srcImage, buff, 1024, mAtlasFile.getFilePath());
                     mAcmic_b->registerSourceImage(buff);
                  }

                  for(S32 i=srcCount; i<8; i++)	//changed from 4 to 8 //#CUSTOM#: Atlas Extra Opacity Map
                     mAcmic_b->registerSourceImage("");

endOfBlenderInitBlock: ;

               }
               else
               {
                  Con::errorf("AtlasInstance2::onAdd - unknown texture schema type '%s'", schemaType);
               }
            }
            else
            {
               Con::errorf("AtlasInstance2::onAdd - no texture schema type specified", schemaType);
            }
         }
         else
         {
            Con::errorf("AtlasInstance2::onAdd - no texture schema present.");
         }		   

         if(mClipMap)
         {
            // We need to initialize the clipmap before we can do stuff to it!
            mClipMap->initClipStack();

            // If there is a clipmap, make sure it's all loaded and ready to go.
            U32 time = Platform::getRealMilliseconds();
            while(!mClipMap->fillWithTextureData())
            {
               syncThreads();               
               Platform::sleep(10);
            }
            U32 postTime = Platform::getRealMilliseconds();
            Con::printf("AtlasInstance2::onAdd - took %d ms to fill clipmap with texture data.", postTime - time);
         }
         else
         {
            Con::errorf("AtlasInstance2::onAdd - failed to initialize clipmap!");
         }

         if( mDetailTexFileName )
         {
            if( !mDetailTex.set( mDetailTexFileName, &GFXDefaultStaticDiffuseProfile ) )
            {
               Con::warnf( "AtlasInstance2::onAdd - could not load detail map; disabling detail mapping" );
               mBatcher.isDetailMappingEnabled( false );
            }
         }
         else
          mBatcher.isDetailMappingEnabled( false );
      }
   }

   // Root node contains all children, so we can just grab its bounds and go from there.
   AtlasResourceGeomStub *args = mGeomTOC->getResourceStub(mGeomTOC->getStub(0));
   mObjBox = args->mBounds;

   // Get our collision info as well.
   mGeomTOC->loadCollisionLeafChunks();

   // Do general render initialization stuff.
   setRenderTransform(mObjToWorld);
   resetWorldBox();
   addToScene();

   // Ok, all done.
   return true;
}

Next, in clipmap\clipMapBlenderCache.cpp:
Find:
ClipMapBlenderCache::ClipMapBlenderCache( IClipMapImageSource *opacitySource, IClipMapImageSource *lightmapSource )

And directly UNDER this ADD this new constructor:
//#CUSTOM#: Atlas Extra Opacity Map
ClipMapBlenderCache::ClipMapBlenderCache( IClipMapImageSource *opacitySource, IClipMapImageSource *opacitySource2, IClipMapImageSource *lightmapSource )
{
   mOpacitySources.push_back(opacitySource);
   mOpacitySources.push_back(opacitySource2);
   mLightmapSources.push_back(lightmapSource);
   mOwningClipMap = NULL;
   mClipMapSize = -1;
   mShaderBaseName = StringTable->insert( "AtlasBlender" );
}
//#CUSTOM#: Atlas Extra Opacity Map

Finally in clipmap\clipMapBlenderCache.h
Find:
ClipMapBlenderCache(IClipMapImageSource *opacitySource, IClipMapImageSource *lightmapSource);

And ADD directly under it:
ClipMapBlenderCache( IClipMapImageSource *opacitySource, IClipMapImageSource *opacitySource2, IClipMapImageSource *lightmapSource );	//#CUSTOM#: Atlas Extra Opacity Map

That's it for engine changes.

Finally, a sample script based off of the Grome importer script that can easily handle creating a new Atlas terrain with a second opacity map and 8 terrain textures:
enableWinConsole(true);

// Output a log file
setLogMode(6);

//----------- Atlas2 Varibles --------------
echo ( "Loading Variables.\n");

// Pre set file paths
$Heightmap = "GromeAtlas30.raw";
$GeometryChu = "geometry.chu";
$GeometryAtlas = "geometry.atlas";
$Lightmap = "GromeAtlas30_lightmap.jpg";
$LightmapAtlas = "lightmap.atlas";
$Opacity = "GromeAtlas30_masks_1.png";
$OpacityAtlas = "opacity.atlas";

$Opacity2 = "GromeAtlas30_masks_2.png";	//### NEW
$OpacityAtlas2 = "opacity2.atlas";	//### NEW

$Path = "game/data/terrain/";
$Size = "256";
$MPP = "2";
$Scale = "143.39";
$HeightError = "1";
$TreeDepth = "4";
$LightmapTreeDepth = "4";
$VirtualTexture = "32768";
$AtlasOutputPath = "./";
$AtlasOutput = "GromeAtlas30.atlas";

$Tile1 = "grass2";
$Tile2 = "rock5";
$Tile3 = "rock6";
$Tile4 = "rock4";
$Tile5 = "grass5";
$Tile6 = "snow02";
$Tile7 = "dry_mud01";
$Tile8 = "vegetation01";

//----------- Atlas2 Process --------------
echo ( "Atlas2 process has begun.\n");

// Step 1 - creating geometry from the heightmap.
// Step 2 - convert the old CHU file to Atlas format.
// Step 3 - import the Lightmap, we use the same TreeDepth tile number.
// Step 4 - import the Opacity map, we use the same TreeDepth tile number.
// Step 5 - Export the finial Atlas, and merge the tile textures.

echo ( "Step 1 - creating geometry.");
atlasOldGenerateChunkFileFromRaw16( $Path @ $Heightmap, $Size, $MPP, $Scale/65535, $Path @ $GeometryChu, $HeightError, $TreeDepth);

echo ( "Step 2 - Convert CHU to Atlas.");
importOldAtlasCHU( $Path @ $GeometryChu, $Path @ $GeometryAtlas);

// echo ( "Step 3 - Import lightmap.");
atlasGenerateTextureTOCFromLargeJPEG( $Path @ $Lightmap, $LightmapTreeDepth, $Path @ $LightmapAtlas); 

// echo ( "Step 4 - Import Opacity.");
atlasGenerateTextureTOCFromTiles(1, $Path @ $Opacity, $Path @ $OpacityAtlas, 1);
atlasGenerateTextureTOCFromTiles(1, $Path @ $Opacity2, $Path @ $OpacityAtlas2, 1);	//### NEW

// echo ( "Step 5 - Finial Atlas File output.");
atlasGenerateBlenderTerrain(
$AtlasOutputPath @ $AtlasOutput,
$Path @ $GeometryAtlas,
$Path @ $OpacityAtlas,
$Path @ $OpacityAtlas2,
$Path @ $LightmapAtlas,

$VirtualTexture,

$Path @ $Tile1,
$Path @ $Tile2,
$Path @ $Tile3,
$Path @ $Tile4,
$Path @ $Tile5,
$Path @ $Tile6,
$Path @ $Tile7,
$Path @ $Tile8
);

echo ( "Done, closing.");
quit();

#1
10/02/2008 (11:05 pm)
Nice resource, this is something Atlas needed badly.
#2
10/03/2008 (1:16 am)
Nice indeed. Thank you Matt!
#3
10/03/2008 (1:29 am)
Matt, what did you find to be the limits to expanding this further to have a 3rd or a 4th opacity map? Are there any? Would it be possible to add further opacity maps the way you added the second one?
#4
10/03/2008 (1:53 am)
I haven't tried expanding it beyond 2 but I don't see why you couldn't use more opacity maps. You should be able to do it the exact same way, just account for handling 3 (or more) instead of 2. The only thing that might be a problem (and this would likely apply to this resource as well since I didn't change any of the rendering code) is how the 1.0 Shader deals with it. From what I saw the 1.0 implementation arbitrarily does two passes to the shader with two textures per pass. The 2.0 handling does does a single pass and keeps adding on 4 extra textures per opacity map.

The clip map code is already setup in 1.7.1 (possibly earlier versions? I didn't check) to loop through a list of opacity maps. The two main reasons it wasn't using multiple opacity maps already were that the extra opacity maps weren't being stored in the atlas terrain and the onAdd code wasn't trying to load them. For my implementation I created a new constructor for ClipMapBlenderCache that takes a second opacity map as a parameter to get it into the list. If you wanted to handle this in a more generic way it would probably make sense to just write a public method that would let you arbitrarily add new opacity maps to the mOpacitySources vector.
#5
10/03/2008 (1:58 am)
J.C.: thanks for the feedback and glad you found it useful. Any plans to post your Atlas ground cover code and release a resource? I've made a few changes to what you posted which I'll post when I have time, but a cleaned up version in a resource format would be nice to have ;).
#6
10/03/2008 (2:06 am)
That's great news! Atlas just became a lot more useful to me.

Thanks for the explanation. It'd make sense to generalize the solution to handle an arbitrary number of opacity maps. The only design limit would be keeping in mind that the more maps you use the more passes you need and the more fps you lose. That's pretty cool.

The Grome exporter could also be prepared in a way that would handle any number of opacity maps. Maybe it already is, I've only tried it with textures requiring only 2 maps.

First, I'll make sure I implement your solution correctly. Then I'm going to try and add a new opacity map. If that works, then I'll try and take a look at making the mOpacitySources vector dynamically scalable. Really exciting!

Thanks again for this great resource! This really is something that makes Atlas finally usable.
#7
10/03/2008 (2:19 am)
The Grome exporter could easily be updated to use this. The sample Torque script is exactly what Grome generates with a few small hand modifications. Grome will also export the extra opacity maps for you.
#8
10/04/2008 (3:57 am)
@Matt: I've made a couple additional changes to it since the forum post, but I just never found the time to type it up as a resource. Before doing it though I'd probably want to take out the hardcoded values in a couple of places and allow those to be user definable (for the calculations on where they appear on map). Since my game all uses the same sized maps though I just never got around to doing it. If anyone wants to post it as one though they can feel free to, would probalby be easier for people to find than in the forum thread.
#9
10/04/2008 (2:11 pm)
Great job!


Now that the resource is out and I finally got my laptop back together I'll give it a spin. Thanks for doing the dirty stuff, saved me hours of tedious code-poking ;)

Still, we need someone to fix the clipmap LOD issues (a.k.a. blurry textyres). Let's hope GeeGee allocates some resources for this?
#10
10/23/2008 (7:11 pm)
Anyone using L3DT and trying this resource?

I have had not problems other than removing AFX stuff(very small ad VS catches it).

Except when i try to export fromL3DT to Atlas, I think it might be the way L3DT is naming the files...just a guess

Here is the error log:

tarting Atlas Build!


Generating chunked geometry (depth=6, baseMaxError=1.000000)
o Calculating activation levels...
- done.
o Propagating activation levels...
- done.
o Writing file header...
- done.
o Generating empty TOC...
- done.
o Generating meshes...
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Max exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
AtlasOldActivationHeightfield::generateNodeData - Min exceeded! May have paging issues!
- done.
=== Chunk Statistics ===\
\===== Level Count Avg. Size
0 1024 473.621094
1 256 1752.781250
2 64 4110.390625
3 16 7812.250000
4 4 18867.000000
5 1 51207.000000
========================================
chunks: 1365
input verts: 16777216
output verts: 130868

avg verts/chunk: 96
NOTE: verts/chunk is low; for higher poly throughput consider setting the tree depth to 5 and reprocessing.
output bytes: 50533
bytes/input vert: 0.00
bytes/output vert: 0.39
real triangles: 1448436
AtlasDeferredFile::ensureStreamWritable - switching file into writable mode... This may cause a crash if you have other copies of Torque accessing it!
importOldAtlasCHU - created new Atlas file 'scriptsAndAssets/data/terrains/deleteme/geom.atlas'
importOldAtlasCHU - Atlas file initialized, converting...
importOldAtlasCHU - Headers read, remapping chunks...
importOldAtlasCHU - Importing geometry chunks...
importOldAtlasCHU - level 5 chunks...
importOldAtlasCHU - level 4 chunks...
importOldAtlasCHU - level 3 chunks...
importOldAtlasCHU - level 2 chunks...
importOldAtlasCHU - level 1 chunks...
importOldAtlasCHU - level 0 chunks...
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
importOldAtlasCHU - import OK!
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
atlasGenerateTextureTOCFromTiles - Initializing atlas file 'scriptsAndAssets/data/terrains/deleteme/lightmap.atlas'...
AtlasDeferredFile::ensureStreamWritable - switching file into writable mode... This may cause a crash if you have other copies of Torque accessing it!
atlasGenerateTextureTOCFromTiles - Atlas started, processing tiles for 4 deep tree...
atlasGenerateTextureTOCFromTiles - Storing tiles in JPEG format...
atlasGenerateTextureTOCFromTiles - processing row 0...
atlasGenerateTextureTOCFromTiles - processing row 1...
atlasGenerateTextureTOCFromTiles - processing row 2...
atlasGenerateTextureTOCFromTiles - processing row 3...
atlasGenerateTextureTOCFromTiles - processing row 4...
atlasGenerateTextureTOCFromTiles - processing row 5...
atlasGenerateTextureTOCFromTiles - processing row 6...
atlasGenerateTextureTOCFromTiles - processing row 7...
generating (2 @ 0, 0) from (3 @ 0, 0), (3 @ 0, 1), (3 @ 1, 0), (3 @ 1, 1)
generating (2 @ 0, 1) from (3 @ 0, 2), (3 @ 0, 3), (3 @ 1, 2), (3 @ 1, 3)
generating (2 @ 0, 2) from (3 @ 0, 4), (3 @ 0, 5), (3 @ 1, 4), (3 @ 1, 5)
generating (2 @ 0, 3) from (3 @ 0, 6), (3 @ 0, 7), (3 @ 1, 6), (3 @ 1, 7)
generating (2 @ 1, 0) from (3 @ 2, 0), (3 @ 2, 1), (3 @ 3, 0), (3 @ 3, 1)
generating (2 @ 1, 1) from (3 @ 2, 2), (3 @ 2, 3), (3 @ 3, 2), (3 @ 3, 3)
generating (2 @ 1, 2) from (3 @ 2, 4), (3 @ 2, 5), (3 @ 3, 4), (3 @ 3, 5)
generating (2 @ 1, 3) from (3 @ 2, 6), (3 @ 2, 7), (3 @ 3, 6), (3 @ 3, 7)
generating (2 @ 2, 0) from (3 @ 4, 0), (3 @ 4, 1), (3 @ 5, 0), (3 @ 5, 1)
generating (2 @ 2, 1) from (3 @ 4, 2), (3 @ 4, 3), (3 @ 5, 2), (3 @ 5, 3)
generating (2 @ 2, 2) from (3 @ 4, 4), (3 @ 4, 5), (3 @ 5, 4), (3 @ 5, 5)
generating (2 @ 2, 3) from (3 @ 4, 6), (3 @ 4, 7), (3 @ 5, 6), (3 @ 5, 7)
generating (2 @ 3, 0) from (3 @ 6, 0), (3 @ 6, 1), (3 @ 7, 0), (3 @ 7, 1)
generating (2 @ 3, 1) from (3 @ 6, 2), (3 @ 6, 3), (3 @ 7, 2), (3 @ 7, 3)
generating (2 @ 3, 2) from (3 @ 6, 4), (3 @ 6, 5), (3 @ 7, 4), (3 @ 7, 5)
generating (2 @ 3, 3) from (3 @ 6, 6), (3 @ 6, 7), (3 @ 7, 6), (3 @ 7, 7)
generating (1 @ 0, 0) from (2 @ 0, 0), (2 @ 0, 1), (2 @ 1, 0), (2 @ 1, 1)
generating (1 @ 0, 1) from (2 @ 0, 2), (2 @ 0, 3), (2 @ 1, 2), (2 @ 1, 3)
generating (1 @ 1, 0) from (2 @ 2, 0), (2 @ 2, 1), (2 @ 3, 0), (2 @ 3, 1)
generating (1 @ 1, 1) from (2 @ 2, 2), (2 @ 2, 3), (2 @ 3, 2), (2 @ 3, 3)
generating (0 @ 0, 0) from (1 @ 0, 0), (1 @ 0, 1), (1 @ 1, 0), (1 @ 1, 1)
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
atlasGenerateTextureTOCFromTiles - Done.
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
atlasGenerateTextureTOCFromTiles - Initializing atlas file 'scriptsAndAssets/data/terrains/deleteme/opacity.atlas'...
AtlasDeferredFile::ensureStreamWritable - switching file into writable mode... This may cause a crash if you have other copies of Torque accessing it!
atlasGenerateTextureTOCFromTiles - Atlas started, processing tiles for 4 deep tree...
atlasGenerateTextureTOCFromTiles - Storing tiles in PNG format...
atlasGenerateTextureTOCFromTiles - processing row 0...
atlasGenerateTextureTOCFromTiles - processing row 1...
atlasGenerateTextureTOCFromTiles - processing row 2...
atlasGenerateTextureTOCFromTiles - processing row 3...
atlasGenerateTextureTOCFromTiles - processing row 4...
atlasGenerateTextureTOCFromTiles - processing row 5...
atlasGenerateTextureTOCFromTiles - processing row 6...
atlasGenerateTextureTOCFromTiles - processing row 7...
generating (2 @ 0, 0) from (3 @ 0, 0), (3 @ 0, 1), (3 @ 1, 0), (3 @ 1, 1)
generating (2 @ 0, 1) from (3 @ 0, 2), (3 @ 0, 3), (3 @ 1, 2), (3 @ 1, 3)
generating (2 @ 0, 2) from (3 @ 0, 4), (3 @ 0, 5), (3 @ 1, 4), (3 @ 1, 5)
generating (2 @ 0, 3) from (3 @ 0, 6), (3 @ 0, 7), (3 @ 1, 6), (3 @ 1, 7)
generating (2 @ 1, 0) from (3 @ 2, 0), (3 @ 2, 1), (3 @ 3, 0), (3 @ 3, 1)
generating (2 @ 1, 1) from (3 @ 2, 2), (3 @ 2, 3), (3 @ 3, 2), (3 @ 3, 3)
generating (2 @ 1, 2) from (3 @ 2, 4), (3 @ 2, 5), (3 @ 3, 4), (3 @ 3, 5)
generating (2 @ 1, 3) from (3 @ 2, 6), (3 @ 2, 7), (3 @ 3, 6), (3 @ 3, 7)
generating (2 @ 2, 0) from (3 @ 4, 0), (3 @ 4, 1), (3 @ 5, 0), (3 @ 5, 1)
generating (2 @ 2, 1) from (3 @ 4, 2), (3 @ 4, 3), (3 @ 5, 2), (3 @ 5, 3)
generating (2 @ 2, 2) from (3 @ 4, 4), (3 @ 4, 5), (3 @ 5, 4), (3 @ 5, 5)
generating (2 @ 2, 3) from (3 @ 4, 6), (3 @ 4, 7), (3 @ 5, 6), (3 @ 5, 7)
generating (2 @ 3, 0) from (3 @ 6, 0), (3 @ 6, 1), (3 @ 7, 0), (3 @ 7, 1)
generating (2 @ 3, 1) from (3 @ 6, 2), (3 @ 6, 3), (3 @ 7, 2), (3 @ 7, 3)
generating (2 @ 3, 2) from (3 @ 6, 4), (3 @ 6, 5), (3 @ 7, 4), (3 @ 7, 5)
generating (2 @ 3, 3) from (3 @ 6, 6), (3 @ 6, 7), (3 @ 7, 6), (3 @ 7, 7)
generating (1 @ 0, 0) from (2 @ 0, 0), (2 @ 0, 1), (2 @ 1, 0), (2 @ 1, 1)
generating (1 @ 0, 1) from (2 @ 0, 2), (2 @ 0, 3), (2 @ 1, 2), (2 @ 1, 3)
generating (1 @ 1, 0) from (2 @ 2, 0), (2 @ 2, 1), (2 @ 3, 0), (2 @ 3, 1)
generating (1 @ 1, 1) from (2 @ 2, 2), (2 @ 2, 3), (2 @ 3, 2), (2 @ 3, 3)
generating (0 @ 0, 0) from (1 @ 0, 0), (1 @ 0, 1), (1 @ 1, 0), (1 @ 1, 1)
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
atlasGenerateTextureTOCFromTiles - Done.
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
atlasGenerateBlenderTerrain - Getting ready to produce blender terrain...
o Opening 'scriptsAndAssets/data/terrains/deleteme/geom.atlas' for geometry...
AtlasFile::load - loading Atlas resource 900ca00 with scriptsAndAssets/data/terrains/deleteme/geom.atlas
o Opening 'scriptsAndAssets/data/terrains/deleteme/opacity.atlas' for opacity data...
AtlasFile::load - loading Atlas resource 88cfcc8 with scriptsAndAssets/data/terrains/deleteme/opacity.atlas
o Opening 'scriptsAndAssets/data/terrains/deleteme/lightmap.atlas' for opacity data(2)...
AtlasFile::load - loading Atlas resource 3c3a7f0 with scriptsAndAssets/data/terrains/deleteme/lightmap.atlas
o Opening '131072' for lightmap data...
AtlasFile::load - cannot open atlas file '131072'.
atlasGenerateBlenderTerrain - unable to open '131072' for input!
Build complete!
onExit: Unknown command.
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!
AtlasFile::waitForPendingWrites - Waiting for pending output to finish.
AtlasFile::waitForPendingWrites - Done!

Anyhelp would be appreciated:)
#11
10/26/2008 (8:44 am)
Bobby,

I have the same issue with l3dt exporting blended terrains. If you export the files yourself and make a custom build script based off the TDN Resource it works fine.
#12
10/27/2008 (5:24 am)
Hi Guys,

I'm working on a mod of the L3DT Atlas2 exporter to support this resource. If any 'L3DT for Torque' users would like to try the developmental build, please e-mail aaron@bundysoft.com to request download/usage instructions.

Best regards,
Aaron Torpy.
#13
01/04/2009 (8:15 pm)
A slight modification to the resource to get it to work for 1.8.0.

In clipmap\clipMapBlenderCache.h change
ClipMapBlenderCache( IClipMapImageSource *opacitySource, IClipMapImageSource *opacitySource2, IClipMapImageSource *lightmapSource );

To

ClipMapBlenderCache(IClipMapImageSource *opacitySource, IClipMapImageSource *opacitySource2, IClipMapImageSource *lightmapSource[b] , bool LM1 = false[/b]);

And change the new constructor to
//#CUSTOM#: Atlas Extra Opacity Map
ClipMapBlenderCache::ClipMapBlenderCache( IClipMapImageSource *opacitySource, IClipMapImageSource *opacitySource2, IClipMapImageSource *lightmapSource )
{
   mOpacitySources.push_back(opacitySource);
   mOpacitySources.push_back(opacitySource2);
   mLightmapSources.push_back(lightmapSource);
   mOwningClipMap = NULL;
   mClipMapSize = -1;
   [b]mTempScaleFactors.set(128.0f, 128.0f, 128.0f, 128.0f);
   mLM1 = LM1;[/b]
}
//#CUSTOM#: Atlas Extra Opacity Map
#14
01/31/2009 (1:54 am)
For 8192x8192 opacity maps, I had trouble loading the second opacity map into memory - the thing would fail here:

//#CUSTOM#: Atlas Extra Opacity Map
  if(useSecondOpacityMap)
  {
	  Con::printf("AtlasInstance2::onAdd - Using TWO Opacity Maps!");
	  acmis_opacity2 = new AtlasClipMapImageSource();
	  [b]acmis_opacity2->setTOC(opacityToc2);[/b]
	  acmis_opacity2->setUnique(false);
	  mAcmic_b = new ClipMapBlenderCache(acmis_opacity, acmis_opacity2, acmis_lightmap);
  }
  else
  {
	  Con::printf("AtlasInstance2::onAdd - Using ONE Opacity Map!");
	  mAcmic_b = new ClipMapBlenderCache(acmis_opacity, acmis_lightmap);
  }
  //#CUSTOM#: Atlas Extra Opacity Map

I've been wondering about using one opacity map, and 4 bits for each layer, or alternatively doing some preprocessing of the opacity maps and optimizing it all to fit into one map (ie. by normalizing layers and always displaying only the 3-4 that's the most visible + hiding layer info somewhere)

Am I right to assume that this would need a rewrite in the shader code?
#15
04/22/2009 (1:36 am)
Thanks For the Help:

I get the Following:

1>atlasInstance2.cpp
1>..\..\..\..\..\engine\source\atlas\runtime\atlasInstance2.cpp(245) : error C2065: 'mStripTexturePath' : undeclared identifier
1>..\..\..\..\..\engine\source\atlas\runtime\atlasInstance2.cpp(245) : error C2065: 'mStripTexturePath' : undeclared identifier
1>..\..\..\..\..\engine\source\atlas\runtime\atlasInstance2.cpp(245) : error C2015: too many characters in constant
1>..\..\..\..\..\engine\source\atlas\runtime\atlasInstance2.cpp(247) : error C2065: 'mStripTexturePath' : undeclared identifier
1>..\..\..\..\..\engine\source\atlas\runtime\atlasInstance2.cpp(248) : error C2065: 'mStripTexturePath' : undeclared identifier
1>Build log was saved at "file://m:\GameDev\Engine\TGEA_1_7_1\GameExamples\AtlasDemo\buildFiles\Link\VC2k8.Release.Win32\AtlasDemo\BuildLog.htm"
1>AtlasDemo - 5 error(s), 1 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
#16
04/22/2009 (1:51 am)
This resource was implemented into TGEA 1.7.1 with AFX. If you don't have AFX you probably need to cut out all of the code enclosed in // AFX comments.
#17
04/22/2009 (4:54 am)
What do i do with the Grome Based Script?

And i get an Error 2 many characters in constant at line 269 of atlasInstance2.cpp
#18
04/22/2009 (10:12 am)
To use the Grome-based script find the comment
// Pre set file paths

and then configure all of the global variables (all of the ones that start with $). $Heightmap through $Tile8

If you are using Grome you can have it generate almost the same script for you. Just find the lines I labeled as //### NEW and add those in. The Grome exporter code can be modified and recompiled so that it outputs 8 $Tile variables instead of 4, or you can manually add Tiles 5 through 8.
#19
05/29/2009 (4:41 am)
Hi again Matt, I gave unique textures a try only to realize there are no light maps on them. So it's back to blended, how ever i get the error
"error C2015: too many characters in constant"
You told me to for if i didn't have AFX i comment out this line, but i have AFX so this makes no sense. Please help me out i am really confused.