Changing Interior(dif) textures on the fly - TGE
by Duncan Gray · 09/01/2006 (3:03 pm) · 21 comments
Download Code File
I needed this solution for putting street names on roads for my current game. A search of the forums showed that other people needed a similar solution but had not found an answer. There were conflicting reports about the problems of changing textures affecting ALL interiors which use that texture.
I'm happy to report that none of that is true. Here is why.
bool InteriorInstance::onAdd() is where the material gets instanciated. Near the end of this method you will find...
That call creates a new instance of the material list which is unique to that interior instance. It does, however, copy the list of material pointers passed as a parameter to the constructor. But if you take any Interior instance and point one of it's material pointers to another texture, it will use that new material for that interior instance only.
It does not make unique copies of the material. It's just a matter of taking the current Interior instance and pointing an item in it's material list to some other material.
Try it and see. Here is how to use it.
Unzip the files into your game/interior folder. Do a rebuild all. Yes, rebuild ALL.
To change a skin when the object is first created, do the following. (in script)
%obj = new InteriorInstance() {
position = %myposition;
rotation = %rot;
scale = "1 1 1";
OldSkin = "dirty_old_skin";
NewSkin = "Shiny_new_skin";
interiorFile = "~/data/interiors/awesome.dif";
};
If you then want to update the skin on the fly, during a game, perhaps after an explosion...
%obj.setSkinBase("dirty_old_skin", "Shiny_new_skin" );
You can then swop back again by doing...
%obj.setSkinBase("dirty_old_skin", "dirty_old_skin" );
The format of the setSkinBase call is to have the 1st parameter as the name of the skin you wish to replace and the second parameter must be the skin/texture name you want it replaced with.
Texture/skin names must not have the path or the file extension attached.
i.e. just OLD_BROWN_LEATHER not ~/data/bla/OLD_BROWN_LEATHER.jpg
Yes, it's fully networked as well.
I needed this solution for putting street names on roads for my current game. A search of the forums showed that other people needed a similar solution but had not found an answer. There were conflicting reports about the problems of changing textures affecting ALL interiors which use that texture.
I'm happy to report that none of that is true. Here is why.
bool InteriorInstance::onAdd() is where the material gets instanciated. Near the end of this method you will find...
// Install material list
mMaterialMaps.push_back(new MaterialList(pInterior->mMaterialList));That call creates a new instance of the material list which is unique to that interior instance. It does, however, copy the list of material pointers passed as a parameter to the constructor. But if you take any Interior instance and point one of it's material pointers to another texture, it will use that new material for that interior instance only.
It does not make unique copies of the material. It's just a matter of taking the current Interior instance and pointing an item in it's material list to some other material.
Try it and see. Here is how to use it.
Unzip the files into your game/interior folder. Do a rebuild all. Yes, rebuild ALL.
To change a skin when the object is first created, do the following. (in script)
%obj = new InteriorInstance() {
position = %myposition;
rotation = %rot;
scale = "1 1 1";
OldSkin = "dirty_old_skin";
NewSkin = "Shiny_new_skin";
interiorFile = "~/data/interiors/awesome.dif";
};
If you then want to update the skin on the fly, during a game, perhaps after an explosion...
%obj.setSkinBase("dirty_old_skin", "Shiny_new_skin" );
You can then swop back again by doing...
%obj.setSkinBase("dirty_old_skin", "dirty_old_skin" );
The format of the setSkinBase call is to have the 1st parameter as the name of the skin you wish to replace and the second parameter must be the skin/texture name you want it replaced with.
Texture/skin names must not have the path or the file extension attached.
i.e. just OLD_BROWN_LEATHER not ~/data/bla/OLD_BROWN_LEATHER.jpg
Yes, it's fully networked as well.
About the author
#2
08/29/2006 (7:19 am)
Works like a charm. Thanks!
#3
08/31/2006 (4:06 pm)
Awesome! I can think of a ton of different uses for this one! Thanks!
#4
I did a minor bug fix to the packUpdate section ( I had left out some brackets) It's best you use the update to avoid future problems.
Also added a return in the RenewOverlays() method as a early-out performance boost.
08/31/2006 (9:08 pm)
Thanks guys. I did a minor bug fix to the packUpdate section ( I had left out some brackets) It's best you use the update to avoid future problems.
Also added a return in the RenewOverlays() method as a early-out performance boost.
#5
can you give an example of how one might use it to switch the texture on a DIF between say three textures A, B, and then C ?
do you have a notion for the bandwidth usage of this ?
eg,
would it be feasible to use this to "re-skin" an entire dif ?
or say even every dif in a mission ?
09/01/2006 (4:35 pm)
this sounds fantastic duncan.can you give an example of how one might use it to switch the texture on a DIF between say three textures A, B, and then C ?
do you have a notion for the bandwidth usage of this ?
eg,
would it be feasible to use this to "re-skin" an entire dif ?
or say even every dif in a mission ?
#6
Bandwidth? if you're concerned about all the texture names. You could even extract the texture index and just send that to the client.
command which will instruct the client to loop through the texture list from group1 and swop it with those of group 2. That is why I added the decodeSkins() method which is where your would implement the above or other solution.
I do have a more complex solution which can isolate and swop a particular surface texture, but you gonna have to pay for that one. It also allows you to find out what texture is under the players feet so that you can do damage if he's walking on battery acid or lava or teleport him somewhere etc. It's done server side as well. (server does not load textures)
.
09/01/2006 (6:18 pm)
You would need to construct your dif within the limitions of this resource. i.e give some forthought to texture names and use a naming convention etc (keep the names short). I see no problem switching between three textures, unless I missed your point.Bandwidth? if you're concerned about all the texture names. You could even extract the texture index and just send that to the client.
Quote:Yes you can, but it would be silly to send 100 setSkinBase commands to 32 clients etc. Rather keep an array or vector list of textures groups on the client then send a single setSkinBase("group1","group2")
"re-skin" an entire dif
command which will instruct the client to loop through the texture list from group1 and swop it with those of group 2. That is why I added the decodeSkins() method which is where your would implement the above or other solution.
I do have a more complex solution which can isolate and swop a particular surface texture, but you gonna have to pay for that one. It also allows you to find out what texture is under the players feet so that you can do damage if he's walking on battery acid or lava or teleport him somewhere etc. It's done server side as well. (server does not load textures)
.
#7
BTW, very nice resource!!!
09/05/2006 (7:14 am)
Is it possible to change the skin of an interior object only on the client? So for example one client sees a red tower and another one sees a yellow tower?BTW, very nice resource!!!
#8
09/05/2006 (7:25 am)
Klaas - it may or may not be possible to do that 'right out of the box', but i'm sure it should be relatively easy to adapt this resource to do that.
#9
09/05/2006 (1:29 pm)
@Klaas, yep, it's like Orion said.
#10
Thanx for the info and FYI, local skin changes on the client are working 'right out of the box' :-)
But I have anoher problem, when i change the skin (on server or client), for example of the watertower in starter.fps, everything is ok when I'm near the object but from a distance the skin changes back to the original. Is this related to this resource or to the watertower dif itself?
Thanx,
Klaas
09/11/2006 (4:26 am)
Orion, Ducan,Thanx for the info and FYI, local skin changes on the client are working 'right out of the box' :-)
But I have anoher problem, when i change the skin (on server or client), for example of the watertower in starter.fps, everything is ok when I'm near the object but from a distance the skin changes back to the original. Is this related to this resource or to the watertower dif itself?
Thanx,
Klaas
#11
09/11/2006 (5:10 am)
Klaas - huh. does the water tower have LODs ? if so i'd guess that the resource is not reskinning the lower LODs, just the top one.
#12
Your are right, the problem was:
I replaced the return with a break and it is ok!
Thanx,
Klaas
09/11/2006 (6:51 am)
Orion,Your are right, the problem was:
Quote:
Also added a return in the RenewOverlays() method as a early-out performance boost.
I replaced the return with a break and it is ok!
Thanx,
Klaas
#13
09/11/2006 (8:48 am)
nice!
#14
09/11/2006 (2:00 pm)
good catch Klaas, updating zip accordingly
#15
10/08/2006 (11:19 am)
excellent!
#16
That doesn't work since after you call setSkinBase the first time, you don't update the mMaterialNames[j] string to point to "Shiny_new_skin" inside of renewOverlays(). So basically it will only work as follows:
%obj.setSkinBase("dirty_old_skin", "Shiny_new_skin" );
%obj.setSkinBase("dirty_old_skin", "dirty_old_skin" );
I'm sure it's misbehaving by just looking at the code, plus I ran it in our application. You probably want to:
i think that should do it...
11/29/2006 (9:57 pm)
Quote:
If you then want to update the skin on the fly, during a game, perhaps after an explosion...
%obj.setSkinBase("dirty_old_skin", "Shiny_new_skin" );
You can then swop back again by doing...
%obj.setSkinBase("Shiny_new_skin", "dirty_old_skin" );
That doesn't work since after you call setSkinBase the first time, you don't update the mMaterialNames[j] string to point to "Shiny_new_skin" inside of renewOverlays(). So basically it will only work as follows:
%obj.setSkinBase("dirty_old_skin", "Shiny_new_skin" );
%obj.setSkinBase("dirty_old_skin", "dirty_old_skin" );
I'm sure it's misbehaving by just looking at the code, plus I ran it in our application. You probably want to:
void InteriorInstance::renewOverlays()
{
for (U32 i = 0; i < mMaterialMaps.size(); i++)
{
MaterialList* pMatList = mMaterialMaps[i];
for (U32 j = 0; j < pMatList->mMaterialNames.size(); j++)
{
const char* pName = pMatList->mMaterialNames[j];
//if the org name is blank, then continue to next texture...
if (!pName || pName[0] == '[[60c20c6b66943]]')
{
continue;
}
const char *path = mInteriorRes.getFilePath();
if (dStricmp(mSkinOld, pName) == 0)
{
char newName[256];
dStrcpy(newName, path);
dStrcat(newName, "/");
dStrcat(newName, mSkinNew);
TextureHandle test = TextureHandle(newName, MeshTexture, false);
if (test.getGLName() != 0)
{
pMatList->mMaterials[j] = test;
//free memory for mMaterialNames[j] which pName refers to..
//don't need to check for NULL since we already did that...
delete [] pName;
//go ahead create new memory
//yeah..yeah we culd have used dStrdup, but the rest of materialList.cc
//followed c++ memory allocation, so I stuck with it..
pMatList->mMaterialNames[j] = new char[dStrlen(mSkinNew) + 1];
dStrcpy(pMatList->mMaterialNames[j], mSkinNew);
break;
}
}
}
}
}i think that should do it...
#17
I have been using it in it's current form by refering to the old texture name as reference, rather than replacing the texture name with the new name. Reason - it can be difficult to revert back to your original texture if you have multiple "Shiny new textures" on an interior.
But your implementation may suit your requirements better. Thanks for the feedback.
I'm using a much modified version in Token Frenzy to place/re-place textures on a road and traffic lights.
11/29/2006 (10:33 pm)
Yes that's correct. You would have to use%obj.setSkinBase("dirty_old_skin", "Shiny_new_skin" );
%obj.setSkinBase("dirty_old_skin", "dirty_old_skin" );I have been using it in it's current form by refering to the old texture name as reference, rather than replacing the texture name with the new name. Reason - it can be difficult to revert back to your original texture if you have multiple "Shiny new textures" on an interior.
But your implementation may suit your requirements better. Thanks for the feedback.
I'm using a much modified version in Token Frenzy to place/re-place textures on a road and traffic lights.
#19
sgLightMap.cc
..\engine\lightingSystem\sgLightMap.cc(364) : error C2039: 'getSurfaceZone' : is not a member of 'InteriorInstance'
../engine\interior/interiorInstance.h(45) : see declaration of 'InteriorInstance'
..\engine\lightingSystem\sgLightMap.cc(379) : error C2039: 'getSurfaceZone' : is not a member of 'InteriorInstance'
../engine\interior/interiorInstance.h(45) : see declaration of 'InteriorInstance'
sgLightManager.cc
interiorInstance.cc
..\engine\interior\interiorInstance.cc(910) : error C3861: 'installLights': identifier not found
..\engine\interior\interiorInstance.cc(967) : error C3861: 'uninstallLights': identifier not found
..\engine\interior\interiorInstance.cc(976) : error C2039: 'getNumLights' : is not a member of 'LightManager'
../engine\lightingSystem/sgLightManager.h(102) : see declaration of 'LightManager'
..\engine\interior\interiorInstance.cc(982) : error C2039: 'getLights' : is not a member of 'LightManager'
../engine\lightingSystem/sgLightManager.h(102) : see declaration of 'LightManager'
..\engine\interior\interiorInstance.cc(1016) : error C2660: 'Interior::renderLights' : function does not take 5 arguments
interiorDebug.cc
03/19/2008 (6:45 pm)
I am getting the following errors when rebuilding in TGE 1.5sgLightMap.cc
..\engine\lightingSystem\sgLightMap.cc(364) : error C2039: 'getSurfaceZone' : is not a member of 'InteriorInstance'
../engine\interior/interiorInstance.h(45) : see declaration of 'InteriorInstance'
..\engine\lightingSystem\sgLightMap.cc(379) : error C2039: 'getSurfaceZone' : is not a member of 'InteriorInstance'
../engine\interior/interiorInstance.h(45) : see declaration of 'InteriorInstance'
sgLightManager.cc
interiorInstance.cc
..\engine\interior\interiorInstance.cc(910) : error C3861: 'installLights': identifier not found
..\engine\interior\interiorInstance.cc(967) : error C3861: 'uninstallLights': identifier not found
..\engine\interior\interiorInstance.cc(976) : error C2039: 'getNumLights' : is not a member of 'LightManager'
../engine\lightingSystem/sgLightManager.h(102) : see declaration of 'LightManager'
..\engine\interior\interiorInstance.cc(982) : error C2039: 'getLights' : is not a member of 'LightManager'
../engine\lightingSystem/sgLightManager.h(102) : see declaration of 'LightManager'
..\engine\interior\interiorInstance.cc(1016) : error C2660: 'Interior::renderLights' : function does not take 5 arguments
interiorDebug.cc
#20
04/15/2008 (8:41 pm)
Russell, you will need to dif against the current interior files for Duncan's changes. This version is probably a little older by now, but using winmerge you should see the changes easily. 
Torque Owner Skylar Kelty
SkylarK
Got it to work with TLK with no changes jsyk