tge/lib/dtsSDKPlus/ShapeMimic.cpp
2017-04-17 06:17:10 -06:00

3961 lines
138 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
// The ShapeMimic tries to hold court in both the App world and
// the Torque three-space world. It holds a shape tree isomorphic
// to what the shape will look like when exported, but maintains
// links to App objects and delays computing certain things
// until the tsshape is finally created in generateShape().
#ifdef _MSC_VER
#pragma warning(disable : 4786 4018)
#endif
#include "ShapeMimic.h"
#include "appConfig.h"
#include "translucentSort.h"
#include "nvStripWrap.h"
#include "stripper.h"
#include "dtsdecimator.h"
// See comment in dtsBitMatrix.h for why we don't use this.
// #define USE_NVIDIA_STRIPPER
namespace DTS {
std::vector<Quaternion*> ShapeMimic::nodeRotCache;
std::vector<Point3D*> ShapeMimic::nodeTransCache;
std::vector<Quaternion*> ShapeMimic::nodeScaleRotCache;
std::vector<Point3D*> ShapeMimic::nodeScaleCache;
std::vector<AppNode*> ShapeMimic::cutNodes;
std::vector<AppNode*> ShapeMimic::cutNodesParents;
//-----------------------------------------------------------
//
//-----------------------------------------------------------
ShapeMimic::ShapeMimic()
{
}
ShapeMimic::~ShapeMimic()
{
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
Shape * ShapeMimic::generateShape()
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return NULL;
// this may be our second time around, make sure
// certain variables and lists are initialized:
nodes.clear();
// cull as needed
AppConfig::SetProgress(0.0f, 0.0f, "Collapsing transforms...");
collapseTransforms();
// no frills construction
Shape * shape = new Shape;
// step one: generate bounds
AppConfig::SetProgress(0.0f, 0.07f, "Generating bounds...");
generateBounds(shape);
// step two: generate detail levels sort subTrees according to dl
AppConfig::SetProgress(0.0f, 0.14f, "Generating detail levels...");
generateDetails(shape);
// step three: generate subTrees (tree structure w/o objects connected)
AppConfig::SetProgress(0.0f, 0.21f, "Generating subtrees...");
generateSubtrees(shape);
// step four: generate objects -- hook up to nodes
AppConfig::SetProgress(0.0f, 0.28f, "Generating objects...");
generateObjects(shape);
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return shape;
// at this point, we have a shape with all the details,
// nodes, and objects set up. We have also added a bunch
// of names. In addition, the meshIndexList is set up and
// the meshes list is set up.
// step five: set default states (including mesh data)
AppConfig::SetProgress(0.0f, 0.35f, "Generating default states...");
generateDefaultStates(shape);
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return shape;
// step six: generate ifl materials
AppConfig::SetProgress(0.0f, 0.42f, "Generating Ifl materials...");
generateIflMaterials(shape);
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return shape;
// step seven: animation
if (AppConfig::GetEnableSequences())
{
AppConfig::SetProgress(0.0f, 0.49f, "Generating sequences...");
generateSequences(shape);
}
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return shape;
// step eight: generate material list
AppConfig::SetProgress(0.0f, 0.56f, "Generating material list...");
generateMaterialList(shape);
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return shape;
// step eight: generate the skins
AppConfig::SetProgress(0.0f, 0.63f, "Generating skins...");
generateSkins(shape);
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return shape;
// step nine: optimize the meshes (but only if exporting them)
if (AppConfig::GetExportOptimized())
{
AppConfig::SetProgress(0.0f, 0.71f, "Optimizing meshes...");
optimizeMeshes(shape);
}
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return shape;
// step ten: convert sortObjects
AppConfig::SetProgress(0.0f, 0.78f, "Converting sort objects...");
convertSortObjects(shape);
AppConfig::SetProgress(0.0f, 0.85f, "Initiating shapes...");
initShape(shape);
if (!AppConfig::IsExportError())
{
AppConfig::SetProgress(0.0f, 0.92f, "Dumping shape...");
dumpShape(shape);
}
AppConfig::SetProgress(0.0f, 1.0f, "Conversion complete.");
return shape;
}
void ShapeMimic::initShape(Shape * shape)
{
// Select smallest visible pixel size and detail level.
// Don't use setSmallestSize method on shape because
// it seems to be doing something else.
S32 i;
shape->smallestSize = 10E10f;
shape->smallestDetailLevel = shape->detailLevels.size()-1;
for (i=0; i<shape->detailLevels.size(); i++)
{
if (shape->detailLevels[i].size < shape->smallestSize)
{
shape->smallestSize = shape->detailLevels[i].size;
shape->smallestDetailLevel = i;
}
}
S32 numss = shape->subshapes.size();
// compute subShape numNodes,
S32 prev = shape->nodes.size();
for (i=numss-1; i>=0; i--)
{
shape->subshapes[i].numNodes = prev - shape->subshapes[i].firstNode;
prev = shape->subshapes[i].firstNode;
}
// compute subShape numObjects
prev = shape->objects.size();
for (i=numss-1; i>=0; i--)
{
shape->subshapes[i].numObjects = prev - shape->subshapes[i].firstObject;
prev = shape->subshapes[i].firstObject;
}
// compute subShape numDecals -- don't do decals...so this should be easy
for (i=0; i<numss; i++)
shape->subshapes[i].numDecals = shape->subshapes[i].firstDecal = 0;
// make sure bounds are built on all the meshes
for (i=0; i<shape->meshes.size(); i++)
shape->meshes[i].calculateBounds();
// ts shape quaternions are transposes of dtsSdk versions
for (i=0; i<shape->nodeDefRotations.size(); i++)
shape->nodeDefRotations[i][3] *= -1.0f;
for (i=0; i<shape->nodeRotations.size(); i++)
shape->nodeRotations[i][3] *= -1.0f;
for (i=0; i<shape->nodeScaleRotsArbitrary.size(); i++)
shape->nodeScaleRotsArbitrary[i][3] *= -1.0f;
for (i=0; i<shape->groundRotations.size(); i++)
shape->groundRotations[i][3] *= -1.0f;
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateBounds(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppMesh * boundsMesh = boundsNode->getNumMesh() ? boundsNode->getMesh(0) : NULL;
if (!boundsMesh)
{
AppConfig::SetExportError("12", "Bounds node has no mesh.");
return;
}
Matrix<4,4,F32> meshMat = boundsMesh->getMeshTransform(AppTime::DefaultTime());
Matrix<4,4,F32> nodeMat = boundsNode->getNodeTransform(AppTime::DefaultTime());
zapScale(nodeMat);
Matrix<4,4,F32> objectOffset = nodeMat.inverse() * meshMat;
Box boundsBox = boundsMesh->getBounds(objectOffset);
F32 radius = boundsMesh->getRadius(objectOffset);
F32 tubeRadius = boundsMesh->getTubeRadius(objectOffset);
// now set up shape bounds parameters
shape->center = (boundsBox.min + boundsBox.max) * 0.5f;
shape->bounds.min = boundsBox.min;
shape->bounds.max = boundsBox.max;
shape->radius = radius;
shape->tubeRadius = tubeRadius;
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateDetails(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// if nothing to export...
if (subtrees.empty())
{
AppConfig::SetExportError("1", "Nothing to export.");
return;
}
// each subTree has a set of detail levels
// just plug them in and sort the list
S32 i,j;
for (i=0; i<subtrees.size(); i++)
{
AppConfig::SetProgress(((F32)i + 1.0f) / (F32)subtrees.size(), 0.14f, "Generating detail levels...");
Subtree * subtree = subtrees[i];
for (j=0; j<subtree->validDetails.size(); j++)
{
DetailLevel detail;
detail.subshape = i;
detail.objectDetail = j;
detail.size = (F32) subtree->validDetails[j];
detail.avgError = -1;
detail.maxError = -1;
detail.polyCount = 0; // not currently using this
detail.name = addName(subtree->detailNames[j],shape);
//if (!strnicmp(subtree->detailNames[j],"BB::",4) || subtree->detailNodes[]->)
if( subtree->detailNodes[j]->isBillboard() )
generateBillboardDetail(subtree->detailNodes[j],detail);
shape->detailLevels.push_back(detail);
}
}
// sort detail levels based on projection size
sortTSDetails(shape->detailLevels);
}
void ShapeMimic::generateBillboardDetail(AppNode * detailNode, DetailLevel & detail)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// this is a billboard detail, this works a little differently...
detail.subshape = -1;
// determine properties...
S32 numEquatorSteps = 4;
S32 numPolarSteps = 0;
F32 polarAngle;
S32 dl = 0;
S32 dim = 64;
bool includePoles = true;
detailNode->getInt("bb_equator_steps",numEquatorSteps);
detailNode->getInt("bb_polar_steps",numPolarSteps);
polarAngle = F32(M_PI)/(F32)(((numPolarSteps>>1)<<1)+5);
detailNode->getFloat("bb_polar_angle",polarAngle);
detailNode->getInt("bb_dl",dl);
detailNode->getInt("bb_dim",dim);
detailNode->getBool("bb_include_poles",includePoles);
// set properties...
U32 props = 0;
props |= numEquatorSteps;
props |= (numPolarSteps>>1) << 7;
props |= ((S32)(64.0f * polarAngle/(M_PI*0.5f))) << 13;
props |= dl<<19;
props |= dim<<23;
props |= includePoles ? 1<<31 : 0;
detail.objectDetail = props;
}
S32 __cdecl compareTSDetails( void const *e1, void const *e2 )
{
const DetailLevel * d1 = (const DetailLevel*)e1;
const DetailLevel * d2 = (const DetailLevel*)e2;
if (d1->size > d2->size)
return -1;
if (d2->size > d1->size)
return 1;
return 0;
}
void ShapeMimic::sortTSDetails(std::vector<DetailLevel> & details)
{
if (details.size())
qsort(&details[0],details.size(),sizeof(DetailLevel),compareTSDetails);
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
S32 ShapeMimic::addFaceMaterial(AppMesh * mesh,S32 matIdx)
{
Material mat;
if (!mesh->getMaterial(matIdx,mat))
// code no material as -1...
return -1;
S32 retIdx = addMaterial(mat);
// if we just added an ifl material, check to see if the ifl mimic
// already exists...if not, create it
if (mat.flags & Material::IFLMaterial)
{
S32 i;
for (i=0; i<iflList.size(); i++)
{
if (iflList[i]->materialSlot == retIdx)
// already there
break;
}
if (i==iflList.size())
{
// no ifl mimic, add it now
IflMimic * ifl = new IflMimic;
ifl->appIfl = mesh->getIfl(matIdx);
ifl->materialSlot = retIdx;
iflList.push_back(ifl);
}
}
return retIdx;
}
S32 ShapeMimic::addMaterial(Material mat)
{
// return the index to this material if we already have it saved off
// otherwise create a new entry...
// material considered new if anything is different (name, flags, or any of the maps)
// exception #1 is if the incoming item is an auxiliary item (a refelctionMap, bumpMap,
// or detail map) in that case, it will ignore everything but the name...
// exception #2 is if the incoming item is an ifl (in which case it is added to back)
bool auxiliary = mat.flags & Material::AuxiliaryMask ? true : false;
bool isTranslucent = mat.flags & Material::Translucent ? true : false;
bool wraps = mat.flags & (Material::SWrap|Material::TWrap) ? true : false;
bool isIfl = mat.flags & Material::IFLMaterial ? true : false;
if (AppConfig::GetNoMipMap())
mat.flags |= Material::NoMipMap;
if (AppConfig::GetNoMipMapTranslucent() && isTranslucent)
mat.flags |= Material::NoMipMap;
if (AppConfig::GetZapBorder() && isTranslucent && !wraps)
// material is translucent and doesn't wrap -- zap border
mat.flags |= Material::MipMapZeroBorder;
// get rid of path -- drop everything before last slash or :
mat.name = std::string(getFileBase(mat.name.c_str()));
for (S32 i=0; i<materials.size(); i++)
{
// first check name
if (_stricmp(mat.name.c_str(),materials[i].name.c_str()))
continue;
// good enough for auxiliary
if (auxiliary)
{
if (materials[i].flags & Material::AuxiliaryMask)
// if we're using an auxiliary map for two purpose, let it be known...
materials[i].flags |= (mat.flags & Material::AuxiliaryMask);
return i;
}
// a reflection map of -1 gets mapped to i...
if (mat.reflectance!=-1 && materials[i].reflectance!=mat.reflectance)
continue;
// check the rest
if (materials[i].flags==mat.flags &&
materials[i].bump == mat.bump &&
materials[i].detail == mat.detail &&
isEqual(materials[i].detailScale,mat.detailScale,0.01f) &&
isEqual(materials[i].reflection,mat.reflection,0.01f))
return i;
}
// always have a reflection map, even if it's ourself
if (mat.reflectance==-1 && !auxiliary)
mat.reflectance = materials.size();
// new one -- save the texture map material and return new index
materials.push_back(mat);
return materials.size()-1;
}
void ShapeMimic::addNodeRotation(NodeMimic * curNode, const AppTime & time, Shape * shape, bool blend, Quaternion & rot, bool defaultVal)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDNodeStates,avar("Adding%snode rotation at time %s for node \"%s\".\r\n",
blend ? " blend " : " ", time.getStr(), curNode->appNode->getName()));
if (!defaultVal)
shape->nodeRotations.push_back(rot);
else
shape->nodeDefRotations.push_back(rot);
AppConfig::PrintDump(PDNodeStateDetails,avar(" rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",rot.x(),rot.y(),rot.z(),rot.w()));
// all added, add separator to dump file...
AppConfig::PrintDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeTranslation(NodeMimic * curNode, const AppTime & time, Shape * shape, bool blend, Point3D & trans, bool defaultVal)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDNodeStates,avar("Adding%snode translation at time %s for node \"%s\".\r\n",
blend ? " blend " : " ", time.getStr(), curNode->appNode->getName()));
if (!defaultVal)
shape->nodeTranslations.push_back(trans);
else
shape->nodeDefTranslations.push_back(trans);
AppConfig::PrintDump(PDNodeStateDetails,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",trans.x(),trans.y(),trans.z()));
// all added, add separator to dump file...
AppConfig::PrintDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeUniformScale(NodeMimic * curNode, const AppTime & time, Shape * shape, bool blend, F32 scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDNodeStates,avar("Adding%snode scale at time %s for node \"%s\".\r\n",
blend ? " blend " : " ", time.getStr(), curNode->appNode->getName()));
shape->nodeScalesUniform.push_back(scale);
AppConfig::PrintDump(PDNodeStateDetails,avar(" uniform scale: %3.5f\r\n",scale));
// all added, add separator to dump file...
AppConfig::PrintDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeAlignedScale(NodeMimic * curNode, const AppTime & time, Shape * shape, bool blend, Point3D & scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDNodeStates,avar("Adding%snode scale at time %s for node \"%s\".\r\n",
blend ? " blend " : " ", time.getStr(), curNode->appNode->getName()));
shape->nodeScalesAligned.push_back(scale);
AppConfig::PrintDump(PDNodeStateDetails,avar(" aligned scale: x=%3.5f, y=%3.5f, z=%3.5f\r\n",scale.x(),scale.y(),scale.z()));
// all added, add separator to dump file...
AppConfig::PrintDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeArbitraryScale(NodeMimic * curNode, const AppTime & time, Shape * shape, bool blend, Quaternion & qrot, Point3D & scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDNodeStates,avar("Adding%snode scale at time %s for node \"%s\".\r\n",
blend ? " blend " : " ", time.getStr(), curNode->appNode->getName()));
shape->nodeScaleRotsArbitrary.push_back(qrot);
shape->nodeScalesArbitrary.push_back(scale);
AppConfig::PrintDump(PDNodeStateDetails,avar(" arbitrary scale rot: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",qrot.x(),qrot.y(),qrot.z(),qrot.w()));
AppConfig::PrintDump(PDNodeStateDetails,avar(" arbitrary scale factor: x=%3.5f, y=%3.5f, z=%3.5f\r\n",scale.x(),scale.y(),scale.z()));
// all added, add separator to dump file...
AppConfig::PrintDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
S32 ShapeMimic::addName(const char * name, Shape * shape)
{
tweakName(&name);
return shape->addName(name);
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateSubtrees(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// this should already have been caught, but...
if (subtrees.empty())
{
AppConfig::SetExportError("1", "Nothing to export.");
return;
}
// generate a set of nodes for each subtree
for (S32 i=0; i<subtrees.size(); i++)
{
AppConfig::SetProgress(((F32)i + 1.0f) / (F32)subtrees.size(), 0.21f, "Generating subtrees...");
Subtree * subtree = subtrees[i];
subtree->start.number = -1; // translates to NULL...
// this means branches will have no parent
NodeMimic * curNode = subtree->start.child;
// mark the beginning of the subshape
shape->subshapes.push_back(Subshape());
shape->subshapes.back().firstNode = shape->nodes.size();
// traverse depth first
while (curNode)
{
curNode->number = shape->nodes.size();
// add node to shape
shape->nodes.push_back(Node());
Node & tsnode = shape->nodes.back();
tsnode.name = addName(curNode->appNode->getName(),shape);
tsnode.parent = curNode->parent->number;
// set up ShapeMimic::Object with the right ts node index
for (S32 j=0;j<curNode->objects.size(); j++)
curNode->objects[j]->tsNodeIndex = curNode->number;
// add NodeMimic to nodes list to keep track of order of nodes in shape
nodes.push_back(curNode);
// figure out where to go next
curNode = findNextNode(curNode);
}
}
}
NodeMimic * ShapeMimic::findNextNode(NodeMimic * cur)
{
if (cur->child)
return cur->child;
if (cur->sibling)
return cur->sibling;
while (cur->parent)
{
if (cur->parent->sibling)
return cur->parent->sibling;
cur = cur->parent;
}
// done...
return NULL;
}
void ShapeMimic::collapseTransforms()
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDPass3,"\r\nThird pass: Collapsing unneeded nodes...\r\n\r\n");
cutNodes.clear();
cutNodesParents.clear();
Subtree * subtree;
for (S32 i=0; i<subtrees.size(); i++)
{
AppConfig::SetProgress(((F32)i + 1.0f) / (F32)subtrees.size(), 0.0f, "Collapsing transforms...");
subtree = subtrees[i];
NodeMimic * mimicNode = subtree->start.child;
while (mimicNode)
{
if (mimicNode==&subtree->start)
{
// this should just never happen...
AppConfig::SetExportError("13", "Assertion failed: Illegal condition.");
return;
}
// figure out where to go next now
// because we might end up cutting
// out the current node
NodeMimic * nextNode = findNextNode(mimicNode);
// cut?
if (cut(mimicNode))
{
AppConfig::PrintDump(PDPass3,avar("Removing node \"%s\"\r\n",mimicNode->appNode->getName()));
snip(mimicNode);
}
mimicNode = nextNode;
}
}
}
bool ShapeMimic::cut(NodeMimic * mimicNode)
{
const char * name = mimicNode->appNode->getName();
// search always export list
if (AppConfig::AlwaysExport(mimicNode->appNode))
return false;
// search never export list
if (AppConfig::NeverExport(mimicNode->appNode))
return true;
// if transform collapse is false, only collapse explicitly named nodes (in neverExport list)
if (!AppConfig::GetAllowCollapse())
return false;
// not in either list -- cut if no object and not dummy
return (mimicNode->objects.empty() && !mimicNode->appNode->isDummy());
}
void ShapeMimic::snip(NodeMimic * nodeMimic)
{
// if nodeMimic has a mesh, we want to make sure there is no animation between it
// and it's parent...can't do that yet (no sequence) so add to a list and check later
if (!nodeMimic->objects.empty())
{
cutNodes.push_back(nodeMimic->appNode);
cutNodesParents.push_back(nodeMimic->parent->appNode);
}
// get rid of nodeMimic, bring children up to nodeMimic level
// they will sit between nodeMimic's siblings
// go to parent, find me
// point parent->child or prev-sibling->sibling pointer to my child
NodeMimic * parent = nodeMimic->parent;
NodeMimic * n;
if (parent->child == nodeMimic)
parent->child = nodeMimic->child ? nodeMimic->child : nodeMimic->sibling;
else
{
n = parent->child;
while (n->sibling != nodeMimic)
n = n->sibling;
n->sibling = nodeMimic->child ? nodeMimic->child : nodeMimic->sibling;
}
// point my children's parent pointer to my parent
n = nodeMimic->child;
if (n)
{
n->parent = nodeMimic->parent;
while (n->sibling)
{
n = n->sibling;
n->parent = nodeMimic->parent;
}
// point my last child's sibling point to my sibling pointer
n->sibling = nodeMimic->sibling;
}
// attach my objects to my parent
for (S32 i=0; i<nodeMimic->objects.size(); i++)
{
parent->objects.push_back(nodeMimic->objects[i]);
parent->objects.back()->appTSParent = parent->appNode;
}
delete nodeMimic;
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateObjects(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
S32 i,j;
// index into shapes meshes
S32 nextMeshIndex = 0;
// set object priority -- requires running through all the faces of
// all the meshes and extracting the materials...
setObjectPriorities(objectList);
// sort the objectList by subTree and priority
sortObjectList(objectList);
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// initialize array that indexes first object in subshape
for (i=0; i<subtrees.size(); i++)
shape->subshapes[i].firstObject = -1;
// reserve enough memory for all the possible meshes
// so that we don't shift in memory while building vector
S32 maxmeshes = shape->detailLevels.size() * objectList.size();
shape->meshes.reserve(maxmeshes);
// go through mesh list and add objects as we go
for (i=0; i<objectList.size(); i++)
{
AppConfig::SetProgress(((F32)i + 1.0f) / (F32)objectList.size(), 0.28f, "Generating objects...");
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
ObjectMimic * object = objectList[i];
if (!AppConfig::GetAllowUnusedMeshes() && !object->validDetails)
{
AppConfig::SetExportError("16", avar("Mesh \"%s\" not hooked up to shape.",object->fullName));
return;
}
// we may have cut out our actual parent...if so, we need to update object offset
if (object->appParent != object->appTSParent && object->isBone)
{
// trying to cut out a bone node ... not allowed
AppConfig::SetExportError("7", avar("Cannot collapse node \"%s\" because it is a bone.",object->appParent->getName()));
return;
}
// if object not in shape, skip it
if (!object->validDetails || object->isBone)
{
// don't need it, don't want it
delete object;
delElementAtIndex(objectList,i);
i--;
continue;
}
std::vector<S32> * validDetails = object->validDetails;
shape->objects.push_back(Object());
Object & tsobj = shape->objects.back();
tsobj.name = addName(object->name,shape);
tsobj.numMeshes = validDetails->size();
tsobj.firstMesh = shape->meshes.size();
tsobj.node = object->tsNodeIndex;
// is this the first object for this subshape...
if (shape->subshapes[object->subtreeNum].firstObject == -1)
shape->subshapes[object->subtreeNum].firstObject = shape->objects.size()-1;
S32 k,prevk = -1;
for (j=0; j<object->numDetails; j++)
{
for (k=0; k<validDetails->size(); k++)
if ((*validDetails)[k]==object->details[j].size)
break;
if (k==validDetails->size() && !AppConfig::GetAllowUnusedMeshes())
{
// ooh, this mesh is an invalid detail size
AppConfig::SetExportError("41", avar("Mesh \"%s\" was found with invalid detail (%i)",object->name,object->details[j].size));
return;
}
// if this is an invalid detail size get rid of it here
if (k==validDetails->size())
{
delete object->details[j].mesh;
for (k=j;k+1<object->numDetails;k++)
object->details[k]=object->details[k+1];
object->numDetails--;
j--;
continue;
}
// add NULL meshes for all the unused detail levels
for (S32 l=prevk+1; l<k; l++)
shape->meshes.push_back(Mesh(Mesh::T_Null));
prevk=k;
// fill in some data for later use
object->details[j].mesh->meshNum = shape->meshes.size();
if (object->details[j].mesh->skinMimic)
object->details[j].mesh->skinMimic->meshNum = shape->meshes.size();
// now hook up this mesh...
if (object->details[j].mesh->sortedObject && !object->details[j].mesh->skinMimic)
shape->meshes.push_back(Mesh(Mesh::T_Sorted));
else if (object->details[j].mesh->skinMimic)
shape->meshes.push_back(Mesh(Mesh::T_Skin));
else
shape->meshes.push_back(Mesh(Mesh::T_Standard));
shape->meshes.back().numFrames = 0;
shape->meshes.back().matFrames = 0;
if (object->details[j].mesh->billboard)
{
shape->meshes.back().setFlag(Mesh::Billboard);
if (object->details[j].mesh->appMesh->isBillboardZAxis())
shape->meshes.back().setFlag(Mesh::BillboardZ);
}
// get address of tsMesh -- note: important that mesh
// vector doesn't shift in memory (see above for how we
// make sure this doesn't happen).
object->details[j].mesh->tsMesh = &shape->meshes.back();
}
// may have rid ourselves of all the meshes above...
// if so, delete this object and continue
if (object->numDetails==0)
{
delete object;
delElementAtIndex(objectList,i);
shape->objects.pop_back();
i--;
continue;
}
// for any remaining null meshes, decrement object count
for (j=prevk+1; j<validDetails->size(); j++)
tsobj.numMeshes--;
}
// some subtrees may not have objects on them...
// make sure subShapeFirstObject array is valid
S32 prev = shape->objects.size();
for (i=subtrees.size()-1; i>=0; i--)
{
if (shape->subshapes[i].firstObject == -1)
shape->subshapes[i].firstObject = prev;
prev = shape->subshapes[i].firstObject;
}
// now that the address of all the tsobjects are
// set (since no longer incrementing objects vector)
// set up pointers back to these objects from the
// ShapeMimic versions...
S32 tsObjIndex = 0;
for (i=0; i<objectList.size(); i++)
{
ObjectMimic * obj = objectList[i];
// if object not in shape, skip it as we did above
if (!obj->validDetails)
obj->tsObject = NULL;
else
{
obj->tsObjectIndex = tsObjIndex;
obj->tsObject = &shape->objects[tsObjIndex++];
}
}
}
void ShapeMimic::setObjectPriorities(std::vector<ObjectMimic*> & objects)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
S32 i,j;
for (i=0; i<objects.size(); i++)
{
ObjectMimic * om = objects[i];
// assign priority as follows:
// 1. if it has translucency, set the highest 2 bits so it will occur after all non-translucent items
// but if it is also a sortObject, only set the highest bit so that it will occur before other
// translucent objects
// 2. if it has exactly 1 material set next bit and put material index into next 14 bits
// so all objects that use exactly that material occur consecutively
// 3. put tsnodeIndex into final 16 bits so objects hanging off same node will be consecutive,
// all else (1&2) being equal
om->priority = (U32) om->tsNodeIndex;
// use highest detail mesh we can find...
for (j=0;j<om->numDetails;j++)
if (om->details[j].mesh)
break;
if (j==om->numDetails)
// not sure what this would mean, but don't want to crash
continue;
// if bone object, set priority to 0
if (om->isBone)
{
om->priority = 0;
continue;
}
AppMesh * appMesh = om->details[j].mesh->appMesh;
if (!appMesh)
// not sure what this would mean, but don't want to crash
continue;
bool hasTranslucent = false;
bool hasMultiple = false;
bool isSortObject = om->details[j].mesh->sortedObject;
S32 matIndex = -1;
AppMeshLock lock = appMesh->lockMesh(AppTime::DefaultTime(),Matrix<4,4,F32>::identity());
for (j=0; j<appMesh->getNumFaces(); j++)
{
// add material for face j
S32 mi = addFaceMaterial(appMesh,appMesh->getFaceMaterial(j));
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
if (mi==-1 || mi==matIndex)
continue;
if (matIndex!=-1)
hasMultiple=true;
matIndex = mi;
if (!(matIndex & Primitive::NoMaterial) && (materials[matIndex].flags & Material::Translucent))
hasTranslucent = true;
}
if (hasTranslucent && !isSortObject)
om->priority |= 3 << 30;
else if (hasTranslucent && isSortObject)
om->priority |= 2 << 30;
if (!hasMultiple)
{
om->priority |= 1 << 29;
om->priority |= (matIndex & 0x0FFF) << 16;
}
}
}
S32 __cdecl compareObjectMimics( void const *e1, void const *e2 )
{
const ObjectMimic * om1 = *(const ObjectMimic**)e1;
const ObjectMimic * om2 = *(const ObjectMimic**)e2;
if (om1->subtreeNum < om2->subtreeNum)
return -1;
else if (om1->priority < om2->priority)
return -1;
else if (om2->subtreeNum < om1->subtreeNum)
return 1;
else if (om2->priority < om1->priority)
return 1;
else
return 0;
}
void ShapeMimic::sortObjectList(std::vector<ObjectMimic*> & olist)
{
if (olist.size())
qsort(&olist[0],olist.size(),sizeof(ObjectMimic*),compareObjectMimics);
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateDefaultStates(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
U32 i;
AppConfig::PrintDump(PDObjectStates,"\r\nAdd default object states...\r\n\r\n");
// visit all the objects in order
for (i=0; i<objectList.size(); i++)
{
ObjectMimic * obj = objectList[i];
// if object not in shape, skip it
if (!obj->validDetails)
continue;
generateObjectState(obj,AppTime::DefaultTime(),shape,true,true);
}
AppConfig::PrintDump(PDNodeStates,"\r\nAdd default node states...\r\n\r\n");
// iterate through the nodes
for (i=0; i<nodes.size(); i++)
{
NodeMimic * curNode = nodes[i];
Quaternion rot;
Point3D trans;
Quaternion srot; // won't matter
Point3D scale; // should be uniform
// create affine decomposition for node+parent at default time
decomp_affine(curNode->appNode->getNodeTransform(AppTime::DefaultTime()),&curNode->child0);
if (curNode->parent && curNode->parent->appNode)
decomp_affine(curNode->parent->appNode->getNodeTransform(AppTime::DefaultTime()),&curNode->parent0);
else
{
Matrix<4,4,F32> m = Matrix<4,4,F32>::identity();
decomp_affine(m,&curNode->parent0);
}
generateNodeTransform(curNode,AppTime::DefaultTime(),false,AppTime::DefaultTime(),rot,trans,srot,scale);
addNodeRotation(curNode,AppTime::DefaultTime(),shape,false,rot,true);
addNodeTranslation(curNode,AppTime::DefaultTime(),shape,false,trans,true);
if (!isEqual(scale,Point3D(1,1,1),0.01f))
{
AppConfig::SetExportError("17", "Assertion failed: scale on default transform");
return;
}
}
}
void ShapeMimic::generateObjectState(ObjectMimic * om, const AppTime & time, Shape * shape, bool addFrame, bool addMatFrame)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDObjectStates,avar("Adding object state to %i detail level(s) of mesh \"%s\".\r\n",om->numDetails,om->name));
if (addFrame)
AppConfig::PrintDump(PDObjectStates,"Adding frame.\r\n");
shape->objectStates.push_back(ObjectState());
ObjectState & os = shape->objectStates.back();
os.frame = 0;
os.matFrame = 0;
os.vis = om->inTreeMesh ? om->inTreeMesh->getVisValue(time) : 1.0f;
if (os.vis < 0.0f)
os.vis = 0.0f;
else if (os.vis > 1.0f)
os.vis = 1.0f;
if (os.vis < 0.01f || os.vis > 0.99f)
AppConfig::PrintDump(PDObjectStateDetails,avar("Object is%svisible.\r\n",os.vis>0.5f ? " " : " not "));
else
AppConfig::PrintDump(PDObjectStateDetails,avar("Object visibility = %5.3f out of 1.\r\n",os.vis));
if (addFrame || addMatFrame)
{
generateFrame(om,time,addFrame,addMatFrame);
// must have highest detail level...
if (!om->details[0].mesh->tsMesh || om->details[0].mesh->tsMesh->getType() == Mesh::T_Null)
{
AppConfig::SetExportError("18", avar("Missing highest detail level on mesh \"%s\".",om->name));
return;
}
// set the frame number for the object state
os.frame = om->details[0].mesh->tsMesh->numFrames - 1;
os.matFrame = om->details[0].mesh->tsMesh->matFrames - 1;
if (os.frame<0)
os.frame=0;
if (os.matFrame<0)
os.matFrame=0;
}
// all added, add separator to dump file...
AppConfig::PrintDump(PDObjectStates|PDObjectStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::generateFrame(ObjectMimic * om, const AppTime & time, bool addFrame, bool addMatFrame)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
if (om->isBone)
{
AppConfig::SetExportError("19", "Assertion failed: bone should no longer be on node");
return;
}
if (om->isSkin)
// don't generate frame
return;
S32 i,dl;
for (dl=0; dl<om->numDetails; dl++)
{
Mesh * tsMesh = om->details[dl].mesh->tsMesh;
AppMesh * appMesh = om->details[dl].mesh->appMesh;
Matrix<4,4,F32> & objectOffset = om->details[dl].mesh->objectOffset;
F32 multiResPercent = om->details[dl].multiResPercent;
// if first frame then compute object offset
if (tsMesh->numFrames==0)
{
// compute object offset -- need to compute offset vs. node in shape
// tree, not node the mesh hangs off of in the app
// Also note, node transform will have scale stripped off in shape,
// so we zapScale before accounting for that transform.
Matrix<4,4,F32> meshMat = appMesh->getMeshTransform(AppTime::DefaultTime());
Matrix<4,4,F32> nodeMat = om->inTreeNode->getNodeTransform(AppTime::DefaultTime());
zapScale(nodeMat);
objectOffset = nodeMat.inverse() * meshMat;
// print out object offsets?
if (AppConfig::GetDumpMask() & PDObjectOffsets)
{
AffineParts parts;
decomp_affine(objectOffset,&parts);
AppConfig::PrintDump(PDObjectOffsets,avar("Object offset transform for mesh dl=%i:\r\n",dl));
AppConfig::PrintDump(PDObjectOffsets,avar(" scale: x=%3.5f, y=%3.5f, z=%3.5f\r\n",parts.scale.x(),parts.scale.y(),parts.scale.z()));
AppConfig::PrintDump(PDObjectOffsets,avar(" stretch rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",parts.scaleRot.x(),parts.scaleRot.y(),parts.scaleRot.z(),parts.scaleRot.w()));
AppConfig::PrintDump(PDObjectOffsets,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",parts.trans.x(),parts.trans.y(),parts.trans.z()));
AppConfig::PrintDump(PDObjectOffsets,avar(" actual rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",parts.rot.x(),parts.rot.y(),parts.rot.z(),parts.rot.w()));
if (parts.sign<0)
AppConfig::PrintDump(PDObjectOffsets, " ---determinant negative---\r\n");
}
}
std::vector<Primitive> faces;
std::vector<Point3D> verts;
std::vector<Point3D> norms;
std::vector<Point2D> tverts;
std::vector<U16> indices;
std::vector<U32> smooth;
std::vector<U32> vertId;
AppMeshLock lock = appMesh->lockMesh(time,objectOffset);
appMesh->generateFaces(faces,verts,tverts,indices,smooth,norms,&vertId);
if (AppConfig::IsExportError()) return;
if (tsMesh->numFrames==0)
{
// first frame, copy faces into mesh
tsMesh->primitives = faces;
tsMesh->indices = indices;
tsMesh->vertsPerFrame = verts.size();
om->details[dl].mesh->numVerts = verts.size();
om->details[dl].mesh->smoothingGroups = smooth;
om->details[dl].mesh->vertId = vertId;
// make sure all the materials are added
for (S32 j=0; j<faces.size(); j++)
{
// add material for face j
S32 mi = addFaceMaterial(appMesh,tsMesh->primitives[j].type&Primitive::NoMaterial ? -1 : tsMesh->primitives[j].type&Primitive::MaterialMask);
// replace appmesh material index with ts material index
tsMesh->primitives[j].type &= ~Primitive::MaterialMask;
if (mi<0)
tsMesh->primitives[j].type |= Primitive::NoMaterial;
else
tsMesh->primitives[j].type |= mi;
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError())
return;
}
}
else
{
// not first frame, make sure topology of this frame is same as first frame
// NOTE: if multi-frame then can't be multi-res...
bool error = false;
if (vertId.size()==om->details[dl].mesh->vertId.size())
{
for (i=0;i<vertId.size();i++)
if (vertId[i]!=om->details[dl].mesh->vertId[i])
break;
if (i!=vertId.size())
error=true;
}
else
error=true;
for (i=0; i<faces.size(); i++)
{
if (tsMesh->primitives.size() != faces.size())
// only need to check this once really, but code simpler this way
break;
if (tsMesh->indices.size() != indices.size())
// different number of indices/verts -- should hit prior break in that case...
break;
// make sure tsMesh->face[i] is same as face[i]
if (tsMesh->primitives[i].firstElement!=faces[i].firstElement ||
tsMesh->primitives[i].type!=faces[i].type)
break;
}
if (i!=faces.size() || error)
{
AppConfig::SetExportError("20", avar("Mesh topology is animated on mesh \"%s\".",appMesh->getName()));
return;
}
}
if (addFrame)
{
// copy verts...
for (i=0; i<verts.size(); i++)
tsMesh->verts.push_back(verts[i]);
// copy normals...
for (i=0; i<verts.size(); i++)
{
if (norms.size())
tsMesh->normals.push_back(norms[i]);
else
// not important what...gets overwritten later
tsMesh->normals.push_back(Point3D(0,0,1));
}
tsMesh->numFrames++;
}
if (addMatFrame)
{
// copy tverts...
for (i=0; i<tverts.size(); i++)
tsMesh->tverts.push_back(tverts[i]);
tsMesh->matFrames++;
}
}
}
void ShapeMimic::generateNodeTransform(NodeMimic * curNode, const AppTime & time,
bool blend, const AppTime & blendReferenceTime,
Quaternion & rot, Point3D & trans, Quaternion & qrot, Point3D & scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
if (blend)
getBlendNodeTransform(curNode->appNode,curNode->parent->appNode,curNode->child0,curNode->parent0,time,blendReferenceTime,rot,trans,qrot,scale);
else
getLocalNodeTransform(curNode->appNode,curNode->parent->appNode,curNode->child0,curNode->parent0,time,rot,trans,qrot,scale);
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateIflMaterials(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// if none to make...
if (iflList.empty())
return;
AppConfig::PrintDump(PDSequences,avar("\r\nAdding %i ifl materials...\r\n\r\n",iflList.size()));
for (S32 i=0; i<iflList.size(); i++)
{
if( iflList[i]->appIfl )
{
shape->IFLmaterials.push_back(IFLMaterial());
IFLMaterial & iflMaterial = shape->IFLmaterials.back();
iflMaterial.name = addName(getFileBase(iflList[i]->appIfl->getFilename()),shape);
iflMaterial.slot = iflList[i]->materialSlot;
AppConfig::PrintDump(PDSequences,avar("Adding ifl material \"%s\".\r\n",iflList[i]->appIfl->getFilename()));
}
}
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateSequences(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
AppConfig::PrintDump(PDSequences,avar("\r\nAdding %i sequences...\r\n\r\n",sequences.size()));
for (S32 i=0; i<sequences.size(); i++)
{
AppConfig::SetProgress(((F32)i + 1.0f) / (F32)sequences.size(), 0.49f, "Generating sequences...");
AppSequence * appSeq = sequences[i];
if (appSeq==NULL)
{
AppConfig::SetExportError("21", "Assertion failed. Null sequence");
return;
}
AppSequenceData seqData;
appSeq->getSequenceData(&seqData);
shape->sequences.push_back(Sequence());
Sequence & seq = shape->sequences.back();
constructInPlace(&seq);
const char * name = appSeq->getName();
seq.nameIndex = addName(name,shape);
AppConfig::PrintDump(PDSequences,avar("Adding sequence %i named \"%s\"\r\n",i,name));
appSeq->setTSSequence(&seq);
// determine which nodes/objects are controlled by this sequence
S32 rotCount, transCount, uniformScaleCount, alignedScaleCount, arbitraryScaleCount, objectCount, iflCount;
setNodeMembership(shape,seq,seqData,rotCount,transCount,uniformScaleCount,alignedScaleCount,arbitraryScaleCount);
setObjectMembership(shape,seq,seqData,objectCount);
setIflMembership(shape,seq,seqData,iflCount);
S32 scaleCount = getmax(uniformScaleCount,getmax(alignedScaleCount,arbitraryScaleCount));
S32 nodeCount = getmax(rotCount,getmax(transCount,scaleCount));
const char * scaleType;
if (arbitraryScaleCount)
scaleType=" (arbitary scale)";
else if (alignedScaleCount)
scaleType=" (aligned scale)";
else if (uniformScaleCount)
scaleType=" (uniform scale)";
else
scaleType="";
if (AppConfig::IsExportError()) return;
// supply some dump information
if (!seqData.cyclic)
AppConfig::PrintDump(PDSequences,"One-shot sequence. ");
if (seqData.blend)
AppConfig::PrintDump(PDSequences,"Blend sequence. ");
AppConfig::PrintDump(PDSequences,avar("Enabled animation: %c%c%c%c%c%c%c%c%c, Forced animation: %c%c%c, Default priority = %i.\r\n",
seqData.enableMorph ? 'M' : ' ',
seqData.enableVis ? 'V' : ' ',
seqData.enableTransform ? 'T' : ' ',
seqData.enableUniformScale ? 'U' : ' ',
seqData.enableArbitraryScale ? 'A' : ' ',
seqData.enableIFL ? 'I' : ' ',
seqData.forceMorph ? 'M' : ' ',
seqData.forceVis ? 'V' : ' ',
seqData.forceTransform ? 'T' : ' ',
seqData.forceScale ? 'S' : ' ',
seqData.priority));
if (seqData.ignoreGround)
AppConfig::PrintDump(PDSequences,"Ignoring ground transform.\r\n");
AppConfig::PrintDump(PDSequences,avar("Duration = %3.5f, secPerFrame = %3.5f, # frames = %i\r\n",seqData.duration.getF32(),seqData.delta.getF32(),seqData.numFrames));
AppConfig::PrintDump(PDSequences,avar("Sequence includes %i nodes, %i objects, and %i ifl materials\r\n",nodeCount,objectCount,iflCount));
AppConfig::PrintDump(PDSequences,avar(" %i rotations, %i translations, %i scales%s\r\n\r\n",rotCount,transCount,scaleCount,scaleType));
// these methods generate the animation data...each of
// them operates on the most recently created sequence
generateNodeAnimation(shape,seq,seqData);
generateObjectAnimation(shape,seq,seqData);
generateGroundAnimation(shape,seq,seqData);
generateFrameTriggers(shape,seq,seqData,appSeq);
if (testCutNodes(seqData))
return;
}
}
S32 ShapeMimic::setObjectMembership(Shape * shape, Sequence & seq, AppSequenceData & seqData, S32 & objectCount)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return 0;
// clear out all object membership...
setMembershipArray(seq.matters.vis,seqData.forceVis,0,objectList.size());
setMembershipArray(seq.matters.frame,seqData.forceMorph,0,objectList.size());
setMembershipArray(seq.matters.matframe,seqData.forceTVert,0,objectList.size());
// don't do any work we don't have to...
bool doVis = seqData.enableVis && !seqData.forceVis;
bool doMorph = seqData.enableMorph && !seqData.forceMorph;
bool doTVert = seqData.enableTVert && !seqData.forceTVert;
// add objects to the subsets...
for (S32 i=0; i<objectList.size(); i++)
{
// the node to perform membership tests on
AppMesh * testMesh = objectList[i]->inTreeMesh;
if (objectList[i]->isSkin)
{
// in case force was set
setMembershipArray(seq.matters.frame,false,i);
setMembershipArray(seq.matters.matframe,false,i);
testMesh = objectList[i]->getSkin();
if (!testMesh)
continue;
}
if (doVis && testMesh->animatesVis(seqData))
setMembershipArray(seq.matters.vis,true,i);
if (objectList[i]->isSkin)
continue;
if (doTVert && testMesh->animatesMatFrame(seqData))
setMembershipArray(seq.matters.matframe,true,i);
if (doMorph && testMesh->animatesFrame(seqData))
setMembershipArray(seq.matters.frame,true,i);
}
// how many objects are in the set?
objectCount=0;
for (S32 j=0; j<objectList.size(); j++)
if (seq.matters.frame[j] ||
seq.matters.matframe[j] ||
seq.matters.vis[j])
objectCount++;
return objectCount;
}
void ShapeMimic::setNodeMembership(Shape * shape, Sequence & seq, AppSequenceData & seqData,
S32 & rotCount, S32 & transCount,
S32 & uniformScaleCount, S32 & alignedScaleCount, S32 & arbitraryScaleCount)
{
// if already encountered an error, then
// we'll just go through the motions
rotCount = transCount = uniformScaleCount = alignedScaleCount = arbitraryScaleCount = 0;
if (AppConfig::IsExportError()) return;
// decide node membership
S32 skipScaleCount = 0;
S32 skipTransCount = 0;
setMembershipArray(seq.matters.rotation,seqData.forceTransform,0,nodes.size());
setMembershipArray(seq.matters.translation,seqData.forceTransform,0,nodes.size());
setMembershipArray(seq.matters.scale,seqData.forceScale,0,nodes.size());
if (seqData.forceTransform || seqData.forceScale)
{
// clear nodes that are set not to animate
for (S32 i=0; i<nodes.size(); i++)
{
if (AppConfig::NeverAnimate(nodes[i]->appNode))
{
if (seqData.forceTransform)
{
setMembershipArray(seq.matters.rotation,false,i);
setMembershipArray(seq.matters.translation,false,i);
skipTransCount++;
}
if (seqData.forceScale)
{
setMembershipArray(seq.matters.scale,false,i);
skipScaleCount++;
}
}
}
return;
}
if (!seqData.enableTransform && !seqData.enableUniformScale && !seqData.enableArbitraryScale)
// not animating transforms, so no nodes are members
return;
// this shouldn't be allowed, but check anyway...
if (seqData.numFrames<2)
return;
// Note: this fills the cache with current sequence data.
// Methods that get called later (e.g.,
// generateNodeAnimation) use this info (and assume it's set).
fillNodeTransformCache(nodes,seq,seqData);
// test to see if the transform changes over the interval
// in order to decide whether to animate the transform in 3space
// we don't use app's mechanism for doing this because it functions
// different in different apps and we do some special stuff with scale.
setRotationMembership(shape,seq,seqData,rotCount);
setTranslationMembership(shape,seq,seqData,transCount);
setScaleMembership(seq,seqData,arbitraryScaleCount,alignedScaleCount,uniformScaleCount);
// adjust counts by non-rotating nodes (in case of force transform)
// add scale flags to sequence
rotCount -= skipTransCount;
transCount -= skipTransCount;
if (arbitraryScaleCount)
{
arbitraryScaleCount -= skipScaleCount;
seq.flags |= Sequence::ArbitraryScale;
}
if (alignedScaleCount)
{
alignedScaleCount -= skipScaleCount;
seq.flags |= Sequence::AlignedScale;
}
if (uniformScaleCount)
{
uniformScaleCount -= skipScaleCount;
seq.flags |= Sequence::UniformScale;
}
}
void ShapeMimic::setRotationMembership(Shape * shape, Sequence & seq, AppSequenceData & seqData, S32 & rotCount)
{
// if already encountered an error, then
// we'll just go through the motions
rotCount = 0;
if (AppConfig::IsExportError()) return;
if (seqData.forceTransform || !seqData.enableTransform)
{
rotCount = !seqData.enableTransform ? 0 : nodes.size();
return;
}
rotCount = 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (AppConfig::NeverAnimate(nodes[i]->appNode))
continue;
// first rotation
Quaternion * firstRot = &nodeRotCache[i][0];
Quaternion * prevRot = firstRot;
Quaternion & defaultRot = shape->nodeDefRotations[i];
if (!(*firstRot==defaultRot))
{
setMembershipArray(seq.matters.rotation,true,i);
rotCount++;
continue;
}
for (S32 frame=1; frame<seqData.numFrames; frame++)
{
Quaternion * curRot = &nodeRotCache[i][frame];
if (!(*curRot==*prevRot) || !(*curRot==*firstRot))
{
setMembershipArray(seq.matters.rotation,true,i);
rotCount++;
break;
}
prevRot = curRot;
}
}
}
void ShapeMimic::setTranslationMembership(Shape * shape, Sequence & seq, AppSequenceData & seqData, S32 & transCount)
{
// if already encountered an error, then
// we'll just go through the motions
transCount = 0;
if (AppConfig::IsExportError()) return;
if (seqData.forceTransform || !seqData.enableTransform)
{
transCount = !seqData.enableTransform ? 0 : nodes.size();
return;
}
transCount = 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (AppConfig::NeverAnimate(nodes[i]->appNode))
continue;
// first rotation
Point3D * firstTrans = &nodeTransCache[i][0];
Point3D * prevTrans = firstTrans;
Point3D & defaultTrans = shape->nodeDefTranslations[i];
if (!isEqual(*firstTrans,defaultTrans,0.001f))
{
setMembershipArray(seq.matters.translation,true,i);
transCount++;
continue;
}
for (S32 frame=1; frame<seqData.numFrames; frame++)
{
Point3D * curTrans = &nodeTransCache[i][frame];
if (!isEqual(*curTrans,*prevTrans,AppConfig::AnimationDelta()) || !isEqual(*curTrans,*firstTrans,AppConfig::AnimationDelta()))
{
setMembershipArray(seq.matters.translation,true,i);
transCount++;
break;
}
prevTrans = curTrans;
}
}
}
void ShapeMimic::setScaleMembership(Sequence & seq, AppSequenceData & seqData, S32 & arbitraryScaleCount, S32 & alignedScaleCount, S32 & uniformScaleCount)
{
// if already encountered an error, then
// we'll just go through the motions
arbitraryScaleCount = alignedScaleCount = uniformScaleCount = 0;
if (AppConfig::IsExportError()) return;
S32 scaleCount = 0;
if (seqData.forceScale)
scaleCount = nodes.size();
else if (!seqData.enableScale)
return;
if (animatesArbitraryScale(seqData))
arbitraryScaleCount = scaleCount ? scaleCount : setArbitraryScaleMembership(seq,seqData);
else if (animatesAlignedScale(seqData))
alignedScaleCount = scaleCount ? scaleCount : setAlignedScaleMembership(seq,seqData);
else
uniformScaleCount = scaleCount ? scaleCount : setUniformScaleMembership(seq,seqData);
}
S32 ShapeMimic::setUniformScaleMembership(Sequence & seq, AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return 0;
S32 nodeCount = 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (AppConfig::NeverAnimate(nodes[i]->appNode))
continue;
// first rotation
Point3D a = nodeScaleCache[i][0];
F32 firstScale = (a.x()+a.y()+a.z())/3.0f;
F32 prevScale = firstScale;
if (fabs(firstScale-1.0f)>0.001f)
{
setMembershipArray(seq.matters.scale,true,i);
nodeCount++;
continue;
}
for (S32 frame=1; frame<seqData.numFrames; frame++)
{
Point3D a = nodeScaleCache[i][frame];
F32 curScale = (a.x()+a.y()+a.z())/3.0f;
if (fabs(curScale-prevScale)>0.001f)
{
setMembershipArray(seq.matters.scale,true,i);
nodeCount++;
break;
}
prevScale = curScale;
}
}
return nodeCount;
}
S32 ShapeMimic::setAlignedScaleMembership(Sequence & seq, AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return 0;
S32 nodeCount = 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (AppConfig::NeverAnimate(nodes[i]->appNode))
continue;
// first rotation
Point3D * firstScale = &nodeScaleCache[i][0];
Point3D * prevScale = firstScale;
if (!isEqual(*firstScale,Point3D(1,1,1),AppConfig::AnimationDelta()))
{
setMembershipArray(seq.matters.scale,true,i);
nodeCount++;
continue;
}
for (S32 frame=1; frame<seqData.numFrames; frame++)
{
Point3D * curScale = &nodeScaleCache[i][frame];
if (!isEqual(*curScale,*prevScale,AppConfig::AnimationDelta()) || !isEqual(*curScale,*firstScale,AppConfig::AnimationDelta()))
{
setMembershipArray(seq.matters.scale,true,i);
nodeCount++;
break;
}
prevScale = curScale;
}
}
return nodeCount;
}
S32 ShapeMimic::setArbitraryScaleMembership(Sequence & seq, AppSequenceData & seqData)
{
// for determining membership, we just care if scale factor is animated...
return setAlignedScaleMembership(seq,seqData);
}
bool ShapeMimic::animatesAlignedScale(AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return false;
if (!seqData.enableAlignedScale)
return false;
for (S32 i=0; i<nodes.size(); i++)
{
if (AppConfig::NeverAnimate(nodes[i]->appNode))
continue;
for (S32 frame=0; frame<seqData.numFrames; frame++)
{
Point3D delta = nodeScaleCache[i][frame] - Point3D(1,1,1);
if (!isZero(delta,AppConfig::AnimationDelta()) &&
(fabs(delta.x()-delta.y())>AppConfig::AnimationDelta() ||
fabs(delta.y()-delta.z())>AppConfig::AnimationDelta() ||
fabs(delta.z()-delta.x())>AppConfig::AnimationDelta()))
// we not only animate scale, but we do it non-uniformly
return true;
}
}
return false;
}
bool ShapeMimic::animatesArbitraryScale(AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return false;
if (!seqData.enableArbitraryScale)
return false;
for (S32 i=0; i<nodes.size(); i++)
{
if (AppConfig::NeverAnimate(nodes[i]->appNode))
continue;
Quaternion idQuat(0,0,0,1);
for (S32 frame=0; frame<seqData.numFrames; frame++)
{
Quaternion curRot = nodeScaleRotCache[i][frame];
Point3D curScale = nodeScaleCache[i][frame];
if (isEqualQ16(curRot,idQuat))
// scale factor is aligned, not arbitrary
continue;
Point3D delta = curScale - Point3D(1,1,1);
if (fabs(delta.x())<0.001f && fabs(delta.y())<0.001f && fabs(delta.z())<0.001f)
// no scale
continue;
// we have scale and it isn't aligned...
return true;
}
}
return false;
}
void ShapeMimic::setIflMembership(Shape * shape, Sequence & seq, AppSequenceData & seqData, S32 & iflCount)
{
// if already encountered an error, then
// we'll just go through the motions
iflCount = 0;
if (AppConfig::IsExportError()) return;
setMembershipArray(seq.matters.ifl,false,0,iflList.size());
// get start and end frame
AppTime startTime = seqData.startTime;
AppTime endTime = seqData.endTime;
// decide object membership
if (seqData.enableIFL)
{
for (S32 i=0; i<iflList.size(); i++)
{
// does ifl animate any materials during our range?
AppTime time = iflList[i]->appIfl->getStartTime();
const std::vector<AppTime> & durations = iflList[i]->appIfl->getDurations();
const std::vector<char*> & names = iflList[i]->appIfl->getNames();
S32 len = names.size();
if (durations.size() != len)
{
AppConfig::SetExportError("22", "Assertion failed: mismatch between ifl names and ifl durations");
return;
}
if (len==0)
// degenerate ifl...just leave
continue;
S32 idx = 0;
const char * prev = "";
while (time<=endTime)
{
if (time==startTime && !_stricmp(names[0],names[idx % len]))
{
// changing material during this sequence...
iflCount++;
setMembershipArray(seq.matters.ifl,true,i);
break;
}
if (time>startTime && !_stricmp(prev,names[idx % len]))
{
// changing material during this sequence...
iflCount++;
setMembershipArray(seq.matters.ifl,true,i);
break;
}
prev = names[idx % len];
time += durations[idx % len];
++idx;
}
}
}
}
void ShapeMimic::generateGroundAnimation(Shape * shape, Sequence & seq, AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
seq.firstGroundFrame = shape->groundTranslations.size();
seq.numGroundFrames = 0;
if (seqData.ignoreGround)
// nothing more to do
return;
// does this sequence animate the bounds node, if not, don't add ground transform
if (!boundsNode->animatesTransform(seqData))
// no ground animation
return;
// at this point we know that we do animate bounds node,
// so we do have ground animation...
S32 groundNumFrames = seqData.groundNumFrames;
seq.flags |= Sequence::MakePath;
seq.numGroundFrames = groundNumFrames-1; // we only really add this many frames
AppConfig::PrintDump(PDSequences,
avar("\r\nAdding %i ground transform frames at %s sec per frame intervals.\r\n\r\n",groundNumFrames,seqData.groundDelta.getStr()));
// frame at start isn't added since it would just be identity anyway...
AppTime time = seqData.startTime + seqData.groundDelta;
for (S32 i=0; i<groundNumFrames-1; i++, time += seqData.groundDelta)
{
shape->groundTranslations.push_back(Point3D());
shape->groundRotations.push_back(Quaternion());
Quaternion & rot = shape->groundRotations.back();
Point3D & trans = shape->groundTranslations.back();
Quaternion srot; // ignored on ground transform
Point3D scale; // ignored on ground transform
getDeltaTransform(boundsNode,seqData.startTime,time,rot,trans,srot,scale);
AppConfig::PrintDump(PDSequences,avar("Ground transform frame:\r\n trans=(%f,%f,%f)\r\n rot=(%f,%f,%f,%f)\r\n",
trans.x(),trans.y(),trans.y(),rot.x(),rot.y(),rot.z(),rot.w()));
}
}
void ShapeMimic::generateNodeAnimation(Shape * shape, Sequence & seq, AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// add the states -- add all the states for each node in a row
seq.baseRotation = shape->nodeRotations.size();
seq.baseTranslation = shape->nodeTranslations.size();
seq.baseScale = (seq.flags & Sequence::ArbitraryScale) ? shape->nodeScalesArbitrary.size() :
(seq.flags & Sequence::AlignedScale) ? shape->nodeScalesAligned.size() : shape->nodeScalesUniform.size();
for (S32 i=0; i<nodes.size(); i++)
{
AppTime time = seqData.startTime;
for (S32 frame = 0; frame<seqData.numFrames; frame++, time += seqData.delta)
{
// may go just a tad over/under due to round-off...correct that here
if (seq.matters.rotation[i])
addNodeRotation(nodes[i],time,shape,seqData.blend,nodeRotCache[i][frame],false);
if (seq.matters.translation[i])
addNodeTranslation(nodes[i],time,shape,seqData.blend,nodeTransCache[i][frame],false);
if (seq.matters.scale[i])
{
Quaternion & rot = nodeScaleRotCache[i][frame];
Point3D scale = nodeScaleCache[i][frame];
if (animatesArbitraryScale(seqData))
addNodeArbitraryScale(nodes[i],time,shape,seqData.blend,rot,scale);
else if (animatesAlignedScale(seqData))
addNodeAlignedScale(nodes[i],time,shape,seqData.blend,scale);
else
addNodeUniformScale(nodes[i],time,shape,seqData.blend,(scale.x()+scale.y()+scale.z())/3.0f);
}
}
}
}
void ShapeMimic::generateObjectAnimation(Shape * shape, Sequence & seq, AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// add the states -- add all the states for each object in a row
seq.baseObjectState = shape->objectStates.size();
for (S32 i=0; i<objectList.size(); i++)
{
if (seq.matters.frame[i] || seq.matters.matframe[i] || seq.matters.vis[i])
{
AppTime time = seqData.startTime;
for (S32 frame = 0; frame<seqData.numFrames; frame++, time += seqData.delta)
generateObjectState(objectList[i],time,shape,seq.matters.frame[i],seq.matters.matframe[i]);
}
}
}
void ShapeMimic::generateFrameTriggers(Shape * shape, Sequence & seq, AppSequenceData & seqData, AppSequence * appSeq)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// initialize triggers...
seq.firstTrigger = shape->triggers.size();
seq.numTriggers = appSeq->getNumTriggers();
if (!seq.numTriggers)
// no triggers
return;
S32 i;
for (i=0; i<seq.numTriggers; i++)
shape->triggers.push_back(appSeq->getTrigger(i));
// track the triggers that get turned off by this shape...normally, triggers
// aren't turned on/off, just on...if we are a trigger that does both then we
// need to mark ourselves as such so that on/off can become off/on when sequence
// is played in reverse...
U32 offTriggers = 0;
for (i=0; i<seq.numTriggers; i++)
{
U32 state = shape->triggers[seq.firstTrigger+i].state;
if ((state & TriggerState::StateOn) == 0)
offTriggers |= state & (~TriggerState::StateMask);
}
// we now know which states are turned off, set invert on all those (including when turned on)
for (i=0; i<seq.numTriggers; i++)
{
if (shape->triggers[seq.firstTrigger + i].state & offTriggers)
shape->triggers[seq.firstTrigger + i].state |= TriggerState::InvertOnReverse;
}
// do a quick bubble sort so that we don't have to mess with trigger compare operators
for (i=0; i<seq.numTriggers-1; i++)
{
for (S32 j=i+1; j<seq.numTriggers; j++)
{
if (shape->triggers[seq.firstTrigger + j].pos < shape->triggers[seq.firstTrigger + i].pos)
{
// swap
Trigger tmp = shape->triggers[seq.firstTrigger + j];
shape->triggers[seq.firstTrigger + j] = shape->triggers[seq.firstTrigger + i];
shape->triggers[seq.firstTrigger + i] = tmp;
}
}
}
// now add to dump file...
AppConfig::PrintDump(PDSequences,avar("\r\n------Trigger info for sequence %s",appSeq->getName()));
for (i=0; i<seq.numTriggers; i++)
{
Trigger & trigger = shape->triggers[i];
AppConfig::PrintDump(PDSequences,avar("Trigger state %i at pos %f%s",
trigger.state&TriggerState::StateMask,
trigger.pos,
trigger.state & TriggerState::StateOn ? "." : " (off)."));
}
}
bool ShapeMimic::testCutNodes(AppSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return true;
// this shouldn't be allowed, but check anyway...
S32 numFrames = seqData.numFrames;
if (numFrames<2)
return false;
S32 i, frame;
std::vector<Quaternion> rotTrans(numFrames);
std::vector<Point3D> transTrans(numFrames);
std::vector<Point3D> scaleTrans(numFrames);
std::vector<Quaternion> scaleRotTrans(numFrames);
std::vector<AffineParts> child0(cutNodes.size());
std::vector<AffineParts> parent0(cutNodes.size());
Quaternion tmpRot;
Point3D tmpTrans;
Quaternion tmpScaleRot;
Point3D tmpScaleTrans;
for (i=0;i<cutNodes.size();i++)
getLocalNodeTransform(cutNodes[i],cutNodesParents[i],child0[i],parent0[i],AppTime::DefaultTime(),tmpRot,tmpTrans,tmpScaleRot,tmpScaleTrans);
// get the transform for each frame
for (i=0;i<cutNodes.size();i++)
{
// get all the node transforms for every frame
AppTime time = seqData.startTime;
for (frame = 0; frame<numFrames; frame++, time += seqData.delta)
getLocalNodeTransform(cutNodes[i],cutNodesParents[i],child0[i],parent0[i],time,rotTrans[frame],transTrans[frame],scaleRotTrans[frame],scaleTrans[frame]);
// we now have all numFrames transforms...check to see if they change
Quaternion * firstRot = &rotTrans[0];
Quaternion * prevRot = firstRot;
Point3D * firstTrans = &transTrans[0];
Point3D * prevTrans = firstTrans;
Quaternion * firstScaleRot = &scaleRotTrans[0];
Quaternion * prevScaleRot = firstScaleRot;
Point3D * firstScale = &scaleTrans[0];
Point3D * prevScale = firstScale;
for (frame=0; frame<numFrames; frame++)
{
Quaternion * curRot = &rotTrans[frame];
Point3D * curTrans = &transTrans[frame];
Quaternion * curScaleRot = &scaleRotTrans[frame];
Point3D * curScale = &scaleTrans[frame];
Point3D delta = *curTrans-*prevTrans;
Point3D deltaScale = *curScale-*prevScale;
bool idScale1 = isEqual(*curScale,Point3D(1,1,1),0.01f);
bool idScale2 = isEqual(*prevScale,Point3D(1,1,1),0.01f);
bool pureScaleDiff = isZero(deltaScale,AppConfig::AnimationDelta());
bool isScaled = pureScaleDiff || ((!idScale1 || !idScale2));
bool isTrans = isZero(delta,AppConfig::AnimationDelta());
bool isRot = !isEqualQ16(*curRot,*prevRot);
if (isRot || isTrans || isScaled)
{
// going to report error -- add extra information to the dump file:
AppConfig::PrintDump(PDAlways,"\r\n----------------------------------------------\r\n");
AppConfig::PrintDump(PDAlways,avar("\r\nIllegal transform animiation detected between collapsed node \"%s\" and \"%s\".\r\n",
cutNodes[i]->getName(),cutNodesParents[i]->getName()));
AppConfig::PrintDump(PDAlways,"Transform dump:\r\n\r\n");
Point3D maxT(0,0,0);
Quaternion maxQ(0,0,0,0);
Point3D startT = *firstTrans;
Quaternion startQ = *firstRot;
for (S32 f=0; f<numFrames; f++)
{
Point3D t = transTrans[f];
Quaternion q = rotTrans[f];
AppConfig::PrintDump(PDAlways,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",t.x(),t.y(),t.z()));
AppConfig::PrintDump(PDAlways,avar(" rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",q.x(),q.y(),q.z(),q.w()));
AppConfig::PrintDump(PDAlways,"---------------------------------\r\n");
F32 diff;
// get maximum difference for the dump file
diff = fabs(t.x()-startT.x());
if (diff > maxT.x())
maxT.x(diff);
diff = fabs(t.y()-startT.y());
if (diff > maxT.y())
maxT.y(diff);
diff = fabs(t.z()-startT.z());
if (diff > maxT.z())
maxT.z(diff);
diff = fabs(q.x()-startQ.x());
if (diff > maxQ.x())
maxQ.x(diff);
diff = fabs(q.y()-startQ.y());
if (diff > maxQ.y())
maxQ.y(diff);
diff = fabs(q.z()-startQ.z());
if (diff > maxQ.z())
maxQ.z(diff);
diff = fabs(q.w()-startQ.w());
if (diff > maxQ.w())
maxQ.w(diff);
}
AppConfig::PrintDump(PDAlways,"Maximum deviation:\r\n");
AppConfig::PrintDump(PDAlways,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",maxT.x(),maxT.y(),maxT.z()));
AppConfig::PrintDump(PDAlways,avar(" rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",maxQ.x(),maxQ.y(),maxQ.z(),maxQ.w()));
AppConfig::PrintDump(PDAlways," Scale may have animated too.\r\n");
AppConfig::PrintDump(PDAlways,"---------------------------------\r\n");
AppConfig::SetExportError("23", avar("Illegal transform animation detected between collapsed node \"%s\" and \"%s\".",
cutNodes[i]->getName(),cutNodesParents[i]->getName()));
return true;
}
prevTrans = curTrans;
prevScaleRot = curScaleRot;
prevScale = curScale;
}
}
return false;
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateMaterialList(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
shape->materials = materials;
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::generateSkins(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// put skins in the right order...we're basically sorting by detailSize member of skinMimic
// but we'll do it a slow way to make sure in synch with detail ordering
{
S32 i,j,k;
j=0;
for (i=0; i<shape->detailLevels.size(); i++)
{
// search for skins of size details[i].size
// place any found at j and advance j
for (k=j; k<skins.size(); k++)
if (skins[k]->detailSize == (S32) shape->detailLevels[i].size)
{
// swap skin j and k, increment j
SkinMimic * tmp = skins[j];
skins[j] = skins[k];
skins[k] = tmp;
j++;
}
}
if (j!=skins.size() && !AppConfig::GetAllowUnusedMeshes())
{
AppConfig::SetExportError("24", "Unused skins were found.");
return;
}
for (;j<skins.size();j++)
{
delete skins[j];
delElementAtIndex(skins,j);
j--;
}
}
// skins are now in order of detail size and unused ones have been deleted
for (S32 i=0; i<objectList.size(); i++)
{
if (!objectList[i]->isSkin)
continue;
// all meshes on this object should be skins...
for (S32 meshNum=0; meshNum<objectList[i]->numDetails; meshNum++)
{
if (!objectList[i]->details[meshNum].mesh)
continue;
if (!objectList[i]->details[meshNum].mesh->skinMimic)
{
AppConfig::SetExportError("25", "Assertion failed generating skins");
return;
}
// this mesh is a skin...set it up (it's already been created)
Mesh * skinMesh = objectList[i]->details[meshNum].mesh->tsMesh;
// now find the corresponding skin
SkinMimic * skin = objectList[i]->details[meshNum].mesh->skinMimic;
skin->skinMesh = skinMesh;
skinMesh->numFrames = 1;
skinMesh->matFrames = 1;
skinMesh->vertsPerFrame = skin->verts.size();
skinMesh->primitives = skin->faces;
skinMesh->indices = skin->indices;
skinMesh->normals = skin->normals;
skinMesh->verts = skin->verts;
skinMesh->tverts = skin->tverts;
S32 j,k;
// up till now weights have been stored in terms of vertId...convert to vertex number now
// copyWeightsToVerts(objectList[i]->details[meshNum].mesh->skinMimic);
// for each bone, indicate node index in shape and inverseDefaultTransform
assert(skinMesh->nodeTransform.size()==0 && "Assertion failed");
assert(skinMesh->nodeIndex.size()==0 && "Assertion failed");
Matrix<4,4,F32> boundsTransform = boundsNode->getNodeTransform( AppTime::DefaultTime() );
zapScale(boundsTransform);
for (j=0; j<skin->bones.size(); j++)
{
// find node index
for (k=0; k<nodes.size(); k++)
if (nodes[k]->appNode->isEqual(skin->bones[j]))
break;
if (k==nodes.size())
{
AppConfig::SetExportError("26", "Error: bone missing from shape");
return;
}
skinMesh->nodeIndex.push_back(k);
Matrix<4,4,F32> boneTransform = nodes[k]->appNode->getNodeTransform( AppTime::DefaultTime() );
zapScale(boneTransform);
Matrix<4,4,F32> initTransform = boneTransform.inverse() * boundsTransform;
skinMesh->nodeTransform.push_back(initTransform);
}
AppConfig::PrintDump(PDObjectStateDetails|PDPass2,avar("\r\nGenerating skin \"%s\".\r\n",skin->appMesh->getName()));
// push all vertex, bone, weight triples
AppConfig::PrintDump(PDObjectStateDetails,"\r\nVertex, bone, & weight data:\r\n\r\n");
for (j=0; j<skinMesh->verts.size(); j++)
{
AppConfig::PrintDump(PDObjectStateDetails,avar("Vertex %i\r\n",j));
for (k=0; k<skin->bones.size(); k++)
{
if ((*skin->weights[k])[j]>=AppConfig::WeightThreshhold())
{
skinMesh->vindex.push_back(j);
skinMesh->vbone.push_back(k);
skinMesh->vweight.push_back((*skin->weights[k])[j]);
AppConfig::PrintDump(PDObjectStateDetails,avar(" Bone %i, weight = %5.3f, name = \"%s\"\r\n",
skinMesh->vbone.back(),skinMesh->vweight.back(),skin->bones[k]->getName()));
}
}
}
}
}
}
void ShapeMimic::copyWeightsToVerts(SkinMimic * skinMimic)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// on input, weights are stored in a bone x vertId matrix
// on output, weights will be stored in a bone x vert index matrix
// difference between vertId and vert Index is that the former
// signifies the order of the vert in the 3d app while the latter
// corresponds to the order in our vert list
S32 i,j;
std::vector<SkinMimic::WeightList*> oldWeights = skinMimic->weights;
for (i=0; i<oldWeights.size(); i++)
skinMimic->weights[i] = new SkinMimic::WeightList;
for (i=0; i<oldWeights.size(); i++)
for (j=0; j<skinMimic->vertId.size(); j++)
skinMimic->weights[i]->push_back((*(oldWeights[i]))[skinMimic->vertId[j]]);
for (i=0; i<oldWeights.size(); i++)
delete oldWeights[i];
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::optimizeMeshes(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
S32 i,j,k;
AppConfig::PrintDump(PDObjectStateDetails,"\r\nOptimizing meshes...\r\n");
// go through meshes and optimize each one...
for (i=0; i<objectList.size(); i++)
{
AppConfig::SetProgress(((F32)i + 1.0f) / (F32)objectList.size() / 2.0f, 0.71f, "Optimizing meshes...");
ObjectMimic * om = objectList[i];
if (om->isSkin)
// save skins for later...
continue;
for (j=om->numDetails-1; j>=0; j--)
{
if (!om->details[j].mesh)
continue;
AppConfig::PrintDump(PDObjectStateDetails,avar("\r\nOptimizing mesh \"%s\" detail level %i.\r\n",om->name,om->details[j].size));
Mesh * mesh = om->details[j].mesh->tsMesh;
std::vector<U32> & smooth = om->details[j].mesh->smoothingGroups;
std::vector<U32> & remap = om->details[j].mesh->remap;
// collapse vertices
collapseVertices(mesh,smooth,remap,NULL);
// need to sprinkle these here and there to avoid crashes...
if (AppConfig::IsExportError()) return;
// now that verts are collapsed, delete any trivial facees
for (S32 k=0; k<mesh->primitives.size(); k++)
{
Primitive & face = mesh->primitives[k];
U32 start = face.firstElement;
U32 idx0 = mesh->indices[start + 0];
U32 idx1 = mesh->indices[start + 1];
U32 idx2 = mesh->indices[start + 2];
if (idx0==idx1 || idx1==idx2 || idx0==idx2)
{
delElementAtIndex(mesh->indices,start);
delElementAtIndex(mesh->indices,start);
delElementAtIndex(mesh->indices,start);
for (S32 l=0; l<mesh->primitives.size(); l++)
if (U16(mesh->primitives[l].firstElement) >= start)
mesh->primitives[l].firstElement -= 3;
delElementAtIndex(mesh->primitives,k);
k--;
}
}
if( om->details[j].multiResPercent < 1.0f )
{
decimate(mesh, om->details[j].multiResPercent);
}
//
if (om->details[j].mesh->sortedObject)
continue;
// strip
stripify(mesh->primitives,mesh->indices);
// need to sprinkle these here and there to avoid crashes...
if (AppConfig::IsExportError()) return;
}
}
// need to sprinkle these here and there to avoid crashes...
if (AppConfig::IsExportError()) return;
// optimize skins...
std::vector<U32> remap;
for (i=skins.size()-1,j=0; i>=0; i--,j++)
{
AppConfig::SetProgress(((F32)j + 1.0f) / (F32)skins.size() / 2.0f, 0.71f, "Optimizing meshes...");
SkinMimic * skin = skins[i];
Mesh * skinMesh = skin->skinMesh;
std::vector<U32> & smooth = skin->smoothingGroups;
std::vector<U32> * vertId = &skin->vertId;
// first make sure we have no missing verts...
for (j=1; j<skinMesh->vindex.size(); j++)
{
if (skinMesh->vindex[j]-skinMesh->vindex[j-1]>1)
{
AppConfig::SetExportError("27", avar("Vertex %i missing weight on skin \"%s\"",skinMesh->vindex[j]+1,skin->appMesh->getName()));
return;
}
}
// start optimizing this skin...
AppConfig::PrintDump(PDObjectStateDetails,avar("\r\nOptimizing skin mesh \"%s\" detail level %i.\r\n",skin->appMesh->getName(),skin->detailSize));
if( AppConfig::IgnoreSmoothingGroupOnSkinMesh() )
{
// we don't respect smoothing groups on skins
for (j=0;j<smooth.size();j++)
smooth[j]=0;
}
// collapse vertices
collapseVertices(skinMesh,smooth,remap,vertId);
// remap some information
for (j=0; j<skinMesh->vindex.size(); j++)
skinMesh->vindex[j] = remap[skinMesh->vindex[j]];
for (j=(S32)skinMesh->vindex.size()-1; j>0; j--)
{
for (k=0; k<j; k++)
{
if (skinMesh->vindex[k]==skinMesh->vindex[j] && skinMesh->vbone[k]==skinMesh->vbone[j])
{
if (fabs(skinMesh->vweight[j]-skinMesh->vweight[k])>0.01f)
{
AppConfig::SetExportError("28", "Assertion failed when collapsing vertices on skin (1)");
return;
}
// vertex and bone index for kth and jth tuple match...merge them
delElementAtIndex(skinMesh->vweight,j);
delElementAtIndex(skinMesh->vindex,j);
delElementAtIndex(skinMesh->vbone,j);
break; // out of k loop
}
}
}
if( skin->multiResPercent < 1.0f )
{
decimate(skinMesh, skin->multiResPercent);
}
// re-sort the vertexIndex, boneIndex, weight lists by vertex and bone, respectively...
for (j=0; j<(S32)skinMesh->vindex.size()-1; j++)
{
for (k=j+1; k<skinMesh->vindex.size(); k++)
{
if ((skinMesh->vindex[k]<skinMesh->vindex[j]) || (skinMesh->vindex[k]==skinMesh->vindex[j] && skinMesh->vbone[k]<skinMesh->vbone[j]))
{
// swap
S32 tmp = skinMesh->vindex[k];
skinMesh->vindex[k] = skinMesh->vindex[j];
skinMesh->vindex[j] = tmp;
tmp = skinMesh->vbone[k];
skinMesh->vbone[k] = skinMesh->vbone[j];
skinMesh->vbone[j] = tmp;
F32 tmp2 = skinMesh->vweight[k];
skinMesh->vweight[k] = skinMesh->vweight[j];
skinMesh->vweight[j] = tmp2;
}
}
}
// strip
stripify(skinMesh->primitives,skinMesh->indices);
}
}
void ShapeMimic::collapseVertices(Mesh * mesh, std::vector<U32> & smooth, std::vector<U32> & remap, std::vector<U32> * vertId)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
if (mesh->verts.size() != mesh->normals.size())
{
AppConfig::SetExportError("29", "Assertion failed when collapsing vertices (2)");
return;
}
AppConfig::PrintDump(PDObjectStateDetails,avar("%i verts before joining verts\r\n",mesh->verts.size()));
S32 i,j;
// set up remap
remap.resize(mesh->vertsPerFrame);
for (i=0; i<remap.size(); i++)
remap[i]=i;
for (i=(S32)mesh->vertsPerFrame-1; i>0; i--)
{
// try to find a vertex earlier in the list that matches this one
for (j=i-1; j>=0; j--)
{
//------------------------------------------
// same location, tvert, smoothing group, vert id (if passed)?
U32 s1 = 1;
U32 s2 = 1;
if( !AppConfig::IgnoreSmoothingGroupDuringCollapse() && smooth.size() > 0 )
{
s1 = smooth[i];
s2 = smooth[j];
}
if (!vertexSame(mesh->verts[i],mesh->verts[j],mesh->tverts[i],mesh->tverts[j],s1,s2,mesh->normals[i],mesh->normals[j],i,j,vertId))
continue;
//------------------------------------------
// ok, but are we the same for all frames and matFrames too?
S32 k,l;
for (k=1; k<mesh->numFrames; k++)
{
S32 startVert = k * mesh->vertsPerFrame;
for (l=1; l<mesh->matFrames; l++)
{
S32 startTVert = l * mesh->vertsPerFrame;
if (!vertexSame(mesh->verts[i+startVert],mesh->verts[j+startVert],mesh->tverts[i+startTVert],mesh->tverts[j+startTVert],s1,s2,mesh->normals[i+startVert],mesh->normals[j+startVert],i,j,vertId))
break;
}
if (l!=mesh->matFrames)
break;
}
if (k!=mesh->numFrames)
// not same throughout
continue;
//------------------------------------------
// alright, vertex i and j are the same...get rid of vertex i (i>j)
if (i<=j)
{
AppConfig::SetExportError("30", "Assertion failed when collapsing vertex (3)");
return;
}
for (k=0; k<mesh->indices.size(); k++)
{
if (mesh->indices[k] == i)
mesh->indices[k] = j;
else if (mesh->indices[k]>i)
mesh->indices[k]--;
}
for (k=mesh->numFrames-1; k>=0; k--)
{
S32 startVert = mesh->vertsPerFrame * k;
delElementAtIndex(mesh->verts,i+startVert);
delElementAtIndex(mesh->normals,i+startVert);
}
for (k=mesh->matFrames-1; k>=0; k--)
{
S32 startTVert = mesh->vertsPerFrame * k;
delElementAtIndex(mesh->tverts,i+startTVert);
}
if (vertId)
delElementAtIndex(*vertId,i);
if (smooth.size() != 0)
delElementAtIndex(smooth,i);
mesh->vertsPerFrame--;
// update remap -- we're getting rid of vertex i and replacing it with vertex j
// any vertex currently mapped to i should be replaced by j, but we know all verts
// before i are there original selves (since i-loop is going backwards) so we can start
// at i...also shift indices greater than i
for (k=i;k<remap.size();k++)
if (remap[k]==i)
remap[k]=j;
else if (remap[k]>i)
remap[k]--;
break; // out of j loop
}
}
if (smooth.size() != 0)
// generate normals using smoothing groups...
computeNormals(mesh->primitives,mesh->indices,mesh->verts,mesh->normals,smooth,mesh->vertsPerFrame,mesh->numFrames);
// have normals...now encode them
mesh->enormals.clear();
for (i=0; i<mesh->normals.size(); i++)
mesh->enormals.push_back(Mesh::encodeNormal(mesh->normals[i]));
AppConfig::PrintDump(PDObjectStateDetails,avar("%i verts after joining verts\r\n",mesh->verts.size()));
if (mesh->verts.size() * mesh->matFrames != mesh->tverts.size() * mesh->numFrames)
AppConfig::SetExportError("31", "ShapeMimic::collapseVertices (3)");
else if (mesh->verts.size() != mesh->normals.size())
AppConfig::SetExportError("31", "ShapeMimic::collapseVertices (4)");
}
void ShapeMimic::computeNormals(std::vector<Primitive> & faces, std::vector<U16> & indices, std::vector<Point3D> & verts, std::vector<Point3D> & norms, std::vector<U32> & smooth, S32 vertsPerFrame, S32 numFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
if (vertsPerFrame * numFrames != verts.size() || vertsPerFrame!=smooth.size())
{
AppConfig::SetExportError("32", "Assertion failed: vertex number mismatch");
return;
}
S32 i,j;
std::vector<S32> counts(verts.size());
norms.resize(verts.size());
for (i=0; i<verts.size(); i++)
{
counts[i]=0;
norms[i] = Point3D(0.0f,0.0f,0.0f);
}
for (S32 frameNum = 0; frameNum<numFrames; frameNum++)
{
S32 startVert = frameNum * vertsPerFrame;
for (i=0; i<faces.size(); i++)
{
Primitive & tsFace = faces[i];
if ((tsFace.type & Primitive::TypeMask) != Primitive::Triangles)
{
AppConfig::SetExportError("33", "Assertion error while computing normals");
return;
}
// find the normal to this face
S32 idx0 = indices[tsFace.firstElement+0];
S32 idx1 = indices[tsFace.firstElement+1];
S32 idx2 = indices[tsFace.firstElement+2];
Point3D v0 = verts[startVert+idx0];
Point3D v1 = verts[startVert+idx1];
Point3D v2 = verts[startVert+idx2];
Point3D n,v20,v10;
v20 = v2-v0;
if (dotProduct(v20,v20)>0.0000001f)
v20.normalize();
v10 = v1-v0;
if (dotProduct(v10,v10)>0.0000001f)
v10.normalize();
crossProduct(v20,v10,&n);
if (dotProduct(n,n) > 0.0000001f)
{
n.normalize();
for (S32 j=0; j<vertsPerFrame; j++)
{
Point3D vj = verts[startVert+j];
if (isEqual(v0,vj,AppConfig::SameVertTOL()) && (smooth[idx0]&smooth[j] || smooth[idx0]==smooth[j]))
{
norms[startVert+j] += n;
counts[startVert+j]++;
}
if (isEqual(v1,vj,AppConfig::SameVertTOL()) && (smooth[idx1]&smooth[j] || smooth[idx1]==smooth[j]))
{
norms[startVert+j] += n;
counts[startVert+j]++;
}
if (isEqual(v2,vj,AppConfig::SameVertTOL()) && (smooth[idx2]&smooth[j] || smooth[idx2]==smooth[j]))
{
norms[startVert+j] += n;
counts[startVert+j]++;
}
}
}
}
}
// now average normals...
for (i=0; i<norms.size(); i++)
{
if (counts[i] && dotProduct(norms[i],norms[i])>0.0000001f)
norms[i].normalize();
}
// for verts w/o a normal, search for someone with same vert location and smoothing group
for (i=0; i<counts.size(); i++)
{
if (!counts[i])
{
for (j=0; j<verts.size(); j++)
{
if (!counts[j])
continue;
if (!isEqual(verts[i],verts[j],AppConfig::SameVertTOL()))
continue;
if (smooth[i]==smooth[j])
{
norms[i]=norms[j];
counts[i]++;
break;
}
}
}
}
// if above didn't work, try anyone in same location
for (i=0; i<counts.size(); i++)
{
if (!counts[i])
{
// search for someone with same vert location sans smoothing group
for (j=0; j<verts.size(); j++)
{
if (!counts[j])
continue;
if (!isEqual(verts[i],verts[j],AppConfig::SameVertTOL()))
continue;
norms[i]=norms[j];
counts[i]++;
break;
}
}
}
// just in case, set any normal still without a value to 0,0,1
for (i=0; i<counts.size(); i++)
if (!counts[i])
norms[i] = Point3D(0,0,1);
}
void ShapeMimic::stripify(std::vector<Primitive> & primitives, std::vector<U16> & indices)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
if (primitives.empty() || indices.empty())
// shouldn't really have empty meshes...but no harm, no foul (we would, however, cause
// problems in the stripper with empty meshes).
return;
S32 i;
S32 startFaces = primitives.size();
// in: primitives better just be faces and better use indexes
for (i=0; i<primitives.size(); i++)
{
if (primitives[i].type == -1)
{
AppConfig::SetExportError("34", "Assertion failed when stripping -- negative material index");
return;
}
if ( (primitives[i].type & ~(Primitive::NoMaterial^Primitive::MaterialMask)) != (Primitive::Triangles|Primitive::Indexed) || primitives[i].numElements!=3)
{
AppConfig::SetExportError("35", "Assertion failed when stripifying (1)");
return;
}
}
AppConfig::PrintDump(PDObjectStateDetails,avar("%i faces before stripping\r\n",startFaces));
#ifdef USE_NVIDIA_STRIPPER
nvStripWrap(primitives,indices,16);
const char * method = "NVidia";
#else
Stripper stripper(primitives,indices);
stripper.setLimitStripLength(false);
stripper.makeStrips();
stripper.getStrips(primitives,indices);
const char * method = "Quick, dirty, & ugly";
#endif
if (AppConfig::GetDumpMask() & PDObjectStateDetails)
{
AppConfig::PrintDump(PDObjectStateDetails,avar("Using %s stripping method.\r\n",method));
F32 len = 0.0f;
S32 hi = -1;
S32 lo = -1;
for (i=0; i<primitives.size(); i++)
{
len += primitives[i].numElements;
if (primitives[i].numElements > hi)
hi = primitives[i].numElements;
if (lo==-1 || primitives[i].numElements < lo)
lo = primitives[i].numElements;
}
S32 reversals = S32(len) - (startFaces + primitives.size() * 2); // no. of times we needed to reverse order of face by sending extra vert
if (!primitives.empty())
len *= 1.0f / (F32)primitives.size();
AppConfig::PrintDump(PDObjectStateDetails,avar("%i strips with average length %3.2f (range %i to %i) and %i reversals\r\n",primitives.size(),len,lo,hi,reversals));
}
}
void ShapeMimic::decimate(Mesh * mesh, F32 percentage)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
std::vector<Primitive> & faces = mesh->primitives;
std::vector<Point3D> & verts = mesh->verts;
std::vector<Point2D> & tverts = mesh->tverts;
std::vector<U16> & indices = mesh->indices;
std::vector<Point3D> & normals = mesh->normals;
std::vector <char> & enormals = mesh->enormals;
std::vector <S32> & vindex = mesh->vindex;
std::vector <S32> & vbone = mesh->vbone;
std::vector <F32> & vweight = mesh->vweight;
if (faces.empty() || indices.empty())
// shouldn't really have empty meshes...but no harm, no foul (we would, however, cause
// problems in the decimator with empty meshes).
return;
S32 startFaces = faces.size();
S32 targetFaces = S32(F32(startFaces) * percentage);
bool isSkinMesh = vindex.size() > 0;
// in: faces better just be faces and better use indexes
for (S32 i=0; i<faces.size(); i++)
{
if (faces[i].type == -1)
{
AppConfig::SetExportError("34", "Assertion failed when decimating -- negative material index");
return;
}
if ( (faces[i].type & ~(Primitive::NoMaterial^Primitive::MaterialMask)) != (Primitive::Triangles|Primitive::Indexed) || faces[i].numElements!=3)
{
AppConfig::SetExportError("35", "Assertion failed when decimating -- we can only strip indexed triangle meshes");
return;
}
}
AppConfig::PrintDump(PDObjectStateDetails,avar("%i faces, %i vertices before decimating\r\n",startFaces, verts.size()));
AppConfig::PrintDump(PDObjectStateDetails,avar("Targeting %i faces\r\n",targetFaces));
Decimator fastAndSimple( faces, indices, verts );
fastAndSimple.ReduceMesh( targetFaces );
fastAndSimple.GetMesh( faces, indices );
// Cleanup unused vertices
std::vector<bool> removed;
removed.resize( verts.size(), true );
for( S32 i = 0; i < indices.size(); i++ )
{
removed[ indices[i] ] = false;
}
S32 j = 0;
std::vector<S32> maps;
maps.resize( verts.size(), -1 );
for( S32 i = removed.size() - 1; i >= 0; i-- )
{
// Compress
if( !removed[i] )
{
maps[i] = j++; // We were going backwards through the list so we will need to invert the map later
}
else
{
delElementAtIndex(verts,i);
delElementAtIndex(tverts,i);
delElementAtIndex(normals,i);
delElementAtIndex(enormals,i);
// Handle vertex bones & weights
if(isSkinMesh)
{
for(S32 k=vindex.size()-1;k>=0; k-- )
{
if( vindex[k] == i )
{
delElementAtIndex(vweight, k);
delElementAtIndex(vindex, k);
delElementAtIndex(vbone, k);
}
}
}
}
}
for( S32 i = 0; i < indices.size(); i++ )
{
indices[i] = j - maps[ indices[i] ] - 1; // We were going backwards through the list so we need to invert the map
}
if(isSkinMesh)
{
for( S32 i = 0; i < vindex.size(); i++ )
{
vindex[i] = j - maps[ vindex[i] ] - 1; // We were going backwards through the list so we need to invert the map
}
}
AppConfig::PrintDump(PDObjectStateDetails,avar("%i faces, %i vertices after decimating\r\n",faces.size(), verts.size()));
}
bool ShapeMimic::vertexSame(Point3D & v1, Point3D & v2, Point2D & tv1, Point2D & tv2, U32 smooth1, U32 smooth2, Point3D & norm1, Point3D & norm2, U32 idx1, U32 idx2, std::vector<U32> * vertId)
{
if (!isEqual(norm1,norm2,AppConfig::SameNormTOL()))
return false;
if (!isEqual(v1,v2,AppConfig::SameVertTOL()) || !isEqual(tv1,tv2,AppConfig::SameTVertTOL()) || smooth1 != smooth2)
return false;
if (!vertId || (*vertId)[idx1]==(*vertId)[idx2])
return true;
// At this point, we know that the vert has the same tvert and vert coords, but that the 3d app thinks of
// it as a different vertex...for non-skin meshes we don't care, but we won't get _this_ far in that case
// (we'd have returned true because vertId==NULL for non-skin meshes).
// At some point, we may want some way to determine whether we can delete this vertex if we get this far...
// e.g., if both verts have same set of weights...
return false;
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
void ShapeMimic::convertSortObjects(Shape * shape)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// go through meshes and convert sortObjects when we find them
for (S32 i=0; i<objectList.size(); i++)
{
ObjectMimic * om = objectList[i];
for (S32 j=0; j<om->numDetails; j++)
{
if (!om->details[j].mesh || !om->details[j].mesh->sortedObject || om->isSkin)
continue;
Mesh * sortMesh = om->details[j].mesh->tsMesh;
AppConfig::PrintDump(PDObjectStateDetails,avar("%i faces, %i vertices before sorting\r\n",sortMesh->primitives.size(), sortMesh->verts.size()));
// get sort data from user properties...
AppMesh * appMesh = om->details[j].mesh->appMesh;
S32 numBigFaces = 0;
S32 maxDepth = 2;
bool zLayerUp = false;
bool zLayerDown = false;
bool writeZ = false;
appMesh->getInt("num_big_faces",numBigFaces);
appMesh->getInt("max_depth",maxDepth);
appMesh->getBool("z_layer_up",zLayerUp);
appMesh->getBool("z_layer_down",zLayerDown);
appMesh->getBool("write_z",writeZ);
sortMesh->alwaysWriteDepth = writeZ;
if (zLayerUp && zLayerDown)
{
AppConfig::SetExportError("36", "Cannot use both Z_LAYER_UP and Z_LAYER_DOWN.");
return;
}
TranslucentSort::generateSortedMesh(sortMesh,numBigFaces,maxDepth,zLayerUp,zLayerDown);
S32 saveNumFrames = sortMesh->numFrames;
sortMesh->vertsPerFrame = sortMesh->verts.size();
std::vector<U32> remap;
std::vector<U32> smooth(sortMesh->verts.size());
for (S32 k=0; k<smooth.size(); k++)
smooth[k]=0;
collapseVertices(sortMesh,smooth,remap,NULL);
sortMesh->numFrames = saveNumFrames;
sortMesh->vertsPerFrame = 0; // not used
AppConfig::PrintDump(PDObjectStateDetails,avar("%i faces, %i vertices after sorting\r\n",sortMesh->primitives.size(), sortMesh->verts.size()));
}
}
}
void ShapeMimic::fillNodeTransformCache(std::vector<NodeMimic*> & nodes, Sequence & seq, AppSequenceData & seqData)
{
S32 i;
// clear out the transform caches and set it up for this sequence
for (i=0; i<nodeRotCache.size(); i++)
delete [] nodeRotCache[i];
nodeRotCache.clear();
for (i=0; i<nodeTransCache.size(); i++)
delete [] nodeTransCache[i];
nodeTransCache.clear();
for (i=0; i<nodeScaleRotCache.size(); i++)
delete [] nodeScaleRotCache[i];
nodeScaleRotCache.clear();
for (i=0; i<nodeScaleCache.size(); i++)
delete [] nodeScaleCache[i];
nodeScaleCache.clear();
nodeRotCache.resize(nodes.size());
for (i=0; i<nodeRotCache.size(); i++)
nodeRotCache[i] = new Quaternion[seqData.numFrames];
nodeTransCache.resize(nodes.size());
for (i=0; i<nodeTransCache.size(); i++)
nodeTransCache[i] = new Point3D[seqData.numFrames];
nodeScaleRotCache.resize(nodes.size());
for (i=0; i<nodeScaleRotCache.size(); i++)
nodeScaleRotCache[i] = new Quaternion[seqData.numFrames];
nodeScaleCache.resize(nodes.size());
for (i=0; i<nodeScaleCache.size(); i++)
nodeScaleCache[i] = new Point3D[seqData.numFrames];
// get all the node transforms for every frame
AppTime time = seqData.startTime;
for (S32 frame = 0; frame<seqData.numFrames; frame++, time += seqData.delta)
for (i=0;i<nodes.size();i++)
generateNodeTransform(nodes[i],time,seqData.blend,seqData.blendReferenceTime,
nodeRotCache[i][frame],nodeTransCache[i][frame],
nodeScaleRotCache[i][frame],nodeScaleCache[i][frame]);
}
//-----------------------------------------------------------
//
//-----------------------------------------------------------
ObjectMimic * ShapeMimic::addObject(AppNode * node, AppMesh * mesh, std::vector<S32> * validDetails)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return NULL;
ObjectMimic * om;
// detect MultiRes...
std::vector<S32> multiResSize;
std::vector<F32> multiResPercent;
getMultiResData(node,multiResSize,multiResPercent);
if (multiResSize.size())
{
//addMultiRes(node,node);
for (S32 i=0; i<multiResSize.size(); i++)
// om will be the same for each object
om = addObject(node,mesh,validDetails,true,multiResSize[i],multiResPercent[i]);
}
om = addObject(node,mesh,validDetails,false);
return om;
}
ObjectMimic * ShapeMimic::addObject(AppNode * node, AppMesh * mesh, std::vector<S32> * validDetails, bool multiRes, S32 multiResSize, F32 multiResPercent)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return NULL;
ObjectMimic * om;
const char * name = mesh->getName();
tweakName(&name);
// separate object name from detail size for current mesh
S32 size;
char * objectName = chopTrailingNumber(name,size);
if( multiResSize > 0 )
size = multiResSize;
else
// artist can set detail level in the user properties if they want...
mesh->getInt("Detail",size);
S32 detailPos;
om = getObject(node,mesh,objectName,size,&detailPos,multiResPercent);
if (!om)
return NULL;
// set up the table of allowed detail levels
// this will be checked when we start adding meshes
// to the shape...start by making sure it hasn't
// been set up already or if it has that at least
// it points to the same place...
if (om->validDetails && validDetails && validDetails!=om->validDetails)
{
AppConfig::SetExportError("37", avar("Mesh \"%s\" occurs in two different places on the shape.",om->name));
return NULL;
}
if (validDetails)
{
// set valid detail levels...
om->validDetails = validDetails;
om->inTreeNode = node;
om->inTreeMesh = mesh;
// we now know what subtree we belong in -- unless error
if (om->subtreeNum>=0 && om->subtreeNum != subtrees.size()-1)
{
AppConfig::SetExportError("38", avar("Mesh \"%s\" occurs in two different subtrees on the shape.",om->name));
return NULL;
}
om->subtreeNum = subtrees.size() - 1;
}
// if we were passed validDetails then we are on the shape (a hack for a check?)
// in this case, fill in the parent of the object
if (validDetails)
{
om->appParent = node;
om->appTSParent = node; // this may change later...
}
return om;
}
ObjectMimic * ShapeMimic::getObject(AppNode * node, AppMesh * mesh, char * name, S32 size, S32 * detailNum, F32 multiResPercent, bool matchFull, bool isBone, bool isSkin)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return NULL;
S32 i;
// if this is a billboard object, detect that now and adjust name accordingly
// Note: billboard objects are named BB::* (the BB:: is dropped from here on).
bool billboard = mesh ? mesh->isBillboard() : false;
bool sortedObject = mesh ? mesh->isSorted() : false;
// if the name ends in : then ignore the : (and when we search this object, we compare to full name instead of name)
char * colon = NULL;
if (strlen(name)>1 && name[strlen(name)-1]==':')
{
colon = name+strlen(name)-1;
*colon = '\0';
}
// if we're in the shape tree, find out here (alternative is that we are unlinked and a detail level of a mesh)
bool inTree = node && !node->isParentRoot();
// if we're in the tree, we may need the full name...
const char * fullName = NULL;
if (inTree)
{
fullName = mesh? mesh->getName() : node->getName();
tweakName(&fullName); // get's rid of "BB::" prefix, for example
}
// look for name in list of objects
for (i=0; i<objectList.size(); i++)
{
if (isBone!=objectList[i]->isBone || isSkin!=objectList[i]->isSkin)
continue;
if (matchFull && inTree && objectList[i]->fullName && !_stricmp(fullName,objectList[i]->fullName))
break;
if ( colon && objectList[i]->fullName && !_stricmp(name,objectList[i]->fullName))
break;
if (!colon && !_stricmp(name,objectList[i]->name))
break;
}
// add an entry if needed
if (i==objectList.size())
{
objectList.push_back(new ObjectMimic);
objectList.back()->name = name;
// note: not straight forward ... full name is the full name of the object
// on the tree. If inTree, add that now...if "colon" is not null, use "name" for
// full name.
if (inTree)
objectList.back()->fullName = strnew(fullName);
else if (colon)
objectList.back()->fullName = strnew(name);
else
objectList.back()->fullName = NULL;
objectList.back()->numDetails = 0;
objectList.back()->validDetails = NULL;
objectList.back()->subtreeNum = -1;
objectList.back()->appParent = NULL;
objectList.back()->appTSParent = NULL;
objectList.back()->tsObject = NULL;
objectList.back()->tsNodeIndex = -1;
objectList.back()->isBone = isBone;
objectList.back()->isSkin = isSkin;
AppConfig::PrintDump(PDPass2,avar("Adding object named \"%s\".\r\n",name));
}
else
delete [] name; // don't need duplicate name
ObjectMimic * om = objectList[i];
if (om->isBone)
{
AppConfig::PrintDump(PDPass2,"Object is bone\r\n");
om->appParent = node;
om->appTSParent = node;
return om;
}
// enter data
S32 dl = om->numDetails++;
if (om->numDetails>ObjectMimic::MaxDetails)
{
AppConfig::SetExportError("39", avar("Assertion failed: too many details for mesh %s.",name));
return NULL;
}
char multiResString[256] = "";
if( multiResPercent < 1.0f )
sprintf( multiResString, " from multiRes of %f", multiResPercent);
AppConfig::PrintDump(PDPass2,avar("Adding mesh of size %i to object \"%s\"%s.\r\n", size,om->name,multiResString));
// keep meshes sorted by size
S32 j;
for (j=dl; j>=0; j--)
{
if (j==0 || size<om->details[j-1].size)
{
if (j<dl && om->details[j].size==size)
{
AppConfig::SetExportError("40", avar("Found two meshes named \"%s\" of size %i.\r\n",om->name,size));
// avoid crash...
*detailNum = j;
om->details[j].size = size;
om->details[j].multiResPercent = multiResPercent;
om->details[j].mesh = NULL;
return NULL;
}
// this is where we go:
// larger than all that follow us
// smaller than all that precede us
om->details[j].size = size;
om->details[j].multiResPercent = multiResPercent;
om->details[j].mesh = new MeshMimic(mesh);
om->details[j].mesh->billboard = billboard;
om->details[j].mesh->sortedObject = sortedObject;
*detailNum = j;
break; // all in order
}
else
// not here, move j-1 up to make room
om->details[j] = om->details[j-1];
}
return om;
}
void ShapeMimic::addSubtree(AppNode * node)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
subtrees.push_back(new Subtree);
Subtree * subtree = subtrees.back();
std::vector<S32> & validDetails = subtree->validDetails;
std::vector<const char*> & detailNames = subtree->detailNames;
std::vector<AppNode*> & detailNodes = subtree->detailNodes;
const char * name = node->getName();
// we need to create a dummy node for branches to hang off of
// it will correspond to pNode...but won't be exported
subtree->start.appNode = node;
subtree->start.parent = NULL;
subtree->start.child = NULL;
subtree->start.sibling = NULL;
// first go through the top level and parse
// into detail markers and shape branches
std::vector<AppNode*> branches;
S32 i;
for (i=0; i<node->getNumChildNodes(); i++)
{
AppNode * child = node->getChildNode(i);
// we'll deal with these separately...
if (child->isBounds())
continue;
if (child->getNumChildNodes()==0)
{
S32 size;
char * dname = chopTrailingNumber(child->getName(),size);
if (strcmp(dname,child->getName()))
{
delete [] dname;
dname = strnew(child->getName()); // use full name, with size
validDetails.push_back(size);
detailNames.push_back(dname);
detailNodes.push_back(child);
AppConfig::PrintDump(PDPass2,avar("Adding detail named \"%s\" of size %i to subtree \"%s\".\r\n", dname, size, name));
}
else
{
AppConfig::PrintDump(PDPass2,avar("Ignoring node named \"%s\" off subtree \"%s\" because no trailing number.\r\n", dname,name));
delete [] dname;
}
}
else
branches.push_back(child);
}
// the detail list
// need to keep names and sizes in synch...we don't already
// have a struct set up to do this the easy way, so do a
// bubble sort
S32 j;
for (i=0; i<(S32)validDetails.size()-1; i++)
{
for (j=i+1; j<validDetails.size(); j++)
{
if (validDetails[j]>validDetails[i])
{
S32 tmpInt;
const char * tmpCh;
tmpInt = validDetails[i];
tmpCh = detailNames[i];
validDetails[i] = validDetails[j];
detailNames[i] = detailNames[j];
validDetails[j] = tmpInt;
detailNames[j] = tmpCh;
}
}
}
if (validDetails.empty() || branches.empty())
{
// nothing here, but if we made it this far it isn't an error
delete subtree;
subtrees.pop_back();
return;
}
addNode(&subtree->start,node,validDetails,false);
for (i=0; i<branches.size(); i++)
addNode(subtree->start.child,branches[i],validDetails,true);
// everything needs to be rooted to the bounds node...
subtree->start.appNode = boundsNode;
}
void ShapeMimic::addNode(NodeMimic * mimicParent,
AppNode * appChild,
std::vector<S32> & validDetails,
bool recurseChildren)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// if it's the bounds node or a camera, don't do anything
if (appChild->isBounds())
return;
AppConfig::PrintDump(PDPass2,avar("Adding node \"%s\" with parent \"%s\" to subtree rooted on node \"%s\".\r\n", appChild->getName(), mimicParent->appNode->getName(),subtrees.back()->start.appNode->getName()));
NodeMimic * mimicChild = new NodeMimic;
mimicChild->appNode = appChild;
mimicChild->parent = mimicParent;
mimicChild->child = NULL;
mimicChild->sibling = NULL;
if (mimicParent->child)
{
// put us at the end of parent's child list
NodeMimic * sib = mimicParent->child;
while (sib->sibling)
sib = sib->sibling;
sib->sibling = mimicChild;
}
else
// so far, an only child
mimicParent->child = mimicChild;
S32 i;
for (i=0; i<appChild->getNumMesh(); i++)
{
AppMesh * appMesh = appChild->getMesh(i);
if (!appMesh->isDummy())
{
AppConfig::PrintDump(PDPass2,"Attaching object to node.\r\n");
mimicChild->objects.push_back(addObject(appChild,appMesh,&validDetails));
}
}
// now mimic the children of maxChild...
if (recurseChildren)
{
S32 i;
for (i=0; i<appChild->getNumChildNodes(); i++)
addNode(mimicChild,appChild->getChildNode(i),validDetails,true);
}
}
void ShapeMimic::addSkin(AppMesh * mesh)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
// detect MultiRes...
std::vector<S32> multiResSize;
std::vector<F32> multiResPercent;
getMultiResData(mesh,multiResSize,multiResPercent);
if (multiResSize.size())
{
//addMultiRes(node,node);
for (S32 i=0; i<multiResSize.size(); i++)
// om will be the same for each object
addSkin(mesh,true,multiResSize[i],multiResPercent[i]);
}
addSkin( mesh, false );
}
void ShapeMimic::addSkin(AppMesh * mesh, bool multiRes, S32 multiResSize, F32 multiResPercent )
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
S32 i,j,k;
skins.push_back(new SkinMimic);
SkinMimic * skinMimic = skins.back();
skinMimic->appMesh = mesh;
skinMimic->multiResPercent = multiResPercent;
if( multiResSize > 0 )
skinMimic->detailSize = multiResSize;
else
skinMimic->detailSize = getTrailingNumber(mesh->getName());
// get offset matrix
Matrix<4,4,F32> meshTransform = mesh->getMeshTransform(AppTime::DefaultTime());
Matrix<4,4,F32> boundsTransform = boundsNode->getNodeTransform(AppTime::DefaultTime());
zapScale(boundsTransform);
Matrix<4,4,F32> objectOffset = boundsTransform.inverse() * meshTransform;
// lock the mesh
AppMeshLock lock = mesh->lockMesh(AppTime::DefaultTime(),objectOffset);
// get bones
S32 numBones = mesh->getNumBones();
skinMimic->bones.resize(numBones);
for (i=0; i<numBones; i++)
{
skinMimic->bones[i] = mesh->getBone(i);
AppConfig::PrintDump(PDPass2,avar("Adding skin object from skin \"%s\" to bone \"%s\" (%i).\r\n",mesh->getName(),skinMimic->bones[i]->getName(),i));
}
// if no bones...don't add anything
if (skinMimic->bones.empty())
{
delete skins.back();
skins.pop_back();
return;
}
// generate the faces of the mesh -- will be transfered to objects on subtrees later (as ts objects are generated)
AppConfig::PrintDump(PDPass2,avar("Generating faces for skin \"%s\".\r\n",mesh->getName()));
mesh->generateFaces(skinMimic->faces,
skinMimic->verts,
skinMimic->tverts,
skinMimic->indices,
skinMimic->smoothingGroups,
skinMimic->normals,
&skinMimic->vertId);
std::vector<U32> vertMap;
for( i=0; i<skinMimic->verts.size(); i++ )
vertMap.push_back(i);
if (AppConfig::IsExportError()) return;
S32 numVerts = skinMimic->verts.size();
skinMimic->weights.resize(numBones);
for (i=0; i<skinMimic->weights.size(); i++)
{
skinMimic->weights[i] = new SkinMimic::WeightList;
skinMimic->weights[i]->resize(numVerts);
}
for (i=0; i<numBones; i++)
for (j=0; j<numVerts; j++)
(*skinMimic->weights[i])[j] = mesh->getWeight(i,vertMap[j]);
// limit number of bones per vertex and apply weight threshhold
for (i=0;i<skinMimic->weights[0]->size();i++)
{
F32 ** hi = new F32 * [AppConfig::WeightsPerVertex()];
for (k=0; k<AppConfig::WeightsPerVertex(); k++)
hi[k] = NULL;
for (j=0; j<skinMimic->bones.size(); j++)
{
F32 & w = (*skinMimic->weights[j])[i];
for (k=0; k<AppConfig::WeightsPerVertex() && (!hi[k] || *hi[k]<w); k++);
k--;
if (k<0 || w<AppConfig::WeightThreshhold())
{
w=0.0f;
continue;
}
S32 pos = k;
// zero out least significant saved weight
if (hi[0])
*hi[0] = 0.0f;
// shift all the weights below new one down a notch
for (k=0;k<pos;k++)
hi[k] = hi[k+1];
// add our new weight
hi[pos] = &w;
}
F32 sum = 0;
for (k=0;k<AppConfig::WeightsPerVertex();k++)
if (hi[k])
sum += *hi[k];
if (sum>AppConfig::WeightThreshhold())
for (k=0;k<AppConfig::WeightsPerVertex();k++)
if (hi[k])
*hi[k] /= sum;
delete [] hi;
}
// some bones may no longer have any weight...if so, throw them out
for (i=0;i<skinMimic->bones.size();i++)
{
F32 sum = 0.0f;
for (j=0;j<skinMimic->weights[i]->size();j++)
sum += (*skinMimic->weights[i])[j];
if (sum<AppConfig::WeightThreshhold())
{
// delete weight data for this bone
AppConfig::PrintDump(PDPass2,avar("Deleting skin object \"%s\" with no weight.\r\n",skinMimic->bones[i]->getName()));
delete skinMimic->weights[i];
delElementAtIndex(skinMimic->weights,i);
delElementAtIndex(skinMimic->bones,i);
i--;
}
}
MeshMimic * meshMimic = addSkinObject(skinMimic); // goes into object list without node...
// generate the faces of the mesh -- will be transfered to objects on subtrees later (as ts objects are generated)
// AppConfig::PrintDump(PDPass2,avar("Generating faces for skin \"%s\".\r\n",mesh->getName()));
// mesh->generateFaces(skinMimic->faces,
// skinMimic->verts,
// skinMimic->tverts,
// skinMimic->indices,
// skinMimic->smoothingGroups,
// skinMimic->normals,
// &skinMimic->vertId);
// meshMimic->numVerts = mesh->getNumVerts();
meshMimic->numVerts = skinMimic->verts.size();
if (skinMimic->normals.size() == 0)
{
skinMimic->normals.resize(meshMimic->numVerts);
for (i=0; i<skinMimic->normals.size(); i++)
// normals get reset when collapsing verts...make sure sensible value is in here for now
skinMimic->normals[i] = Point3D(0,0,1);
}
// make sure all the materials are added
for (j=0; j<skinMimic->faces.size(); j++)
{
// add material for face j
S32 mi = addFaceMaterial(mesh,skinMimic->faces[j].type&Primitive::NoMaterial ? -1 : skinMimic->faces[j].type&Primitive::MaterialMask);
// replace appmesh material index with ts material index
skinMimic->faces[j].type &= ~Primitive::MaterialMask;
if (mi<0)
skinMimic->faces[j].type |= Primitive::NoMaterial;
else
skinMimic->faces[j].type |= mi;
}
// iterate through the subtrees looking for bones...when we find them, add a skin object
for (i=0; i<subtrees.size(); i++)
{
Subtree * subtree = subtrees[i];
NodeMimic * mimicNode = subtree->start.child;
while (mimicNode)
{
if (mimicNode==&subtree->start)
{
// this should just never happen...
AppConfig::SetExportError("13", "Assertion failed: Illegal condition.");
return;
}
// a bone?
for (j=0; j<skinMimic->bones.size(); j++)
{
if (skinMimic->bones[j]->isEqual(mimicNode->appNode))
{
ObjectMimic * obj = addBoneObject(skinMimic->bones[j],i);
if (!obj)
return;
for (k=0;k<mimicNode->objects.size();k++)
if (mimicNode->objects[k]==obj)
break;
if (k==mimicNode->objects.size())
mimicNode->objects.push_back(obj);
}
}
// figure out where to go next
mimicNode = findNextNode(mimicNode);
}
}
}
ObjectMimic * ShapeMimic::addBoneObject(AppNode * node, S32 subtreeNum)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return NULL;
const char * name = node->getName();
char * boneName = new char[strlen(name)+20];
sprintf(boneName,"Bone::%s:",name);
S32 detailPos;
ObjectMimic * om = getObject(node,NULL,boneName,0,&detailPos,1.0,false,true,false);
return om;
}
MeshMimic * ShapeMimic::addSkinObject(SkinMimic * skinMimic)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return NULL;
S32 size;
// first, separate object name from detail size for current mesh
const char * name = skinMimic->appMesh->getName();
char * objectName = chopTrailingNumber(name,size);
S32 detailPos;
ObjectMimic * om = getObject(NULL,skinMimic->appMesh,objectName,skinMimic->detailSize,&detailPos,1.0,true,false,true);
if (AppConfig::IsExportError() || om==NULL) return NULL; // detailPos might not be valid...
om->details[detailPos].mesh->skinMimic = skinMimic;
om->subtreeNum = 0; // we'll assume skins are on the first detail...at least for now
om->validDetails = &subtrees[om->subtreeNum]->validDetails;
om->appParent = om->appTSParent = NULL;
om->inTreeMesh = NULL;
om->inTreeNode = NULL;
return om->details[detailPos].mesh;
}
void ShapeMimic::dumpShapeNode(Shape * shape, S32 level, S32 nodeIndex, std::vector<S32> & detailSizes)
{
if (nodeIndex<0)
return;
S32 i;
char space[256];
for (i = 0; i < level*3; i++)
space[i] = ' ';
space[level*3] = '\0';
const char *nodeName = "";
const Node & node = shape->nodes[nodeIndex];
if (node.name != -1)
nodeName = shape->names[node.name].c_str();
AppConfig::PrintDump(PDShapeHierarchy,avar("%s%s", space, nodeName));
// find all the objects that hang off this node...
std::vector<Object*> objectList;
for (i=0; i<shape->objects.size(); i++)
if (shape->objects[i].node == nodeIndex)
objectList.push_back(&shape->objects[i]);
if (objectList.size() == 0)
AppConfig::PrintDump(PDShapeHierarchy,"\r\n");
S32 spaceCount = -1;
for (S32 j=0;j<objectList.size(); j++)
{
Object * obj = objectList[j];
if (!obj)
continue;
// object name
const char *objectName = "";
if (obj->name!=-1)
objectName = shape->names[obj->name].c_str();
// more spaces if this is the second object on this node
if (spaceCount>0)
{
char buf[1024];
memset(buf,' ',spaceCount);
buf[spaceCount] = '\0';
AppConfig::PrintDump(PDShapeHierarchy,buf);
}
// dump object name
AppConfig::PrintDump(PDShapeHierarchy,avar(" --> Object %s with following details: ",objectName));
// dump object detail levels
for (S32 k=0; k<obj->numMeshes; k++)
{
S32 f = obj->firstMesh;
if (shape->meshes[f+k].getType() != Mesh::T_Null)
AppConfig::PrintDump(PDShapeHierarchy,avar(" %i",detailSizes[k]));
}
AppConfig::PrintDump(PDShapeHierarchy,"\r\n");
// how many spaces should we prepend if we have another object on this node
if (spaceCount<0)
spaceCount = (S32)(strlen(space) + strlen(nodeName));
}
// search for children
for (S32 k=nodeIndex+1; k<shape->nodes.size(); k++)
{
if (shape->nodes[k].parent == nodeIndex)
// this is our child
dumpShapeNode(shape, level+1, k, detailSizes);
}
}
void ShapeMimic::dumpShape(Shape * shape)
{
S32 i,j,ss,od,sz;
const char * name;
AppConfig::PrintDump(PDShapeHierarchy,"\r\nShape Hierarchy:\r\n");
AppConfig::PrintDump(PDShapeHierarchy,"\r\n Details:\r\n");
for (i=0; i<shape->detailLevels.size(); i++)
{
const DetailLevel & detail = shape->detailLevels[i];
name = detail.name<0 ? NULL : shape->names[detail.name].c_str();
ss = detail.subshape;
od = detail.objectDetail;
sz = (S32)detail.size;
AppConfig::PrintDump(PDShapeHierarchy,avar(" %s, Subtree %i, objectDetail %i, size %i\r\n",name,ss,od,sz));
}
AppConfig::PrintDump(PDShapeHierarchy,"\r\n Subtrees:\r\n");
for (i=0; i<shape->subshapes.size(); i++)
{
S32 a = shape->subshapes[i].firstNode;
S32 b = a + shape->subshapes[i].numNodes;
AppConfig::PrintDump(PDShapeHierarchy,avar(" Subtree %i\r\n",i));
// compute detail sizes for each subshape
std::vector<S32> detailSizes;
for (S32 l=0;l<shape->detailLevels.size(); l++)
{
if (shape->detailLevels[l].subshape==i)
detailSizes.push_back((S32)shape->detailLevels[l].size);
}
for (j=a; j<b; j++)
{
const Node & node = shape->nodes[j];
// if the node has a parent, it'll get dumped via the parent
if (node.parent<0)
dumpShapeNode(shape,3,j,detailSizes);
}
}
bool foundSkin = false;
for (i=0; i<shape->objects.size(); i++)
{
if (shape->objects[i].node<0) // must be a skin
{
if (!foundSkin)
AppConfig::PrintDump(PDShapeHierarchy,"\r\n Skins:\r\n");
foundSkin=true;
const char * skinName = "";
S32 nameIndex = shape->objects[i].name;
if (nameIndex>=0)
skinName = nameIndex<0 ? NULL : shape->names[nameIndex].c_str();
AppConfig::PrintDump(PDShapeHierarchy,avar(" Skin %s with following details: ",skinName));
for (S32 num=0; num<shape->objects[i].numMeshes; num++)
{
if (shape->meshes[num].getType() != Mesh::T_Null)
AppConfig::PrintDump(PDShapeHierarchy,avar(" %i",(S32)shape->detailLevels[num].size));
}
AppConfig::PrintDump(PDShapeHierarchy,"\r\n");
}
}
if (foundSkin)
AppConfig::PrintDump(PDShapeHierarchy,"\r\n");
AppConfig::PrintDump(PDShapeHierarchy,"\r\n Sequences:\r\n");
for (i = 0; i < shape->sequences.size(); i++)
{
const char *name = "(none)";
S32 nameIndex = shape->sequences[i].nameIndex;
if (nameIndex != -1)
name = shape->names[nameIndex].c_str();
AppConfig::PrintDump(PDShapeHierarchy,avar(" %3d: %s\r\n",i,name));
}
std::vector<Material> & ml = shape->materials;
AppConfig::PrintDump(PDShapeHierarchy,"\r\n Material list:\r\n");
for (i=0; i<ml.size(); i++)
{
U32 flags = ml[i].flags;
const char * name = ml[i].name.c_str();
AppConfig::PrintDump(PDShapeHierarchy,avar(" material #%i: \"%s\"%s.",i,name ? name : "",flags&(Material::SWrap|Material::TWrap) ? "" : " not tiled"));
if (flags & Material::IFLMaterial)
AppConfig::PrintDump(PDShapeHierarchy," Place holder for ifl.");
if (flags & Material::IFLFrame)
AppConfig::PrintDump(PDShapeHierarchy," Ifl frame.");
if (flags & Material::DetailMap)
AppConfig::PrintDump(PDShapeHierarchy," Used as a detail map.");
if (flags & Material::BumpMap)
AppConfig::PrintDump(PDShapeHierarchy," Used as a bump map.");
if (flags & Material::ReflectanceMap)
AppConfig::PrintDump(PDShapeHierarchy," Used as a reflectance map.");
if (flags & Material::Translucent)
{
if (flags & Material::Additive)
AppConfig::PrintDump(PDShapeHierarchy," Additive-translucent.");
else if (flags & Material::Subtractive)
AppConfig::PrintDump(PDShapeHierarchy," Subtractive-translucent.");
else
AppConfig::PrintDump(PDShapeHierarchy," Translucent.");
}
AppConfig::PrintDump(PDShapeHierarchy,"\r\n");
}
}
//--------------------------------------------
// get multi-res info from a node...
void ShapeMimic::getMultiResData(AppNode * node, std::vector<S32> & multiResSize, std::vector<F32> & multiResPercent)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
S32 numAutoDetails = 0;
node->getInt( "numAutoDetails", numAutoDetails );
if( numAutoDetails == 0 )
return;
AppConfig::PrintDump(PDAlways,"Found multiRes data.\r\n");
multiResSize.resize( numAutoDetails );
multiResPercent.resize( numAutoDetails );
S32 i;
for( i = 0; i < numAutoDetails; i++ )
{
multiResSize[i] = 2;
multiResPercent[i] = 1.0f;
node->getInt( avar( "autoDetailSize%i", i ), multiResSize[i] );
node->getFloat( avar( "autoDetailPercent%i", i ), multiResPercent[i] );
}
// make sure percent's are in the right order...sort if they aren't
for (S32 i=0; i<multiResSize.size(); i++)
{
for (S32 j=i+1; j<multiResSize.size(); j++)
{
if (multiResSize[i]<multiResSize[j])
{
S32 tmp1 = multiResSize[i];
multiResSize[i]=multiResSize[j];
multiResSize[j]=tmp1;
F32 tmp2 = multiResPercent[i];
multiResPercent[i]=multiResPercent[j];
multiResPercent[j]=tmp2;
}
}
}
}
//--------------------------------------------
// get multi-res info from a node...
void ShapeMimic::getMultiResData(AppMesh * node, std::vector<S32> & multiResSize, std::vector<F32> & multiResPercent)
{
// if already encountered an error, then
// we'll just go through the motions
if (AppConfig::IsExportError()) return;
S32 numAutoDetails = 0;
node->getInt( "numAutoDetails", numAutoDetails );
if( numAutoDetails == 0 )
return;
AppConfig::PrintDump(PDAlways,"Found multiRes data.\r\n");
multiResSize.resize( numAutoDetails );
multiResPercent.resize( numAutoDetails );
S32 i;
for( i = 0; i < numAutoDetails; i++ )
{
multiResSize[i] = 2;
multiResPercent[i] = 1.0f;
node->getInt( avar( "autoDetailSize%i", i ), multiResSize[i] );
node->getFloat( avar( "autoDetailPercent%i", i ), multiResPercent[i] );
}
// make sure size's are in the right order...sort if they aren't
for (S32 i=0; i<multiResSize.size(); i++)
{
for (S32 j=i+1; j<multiResSize.size(); j++)
{
if (multiResSize[i]<multiResSize[j])
{
S32 tmp1 = multiResSize[i];
multiResSize[i]=multiResSize[j];
multiResSize[j]=tmp1;
F32 tmp2 = multiResPercent[i];
multiResPercent[i]=multiResPercent[j];
multiResPercent[j]=tmp2;
}
}
}
}
} // namespace DTS