Game Development Community

dev|Pro Game Development Curriculum

TSE Interior material/texture swap in runtime

by Bryan Stroebel · 07/03/2006 (11:22 am) · 8 comments

The following mod lets you swap materials on interiors from the console. It is setup to work across the network. However, I don't have two TSE machines to test this. You can swap file for file or file for shader materials. For example, from the console I would get the interior id and run this:

1505.setSKinName(plating,orceye);
1505.setSKinName(plating,orcskin);

this would change the plating material on the orcbase from plating to orceye and orcskin. If you want to change it back you would type:

1505.setSkinName(plating,plating);

the first parameter is the name of the file you want to swap on the interior. The second parameter is the name of the new material. Even though we've changed materials, we still use the original name as the first parameter to change back. This makes it much easier.

BEWARE: Interiors only create one matInstance for each material, for each interior. Well, actually two becuase one is used for the outside rendering and the other is used for inside rendering(because inside has different lighting). So if you have multiple of the same interior in your mission it will swap the material on all interior instances in that mission. I didn't want to rewrite the rendering part of the interiors to keep separate material instances because it would drastically increase the memory required for the game to run. That's probably why they designed it that way. To get around this you could create a copy of the interior and skin it in your 3d software to use different file named materials.

BEWARE: All materials must map successfully when the mission loads or TSE will crash when swapping an unmapped material. This means if you can't swap a material if it shows this error in the console:

material unmapped: orc_ID1_skin

Here is the code starting from the console method.

Open interior/interiorInstance.cpp. Add this console method to the bottom of the file:
//BCS - swap material console function
ConsoleMethod( InteriorInstance, setSkinName, void, 4, 4, "(string material, string newMaterial)")
{
	object->setSkinName(argv[2],argv[3]);
}

The above console method calls the seSkinName method. Lets add that to the bottom of the interiorInstance.cpp file as well.
//BCS - added this method for metrial swapping
void InteriorInstance::setSkinName(const char* name,const char * newSkinName)
{
	//Assign the original material name and new material name to our stringhandle's
	//lfor network passage.
   if (!isGhost()) {
      if (name[0] != '[[60c1e3d6dfe91]]') {
         if (name[0] == StringTagPrefixByte) {
            mSkinNameHandle = StringHandle(U32(dAtoi(name + 1)));
         }
         else {
            mSkinNameHandle = StringHandle(name);
         }
      }
      else {
         mSkinNameHandle = StringHandle();
      }
      if (newSkinName[0] != '[[60c1e3d6dfe91]]') {
         if (newSkinName[0] == StringTagPrefixByte) {
            mNewSkinNameHandle = StringHandle(U32(dAtoi(newSkinName + 1)));
         }
         else {
            mNewSkinNameHandle = StringHandle(newSkinName);
         }
      }
      else {
         mNewSkinNameHandle = StringHandle();
      }
      setMaskBits(SkinMask);
   }
}

next we need to add our stringhandles from above to the InteriorInstance::packUpdate method. Look at the ELSE part of the first IF statement and REMOVE the following lines:
if (stream->writeFlag(mask & SkinBaseMask))
         stream->writeString(mSkinBase);
and ADD these lines in the same place as the above:
//BCS - Swap Skin update
		if (stream->writeFlag(mask & SkinMask)) {
			c->packStringHandleU(stream, mSkinNameHandle);
			c->packStringHandleU(stream, mNewSkinNameHandle);
		}

Next, find the InteriorInstance::unpackUpdate void and REMOVE the following lines:
if (stream->readFlag()) {
         mSkinBase = stream->readSTString();
         renewOverlays();
      }
ADD these lines in the same spot you removed the previous lines:
//BCS - added this for Interior material swap
		if (stream->readFlag()) {  // SkinMask
			StringHandle skinDesiredNameHandle = c->unpackStringHandleU(stream);
			StringHandle newskinDesiredNameHandle = c->unpackStringHandleU(stream);
			if (mSkinNameHandle != newskinDesiredNameHandle) {
				mSkinNameHandle = newskinDesiredNameHandle;
				reSkin(skinDesiredNameHandle,newskinDesiredNameHandle);
				if (mSkinNameHandle.isValidString()) {
					mSkinHash = _StringTable::hashString(mSkinNameHandle.getString());
				}
			}
		}


