Game Development Community

dev|Pro Game Development Curriculum

Hiding Meshes v2

by Fyodor -bank- Osokin · 09/24/2008 (6:49 am) · 46 comments

This is a second (improved) version of Hiding Meshes resource made for TGEA (can be easily back-ported to TGE with minor changes).
This resource allows you to toggle visibility of parts of your ShapeBase-based objects (like Vehicle or Player), like Clothes, Armor, etc.
This requires that your model is made with separate meshes, e.g. not with single object.
From original resource's description:
Quote: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.

------------------------------------------------------
Notice 1:
All changes are covered with #ifdef / #endif, so it's quite easy to implemented into your engine and to toggle this resource on/off via simple define in torqueConfig.h by commenting/uncommenting:

torqueConfig.h
// Hidden Meshes v2 resource
#define _res__hideMeshResource

Another major change, that it doesn't apply changes immediately, you need to force updating network by calling
%obj.updateMeshes();
after you have done toggling meshes.
I've made it in a such way to save network. See example below.

------------------------------------------------------
Console Methods:
ConsoleMethod( ShapeBase, updateMeshes, void, 2, 2, "() sets HideMesh mask for network updates")
ConsoleMethod( ShapeBase, MeshOffAll, void, 2, 2, "() Hide all meshes")
ConsoleMethod( ShapeBase, MeshOnAll, void, 2, 2, "() Show all meshes")
ConsoleMethod( ShapeBase, MeshOff, void, 3, 3, "(string meshname)")
ConsoleMethod( ShapeBase, MeshOn, void, 3, 3, "(string meshname)")
Debug console methods:
ConsoleMethod( ShapeBase, MeshOffList, void, 2, 2, "() List all not visible meshes")
ConsoleMethod( ShapeBase, MeshOnList, void, 2, 2, "List all visible meshes")
ConsoleMethod( ShapeBase, ModelDump, void, 2, 2, "() Dump known info on a model")
Usage:
%obj.MeshOffAll(); // Hide all meshes, so we can switch "on" only needed ones.
%obj.MeshOn("head"); // We need a head
%obj.MeshOn("armorTorso"); // armored torso
%obj.MeshOn("hips"); // hips part
%obj.MeshOn("legs"); // legs
%obj.MeshOn("armorBoots"); // armored boots
%obj.updateMeshes(); // set netMask so server will send changes on this object to all connections in scope
%obj.MeshOnList(); // looking at the console to check if all okay

------------------------------------------------------
Notice 2:
This implementation is a bit different from original - the main thing is:
(quote from comments of original resource - by Orion Elenzil)
Quote:If you're going to have mostly hidden meshes with only a few visible ones...
You can set it via datablock:
%datablock.invertHiddenMeshes = true;
This doesn't affect the scripting, you still do MeshOn() to make mesh visible. All the stuff is handled via engine for network saving.
You will need to set to true in case you have most of meshes invisible with only "some" visible. Otherwise keep it "false" (default).

------------------------------------------------------
Source changes:

tsShapeInstance.h
/// These are set up by default based on shape data
   struct MeshObjectInstance : ObjectInstance
   {
#ifdef _res__hideMeshResource
      bool forceHidden;
#endif
      TSMesh * const * meshList; ///< one mesh per detail level... Null entries allowed.

tsShapeInstance.cc
void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)
{
   ......
      else
         objInst->meshList = NULL;

      objInst->object = obj;
#ifdef _res__hideMeshResource
      objInst->forceHidden = false;
#endif
   }




   // construct ifl material objects

