3961 lines
138 KiB
C++
Executable File
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
|
|
|