Notice that the previous unpack runs the reSkin command. Lets add that to the bottom of this file. This is the command that actually does the reskinning.
//BCS - reskins interior instances.
void InteriorInstance::reSkin(StringHandle& BaseHandle,StringHandle& newBaseHandle)
{
   const char* BaseName = NULL;
   const char* newBaseName = NULL;
   if (BaseHandle.isValidString()) {
      BaseName = BaseHandle.getString();
      if (BaseName == NULL) {
         return;
      }
   }
   if (newBaseHandle.isValidString()) {
      newBaseName = newBaseHandle.getString();
      if (newBaseName == NULL) {
         return;
      }
   }

   for (U32 a = 0; a < mInteriorRes->getNumDetailLevels(); a++) {
	   Interior* pInterior = mInteriorRes->getDetailLevel(a);
		for( U32 i=0; i<pInterior->getNumZones(); i++ )
		{
			for( U32 j=0; j<pInterior->mZoneRNList[i].renderNodeList.size(); j++ )
			{
				Interior::RenderNode &node = pInterior->mZoneRNList[i].renderNodeList[j];
				const char* pName = node.MaterialName;
				if(!dStricmp(pName,BaseName))
				{
					node.matInst->swapMaterial(newBaseName);
				}
			}
		}
	}
}

now we need to add a couple includes to the beginning of the file:
//BCS - added for material swapping
#include "materials/material.h"
#include "materials/matInstance.h"

look for this method and comment the code out of it. It is old and the reSkin method takes it's place. If you want you can remove it and all references to it.
void InteriorInstance::setSkinBase(const char* newBase)
{
   //if (dStricmp(mSkinBase, newBase) == 0)
   //  return;

   //mSkinBase = StringTable->insert(newBase);

   //if (isServerObject())
   //   setMaskBits(SkinBaseMask);
   //else
   //   renewOverlays();
}
SAVE THIS FILE.

Now lets make changes to the interior/interiorInstance.h file. We need to add the methods and variables that are being used in the above commands.

Add this to the beginning of the file around line 37 or so:
//BCS - added for swap material
#ifndef _NETSTRINGTABLE_H_
#include "sim/netStringTable.h"
#endif

add this in the public section at around line 104 or so:
//BCS - changed for material swapping
   void reSkin(StringHandle& BaseHandle,StringHandle& newBaseHandle);

   //BCS - added second parameter for swap material
   void setSkinName(const char *,const char *);

Now add these variables to the protected section at around line 125
//BCS - added for material swap ****
   U32 mSkinHash;
   StringHandle     mSkinNameHandle;
   StringHandle     mNewSkinNameHandle;
   //**********************************

now we need to change an enum value. Look for enum UpdateMaskBits and change skinBaseMask to skinMask. should look like this
SkinMask   = BIT(11),
I changed this to match the naming scheme for reskinning shapebase objects.

We need to make a change to material.h. I needed to keep the materials original name in a variable in the render node so it will function like the other reSKin methods for shapes. This way we would always use the original material name to perform the swapping on. Even if we've already swapped.

Open interior.h and look for struct RenderNode. Add this right above RenderNode():
const char* MaterialName;
SAVE THIS FILE.

now open interior/interior.cpp and look for void Interior::cloneMatInstances(). Add this to the end of the void right before the last }. This sets the orginal material name to the variable we created in the previous step.
//BCS - added for interior material swap. assigns material name to rendernode->materialname
	for( U32 k=0; k<getNumZones(); k++ )
	{
		for( U32 l=0; l<mZoneRNList[k].renderNodeList.size(); l++ )
		{
			RenderNode &node = mZoneRNList[k].renderNodeList[l];
			Material* mymat = node.matInst->getMaterial();
			if(mymat)
			{
				node.MaterialName = mymat->getName();
			}
			mymat = NULL;
		}
	}


now open materials/matInstance.cpp and add the following method to the end 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;
}
This method was added because I couldn't change mMaterial from the interiorInstance method. It is protected. I left it protected. Besides, you could really use this method to swap any material in any material instance.
SAVE THIS FILE

Open materials/matInstance.h. add the following to the public area of the class around line 100:
//BCS - added for material swap
	bool swapMaterial(const char*);
SAVE THIS FILE AND COMPILE.

You are done.

#1
08/10/2006 (8:12 pm)
Any updates for this code by chance? I am using the TSStatic portion and it doesn't seem to be working as needed
#2
08/13/2006 (7:01 pm)
Excellent stuff
#3
08/21/2006 (9:19 am)
Can this also be used for TGE?
#4
09/06/2006 (10:48 am)
This has been updated. It will not work on TGE.
#5
09/08/2006 (5:52 pm)
Looks like this might have some issues with MS4, as I'm having some crashes, but haven't looked deeper yet
#6
04/30/2007 (8:53 am)
Has anyone implemented this with TGEA 1.0?
#7
07/24/2008 (9:09 am)
Works in TGEA 1.0.3 and 1.7.1.
#8
08/24/2009 (7:31 pm)
Don't know if this thread is actually still active, but could anybody help with a port for 1.8.1?

i have it compiling with no errors, but when i swap the interior material at runtime, it gets swapped with nothing, all that shows up is a white map of nothing. The same code works in 1.7.1 btw.

Any pointers??

thx-in-advance
seb!