Game Development Community

dev|Pro Game Development Curriculum

TSE shapebase material swap

by Bryan Stroebel · 08/17/2006 (12:12 pm) · 13 comments

TSE milestone 3.5 now requires all materials to be defined in a materials.cs file. If a material is not defined the error texture will show on your shapes. I have rewritten the reskin functionality to work with the latest TSE milestone 3.5. I have expanded it's functionality to allow you to change a material to any material defined in any of your materials.cs files. You can switch back and fourth as many times as you want and use any defined material you want. Here's the code changes:

Open ts/tsShapeInstance.h and look for the following line:
void reSkin(StringHandle& newBaseHandle);
Change it to:
void reSkin(StringHandle& oldMaterial,StringHandle& newMaterial);
I added an arg and changed the names of the agrs to make more sense. Save this file.

Open ts/tsShapeInstance.cpp and look for the TSShapeInstance::reSkin function and change it to:
void TSShapeInstance::reSkin(StringHandle& oldMaterial,StringHandle& newMaterial)
{
//BCS - Swaps skin on shapebase and static objects
const char* MatName = null;
const char* newMatName = null;
      if (oldMaterial.isValidString() && newMaterial.isValidString()) {
      	MatName = oldMaterial.getString();
	newMatName = newMaterial.getString();
      	}
   if (MatName == NULL || newMatName == NULL){return;}
   if (ownMaterialList() == false) {cloneMaterialList();}
   // Cycle through the materials.
   TSMaterialList* pMatList = getMaterialList();
   for (S32 j = 0; j < pMatList->mMaterialNames.size(); j++)
  {
      const char* pName = pMatList->mMaterialNames[j];
      if (pName == NULL){continue;}
      if(!dStricmp(pName,MatName))
      {
         MatInstance * matInst = pMatList->getMaterialInst(j);
         if(!matInst->swapMaterial(newMatName)){
	Con::errorf("reSkin failed. Material %s not found.",newMatName);
         }
      }
   }
}
Save this file. This function calls swapMaterial() which we need to create in the materials/matInstance.h

Open materials/matInstance.h and add the following around line 110 in the public section of the class:
//BCS - added for material swap
	bool swapMaterial(const char*);
Save this file.

Open materials/matInstance.cpp and add the following function to the bottom of the file:
//BCS - added for material swap
bool MatInstance::swapMaterial(const char* newName)
{
	Material * myMat;
	//if we find the new material swap it out
	if(!Sim::findObject(newName, myMat))
	{
		return false;
	}
	else
	{
		mMaterial = myMat;
		reInit();
		return true;
	}
	myMat = NULL;
}
Save this file.

Open game/ShapeBase.h and add the following line around line 709 right after the mSkinNameHandle line:
//BCS - added for material swap
	StringHandle     mNewSkinNameHandle;
We need a second argument for updating the skin over the network. That's what this is used for.

Search for setSkinName(const char*); and change it to:
void setSkinName(const char*,const char*);
Again we need that second arg. Save this file.

Open game/ShapeBase.cpp and look for the setSkinName console method. It's towards the bottom of the file. Change it to this:
ConsoleMethod( ShapeBase, setSkinName, void, 4, 4, "(oldMaterialName,newMaterialName)")
{
   object->setSkinName(argv[2],argv[3]);
}
All we did was add a second arg. This allows us to pass the old and new material name.

Search for the ShapeBase::setSkinName function and change it to:
void ShapeBase::setSkinName(const char* name,const char* newSkinName)
{
   if (!isGhost()) {
      if (name[0] != '[[60c1e3d54fbbd]]') {
         // Use tags for better network performance
         // Should be a tag, but we'll convert to one if it isn't.
         if (name[0] == StringTagPrefixByte) {
            mSkinNameHandle = StringHandle(U32(dAtoi(name + 1)));
         }
         else {
            mSkinNameHandle = StringHandle(name);
         }
      }
      else {
         mSkinNameHandle = StringHandle();
      }
      if (newSkinName[0] != '[[60c1e3d54fbbd]]') {
         // Use tags for better network performance
         // Should be a tag, but we'll convert to one if it isn't.
         if (newSkinName[0] == StringTagPrefixByte) {
            mNewSkinNameHandle = StringHandle(U32(dAtoi(newSkinName + 1)));
         }
         else {
            mNewSkinNameHandle = StringHandle(newSkinName);
         }
      }
      else {
         mNewSkinNameHandle = StringHandle();
      }
      setMaskBits(SkinMask);
   }
}
All we did was add the second arg that is passed to us from the console function.

Now find the ShapeBase::packUpdate function and add the following around line 2864 right under con->packStringHandleU(stream, mSkinNameHandle);
con->packStringHandleU(stream, mNewSkinNameHandle);
This packs our second argument for network update.