tsAnimate.cc
void TSShapeInstance::animateVisibility(S32 ss)
{
   ......
   S32 b = a + mShape->subShapeNumObjects[ss];
   for (i=a; i<b; i++)
#ifdef _res__hideMeshResource
      if (mMeshObjects[i].forceHidden)
         mMeshObjects[i].visible = 0.f;
      else
#endif
      if (beenSet.test(i))
         mMeshObjects[i].visible = mShape->objectStates[i].vis;

ShapeBase.h
struct ShapeBaseData : public GameBaseData {
   ......
   bool shadowEnable;
#ifdef _res__hideMeshResource
   bool invertHiddenMeshes;
#endif
   bool shadowCanMove;
   bool shadowCanAnimate;
InvincibleMask  = Parent::NextFreeMask << 6,
      SkinMask        = Parent::NextFreeMask << 7,
#ifdef _res__hideMeshResource
      HideMeshMask    = Parent::NextFreeMask << 8,
      SoundMaskN      = Parent::NextFreeMask << 9,       ///< Extends + MaxSoundThreads bits
#else
      SoundMaskN      = Parent::NextFreeMask << 8,       ///< Extends + MaxSoundThreads bits
#endif
      ThreadMaskN     = SoundMaskN  << MaxSoundThreads,  ///< Extends + MaxScriptThreads bits
void setSkinName(const char*);
   const char* getSkinName();
   /// @}

#ifdef _res__hideMeshResource
   /// @name HideMesh v2 resource
   /// @{
   Vector<S32> mToggledMeshes;               ///< The list of toggled meshes
protected:
   void updateToggledMeshes();               ///< Client-side function to update visibility of meshes in TSShapeInstance 
   bool isMeshToggled(S32 node);             ///< Returns true if the mesh is in toggled list

public:
   void MeshOffAll();                        ///< Hide all meshes
   void MeshOnAll();                         ///< Show all meshes
   void MeshToggleOnAll();                   ///< Add all meshes to "toggled" list
   void MeshToggleOffAll();                  ///< Clear "toggled" list, all meshes in default state
   void MeshOn(const char *);                ///< Show mesh
   void MeshOff(const char *);               ///< Hide mesh
   void MeshClearFromToggled(const char *);  ///< Clear specified mesh from toggled list
   void MeshAddToToggled(const char *);      ///< Add specified mesh to toggled list
#ifndef TORQUE_SHIPPING
   void MeshListToggled();                   ///< Dump list of toggled meshes into console
   void MeshListNonToggled();                ///< Dump list of non-toggled meshes into console
   void MeshOffList();                       ///< Dump list of hidden meshes
   void MeshOnList();                        ///< Dump list of visible meshes
   void ModelDump();                         ///< Save ./Model.dump report from TSDump
#endif
   const char * getMeshList(bool);           ///< Returns the space-separated list of meshes (visible or hidden)
   const char * getMaterialList();           ///< Returns material list
   /// @}

public:
#endif // #ifdef _res__hideMeshResource
   /// @name Basic attributes

ShapeBase.cc
ShapeBaseData::ShapeBaseData()
{
#ifdef _res__hideMeshResource
   invertHiddenMeshes = false;
#endif
   shadowEnable = false;
   shadowCanMove = false;
void ShapeBaseData::initPersistFields()
{
   ......
   addGroup("Render");
#ifdef _res__hideMeshResource
   addField("invertHiddenMeshes", TypeBool, Offset(invertHiddenMeshes, ShapeBaseData));
#endif
   addField("shapeFile",      TypeFilename, Offset(shapeName,      ShapeBaseData));
   addField("emap",           TypeBool,       Offset(emap,           ShapeBaseData));
   endGroup("Render");
void ShapeBaseData::packData(BitStream* stream)
{
   Parent::packData(stream);

   if(stream->writeFlag(computeCRC))
      stream->write(mCRC);
   
#ifdef _res__hideMeshResource
   stream->writeFlag(invertHiddenMeshes);
#endif
   stream->writeFlag(shadowEnable);
   stream->writeFlag(shadowCanMove);
void ShapeBaseData::unpackData(BitStream* stream)
{
   Parent::unpackData(stream);
   computeCRC = stream->readFlag();
   if(computeCRC)
      stream->read(&mCRC);

#ifdef _res__hideMeshResource
   invertHiddenMeshes = stream->readFlag();
#endif
   shadowEnable = stream->readFlag();
   shadowCanMove = stream->readFlag();

This is a small improvement - if no meshes visible, the object will not be rendered! Another way of being "hidden" in game :)
bool ShapeBase::prepRenderImage(SceneState* state, const U32 stateKey,
                                const U32 startZone, const bool modifyBaseState)
{
   ...
   if( ( getDamageState() == Destroyed ) && ( !mDataBlock->renderWhenDestroyed ) )
   {  
      PROFILE_END();
      return false;
   }

#ifdef _res__hideMeshResource
   // HideMesh additions / optimization
   // We don't need to to prepRenderImage if no meshes visible
   if (mShapeInstance)
   {
      if (!mDataBlock->invertHiddenMeshes)
      {
         if (mToggledMeshes.size() == mShapeInstance->mMeshObjects.size())
         {
            PROFILE_END();
            return false;
         }
      }
      else
      {
         if (mToggledMeshes.size()==0)
         {
            PROFILE_END();
            return false;
         }
      }
   }
#endif // #ifdef _res__hideMeshResource
   // Select detail levels on mounted items
   // but... always draw the control object's mounted images
   // in high detail (I can't believe I'm commenting this hack :)
   F32 saveError = TSShapeInstance::smScreenError;

U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
   ......
   if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask |
#ifdef _res__hideMeshResource
         HideMeshMask |
#endif
         ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask | ShieldMask | SkinMask)))
   ......
   // Group some of the uncommon stuff together.
   if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask | SkinMask 
#ifdef _res__hideMeshResource
      | HideMeshMask 
#endif
      ))) {
   ......
         stream->write(mInvincibleSpeed);
      }
