Game Development Community

TGEA 1.7.0 Beta 1 - Bug Texture Editor Dead

by Michael Perry · in Torque Game Engine Advanced · 03/24/2008 (12:07 pm) · 5 replies

While writing a tutorial about using the Terrain Texture Editor, I noticed a problem. The Texture Editor is still coded to work on a single Legacy TerrainBlock, and only if it is named "Terrain." I tracked down what function eventually gets called in the engine that paints your terrain based on what placement procedure you pick:

In engine/source/gui/missionEditor/terraformerTexture.cpp
bool Terraformer::setMaterials(const char *r_src, const char *materials )
{
   TerrainBlock *serverTerrBlock = dynamic_cast<TerrainBlock*>(Sim::findObject("Terrain"));
   if (!serverTerrBlock)
      return false;
...
}

At the very start of the function, we are dead in the water on various levels. I put in a fix that works for me, but I'm not sure if it needs to be optimized or tweaked to act as a proper bug fix.

Fix in next post...

#1
03/24/2008 (12:07 pm)
New Terraformer::setMaterials(...) method:
//--------------------------------------
bool Terraformer::setMaterials(const char *r_src, const char *materials )
{
	Vector<TerrainBlock *> terrains;
	
	for(SimGroupIterator itr(Sim::getRootGroup());  *itr; ++itr)
	{


		const char* className = (*itr)->getClassName();
		if (!dStrcmp(className, "TerrainBlock"))
		{
			
			TerrainBlock *clientTerrBlock = dynamic_cast<TerrainBlock*>((*itr));
			if (!clientTerrBlock || clientTerrBlock->isServerObject())
				continue;
			terrains.push_back(clientTerrBlock);
		}
	}
	
	if(terrains.size()<1)
		return false;

	for (int i = 0; i < terrains.size(); i++)
	{
		TerrainBlock* clientTerrBlock = terrains[i];

		VectorPtr<Heightfield*> src;
		VectorPtr<char*> dml;
		Vector<S32> dmlIndex;

		//--------------------------------------
		// extract the source registers
		char buffer[1024];
		dStrcpy(buffer, r_src);
		char *str = dStrtok(buffer, " [[6288102ebc44c]]");
		while (str)
		{
			src.push_back( getRegister(dAtoi(str)) );
			str = dStrtok(NULL, " [[6288102ebc44c]]");
		}

		//--------------------------------------
		// extract the materials
		dStrcpy(buffer, materials);
		str = dStrtok(buffer, " [[6288102ebc44c]]");
		while (str)
		{
			S32 i;
			for (i=0; i<dml.size(); i++)
				if (dStricmp(str, dml[i]) == 0)
					break;

			// a unique material list ?
			if (i == dml.size())
				dml.push_back(str);

			// map register to dml
			dmlIndex.push_back(i);

			str = dStrtok(NULL, " [[6288102ebc44c]]");
		}

		if (dml.size() > TerrainBlock::MaterialGroups)
		{
			Con::printf("maximum number of DML Material Exceeded");
			return false;
		}

		// install the new DMLs
		clientTerrBlock->setBaseMaterials(dml.size(), (const char**)dml.address());

		//--------------------------------------
		// build alpha masks for each material type

		for (S32 y=0; y<blockSize; y++)
		{
			for (S32 x=0; x<blockSize; x++)
			{
				// skip? (cannot skip if index is out of range...)
				F32 total = 0;
				F32 matVals[TerrainBlock::MaterialGroups];
				S32 i;

				for(i = 0; i < TerrainBlock::MaterialGroups; i++)
					matVals[i] = 0;

				for(i = 0; i < src.size(); i++)
				{
					matVals[dmlIndex[i]] += src[i]->val(x,y);
					total += src[i]->val(x,y);
				}

				if(total == 0)
				{
					matVals[0] = 1;
					total = 1;
				}

				// axe out any amount that is less than the threshold
				F32 threshold = 0.15 * total;
				for(i = 0; i < TerrainBlock::MaterialGroups; i++)
					if(matVals[i] < threshold)
						matVals[i] = 0;

				total = 0;
				for(i = 0; i < TerrainBlock::MaterialGroups; i++)
					total += matVals[i];

				for(i = 0; i < TerrainBlock::MaterialGroups; i++)
				{
					U8 *map = clientTerrBlock->getMaterialAlphaMap(i);
					map[x + (y << TerrainBlock::BlockShift)] = (U8)(255 * matVals[i] / total);
				}

				S32 material = 0;
				F32 best = 0.0f;
				for (i=0; i<src.size(); i++)
				{
					F32 value = src[i]->val(x,y);
					if ( value > best)
					{
						material = dmlIndex[i];
						best = value;
					}
				}
				// place the material
				*clientTerrBlock->getBaseMaterialAddress(x, y) = material;
			}
		}

		// make it so!
		clientTerrBlock->buildGridMap();
		clientTerrBlock->buildMaterialMap();

		// reload the material lists?
		if(gEditingMission)
			clientTerrBlock->refreshMaterialLists();

		//--------------------------------------------------------------------------
		// for mow steal the first bitmap out of each dml

		if (Con::getBoolVariable("$terrainTestBmp", false) == true)
		{
			VectorPtr<GBitmap*> mats;
			for (S32 i=0; i<dmlIndex.size(); i++)
			{
				/*
				Resource<MaterialList> mlist = ResourceManager->load(dml[dmlIndex[i]]);
				mlist->load();
				GBitmap *bmp = mlist->getMaterial(0).getBitmap();
				mats.push_back(bmp);
				*/
			}
			GBitmap *texture = merge(src,mats);

			FileStream stream;
			stream.open("terrain.png", FileStream::Write);
			texture->writePNG(stream);
			stream.close();
			delete texture;
		}
	}
	return true;
}

