Game Development Community

Materials seperated from meshes for TSStatics in T3D

by Ian Omroth Hardingham · 11/17/2009 (1:33 pm) · 7 comments

One of the stranger T3D design decisions is that if you have many objects which use the same model, they also have to use the same texture. No longer with my handy resource! Also, you can change a TSStatic's material easily with a simple console command.

WARNING: this is currently un-networked, and will only work for client-servers (ie single player games). I only need this for a single player thing, but it will be the work of 5 minutes for someone knowledgeable to network it, hopefully someone will do that and post a new resource.

I've kept in my "hax" tags for the most part, so you can easily find where you've added my changes.

Alright, let's get on with the killing.

In T3D/TSStatic.h:

Find

TSShapeInstance*  mShapeInstance;

And change to:

public:
   TSShapeInstance*  mShapeInstance;
protected:

After:

const Vector<S32>& getLOSDetails() const { return mLOSDetails; }

Add:

//hax
   bool mUseCustomMaterial;
	String mCustomMaterialName;
	bool mLoadedCustomMaterial;
	void loadCustomMat(String& name);
	void setCustomMat(String& name);
	//hax


In T3D/TSStatic.cpp:

after:

mCollisionType = CollisionMesh;

put:

//hax
   mUseCustomMaterial = false; // note: put this to true if you always want to use custom materials
	mCustomMaterialName = String("");
	mLoadedCustomMaterial = false;
	//hax


After:

endGroup("Debug");

Put:

//hax
   addGroup("CustomMat");
	addField("useCustomMaterial", TypeBool, Offset(mUseCustomMaterial, TSStatic), "use custom material?");
	addField("customMaterialName", TypeRealString, Offset(mCustomMaterialName, TSStatic), "custom material name");
	endGroup("CustomMat");
	//hax

After:

if ( mPlayAmbient && mAmbientThread && isServerObject() )
      mShapeInstance->advanceTime( getTickSec(), mAmbientThread );

Put:

//hax
	// for some reason when the object is actually created we don't have a material list
	// this isn't great, but it should work
	if (mUseCustomMaterial && !mLoadedCustomMaterial)
	{
		// if we have a "" custom material, we will just pretend we loaded it so we don't keep checking

		if (mCustomMaterialName.compare(String("")) == 0)
		{
			mLoadedCustomMaterial = true;
		}
		else
		{

			TSStatic* stat = dynamic_cast<TSStatic*>(getClientObject());

			if (stat && stat->mShapeInstance->mMaterialList)
			{
				mLoadedCustomMaterial = true;

				stat->loadCustomMat(mCustomMaterialName);
			}
		}
	}
	//hax

Just below that, add these functions:

//hax

void TSStatic::loadCustomMat(String& name)
{
	Material *newMat = dynamic_cast<Material*>(Sim::findObject(name));

	// make sure we have our own mat list.  This actually tests within the function to see if we've got our own already, and if so, doesn't clone.
	mShapeInstance->cloneMaterialList();

	TSMaterialList* matList = mShapeInstance->mMaterialList;

	int matListSize = matList->getMaterialNameList().size();

	if (matListSize > 0)
	{
		// WARNING: this currently only works for the first material instance... when I work out how I want to handle more, I'll change it
		delete [] matList->mMatInstList[0];
		matList->mMatInstList[0] = newMat->createMatInstance();

		const GFXVertexFormat *flags = getGFXVertexFormat<GFXVertexPNTTB>();
		FeatureSet features = MATMGR->getDefaultFeatures();
		matList->getMaterialInst(0)->init( features, flags );
	}
}

void TSStatic::setCustomMat(String& name)
{
	TSStatic* stat = dynamic_cast<TSStatic*>(getClientObject());

	if (stat && stat->mShapeInstance->mMaterialList)
	{
		stat->loadCustomMat(name);

		mUseCustomMaterial = true;
		mCustomMaterialName = name;
		mLoadedCustomMaterial = true;
	}
}