#ifdef _res__hideMeshResource
      if (stream->writeFlag(mask & HideMeshMask)) {
         stream->writeInt(mToggledMeshes.size(), 7);
         for(int x = 0; x < mToggledMeshes.size(); x++)
            stream->writeInt(mToggledMeshes[x], 8);
      }
#endif
      if (stream->writeFlag(mask & SkinMask)) {
void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)
{
   ......
         setupInvincibleEffect(time, speed);
      }
#ifdef _res__hideMeshResource
      if (stream->readFlag())
      { // HideMeshMask
         mToggledMeshes.clear();
         S32 count = stream->readInt(7);
         for(S32 x = 0; x < count; x++)
            mToggledMeshes.push_back(stream->readInt(8));
         //Con::warnf("hiddenMesh::: %d", count);
         updateToggledMeshes();
      }
#endif
      if (stream->readFlag()) {  // SkinMask

Paste the following code at the end of shapeBase.cpp file:
#ifdef _res__hideMeshResource
//--------------------------------------------------------------------------
// Start of HideMesh v2 Part
//--------------------------------------------------------------------------

void ShapeBase::updateToggledMeshes()
{
   if(!isGhost())
   {
      Con::errorf("ShapeBase::updateToggledMeshes() not allowed to be called on server!");
      return;
   }
   if(!mShapeInstance)
     return;
   S32 s = mShapeInstance->mMeshObjects.size();
   if (!mDataBlock->invertHiddenMeshes)
   {
      for(S32 x = 0; x < s; x++)
      {
         S32 nameIndex = mShapeInstance->mMeshObjects[x].object->nameIndex;
         F32 visible = 1.f;
         if(isMeshToggled(nameIndex))
            visible = 0.f;
         mShapeInstance->mMeshObjects[x].visible = visible;
         mShapeInstance->mMeshObjects[x].forceHidden = (visible == 0.f);
      }
   }
   else
   {
      for(S32 x = 0; x < s; x++)
      {
         S32 nameIndex = mShapeInstance->mMeshObjects[x].object->nameIndex;
         F32 visible = 0.f;
         if (isMeshToggled(nameIndex))
            visible = 1.f;
         mShapeInstance->mMeshObjects[x].visible = visible;
         mShapeInstance->mMeshObjects[x].forceHidden = (visible == 0.f);
      }
   }
}

bool ShapeBase::isMeshToggled(S32 node)
{
   for(int x = 0; x < mToggledMeshes.size(); x++)
      if(mToggledMeshes[x] == node)
         return true;
   return false;
}

ConsoleMethod( ShapeBase, updateMeshes, void, 2, 2, "() sets HideMesh mask for network updates")
{
   if(!object->isServerObject()) return;
   object->setMaskBits(ShapeBase::HideMeshMask);
}

void ShapeBase::MeshToggleOffAll()
{
   mToggledMeshes.clear();
}

void ShapeBase::MeshToggleOnAll()
{
   S32 i;
   TSShape const* mShape = getShape();
   mToggledMeshes.clear();
   for (i=0; i< mShape->objects.size(); i++)
   {
      if (mShape->objects[i].nameIndex>=0)
         mToggledMeshes.push_back(mShape->objects[i].nameIndex);
   }
}

void ShapeBase::MeshOffAll()
{
   if(!mDataBlock->invertHiddenMeshes)
      MeshToggleOnAll();
   else
      MeshToggleOffAll();
}

ConsoleMethod( ShapeBase, MeshOffAll, void, 2, 2, "() Hide all meshes")
{
   if(!object->isServerObject()) return;
   object->MeshOffAll();
}

void ShapeBase::MeshOnAll()
{
   if(!mDataBlock->invertHiddenMeshes)
      MeshToggleOffAll();
   else
      MeshToggleOnAll();
}

ConsoleMethod( ShapeBase, MeshOnAll, void, 2, 2, "() Show all meshes")
{
   if(!object->isServerObject()) return;
   object->MeshOnAll();
}

void ShapeBase::MeshClearFromToggled(const char * meshName)
{
   TSShape const* mShape = getShape();
   for(S32 x = 0; x < mToggledMeshes.size(); x++)
   {
      if (dStricmp(meshName, mShape->getName(mToggledMeshes[x])) == 0)
      {
         mToggledMeshes.erase(x);
         return;
      }
   }
}

void ShapeBase::MeshAddToToggled(const char * meshName)
{
   TSShape const* mShape = getShape();
   for (S32 i=0; i< mShape->objects.size(); i++)
   {
      S32 nameIndex = mShape->objects[i].nameIndex;
      if (nameIndex>=0)
      {
         if (dStricmp(meshName, mShape->getName(nameIndex)) == 0)
         {
            for(int x = 0; x < mToggledMeshes.size(); x++)
            {
               if(mToggledMeshes[x] == nameIndex)
                  return;
            }
            mToggledMeshes.push_back(nameIndex);
            return;
         }
      }
   }
}

void ShapeBase::MeshOff(const char * meshName)
{
   if (meshName == '\0')
   {
      Con::errorf("MeshOff(): Unable to toggle visibility for mesh %s.", meshName);
      return;
   }
   if(!mDataBlock->invertHiddenMeshes)
      MeshAddToToggled(meshName);
   else
      MeshClearFromToggled(meshName);
}

ConsoleMethod( ShapeBase, MeshOff, void, 3, 3, "(string meshname)")
{
   if(!object->isServerObject()) return;
   object->MeshOff(argv[2]);
}

void ShapeBase::MeshOn(const char * meshName)
{
   if (meshName == '\0') return;
   if(!mDataBlock->invertHiddenMeshes)
      MeshClearFromToggled(meshName);
   else
      MeshAddToToggled(meshName);
}

ConsoleMethod( ShapeBase, MeshOn, void, 3, 3, "(string meshname)")
{
   if(!object->isServerObject()) return;
   object->MeshOn(argv[2]);
}

// Some development-handy functions
#ifndef TORQUE_SHIPPING

void ShapeBase::MeshListToggled()
{
   for (int i=0; i<mShapeInstance->getShape()->objects.size(); i++)
   {
      const char * skinName = "";
      S32 nameIndex = mShapeInstance->getShape()->objects[i].nameIndex;         
      if (nameIndex>=0 && (isMeshToggled(nameIndex))) 
      {
         skinName = mShapeInstance->getShape()->getName(nameIndex);
         Con::errorf("nameIndex %3d: %s ", nameIndex, skinName);
      }
   }
}
void ShapeBase::MeshListNonToggled()
{
   for (int i=0; i<mShapeInstance->getShape()->objects.size(); i++)
   {
      const char * skinName = "";
      S32 nameIndex = mShapeInstance->getShape()->objects[i].nameIndex;         
      if (nameIndex>=0 && (!isMeshToggled(nameIndex))) 
      {
         skinName = mShapeInstance->getShape()->getName(nameIndex);
         Con::errorf("nameIndex %3d: %s ", nameIndex, skinName);
      }
   }
}

void ShapeBase::MeshOffList()
{
   if(!mDataBlock->invertHiddenMeshes)
      MeshListToggled();
   else
      MeshListNonToggled();
}

ConsoleMethod( ShapeBase, MeshOffList, void, 2, 2, "() List all not visible meshes")
{
   object->MeshOffList();
}

void ShapeBase::MeshOnList()
{
   if(!mDataBlock->invertHiddenMeshes)
      MeshListNonToggled();
   else
      MeshListToggled();
}

ConsoleMethod( ShapeBase, MeshOnList, void, 2, 2, "List all visible meshes")
{
   object->MeshOnList();
}

void ShapeBase::ModelDump()
{
   //A little bit of test info on the layout of the model
   FileStream st;   
#if (defined TORQUE_APP_VERSION) && (TORQUE_APP_VERSION>=1800)
   if(gResourceManager->openFileForWrite(st, "Model.dump"))
#else
   st.open("Model.dump", FileStream::ReadWrite);
   if ((st.getStatus() == Stream::Ok) || (st.getStatus() == Stream::EOS))
#endif
      if (mShapeInstance)
         mShapeInstance->dump(st);
      else
         Con::errorf("No shapeinstance");
   else
      Con::errorf("Error opening dump file");
}

ConsoleMethod( ShapeBase, ModelDump, void, 2, 2, "() Dump known info on a model")
{
   object->ModelDump();
}

#endif // #ifndef TORQUE_SHIPPING

const char * ShapeBase::getMeshList(bool vis)
{
   if (mDataBlock->invertHiddenMeshes) vis = !vis;
   char *ret = Con::getReturnBuffer(2048);
   ret[0] = '\0';
   for (int i=0; i<mShapeInstance->getShape()->objects.size(); i++)
   {
      const char * skinName = "";
      S32 nameIndex = mShapeInstance->getShape()->objects[i].nameIndex;         
      // Some boolean logic play
      if (nameIndex>=0 && ( (!vis && isMeshToggled(nameIndex)) || (vis && !isMeshToggled(nameIndex)) ))
      {
         skinName = mShapeInstance->getShape()->getName(nameIndex);
         dSprintf(ret, 2048, "%s %s", ret, skinName);
         //Con::errorf("nameIndex %3d: %s ", nameIndex, skinName);
      }
   }
   return ret;
}

ConsoleMethod( ShapeBase, getMeshList, const char *, 3, 3, "(bool onlyVisibles) Get a list of meshes")
{
   return object->getMeshList(dAtob(argv[2]));
}

//--------------------------------------------------------------------------
// End of HideMesh v2 Part
//--------------------------------------------------------------------------
#endif
------------------------------------------------------
Notice 3:
This is network-safe implementation, but in case you catch anything strange - please share with the community, fixes and improvements are welcomed!
Page«First 1 2 3 Next»
#41
02/16/2009 (9:22 pm)
Hi Nabarro -

i believe the stock GuiObjectViewControl renders a DTS file loaded specifically by the GioObjectViewCtrl, so it's not part of the regular scene graph. (right, gClientSceneGraph) but for my purposes i wanted to render the local player object as it was in the scenegraph. I don't think this is critical to the method, so you may well be able to skip that part. Your summary of the method is spot-on, except for the additional detail in my case of remembering to store the original meshes/textures at the very beginning and restoring them at the end, because this is the actual player object being modified.

John -
great question. i have no idea.
i believe the answer would be no, because if you implement this resource and then dump all the meshes, collision is not included. i *think*. try it!
#42
02/16/2009 (10:50 pm)
Very appreciated, Orion,

Very clear, thank you very much.

Regards,
Nabarro
#43
03/13/2009 (8:17 am)
how would I go about hiding/showing different weapon meshes for first or third person views?
I have modelled a weapon in high detail with arms and hands for first person, and a low detail one, sans arms/hands for third person, they are both in the same dts file, and the meshes are called "weaponX_fps" and "weaponX_tps" respectfuly,
how/where would I put the calls to hide/unhide the meshes if in third or first person view?
this part has me stumped.
#44
06/21/2009 (3:01 pm)
works in 1.8.1 AFX :-)
#45
08/23/2009 (5:29 am)
Quote:
say you have a single soldier.dts file which has eight meshes in it:

1. head w/ bronze
2. head w/ leather
3. arms w/ bronze
4. arms w/ leather
5. chest w/ bronze
6. chest w/ leather
7. legs w/ bronze
8. legs w/ leather

you would always be hiding 4 meshes and always showing 4:
show one of 1 & 2
show one of 3 & 4
show one of 5 & 6
show one of 7 & 8

for a total of sixteen possible combinations, all from one DTS file.

technical noob questions:
1. You use 1 1024x1024 texture, or 4 1024x1024 textures for each player, or maybe 1 texture for each part?
2. Use many players (ex. 64) like this, dont down the framerate?, is good only for single-player?

my questions is because i was read someday in the docs, that if you use 2 textures in one dts model, the engine render 2 times de mesh, so if the dts have 2.000 triangles, for torque is like 4.000 triangles...

in my current project i dont know if use this resource or make 2 different dts and interchange in gameplay from one to another...
#46
09/13/2009 (2:05 am)
I Understand the working example and exactly how to use the meshon hide and show. But how exactly do you declare which section of the body to use? Are we splitting up the whole model here into little pieces and sections ? Im not quiet following, im sorry but
Page«First 1 2 3 Next»