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.
#22
seems the FileStream has changed & dosn't recognise ReadWrite anymore.
I haven't thouroughly tested this change but it seems to work.
Am sure someone with a bit more knowledge will be able to fix this properly as just commenting out the offending code isn't good practice :-)
12/19/2008 (5:08 am)
seems to work fine with tgea 1.8 if you change the following code from the end of shapeBase.cpp.:-void ShapeBase::ModelDump(){
//A little bit of test info on the layout of the model
[b][i]// 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");[/b][/i]
}seems the FileStream has changed & dosn't recognise ReadWrite anymore.
I haven't thouroughly tested this change but it seems to work.
Am sure someone with a bit more knowledge will be able to fix this properly as just commenting out the offending code isn't good practice :-)
#23
Resource updated!
12/19/2008 (5:30 am)
replace the ModelDump() function with the following code (made it 1.8-safe):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");
}Resource updated!
#24
This probably isn't a practical solution if you have hundreds of different pieces of armor that any of your base models can equip - is that correct?
01/20/2009 (5:23 am)
Does anyone have answers for Dante's questions?This probably isn't a practical solution if you have hundreds of different pieces of armor that any of your base models can equip - is that correct?
#25
02/13/2009 (10:38 am)
most MMORPG's use 5 or 10 base meshes plus 10-20 different skins giving 50-200 different armour types, and by adding a few different bumpmaps you increase the number of armour types even more, all with a few base meshes.
#26
Totally with you on combinations we could aquire with this. My only concern is around performance and resource allocation. I'm just wondering if someone has vetted those metrics vs. another method (such as limb swapping or equipable skinned meshes).
02/13/2009 (11:34 am)
@David,Totally with you on combinations we could aquire with this. My only concern is around performance and resource allocation. I'm just wondering if someone has vetted those metrics vs. another method (such as limb swapping or equipable skinned meshes).
#27
02/13/2009 (11:52 am)
dante, the project i work on uses the approach of having a single DTS model with all but a few meshes hidden combined with texture swapping, and i can definitely attest to its performance & resource allocation. where you might run into trouble is in the network overhead around ghosting down the names of all the various textures. what we ended up doing is making a system where each article of clothing (ie a Mesh/Texture pair) is identified by a SKU number, and so an entire outfit is described by a short list of SKUs instead of a long list of mesh & texture names. we currently have about 3,300 clothing items defined this way, using two model files (one per gender) and about 400 meshes per gender. another advantage of abstracting the mesh/texture stuff into something like a SKU is you can define SKUs which are outside the mesh/texture paradigm. eg perhaps a SKU represents a particle effect, or a mounted item.
#28
That's awesome you are fully utilizing texture swapping. I appologize for being unclear before, I'm just wondering the metrics for this approach versus others for mesh swapping. This is just theoretical, and more of a code question. I'm actually dealing with this on a pro industry mmo I'm working on right now too, as well as my indie project, so I'm really curious what anyone here thinks. I'll also have some more info on different approach metrics to share after vetting it with various programmers at work.
02/13/2009 (11:59 am)
Hi Orion,That's awesome you are fully utilizing texture swapping. I appologize for being unclear before, I'm just wondering the metrics for this approach versus others for mesh swapping. This is just theoretical, and more of a code question. I'm actually dealing with this on a pro industry mmo I'm working on right now too, as well as my indie project, so I'm really curious what anyone here thinks. I'll also have some more info on different approach metrics to share after vetting it with various programmers at work.
#29
it's difficult to imagine a method which would be more performant,
but i'm unfamiliar w/ limb swapping or equippable skinned meshes and what advantages they may have.
i forgot to mention,
another difficulty you may run into with this method if you want to use a Lot of meshes is the art tool chain. when we started to get a whole lot of meshes we had to modify max2dts to support exporting them.
02/13/2009 (12:12 pm)
the core of this resource seems to be introducing a new boolean "forceHidden" per mesh, networking that, and checking it in animateVisibility().it's difficult to imagine a method which would be more performant,
but i'm unfamiliar w/ limb swapping or equippable skinned meshes and what advantages they may have.
i forgot to mention,
another difficulty you may run into with this method if you want to use a Lot of meshes is the art tool chain. when we started to get a whole lot of meshes we had to modify max2dts to support exporting them.
#30
Anyway, totally I get you the hide mesh code itself is performant, but are we loading every mesh seperately on every character when we load that character? I assume that at least means longer load times (that's better than FPS drop). So, the data streaming to update each mesh is one possible hit, unless you only update it when a mesh is visable. The only other thing I'm wondering, and could be nothing, is whether torque tries to do anything with a mesh that is hidden in the draw pipeline, or does it completely ignore it?
02/13/2009 (1:15 pm)
I'm using Maya, it has a mesh referencing system, but it is a bit buggy in 2k8. 2k9 is supposed to be more stable, but I haven't tested it yet. Anyway, I've found for characters, max is very limited specifically for rigging. We actually made a switch to maya from max because of this.Anyway, totally I get you the hide mesh code itself is performant, but are we loading every mesh seperately on every character when we load that character? I assume that at least means longer load times (that's better than FPS drop). So, the data streaming to update each mesh is one possible hit, unless you only update it when a mesh is visable. The only other thing I'm wondering, and could be nothing, is whether torque tries to do anything with a mesh that is hidden in the draw pipeline, or does it completely ignore it?
#31
> are we loading every mesh seperately on every character ?
nope. the engine only keeps one instance of each DTS object, and re-animates it for each player which uses it. so if you have a DTS with 20 meshes in it and have 10 players who all use that same DTS, it's only 20 meshes, not 200.
> does torque try to do anything with a mesh that is hidden in the draw pipeline, or does it completely ignore it?
it completely ignores it.
it's neither animated (deformed around the skeleton) nor rendered.
i've done tests with 400+ AIPlayers where one way their DTS file contains maybe 100 meshes but we only display 5, and another way where the DTS file contains just those 5, and the framerate is identical.
02/13/2009 (2:02 pm)
Hi Dante -> are we loading every mesh seperately on every character ?
nope. the engine only keeps one instance of each DTS object, and re-animates it for each player which uses it. so if you have a DTS with 20 meshes in it and have 10 players who all use that same DTS, it's only 20 meshes, not 200.
> does torque try to do anything with a mesh that is hidden in the draw pipeline, or does it completely ignore it?
it completely ignores it.
it's neither animated (deformed around the skeleton) nor rendered.
i've done tests with 400+ AIPlayers where one way their DTS file contains maybe 100 meshes but we only display 5, and another way where the DTS file contains just those 5, and the framerate is identical.
#32
I didn't realize the game could still be able to distinguish a mesh as being the same even though it is embedded in seperate DTS files. Looks like I underestimated that ;).
02/13/2009 (2:43 pm)
Hey Orion, wow sweet! Thanks for informing me. I'm definitely going to try this method out. It is just my hobby project, but I really want to try different armor and such with very different geometry for fun. I didn't realize the game could still be able to distinguish a mesh as being the same even though it is embedded in seperate DTS files. Looks like I underestimated that ;).
#33
> even though it is embedded in seperate DTS files.
woops, i didn't mean to give that impression.
the scenario where it knows the mesh is the same
is when there is one DTS file but multiple players referencing it.
or say one ShapeBaseDataBlock but multiple ShapeBase objects using it.
02/13/2009 (3:14 pm)
> game could still be able to distinguish a mesh as being the same> even though it is embedded in seperate DTS files.
woops, i didn't mean to give that impression.
the scenario where it knows the mesh is the same
is when there is one DTS file but multiple players referencing it.
or say one ShapeBaseDataBlock but multiple ShapeBase objects using it.
#34
So, 10 player models can wear the bronze chest armor. The bronze chest armor has to be embedded in each player's DTS, is that right?
02/13/2009 (5:23 pm)
Oh ok, that's understood. How does that tie into hiding meshes though? Wouldn't skinned meshes that you can hide/unhide have to be embedded in a DTS? Or can you do this with multiple DTS?So, 10 player models can wear the bronze chest armor. The bronze chest armor has to be embedded in each player's DTS, is that right?
#35
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.
02/13/2009 (5:35 pm)
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.
#36
Thanks for all the info!
02/13/2009 (5:57 pm)
Gotcha, but unless you want to author every character in the same max or maya file, you'd need to had head leather in each unique player model's source project. So, in this case, Maya referencing would help you out a lot if you ever hit that kind of content requirement.Thanks for all the info!
#37
The avatar system for your project is very cool indeed. :)
I'm very curious how do you make your avatar show/unhide different meshes at the same time? I mean, in your avatar system, we can see your avatar appear in multiple small windows, while they are wearing different clothes/meshes at the same time.
If you show one mesh for your avatar, how can you make it show another mesh at the same time?
Very appreciated,
02/13/2009 (9:20 pm)
Hi Orion,The avatar system for your project is very cool indeed. :)
I'm very curious how do you make your avatar show/unhide different meshes at the same time? I mean, in your avatar system, we can see your avatar appear in multiple small windows, while they are wearing different clothes/meshes at the same time.
If you show one mesh for your avatar, how can you make it show another mesh at the same time?
Very appreciated,
#38
sure, i'd be happy to outline how we did the avatar-dressing thing,
but i'm afraid it wasn't really an off-the-shelf thing.
first, i changed GuiObjectViewCtrl so that it could render an object from the local client scenegraph, instead of a DTS file loaded separately. this was so that i could use the existing character animation stuff we had, instead of the somewhat simple subset GuiObjectView provides. This gives the main, large view of the character with ambient animation.
then, i made it so you could 'slave' one GuiObjectViewCtrl to another - ie, the slave would render the same object as the master, and from the same camera position. (although perhaps with a different camera lookat point & FOV etc). This gives the little thumbnails which render little copies of the character in the main window, although at this point they would all have the same clothing.
then, i made it so GuiObjectViewControl could have a list of Mesh/Texture pairs which should be applied to that particular version of the player. (Actually i use "SKUs", as described above) Before rendering the character, each little thumbnail first stores off the currently visible meshes and textures for the model, then applies its own meshes & textures to the model, then re-deforms the meshes to the skeleton, then renders the model, then restores the original meshes & textures back again. Note that this is moderately expensive due to the fact that you have to deform the mesh each time because it might be a different mesh. If you knew that it was the same mesh (ie if the change to the player was texture-only) then you could skip that. (hmm.. i should look into optimizing that!) Also the original mesh-hiding and texture-swapping resources weren't really intended to happen ~16 times per frame, more like once every few frames at most, and i think i had to do some optimization there to avoid some linear string searches and such.
hope that helps!
02/14/2009 (9:01 pm)
hey Nabarro - thanks !sure, i'd be happy to outline how we did the avatar-dressing thing,
but i'm afraid it wasn't really an off-the-shelf thing.
first, i changed GuiObjectViewCtrl so that it could render an object from the local client scenegraph, instead of a DTS file loaded separately. this was so that i could use the existing character animation stuff we had, instead of the somewhat simple subset GuiObjectView provides. This gives the main, large view of the character with ambient animation.
then, i made it so you could 'slave' one GuiObjectViewCtrl to another - ie, the slave would render the same object as the master, and from the same camera position. (although perhaps with a different camera lookat point & FOV etc). This gives the little thumbnails which render little copies of the character in the main window, although at this point they would all have the same clothing.
then, i made it so GuiObjectViewControl could have a list of Mesh/Texture pairs which should be applied to that particular version of the player. (Actually i use "SKUs", as described above) Before rendering the character, each little thumbnail first stores off the currently visible meshes and textures for the model, then applies its own meshes & textures to the model, then re-deforms the meshes to the skeleton, then renders the model, then restores the original meshes & textures back again. Note that this is moderately expensive due to the fact that you have to deform the mesh each time because it might be a different mesh. If you knew that it was the same mesh (ie if the change to the player was texture-only) then you could skip that. (hmm.. i should look into optimizing that!) Also the original mesh-hiding and texture-swapping resources weren't really intended to happen ~16 times per frame, more like once every few frames at most, and i think i had to do some optimization there to avoid some linear string searches and such.
hope that helps!
#39
I'm not sure I've grasped your idea. May I understand that like below?
...
[thumbnail #1 ]
Skin Off the current visible meshes/textures
Skin on the meshes/texture for thumbnail #1
render
[thumbnail #2 ]
Skin Off the current visible meshes/textures
Skin on the meshes/texture for thumbnail #2
render
[thumbnail #3]
...
Is that correct?
BTW, what's the meaning of "i changed GuiObjectViewCtrl so that it could render an object from the local client scenegraph, instead of a DTS file loaded separately"? What does "local client scenegraph" mean here? We just have gClientSceneGraph here, do you refer to this global variable?
Thank you very much.
02/15/2009 (8:50 pm)
Thanks Orion,I'm not sure I've grasped your idea. May I understand that like below?
...
[thumbnail #1 ]
Skin Off the current visible meshes/textures
Skin on the meshes/texture for thumbnail #1
render
[thumbnail #2 ]
Skin Off the current visible meshes/textures
Skin on the meshes/texture for thumbnail #2
render
[thumbnail #3]
...
Is that correct?
BTW, what's the meaning of "i changed GuiObjectViewCtrl so that it could render an object from the local client scenegraph, instead of a DTS file loaded separately"? What does "local client scenegraph" mean here? We just have gClientSceneGraph here, do you refer to this global variable?
Thank you very much.
#40
02/16/2009 (8:14 am)
Question: Is it possible to hide collision meshes? For example, if I wanted to have a wall (which you can walk on) that crumbled to 2/3 height at 2/3 damage level, could I hide the Col0 (and full wall) meshes and show the Col1 mesh along with 2/3 wall mesh? Or, would this cause problems in the exporter or ingame with collision detection? 
Torque Owner Dante Falcone
I'm looking to implement a strategy to achieve armor swapping, as this resource suggests, but much more along the lines of Morrowind or Oblivion. So, to see if this is the ideal method, I have the following questions:
1. Are all meshes in the player DTS loaded for every instance of that player object?
1b. For other player datablock objects that use the same DTS?
2. Are all the meshes in the player DTS loaded in memory?
3. Taking top 2 questions into account, is this solution scalable for N number of armor dts types?
The ideal method I've been brainstorming is to hijack the model rig/skin code and be able to swap meshes rigged to the same skeleton, but exported to different DTS files.
Thanks,
Dante