TGEA Dynamic Skins & Material Swapping
by Gareth Fouche · 02/28/2008 (8:41 am) · 104 comments
Download Code File
Alright, this resource is based on the idea of this resource :
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=12197
The basic idea is to take a base texture and blend on a couple of overlay textures, so you can have tattoos, different eye colours, decals on cars, etc.
Since I am porting my project to TGEA and I make use of this resource I decided to port it over. Now, there are some differences. The key strength of TGEA is it's use of modern gfx techniques and tricks such as shaders and the like. After some discussion on the forum I decided to make use of these strengths, so the technique uses shader based blending of textures, NOT blitting the layers to a base texture.
The advantage to this is texture memory efficiency. Lets say you have 5 face textures, 5 tattoos and 5 eye colors. If you were to have a scene with all combinations on the screen using the TGE method of texture blitting it would result in 5X5X5 = 125 unique textures to render. This would put strain on even modern gfx cards I reckon.
Now, the shader method doesn't create new textures, it simple blends existing ones on the gfx card. So this means the same scene would need 5+5+5 = 15 textures on the gfx card. Much better. :D
The trade off though is that you can blend fewer textures together. Using the older method, even if you blended 20 textures together, it results in only 1 unique texture. As long as your player is willing to wait through that initial creation step it isn't an issue. With the shader method, to blend 20 textures would require 20 texture units. That kind of thing might be reasonable in the future but I don't think it is right now. Older shader models can handle about 4 and newer models about 8 (or more for the latest models). The other issue is that if you use up all your texture units blending texture layers together you won't have any available for bump mapping and the like. You may need to use another pass or something. Personally, I'd stick to about 4 layers, if you plan to make use of things like specular mapping you are probably aiming for at least shader model 2.0, so that should give you enough texture registers left to do special effects comfortably. The real trick is the same as with all shaders, to tune it to YOUR target platform.
Alright, so with that out the way, let me explain the theory. It'll be easier to implement and use if you understand what I'm attempting to do. More experienced devs can skip this probably.
TGEA operates differently to TGE in order to support shaders in a nice abstract manner. The most important object is probably the Material, which is a structure storing data about...well, your material. It stores data about the textures used, the shader used to render the material and assorted other effect data. The important thing about this structure is it stores data COMMON to all shapes that reference it. That means that if you change the Material, all shapes that reference it will also be changed. Paint a big red X on it's texture and all shapes that use that material will have a big red X on them.
Then we get down to the Material Instance, the "MatInstance". From the docs :
"Materials can be placed on any type of surface in the TGE. Since different types of surfaces have different underlying geometry and lighting data, they need to generate different types of shaders even though the Material may be the same. The mapping of a material to different types of geometry is handled through a material instance (MatInstance)."
Right, so MatInstances are a bridge of a sorts between specific shapes and the Material they use. They store data unique to each individual shape. If you have 5 shapes using Material X, there will be 1 Material X object and 5 MatInstances pointing to that material.
Now, most programmers who've worked with the torque engine(s) a bit have encountered Shapebase. It is the root (well, sorta) of objects that have a shape, as the name implies. Now, shapebase has an object representing an instance of a shape called TSShapeInstance. This is to TSShape what MatInstance is to Material. A specific instance of a 3D shape that belongs to us, whereas TSShape is the shared data. TSShapeInstance also keeps a list of all the MatInstances used by our specific instance of a shape. Thats important. Because it means we can change the list without changing other shapes material details.
Alright, so if we examine those details we can see the main problem with creating a dynamic skin. If you change a material, you will change that material for all shapes using it. Which is kinda the opposite of what we want, we want individuals to be customized with unique combinations of details. But we can't give our shape a new texture unless it is through a material, since materials are needed to tell TGEA how to render the shape.
So the basic steps we will need to do are :
- Create a new material which represents our dynamic texture.
- Find the MatInstance in our shapes list that points at the current material being used.
- replace that MatInstance with one that points to our new material.
Simple enough, conceptually.
Right, to start off with, we need to be able to do step 1, create a new material. Now there are two ways you can go about this. You could precreate the material in the script files, one for each of the possible combinations, then simply pass it in to a Reskin feature. Bleh. Too much labor. The better way is the programmatically create it, on the fly. But materials are a bit complex, they have shaders and different types of values that can be set, values which could be different for different projects (in other words I can't pre-code them for you ;) ) and we don't want to just default that info. So what we need is a way to create a dynamic skin from a template. Ie, to be able to make a copy of an existing material, then just fiddle with the textures in our copy. We need a copy constructor for our materials :
In material.h, above this line :
put :
In material.cpp, just add this function to the end of the file :
In CustomMaterial.h, above this line :
put this :
In CustomMaterial.cpp, just add this function to the bottom of the file :
Ok, this will allow us to make copies of our texture. Note, if you didn't do it this way, when you said NewMateriaA = OldMaterial you'd copy ALL the details of OldMaterial, including simobject IDs. Then when you tried to register your new material it would bomb because a material with that ID is already registered.
Alright, now that we can copy materials, what materials do we copy? Well, we could copy the material that is already in that slot, but that wouldn't work because we can have a variable number of skins, and the existing materials shader might not know how to handle X textures. So we need a little setup.
First, in script we define a number of materials. One for each possible number of layers we can have in our dynamic skin. I've gone with 4. Put them in a convenient material definition script file, "materials.cs" in the "data\shapes\player\" for example. Just add them at the bottom :
And then the shader defs these point to, again, put them somewhere convenient like "shaders.cs" in "client\scripts\", at the bottom of the file :
I've included the shader files these definitions point to, drop them into a "shaders\DynamicSkin\" directory off your root game directory. Note that your shaders don't have to be exactly like I've defined them, in fact my intent is that you customize them for your needs. Read them over, they are easy to extrapolate so you can write them for however many layers you need.
Now I wanted a nice flexible way for the code to know which material corresponds to what template so I put in some Prefs, they go in "client\prefs.cs", add them to the end :
If you add in more dynamic material templates don't forget to add in the prefs so the code knows what they are called.
Right, so hopefully you have the basic idea. You, the game designer, setup these material templates. When you create a new dynamic texture that blends 3 textures, the code is going to make a copy of the 3 texture Material you predefined above and then replace the textures with the ones we want, using our nice copy constructor.
But creating our material is not enough. The shape needs to use it. Before I continue, I should discuss "Texture Tags". I don't know what it is officially called but it is pretty much the name you give to a material when you are mapping it to geometry in your art program. Your texture might have a name like "HairG1558.jpg" on file but in your art program you call it "Hair".
So you have these Texture Tags and we are going to use them to our advantage. Basically, I'm going to use them to reference the material we want to replace.
First add this function to TSMaterialList . What it does is replace the matInstance at an index in the list with another one, pointing at a different material. We will find out the index in another function by examining the texture tags to determine which one matches the one we want to replace. Then we will call this function with the material we want to use instead.
At the top of TSShape.h, after this :
add :
In TSShape.h, in TSMaterialList class definition, above this :
put this :
In TSMaterialList.cpp, just add the function to the end :
To build a dynamic material we need a list which stores the texture names to be blended together. In fact, we keep that list in an object representing the dynamic texture as a whole, then keep a list of THOSE objects, one for each dynamic texture we have. This is done in shapebase and TSShapeInstance :
In TSShapeInstance.h, after this section :
add :
In shapebase.h, after this :
put :
In Shapebase.cpp, add functions at bottom of file :
Still in shapebase.cpp, add a ClearDynamicSkins() function call to the end of the destructor (~Shapebase() ) like so :
Make sure we ghost dynamic skin data across to the client.
In shapebase.h :
We need to add a new bitmask after the sound mask, like so.
Change :
to :
further down in the same file, shapebase.h, add this to the end of the header, above the close brackets "};" :
(A note, in the PackUpdate/UnpackUpdate, the order of these reads and writes in relation to the other calls in the function is VERY important. If you have problems where your stage doesn't want to finish loading this is a good place to check. Speaking from experience ;) )
In shapebase.cpp :
In PackUpdate(), inside the initial mask if statement ("if (mask & InitialUpdateMask)...") add in code like so :
Change :
to :
further down in the same function, change :
to :
then still further down change :
to :
then in UnpackUpdate() :
change :
to :
Then add this function at the bottom of the same .cpp file :
The UpdateDynamicSkins call is so that you can batch the networking updates of layers. Once you have setup all your layers to blend properly in script, call UpdateDynamicSkins and the data will be sent off to the client all at once.
Add in some accessors and the console methods which will allow script to call them. The basic idea is you reset a dynamic skin to a base texture, then add blend layers on top of that. Then call UpdateDynamicSkins to get the server to update the client.
In Shapebase. h, after this line :
put this:
in shapebase.cpp, add these to the end :
Right, on to the function which does most of the work, SetDynamicSkin. This takes one of those objects representing a dynamic skin and builds us a nice new CustomMaterial from it's texture list, copying the appropriate base template Material and substituting data in.
In TSShapeInstance.h, after this line:
put :
In TSShapeInstance.cpp, add to the end :
Note that it attempts to find the material before assigning it. Which seems to go against what I said before, about how our dynamic materials aren't shared. Let me explain. See, one of the strengths of TGEA is it's batched rendering. If it can it will attempt to render objects with the same material together, to decrease gpu overhead. Nice. But dynamic textures will remove that benefit, since by definition they are unique. But actually, they don't have to be, do they? You could have two players with the same face, eye colour and tattoo. Or two cars with the same decals on. It's less likely, but still possible. So we want to make it so that this possibility is handled by the batching system. The way I do that is by allowing you to pass in the dynamic material name. The intention is that you pass in a name which uniquely identifies that material combination.
For example, if your NPC has a dynamic material which uses base texture FaceA.jpg and has TattooB.jpg blended over it, you could pass in "dynamic_mtl_FaceA_TattooB" as the name. If you then set another NPC to using this same combination, pass in the same name and it will be shared between the two. if you later decide to alter the second NPC, if you want it to use FaceB.jpg but still TattooB.jpg, pass in "dynamic_mtl_FaceB_TattooB". The underlying system will then make you a new dynamic material while leaving the one used by the first NPC alone. If you add in a 3rd NPC who uses the first dynamic material, it will then be shared by them, etc etc. The advantages of batching combined with dynamic materials :D The only trick is to make sure your names uniquely identify the combinations. I just append the texture names and underscores, as shown above.
(Note that I haven't got any bump mapping or anything like that in these scripts. They're easy to add, just add them as another texture layer. The trick will just be making sure each of the possible shaders handle the bump mapping. Check the example shaders that come with TGEA, should be trivial.)
Here are some demo scripts you can use to test the system. Unzip the test textures I have included in the rar file into the "\data\shapes\player\" directory, then...
in server\scripts\game.cs, add this to the bottom :
NOTE : PLEASE REMEMBER YOU NEED TO CHANGE THE TEXTURE PATHS IN THE ABOVE SCRIPT TO MATCH YOUR FILES.
in client\scripts\dynamic.bind, add to the bottom :
If you are already using the g and h keys, change the binding to something else. ;)
Don't forget to delete config.cs to get the bindings to take. In game press "g" to cycle base layers, "h" to cycle overlay blend layers.
I haven't tested this code in a networked environment (I have only the single PC at home) but it should be good, I've handled the ghosting. If you spot any errors or encounter problems let me know and I'll take a look. Also, since default TGEA hasn't got a good Stronghold-like mission you will need to implement that first to test this (there is a stronghold FPS starter port I believe), unless you are a decent coder. I've tested this against a fresh TGEA-AFX 1.03 build myself.
Enjoy.
Alright, this resource is based on the idea of this resource :
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=12197
The basic idea is to take a base texture and blend on a couple of overlay textures, so you can have tattoos, different eye colours, decals on cars, etc.
Since I am porting my project to TGEA and I make use of this resource I decided to port it over. Now, there are some differences. The key strength of TGEA is it's use of modern gfx techniques and tricks such as shaders and the like. After some discussion on the forum I decided to make use of these strengths, so the technique uses shader based blending of textures, NOT blitting the layers to a base texture.
The advantage to this is texture memory efficiency. Lets say you have 5 face textures, 5 tattoos and 5 eye colors. If you were to have a scene with all combinations on the screen using the TGE method of texture blitting it would result in 5X5X5 = 125 unique textures to render. This would put strain on even modern gfx cards I reckon.
Now, the shader method doesn't create new textures, it simple blends existing ones on the gfx card. So this means the same scene would need 5+5+5 = 15 textures on the gfx card. Much better. :D
The trade off though is that you can blend fewer textures together. Using the older method, even if you blended 20 textures together, it results in only 1 unique texture. As long as your player is willing to wait through that initial creation step it isn't an issue. With the shader method, to blend 20 textures would require 20 texture units. That kind of thing might be reasonable in the future but I don't think it is right now. Older shader models can handle about 4 and newer models about 8 (or more for the latest models). The other issue is that if you use up all your texture units blending texture layers together you won't have any available for bump mapping and the like. You may need to use another pass or something. Personally, I'd stick to about 4 layers, if you plan to make use of things like specular mapping you are probably aiming for at least shader model 2.0, so that should give you enough texture registers left to do special effects comfortably. The real trick is the same as with all shaders, to tune it to YOUR target platform.
Alright, so with that out the way, let me explain the theory. It'll be easier to implement and use if you understand what I'm attempting to do. More experienced devs can skip this probably.
TGEA operates differently to TGE in order to support shaders in a nice abstract manner. The most important object is probably the Material, which is a structure storing data about...well, your material. It stores data about the textures used, the shader used to render the material and assorted other effect data. The important thing about this structure is it stores data COMMON to all shapes that reference it. That means that if you change the Material, all shapes that reference it will also be changed. Paint a big red X on it's texture and all shapes that use that material will have a big red X on them.
Then we get down to the Material Instance, the "MatInstance". From the docs :
"Materials can be placed on any type of surface in the TGE. Since different types of surfaces have different underlying geometry and lighting data, they need to generate different types of shaders even though the Material may be the same. The mapping of a material to different types of geometry is handled through a material instance (MatInstance)."
Right, so MatInstances are a bridge of a sorts between specific shapes and the Material they use. They store data unique to each individual shape. If you have 5 shapes using Material X, there will be 1 Material X object and 5 MatInstances pointing to that material.
Now, most programmers who've worked with the torque engine(s) a bit have encountered Shapebase. It is the root (well, sorta) of objects that have a shape, as the name implies. Now, shapebase has an object representing an instance of a shape called TSShapeInstance. This is to TSShape what MatInstance is to Material. A specific instance of a 3D shape that belongs to us, whereas TSShape is the shared data. TSShapeInstance also keeps a list of all the MatInstances used by our specific instance of a shape. Thats important. Because it means we can change the list without changing other shapes material details.
Alright, so if we examine those details we can see the main problem with creating a dynamic skin. If you change a material, you will change that material for all shapes using it. Which is kinda the opposite of what we want, we want individuals to be customized with unique combinations of details. But we can't give our shape a new texture unless it is through a material, since materials are needed to tell TGEA how to render the shape.
So the basic steps we will need to do are :
- Create a new material which represents our dynamic texture.
- Find the MatInstance in our shapes list that points at the current material being used.
- replace that MatInstance with one that points to our new material.
Simple enough, conceptually.
Right, to start off with, we need to be able to do step 1, create a new material. Now there are two ways you can go about this. You could precreate the material in the script files, one for each of the possible combinations, then simply pass it in to a Reskin feature. Bleh. Too much labor. The better way is the programmatically create it, on the fly. But materials are a bit complex, they have shaders and different types of values that can be set, values which could be different for different projects (in other words I can't pre-code them for you ;) ) and we don't want to just default that info. So what we need is a way to create a dynamic skin from a template. Ie, to be able to make a copy of an existing material, then just fiddle with the textures in our copy. We need a copy constructor for our materials :
In material.h, above this line :
DECLARE_CONOBJECT(Material);
put :
Material &operator=( const Material &material );
In material.cpp, just add this function to the end of the file :
Material & Material::operator=( const Material &material )
{
// tedious, but want to avoid copying simobject data, which a straight memcopy would do.
// if you do that you get the simobject ID and then it complains when you try to register it.
for(S32 i = 0; i < MAX_STAGES; i++)
{
baseTexFilename[i] = material.baseTexFilename[i];
detailFilename[i] = material.detailFilename[i];
bumpFilename[i] = material.bumpFilename[i];
envFilename[i] = material.envFilename[i];
stages[i] = material.stages[i];
diffuse[i] = material.diffuse[i];
specular[i] = material.specular[i];
specularPower[i] = material.specularPower[i];
pixelSpecular[i] = material.pixelSpecular[i];
vertexSpecular[i] = material.vertexSpecular[i];
exposure[i] = material.exposure[i];
animFlags[i] = material.animFlags[i];
scrollDir[i] = material.scrollDir[i];
scrollSpeed[i] = material.scrollSpeed[i];
scrollOffset[i] = material.scrollOffset[i];
rotSpeed[i] = material.rotSpeed[i];
rotPivotOffset[i] = material.rotPivotOffset[i];
rotPos[i] = material.rotPos[i];
wavePos[i] = material.wavePos[i];
waveFreq[i] = material.waveFreq[i];
waveAmp[i] = material.waveAmp[i];
waveType[i] = material.waveType[i];
seqFramePerSec[i] = material.seqFramePerSec[i];
seqSegSize[i] = material.seqSegSize[i];
glow[i] = material.glow[i];
emissive[i] = material.emissive[i];
}
castsShadow = material.castsShadow;
breakable = material.breakable;
doubleSided = material.doubleSided;
attenuateBackFace = material.attenuateBackFace;
preload = material.preload;
cubemapName = material.cubemapName;
mCubemapData = material.mCubemapData;
dynamicCubemap = material.dynamicCubemap;
translucent = material.translucent;
translucentBlendOp = material.translucentBlendOp;
translucentZWrite = material.translucentZWrite;
alphaTest = material.alphaTest;
alphaRef = material.alphaRef;
planarReflection = material.planarReflection;
mapTo = material.mapTo;
hasSetStageData = material.hasSetStageData;
mType = material.mType;
mIsIFL = material.mIsIFL;
dStrcpy(mPath, material.mPath);
return *this;
}In CustomMaterial.h, above this line :
DECLARE_CONOBJECT(CustomMaterial);
put this :
CustomMaterial &operator=( const CustomMaterial &material );
In CustomMaterial.cpp, just add this function to the bottom of the file :
CustomMaterial & CustomMaterial::operator=( const CustomMaterial &material )
{
Parent::operator=(material);
for(S32 i = 0; i < MAX_TEX_PER_PASS; i++)
{
texFilename[i] = material.texFilename[i];
tex[i] = material.tex[i];
mFlags[i] = material.mFlags[i];
}
// this will recursively call CustomMaterials copy constructor.
for(S32 i = 0; i < MAX_PASSES; i++)
{
if(NULL == material.pass[i])
pass[i] = NULL;
else
*pass[i] = *(material.pass[i]);
}
// this will recursively call CustomMaterials copy constructor.
if(NULL == material.fallback)
fallback = NULL;
else
*fallback = *material.fallback;
// this will recursively call CustomMaterials copy constructor.
if(NULL == material.dynamicLightingMaterial)
dynamicLightingMaterial = NULL;
else
*dynamicLightingMaterial = *material.dynamicLightingMaterial;
// this will recursively call CustomMaterials copy constructor.
if(NULL == material.dynamicLightingMaskMaterial)
dynamicLightingMaskMaterial = NULL;
else
*dynamicLightingMaskMaterial = *material.dynamicLightingMaskMaterial;
startDataMarker = material.startDataMarker;
mVersion = material.mVersion;
blendOp = material.blendOp;
refract = material.refract;
mMaxTex = material.mMaxTex;
if( material.mShaderDataName )
{
mShaderData = static_cast<ShaderData*>(Sim::findObject( material.mShaderDataName ) );
}
mShaderDataName = material.mShaderDataName;
mCurPass = material.mCurPass;
mCullMode = material.mCullMode;
endDataMarker = material.endDataMarker;
return *this;
}Ok, this will allow us to make copies of our texture. Note, if you didn't do it this way, when you said NewMateriaA = OldMaterial you'd copy ALL the details of OldMaterial, including simobject IDs. Then when you tried to register your new material it would bomb because a material with that ID is already registered.
Alright, now that we can copy materials, what materials do we copy? Well, we could copy the material that is already in that slot, but that wouldn't work because we can have a variable number of skins, and the existing materials shader might not know how to handle X textures. So we need a little setup.
First, in script we define a number of materials. One for each possible number of layers we can have in our dynamic skin. I've gone with 4. Put them in a convenient material definition script file, "materials.cs" in the "data\shapes\player\" for example. Just add them at the bottom :
new CustomMaterial(dynamic_skin_1)
{
texture[0] = "~/data/shapes/player/Textures/Skins/WhiteMale1/heads/head1.jpg";
texture[1] = "$fog";
shader = dynamic_skin_shader_1;
version = 2.0;
};
new CustomMaterial(dynamic_skin_2)
{
texture[0] = "~/data/shapes/player/Textures/Skins/WhiteMale1/heads/head1.jpg";
texture[1] = "~/data/shapes/player/Textures/Eyes/MaleBlueEyes.png";
texture[2] = "$fog";
shader = dynamic_skin_shader_2;
version = 2.0;
};
new CustomMaterial(dynamic_skin_3)
{
texture[0] = "~/data/shapes/player/Textures/Skins/WhiteMale1/heads/head2.jpg";
texture[1] = "~/data/shapes/player/Textures/Eyes/MaleGreenEyes.png";
texture[2] = "~/data/shapes/player/Textures/Skins/WhiteMale1/scars/scar2.png";
texture[3] = "$fog";
shader = dynamic_skin_shader_3;
version = 2.0;
};
new CustomMaterial(dynamic_skin_4)
{
texture[0] = "~/data/shapes/player/Textures/Skins/WhiteMale1/heads/head2.jpg";
texture[1] = "~/data/shapes/player/Textures/Eyes/MaleGreenEyes.png";
texture[2] = "~/data/shapes/player/Textures/Skins/WhiteMale1/scars/scar3.png";
texture[3] = "~/data/shapes/player/Textures/Tattoos/MaleTattooFace2b.png";
texture[4] = "$fog";
shader = dynamic_skin_shader_4;
version = 2.0;
};And then the shader defs these point to, again, put them somewhere convenient like "shaders.cs" in "client\scripts\", at the bottom of the file :
new ShaderData( dynamic_skin_shader_1 )
{
DXVertexShaderFile = "shaders/DynamicSkin/dynamicSkin1V.hlsl";
DXPixelShaderFile = "shaders/DynamicSkin/dynamicSkin1P.hlsl";
pixVersion = 2.0;
};
new ShaderData( dynamic_skin_shader_2 )
{
DXVertexShaderFile = "shaders/DynamicSkin/dynamicSkin2V.hlsl";
DXPixelShaderFile = "shaders/DynamicSkin/dynamicSkin2P.hlsl";
pixVersion = 2.0;
};
new ShaderData( dynamic_skin_shader_3 )
{
DXVertexShaderFile = "shaders/DynamicSkin/dynamicSkin3V.hlsl";
DXPixelShaderFile = "shaders/DynamicSkin/dynamicSkin3P.hlsl";
pixVersion = 2.0;
};
new ShaderData( dynamic_skin_shader_4 )
{
DXVertexShaderFile = "shaders/DynamicSkin/dynamicSkin4V.hlsl";
DXPixelShaderFile = "shaders/DynamicSkin/dynamicSkin4P.hlsl";
pixVersion = 2.0;
};I've included the shader files these definitions point to, drop them into a "shaders\DynamicSkin\" directory off your root game directory. Note that your shaders don't have to be exactly like I've defined them, in fact my intent is that you customize them for your needs. Read them over, they are easy to extrapolate so you can write them for however many layers you need.
Now I wanted a nice flexible way for the code to know which material corresponds to what template so I put in some Prefs, they go in "client\prefs.cs", add them to the end :
$pref::DynamicSkins::MaxBlendedTextures = 4; $pref::DynamicSkins::MaterialTemplate_1 = "dynamic_skin_1"; $pref::DynamicSkins::MaterialTemplate_2 = "dynamic_skin_2"; $pref::DynamicSkins::MaterialTemplate_3 = "dynamic_skin_3"; $pref::DynamicSkins::MaterialTemplate_4 = "dynamic_skin_4";
If you add in more dynamic material templates don't forget to add in the prefs so the code knows what they are called.
Right, so hopefully you have the basic idea. You, the game designer, setup these material templates. When you create a new dynamic texture that blends 3 textures, the code is going to make a copy of the 3 texture Material you predefined above and then replace the textures with the ones we want, using our nice copy constructor.
But creating our material is not enough. The shape needs to use it. Before I continue, I should discuss "Texture Tags". I don't know what it is officially called but it is pretty much the name you give to a material when you are mapping it to geometry in your art program. Your texture might have a name like "HairG1558.jpg" on file but in your art program you call it "Hair".
So you have these Texture Tags and we are going to use them to our advantage. Basically, I'm going to use them to reference the material we want to replace.
First add this function to TSMaterialList . What it does is replace the matInstance at an index in the list with another one, pointing at a different material. We will find out the index in another function by examining the texture tags to determine which one matches the one we want to replace. Then we will call this function with the material we want to use instead.
At the top of TSShape.h, after this :
#ifndef _STREAM_H_ #include "core/stream.h" #endif
add :
#ifndef _MATERIAL_H_ #include "materials/material.h" #endif #ifndef _CUSTOMMATERIAL_H_ #include "materials/customMaterial.h" #endif
In TSShape.h, in TSMaterialList class definition, above this :
/// pre-load only ... support for ifl sequences
void push_back(const char * name, U32 flags,
U32 a=0xFFFFFFFF, U32 b=0xFFFFFFFF, U32 c=0xFFFFFFFF,
F32 dm=1.0f, F32 em=1.0f,
U32 l=0xFFFFFFFF);put this :
bool setMaterial(U32 index, Material* newMat);
In TSMaterialList.cpp, just add the function to the end :
bool TSMaterialList::setMaterial(U32 index, Material* newMat)
{
if (index < 0 || index > mMaterials.size())
return false;
if( newMat )
{
if(mMatInstList[index]->getMaterial() == newMat)
return true;
// dump the old mat instance
if (mMatInstList[index])
delete mMatInstList[index];
mMatInstList[index] = NULL;
// change texture
CustomMaterial* cust = dynamic_cast<CustomMaterial*>(newMat);
StringTableEntry texPath;
if(cust)
texPath = cust->texFilename[0];
else
texPath = newMat->baseTexFilename[0];
GFXTexHandle tex( texPath, &GFXDefaultStaticDiffuseProfile );
if (!tex.isValid())
return false;
// change texture
mMaterials[index] = tex;
// DONT CHANGE THE MATERIAL NAME.
// We want to be able to reference the material by the original name. This allows us to do this :
// %obj.reskin("Head", "Head_A");
// %obj.reskin("Head", "Head_B");
// if we changed it we'd need to pass in the last changed skin into the first parameter (Head_A in this case). Which sucks.
MatInstance * matInst = new MatInstance( *newMat );
mMatInstList[index] = matInst;
// initialize the new mat instance
SceneGraphData sgData;
sgData.useLightDir = true;
sgData.useFog = SceneGraph::renderFog;
GFXVertexPNT *tsVertex = NULL;
matInst->init( sgData, (GFXVertexFlags)getGFXVertFlags( tsVertex ) );
}
return true;
}To build a dynamic material we need a list which stores the texture names to be blended together. In fact, we keep that list in an object representing the dynamic texture as a whole, then keep a list of THOSE objects, one for each dynamic texture we have. This is done in shapebase and TSShapeInstance :
In TSShapeInstance.h, after this section :
class RenderItem; class TSThread; class ConvexFeature;
add :
class DynamicSkinData
{
public :
Vector<StringTableEntry> mTextures; // texture layers to blend together
StringTableEntry mMaterialName;
StringTableEntry mSkinTag;
};In shapebase.h, after this :
static void initPersistFields();
put :
protected : Vector<DynamicSkinData> mDynamicSkins; void BuildDynamicSkins(); void ClearDynamicSkins();
In Shapebase.cpp, add functions at bottom of file :
void ShapeBase::BuildDynamicSkins()
{
for(S32 i = 0; i < mDynamicSkins.size(); i ++)
mShapeInstance->SetDynamicSkin( &mDynamicSkins[i]);
}
void ShapeBase::ClearDynamicSkins()
{
for(S32 i = 0; i < mDynamicSkins.size(); i++)
mDynamicSkins[i].mTextures.clear();
mDynamicSkins.clear();
}Still in shapebase.cpp, add a ClearDynamicSkins() function call to the end of the destructor (~Shapebase() ) like so :
Shapebase::~Shapebase()
{
...stuff...
// make sure list cleaned up
ClearDynamicSkins();
}Make sure we ghost dynamic skin data across to the client.
In shapebase.h :
We need to add a new bitmask after the sound mask, like so.
Change :
SoundMaskN = Parent::NextFreeMask << 8, ///< Extends + MaxSoundThreads bits
to :
SoundMaskN = Parent::NextFreeMask << 8, ///< Extends + MaxSoundThreads bits DynamicSkinMask = Parent::NextFreeMask << 9,
further down in the same file, shapebase.h, add this to the end of the header, above the close brackets "};" :
public : void UpdateDynamicSkins();
(A note, in the PackUpdate/UnpackUpdate, the order of these reads and writes in relation to the other calls in the function is VERY important. If you have problems where your stage doesn't want to finish loading this is a good place to check. Speaking from experience ;) )
In shapebase.cpp :
In PackUpdate(), inside the initial mask if statement ("if (mask & InitialUpdateMask)...") add in code like so :
Change :
// mask off images that aren't updated for(i = 0; i < MaxMountedImages; i++) if(!mMountedImageList[i].dataBlock) mask &= ~(ImageMaskN << i);
to :
// mask off images that aren't updated for(i = 0; i < MaxMountedImages; i++) if(!mMountedImageList[i].dataBlock) mask &= ~(ImageMaskN << i); if(mDynamicSkins.size() == 0) mask &= ~DynamicSkinMask; else mask |= DynamicSkinMask;
further down in the same function, change :
if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask | SkinMask ))) {to :
if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask | SkinMask | DynamicSkinMask ))) {then still further down change :
if (stream->writeFlag(mask & InvincibleMask))
{
stream->write(mInvincibleTime);
stream->write(mInvincibleSpeed);
}to :
if (stream->writeFlag(mask & InvincibleMask))
{
stream->write(mInvincibleTime);
stream->write(mInvincibleSpeed);
}
if(stream->writeFlag(mask & DynamicSkinMask))
{
// the number of dynamic skins
stream->writeInt(mDynamicSkins.size(), 10);
for(S32 x = 0; x < mDynamicSkins.size(); x++)
{
stream->writeString(mDynamicSkins[x].mSkinTag);
stream->writeString(mDynamicSkins[x].mMaterialName);
stream->writeInt(mDynamicSkins[x].mTextures.size(), 10);
for(S32 y = 0; y < mDynamicSkins[x].mTextures.size(); y++)
{
stream->writeString(mDynamicSkins[x].mTextures[y]);
}
}
}then in UnpackUpdate() :
change :
if (stream->readFlag())
{ // InvincibleMask
F32 time, speed;
stream->read(&time);
stream->read(&speed);
setupInvincibleEffect(time, speed);
}to :
if (stream->readFlag())
{ // InvincibleMask
F32 time, speed;
stream->read(&time);
stream->read(&speed);
setupInvincibleEffect(time, speed);
}
if(stream->readFlag())
{
ClearDynamicSkins();
// the number of dynamic skins
S32 numDynamicSkins = stream->readInt(10);
for(S32 x = 0; x < numDynamicSkins; x++)
{
DynamicSkinData data;
mDynamicSkins.push_back(data);
S32 index = mDynamicSkins.size() -1;
mDynamicSkins[index].mSkinTag = stream->readSTString();
mDynamicSkins[index].mMaterialName = stream->readSTString();
// the number of dynamic skins
S32 numDynamicTextures = stream->readInt(10);
for(S32 y = 0; y < numDynamicTextures; y++)
{
mDynamicSkins[index].mTextures.push_back(stream->readSTString());
}
}
BuildDynamicSkins();
}Then add this function at the bottom of the same .cpp file :
void ShapeBase::UpdateDynamicSkins()
{
setMaskBits(DynamicSkinMask);
}The UpdateDynamicSkins call is so that you can batch the networking updates of layers. Once you have setup all your layers to blend properly in script, call UpdateDynamicSkins and the data will be sent off to the client all at once.
Add in some accessors and the console methods which will allow script to call them. The basic idea is you reset a dynamic skin to a base texture, then add blend layers on top of that. Then call UpdateDynamicSkins to get the server to update the client.
In Shapebase. h, after this line :
public :
void UpdateDynamicSkins();put this:
bool AddDynamicSkinLayerTexture(const char* skinTag, const char* texture, const char* matName); bool ResetDynamicSkin(const char* skinTag, const char* texture, const char* matName);
in shapebase.cpp, add these to the end :
ConsoleMethod( ShapeBase, UpdateDynamicSkins, void, 2, 2, "")
{
object->UpdateDynamicSkins();
}
bool ShapeBase::AddDynamicSkinLayerTexture(const char* skinTag, const char* texture, const char* matName)
{
// for sanitys sake, lets prevent scripters from going crazy with skin layers.
if( mDynamicSkins.size() >= CustomMaterial::MAX_TEX_PER_PASS )
{
Con::errorf("AddDynamicSkinLayerTexture(): Max number of textures reached! (%d)", mDynamicSkins.size());
return false;
}
bool found = false;
for(S32 i = 0; i < mDynamicSkins.size(); i ++)
{
if(0 == dStricmp(mDynamicSkins[i].mSkinTag, skinTag))
{
found = true;
mDynamicSkins[i].mTextures.push_back( StringTable->insert(texture) );
mDynamicSkins[i].mMaterialName = StringTable->insert(matName);
}
}
if(!found)
{
DynamicSkinData data;
mDynamicSkins.push_back(data);
S32 index = mDynamicSkins.size() - 1;
mDynamicSkins[index].mTextures.clear();// redundant but lets just make sure.
mDynamicSkins[index].mTextures.push_back( StringTable->insert(texture) );
mDynamicSkins[index].mSkinTag = StringTable->insert(skinTag);
mDynamicSkins[index].mMaterialName = StringTable->insert(matName);
}
return true;
}
//--------------------------------------------------------------------------------------------------------
ConsoleMethod( ShapeBase, AddDynamicSkinLayerTexture, bool, 5, 5, "(string skinTag, string texture, string materialName)")
{
return object->AddDynamicSkinLayerTexture(argv[2],argv[3],argv[4]);
}
//--------------------------------------------------------------------------------------------------------
bool ShapeBase::ResetDynamicSkin(const char* skinTag, const char* texture, const char* matName)
{
if(!texture || !skinTag || !matName)
return false;
bool found = false;
for(S32 i = 0; i < mDynamicSkins.size(); i ++)
{
if(0 == dStricmp(mDynamicSkins[i].mSkinTag, skinTag))
{
found = true;
mDynamicSkins[i].mTextures.clear();
mDynamicSkins[i].mTextures.push_back( StringTable->insert(texture) );
mDynamicSkins[i].mMaterialName = StringTable->insert(matName);
}
}
if(!found)
{
DynamicSkinData data;
mDynamicSkins.push_back(data);
S32 index = mDynamicSkins.size() - 1;
mDynamicSkins[index].mTextures.clear();// redundant but lets just make sure.
mDynamicSkins[index].mTextures.push_back( StringTable->insert(texture) );
mDynamicSkins[index].mSkinTag = StringTable->insert(skinTag);
mDynamicSkins[index].mMaterialName = StringTable->insert(matName);
}
return true;
}
//--------------------------------------------------------------------------------------------------------
ConsoleMethod( ShapeBase, ResetDynamicSkin, bool, 5, 5, "(string skinTag, string texture, string materialName)")
{
return object->ResetDynamicSkin(argv[2], argv[3], argv[4]);
}Right, on to the function which does most of the work, SetDynamicSkin. This takes one of those objects representing a dynamic skin and builds us a nice new CustomMaterial from it's texture list, copying the appropriate base template Material and substituting data in.
In TSShapeInstance.h, after this line:
bool ownMaterialList() const { return mOwnMaterialList; }put :
void SetDynamicSkin(DynamicSkinData* data);
In TSShapeInstance.cpp, add to the end :
void TSShapeInstance::SetDynamicSkin(DynamicSkinData* data)
{
// First, check if the skin tag (materialName) exists in our matInstance list.
// if it doesn't we don't do anything as this is incorrect skin data.
// (can't replace a skin we don't have ;) )
TSMaterialList* pMatList = getMaterialList();
S32 foundMatInstIndex = -1;
for (S32 j = 0; j < pMatList->mMaterialNames.size(); j++)
{
const char* pName = pMatList->mMaterialNames[j];
if (pName == NULL)
continue;
if(0 == dStricmp(pName,data->mSkinTag))
{
foundMatInstIndex = j;
break;
}
}
if(-1 == foundMatInstIndex)
return;
// now check if the material mapped to this skin tag matches the one we want to map to it.
// if it does then our work is already done and we can simply return.
MatInstance* matInst = pMatList->getMaterialInst(foundMatInstIndex);
Material* currentMat;
if(matInst)
currentMat = matInst->getMaterial();
Material *mat = dynamic_cast<Material*>( Sim::findObject( data->mMaterialName ) );
if( mat )
{
// oh look, it's already mapped at the current material, yay, lets just return
if(mat == currentMat)
return;
// ok, the material already exists so lets just map it to this matInst/skin tag slot.
pMatList->setMaterial(foundMatInstIndex,mat);
}
else
{
// oh dear, the material doesn't exist. So we have to create it then map it to the matInst/skin tag slot.
// the trick to creating a new material is that we are going to copy the details of a "template" material.
// The material we choose to copy is based on how many textures we are blending. If 3 then we choose
// the material that is set up to blend 3 textures, etc. That way designers can control EXACTLY how
// these materials operate without needing to recompile code. :D
S32 maxNumTextures = Con::getIntVariable( "$pref::DynamicSkins::MaxBlendedTextures" );
// clamp the number of textures, for sanitys sake
S32 numBlendedTextures = data->mTextures.size();
if(numBlendedTextures > maxNumTextures)
numBlendedTextures = maxNumTextures;
// read the name of the desired material template from prefs
char materialTemplate[512];
dSprintf(materialTemplate, sizeof(materialTemplate), "$pref::DynamicSkins::MaterialTemplate_%d", numBlendedTextures);
const char* templateName = NULL;
templateName = Con::getVariable(materialTemplate);
// if we couldn't read the name return
if(!templateName)
return;
// we're going for custom materials here. If players want normal materials that option will need to
// be added in code. Ah well.
CustomMaterial *templateMat = dynamic_cast<CustomMaterial*>( Sim::findObject( templateName ) );
// if the template material doesn't exist return;
if(!templateMat)
return;
// ok, we found it, lets shamelessly copy it!
CustomMaterial* newMat = new CustomMaterial();
// copy...
*newMat = *templateMat;
// now change the variables we want on our copy.
// name...
newMat->assignName( data->mMaterialName );
// don't make the "mapTo" equal to the skin tag. We don't want all objects which have a "Head"
//texture (skin tag) using our dynamic material for instance. Then ALL of them would have the same
// material as "Head". Completely negates the point of having dynamic skins!
newMat->mapTo = StringTable->insert( data->mMaterialName );
// replace textures...
for (S32 i = 0; i < data->mTextures.size(); i++)
{
newMat->texFilename[i] = data->mTextures[i];
}
AssertFatal( newMat->registerObject(), "Unable to register dynamic material!" );
Sim::getRootGroup()->addObject( newMat );
pMatList->setMaterial(foundMatInstIndex,newMat);
}
}Note that it attempts to find the material before assigning it. Which seems to go against what I said before, about how our dynamic materials aren't shared. Let me explain. See, one of the strengths of TGEA is it's batched rendering. If it can it will attempt to render objects with the same material together, to decrease gpu overhead. Nice. But dynamic textures will remove that benefit, since by definition they are unique. But actually, they don't have to be, do they? You could have two players with the same face, eye colour and tattoo. Or two cars with the same decals on. It's less likely, but still possible. So we want to make it so that this possibility is handled by the batching system. The way I do that is by allowing you to pass in the dynamic material name. The intention is that you pass in a name which uniquely identifies that material combination.
For example, if your NPC has a dynamic material which uses base texture FaceA.jpg and has TattooB.jpg blended over it, you could pass in "dynamic_mtl_FaceA_TattooB" as the name. If you then set another NPC to using this same combination, pass in the same name and it will be shared between the two. if you later decide to alter the second NPC, if you want it to use FaceB.jpg but still TattooB.jpg, pass in "dynamic_mtl_FaceB_TattooB". The underlying system will then make you a new dynamic material while leaving the one used by the first NPC alone. If you add in a 3rd NPC who uses the first dynamic material, it will then be shared by them, etc etc. The advantages of batching combined with dynamic materials :D The only trick is to make sure your names uniquely identify the combinations. I just append the texture names and underscores, as shown above.
(Note that I haven't got any bump mapping or anything like that in these scripts. They're easy to add, just add them as another texture layer. The trick will just be making sure each of the possible shaders handle the bump mapping. Check the example shaders that come with TGEA, should be trivial.)
Here are some demo scripts you can use to test the system. Unzip the test textures I have included in the rar file into the "\data\shapes\player\" directory, then...
in server\scripts\game.cs, add this to the bottom :
//---- Testing Dynamic Skins
// replace texture names with your own. Don't forget the paths...
$base[0]="arcane.fx/data/shapes/player/base1.jpg";
$base[1]="arcane.fx/data/shapes/player/base2.jpg";
$base[2]="arcane.fx/data/shapes/player/base3.jpg";
$blend[0]="arcane.fx/data/shapes/player/Marking1.png";
$blend[1]="arcane.fx/data/shapes/player/Marking2.png";
$blend[2]="arcane.fx/data/shapes/player/Marking3.png";
//Your model will need to be set up with a "Player" texture tag.
function SetDynamicSkin(%player, %baseIndx, %blendIndx)
{
echo("setting dynamic skins for " @ %player @ " to - " @ %baseIndx @ " - " @ %blendIndx );
%name = "Dynamic_mtl_" @ $base[%baseIndx] @ "_" @ $blend[%blendIndx];
%player.ResetDynamicSkin("Player", $base[%baseIndx], %name);
%player.AddDynamicSkinLayerTexture("Player", $blend[%blendIndx], %name);
%player.UpdateDynamicSkins();
}
function serverCmdSetDynamicSkin(%client, %baseIndx, %blendIndx)
{
echo("CMD set dynamic skins for " @ %client.player @ " to - " @ %baseIndx @ " - " @ %blendIndx );
SetDynamicSkin(%client.player, %baseIndx, %blendIndx);
}NOTE : PLEASE REMEMBER YOU NEED TO CHANGE THE TEXTURE PATHS IN THE ABOVE SCRIPT TO MATCH YOUR FILES.
in client\scripts\dynamic.bind, add to the bottom :
//---- Testing Dynamic Skins
$BaseIndx = 0;
$BlendIndx = 0;
function IncBaseIndx(%val)
{
if(%val)
{
$BaseIndx++;
if($BaseIndx > 2)
$BaseIndx = 0;
commandToServer('SetDynamicSkin', $BaseIndx, $BlendIndx);
}
}
function IncBlendIndx(%val)
{
if(%val)
{
$BlendIndx++;
if($BlendIndx > 2)
$BlendIndx = 0;
commandToServer('SetDynamicSkin', $BaseIndx, $BlendIndx);
}
}
moveMap.bind(keyboard, "g", IncBaseIndx);
moveMap.bind(keyboard, "h", IncBlendIndx);If you are already using the g and h keys, change the binding to something else. ;)
Don't forget to delete config.cs to get the bindings to take. In game press "g" to cycle base layers, "h" to cycle overlay blend layers.
I haven't tested this code in a networked environment (I have only the single PC at home) but it should be good, I've handled the ghosting. If you spot any errors or encounter problems let me know and I'll take a look. Also, since default TGEA hasn't got a good Stronghold-like mission you will need to implement that first to test this (there is a stronghold FPS starter port I believe), unless you are a decent coder. I've tested this against a fresh TGEA-AFX 1.03 build myself.
Enjoy.
About the author
Recent Blogs
• SoW Weekly Update 10• SoW Weekly Update 9
• SoW Weekly Update 8
• SoW Weekly Update 7
• SoW Weekly Update 6
#2
02/09/2008 (11:40 pm)
finally
#3
02/26/2008 (8:15 am)
Hey Gareth this is a really sweet addition to materials handling. There are a lot of other possibilities as well with this kind of system.
#4
Now I can finally add my Stealth ship to Solar Battles... YAY!
This resource has so many uses.
This should go to the next version of TGEA!
02/27/2008 (10:35 pm)
Wow... Awesome Resource Gareth. Now I can finally add my Stealth ship to Solar Battles... YAY!
This resource has so many uses.
This should go to the next version of TGEA!
#5
And yeah, there are a lot of possibilities. I must admit I'm really enjoying working with shaders again. TGEA rocks!
Edit : Just spotted a minor mistake, in the example script I provided I said the model needs a "Face" skin tag. Should be "Player" skin tag. I've fixed it now. The default orc has this skin tag, so you should be good.
02/27/2008 (11:16 pm)
Thanks guys, hope you have fun with it. :)And yeah, there are a lot of possibilities. I must admit I'm really enjoying working with shaders again. TGEA rocks!
Edit : Just spotted a minor mistake, in the example script I provided I said the model needs a "Face" skin tag. Should be "Player" skin tag. I've fixed it now. The default orc has this skin tag, so you should be good.
#6
02/28/2008 (12:54 pm)
BTW, I wanted to tell you that I know the pain you went through of preserving and packing/unpacking the changed material names, this really lets it all work on newly joined clients instead of just on existing ones. It's a must have for multi material models. I did a very similar approach in my char creation course, minus all the wonderment you've added!!
#7
Heh, the glamorous life of a coder!
Thanks again for the kind words Dave :)
02/28/2008 (1:01 pm)
Hehe, yeah, "pain" is the right word. While developing this I somehow managed to press backspace at the beginning of a line of code, moving it up a line...behind a comment. But so far offscreen that I missed it when scrolling up. Mission would hang on loading without fail. I sat for 4 hours tracing through the bottom-most level of the network code trying to figure out what was happening, only on using diff tool was I able to find out what had happened...shudder. Not fun. Heh, the glamorous life of a coder!
Thanks again for the kind words Dave :)
#8
02/28/2008 (8:44 pm)
Nice work Gareth. I recently started playing around with the modernization kit for TGE, now I wonder if I could not do something similar with it :p , but that will have to wait.
#9
02/28/2008 (11:09 pm)
Very nice resource indeed, thanks a lot! I've merged the code last night but it doesn't work yet. Didn't have the time to search for the reason yet, commands are echo'ed, it just doesn't change anything. Need some time to go deeper...
#10
Had some slight hitches implementing it and the example. Tested with TGEA 1.0.3 with starter.fps port
At first nothing happened, and I found I hadn't put the $pref stuff in a very clever place, I ended up putting it in common/main.cs
Also
$pref::DynamicSkins::MaxBlendedTextures = 2;
should be set somewhere (the description doesn't tell you to do this), I put it in the same place.
when using starter.fps port, I didn't have a "client\scripts\dynamic.bind" file
I put this stuff in "default.bind.cs" instead
"h" is already mapped to using healthkit, so this has to be mapped to something else, or change the key.
the example says $base[0]="arcane.fx/data/shapes/player/base1.png"; etc, and tells to change the path. Also the extension is wrong if using the files from the zip. it's .jpg not .png for the base* files
With the default code, if one makes the mistake of using the wrong case for the texturetag, it seems to work, however the code creates a new material everytime it changes, with my setup, this caused TGEA to crash after the 14th time skinchange.
to fix this, and make it do a case insensitive search instead, when setting new materials on a shape.
in ShapeBase::ResetDynamicSkin and ShapeBase::AddDynamicSkinLayerTexture change
if(0 == strcmp(mDynamicSkins[i].mSkinTag, skinTag))
to
if(0 == dStricmp(mDynamicSkins[i].mSkinTag, skinTag))
03/02/2008 (12:12 pm)
Great resource, TGEA was really missing something like this!Had some slight hitches implementing it and the example. Tested with TGEA 1.0.3 with starter.fps port
At first nothing happened, and I found I hadn't put the $pref stuff in a very clever place, I ended up putting it in common/main.cs
Also
$pref::DynamicSkins::MaxBlendedTextures = 2;
should be set somewhere (the description doesn't tell you to do this), I put it in the same place.
when using starter.fps port, I didn't have a "client\scripts\dynamic.bind" file
I put this stuff in "default.bind.cs" instead
"h" is already mapped to using healthkit, so this has to be mapped to something else, or change the key.
the example says $base[0]="arcane.fx/data/shapes/player/base1.png"; etc, and tells to change the path. Also the extension is wrong if using the files from the zip. it's .jpg not .png for the base* files
With the default code, if one makes the mistake of using the wrong case for the texturetag, it seems to work, however the code creates a new material everytime it changes, with my setup, this caused TGEA to crash after the 14th time skinchange.
to fix this, and make it do a case insensitive search instead, when setting new materials on a shape.
in ShapeBase::ResetDynamicSkin and ShapeBase::AddDynamicSkinLayerTexture change
if(0 == strcmp(mDynamicSkins[i].mSkinTag, skinTag))
to
if(0 == dStricmp(mDynamicSkins[i].mSkinTag, skinTag))
#11
03/02/2008 (1:18 pm)
Heh, thanks for the catch Jan, updated the resource. :)
#12
03/02/2008 (6:23 pm)
Could someone post some line numbers or host the working files?
#13
03/04/2008 (11:40 am)
I've updated the resource with explicit instructions as to where to put the code. :) Let me know if I missed anything.
#14
only the base* files in the zip were jpg, the marking* files are still png.
so it should be
$base[0]="arcane.fx/data/shapes/player/base1.jpg";
$base[1]="arcane.fx/data/shapes/player/base2.jpg";
$base[2]="arcane.fx/data/shapes/player/base3.jpg";
$blend[0]="arcane.fx/data/shapes/player/Marking1.png";
$blend[1]="arcane.fx/data/shapes/player/Marking2.png";
$blend[2]="arcane.fx/data/shapes/player/Marking3.png";
03/04/2008 (3:04 pm)
Seems I was a bit unclear earlieronly the base* files in the zip were jpg, the marking* files are still png.
so it should be
$base[0]="arcane.fx/data/shapes/player/base1.jpg";
$base[1]="arcane.fx/data/shapes/player/base2.jpg";
$base[2]="arcane.fx/data/shapes/player/base3.jpg";
$blend[0]="arcane.fx/data/shapes/player/Marking1.png";
$blend[1]="arcane.fx/data/shapes/player/Marking2.png";
$blend[2]="arcane.fx/data/shapes/player/Marking3.png";
#15
03/04/2008 (3:07 pm)
Thank you very much Gareth. This is very nice.
#16
#include "sceneGraph/sceneGraph.h"
to the includes at the top of tsShape.cpp.
03/05/2008 (4:46 pm)
Well I have had some problems compiling. I figured out that you need to add#include "sceneGraph/sceneGraph.h"
to the includes at the top of tsShape.cpp.
#17
It looks like in one instance you have to have all of the textures to be blended added to a customMaterial and it just blends those together.
The other instance looks as if you can just specify the sin area like "base" or "head" and then what texture you want to merge with it. This method is definitely my favorite, but I am a bit confused on how the custom materials and shaders work in there.
I also see that you have hard coded prefs which if I wanted to use this for several different vehicles for example, I cannot see how I could possible use $pref::DynamicSkins::MaterialTemplates system for more than one object type.
Perhaps you can explain this to me so I can use this wonderful resource.
03/05/2008 (8:35 pm)
OK I have to ask what is probably a dumb question but I am seeing a couple of methods here, I could be wrong.It looks like in one instance you have to have all of the textures to be blended added to a customMaterial and it just blends those together.
The other instance looks as if you can just specify the sin area like "base" or "head" and then what texture you want to merge with it. This method is definitely my favorite, but I am a bit confused on how the custom materials and shaders work in there.
I also see that you have hard coded prefs which if I wanted to use this for several different vehicles for example, I cannot see how I could possible use $pref::DynamicSkins::MaterialTemplates system for more than one object type.
Perhaps you can explain this to me so I can use this wonderful resource.
#18
Now how does the custom shader fit in? I am a bit confused there because reading this and from what I see of your blend list it almost looks as if you can use pretty much what you want, but I am not seeing how that would work because in the code it looks as if it has to use the custom materials.
Does it just change out what the custom material's textures are when you tell it to add a texture layer?
Would you please mind explaing if this is possible and how?
03/08/2008 (2:35 am)
I guess what I am trying to ask is because of the set preferences, is there some what to use this for several different vehicles and player characters in a game with different blended textures for each? As far as I can see you can only have the textures for one object.Now how does the custom shader fit in? I am a bit confused there because reading this and from what I see of your blend list it almost looks as if you can use pretty much what you want, but I am not seeing how that would work because in the code it looks as if it has to use the custom materials.
Does it just change out what the custom material's textures are when you tell it to add a texture layer?
Would you please mind explaing if this is possible and how?
#19
%player.ResetDynamicSkin("Player", $base[%baseIndx], %name);
%player.AddDynamicSkinLayerTexture("Player", $blend[%blendIndx], %name);
%player.UpdateDynamicSkins();
instead of the array $base and $blend, you can specify yourself what texture to use
%player.resetDynamicSkin("Player","your/skin/path/here.jpg","someName");
%player.AddDynamicSkinLayerTexture("Player", "your/skin/path/here2.jpg","someName");
%player.UpdateDynamicSkins();
note that you don't need to use the addDynamicSkinLayerTexture function, if you don't want several layers.
03/08/2008 (2:48 am)
these commands are the ones that set what skin the specific object should use:%player.ResetDynamicSkin("Player", $base[%baseIndx], %name);
%player.AddDynamicSkinLayerTexture("Player", $blend[%blendIndx], %name);
%player.UpdateDynamicSkins();
instead of the array $base and $blend, you can specify yourself what texture to use
%player.resetDynamicSkin("Player","your/skin/path/here.jpg","someName");
%player.AddDynamicSkinLayerTexture("Player", "your/skin/path/here2.jpg","someName");
%player.UpdateDynamicSkins();
note that you don't need to use the addDynamicSkinLayerTexture function, if you don't want several layers.
#20
So each and every vehicle and player can have different combos of textures, definitely. You would however need to modify the code if you wanted cars with 4 blended textures to work differently from players with 4 blended textures. Say in the case of players you wanted the 4th texture to be opacity but in the case of cars you wanted the 4th layer to be specular. That would require modifying the code.
HOWEVER, in that case all I'd recommend that you change your idea to make life easier. Create a 5-blend material/shader template, create the $Pref, make the 4th layer opacity and the 5th layer specular, and just pass in different textures for cars and players, using that template for both.
But basically, unless you want X textures to be blended in a very different way across objects, you're good. Simply blending textures, even if they are different across objects, is fine.
I don't have :
#include "sceneGraph/sceneGraph.h"
in tsShape.cpp, what errors were you getting? I'm not saying I didn't forget anything, just think it may be somewhere else. ;)
@ Jan : Thanks :)
03/08/2008 (3:20 am)
Ron : You can most definitely have several different characters and vehicles with different blended textures. I think you are getting confused about what those $Prefs are. Think of them as plans, templates, instructions on how the code is supposed to go about blending X number of textures together. It doesn't actually use them directly. Materials are very customizable, I didn't want to specify how exactly code would blend 4 textures together. You create a material/shader combo which blends 4 textures together in script, set the pref to point to it, then when the code wants to create a new 4-blend material it looks at that as an example. It says "ok, the pref material for blending 4 textures does it like this....so I will make another material that works exactly the same, uses the same type of shader, except with 4 OTHER textures for the shape I'm using". It's not altering the template materials. It's looking at them to see how it should setup the new CustomMaterial it is making.So each and every vehicle and player can have different combos of textures, definitely. You would however need to modify the code if you wanted cars with 4 blended textures to work differently from players with 4 blended textures. Say in the case of players you wanted the 4th texture to be opacity but in the case of cars you wanted the 4th layer to be specular. That would require modifying the code.
HOWEVER, in that case all I'd recommend that you change your idea to make life easier. Create a 5-blend material/shader template, create the $Pref, make the 4th layer opacity and the 5th layer specular, and just pass in different textures for cars and players, using that template for both.
But basically, unless you want X textures to be blended in a very different way across objects, you're good. Simply blending textures, even if they are different across objects, is fine.
I don't have :
#include "sceneGraph/sceneGraph.h"
in tsShape.cpp, what errors were you getting? I'm not saying I didn't forget anything, just think it may be somewhere else. ;)
@ Jan : Thanks :)
Torque 3D Owner Ron Nelson