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:
------------------------------------------------------
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
Another major change, that it doesn't apply changes immediately, you need to force updating network by calling
I've made it in a such way to save network. See example below.
------------------------------------------------------
Console Methods:
------------------------------------------------------
Notice 2:
This implementation is a bit different from original - the main thing is:
(quote from comments of original resource - by Orion Elenzil)
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
tsShapeInstance.cc
tsAnimate.cc
ShapeBase.h
ShapeBase.cc
This is a small improvement - if no meshes visible, the object will not be rendered! Another way of being "hidden" in game :)
Paste the following code at the end of shapeBase.cpp file:
Notice 3:
This is network-safe implementation, but in case you catch anything strange - please share with the community, fixes and improvements are welcomed!
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:
// 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:
/// 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.void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)
{
......
else
objInst->meshList = NULL;
objInst->object = obj;
#ifdef _res__hideMeshResource
objInst->forceHidden = false;
#endif
}
// construct ifl material objectsvoid 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;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 bitsvoid 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 attributesShapeBaseData::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()) { // SkinMaskPaste 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!
About the author
Game developer.
#42
Very clear, thank you very much.
Regards,
Nabarro
02/16/2009 (10:50 pm)
Very appreciated, Orion,Very clear, thank you very much.
Regards,
Nabarro
#43
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.
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
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...
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 
Associate Orion Elenzil
Real Life Plus
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!