Now find the ShapeBase::unpackUpdate function and replace the existing SkinMask unpack with this one(around line 3040):
//BCS - changed for material swap
      if (stream->readFlag()) {  // SkinMask
         StringHandle skinDesiredNameHandle = con->unpackStringHandleU(stream);
	   StringHandle newskinDesiredNameHandle = con->unpackStringHandleU(stream);
         if (mSkinNameHandle != newskinDesiredNameHandle) {
            mSkinNameHandle = newskinDesiredNameHandle;
            if (mShapeInstance) {
               mShapeInstance->reSkin(skinDesiredNameHandle,newskinDesiredNameHandle);
               if (mSkinNameHandle.isValidString()) {
                  mSkinHash = _StringTable::hashString(mSkinNameHandle.getString());
               }
            }
         }
      }
Again, this unpacks two arguments instead of one.

Now find the reSkin function around line 852 and change it to:
mShapeInstance->reSkin(mSkinNameHandle,mSkinNameHandle);
Save this file.

Open game/ShapeImage.cpp and look for the reSkin function around line 1291. Change it to:
image.shapeInstance->reSkin(image.skinNameHandle,skinNameHandle);
Search again for the reSkin function around line 1333 and change it to:
image.shapeInstance->reSkin(skinNameHandle,skinNameHandle);
Save this file and compile.

To use this simply specify the orginal material and the new material.

Obj.setSkinName(original_MaterialMpTo_Name, New_Material_Name);

The original material mapTo name will not change even after swapping materials. For example, to swap the Orc_Material with the OrcEye material and back we would do the following:
$Game::Player.setSkinName(Orc_Material,OrcEye);
$Game::Player.setSkinName(Orc_Material, SpaceOrc);

I designed it this way so that you wouldn't have to keep track of the names as you swap them out. The original name is the original mapTo name. The new name is the name of the new material
new Material(OrcEye)
{
baseTex[0] = "~/data/shapes/spaceOrc/orc_ID6_eye";
emissive[0] = true;
glow[0] = true;
};

#1
08/19/2006 (2:24 pm)
This is really cool!
#2
03/28/2007 (8:34 pm)
hi, i implemented this in tgea 1.0 and it seem working with the SpaceOrc, but with my own model not i think is my max setup. i used multi/sub object to textur. can any explain short what to do in max?

thx in advance
#3
06/14/2007 (2:24 pm)
Hi all,
I've been trying to figure out a way to swap textures on some models that have multiple textures. I've used this tutorial but it seems like it only works for models that use one texture for the head, body, exec (Tested the orc out and it worked just fine). I have models that are setup to take a texture like so: base.head, base.body, base.hair Then all other swappable textures are named like this: man1.head, man1.body, man1.hair . Then when swapping I call the command like so: obj.setSkinName(base,man1); But nothing happens. I don't get any console errors so Not sure whats going on.
Thanks for any help
#4
07/19/2007 (9:39 pm)
Hello,

I've implemented this resource into the latest TGEA, but it does not work as expected. First, I found that I had to add preload to the materials. Otherwise their pass structures where not loaded and so would not work.

Once I added preload, I can step though all the assignments, and they are all getting assigned properly. Yet, I still get the old materials showing, not the new one.

I'm at a loss on where to look now for the possible causes, can someone shead some light for me?

Thanks

Edit: ok, I am getting there. I can see my new material, but the new texture in it is being blended with the old one. Humm, it's like the old material is still setup to run. I'll dig into that a bit.
#5
07/20/2007 (8:20 pm)
OK, I have this working at last. No more ghosting of the original material. Though I don't realy understand why, but it does work. So if someone with more knowledge about the material system can take a look and see if this is the correct way, or why I had to do it this way and present a better solution, it would be great.

What I ended up doing was changing
void MatInstance::reInit( )
{
   mPasses.clear();
   processMaterial();
   mProcessedMaterial = true;
 
   if(!isDynamicLightingMaterial() && !mMaterial->emissive[0])
   {
      for(U32 i=0; i<LightInfo::sgFeatureCount; i++)
      {
         if(dynamicLightingMaterials_Single[i])
            dynamicLightingMaterials_Single[i]->reInit();
  
         if(dynamicLightingMaterials_Dual[i])
            dynamicLightingMaterials_Dual[i]->reInit();
      }
 
      if(dynamicLightingMaskMaterial)
         dynamicLightingMaskMaterial->reInit();
   };
}