ConsoleMethod( TSStatic, setCustomMat, void, 3, 3, "mat name")
{
	String name = String(argv[2]);
	object->setCustomMat(name);	
}

//hax


After:

ConsoleMethod( TSStatic, changeMaterial, void, 5, 5, "(mapTo, fromMaterial, ToMaterial)")
{
	TSStatic *obj = dynamic_cast< TSStatic* > ( object );

	if(obj)
	{

Put:

//hax
		if (obj->mUseCustomMaterial)
		{
			TSStatic* stat = obj;

			if (obj->getServerObject())
				stat = dynamic_cast< TSStatic* > (obj->getServerObject());

			String name = String(argv[4]);

			stat->setCustomMat(name);

			return;
		}
		//hax

In gametoolsmaterialEditorscriptsmaterialEditor.ed.cs:

After:

function SubMaterialSelector::onSelect( %this )
{

Put:

//hax
if (MaterialEditorGui.currentObject.useCustomMaterial)
{
MaterialEditorGui.prepareActiveMaterial( MaterialEditorGui.currentObject.customMaterialName.getId() );
return;
}
//hax[/code]

After:

function MaterialEditorGui::showMaterialChangeSaveDialog( %this, %toMaterial )
{
   %fromMaterial = MaterialEditorGui.currentMaterial;

Put:

//hax
   
   // we don't annoyingly ask the question if we're using customMaterials
   if (MaterialEditorGui.currentObject.useCustomMaterial)
   {
		MaterialEditorGui.changeMaterial(%fromMaterial, %toMaterial);
		return;
   }
   
   //hax

After:

MaterialEditorGui.currentObject.changeMaterial( %materialTarget, %fromMaterial.getName(), %toMaterial.getName() );

Put:

//hax
      %customMat = MaterialEditorGui.currentObject.useCustomMaterial;
      if (%customMat)
      {
			error("custom mat change - not doing all the other faff");
			
			MaterialEditorGui.submitUndo( %action );
			MaterialEditorGui.prepareActiveMaterial( %toMaterial, true );
			
			return;
      }
      //hax


That's it!

Instructions for use:

All you need to do is set UseCustomMaterial to true in the Inspector for any TSStatics you want to use custom materials rather than their mesh's materials. After that, everything works as normal.

For the other feature, you can also now use %yourScriptObject.setCustomMat(%matName) on any TSStatic to change the material at game-time.

Let me know how this works for you.

#1
11/17/2009 (2:42 pm)
well, mr Hardingham,
this is a wonderful little resource you've put up here,
very useful,
thanks.
#2
11/17/2009 (9:37 pm)
Nice resource ! Thanks !
#3
11/18/2009 (1:04 pm)
Very handy indeed!

Does anyone know how to make it networkable? :)
#4
11/19/2009 (12:18 pm)
As i see it, you only need to hook up TSStatics pack/unpack methods with the new variable(s).

Would help ya there, but i came up with a similar idea and will try to get it working (i need this feature for a multitude of screens that may share the model, but shouldnt share (all) textures).
Im using the TSShapeInstance to store some "override" materialList, which (if present in the given slot) replaces the regular material, thus giving both, the TSStatic and the ShapeBase this feature.
#5
11/20/2009 (7:12 pm)
yupp, that works and is networkable. Some small TOC-Bug at shutdown, besides that, you can add a second "set" of materials and switch (those defined) with the original material. Works for an arbitrary number of materials, you just need to know the name of texture you want it to map to.

If someone is interested, i'll either give some code to Ian, so he can make a nice resource of it, or publish it together with my version of the gui-texture-canvas.
#6
12/02/2009 (7:50 am)
That would be a wonderful addiction to stock T3D...
#7
12/14/2009 (11:06 am)
@Lethal Concept
That'd be great, just the thing we need for our game.