*Note* - You cannot see the effect immediately. I had to open up the Terrain Painter [F8], and paint a single square once on each of the terrains in the scene. I'm not sure if a "refresh" or "postInspectApply" kind of thing needs to be called or not.

Thoughts?
#2
03/24/2008 (12:10 pm)
Just to point out the changes:

Instead of searching for a single object named "Terrain":
TerrainBlock *serverTerrBlock = dynamic_cast<TerrainBlock*>(Sim::findObject("Terrain"));
   if (!serverTerrBlock)
      return false;

   NetConnection* toServer = NetConnection::getConnectionToServer();
   NetConnection* toClient = NetConnection::getLocalClientConnection();

   S32 index = toClient->getGhostIndex(serverTerrBlock);

   TerrainBlock *clientTerrBlock = dynamic_cast<TerrainBlock*>(toServer->resolveGhost(index));
   if (!clientTerrBlock)
      return false;

I iterate through our Root Group and grab all non-server terrain objects:
Vector<TerrainBlock *> terrains;
	
for(SimGroupIterator itr(Sim::getRootGroup());  *itr; ++itr)
{
	const char* className = (*itr)->getClassName();
	if (!dStrcmp(className, "TerrainBlock"))
	{
		
		TerrainBlock *clientTerrBlock = dynamic_cast<TerrainBlock*>((*itr));
		if (!clientTerrBlock || clientTerrBlock->isServerObject())
			continue;
		terrains.push_back(clientTerrBlock);
	}
}

Instead of applying the functionality to a single TerrainBlock:
if (!clientTerrBlock)
      return false;

I iterate through a vector of TerrainBlock pointers I found earlier:
if(terrains.size()<1)
	return false;

for (int i = 0; i < terrains.size(); i++)
{
	TerrainBlock* clientTerrBlock = terrains[i];

	VectorPtr<Heightfield*> src;
...
#3
03/26/2008 (6:02 pm)
Fixed for the next Beta.
#4
03/27/2008 (7:50 am)
Haven't had a chance to fully debug everything in this, just got the terrain painting working and went back to writing in the docs. Could still probably use a real run through to see if all the features still function.
#5
03/31/2008 (10:43 am)
This should be fixed in Beta 2. Please retest if you get a chance. Thanks!