to the following
void MatInstance::reInit( )
{
               mPasses.clear();
               processMaterial();
               mProcessedMaterial = true;
 
	if(!isDynamicLightingMaterial() && !mMaterial->emissive[0])
	{
		clearDynamicLightingMaterials();

		CustomMaterial *custmat = dynamic_cast<CustomMaterial *>(mMaterial);
		if(custmat)
		{
			if(custmat->dynamicLightingMaterial)
			{
				// custom materials only use the assigned single-full dynamic lighting shader...
				dynamicLightingMaterials_Single[LightInfo::sgFull] = new MatInstance(*custmat->dynamicLightingMaterial);
				dynamicLightingMaterials_Single[LightInfo::sgFull]->materialType = mtDynamicLightingSingle;
				dynamicLightingMaterials_Single[LightInfo::sgFull]->dynamicLightingFeatures = LightInfo::sgFull;
				dynamicLightingMaterials_Single[LightInfo::sgFull]->init(mSGData, mVertFlags);
			}
			if(custmat->dynamicLightingMaskMaterial)
			{
				dynamicLightingMaskMaterial = new MatInstance(*custmat->dynamicLightingMaskMaterial);
				dynamicLightingMaskMaterial->materialType = mtDynamicLightingMask;
				dynamicLightingMaskMaterial->dynamicLightingFeatures = LightInfo::sgNoCube;
				dynamicLightingMaskMaterial->init(mSGData, mVertFlags);
			}
		}
		else
		{
			for(U32 i=0; i<LightInfo::sgFeatureCount; i++)
			{
				// always build these...
				dynamicLightingMaterials_Single[i] = new MatInstance(*mMaterial);
				dynamicLightingMaterials_Single[i]->materialType = mtDynamicLightingSingle;
				dynamicLightingMaterials_Single[i]->dynamicLightingFeatures = LightInfo::sgFeatures(i);
				dynamicLightingMaterials_Single[i]->init(mSGData, mVertFlags);

				// cube mapping exceeds 8 tex params, and 2.0 needed...
				if((i > LightInfo::sgFull) && (GFX->getPixelShaderVersion() >= 2.0))
				{
					// build the piggybacked dual light materials...
					dynamicLightingMaterials_Dual[i] = new MatInstance(*mMaterial);
					dynamicLightingMaterials_Dual[i]->materialType = mtDynamicLightingDual;
					dynamicLightingMaterials_Dual[i]->dynamicLightingFeatures = LightInfo::sgFeatures(i);
					dynamicLightingMaterials_Dual[i]->init(mSGData, mVertFlags);
				}
			}

			dynamicLightingMaskMaterial = new MatInstance(*mMaterial);
			dynamicLightingMaskMaterial->materialType = mtDynamicLightingMask;
			dynamicLightingMaskMaterial->dynamicLightingFeatures = LightInfo::sgNoCube;
			dynamicLightingMaskMaterial->init(mSGData, mVertFlags);
		}
	}
}

As I said, I'm not sure why it works, but it does. I have a feeling somewhere in the lighting code, it's keeping the old material. Humm that makes sence now I type it out. So this will clear all the old stuff and recreate it with the new material. Which is what we wanted.
#6
07/21/2007 (5:40 am)
@Simon: I implemented your version of MatInstance::reInit and it works great. I'm no expert on materials so I can't speak to whether this is the best way to handlet this or not. I did have to make one change, however.

I moved:
mPasses.clear();
processMaterial();
mProcessedMaterial = true;

From the beginning of the method to the very end of the method. Without that I would have to call reInit twice to get the texture to show correctly. If I leave the first 3 lines at the start of the function the material turns white.
#7
10/24/2007 (12:14 pm)
Hi all, I implemented this resource in TGEA 1.0.3 and I am having trouble when testing it doesnt like the syntax $Game::Player.setSkinName(Orc_Material,OrcEye);
and I get an ambig err message like: cannot fine objet " " when attempting to call function setSkinName()

Could I get any suggestions for alternative testing methods? I've tried calling this function from the console as well as from within the server/scripts/game.cs ::createPlayer(). Thanks in advance
#8
12/30/2007 (3:45 pm)
Happy New year people!

I've been trying to implement this, but I'm not succeding 100%.
Thanks to the changes by Simon Duggan and Matt Kronyak, the skin can be seen, without those the shapes simply did not seem to render.
However any flags on the basic materials still cause them not to render.
For instance glow and emissive. Though if the material is automapped (using mapto) to the shape it does work. So it is only when changing the skin it doesn't work.
Any ideas?

wildCAT,
You could for instance try changing the skin right after the orc is created.
if you find the line that spawns.
for example:
%orc = AIPlayer::spawnOnPath("NPC",%path);
then add right after that:
%orc.setSkinName(Orc_Material,OrcEye);
HTH
#9
12/31/2007 (10:33 pm)
If someone can get this to work can you give an example of how you did the materials and function?
#10
01/14/2008 (12:16 pm)
stupid forum ate my post...

the point of it was to say that after debugging for a bit I found the simplest way to get this to work is if you put the baseTex[0] with extention included in quotation marks in place of the oldMaterial it works in Maya exported objects at least.

Here is my example.
%newObj.setSkinName("bluePrint.png",bluePrint_invalid);
#11
02/26/2008 (4:58 am)
Will I be able to swop from Normal Material (new Material(blah) ) to CustomMaterial( new CustomMaterial(bleh) )? I'm trying to implement cloaking into Solar Battles.
#12
07/06/2008 (1:57 pm)
Has anyone gotten this to work in TGEA 1.7.1? No luck on this end...
#13
08/16/2008 (7:44 am)
This works with TGEA 1.7.1 if you do everything but change MatInstance::reInit. Just leave MatInstance::reInit as it is and it should work.

I have a slightly different implementation but had no problems porting over.