Game Development Community

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....
[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() methods
Page «Previous 1 2 3 4 5 Last »
#1
03/28/2004 (6:41 am)
Excellent Erik! I'll try this as soon as our modeller can out the various model parts.
#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
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
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
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
03/30/2004 (5:14 am)
@Erik

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. :)
#9
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
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
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
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
04/02/2004 (5:53 pm)
@Eric Hartman
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?
#16
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
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
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
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).
Page «Previous 1 2 3 4 5 Last »