Hiding Meshes
by Erik Madison · 03/28/2004 (6:21 am) · 85 comments
This is based heavily on Justin Mette's resource.
The big difference is I'm hiding/showing the meshes themselves, and not the nodes (skeleton). The main use is to simulate Raven's Ghoul system, wherein 3-4 models are used to represent hundreds of possibilities. This way, your model can contain a standard torso, a torso with platemail, a torso with chainmail, etc. All torsos are skinned the same, on one model. When loading the character, you turn off all meshes but one. When the player puts on a piece of armor, you can then turn off the standard torso, and turn on the armored one.
Available script methods are:
ModelDump() Runs TSDump, giving some somewhat useful info on a given model.
SkinOnList() Lists all visible skins(meshes)
SkinOffList() Lists all hidden skins(meshes)
SkinOn(name) Makes the named mesh visible
SkinOff(name) Makes the named mesh hidden
Basic usage:
1734.SkinOff(StandardLegs);
1734.SkinOn(ArmoredLegs);
Notes:
Using comments here is very important, as TSE is going to be all over this. Merging later will probably be a nightmare without knowing what you've added.
On to the code....
The big difference is I'm hiding/showing the meshes themselves, and not the nodes (skeleton). The main use is to simulate Raven's Ghoul system, wherein 3-4 models are used to represent hundreds of possibilities. This way, your model can contain a standard torso, a torso with platemail, a torso with chainmail, etc. All torsos are skinned the same, on one model. When loading the character, you turn off all meshes but one. When the player puts on a piece of armor, you can then turn off the standard torso, and turn on the armored one.
Available script methods are:
ModelDump() Runs TSDump, giving some somewhat useful info on a given model.
SkinOnList() Lists all visible skins(meshes)
SkinOffList() Lists all hidden skins(meshes)
SkinOn(name) Makes the named mesh visible
SkinOff(name) Makes the named mesh hidden
Basic usage:
1734.SkinOff(StandardLegs);
1734.SkinOn(ArmoredLegs);
Notes:
Using comments here is very important, as TSE is going to be all over this. Merging later will probably be a nightmare without knowing what you've added.
On to the code....
[b]tsShapeInstance.h[/b]
[i] struct MeshObjectInstance : ObjectInstance
{[/i]
bool forceHidden; ///< part of SkinOn()[b]tsShapeInstance.cc[/b]
[i]void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)[/i]
......
[i] else
objInst->meshList = NULL;
objInst->object = obj;[/i]
objInst->forceHidden = false; // part of SkinOn()[b]tsAnimate.cc[/b]
[i]void TSShapeInstance::animateVisibility(S32 ss)[/i]
.....
[i] S32 a = mShape->subShapeFirstObject[ss];
S32 b = a + mShape->subShapeNumObjects[ss];[/i]
for (int x=a; x<b; x++) { // changed from i to x,
// as i is used to make italics in these codeblocks
if(mMeshObjects[x].forceHidden) // part of SkinOn()
mMeshObjects[x].visible = 0.0;
else if (beenSet.test(x))
mMeshObjects[x].visible = 1.0;
}[b]ShapeBase.h[/b]
[i]class ShapeBase : public GameBase[/i]
....
[i]public:[/i]
Vector<S32> mHiddenMeshes; ///< Part of SkinOn()
....
[i] protected:[/i]
void updateHiddenMeshes(); ///< part of SkinOn()
[i] /// @name Damage[/i]
....
[i] /// Returns the current damage state.
DamageState getDamageState() { return mDamageState; }
[/i]
/// Returns true if the mesh is hidden
bool isHiddenMesh(S32 node); // checks mHiddenMeshes array, part of SkinOn()
/// part of SkinOn()
void SkinOffList();
void SkinOnList();
void ModelDump();
[i]
/// Returns true if the object is destroyed.
[/i]
[i]// Network state masks
enum ShapeBaseMasks {[/i]
HideMeshMask = Parent::NextFreeMask << 7,[b]ShapeBase.cc[/b]
[i]U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)[/i]
...
[i]for(i = 0; i < MaxMountedImages; i++)
if(!mMountedImageList[i ].dataBlock)
mask &= ~(ImageMaskN << i); [/i] // mask off mesh visibility if nothing to update
if(mHiddenMeshes.size() == 0)
mask &= ~HideMeshMask;
[i]U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream){[/i]
...
[i] if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask | ThreadMask | ImageMask | CloakMask | MountedMask[/i] | HideMeshMask)[i]))
return retMask;[/i]
...
[i] // Group some of the uncommon stuff together.
if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask [/i] | HideMeshMask[i]))) {[/i]
...
[i] if (stream->writeFlag(mask & InvincibleMask)) {
stream->write(mInvincibleTime);
stream->write(mInvincibleSpeed);
}[/i]
if (stream->writeFlag(mask & HideMeshMask)) {
stream->writeInt(mHiddenMeshes.size(), 8);
for(int x = 0; x < mHiddenMeshes.size(); x++)
stream->writeInt(mHiddenMeshes[x], 8);
}
...
[i]void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)[/i]
...
[i] setupInvincibleEffect(time, speed);
} [/i]
if (stream->readFlag()) { // HideMeshMask
mHiddenMeshes.clear();
int count = stream->readInt(8);
for(int x = 0; x < count; x++)
mHiddenMeshes.push_back(stream->readInt(8));
updateHiddenMeshes();
}
void ShapeBase::updateHiddenMeshes() {
if(!mShapeInstance)
return;
int s = mShapeInstance->mMeshObjects.size();
for(int x = 0; x < s; x++)
{
S32 nodeIndex = mShapeInstance->mMeshObjects[x].object->nodeIndex;
F32 visible = 1.0;
if(isHiddenMesh(nodeIndex))
visible = 0.0;
mShapeInstance->mMeshObjects[x].visible = visible;
mShapeInstance->mMeshObjects[x].forceHidden = (visible == 0.0);
}
}
bool ShapeBase::isHiddenMesh(S32 node)
{
for(int x = 0; x < mHiddenMeshes.size(); x++)
if(mHiddenMeshes[x] == node)
return true;
return false;
}
//--------------------------------------------------------------------------
// methods used with SkinOn()
ConsoleMethod(ShapeBase, ModelDump, void, 2, 2, "Dump known info on a model")
{
object->ModelDump();
}
void ShapeBase::ModelDump()
{
//A little bit of test info on the layout of the model
FileStream st;
st.open("Model.dump", FileStream::ReadWrite);
if ((st.getStatus() == Stream::Ok) || (st.getStatus() == Stream::EOS))
if (mShapeInstance)
mShapeInstance->dump(st);
else
Con::errorf("No shapeinstance");
else
Con::errorf("Error opening dump file");
}
ConsoleMethod( ShapeBase, SkinOffList, void, 2, 2, "List all skins(meshes) not visible")
{
object->SkinOffList();
}
void ShapeBase::SkinOffList()
{
for (int i=0; i<mShapeInstance->getShape()->objects.size(); i++)
{
if (mShapeInstance->getShape()->objects[i].nodeIndex<0) // must be a skin
{
const char * skinName = "";
S32 nameIndex = mShapeInstance->getShape()->objects[i].nameIndex;
if (nameIndex>=0 && isHiddenMesh(nameIndex))
{
skinName = mShapeInstance->getShape()->getName(nameIndex);
Con::errorf("%s ", skinName);
}
}
}
}
ConsoleMethod( ShapeBase, SkinOnList, void, 2, 2, "List all skins(meshes) visible")
{
object->SkinOnList();
}
void ShapeBase::SkinOnList()
{
for (int i=0; i<mShapeInstance->getShape()->objects.size(); i++)
{
if (mShapeInstance->getShape()->objects[i].nodeIndex<0) // must be a skin
{
const char * skinName = "";
S32 nameIndex = mShapeInstance->getShape()->objects[i].nameIndex;
if (nameIndex>=0 && !isHiddenMesh(nameIndex))
{
skinName = mShapeInstance->getShape()->getName(nameIndex);
Con::errorf("%s ", skinName);
}
}
}
}
ConsoleMethod( ShapeBase, SkinOff, void, 3, 3, "(string meshname)")
{
S32 i;
TSShape const* mShape = object->getShape();
if (argv[2] != '[[4fc733dd1b7b2]]') {
for (i=0; i< mShape->objects.size(); i++)
{
if (mShape->objects[i].nodeIndex<0) // must be a skin
{
S32 nameIndex = mShape->objects[i].nameIndex;
if (nameIndex>=0) {
if (dStricmp(argv[2], mShape->getName(nameIndex)) == 0) {
for(int x = 0; x < object->mHiddenMeshes.size(); x++) {
if(object->mHiddenMeshes[x] == nameIndex) {
Con::errorf("SkinOff(): Mesh %s already hidden", argv[2]);
return;
}
}
object->mHiddenMeshes.push_back(nameIndex);
object->setMaskBits(ShapeBase::HideMeshMask);
return;
}
}
}
}
Con::errorf("SkinOff(): Unable to hide mesh %s.", argv[2]);
}
}
ConsoleMethod( ShapeBase, SkinOn, void, 3, 3, "(string meshname)")
{
S32 i;
TSShape const* mShape = object->getShape();
if (argv[2] != '[[4fc733dd1b7b2]]') {
// look for the node in our hidden mesh array
// if we find it, erase it and flag that the array has changed
for(int x = 0; x < object->mHiddenMeshes.size(); x++) {
if (dStricmp(argv[2], mShape->getName(object->mHiddenMeshes[x])) == 0)
{
object->mHiddenMeshes.erase(x);
object->setMaskBits(ShapeBase::HideMeshMask);
return;
}
}
Con::errorf("SkinOn(): Couldn't find %s in the array. Possibly not hidden?", argv[2]);
}
}
// End of SkinOn() methodsAbout the author
Recent Blogs
• Dynamic GUI• Mob Look
• Faster Polysoup
• DreamGames and Titas
• Working with pickActionAnimation()
#2
03/28/2004 (8:06 am)
Looks great! Marked it for later perusal when we get to this point :)
#3
03/29/2004 (2:15 am)
Excellent! I can see many uses for this including damaged buildings etc. Thank you.
#4
John H.
03/29/2004 (1:26 pm)
This is a great resources! I wonder if this could work with a server side database? :)John H.
#5
03/29/2004 (3:37 pm)
Database for what? If it's useful, I'll add it while the code is still fresh in my head...
#6
One thing tough (since I'm the modeler) all I have to do is model ONE model with several different named meshes (that display different alterations of a specific bodypart) for this to work as expected ??
03/30/2004 (2:19 am)
Wow this is going straight into my project!!One thing tough (since I'm the modeler) all I have to do is model ONE model with several different named meshes (that display different alterations of a specific bodypart) for this to work as expected ??
#7
A quick model test: Take your model and clone it. Select your clone, scale x and y up by say 5%. Rig it the same as your original. You now have a player who gets fat.
03/30/2004 (4:31 am)
For a good example of how to model for this, grab a Ghoul model and import it into Max. Soldier of Fortune I/II, and StarWars Jedi Academy use Ghoul, maybe more do as well. You don't actually have to model fancy for this to work though. My models are just regular segmented at the moment. I can make just his hands disappear, or his torso, etc. A quick model test: Take your model and clone it. Select your clone, scale x and y up by say 5%. Rig it the same as your original. You now have a player who gets fat.
#8
That database portion was something I skimmed out of the book massive multiplayer development.
It also talks about using a base char model but different textures to acheive a variety of looks with very miminal memory overhead.
Anyway I planned to buy that book this week, all that skimming in the bookstore has just left me wanting more info. :))
John H. :)
03/30/2004 (5:14 am)
@ErikThat database portion was something I skimmed out of the book massive multiplayer development.
It also talks about using a base char model but different textures to acheive a variety of looks with very miminal memory overhead.
Anyway I planned to buy that book this week, all that skimming in the bookstore has just left me wanting more info. :))
John H. :)
#9
This code:
Is not very clear to me. Could you possibly re-write it with braces please? The indentation makes it seem as though it is supposed to be part of the FOR loop, yet the FOR loop has no braces, so this sits outside of it. On the other hand this doesn't use "i" so I would say from that that it isn't supposed to be in the for loop, in which case why is it indented? Its just confusing.
03/30/2004 (7:04 am)
Erik,This code:
for(i = 0; i < MaxMountedImages; i++)
if(!mMountedImageList[i ].dataBlock)
mask &= ~(ImageMaskN << i); // mask off mesh visibility if noth
ing to update
if(mHiddenMeshes.size() == 0)
mask &= ~HideMeshMask;Is not very clear to me. Could you possibly re-write it with braces please? The indentation makes it seem as though it is supposed to be part of the FOR loop, yet the FOR loop has no braces, so this sits outside of it. On the other hand this doesn't use "i" so I would say from that that it isn't supposed to be in the for loop, in which case why is it indented? Its just confusing.
#10
My masks have extra bits in them:
How many bits are available?
03/30/2004 (7:11 am)
Also, I can't seem to line up the masks that you specify. Your dont' match mine and my shapebase code is untouched. So i'm not 100% sure i'm putting things in the right spots and that worries me. What version of Torque did you do this against? IS it unmodified?My masks have extra bits in them:
enum ShapeBaseMasks {
NameMask = Parent::NextFreeMask,
DamageMask = Parent::NextFreeMask << 1,
NoWarpMask = Parent::NextFreeMask << 2,
MountedMask = Parent::NextFreeMask << 3,
CloakMask = Parent::NextFreeMask << 4,
ShieldMask = Parent::NextFreeMask << 5,
InvincibleMask = Parent::NextFreeMask << 6,
SkinMask = Parent::NextFreeMask << 7,
SoundMaskN = Parent::NextFreeMask << 8, ///< Extends + MaxSoundThreads bits
ThreadMaskN = SoundMaskN << MaxSoundThreads, ///< Extends + MaxScriptThreads bits
ImageMaskN = ThreadMaskN << MaxScriptThreads, ///< Extends + MaxMountedImage bits
NextFreeMask = ImageMaskN << MaxMountedImages
};if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask |
ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask |
ShieldMask | SkinMask)))
return retMask;How many bits are available?
#11
The mask bit itself can be anything you have available. 11 is the bit I actually use. I posted it as 7, since I think thats the next open on an unmodified source. I don't know how many are available, as I'm not a really a coder, I just play one on the web :)
My code was started at GG launch, with occasional updates to head. So anything I post is bound to differ somewhat.
03/30/2004 (9:16 am)
The for loop is correct as you've posted. Yes, my indentation is off. The code is contained in the 'if (mask & InitialUpdateMask) {' block, but not in the MaxMountedImages loop.The mask bit itself can be anything you have available. 11 is the bit I actually use. I posted it as 7, since I think thats the next open on an unmodified source. I don't know how many are available, as I'm not a really a coder, I just play one on the web :)
My code was started at GG launch, with occasional updates to head. So anything I post is bound to differ somewhat.
#12
03/30/2004 (9:59 am)
Thanks for the heads up Erik. I had backed out the partial changes, but now that I have that info I can go ahead and incorporate it. I was afraid there might only be 8 bits :)
#13
04/01/2004 (6:56 am)
Heh, wouldn't you know I just spent a few hours figuring out how to do this right before the resource was posted. But hey, I learned something. I've also verified that you do get your framerate back from hiding the meshes so its ok to go nuts. Now I'm just a little worried about my model getting too huge. Its already over 300kb without the severable limbs.
#14
Changeable clothes, faces, and hair plus severable limbs. Add in some scaling and you've got some really customizable characters.
04/02/2004 (5:44 pm)
Heres it is in action! http://www.filebox.vt.edu/users/ehartman/MeshHideAction.jpg Changeable clothes, faces, and hair plus severable limbs. Add in some scaling and you've got some really customizable characters.
#15
That screenshot is from this resource or your own?
@all
Can anyone explain to me how to make a model to use this resource? How do you meake different "skins" so you can toggle them?
04/02/2004 (5:53 pm)
@Eric HartmanThat screenshot is from this resource or your own?
@all
Can anyone explain to me how to make a model to use this resource? How do you meake different "skins" so you can toggle them?
#16
When making the model you basically just make seperate objects. Instead of making the character all one mesh, make 1 mesh for each leg and 1 for each arm and name each mesh accordingly.
04/02/2004 (8:28 pm)
The screenshot is from using my own code but the effect should be the same from this resource. When making the model you basically just make seperate objects. Instead of making the character all one mesh, make 1 mesh for each leg and 1 for each arm and name each mesh accordingly.
#17
// Group some of the uncommon stuff together. if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask [/i] | HideMeshMask[i]))) {
You need to reverse the i and /i in order for HideMeshMask to come up in italics. First time through I pasted it in thinking it was a variable array - boy did that cause some interesting errors! Teach me not to think through what somebody is doing.
04/26/2004 (9:33 am)
Eric - a small gotcha in the posted listing. In the section shapebase.cc you have part of the listing as...// Group some of the uncommon stuff together. if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask [/i] | HideMeshMask[i]))) {
You need to reverse the i and /i in order for HideMeshMask to come up in italics. First time through I pasted it in thinking it was a variable array - boy did that cause some interesting errors! Teach me not to think through what somebody is doing.
#18
Thanks,
Coz
04/27/2004 (4:05 am)
Anyone having problems with this causing Torque to hang during loading of objects? I will have to examine the code again later.Thanks,
Coz
#19
07/14/2004 (8:57 am)
Nevermind it was a rogue brace.
#20
Then i tried it on items. However no matter what mesh i tried to hide on an item, it says it cannot be hidden (like its not even found).
08/03/2004 (1:36 pm)
I added this , and then tried to hide bodymesh on the orc. It was added to the skinOff list, however it was still visible.Then i tried it on items. However no matter what mesh i tried to hide on an item, it says it cannot be hidden (like its not even found).
Torque Owner Cisor