tge/tools/max2dtsExporter/ShapeMimic.cc
2017-04-17 06:17:10 -06:00

8820 lines
303 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "max2dtsExporter/ShapeMimic.h"
#include "max2dtsExporter/SceneEnum.h"
#include "max2dtsExporter/maxUtil.h"
#include "max2dtsExporter/Sequence.h"
#include "max2dtsExporter/stripper.h"
#include "max2dtsExporter/translucentSort.h"
#include "max2dtsExporter/skinHelper.h"
#pragma pack(push,8)
#include <istdplug.h>
#include <iparamm2.h>
#include <modstack.h>
#pragma pack(pop)
// The ShapeMimic tries to hold court in both the max 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 max objects and delays computing certain things
// until the tsshape is finally created in generateShape().
#define printDump SceneEnumProc::printDump
#define QUICK_STRIP
//#define NV_STRIP
// simple struct to bind scale factors together (TSScale uses a QuatF)
struct ScaleStruct
{
Quat16 mRotate;
Point3F mScale;
};
Vector<Quat16*> nodeRotCache;
Vector<Point3F*> nodeTransCache;
Vector<ScaleStruct*> nodeScaleCache;
Vector<INode*> cutNodes;
Vector<INode*> cutNodesParents;
bool cullOffTile = true;
// briefly mentioned in MaxSDK docs, not in any include files...
#define PB_CLIPU 0
#define PB_CLIPV 1
#define PB_CLIPW 2
#define PB_CLIPH 3
#define PB_APPLY 6
#define PB_CROP_PLACE 7
#define HACK_PLANE_CLASS_ID Class_ID(136257020,2002153317)
F32 sameVertTOL = 0.00005f;
F32 sameTVertTOL = 0.00005f;
F32 mSq(F32 a) { return a*a; }
//--------------------------------------------
// Decal data structure and enum
struct DecalInfo
{
INode * decalNode;
Bitmap * filter;
S32 tsMat;
U32 decalType;
Point3F pos;
Point3F x;
Point3F y;
Point3F z;
F32 maxAngle;
F32 minCos;
Vector<Point3> verts;
Vector<Point3> tverts;
Vector<TSDrawPrimitive> faces;
Vector<U16> indices;
Vector<Point3F> n;
Vector<F32> k;
} gDecalInfo;
enum
{
SphereDecal = 0,
CylinderDecal = 1,
BoxDecal = 2,
PlaneDecal = 3
};
struct MipmapMethod
{
S32 noMipmap;
S32 noMipmapTranslucent;
S32 zapBorder;
} gMipmapMethod;
S32 gPolyCount;
F32 gMinVolPerPoly;
F32 gMaxVolPerPoly;
Vector<S32> t2AutoDetailSizes;
Vector<F32> t2AutoDetailPercents;
S32 findClosestMatch(Point3F & p, Vector<Point3F> & points)
{
F32 closestValue = 10E30f;
S32 closestIndex = -1;
for (S32 i=0; i<points.size(); i++)
{
Point3F delta = p-points[i];
if (mDot(delta,delta)<=closestValue)
{
closestIndex=i;
closestValue = mDot(delta,delta);
}
}
return closestIndex;
}
//--------------------------------------------
// Constructor/destructor
ShapeMimic::ShapeMimic()
{
errorStr = NULL;
}
ShapeMimic::~ShapeMimic()
{
S32 i;
dFree(errorStr);
for (i=0;i<objectList.size();i++)
delete objectList[i];
for (i=0;i<decalObjectList.size();i++)
delete decalObjectList[i];
for (i=0;i<skins.size();i++)
delete skins[i];
for (i=0;i<multiResList.size();i++)
{
// restore multiRes meshes to original state
Animatable * obj = (Animatable*)multiResList[i]->multiResNode->GetObjectRef();
for (S32 j=0; j<obj->NumSubs(); j++)
{
if (!dStrcmp(obj->SubAnimName(j),"MultiRes"))
{
IParamBlock * paramBlock = (IParamBlock*)obj->SubAnim(j)->SubAnim(0);
Interval range = multiResList[i]->multiResNode->GetTimeRange(TIMERANGE_ALL|TIMERANGE_CHILDNODES|TIMERANGE_CHILDANIMS);
paramBlock->SetValue(0,DEFAULT_TIME,multiResList[i]->totalVerts);
break;
}
}
delete multiResList[i];
}
for (i=0;i<subtrees.size();i++)
{
S32 j;
for (j=0;j<subtrees[i]->detailNames.size();j++)
dFree((char*) subtrees[i]->detailNames[j]);
NodeMimic * node = subtrees[i]->start.child;
nodes.clear();
while (node)
{
nodes.push_back(node);
node = findNextNode(node);
}
for (j=0;j<nodes.size();j++)
delete nodes[j];
delete subtrees[i];
}
for (i=0;i<materials.size();i++)
dFree(materials[i]);
for (i=0;i<iflList.size();i++)
delete iflList[i];
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<nodeScaleCache.size(); i++)
delete [] nodeScaleCache[i];
nodeScaleCache.clear();
}
//--------------------------------------------
// error handling:
bool ShapeMimic::isError()
{
return errorStr != NULL;
}
void ShapeMimic::setExportError(const char * str)
{
errorStr = errorStr ? errorStr : dStrdup(str);
}
const char * ShapeMimic::getError()
{
return errorStr;
}
//--------------------------------------------
// some utilities
S32 __cdecl compareTSDetails( void const *e1, void const *e2 )
{
const TSShape::Detail * d1 = (const TSShape::Detail*)e1;
const TSShape::Detail * d2 = (const TSShape::Detail*)e2;
if (d1->size > d2->size)
return -1;
if (d2->size > d1->size)
return 1;
return 0;
}
void sortTSDetails(Vector<TSShape::Detail> & details)
{
dQsort(details.address(),details.size(),sizeof(TSShape::Detail),compareTSDetails);
}
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 sortObjectList(Vector<ObjectMimic*> & olist)
{
dQsort(olist.address(),olist.size(),sizeof(ObjectMimic*),compareObjectMimics);
}
S32 __cdecl compareDecalObjectMimics( void const *e1, void const *e2 )
{
const DecalObjectMimic * dom1 = *(const DecalObjectMimic**)e1;
const DecalObjectMimic * dom2 = *(const DecalObjectMimic**)e2;
if (dom1->subtreeNum < dom2->subtreeNum)
return -1;
else if (dom1->priority < dom2->priority)
return -1;
else if (dom2->subtreeNum < dom1->subtreeNum)
return 1;
else if (dom2->priority < dom1->priority)
return 1;
else
return 0;
}
void sortDecalObjectList(Vector<DecalObjectMimic*> & dlist)
{
dQsort(dlist.address(),dlist.size(),sizeof(DecalObjectMimic*),compareDecalObjectMimics);
}
S32 __cdecl compareFaces( void const *e1, void const *e2 )
{
const TSDrawPrimitive * face1 = (const TSDrawPrimitive*)e1;
const TSDrawPrimitive * face2 = (const TSDrawPrimitive*)e2;
if (face1->matIndex < face2->matIndex)
return -1;
else if (face2->matIndex < face1->matIndex)
return 1;
else return 0;
}
void sortFaceList(Vector<TSDrawPrimitive> & faces)
{
dQsort(faces.address(),faces.size(),sizeof(TSDrawPrimitive),compareFaces);
}
//--------------------------------------------
// set up objects for sorting
void ShapeMimic::setObjectPriorities(Vector<ObjectMimic*> & objectList)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
S32 i,j;
for (i=0; i<objectList.size(); i++)
{
ObjectMimic * om = objectList[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 (in case we are writing depth values).
// 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;
}
INode * mesh = om->details[j].mesh->pNode;
if (!mesh)
// not sure what this would mean, but don't want to crash
continue;
bool delTri;
TriObject * tri = getTriObject(mesh,DEFAULT_TIME,-1,delTri);
Mesh & maxMesh = tri->mesh;
bool hasTranslucent = false;
bool hasRepeat = false;
bool isSortObject = om->details[j].mesh->sortedObject;
S32 matIndex = -1;
for (j=0; j<maxMesh.getNumFaces(); j++)
{
CropInfo cropInfo; // throw-away...
S32 mi = addMaterial(mesh,maxMesh.faces[j].getMatID(),&cropInfo); // we'll end up adding again later...
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (mi==matIndex)
continue;
if (matIndex!=-1)
hasRepeat=true;
matIndex = mi;
if (!(matIndex & TSDrawPrimitive::NoMaterial) && (materialFlags[matIndex] & TSMaterialList::Translucent))
hasTranslucent = true;
}
if (delTri)
delete tri;
if (hasTranslucent && !isSortObject)
om->priority |= 3 << 30;
else if (hasTranslucent && isSortObject)
om->priority |= 2 << 30;
if (!hasRepeat)
{
om->priority |= 1 << 29;
om->priority |= (matIndex & 0x0FFF) << 16;
}
}
}
//--------------------------------------------
// set up decals for sorting
void ShapeMimic::setDecalObjectPriorities(Vector<DecalObjectMimic*> & decals)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
S32 i;
for (i=0; i<decals.size(); i++)
{
decals[i]->priority = 0; // they'll all be translucent, so don't worry about it
decals[i]->subtreeNum = decals[i]->targetObject->subtreeNum;
}
}
//--------------------------------------------
// a fleet of utility functions used to
// generate animation
//--------------------------------------------
struct ExporterSequenceData
{
S32 cyclic;
S32 blend;
S32 blendReferenceTime;
S32 priority;
S32 addPadFrame;
S32 useFrameRate;
F32 frameRate;
S32 numFrames;
S32 ignoreGround;
S32 useGroundFrameRate;
F32 groundFrameRate;
S32 groundNumFrames;
S32 enableMorph;
S32 enableVis;
S32 enableTransform;
S32 enableUniformScale;
S32 enableArbitraryScale;
S32 enableTVert;
S32 enableIFL;
S32 enableDecal;
S32 enableDecalFrame;
S32 forceMorph;
S32 forceVis;
S32 forceTransform;
S32 forceScale;
S32 forceTVert;
S32 forceDecal;
S32 overrideDuration;
F32 overriddenDuration;
};
void setupOldSequence(IParamBlock * pblock, Interval range, ExporterSequenceData * data)
{
// if we got this far with an old sequence, then it's allowed
// but we need to figure out what values to use
S32 oneShot;
S32 forceVis;
S32 visOnly;
pblock->GetValue(PB_OLD_SEQ_ONESHOT, range.Start(), oneShot, FOREVER);
pblock->GetValue(PB_OLD_SEQ_FORCEVIS, range.Start(), forceVis, FOREVER );
pblock->GetValue(PB_OLD_SEQ_VISONLY, range.Start(), visOnly, FOREVER );
data->cyclic = !oneShot;
data->blend = false;
data->blendReferenceTime = 0;
data->priority = getIntSequenceDefault(PB_SEQ_DEFAULT_PRIORITY);
data->addPadFrame = !getBoolSequenceDefault(PB_SEQ_LAST_FIRST_FRAME_SAME);
data->useFrameRate = getBoolSequenceDefault(PB_SEQ_USE_FRAME_RATE);
data->frameRate = getFloatSequenceDefault(PB_SEQ_FRAME_RATE);
data->numFrames = getIntSequenceDefault(PB_SEQ_NUM_FRAMES);
data->ignoreGround = getBoolSequenceDefault(PB_SEQ_IGNORE_GROUND_TRANSFORM);
data->useGroundFrameRate = getBoolSequenceDefault(PB_SEQ_USE_GROUND_FRAME_RATE);
data->groundFrameRate = getFloatSequenceDefault(PB_SEQ_GROUND_FRAME_RATE);
data->groundNumFrames = getIntSequenceDefault(PB_SEQ_NUM_GROUND_FRAMES);
data->forceMorph = false;
data->forceVis = forceVis;
data->forceTransform = false;
data->forceScale = false;
data->forceTVert = false;
data->forceDecal = false;
data->enableMorph = true;
data->enableVis = true;
data->enableTransform = true;
data->enableUniformScale = false;
data->enableArbitraryScale = false;
data->enableIFL = true;
data->enableTVert = true;
data->enableDecal = false;
data->enableDecalFrame = false;
if (visOnly)
{
data->enableMorph = false;
data->enableTransform = false;
data->enableIFL = false;
data->enableTVert = false;
}
data->overrideDuration = false;
data->overriddenDuration = 1.0f;
}
void setupSequence(IParamBlock * pblock, Interval range, ExporterSequenceData * data)
{
S32 noPad;
pblock->GetValue(PB_SEQ_CYCLIC, range.Start(), data->cyclic, FOREVER);
pblock->GetValue(PB_SEQ_BLEND, range.Start(), data->blend, FOREVER);
data->blendReferenceTime = 0;
Control * control = pblock->GetController(PB_SEQ_BLEND_REFERENCE_TIME);
IKeyControl * ikc = control ? GetKeyControlInterface(control) : NULL;
if (ikc && ikc->GetNumKeys()>=1)
{
// find first keyframe...time will determine reference position of blend sequence
ILinFloatKey key;
char buffer[128]; // just in case some other key type gets in there make sure nothing gets trashed...
ikc->GetKey(0,&key);
data->blendReferenceTime = key.time;
}
pblock->GetValue(PB_SEQ_DEFAULT_PRIORITY, range.Start(), data->priority, FOREVER);
pblock->GetValue(PB_SEQ_LAST_FIRST_FRAME_SAME, range.Start(), noPad, FOREVER);
data->addPadFrame = !noPad;
pblock->GetValue(PB_SEQ_USE_FRAME_RATE, range.Start(), data->useFrameRate, FOREVER);
pblock->GetValue(PB_SEQ_FRAME_RATE, range.Start(), data->frameRate, FOREVER);
pblock->GetValue(PB_SEQ_NUM_FRAMES, range.Start(), data->numFrames, FOREVER);
pblock->GetValue(PB_SEQ_IGNORE_GROUND_TRANSFORM,range.Start(), data->ignoreGround, FOREVER);
pblock->GetValue(PB_SEQ_USE_GROUND_FRAME_RATE, range.Start(), data->useGroundFrameRate, FOREVER);
pblock->GetValue(PB_SEQ_GROUND_FRAME_RATE, range.Start(), data->groundFrameRate, FOREVER);
pblock->GetValue(PB_SEQ_NUM_GROUND_FRAMES, range.Start(), data->groundNumFrames, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_MORPH_ANIMATION, range.Start(), data->enableMorph, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_VIS_ANIMATION, range.Start(), data->enableVis, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_TRANSFORM_ANIMATION, range.Start(), data->enableTransform, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_UNIFORM_SCALE_ANIMATION, range.Start(), data->enableUniformScale, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_ARBITRARY_SCALE_ANIMATION, range.Start(), data->enableArbitraryScale, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_TEXTURE_ANIMATION, range.Start(), data->enableTVert, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_IFL_ANIMATION, range.Start(), data->enableIFL, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_DECAL_ANIMATION, range.Start(), data->enableDecal, FOREVER);
pblock->GetValue(PB_SEQ_ENABLE_DECAL_FRAME_ANIMATION, range.Start(), data->enableDecalFrame, FOREVER);
pblock->GetValue(PB_SEQ_FORCE_MORPH_ANIMATION, range.Start(), data->forceMorph, FOREVER);
pblock->GetValue(PB_SEQ_FORCE_VIS_ANIMATION, range.Start(), data->forceVis, FOREVER);
pblock->GetValue(PB_SEQ_FORCE_TRANSFORM_ANIMATION, range.Start(), data->forceTransform, FOREVER);
pblock->GetValue(PB_SEQ_FORCE_SCALE_ANIMATION, range.Start(), data->forceScale, FOREVER);
pblock->GetValue(PB_SEQ_FORCE_TEXTURE_ANIMATION, range.Start(), data->forceTVert, FOREVER);
pblock->GetValue(PB_SEQ_FORCE_DECAL_ANIMATION, range.Start(), data->forceDecal, FOREVER);
pblock->GetValue(PB_SEQ_OVERRIDE_DURATION, range.Start(), data->overrideDuration, FOREVER);
pblock->GetValue(PB_SEQ_DURATION, range.Start(), data->overriddenDuration, FOREVER);
}
void getSequenceTiming(ExporterSequenceData & data,
F32 * start, F32 * end, F32 * duration,
F32 * delta, S32 * numFrames)
{
// following 4 cases set up duration, delta, and numFrames
// duration goes into sequence while delta and numFrames
// are used to generate keyframes...
// we generate keyframes at:
// start + i * delta, where i: 0 to numFrames-1
if (data.cyclic && data.useFrameRate)
{
if (data.addPadFrame)
// go one frame beyond the end
*end += 1.0f / maxFrameRate; // as in 3ds max
*duration = *end - *start + 0.000001f; // add a little for rounding purposes
*delta = 1.0f / data.frameRate;
*numFrames = (S32) (*duration / *delta);
*delta = *duration / (F32)*numFrames;
}
else if (data.cyclic && !data.useFrameRate)
{
// ignore frame rate, use numFrames instead
// we ignore 'addPadFrame' variable, because
// it would not be useful at all
*numFrames = data.numFrames;
*duration = *end - *start + 0.000001f; // add a little for rounding purposes
*delta = *duration / (F32) *numFrames;
}
else if (!data.cyclic && data.useFrameRate)
{
*duration = *end - *start + 0.000001f; // add a little for rounding purposes
*delta = 1.0f / data.frameRate;
*numFrames = (S32) (*duration / *delta);
*delta = *duration / (F32) *numFrames;
// add one more frame at the end of one-shot (not cyclic) sequence
(*numFrames)++;
}
else // (!data.cyclic && !data.useFrameRate)
{
*duration = *end - *start + 0.000001f; // add a little for rounding purposes
*numFrames = data.numFrames;
*delta = *duration / (F32)(data.numFrames-1);
}
}
void getGroundSequenceTiming(ExporterSequenceData & data, F32 groundDuration,
F32 *groundDelta, S32 *groundNumFrames)
{
if (data.ignoreGround)
return;
if (data.useGroundFrameRate)
{
*groundDelta = groundDuration / (F32) data.groundFrameRate;
*groundNumFrames = (S32)(groundDuration / *groundDelta);
*groundDelta = groundDuration / (F32) *groundNumFrames;
*groundNumFrames++;
}
else
{
*groundNumFrames = data.groundNumFrames;
*groundDelta = groundDuration / (F32)(*groundNumFrames-1);
}
}
//--------------------------------------------
// set global parameters
void ShapeMimic::setGlobals(INode * pNode, S32 polyCount, F32 minVolPerPoly, F32 maxVolPerPoly)
{
if (!pNode->GetUserPropBool("MIPMAP::NO_MIPMAP",gMipmapMethod.noMipmap))
gMipmapMethod.noMipmap = false;
if (!pNode->GetUserPropBool("MIPMAP::NO_MIPMAP_TRANSLUCENT",gMipmapMethod.noMipmapTranslucent))
gMipmapMethod.noMipmapTranslucent = false;
if (!pNode->GetUserPropBool("MIPMAP::BLACK_BORDER",gMipmapMethod.zapBorder))
gMipmapMethod.zapBorder = true;
gPolyCount = polyCount;
gMinVolPerPoly = minVolPerPoly;
gMaxVolPerPoly = maxVolPerPoly;
}
//--------------------------------------------
// set up bounds node
void ShapeMimic::addBounds(INode * pNode)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
boundsNode = pNode;
}
void ShapeMimic::applyAutoDetail(Vector<S32> & validDetails, Vector<const char *> & detailNames, Vector<INode*> & detailNodes)
{
if (t2AutoDetail<51)
{
// this number determines number of polys per new detail level...anything less than 50
// would probably be way excessive, but we'll just make sure user didn't type in "1" for "true"
// a little bit
setExportError("t2AutoDetail parameter must be 0 or greater than 50");
return;
}
if (subtrees.size()>1)
{
setExportError("Too many subtrees: t2Autodetail only works with single subtree objects");
return;
}
S32 numDetails = (gPolyCount+t2AutoDetail*2)/t2AutoDetail;
S32 lastVisibleDetail = -1;
for (S32 i=0; i<validDetails.size(); i++)
if (validDetails[i]>=0)
lastVisibleDetail = i;
S32 numVisibleDetails = lastVisibleDetail+1;
while (numVisibleDetails<numDetails)
{
S32 maxDiff = 0;
S32 idx = -1;
for (S32 i=0; i<numVisibleDetails; i++)
{
S32 diff = i+1<numVisibleDetails ? validDetails[i]-validDetails[i+1] : validDetails[i];
if (diff>maxDiff)
{
maxDiff = diff;
idx = i;
}
}
if (idx<0)
{
validDetails.push_back(128);
detailNames.push_back(dStrdup("detail128"));
detailNodes.push_back(boundsNode); // no detail nodes...just put something legal in here (eek)
}
else
{
validDetails.insert(idx+1);
detailNames.insert(idx+1);
detailNodes.insert(idx+1);
if (idx+2<validDetails.size())
{
validDetails[idx+1] = (validDetails[idx] + validDetails[idx+2])/2;
detailNames[idx+1] = dStrdup(avar("detail%i",validDetails[idx+1]));
detailNodes[idx+1] = detailNodes[idx];
}
else
{
validDetails[idx+1] = (validDetails[idx])/2;
detailNames[idx+1] = dStrdup(avar("detail%i",validDetails[idx+1]));
detailNodes[idx+1] = detailNodes[idx];
}
}
numVisibleDetails++;
}
t2AutoDetailSizes = validDetails;
for (S32 ii=0; ii<t2AutoDetailSizes.size(); ii++)
{
if (t2AutoDetailSizes[ii]<0)
{
t2AutoDetailSizes.erase(ii);
ii--;
}
}
t2AutoDetailPercents.setSize(t2AutoDetailSizes.size());
for (S32 j=0; j<t2AutoDetailPercents.size(); j++)
{
S32 count;
if (j==0)
count=gPolyCount;
else if (j==t2AutoDetailPercents.size()-1)
count = 16;
else if (j==t2AutoDetailPercents.size()-2)
count = 40;
else if (j==t2AutoDetailPercents.size()-3)
count = 80;
else
// when j=0, count=gPolyCount, when j==t2ADP.size()-3, count=80...
count = 80 + (t2AutoDetailPercents.size()-3-j)*(gPolyCount-80)/(t2AutoDetailPercents.size()-3);
t2AutoDetailPercents[j]=(F32)count/(F32)gPolyCount;
if (j==0)
// be sure...
t2AutoDetailPercents[j]=1.0f;
}
}
void ShapeMimic::fixupT2AutoDetail()
{
for (S32 i=0; i<objectList.size(); i++)
{
ObjectMimic * om = objectList[i];
if (om->numDetails==0 || om->details[0].size<0)
continue;
for (S32 j=0; j<t2AutoDetailSizes.size(); j++)
{
if (j>=om->numDetails || om->details[j].size!=t2AutoDetailSizes[j])
{
if (j==0)
{
break;
// setExportError("Assertion failed");
// return;
}
INode * node = om->details[j-1].mesh->pNode;
S32 stopNumber;
if (node->GetUserPropInt("MULTIRES::STOP",stopNumber))
{
if (j>=stopNumber)
// oops, we explicitly limited number of details...
break;
}
S32 pos;
getObject(node,dStrdup(om->name),t2AutoDetailSizes[j],&pos);
}
}
}
}
//--------------------------------------------
// add a subtree
void ShapeMimic::addSubtree(INode * pNode)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
subtrees.increment();
Subtree * pSubtree = subtrees.last() = new Subtree;
Vector<S32> & validDetails = pSubtree->validDetails;
Vector<const char*> & detailNames = pSubtree->detailNames;
Vector<INode*> & detailNodes = pSubtree->detailNodes;
const char * pname = pNode->GetName();
// we need to create a dummy node for branches to hang off of
// it will correspond to pNode...but won't be exported
pSubtree->start.maxNode = pNode;
pSubtree->start.parent = NULL;
pSubtree->start.child = NULL;
pSubtree->start.sibling = NULL;
// first go through the top level and parse
// into detail markers and shape branches
Vector<INode*> branches;
S32 i;
for (i=0; i<pNode->NumberOfChildren(); i++)
{
INode * child = pNode->GetChildNode(i);
// we'll deal with these separately...
if (SceneEnumProc::isBounds(child))
continue;
if (SceneEnumProc::isCamera(child))
continue;
if (child->NumberOfChildren()==0)
{
S32 size;
char * dname = chopTrailingNumber(child->GetName(),size);
if (dStrcmp(dname,child->GetName()))
{
dFree(dname);
dname = dStrdup(child->GetName()); // use full name, with size
validDetails.push_back(size);
detailNames.push_back(dname);
detailNodes.push_back(child);
printDump(PDPass2,avar("Adding detail named \"%s\" of size %i to subtree \"%s\".\r\n", dname, size, pname));
}
else
{
printDump(PDPass2,avar("Ignoring node named \"%s\" off subtree \"%s\" because no trailing number.\r\n", dname,pname));
dFree(dname);
}
}
else
branches.push_back(child);
}
if (validDetails.empty() && !allowEmptySubtrees)
{
setExportError(avar("Subtree \"%s\" has no detail levels -- this error can be turned off",pNode->GetName()));
return;
}
else if (branches.empty() && !allowEmptySubtrees)
{
setExportError(avar("Subtree \"%s\" has nothing in it -- this error can be turned off",pNode->GetName()));
return;
}
// 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 we are doing t2AutoDetail, then this is where we need to re-configure our details
if (t2AutoDetail>1)
applyAutoDetail(validDetails,detailNames,detailNodes);
if (validDetails.empty() || branches.empty())
{
// nothing here, but if we made it this far it isn't an error
delete pSubtree;
subtrees.decrement();
return;
}
addNode(&pSubtree->start,pNode,validDetails,false);
for (i=0; i<branches.size(); i++)
addNode(pSubtree->start.child,branches[i],validDetails,true);
// everything needs to be rooted to the bounds node...
pSubtree->start.maxNode = boundsNode;
}
//--------------------------------------------
// add a node
void ShapeMimic::addNode(NodeMimic * mimicParent,
INode * maxChild,
Vector<S32> & validDetails,
bool recurseChildren)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// if it's the bounds node or a camera, don't do anything
if (SceneEnumProc::isBounds(maxChild) || SceneEnumProc::isCamera(maxChild) || SceneEnumProc::isDecal(maxChild))
return;
printDump(PDPass2,avar("Adding node \"%s\" with parent \"%s\" to subtree rooted on max-node \"%s\".\r\n",
maxChild->GetName(), mimicParent->maxNode->GetName(),subtrees.last()->start.maxNode->GetName()));
NodeMimic * mimicChild = new NodeMimic;
mimicChild->maxNode = maxChild;
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;
if (hasMesh(mimicChild->maxNode) && !SceneEnumProc::isDummy(mimicChild->maxNode))
{
printDump(PDPass2,"Attaching object to node.\r\n");
mimicChild->objects.push_back(addObject(mimicChild->maxNode,&validDetails));
}
// now mimic the children of maxChild...
if (recurseChildren)
{
S32 i;
for (i=0; i<maxChild->NumberOfChildren(); i++)
addNode(mimicChild,maxChild->GetChildNode(i),validDetails,true);
}
}
//--------------------------------------------
// get object from object list -- create if not present
ObjectMimic * ShapeMimic::getObject(INode * pNode, 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 (isError()) 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 = pNode ? SceneEnumProc::isBillboard(pNode) : false;
bool sortedObject = pNode ? SceneEnumProc::isSortedObject(pNode) : 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 (dStrlen(name)>1 && name[dStrlen(name)-1]==':')
{
colon = name+dStrlen(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 = pNode ? !pNode->GetParentNode()->IsRootNode() : false;
// if we're in the tree, we may need the full name...
const char * fullName = NULL;
if (inTree)
{
fullName = pNode->GetName();
SceneEnumProc::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 && !dStricmp(fullName,objectList[i]->fullName))
break;
if ( colon && objectList[i]->fullName && !dStricmp(name,objectList[i]->fullName))
break;
if (!colon && !dStricmp(name,objectList[i]->name))
break;
}
// add an entry if needed
if (i==objectList.size())
{
objectList.increment();
objectList.last() = new ObjectMimic;
objectList.last()->name = name;
// note: not straight forwared ... 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.last()->fullName = dStrdup(fullName);
else if (colon)
objectList.last()->fullName = dStrdup(name);
else
objectList.last()->fullName = NULL;
objectList.last()->numDetails = 0;
objectList.last()->pValidDetails = NULL;
objectList.last()->subtreeNum = -1;
objectList.last()->maxParent = NULL;
objectList.last()->maxTSParent = NULL;
objectList.last()->tsObject = NULL;
objectList.last()->tsNodeIndex = -1;
objectList.last()->isBone = isBone;
objectList.last()->isSkin = isSkin;
printDump(PDPass2,avar("Adding object named \"%s\".\r\n",name));
}
else
dFree(name); // don't need duplicate name
ObjectMimic * om = objectList[i];
if (om->isBone)
{
printDump(PDPass2,"Object is bone\r\n");
om->maxParent=om->maxTSParent=pNode;
return om;
}
// enter data
S32 dl = om->numDetails++;
if (om->numDetails>ObjectMimic::MaxDetails)
{
setExportError(avar("Assertion failed: too many details for mesh %s.",name));
return NULL;
}
printDump(PDPass2,avar("Adding mesh of size %i to object \"%s\".\r\n", size,om->name));
// 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)
{
setExportError(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].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].mesh = new MeshMimic(pNode);
om->details[j].mesh->billboard = billboard;
om->details[j].mesh->sortedObject = sortedObject;
om->details[j].mesh->multiResPercent = multiResPercent;
*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;
}
//--------------------------------------------
// get multi-res mimic
MultiResMimic * ShapeMimic::getMultiRes(INode * pNode)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
for (S32 i=0; i<multiResList.size(); i++)
{
if (multiResList[i]->pNode==pNode)
return multiResList[i];
}
return NULL;
}
void grabMeshData(INode * multiResNode, Vector<Face> & faces, Vector<Point3F> & verts)
{
bool delTri;
TriObject * tri = getTriObject(multiResNode,DEFAULT_TIME,-1,delTri);
Mesh & mesh = tri->mesh;
faces.clear();
verts.clear();
S32 j;
faces.setSize(mesh.getNumFaces());
verts.setSize(mesh.getNumVerts());
for (j=0; j<mesh.getNumFaces(); j++)
faces[j] = mesh.faces[j];
for (j=0; j<mesh.getNumVerts(); j++)
{
verts[j].x = mesh.getVert(j).x;
verts[j].y = mesh.getVert(j).y;
verts[j].z = mesh.getVert(j).z;
}
if (delTri)
delete tri;
}
void findEdgeHard(Vector<Face> & hiMesh, Vector<Face> & loMesh, Vector<Point3F> & verts, S32 & fromA, S32 & toB)
{
S32 i,j,numVert = fromA;
toB = -1; // initialize to illegal value...if remains -1 on exit, then we shouldn't need it later
// we'll have to do this the hard way...look for someone that has changed
for (i=0; i<loMesh.size(); i++)
{
if (i>=hiMesh.size())
continue;
if (hiMesh[i].v[0]==fromA)
toB = loMesh[i].v[0];
else if (hiMesh[i].v[1]==fromA)
toB = loMesh[i].v[1];
else if (hiMesh[i].v[2]==fromA)
toB = loMesh[i].v[2];
else
continue;
return;
}
// if we get here, then the only face/faces that contain vert "fromA" have been
// removed from loMesh...search hiMesh for a face using "fromA" and choose the
// closest vert on the face as the merge vert...
for (i=0; i<hiMesh.size(); i++)
{
for (j=0;j<3;j++)
if (hiMesh[i].v[j]==fromA)
// bingo
break;
if (j==3)
continue;
Point3F delta1 = verts[(S32)hiMesh[i].v[j]] - verts[(S32)hiMesh[i].v[(j+1)%3]];
Point3F delta2 = verts[(S32)hiMesh[i].v[j]] - verts[(S32)hiMesh[i].v[(j+2)%3]];
if (mDot(delta1,delta1)<mDot(delta2,delta2))
toB=hiMesh[i].v[(j+1)%3];
else
toB=hiMesh[i].v[(j+2)%3];
return;
}
}
bool findEdgeEasy(Vector<Face> & faces, S32 & fromA, S32 & toB)
{
toB = -1; // initialize to illegal value...if remains -1 on exit, then we shouldn't need it later
// now find toB...
S32 vertA1 = -1, vertB1 = -1;
S32 faceNum = faces.size()-1;
if (faceNum<1)
return false;
if (faces[faceNum].v[0]==fromA)
{
vertA1 = faces[faceNum].v[1];
vertB1 = faces[faceNum].v[2];
}
else if (faces[faceNum].v[1]==fromA)
{
vertA1 = faces[faceNum].v[0];
vertB1 = faces[faceNum].v[2];
}
else if (faces[faceNum].v[2]==fromA)
{
vertA1 = faces[faceNum].v[0];
vertB1 = faces[faceNum].v[1];
}
faceNum--;
S32 vertA2 = -1, vertB2 = -1;
if (faces[faceNum].v[0]==fromA)
{
vertA2 = faces[faceNum].v[1];
vertB2 = faces[faceNum].v[2];
}
else if (faces[faceNum].v[1]==fromA)
{
vertA2 = faces[faceNum].v[0];
vertB2 = faces[faceNum].v[2];
}
else if (faces[faceNum].v[2]==fromA)
{
vertA2 = faces[faceNum].v[0];
vertB2 = faces[faceNum].v[1];
}
if (vertA1!=vertA2 && vertA1!=vertB2 && vertB1!=vertA2 && vertB1!=vertB2)
// doh! no shared edge
return false;
else if ( (vertA1==vertA2 && vertB1==vertB2) || (vertA1==vertB2 && vertB1==vertA2) )
// doh! same triangle, different facing
return false;
if (vertA1==vertA2 || vertA1==vertB2)
toB = vertA1;
else if (vertB1==vertA2 || vertB1==vertB2)
toB = vertB1;
if (toB>=0)
return true;
return false;
}
//--------------------------------------------
// get multi-res mimic
void ShapeMimic::addMultiRes(INode * pNode, INode * multiResNode)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (getMultiRes(pNode))
return;
S32 i;
multiResList.increment();
multiResList.last() = new MultiResMimic(pNode,multiResNode);
multiResList.last()->totalVerts = 0;
// look up total verts (we assume we start at max...allows artist to dial down to start with)
Animatable * obj = (Animatable*)multiResList.last()->multiResNode->GetObjectRef();
IParamBlock * paramBlock = NULL;
Interval range = multiResList.last()->multiResNode->GetTimeRange(TIMERANGE_ALL|TIMERANGE_CHILDNODES|TIMERANGE_CHILDANIMS);
for (i=0; i<obj->NumSubs(); i++)
{
if (!dStrcmp(obj->SubAnimName(i),"MultiRes"))
{
paramBlock = (IParamBlock*)obj->SubAnim(i)->SubAnim(0);
paramBlock->GetValue(0,DEFAULT_TIME,multiResList.last()->totalVerts,range);
break;
}
}
// since first time we've since this multi res object, record off vertex merge information
if (paramBlock)
recordMergeOrder(paramBlock,multiResList.last());
}
void ShapeMimic::recordMergeOrder(IParamBlock * paramBlock, MultiResMimic * multiResMimic)
{
struct MeshData
{
Vector<Face> faces;
Vector<Point3F> verts;
} currentMesh, loMesh;
// we will assume that mesh starts with this many verts...
S32 startNumVerts = multiResMimic->totalVerts;
paramBlock->SetValue(0,DEFAULT_TIME,startNumVerts);
grabMeshData(multiResMimic->multiResNode,currentMesh.faces,currentMesh.verts);
S32 numVerts = startNumVerts;
if (numVerts!=currentMesh.verts.size())
setExportError(avar("Multires vertex mis-match on mesh \"%s\" -- hit generate on modifier",multiResMimic->multiResNode->GetName()));
while (numVerts)
{
bool hardWay = false;
S32 fromA, toB;
fromA = numVerts-1;
findEdgeEasy(currentMesh.faces,fromA,toB);
if (toB<0)
{
paramBlock->SetValue(0,DEFAULT_TIME,numVerts-1);
grabMeshData(multiResMimic->multiResNode,loMesh.faces,loMesh.verts);
findEdgeHard(currentMesh.faces,loMesh.faces,currentMesh.verts,fromA,toB);
hardWay = true;
}
if (toB<0)
// merge to ourself...not sure that this can happen, but if it does, it'll be handled ok
toB = fromA;
multiResMimic->mergeFrom.push_back(currentMesh.verts[fromA]);
multiResMimic->mergeTo.push_back(currentMesh.verts[toB]);
// now collapse the edge from hiMesh
collapseEdge(currentMesh.faces,fromA,toB);
// The following check can't be done because we sometimes merge in a different
// order than the plugin does. This is a speed consideration. We assume that
// if the Nth vert is being removed and the top two faces are N i j & N i k
// that N is merged with i. This isn't always the case, but usually is.
/*
if (hardWay)
{
// let's take this opportunity to make sure that the
// collapsed mesh is the same we would get using
// the modifier (i.e., loMesh and hiMesh should have
// same topology on the verts...we don't care about
// tverts since our edge collapse ignores them for now).
if (loMesh.faces.size()!=currentMesh.faces.size())
{
setExportError("Assertion failed during edge collapse.");
return;
}
for (S32 j=0; j<loMesh.faces.size(); j++)
{
if (loMesh.faces[j].v[0]!=currentMesh.faces[j].v[0] ||
loMesh.faces[j].v[1]!=currentMesh.faces[j].v[1] ||
loMesh.faces[j].v[2]!=currentMesh.faces[j].v[2])
{
setExportError("Assertion failed during edge collapse.");
return;
}
}
}
*/
numVerts--;
}
// restore to original state...
paramBlock->SetValue(0,DEFAULT_TIME,startNumVerts);
}
void ShapeMimic::collapseEdge(Vector<Face> & faces, S32 fromA, S32 toB)
{
for (S32 i=0; i<faces.size(); i++)
{
Face & face = faces[i];
for (S32 v=0; v<3; v++)
if (face.v[v]==fromA)
face.v[v]=toB;
if (face.v[0]==face.v[1] || face.v[1]==face.v[2] || face.v[2]==face.v[0])
{
faces.erase(i);
i--;
}
}
}
void doT2AutoDetailPercents(INode * pNode, Vector<F32> & percents, F32 minPercent)
{
Animatable * obj = (Animatable*)pNode->GetObjectRef();
IParamBlock * paramBlock = NULL;
Interval range = pNode->GetTimeRange(TIMERANGE_ALL|TIMERANGE_CHILDNODES|TIMERANGE_CHILDANIMS);
S32 i;
S32 totalVerts = 0;
for (i=0; i<obj->NumSubs(); i++)
{
if (!dStrcmp(obj->SubAnimName(i),"MultiRes"))
{
paramBlock = (IParamBlock*)obj->SubAnim(i)->SubAnim(0);
paramBlock->GetValue(0,DEFAULT_TIME,totalVerts,range);
break;
}
}
F32 totalVertsF = totalVerts;
S32 numNeeded = percents.size();
for (i=0; i<numNeeded; i++)
{
if (i==0)
percents[i]=1.0f;
else
{
F32 k = ((F32)i)/(F32)(numNeeded-1);
/*
F32 k2 = 1.0f - (1.0f-k)*(1.0f-k);
// use k to interpolate between k and k2
k = (1.0f-k) * k + k * k2;
*/
percents[i] = ((1.0f-k)*totalVertsF + k*3.0f)/totalVertsF;
if (percents[i]<minPercent)
percents[i]=minPercent;
}
}
}
//--------------------------------------------
// get multi-res info from a node...
void ShapeMimic::getMultiResData(INode * pNode, Vector<S32> & multiResSize, Vector<F32> & multiResPercent)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
TSTR buffer;
if (!pNode->GetUserPropString("MULTIRES::SIZES",buffer))
return;
if (t2AutoDetail>1)
{
multiResSize = t2AutoDetailSizes;
multiResPercent.setSize(multiResSize.size());
F32 minPercent;
if (!pNode->GetUserPropFloat("MULTIRES::MIN",minPercent))
minPercent=0.0f;
doT2AutoDetailPercents(pNode,multiResPercent,minPercent);
S32 stopNumber;
if (pNode->GetUserPropInt("MULTIRES::STOP",stopNumber))
{
if (stopNumber<0)
stopNumber=0;
else if (stopNumber>multiResSize.size())
stopNumber=multiResSize.size();
multiResSize.setSize(stopNumber);
multiResPercent.setSize(stopNumber);
}
}
else
{
// extract sizes from buffer
char * pos = buffer;
while (*pos && (*pos=='=' || *pos==' '))
pos++;
while (pos && *pos)
{
while (*pos && *pos==' ')
pos++;
char * nextPos = dStrchr(pos,',');
if (!nextPos)
nextPos = pos + dStrlen(pos);
char entry[128];
S32 i;
for (i=0; pos+i<nextPos; i++)
entry[i]=pos[i];
entry[i]='\0';
multiResSize.push_back(dAtoi(entry));
pos = *nextPos ? nextPos+1 : nextPos;
while (*pos && *pos==' ')
pos++;
}
if (pNode->GetUserPropString("MULTIRES::DETAILS",buffer))
{
char * pos = buffer;
while (*pos && (*pos=='=' || *pos==' '))
pos++;
while (pos && *pos)
{
while (*pos && *pos==' ')
pos++;
char * nextPos = dStrchr(pos,',');
if (!nextPos)
nextPos = pos + dStrlen(pos);
char entry[128];
S32 i;
for (i=0; pos+i<nextPos; i++)
entry[i]=pos[i];
entry[i]='\0';
multiResPercent.push_back(dAtof(entry));
pos = *nextPos ? nextPos+1 : nextPos;
while (*pos && *pos==' ')
pos++;
}
}
}
if (multiResSize.size() && !multiResPercent.size())
{
F32 p = 1.0f;
F32 step = 1.0f/(F32)multiResSize.size();
for (S32 i=0; i<multiResSize.size(); i++, p -= step)
multiResPercent.push_back(p);
}
// make sure percent's are in the right order...sort if they aren't
for (S32 i=0; i<multiResPercent.size(); i++)
{
for (S32 j=i+1; j<multiResPercent.size(); j++)
{
if (multiResPercent[i]<multiResPercent[j])
{
F32 tmp = multiResPercent[i];
multiResPercent[i]=multiResPercent[j];
multiResPercent[j]=tmp;
}
}
}
if (multiResSize.size() != multiResPercent.size())
{
setExportError(avar("Multi-res size list has more or less entries than details list on object \"%s\"",pNode->GetName()));
return;
}
}
//--------------------------------------------
// add a mesh -- detect MultiRes
// if multi res, then add one mesh per detail
ObjectMimic * ShapeMimic::addObject(INode * pNode, Vector<S32> * pValidDetails)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
ObjectMimic * om;
// detect MultiRes...
Vector<S32> multiResSize;
Vector<F32> multiResPercent;
getMultiResData(pNode,multiResSize,multiResPercent);
if (multiResSize.size())
{
addMultiRes(pNode,pNode);
for (S32 i=0; i<multiResSize.size(); i++)
// om will be the same for each object
om = addObject(pNode,pValidDetails,true,multiResSize[i],multiResPercent[i]);
}
else
om = addObject(pNode,pValidDetails,false);
// does this object have any decals?
for (S32 i=0; i<pNode->NumberOfChildren(); i++)
if (SceneEnumProc::isDecal(pNode->GetChildNode(i)))
addDecalObject(pNode->GetChildNode(i),om);
return om;
}
//--------------------------------------------
// add a mesh -- add MultiRes info
ObjectMimic * ShapeMimic::addObject(INode * pNode, Vector<S32> * pValidDetails, bool multiRes, S32 multiResSize, F32 multiResPercent)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
ObjectMimic * om;
S32 size;
S32 detailPos;
const char * name = pNode->GetName();
SceneEnumProc::tweakName(&name); // any re-mapping should be done here
// separate object name from detail size for current mesh
char * objectName = chopTrailingNumber(name,size);
// artist can set detail level in the user properties if they want...
S32 tmp;
if (multiResSize>=0)
size = multiResSize;
else if (pNode->GetUserPropInt("Detail",tmp))
size = tmp;
om = getObject(pNode,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->pValidDetails && pValidDetails && pValidDetails!=om->pValidDetails)
{
setExportError(avar("Mesh \"%s\" occurs in two different places on the shape.",om->name));
return NULL;
}
if (pValidDetails)
{
// set valid detail levels...
om->pValidDetails = pValidDetails;
om->inTreeNode = pNode;
// we now know what subtree we belong in -- unless error
if (om->subtreeNum>=0 && om->subtreeNum != subtrees.size()-1)
{
setExportError(avar("Mesh \"%s\" occurs in two different subtrees on the shape.",om->name));
return NULL;
}
om->subtreeNum = subtrees.size() - 1;
computeObjectOffset(pNode,om->objectOffset);
// print out object offsets?
if (dumpMask & PDObjectOffsets)
{
AffineParts parts;
decomp_affine(om->objectOffset,&parts);
printDump(PDObjectOffsets, "Object offset transform:\r\n");
printDump(PDObjectOffsets,avar(" scale: x=%3.5f, y=%3.5f, z=%3.5f\r\n",parts.k.x,parts.k.y,parts.k.z));
printDump(PDObjectOffsets,avar(" stretch rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",parts.u.x,parts.u.y,parts.u.z,parts.u.w));
printDump(PDObjectOffsets,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",parts.t.x,parts.t.y,parts.t.z));
printDump(PDObjectOffsets,avar(" actual rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",parts.q.x,parts.q.y,parts.q.z,parts.q.w));
if (parts.f<0)
printDump(PDObjectOffsets, " ---determinant negative---\r\n");
}
}
// 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 (pValidDetails)
{
om->maxParent = pNode;
om->maxTSParent = pNode; // this may change later...
}
return om;
}
//--------------------------------------------
// add decal object
void ShapeMimic::addDecalObject(INode * pNode, ObjectMimic * om)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
decalObjectList.increment();
decalObjectList.last() = new DecalObjectMimic;
DecalObjectMimic * dom = decalObjectList.last();
dom->numDetails = 0;
dom->targetObject = om;
dom->decalNode = pNode;
};
//--------------------------------------------
// add bone object
ObjectMimic * ShapeMimic::addBoneObject(INode * pNode, S32 subtreeNum)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
S32 i;
const char * name = pNode->GetName();
S32 len = dStrlen(name)+20;
char * boneName = new char[len];
dSprintf(boneName,len,"Bone::%s:",name);
S32 detailPos;
ObjectMimic * om = getObject(pNode,boneName,0,&detailPos,-1,false,true,false);
return om;
}
//--------------------------------------------
// add skin object -- different than above
// version of this method. Above, we add
// an object to make sure bone doesn't get
// deleted. Here we want to add an object
// that will actually go into shape and allow
// us to sort skins with other objects
MeshMimic * ShapeMimic::addSkinObject(SkinMimic * skinMimic)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
S32 i, size;
// first, separate object name from detail size for current mesh
const char * name = skinMimic->skinNode->GetName();
char * objectName = chopTrailingNumber(name,size);
S32 detailPos;
ObjectMimic * om = getObject(skinMimic->skinNode,objectName,skinMimic->detailSize,&detailPos,-1,true,false,true);
if (isError() || 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->pValidDetails = &subtrees[om->subtreeNum]->validDetails;
om->maxParent = om->maxTSParent = NULL;
return om->details[detailPos].mesh;
}
//--------------------------------------------
// add skin...detect multi res
void ShapeMimic::addSkin(INode * pNode)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// detect MultiRes...
Vector<S32> multiResSize;
Vector<F32> multiResPercent;
const char * nodeName = pNode->GetName();
for (S32 i=0; i<pNode->NumberOfChildren(); i++)
{
const char * childName = pNode->GetChildNode(i)->GetName();
if (dStrnicmp(childName,"MultiRes::",dStrlen("MultiRes::")))
continue;
if (dStricmp(childName+dStrlen("MultiRes::"),nodeName))
continue;
getMultiResData(pNode->GetChildNode(i),multiResSize,multiResPercent);
if (multiResSize.size())
{
addMultiRes(pNode,pNode->GetChildNode(i));
break;
}
}
// add skin helper modifier...
Object * obj = pNode->GetObjectRef();
IDerivedObject * dobj = (IDerivedObject*)CreateWSDerivedObject(pNode->GetObjectRef());
SkinHelper * skinHelper = (SkinHelper*)CreateInstance(GetSkinHelperDesc()->SuperClassID(),GetSkinHelperDesc()->ClassID());
dobj->AddModifier(skinHelper);
pNode->SetObjectRef(dobj);
if (multiResSize.size())
{
for (S32 i=0; i<multiResSize.size(); i++)
addSkin(pNode,multiResSize[i],multiResPercent[i]);
}
else
addSkin(pNode,-1,-1);
// done with helper...remove it now...
dobj->DeleteModifier(); // this'll be our skin helper
// following copied from AVCUtil.cpp...is needed to get rid of bar in modifier list
if (dobj->NumModifiers() == 0 && !dobj->TestAFlag(A_DERIVEDOBJ_DONTDELETE))
{
obj = dobj->GetObjRef();
obj->TransferReferences(dobj);
dobj->SetAFlag(A_LOCK_TARGET);
dobj->NotifyDependents(FOREVER,0,REFMSG_SUBANIM_STRUCTURE_CHANGED);
obj->NotifyDependents(FOREVER,0,REFMSG_SUBANIM_STRUCTURE_CHANGED);
dobj->ClearAFlag(A_LOCK_TARGET);
dobj->MaybeAutoDelete();
}
}
//--------------------------------------------
// add skin...will create 1 object per bone...handle multi res
void ShapeMimic::addSkin(INode * pNode, S32 multiResSize, F32 multiResPercent)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
S32 i,j,k;
skins.push_back(new SkinMimic);
SkinMimic * skinMimic = skins.last();
skinMimic->skinNode = pNode;
skinMimic->multiResPercent = multiResPercent;
if (multiResSize<0)
dFree(chopTrailingNumber(pNode->GetName(),skinMimic->detailSize)); // want size, not name
else
skinMimic->detailSize = multiResSize;
ISkin * skin;
ISkinContextData * skinData;
findSkinData(pNode,&skin,&skinData);
if (!skin || !skinData)
{
setExportError("Assertion failed -- skin modifier was here a moment ago :(");
return;
}
// get bones
S32 numBones = skin->GetNumBones();
skinMimic->bones.setSize(numBones);
for (i=0; i<numBones; i++)
{
skinMimic->bones[i] = skin->GetBone(i);
printDump(PDPass2,avar("Adding skin object from skin \"%s\" to bone \"%s\" (%i).\r\n",pNode->GetName(),skinMimic->bones[i]->GetName(),i));
}
// if no bones...don't add anything
if (skinMimic->bones.empty())
{
delete skins.last();
skins.decrement();
return;
}
bool delTri;
TriObject * tri = NULL;
// get skin mesh
tri = getTriObject(pNode,DEFAULT_TIME,-1,delTri);
// get vertex weights from alternate tv channels
S32 numPoints = tri->mesh.getNumVerts();
if (tri->mesh.getNumMaps()<2+((1+numBones)>>1))
{
setExportError("Assertion failed on skin object");
return;
}
skinMimic->weights.setSize(numBones);
for (i=0; i<skinMimic->weights.size(); i++)
{
skinMimic->weights[i] = new SkinMimic::WeightList;
skinMimic->weights[i]->setSize(numPoints);
for (j=0; j<numPoints; j++)
(*skinMimic->weights[i])[j]=0.0f;
}
for (j=0; j<numBones; j++)
{
printDump(-1,avar("Adding weights for bone %i (\"%s\")\r\n",j,skinMimic->bones[j]->GetName()));
Vector<bool> gotWeight;
gotWeight.setSize(tri->mesh.numVerts);
for (i=0; i<gotWeight.size(); i++)
gotWeight[i]=false;
for (i=0; i<tri->mesh.numFaces; i++)
{
S32 ch = 2+(j>>1);
Face & face = tri->mesh.faces[i];
TVFace & tvFace = tri->mesh.mapFaces(ch)[i];
for (S32 count=0; count<3; count++)
{
S32 idx = face.v[count];
if (!gotWeight[idx])
{
UVVert tv = tri->mesh.mapVerts(ch)[tvFace.t[count]];
F32 w = (j&1) ? tv.y : tv.x;
(*skinMimic->weights[j])[idx] = w;
if (w>0.01f)
printDump(-1,avar(" Vertex %i, weight %f\r\n",idx,w));
gotWeight[idx]=true;
}
}
}
}
if (delTri)
delete tri;
// for some reason, skin object likes to duplicate bones...delete dups here
for (i=0; i<skinMimic->bones.size(); i++)
{
for (j=i+1; j<skinMimic->bones.size(); j++)
{
if (skinMimic->bones[i]==skinMimic->bones[j])
{
// delete weight data for this bone
printDump(PDPass2,avar("Deleting duplicate skin object \"%s\" -- stupid max.\r\n",skinMimic->bones[j]->GetName()));
// transfer weights...to first instance
for (k=0; k<skinMimic->weights[i]->size(); k++)
(*skinMimic->weights[i])[k] += (*skinMimic->weights[j])[k];
delete skinMimic->weights[j];
skinMimic->weights.erase(j);
skinMimic->bones.erase(j);
j--;
}
}
}
// limit number of bones per vertex and apply weight threshhold
for (i=0;i<skinMimic->weights[0]->size();i++)
{
F32 ** hi = new F32 * [weightsPerVertex];
for (k=0; k<weightsPerVertex; k++)
hi[k] = NULL;
for (j=0; j<skinMimic->bones.size(); j++)
{
F32 & w = (*skinMimic->weights[j])[i];
for (k=0; k<weightsPerVertex && (!hi[k] || *hi[k]<w); k++);
k--;
if (k<0 || w<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<weightsPerVertex;k++)
if (hi[k])
sum += *hi[k];
if (sum>weightThreshhold)
for (k=0;k<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<weightThreshhold)
{
// delete weight data for this bone
printDump(PDPass2,avar("Deleting skin object \"%s\" with no weight.\r\n",skinMimic->bones[i]->GetName()));
delete skinMimic->weights[i];
skinMimic->weights.erase(i);
skinMimic->bones.erase(i);
i--;
}
}
MeshMimic * meshMimic = addSkinObject(skinMimic); // goes into object list without node...
// get the mesh from the skin node or the multi-res node
if (multiResPercent<0.0f)
tri = getTriObject(pNode,DEFAULT_TIME,-1,delTri);
else
{
MultiResMimic * mrm = getMultiRes(pNode);
remapWeights(skinMimic->weights,mrm->pNode,mrm->multiResNode);
S32 multiResVerts = getMultiResVerts(pNode,multiResPercent);
tri = getTriObject(mrm->multiResNode,DEFAULT_TIME,multiResVerts,delTri);
}
Mesh & maxMesh = tri->mesh;
// get offset matrix
Matrix3 toBounds = boundsNode->GetNodeTM(DEFAULT_TIME);
zapScale(toBounds);
toBounds = Inverse(toBounds);
Matrix3 fromObj = multiResPercent<0.0f ?
pNode->GetObjTMAfterWSM(DEFAULT_TIME) :
getMultiRes(pNode)->multiResNode->GetObjTMAfterWSM(DEFAULT_TIME);
fromObj *= toBounds;
meshMimic->objectOffset = fromObj;
// generate the faces of the mesh -- will be transfered to objects on subtrees later (as ts objects are generated)
printDump(PDPass2,avar("Generating faces for skin \"%s\".\r\n",pNode->GetName()));
generateFaces(pNode,
maxMesh,
meshMimic->objectOffset,
skinMimic->faces,
skinMimic->normals,
skinMimic->verts,
skinMimic->tverts,
skinMimic->indices,
skinMimic->smoothingGroups,
&skinMimic->vertId);
meshMimic->numVerts = maxMesh.getNumVerts();
// iterate through the subtrees looking for bones...when we find them, add a skin object
Subtree * pSubtree;
for (i=0; i<subtrees.size(); i++)
{
pSubtree = subtrees[i];
NodeMimic * mimicNode = pSubtree->start.child;
while (mimicNode)
{
if (mimicNode==&pSubtree->start)
{
// this should just never happen...
setExportError("Assertion failed: Illegal condition.");
return;
}
// a bone?
for (j=0; j<skinMimic->bones.size(); j++)
{
if (skinMimic->bones[j] == mimicNode->maxNode)
{
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);
}
}
// What to do about these???
//skin->GetBoneInitTM(skin->GetBone(i), boneInitTM))
//skin->GetSkinInitTM(pNode, skinInitTM)
}
void ShapeMimic::copyWeightsToVerts(SkinMimic * skinMimic)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// on input, weights are stored in a bone x vertId matrix
// on input, 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 max while the latter corresponds
// to the order in our vert list
// if we have weights, copy them and remap as we add verts
S32 i,j;
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];
}
//--------------------------------------------
// Map weights from skin vertices to multires vertices
void ShapeMimic::remapWeights(Vector<SkinMimic::WeightList*> & weights, INode * skinNode, INode * multiResNode)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
Vector<SkinMimic::WeightList*> newWeights;
S32 i,j;
bool delTriSkin, delTriMR;
TriObject * triMR, * triSkin = getTriObject(skinNode,DEFAULT_TIME,-1,delTriSkin);
S32 multiResVerts = getMultiResVerts(skinNode,100.0f);
triMR = getTriObject(multiResNode,DEFAULT_TIME,multiResVerts,delTriMR);
newWeights.setSize(weights.size());
for (i=0; i<newWeights.size(); i++)
newWeights[i] = new SkinMimic::WeightList;
Matrix3 skinT = skinNode->GetObjTMAfterWSM(DEFAULT_TIME);
Matrix3 multiT = multiResNode->GetObjTMAfterWSM(DEFAULT_TIME);
for (i=0; i<triMR->mesh.getNumVerts(); i++)
{
// find vert in triSkin that each vert in triMR is closest to
F32 closest = 10E10f;
S32 idx = -1;
for (j=0; j<triSkin->mesh.getNumVerts(); j++)
{
Point3F skinVert = Point3ToPoint3F(triSkin->mesh.verts[j] * skinT,skinVert);
Point3F multiVert = Point3ToPoint3F(triMR->mesh.verts[i] * multiT,multiVert);
Point3F delta = skinVert - multiVert;
F32 d = delta.x*delta.x + delta.y*delta.y + delta.z*delta.z;
if (d<closest)
{
closest = d;
idx = j;
}
}
if (idx>=0)
{
for (j=0; j<newWeights.size(); j++)
newWeights[j]->push_back((*weights[j])[idx]);
}
}
for (i=0; i<weights.size(); i++)
delete weights[i];
weights = newWeights; // copies pointers...which are still valid on exit
if (delTriSkin)
delete triSkin;
if (delTriMR)
delete triMR;
}
//--------------------------------------------
// add a sequence
void ShapeMimic::addSequence(INode * pNode)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// just push it
sequences.push_back(pNode);
}
//--------------------------------------------
// add a material
S32 ShapeMimic::addMaterial(INode * pNode, S32 materialIndex, CropInfo * cropInfo)
{
// start out with no auxilary maps...reflection map will point back at us if none found
S32 reflectionMap = -1;
S32 bumpMap = -1;
S32 detailMap = -1;
F32 detailScale = 1.0f;
F32 emapAmount = 1.0f;
U32 flags = 0;
// until proven otherwise...
cropInfo->hasTVerts = false;
cropInfo->crop = false;
cropInfo->place = false;
cropInfo->twoSided = false;
// do some hocus pocus to get the material....
Mtl * mtl = pNode->GetMtl();
if( !mtl )
return TSDrawPrimitive::NoMaterial;
if( mtl->ClassID() == Class_ID(MULTI_CLASS_ID,0) )
{
MultiMtl * multiMtl = (MultiMtl*)mtl;
if (multiMtl->NumSubMtls()==0)
return TSDrawPrimitive::NoMaterial;
materialIndex %= multiMtl->NumSubMtls();
mtl = multiMtl->GetSubMtl( materialIndex );
}
if( mtl->ClassID() != Class_ID(DMTL_CLASS_ID,0) )
{
setExportError(avar("Unexpected material type on node \"%s\".",pNode->GetName()));
return TSDrawPrimitive::NoMaterial;
}
StdMat * stdMat = (StdMat*)mtl;
// we now have a standard material...this guy has a number of maps
// the diffuse map corresponds to the texture
// the reflection map is used for environment mapping (normally this will be in the alpha of
// the texture, but under some circumstances -- translucency -- you want a separate map for it)
// the material also has a bump map and a detail map (we look for this in the ambient map...
// ambient because detail maps add ambiance... :)
// are we two sided?
cropInfo->twoSided = enableTwoSidedMaterials && stdMat->GetTwoSided();
// get diffuse map...
if (stdMat->GetSubTexmap(ID_DI) == NULL || !stdMat->MapEnabled(ID_DI))
return TSDrawPrimitive::NoMaterial;
if (stdMat->GetSubTexmap(ID_DI)->ClassID() != Class_ID(BMTEX_CLASS_ID,0))
{
setExportError(avar("Diffuse channel on node \"%s\" has a non-bitmap texture map.",pNode->GetName()));
return TSDrawPrimitive::NoMaterial;
}
BitmapTex * diffuse = (BitmapTex*)stdMat->GetSubTexmap(ID_DI);
cropInfo->hasTVerts = true;
const char * materialName = getBaseTextureName(diffuse->GetMapName());
// get reflection map...
BitmapTex * reflection = NULL;
if (stdMat->GetSubTexmap(ID_RL) && stdMat->MapEnabled(ID_RL))
{
if (stdMat->GetSubTexmap(ID_RL)->ClassID() != Class_ID(BMTEX_CLASS_ID,0))
{
setExportError(avar("Reflection channel on node \"%s\" has a non-bitmap texture map.",pNode->GetName()));
return TSDrawPrimitive::NoMaterial;
}
reflection = (BitmapTex*)stdMat->GetSubTexmap(ID_RL);
// reflection map will be alpha channel of diffuse map...
// make sure they have the same base name...
const char * materialName2 = getBaseTextureName(reflection->GetMapName());
const char * end1 = dStrrchr(materialName ,'.');
const char * end2 = dStrrchr(materialName2,'.');
const char * s1 = materialName;
const char * s2 = materialName2;
while (s1!=end1 && s2!=end2 && *s1==*s2)
{
s1++; s2++;
}
if (s1!=end1 || s2!=end2)
// reflection map and diffuse map different...
reflectionMap = findMaterial(materialName2,reflection->GetMapName(),TSMaterialList::ReflectanceMapOnly,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF);
}
// get bump map...
if (stdMat->MapEnabled(ID_BU) && stdMat->GetSubTexmap(ID_BU))
{
if (stdMat->GetSubTexmap(ID_BU)->ClassID() != Class_ID(BMTEX_CLASS_ID,0))
{
setExportError(avar("Bump map channel on node \"%s\" has a non-bitmap texture map.",pNode->GetName()));
return TSDrawPrimitive::NoMaterial;
}
BitmapTex * bump = (BitmapTex*)stdMat->GetSubTexmap(ID_BU);
const char * bumpName = getBaseTextureName(bump->GetMapName());
bumpMap = findMaterial(bumpName,bump->GetMapName(),TSMaterialList::BumpMapOnly,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF);
}
// get detail map...
if (stdMat->MapEnabled(ID_AM) && stdMat->GetSubTexmap(ID_AM))
{
if (stdMat->GetSubTexmap(ID_AM)->ClassID() != Class_ID(BMTEX_CLASS_ID,0))
{
setExportError(avar("Detail map channel (ambient channel) on node \"%s\" has a non-bitmap texture map.",pNode->GetName()));
return TSDrawPrimitive::NoMaterial;
}
BitmapTex * detail = (BitmapTex*)stdMat->GetSubTexmap(ID_AM);
const char * detailName = getBaseTextureName(detail->GetMapName());
detailMap = findMaterial(detailName,detail->GetMapName(),TSMaterialList::DetailMapOnly,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF);
detailScale = detail->GetUVGen()->GetUScl(0);
if (mFabs(detailScale-detail->GetUVGen()->GetVScl(0)) > 0.01f)
{
setExportError(avar("U-scale must match V-scale on detail texture (found in ambient channel) on texture %s",detailName));
return TSDrawPrimitive::NoMaterial;
}
}
// set up crop info
diffuse->GetUVGen()->GetUVTransform(cropInfo->uvTransform);
cropInfo->uWrap = diffuse->GetUVGen()->GetTextureTiling() & U_WRAP;
cropInfo->vWrap = diffuse->GetUVGen()->GetTextureTiling() & V_WRAP;
IParamBlock2 * pblock = diffuse->GetParamBlock(0);
Interval interval;
S32 apply;
pblock->GetValue(PB_APPLY,DEFAULT_TIME,apply,interval);
if (apply)
{
S32 cropOrPlace;
pblock->GetValue(PB_CROP_PLACE,DEFAULT_TIME,cropOrPlace,interval);
if (cropOrPlace==0)
cropInfo->crop = true;
else
{
cropInfo->place = true;
// don't wrap if texture placed...
cropInfo->uWrap = cropInfo->vWrap = false;
}
pblock->GetValue(PB_CLIPU,DEFAULT_TIME,cropInfo->uOffset,interval);
pblock->GetValue(PB_CLIPV,DEFAULT_TIME,cropInfo->vOffset,interval);
pblock->GetValue(PB_CLIPW,DEFAULT_TIME,cropInfo->uWidth,interval);
pblock->GetValue(PB_CLIPH,DEFAULT_TIME,cropInfo->vHeight,interval);
if (cropInfo->crop)
{
cropInfo->cropLeft = cropInfo->uOffset>0.001f;
cropInfo->cropRight = cropInfo->uOffset+cropInfo->uWidth<0.999f;
cropInfo->cropTop = cropInfo->vOffset>0.001f;
cropInfo->cropBottom = cropInfo->vOffset+cropInfo->vHeight<0.999f;
if (cropInfo->uWrap && (cropInfo->cropLeft != cropInfo->cropRight))
cropInfo->cropLeft = cropInfo->cropRight = true;
if (cropInfo->vWrap && (cropInfo->cropTop != cropInfo->cropBottom))
cropInfo->cropTop = cropInfo->cropBottom = true;
}
}
// set up texture flags
if (stdMat->MapEnabled(ID_OP)) // note: translucent if opacity channel is enabled
{
flags |= TSMaterialList::Translucent;
if (stdMat->GetTransparencyType() == TRANSP_ADDITIVE)
flags |= TSMaterialList::Additive;
else if (stdMat->GetTransparencyType() == TRANSP_SUBTRACTIVE)
flags |= TSMaterialList::Subtractive;
}
if (!stdMat->MapEnabled(ID_RL))
// only environment map if reflectance check box is set (but no material necessary)
flags |= TSMaterialList::NeverEnvMap;
emapAmount = stdMat->GetTexmapAmt(ID_RL,DEFAULT_TIME);
if (cropInfo->uWrap)
flags |= TSMaterialList::S_Wrap;
if (cropInfo->vWrap)
flags |= TSMaterialList::T_Wrap;
if ( stdMat->GetSelfIllum(0) > 0.99f)
flags |= TSMaterialList::SelfIlluminating;
return findMaterial(materialName,diffuse->GetMapName(),flags,reflectionMap,bumpMap,detailMap,detailScale,emapAmount);
}
S32 ShapeMimic::findMaterial(const char * name, const char * fullName, U32 flags, S32 reflectionMap, S32 bumpMap, S32 detailMap, F32 detailScale, F32 emapAmount)
{
// 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 = flags & TSMaterialList::AuxiliaryMap;
if (dStrstr(name,".ifl") || dStrstr(name,".IFL"))
flags |= TSMaterialList::IflMaterial;
if (gMipmapMethod.noMipmap)
flags |= TSMaterialList::NoMipMap;
if (gMipmapMethod.noMipmapTranslucent && flags & TSMaterialList::Translucent)
flags |= TSMaterialList::NoMipMap;
if (flags & TSMaterialList::Translucent && !(flags & TSMaterialList::S_Wrap|TSMaterialList::T_Wrap) && gMipmapMethod.zapBorder)
// material is translucent and doesn't wrap -- we're clearing the borders on the mipmaps of such materials
flags |= TSMaterialList::MipMap_ZeroBorder;
// we don't care about extension (unless we're an ifl
const char * dot = dStrrchr(name,'.');
S32 len = dot ? dot-name : dStrlen(name);
for (S32 i=0; i<materials.size(); i++)
{
// first check name
if (dStrlen(materials[i]) && (dStrnicmp(materials[i],name,len) || dStrlen(materials[i])!=len))
continue;
// good enough for auxiliary
if (auxiliary)
{
if (materialFlags[i] & TSMaterialList::AuxiliaryMap)
// if we're using an auxiliary map for two purpose, let it be known...
materialFlags[i] |= (flags & TSMaterialList::AuxiliaryMap);
return i;
}
// a reflection map of -1 gets mapped to i...
if (reflectionMap!=-1 && materialReflectionMaps[i]!=(U32)reflectionMap)
continue;
// check the rest
if (materialFlags[i]==flags && materialBumpMaps[i]==bumpMap && materialDetailMaps[i]==detailMap &&
mFabs(materialDetailScales[i]-detailScale)<0.01f && mFabs(materialEmapAmounts[i]-emapAmount)<0.01f)
return i;
}
// check to see if material is an ifl
if (dStrstr(name,".ifl") || dStrstr(name,".IFL"))
{
iflList.push_back(new IflMimic);
iflList.last()->fileName = dStrdup(name);
iflList.last()->fullFileName = dStrdup(fullName);
iflList.last()->materialSlot = materials.size();
}
appendMaterialList(name,flags,reflectionMap,bumpMap,detailMap,detailScale,emapAmount);
return materials.size()-1;
}
void ShapeMimic::appendMaterialList(const char * name, U32 flags, S32 reflectionMap, S32 bumpMap, S32 detailMap, F32 detailScale, F32 emapAmount)
{
// always have a reflection map, even if it's ourself
if (reflectionMap==-1 && !(flags&TSMaterialList::AuxiliaryMap))
reflectionMap = materialReflectionMaps.size();
S32 len = dStrlen(name);
const char * dot = dStrrchr(name,'.');
if (dot)
len = dot-name;
// new name...
char * matName = (char*)dMalloc(len+1);
dStrncpy(matName,name,len);
matName[len]='\0';
// new one -- save the texture map material and return new index
materials.push_back(matName);
materialFlags.push_back(flags);
materialReflectionMaps.push_back(reflectionMap);
materialBumpMaps.push_back(bumpMap);
materialDetailMaps.push_back(detailMap);
materialDetailScales.push_back(detailScale);
materialEmapAmounts.push_back(emapAmount);
}
//--------------------------------------------
// adjust texture name -- remove base texture path
const char * ShapeMimic::getBaseTextureName(const char * name)
{
const char * retName;
if (baseTexturePath[0]=='.')
{
retName = dStrrchr(name,'\\');
if (!retName++)
retName = name;
}
else
{
retName = dStrstr(name,baseTexturePath);
if (!retName)
{
retName = name;
setExportError(avar("Material \"%s\" must be in a subdirectory of \"%s\" -- or you can change the base texture path",name,baseTexturePath));
}
else
retName += dStrlen(baseTexturePath);
}
return retName;
}
//--------------------------------------------
// add a name to the shape
S32 ShapeMimic::addName(const char * name, TSShape * pShape)
{
SceneEnumProc::tweakName(&name); // chops off beginning only
S32 ret = pShape->findName(name);
if (ret<0)
{
ret = pShape->names.size();
pShape->names.increment();
char * newName = dStrdup(name);
// if name terminated with CR, get rid of it
while (newName && dStrlen(newName)>0 && newName[dStrlen(newName)-1]==(char)13)
newName[dStrlen(newName)-1] = '\0';
pShape->names.last() = newName;
}
return ret;
}
//--------------------------------------------
// walk through mimic node structure depth first
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;
}
//--------------------------------------------
// clear lists for next time around
void ShapeMimic::clearCollapseTransforms()
{
cutNodes.clear();
cutNodesParents.clear();
}
//--------------------------------------------
// prune shape of unneeded nodes
void ShapeMimic::collapseTransforms()
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDPass3,"\r\nThird pass: Collapsing unneeded nodes...\r\n\r\n");
Subtree * pSubtree;
for (S32 i=0; i<subtrees.size(); i++)
{
pSubtree = subtrees[i];
NodeMimic * mimicNode = pSubtree->start.child;
while (mimicNode)
{
if (mimicNode==&pSubtree->start)
{
// this should just never happen...
setExportError("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))
{
printDump(PDPass3,avar("Removing node \"%s\"\r\n",mimicNode->maxNode->GetName()));
snip(mimicNode);
}
mimicNode = nextNode;
}
}
}
// perform string compare with wildcards (in s2 only) and case insensitivity
bool stringEqual(const char * s1, const char * s2)
{
if (*s1=='\0' && *s2=='\0')
return true;
if (*s2=='*')
{
if (stringEqual(s1,s2+1))
return true;
if (*s1=='\0')
return false;
return stringEqual(s1+1,s2);
}
if (toupper(*s1)==toupper(*s2))
return stringEqual(s1+1,s2+1);
return false;
}
bool ShapeMimic::cut(NodeMimic * mimicNode)
{
const char * name = mimicNode->maxNode->GetName();
S32 i;
// search always export list
for (i=0;i<SceneEnumProc::alwaysExport.size(); i++)
if (stringEqual(name,SceneEnumProc::alwaysExport[i]))
return false;
// search never export list
for (i=0;i<SceneEnumProc::neverExport.size(); i++)
if (stringEqual(name,SceneEnumProc::neverExport[i]))
return true;
// if transform collapse is false, only collapse explicitly named nodes (in neverExport list)
if (!transformCollapse)
return false;
// not in either list -- cut if no object and not dummy
return (mimicNode->objects.empty() && !SceneEnumProc::isDummy(mimicNode->maxNode));
}
bool ShapeMimic::neverAnimateNode(NodeMimic * mimicNode)
{
const char * name = mimicNode->maxNode->GetName();
// search always export list
for (S32 i=0;i<SceneEnumProc::neverAnimate.size(); i++)
if (stringEqual(name,SceneEnumProc::neverAnimate[i]))
return true;
return false;
}
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->maxNode);
cutNodesParents.push_back(nodeMimic->parent->maxNode);
}
// 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.last()->maxTSParent = parent->maxNode; // until notified o.w.
}
// delete me
delete nodeMimic;
}
//--------------------------------------------
// initialize a shape after it's generated
// mostly just let shape do it...but some
// things are done pre-export that aren't
// done other times
void ShapeMimic::initShape(TSShape * pShape)
{
S32 numSubShapes = pShape->subShapeFirstNode.size();
// compute subShapeNumNodes,
pShape->subShapeNumNodes.setSize(numSubShapes);
S32 i,j,prev = pShape->nodes.size();
for (i=numSubShapes-1; i>=0; i--)
{
pShape->subShapeNumNodes[i] = prev - pShape->subShapeFirstNode[i];
prev = pShape->subShapeFirstNode[i];
}
// compute subShapeNumObjects
pShape->subShapeNumObjects.setSize(numSubShapes);
prev = pShape->objects.size();
for (i=numSubShapes-1; i>=0; i--)
{
pShape->subShapeNumObjects[i] = prev - pShape->subShapeFirstObject[i];
prev = pShape->subShapeFirstObject[i];
}
// compute subShapeNumDecals
pShape->subShapeNumDecals.setSize(numSubShapes);
prev = pShape->decals.size();
for (i=numSubShapes-1; i>=0; i--)
{
pShape->subShapeNumDecals[i] = prev - pShape->subShapeFirstDecal[i];
prev = pShape->subShapeFirstDecal[i];
}
// find the smallest renderable detail level
pShape->mSmallestVisibleSize = 0;
pShape->mSmallestVisibleDL = 0;
for (i=0; i<pShape->details.size(); i++)
{
if (pShape->details[i].size>=0.0f)
{
pShape->mSmallestVisibleSize = pShape->details[i].size;
pShape->mSmallestVisibleDL = i;
}
pShape->details[i].maxError = -1.0f;
pShape->details[i].averageError = -1.0f;
}
for (i=0; i<pShape->objects.size(); i++)
{
for (S32 j=0;j<pShape->objects[i].numMeshes;j++)
if (pShape->meshes[pShape->objects[i].startMeshIndex+j])
pShape->meshes[pShape->objects[i].startMeshIndex+j]->computeBounds();
}
// catch decals that don't do anything
for (i=0; i<pShape->decals.size(); i++)
{
for (j=0; j<pShape->decals[i].numMeshes; j++)
{
S32 idx = pShape->decals[i].startMeshIndex+j;
if (pShape->meshes[idx] && !((TSDecalMesh*)pShape->meshes[idx])->texgenS.size())
{
// empty...get rid of it
delete (TSDecalMesh*)pShape->meshes[idx];
pShape->meshes[idx] = NULL;
}
}
}
// copmpute detail errors...
for (i=0; i<pShape->details.size(); i++)
{
TSShape::Detail & detail = pShape->details[i];
Vector<TSMesh*> & meshes = pShape->meshes;
F32 totalMaxDist = 0.0f, curMaxMax = 0.0f;
S32 count = 0;
for (j=0; j<pShape->objects.size(); j++)
{
// get highest mesh and get current mesh
TSMesh * hiMesh = NULL, * curMesh = NULL;
TSShape::Object & obj = pShape->objects[j];
for (S32 k=0; k<obj.numMeshes; k++)
{
if (meshes[obj.startMeshIndex+k])
{
hiMesh = meshes[obj.startMeshIndex+k];
break;
}
}
if (obj.numMeshes > detail.objectDetailNum)
curMesh = meshes[obj.startMeshIndex + detail.objectDetailNum];
if (!curMesh || !hiMesh)
continue;
F32 total;
S32 cnt;
F32 maxDist = findMaxDistance(curMesh,hiMesh,total,cnt);
totalMaxDist += total;
count += cnt;
if (maxDist > curMaxMax)
curMaxMax = maxDist;
}
F32 avgDist = count ? totalMaxDist / (F32)count : 0.0f;
detail.averageError = avgDist;
detail.maxError = curMaxMax;
}
pShape->init();
}
//
void ShapeMimic::destroyShape(TSShape * pShape)
{
if (!pShape)
return;
// delete the meshes ourselves since we newed them and shape
// assumes they were constructed in place...
// before deleting meshes, we have to delete decals and get them out
// of the mesh list...this is a legacy issue from when decals were meshes
S32 i;
for (i=0; i<pShape->decals.size(); i++)
{
for (S32 j=0; j<pShape->decals[i].numMeshes; j++)
{
delete (TSDecalMesh*)pShape->meshes[pShape->decals[i].startMeshIndex+j];
pShape->meshes[pShape->decals[i].startMeshIndex+j]=NULL;
}
}
// everything left over here is a legit mesh
for (i=0; i<pShape->meshes.size(); i++)
{
delete pShape->meshes[i];
pShape->meshes[i] = NULL;
}
delete pShape;
}
//--------------------------------------------
// generate a shape
TSShape * ShapeMimic::generateShape()
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// this may be our second time around, make sure
// certain variables and lists are initialized:
nodes.clear();
// before going any further, do a little fix-up for auto-detail generation
fixupT2AutoDetail();
// no frills construction
TSShape * pShape = new TSShape;
pShape->mExporterVersion = DTS_EXPORTER_CURRENT_VERSION;
// step one: generate bounds
generateBounds(pShape);
// step two: generate detail levels
// sort subTrees according to dl
generateDetails(pShape);
// step three: generate subTrees (tree structure
// without objects connected)
generateSubtrees(pShape);
// step four: generate objects -- hook up to nodes
generateObjects(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// step fourB: generate decals
generateDecals(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// at this point, we have a tsshape with all the details,
// nodes, and objects set up. We have also set up the
// subShapeFirstNode and subShapeFirstObject vectors and
// 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)
generateDefaultStates(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// step six: generate ifl materials
generateIflMaterials(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// step seven: animation
if (enableSequences)
generateSequences(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// step eight: generate material list
generateMaterialList(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// step eight: generate the skins
generateSkins(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// step nine: optimize the meshes (but only if exporting them)
if (SceneEnumProc::exportType == 'w')
optimizeMeshes(pShape);
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return NULL;
// step ten: convert sortObjects
convertSortObjects(pShape);
if (isError()) return NULL;
// what else?
initShape(pShape);
return pShape;
}
//--------------------------------------------
// generate bounds -- called by generateShape
void ShapeMimic::generateBounds(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
bool delTri;
TriObject * tri = getTriObject(boundsNode,DEFAULT_TIME,-1,delTri);
Mesh & maxMesh = tri->mesh;
// get object offset
Matrix3 objectOffset(true);
Point3 pos = boundsNode->GetObjOffsetPos();
objectOffset.PreTranslate(pos);
Quat quat = boundsNode->GetObjOffsetRot();
PreRotateMatrix(objectOffset,quat);
ScaleValue objectScale = boundsNode->GetObjOffsetScale();
ApplyScaling(objectOffset,objectScale);
// find min and max verts
S32 i;
Point3F minVert = Point3ToPoint3F(maxMesh.verts[0] * objectOffset,minVert);
Point3F maxVert = minVert;
for (i=1; i<maxMesh.numVerts; i++)
{
Point3F v = Point3ToPoint3F(maxMesh.verts[i] * objectOffset,v);
minVert.setMin(v);
maxVert.setMax(v);
}
// now set up shape bounds parameters
pShape->center = (minVert + maxVert) * 0.5f;
pShape->bounds.min = minVert;
pShape->bounds.max = maxVert;
// find the smallest radius that includes bounds...
// if artist uses box as bounds we could just use
// boundsBox to figure this out -- but the following
// allows them to use a bounding sphere and sometimes
// get a smaller radius...
F32 maxRadius2 = -1.0f;
F32 radius2;
for (i=0; i<maxMesh.numVerts; i++)
{
Point3F v2 = Point3ToPoint3F(maxMesh.verts[i] * objectOffset,v2);
Point3F radial3 = v2 - pShape->center;
radius2 = mDot(radial3,radial3);
if (radius2 > maxRadius2)
maxRadius2 = radius2;
}
pShape->radius = mSqrt(maxRadius2);
// find the smallest z-axis aligned bounding tube...
maxRadius2 = -1.0f;
for (i=0; i<maxMesh.numVerts; i++)
{
Point2F radial2;
Point3 maxV = maxMesh.verts[i] * objectOffset;
radial2.x = maxV.x - pShape->center.x;
radial2.y = maxV.y - pShape->center.y;
radius2 = radial2.x * radial2.x + radial2.y * radial2.y;
if (radius2 > maxRadius2)
maxRadius2 = radius2;
}
pShape->tubeRadius = mSqrt(maxRadius2);
if (delTri)
delete tri;
}
//--------------------------------------------
// generate details -- called by generateShape
void ShapeMimic::generateDetails(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// if nothing to export...
if (subtrees.empty())
{
setExportError("No details 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++)
{
Subtree * pSubtree = subtrees[i];
for (j=0; j<pSubtree->validDetails.size(); j++)
{
pShape->details.increment();
TSShape::Detail & detail = pShape->details.last();
detail.subShapeNum = i;
detail.objectDetailNum = j;
detail.size = (F32) pSubtree->validDetails[j];
detail.nameIndex = addName(pSubtree->detailNames[j],pShape);
if (!dStrnicmp(pSubtree->detailNames[j],"BB::",4))
{
// this is a billboard detail, this works a little differently...
detail.subShapeNum = -1;
// determine properties...
S32 numEquatorSteps;
S32 numPolarSteps;
F32 polarAngle;
S32 dl;
S32 dim;
S32 includePoles;
INode * pNode = pSubtree->detailNodes[j];
if (!pNode->GetUserPropInt("BB::EQUATOR_STEPS",numEquatorSteps))
numEquatorSteps = 4;
if (!pNode->GetUserPropInt("BB::POLAR_STEPS",numPolarSteps))
numPolarSteps = 0;
if (!pNode->GetUserPropFloat("BB::POLAR_ANGLE",polarAngle))
polarAngle = M_PI/(F32)(((numPolarSteps>>1)<<1)+5);
if (!pNode->GetUserPropInt("BB::DL",dl))
dl = 0;
if (!pNode->GetUserPropInt("BB::DIM",dim))
dim = 64;
if (!pNode->GetUserPropBool("BB::INCLUDE_POLES",includePoles))
includePoles = true;
// 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.objectDetailNum = props;
}
}
}
// sort detail levels based on projection size
sortTSDetails(pShape->details);
// optionally, check to make sure detail trees
// are not crossed -- that is, that all details
// of a particular subtree are consecutive...
// not really problem for the code, but usually
// indicates a shape error
if (!allowCrossedDetails)
{
Vector<S32> checkers;
S32 last = -999;
for (i=0; i<pShape->details.size(); i++)
{
TSShape::Detail & det = pShape->details[i];
if (det.subShapeNum==last)
continue;
// new block -- make sure we haven't seen this yet
for (j=0; j<checkers.size(); j++)
if (checkers[j]==det.subShapeNum)
{
setExportError("Crossed detail levels -- this error can be turned off.");
return;
}
checkers.push_back(det.subShapeNum);
last = det.subShapeNum;
}
}
}
//--------------------------------------------
// generate sub trees -- called by generateShape
void ShapeMimic::generateSubtrees(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// this should already have been caught, but...
if (subtrees.empty())
{
setExportError("No details to export");
return;
}
// generate a set of nodes for each subtree
S32 i;
for (i=0; i<subtrees.size(); i++)
{
Subtree * pSubtree = subtrees[i];
pSubtree->start.number = -1; // translates to NULL...
// this means branches will have no parent
NodeMimic * curNode = pSubtree->start.child;
// mark the beginning of the subshape
pShape->subShapeFirstNode.increment();
pShape->subShapeFirstNode.last() = pShape->nodes.size();
// traverse depth first
while (curNode)
{
curNode->number = pShape->nodes.size();
// add node to tsshape
pShape->nodes.increment();
TSShape::Node & tsnode = pShape->nodes.last();
tsnode.nameIndex = addName(curNode->maxNode->GetName(),pShape);
tsnode.parentIndex = curNode->parent->number; // special case: start->number = -1 -> NULL
// 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);
}
}
}
//--------------------------------------------
// generate objects -- called by generateShape
void ShapeMimic::generateObjects(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) 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 (isError()) return;
// initialize array that indexes first object in subshape
for (i=0; i<subtrees.size(); i++)
{
pShape->subShapeFirstObject.increment();
pShape->subShapeFirstObject.last() = -1;
}
// go through mesh list and add objects as we go
for (i=0; i<objectList.size(); i++)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
ObjectMimic * pObject = objectList[i];
if (!allowUnusedMeshes && !pObject->pValidDetails)
{
setExportError(avar("Mesh \"%s\" not hooked up to shape -- this error can be turned off"));
return;
}
// we may have cut out our actual parent...if so, we need to update object offset
if (pObject->maxParent != pObject->maxTSParent)
{
// if a bone...
if (pObject->isBone)
{
// trying to cut out a bone node ... not allowed
setExportError(avar("Cannot collapse node \"%s\" because it is a bone.",pObject->maxParent->GetName()));
return;
}
// compute the transform from the unscaled maxParent transform to the
// unscaled maxTSParent transform...
// -- maxParent is the max node the object hangs off of in max.
// -- maxTSParent is the max node the object will hang off of when
// exported to the ts shape (or said a longer way, the max node that
// corresponds to the ts node that the object will hang off of).
// -- unscaled transforms are used because that is what get's stored in
// the ts shapes (the original objectOffset also was to the unscaled
// maxParent transform).
Matrix3 m1,m2;
m1 = pObject->maxParent->GetNodeTM(DEFAULT_TIME);
zapScale(m1);
m2 = pObject->maxTSParent->GetNodeTM(DEFAULT_TIME);
zapScale(m2);
m2 = Inverse(m2);
m1 *= m2;
pObject->objectOffset *= m1;
// print out revised object offsets?
if (dumpMask & PDObjectOffsets)
{
AffineParts parts;
decomp_affine(pObject->objectOffset,&parts);
printDump(PDObjectOffsets,avar("Revising object offset transform for object \"%s\":\r\n",pObject->maxParent->GetName()));
printDump(PDObjectOffsets,avar(" scale: x=%3.5f, y=%3.5f, z=%3.5f\r\n",parts.k.x,parts.k.y,parts.k.z));
printDump(PDObjectOffsets,avar(" stretch rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",parts.u.x,parts.u.y,parts.u.z,parts.u.w));
printDump(PDObjectOffsets,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",parts.t.x,parts.t.y,parts.t.z));
printDump(PDObjectOffsets,avar(" actual rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",parts.q.x,parts.q.y,parts.q.z,parts.q.w));
if (parts.f<0)
printDump(PDObjectOffsets, " ---determinant negative---\r\n");
}
}
// if object not in shape, skip it
if (!pObject->pValidDetails || pObject->isBone)
{
// don't need it, don't want it
delete pObject;
objectList.erase(i);
i--;
continue;
}
Vector<S32> * pValidDetails = pObject->pValidDetails;
pShape->objects.increment();
TSShape::Object & tsobj = pShape->objects.last();
tsobj.nameIndex = addName(pObject->name,pShape);
tsobj.numMeshes = pValidDetails->size();
tsobj.startMeshIndex = pShape->meshes.size();
tsobj.nodeIndex = pObject->tsNodeIndex;
// is this the first object for this subshape...
if (pShape->subShapeFirstObject[pObject->subtreeNum] == -1)
pShape->subShapeFirstObject[pObject->subtreeNum] = pShape->objects.size()-1;
S32 k,prevk = -1;
for (j=0; j<pObject->numDetails; j++)
{
for (k=0; k<pValidDetails->size(); k++)
if ((*pValidDetails)[k]==pObject->details[j].size)
break;
if (k==pValidDetails->size() && !allowUnusedMeshes)
{
// ooh, this mesh is an invalid detail size
setExportError(avar("Mesh \"%s\" was found with invalid detail (%i)",pObject->name,pObject->details[j].size));
return;
}
// if this is an invalid detail size get rid of it here
if (k==pValidDetails->size())
{
delete pObject->details[j].mesh;
for (k=j;k+1<pObject->numDetails;k++)
pObject->details[k]=pObject->details[k+1];
pObject->numDetails--;
j--;
continue;
}
// add NULL meshes for all the unused detail levels
for (S32 l=prevk+1; l<k; l++)
{
pShape->meshes.increment();
pShape->meshes.last() = NULL; // no mesh
}
prevk=k;
// fill in some data for later use
pObject->details[j].mesh->meshNum = pShape->meshes.size();
if (pObject->details[j].mesh->skinMimic)
pObject->details[j].mesh->skinMimic->meshNum = pShape->meshes.size();
// now hook up this mesh...
pShape->meshes.increment();
if (pObject->details[j].mesh->sortedObject)
pShape->meshes.last() = new TSSortedMesh;
else if (pObject->details[j].mesh->skinMimic)
pShape->meshes.last() = new TSSkinMesh;
else
pShape->meshes.last() = new TSMesh;
pShape->meshes.last()->numFrames = 0;
pShape->meshes.last()->numMatFrames = 0;
if (pObject->details[j].mesh->billboard)
{
pShape->meshes.last()->setFlags(TSMesh::Billboard);
if (SceneEnumProc::isBillboardZAxis(pObject->details[j].mesh->pNode))
pShape->meshes.last()->setFlags(TSMesh::BillboardZAxis);
}
pObject->details[j].mesh->tsMesh = pShape->meshes.last();
}
// may have rid ourselves of all the meshes above...
// if so, delete this object and continue
if (pObject->numDetails==0)
{
delete pObject;
objectList.erase(i);
pShape->objects.decrement();
i--;
continue;
}
// for any remaining null meshes, decrement object count
for (j=prevk+1; j<pValidDetails->size(); j++)
tsobj.numMeshes--;
}
// some subtrees may not have objects on them...
// make sure subShapeFirstObject array is valid
S32 prev = pShape->objects.size();
for (i=subtrees.size()-1; i>=0; i--)
{
if (pShape->subShapeFirstObject[i] == -1)
pShape->subShapeFirstObject[i] = prev;
prev = pShape->subShapeFirstObject[i];
}
// 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->pValidDetails)
obj->tsObject = NULL;
else
{
obj->tsObjectIndex = tsObjIndex;
obj->tsObject = &pShape->objects[tsObjIndex++];
}
}
}
//--------------------------------------------
// generate decals -- called by generateShape
void ShapeMimic::generateDecals(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// We have already called generateObjects, so all objects
// and meshes in objectList are in the shape.
// put decal objects in order
setDecalObjectPriorities(decalObjectList);
sortDecalObjectList(decalObjectList);
S32 i,j;
// initialize array that indexes first object in subshape
for (i=0; i<subtrees.size(); i++)
{
pShape->subShapeFirstDecal.increment();
pShape->subShapeFirstDecal.last() = -1;
}
// loop through decal objects and generate decalObjects and decalMeshes on shape
for (i=0; i<decalObjectList.size(); i++)
{
DecalObjectMimic * dom = decalObjectList[i];
S32 size;
char * name = chopTrailingNumber(dom->decalNode->GetName()+7,size);
// add decal object to shape
pShape->decals.increment();
TSShape::Decal & decal = pShape->decals.last();
decal.nameIndex = addName(name,pShape);
decal.objectIndex = dom->targetObject->tsObjectIndex;
decal.numMeshes = dom->targetObject->numDetails;
decal.startMeshIndex = pShape->meshes.size();
dom->numDetails = decal.numMeshes;
// is this the first decal for this subshape...
if (pShape->subShapeFirstDecal[dom->subtreeNum] == -1)
pShape->subShapeFirstDecal[dom->subtreeNum] = pShape->decals.size()-1;
for (j=0; j<decal.numMeshes; j++)
{
MeshMimic * meshMimic = dom->targetObject->details[j].mesh;
if (size>0 && dom->targetObject->details[j].size < size)
meshMimic = NULL; // don't want a decal on this detail level
DecalMeshMimic * dmm = meshMimic ? new DecalMeshMimic(meshMimic) : NULL;
dom->details[j].decalMesh = dmm;
if (!dmm)
{
pShape->meshes.push_back(NULL);
continue;
}
// add decalMesh to shape
pShape->meshes.push_back((TSMesh*)new TSDecalMesh);
dmm->tsMesh = (TSDecalMesh*)pShape->meshes.last();
}
dFree(name);
}
// some subtrees may not have decals on them...
// make sure subShapeFirstDecal array is valid
S32 prev = pShape->decals.size();
for (i=subtrees.size()-1; i>=0; i--)
{
if (pShape->subShapeFirstDecal[i] == -1)
pShape->subShapeFirstDecal[i] = prev;
prev = pShape->subShapeFirstDecal[i];
}
// point DecalMimic's to the tsDecalObject (do it now rather than earlier since
// objects could move in memory before now)
S32 tsDecalIndex = 0;
for (i=0; i<decalObjectList.size(); i++)
{
DecalObjectMimic * decal = decalObjectList[i];
decal->tsDecal = &pShape->decals[tsDecalIndex++];
}
}
//--------------------------------------------
// Set time to zero and generate default states
void ShapeMimic::generateDefaultStates(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
U32 i;
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->pValidDetails)
continue;
generateObjectState(obj,DEFAULT_TIME,pShape,true,true);
generateMergeIndices(obj);
}
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];
Quat16 rot;
Point3F trans;
Quat16 srot; // won't matter
Point3F scale; // should be uniform
generateNodeTransform(curNode,DEFAULT_TIME,pShape,false,0,rot,trans,srot,scale);
addNodeRotation(curNode,DEFAULT_TIME,pShape,false,rot,true);
addNodeTranslation(curNode,DEFAULT_TIME,pShape,false,trans,true);
if (mFabs(scale.x*scale.x-1.0f)>0.01f || mFabs(scale.y*scale.y-1.0f)>0.01f || mFabs(scale.z*scale.z-1.0f)>0.01f)
{
setExportError("Assertion failed: scale on default transform");
return;
}
}
if (decalObjectList.size())
{
printDump(PDNodeStates,"\r\nAdd default decal states...\r\n\r\n");
// iterate through the decals
for (i=0; i<decalObjectList.size(); i++)
{
DecalObjectMimic * dom = decalObjectList[i];
generateDecalState(dom,DEFAULT_TIME,pShape,false);
}
}
}
//--------------------------------------------
// generate an object state at specific time
void ShapeMimic::generateObjectState(ObjectMimic * om, S32 time, TSShape * pShape, bool addFrame, bool addMatFrame)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDObjectStates,avar("Adding object state to %i detail level(s) of mesh \"%s\".\r\n",om->numDetails,om->name));
if (addFrame)
printDump(PDObjectStates,"Adding frame.\r\n");
pShape->objectStates.increment();
TSShape::ObjectState & os = pShape->objectStates.last();
os.frameIndex = 0;
os.matFrameIndex = 0;
os.vis = om->maxParent ? getVisValue(om->maxParent,time) : 1.0f; // might be NULL if we're a skin
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)
printDump(PDObjectStateDetails,avar("Object is%svisible.\r\n",os.vis>0.5f ? " " : " not "));
else
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)
{
setExportError(avar("Missing highest detail level on mesh \"%s\".",om->name));
return;
}
// set the frame number for the object state
os.frameIndex = om->details[0].mesh->tsMesh->numFrames - 1;
os.matFrameIndex = om->details[0].mesh->tsMesh->numMatFrames - 1;
if (os.frameIndex<0)
os.frameIndex=0;
if (os.matFrameIndex<0)
os.matFrameIndex=0;
}
// all added, add separator to dump file...
printDump(PDObjectStates|PDObjectStateDetails,"---------------------------------\r\n");
}
//--------------------------------------------
// generate a mesh frame at specific time -- put it into the shape
void ShapeMimic::generateFrame(ObjectMimic * om, S32 time, bool addFrame, bool addMatFrame)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (om->isBone)
{
setExportError("Assertion failed: bone should no longer be on node");
return;
}
if (om->isSkin)
// don't generate frame
return;
S32 i,start,dl;
for (dl=0; dl<om->numDetails; dl++)
{
TSMesh * tsMesh = om->details[dl].mesh->tsMesh;
INode * meshNode = om->details[dl].mesh->pNode;
F32 multiResPercent = om->details[dl].mesh->multiResPercent;
// compute object offset -- if we're the node in the tree then use object offset,
// otherwise, put ourselves in that nodes space (at time 0)
// NOTE: VERY important to do this before getting the mesh...o.w. morph
// animation doesn't work (weird...)
Matrix3 objectOffset;
if (om->inTreeNode==meshNode)
objectOffset = om->objectOffset;
else
{
Matrix3 mat = om->inTreeNode->GetNodeTM(DEFAULT_TIME);
zapScale(mat);
mat = Inverse(mat);
objectOffset = meshNode->GetObjTMAfterWSM(DEFAULT_TIME);
objectOffset *= mat;
// check to see if in tree version's parent was deleted
// if so, adjust objectOffset -- see generateObjects for
// similar code and an explanation
if (om->maxParent != om->maxTSParent)
{
Matrix3 m1,m2;
m1 = om->maxParent->GetNodeTM(DEFAULT_TIME);
zapScale(m1);
m2 = om->maxTSParent->GetNodeTM(DEFAULT_TIME);
zapScale(m2);
m2 = Inverse(m2);
m1 *= m2;
objectOffset *= m1;
}
}
bool delTri;
S32 multiResVerts = getMultiResVerts(meshNode,multiResPercent);
TriObject * tri = getTriObject(meshNode,time,multiResVerts,delTri);
Mesh & maxMesh = tri->mesh;
Vector<TSDrawPrimitive> faces;
Vector<Point3F> normals;
Vector<Point3> verts;
Vector<Point3> tverts;
Vector<U16> indices;
Vector<U32> smooth;
Vector<U32> vertId;
generateFaces(meshNode,maxMesh,objectOffset,faces,normals,verts,tverts,indices,smooth,&vertId);
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 = maxMesh.getNumVerts();
om->details[dl].mesh->smoothingGroups = smooth;
om->details[dl].mesh->vertId = vertId;
om->details[dl].mesh->objectOffset = objectOffset; // keep around for generateObjects for convenience
}
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].start!=faces[i].start ||
tsMesh->primitives[i].matIndex!=faces[i].matIndex)
break;
}
if (i!=faces.size() || error)
{
setExportError(avar("Mesh topology is animated on mesh \"%s\".",meshNode->GetName()));
return;
}
}
if (addFrame)
{
// copy normals...
start = tsMesh->norms.size();
tsMesh->norms.setSize(start+normals.size());
for (i=0; i<normals.size(); i++)
tsMesh->norms[i+start] = normals[i];
// copy verts...
start = tsMesh->verts.size();
tsMesh->verts.setSize(start+verts.size());
for (i=0; i<verts.size(); i++)
Point3ToPoint3F(verts[i],tsMesh->verts[i+start]);
tsMesh->numFrames++;
}
if (addMatFrame)
{
// copy tverts...
start = tsMesh->tverts.size();
tsMesh->tverts.setSize(start+tverts.size());
for (i=0; i<tverts.size(); i++)
{
tsMesh->tverts[i+start].x = tverts[i].x;
tsMesh->tverts[i+start].y = tverts[i].y;
}
tsMesh->numMatFrames++;
}
if (delTri)
delete tri;
}
}
//--------------------------------------------
// generate merge indices (after generating default state
void ShapeMimic::generateMergeIndices(ObjectMimic * om)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (om->isBone)
{
setExportError("Assertion failed: bone should no longer be on node");
return;
}
if (om->isSkin)
// don't generate frame
return;
S32 i,k,dl;
for (dl=0; dl<om->numDetails; dl++)
{
if (!om->details[dl].mesh)
continue;
TSMesh * tsMesh = om->details[dl].mesh->tsMesh;
// how many verts in the next smallest version of us
S32 numChildVerts = 0;
k = 1;
while (dl+k<om->numDetails)
{
if (om->details[dl+k].mesh)
{
numChildVerts = om->details[dl+k].mesh->numVerts;
break;
}
k++;
}
findMergeIndices(om->details[dl].mesh,
om->details[dl].mesh->objectOffset,
tsMesh->primitives,
tsMesh->verts,
tsMesh->norms,
tsMesh->tverts,
tsMesh->indices,
tsMesh->mergeIndices,
om->details[dl].mesh->smoothingGroups,
om->details[dl].mesh->vertId,
numChildVerts);
tsMesh->vertsPerFrame = tsMesh->verts.size();
}
}
//--------------------------------------------
// generate list of mesh faces -- called for each frame of
// mesh animation and checked against prior versions...
void ShapeMimic::generateFaces(INode * meshNode, Mesh & maxMesh, Matrix3 & objectOffset,
Vector<TSDrawPrimitive> & faces, Vector<Point3F> & normals,
Vector<Point3> & verts, Vector<Point3> & tverts, Vector<U16> & indices,
Vector<U32> & smooth, Vector<U32> * vertId)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// are we mirrored?
AffineParts parts;
decomp_affine(objectOffset,&parts);
bool mirror = parts.f < 0.0f;
S32 i,j;
Vector<CropInfo> cropInfoList;
Vector<U32> vertIdStore;
// start lists empty
verts.clear();
tverts.clear();
normals.clear();
indices.clear();
smooth.clear();
if (!vertId)
vertId = &vertIdStore;
vertId->clear();
// start out with faces and crop data allocated
faces.setSize(maxMesh.getNumFaces());
cropInfoList.setSize(maxMesh.getNumFaces());
// if no faces, exit here without error
if (!maxMesh.getNumFaces())
return;
// get faces, points & materials
for (i=0; i<faces.size();i++)
{
Face & maxFace = maxMesh.faces[i];
TVFace & maxTVFace = maxMesh.mapFaces(1)[i]; // 1 == default map
TSDrawPrimitive & tsFace = faces[i];
CropInfo & cropInfo = cropInfoList[i];
// set faces material index
tsFace.matIndex = addMaterial(meshNode,maxFace.getMatID(),&cropInfo);
tsFace.start = indices.size();
tsFace.numElements = 3;
tsFace.matIndex |= TSDrawPrimitive::Triangles|TSDrawPrimitive::Indexed;
// set vertex indices
S32 idx0 = maxFace.v[0];
S32 idx1 = maxFace.v[2]; // switch the order to be CW
S32 idx2 = maxFace.v[1]; // switch the order to be CW
if (mirror)
{
S32 tmp = idx1;
idx1 = idx2;
idx2 = tmp;
}
verts.push_back(maxMesh.verts[idx0] * objectOffset);
verts.push_back(maxMesh.verts[idx1] * objectOffset);
verts.push_back(maxMesh.verts[idx2] * objectOffset);
// add smoothing group for vertices
smooth.push_back(maxFace.smGroup);
smooth.push_back(maxFace.smGroup);
smooth.push_back(maxFace.smGroup);
// add vertex index to uniquely identify verts as max sees them
// this will allow us to never collapse a vert that max doesn't collapse
if (vertId)
{
vertId->push_back(idx0);
vertId->push_back(idx1);
vertId->push_back(idx2);
}
// set texture vertex indices
if (cropInfo.hasTVerts)
{
// if no tverts...get out now
if (!maxMesh.tVerts)
{
setExportError(avar("No texture verts on mesh \"%s\"",meshNode->GetName()));
return;
}
idx0 = maxTVFace.getTVert(0);
idx1 = maxTVFace.getTVert(2); // switch the order to be CW
idx2 = maxTVFace.getTVert(1); // switch the order to be CW
if (mirror)
{
S32 tmp = idx1;
idx1 = idx2;
idx2 = tmp;
}
tverts.push_back(maxMesh.mapVerts(1)[idx0] * cropInfo.uvTransform);
tverts.push_back(maxMesh.mapVerts(1)[idx1] * cropInfo.uvTransform);
tverts.push_back(maxMesh.mapVerts(1)[idx2] * cropInfo.uvTransform);
}
else
{
tverts.push_back(Point3(0,0,0));
tverts.push_back(Point3(0,0,0));
tverts.push_back(Point3(0,0,0));
}
// now add indices...this is easy right now...later we'll mess this up
indices.push_back(indices.size());
indices.push_back(indices.size());
indices.push_back(indices.size());
}
// duplicate 2-sided faces
S32 sz = faces.size();
for (i=0; i<sz; i++)
{
if (!cropInfoList[i].twoSided)
continue;
faces.increment();
TSDrawPrimitive & frontSide = faces[i];
TSDrawPrimitive & backSide = faces.last();
backSide.matIndex = frontSide.matIndex;
backSide.numElements = frontSide.numElements;
backSide.start = indices.size();
// add vert indices (but flip order since we're the other side)
U32 idx0 = indices[frontSide.start+0];
U32 idx1 = indices[frontSide.start+1];
U32 idx2 = indices[frontSide.start+2];
indices.push_back(idx0);
indices.push_back(idx2);
indices.push_back(idx1);
cropInfoList.increment();
cropInfoList.last() = cropInfoList[i];
}
bool anySplitting = false;
printDump(PDObjectStates,avar("%i faces, %i verts, %i tverts before cropping textures and joining verts\r\n",
faces.size(),verts.size(),tverts.size()));
// flip y (v) coord on all texture coords -- you'd think this should be done
// after cropping ... well it shouldn't be, trust me.
for (i=0; i<tverts.size(); i++)
tverts[i].y = 1.0f - tverts[i].y;
// the following vectors will be tweaked as we crop...new texture verts created
// as we account for wrapping of cropped textures will have to be flipped later
// down the line...
Vector<bool> flipX, flipY;
flipX.setSize(tverts.size());
flipY.setSize(tverts.size());
for (i=0;i<tverts.size();i++)
flipX[i] = flipY[i] = false;
S32 startTV = tverts.size();
// if we crop or place a texture, we might have to split up some polys if they go off texture
// loop through and apply cropping/placement to faces, splitting as necessary...
S32 last1 = -1;
S32 last2 = -1;
S32 last3 = -1;
S32 last4 = -1;
for (i=0; i<faces.size();i++)
{
CropInfo & cropInfo = cropInfoList[i];
if (!cropInfo.crop)
continue;
// cropping ...
Point3 & tv0 = tverts[indices[faces[i].start]];
Point3 & tv1 = tverts[indices[faces[i].start+1]];
Point3 & tv2 = tverts[indices[faces[i].start+2]];
// shall we split...
TSDrawPrimitive faceA,faceB;
if (cropInfo.cropLeft && (tv0.x < 0.0f || tv1.x < 0.0f || tv2.x < 0.0f) && (tv0.x > 0.0f || tv1.x > 0.0f || tv2.x > 0.0f))
{
printDump(PDObjectStates,"Splitting face (U coord) due to cropping -- adding 2 faces.\r\n");
if (i==last1)
{
// should never split the same face twice in same spot, precision error?
setExportError("Re-splitting face -- get programmer (1)");
return;
}
anySplitting = true;
splitFaceX(faces[i],faceA,faceB,verts,tverts,indices,flipX,flipY,0.0f,smooth,vertId);
faces.push_back(faceA);
faces.push_back(faceB);
CropInfo ci = cropInfo; // need to copy because cropInfo is on the list...
cropInfoList.push_back(ci);
cropInfoList.push_back(ci);
last1=i;
i--;
continue;
}
if (cropInfo.cropRight && (tv0.x > 1.0f || tv1.x > 1.0f || tv2.x > 1.0f) && (tv0.x < 1.0f || tv1.x < 1.0f || tv2.x < 1.0f))
{
printDump(PDObjectStates,"Splitting face (U coord) due to cropping -- adding 2 faces.\r\n");
if (i==last2)
{
// should never split the same face twice in same spot, precision error?
setExportError("Re-splitting face -- get programmer (2)");
return;
}
anySplitting = true;
splitFaceX(faces[i],faceA,faceB,verts,tverts,indices,flipX,flipY,1.0f,smooth,vertId);
faces.push_back(faceA);
faces.push_back(faceB);
CropInfo ci = cropInfo; // need to copy because cropInfo is on the list...
cropInfoList.push_back(ci);
cropInfoList.push_back(ci);
last2=i;
i--;
continue;
}
if (cropInfo.cropTop && (tv0.y < 0.0f || tv1.y < 0.0f || tv2.y < 0.0f) && (tv0.y > 0.0f || tv1.y > 0.0f || tv2.y > 0.0f))
{
printDump(PDObjectStates,"Splitting face (V coord) due to cropping -- adding 2 faces.\r\n");
if (i==last3)
{
// should never split the same face twice in same spot, precision error?
setExportError("Re-splitting face -- get programmer (3)");
return;
}
anySplitting = true;
splitFaceY(faces[i],faceA,faceB,verts,tverts,indices,flipX,flipY,0.0f,smooth,vertId);
faces.push_back(faceA);
faces.push_back(faceB);
CropInfo ci = cropInfo; // need to copy because cropInfo is on the list...
cropInfoList.push_back(ci);
cropInfoList.push_back(ci);
last3=i;
i--;
continue;
}
if (cropInfo.cropBottom && (tv0.y > 1.0f || tv1.y > 1.0f || tv2.y > 1.0f) && (tv0.y < 1.0f || tv1.y < 1.0f || tv2.y < 1.0f))
{
printDump(PDObjectStates,"Splitting face (V coord) due to cropping -- adding 2 faces.\r\n");
if (i==last4)
{
// should never split the same face twice in same spot, precision error?
setExportError("Re-splitting face -- get programmer (4)");
return;
}
anySplitting = true;
splitFaceY(faces[i],faceA,faceB,verts,tverts,indices,flipX,flipY,1.0f,smooth,vertId);
faces.push_back(faceA);
faces.push_back(faceB);
CropInfo ci = cropInfo; // need to copy because cropInfo is on the list...
cropInfoList.push_back(ci);
cropInfoList.push_back(ci);
last4=i;
i--;
continue;
}
}
if (anySplitting)
{
printDump(PDObjectStates,avar("%i faces, %i verts, %i tverts after cropping but before joining verts\r\n",
faces.size(),verts.size(),tverts.size()));
// flip verts?
for (i=startTV;i<tverts.size();i++)
{
if (flipX[i])
tverts[i].x = 1.0f - tverts[i].x;
if (flipY[i])
tverts[i].y = 1.0f - tverts[i].y;
}
}
// get rid of faces that don't wrap and are off tile
S32 offTileRemovals = 0;
for (i=0; i<faces.size(); i++)
{
CropInfo & cropInfo = cropInfoList[i];
if (!cropInfo.crop || !cullOffTile)
continue;
if (!cropInfo.uWrap)
{
S32 start = faces[i].start;
S32 idx0 = indices[start+0];
S32 idx1 = indices[start+1];
S32 idx2 = indices[start+2];
if ( (tverts[idx0].x <= 0.0f && tverts[idx1].x <= 0.0f && tverts[idx2].x <= 0.0f) ||
(tverts[idx0].x >= 1.0f && tverts[idx1].x >= 1.0f && tverts[idx2].x >= 1.0f))
{
// get rid of this face
for (j=0; j<faces.size(); j++)
if (faces[j].start>start)
faces[j].start -= 3;
faces.erase(i);
cropInfoList.erase(i);
indices.erase(start);
indices.erase(start);
indices.erase(start);
i--;
offTileRemovals++;
continue;
}
}
if (!cropInfo.vWrap)
{
S32 start = faces[i].start;
S32 idx0 = indices[start+0];
S32 idx1 = indices[start+1];
S32 idx2 = indices[start+2];
if ( (tverts[idx0].y <= 0.0f && tverts[idx1].y <= 0.0f && tverts[idx2].y <= 0.0f) ||
(tverts[idx0].y >= 1.0f && tverts[idx1].y >= 1.0f && tverts[idx2].y >= 1.0f))
{
// get rid of this face
for (j=0; j<faces.size(); j++)
if (faces[j].start>start)
faces[j].start -= 3;
faces.erase(i);
cropInfoList.erase(i);
indices.erase(start);
indices.erase(start);
indices.erase(start);
i--;
offTileRemovals++;
continue;
}
}
}
if (offTileRemovals)
{
// we removed some faces that were off-tile...remove unused verts too
bool * vertUsed = new bool[verts.size()];
for (i=0; i<verts.size(); i++)
vertUsed[i]=true;
for (i=0; i<indices.size(); i++)
{
if (indices[i]<0||indices[i]>=verts.size())
{
setExportError("Assertion failed");
return;
}
vertUsed[indices[i]] = true;
}
for (i=verts.size()-1; i>=0; i--)
{
if (!vertUsed[i])
{
for (j=0; j<indices.size(); j++)
if (indices[j]>i)
indices[j]--;
verts.erase(i);
tverts.erase(i);
smooth.erase(i);
if (vertId)
vertId->erase(i);
}
}
delete [] vertUsed;
printDump(PDObjectStates,avar("Removing off-tile faces, left with %i faces and %i verts\r\n",faces.size(),verts.size()));
}
// now do the actual cropping/placing
Vector<bool> done;
done.setSize(tverts.size());
for (i=0;i<done.size();i++)
done[i]=false;
for (i=0;i<faces.size();i++)
{
CropInfo & cropInfo = cropInfoList[i];
if (!cropInfo.crop && !cropInfo.place)
continue;
S32 idx0 = indices[faces[i].start+0];
S32 idx1 = indices[faces[i].start+1];
S32 idx2 = indices[faces[i].start+2];
if (!done[idx0])
handleCropAndPlace(tverts[idx0],cropInfo);
if (!done[idx1])
handleCropAndPlace(tverts[idx1],cropInfo);
if (!done[idx2])
handleCropAndPlace(tverts[idx2],cropInfo);
done[idx0] = done[idx1] = done[idx2] = true;
}
// generate normals
computeNormals(faces,indices,verts,normals,smooth,verts.size(),1);
// now sort by material index
sortFaceList(faces);
}
S32 ShapeMimic::getMultiResVerts(INode * meshNode, F32 multiResPercent)
{
MultiResMimic * multiResMimic = getMultiRes(meshNode);
if (!multiResMimic || multiResPercent<0.0f)
return -1;
return multiResMimic->totalVerts * multiResPercent;
}
//--------------------------------------------
// generate a node state at specific time -- put state into shape
void ShapeMimic::addNodeRotation(NodeMimic * curNode, S32 time, TSShape * pShape, bool blend, Quat16 & rot, bool defaultVal)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDNodeStates,avar("Adding%snode rotation at time %i for node \"%s\".\r\n",
blend ? " blend " : " ", time, curNode->maxNode->GetName()));
if (!defaultVal)
{
pShape->nodeRotations.increment();
pShape->nodeRotations.last() = rot;
}
else
{
pShape->defaultRotations.increment();
pShape->defaultRotations.last() = rot;
}
QuatF q;
rot.getQuatF(&q);
printDump(PDNodeStateDetails,avar(" rotation: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",q.x,q.y,q.z,q.w));
// all added, add separator to dump file...
printDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeTranslation(NodeMimic * curNode, S32 time, TSShape * pShape, bool blend, Point3F & trans, bool defaultVal)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDNodeStates,avar("Adding%snode translation at time %i for node \"%s\".\r\n",
blend ? " blend " : " ", time, curNode->maxNode->GetName()));
if (!defaultVal)
{
pShape->nodeTranslations.increment();
pShape->nodeTranslations.last() = trans;
}
else
{
pShape->defaultTranslations.increment();
pShape->defaultTranslations.last() = trans;
}
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...
printDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeUniformScale(NodeMimic * curNode, S32 time, TSShape * pShape, bool blend, F32 scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDNodeStates,avar("Adding%snode scale at time %i for node \"%s\".\r\n",
blend ? " blend " : " ", time, curNode->maxNode->GetName()));
pShape->nodeUniformScales.increment();
pShape->nodeUniformScales.last() = scale;
printDump(PDNodeStateDetails,avar(" uniform scale: %3.5f\r\n",scale));
// all added, add separator to dump file...
printDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeAlignedScale(NodeMimic * curNode, S32 time, TSShape * pShape, bool blend, Point3F & scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDNodeStates,avar("Adding%snode scale at time %i for node \"%s\".\r\n",
blend ? " blend " : " ", time, curNode->maxNode->GetName()));
pShape->nodeAlignedScales.increment();
pShape->nodeAlignedScales.last() = scale;
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...
printDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
void ShapeMimic::addNodeArbitraryScale(NodeMimic * curNode, S32 time, TSShape * pShape, bool blend, Quat16 & qrot, Point3F & scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDNodeStates,avar("Adding%snode scale at time %i for node \"%s\".\r\n",
blend ? " blend " : " ", time, curNode->maxNode->GetName()));
pShape->nodeArbitraryScaleRots.increment();
pShape->nodeArbitraryScaleFactors.increment();
pShape->nodeArbitraryScaleRots.last() = qrot;
pShape->nodeArbitraryScaleFactors.last() = scale;
QuatF q;
qrot.getQuatF(&q);
printDump(PDNodeStateDetails,avar(" arbitrary scale rot: x=%3.5f, y=%3.5f, z=%3.5f, w=%3.5f\r\n",q.x,q.y,q.z,q.w));
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...
printDump(PDNodeStates|PDNodeStateDetails,"---------------------------------\r\n");
}
//--------------------------------------------
// generate a node transform at specific time
void ShapeMimic::generateNodeTransform(NodeMimic * curNode,S32 time,TSShape * pShape,
bool blend, S32 blendReferenceTime,
Quat16 & rot, Point3F & trans, Quat16 & qrot, Point3F & scale)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (blend)
getBlendNodeTransform(curNode->maxNode,curNode->parent->maxNode,curNode->child0,curNode->parent0,time,blendReferenceTime,rot,trans,qrot,scale);
else
getLocalNodeTransform(curNode->maxNode,curNode->parent->maxNode,curNode->child0,curNode->parent0,time,rot,trans,qrot,scale);
}
//--------------------------------------------
// generate ifl materials -- called by generateShape
void ShapeMimic::generateIflMaterials(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// if none to make...
if (iflList.empty())
return;
printDump(PDSequences,avar("\r\nAdding %i ifl materials...\r\n\r\n",iflList.size()));
S32 i;
for (i=0; i<iflList.size(); i++)
{
pShape->iflMaterials.increment();
TSShape::IflMaterial & iflMaterial = pShape->iflMaterials.last();
iflMaterial.nameIndex = addName(iflList[i]->fileName,pShape);
iflMaterial.materialSlot = iflList[i]->materialSlot;
printDump(PDSequences,avar("Adding ifl material \"%s\".\r\n",iflList[i]->fileName));
}
}
//--------------------------------------------
// generate sequences -- called by generateShape
void ShapeMimic::generateSequences(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDSequences,avar("\r\nAdding %i sequences...\r\n\r\n",sequences.size()));
for (S32 i=0; i<sequences.size(); i++)
{
INode * pNode = sequences[i];
const char * name = pNode->GetName();
printDump(PDSequences,avar("Adding sequence %i named \"%s\"\r\n",i,name));
pShape->sequences.increment();
TSShape::Sequence & seq = pShape->sequences.last();
constructInPlace(&seq);
// we'll need some tools from max...
Object *obj = pNode->GetObjectRef();
Interval range = pNode->GetTimeRange(TIMERANGE_ALL |
TIMERANGE_CHILDNODES |
TIMERANGE_CHILDANIMS );
IParamBlock *pblock = (IParamBlock *)obj->GetReference(0);
Control * control = pblock->GetController(PB_SEQ_BEGIN_END);
IKeyControl * ikc = GetKeyControlInterface(control);
if (ikc && ikc->GetNumKeys()>=2)
{
IBezFloatKey key; // hopefully the biggest key user can choose...otherwise we might crash...stupid max.
ikc->GetKey(0,&key);
range.SetStart(key.time);
range.SetEnd(key.time);
for (S32 j=1; j<ikc->GetNumKeys(); j++)
{
ikc->GetKey(j,&key);
if (key.time > range.End())
range.SetEnd(key.time);
else if (key.time < range.Start())
range.SetStart(key.time);
}
}
// we know 'obj' is either a sequence object for new exporter or
// a sequence object for the old exporter...find out which
bool oldSequence = obj->ClassID() == OLD_SEQUENCE_CLASS_ID;
ExporterSequenceData seqData;
if (oldSequence)
setupOldSequence(pblock,range,&seqData);
else
setupSequence(pblock,range,&seqData);
// get the range of this sequence...
F32 start = TicksToSec(range.Start());
F32 end = TicksToSec(range.End());
F32 duration;
F32 delta;
S32 numFrames;
getSequenceTiming(seqData,&start,&end,&duration,&delta,&numFrames);
// fill in some sequence data
seq.flags = 0;
if (seqData.cyclic)
seq.flags |= TSShape::Cyclic;
if (seqData.blend)
seq.flags |= TSShape::Blend;
seq.priority = seqData.priority;
seq.nameIndex = addName(name,pShape);
seq.numKeyframes = numFrames;
seq.duration = seqData.overrideDuration ? seqData.overriddenDuration : duration;
seq.toolBegin = start;
// determine which nodes/objects are controlled by this sequence
S32 rotCount, transCount, uniformScaleCount, alignedScaleCount, arbitraryScaleCount;
setNodeMembership(pShape,range,seqData, rotCount,transCount,uniformScaleCount,alignedScaleCount,arbitraryScaleCount);
S32 objectCount = setObjectMembership(seq,range,seqData);
S32 iflCount = setIflMembership(seq,range,seqData);
S32 decalCount = setDecalMembership(seq,range,seqData);
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 (isError()) return;
// supply some dump information
if (!seqData.cyclic)
printDump(PDSequences,"One-shot sequence. ");
if (seqData.blend)
printDump(PDSequences,"Blend sequence. ");
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.enableDecal ? 'D' : ' ',
seqData.enableDecalFrame ? 'F' : ' ',
seqData.forceMorph ? 'M' : ' ',
seqData.forceVis ? 'V' : ' ',
seqData.forceTransform ? 'T' : ' ',
seqData.forceScale ? 'S' : ' ',
seqData.priority));
if (seqData.ignoreGround)
printDump(PDSequences,"Ignoring ground transform.\r\n");
printDump(PDSequences,avar("Duration = %3.5f, secPerFrame = %3.5f, # frames = %i\r\n",duration,delta,numFrames));
printDump(PDSequences,avar("Sequence includes %i nodes, %i objects, %i decals, and %i ifl materials\r\n",nodeCount,objectCount,decalCount,iflCount));
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(pShape,range,seqData);
generateObjectAnimation(pShape,range,seqData);
generateDecalAnimation(pShape,range,seqData);
generateGroundAnimation(pShape,range,seqData);
generateFrameTriggers(pShape,range,seqData,pblock);
if (testCutNodes(range,seqData))
return;
}
}
//--------------------------------------------
// setup object membership-- called by generateSequences
S32 ShapeMimic::setObjectMembership(TSShape::Sequence & seq,
Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0;
// clear out all object membership...
seq.visMatters.clearAll();
seq.frameMatters.clearAll();
seq.matFrameMatters.clearAll();
// force anything?
if (seqData.forceVis)
seq.visMatters.setAll(objectList.size());
if (seqData.forceMorph)
seq.frameMatters.setAll(objectList.size());
if (seqData.forceTVert)
seq.matFrameMatters.setAll(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++)
{
bool error = false;
// the node to perform membership tests on
INode * testNode = objectList[i]->maxParent;
if (objectList[i]->isSkin)
{
// in case force was set
seq.frameMatters.clear(i);
seq.matFrameMatters.clear(i);
testNode=NULL;
for (S32 dl=0; dl<objectList[i]->numDetails; dl++)
if (objectList[i]->details[dl].mesh)
testNode = objectList[i]->details[dl].mesh->skinMimic->skinNode;
if (!testNode)
continue;
}
bool visDiffersFromDefault =
(mFabs(getVisValue(testNode,DEFAULT_TIME)-getVisValue(testNode,range.Start()))>0.01f);
if (doVis && (animatesVis(testNode,range,error) || visDiffersFromDefault))
seq.visMatters.set(i);
if (doTVert && animatesMatFrame(testNode,range,error))
seq.matFrameMatters.set(i);
if (objectList[i]->isSkin)
continue;
if (doMorph && animatesFrame(testNode,range,error))
seq.frameMatters.set(i);
if (error)
{
setExportError("Assertion failed: Error checking for object animation.");
break;
}
}
// how many objects are in the set?
S32 objectCount=0;
for (S32 j=0; j<objectList.size(); j++)
if (seq.frameMatters.test(j) || seq.matFrameMatters.test(j) || seq.visMatters.test(j))
objectCount++;
return objectCount;
}
//--------------------------------------------
// setup decal membership-- called by generateSequences
S32 ShapeMimic::setDecalMembership(TSShape::Sequence & seq,
Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0;
// decide object membership
seq.decalMatters.clearAll();
// don't do any work we don't have to...
if (!seqData.enableDecal)
return 0;
// force anything?
if (seqData.forceDecal)
{
seq.decalMatters.setAll(decalObjectList.size());
return decalObjectList.size();
}
// do the work
S32 count = 0;
for (S32 i=0; i<decalObjectList.size(); i++)
{
for (S32 time = range.Start(); time<=range.End(); time++)
if (isVis(decalObjectList[i]->decalNode,time))
{
// if decal visible at all during the sequence then it is a member...
seq.decalMatters.set(i);
count++;
break;
}
}
return count;
}
//--------------------------------------------
// setup node membership-- called by generateSequences
void ShapeMimic::setNodeMembership(TSShape * pShape,
Interval & range,
ExporterSequenceData & 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 (isError()) return;
// get the sequence we're working on ... always working
// on the last sequence created
TSShape::Sequence & seq = pShape->sequences.last();
// decide node membership
seq.rotationMatters.clearAll();
seq.translationMatters.clearAll();
seq.scaleMatters.clearAll();
if (!seqData.enableTransform && !seqData.enableUniformScale && !seqData.enableArbitraryScale)
// not animating transforms, so no nodes are members
return;
// get the range of this sequence...
F32 start = TicksToSec(range.Start());
F32 end = TicksToSec(range.End());
F32 duration;
F32 delta;
S32 numFrames;
// we re-compute this here rather than pass in the data for
// readability sake only
getSequenceTiming(seqData,&start,&end,&duration,&delta,&numFrames);
// this shouldn't be allowed, but check anyway...
if (numFrames<2)
return;
S32 i, frame;
// clear out the transform caches and set it up for this sequence
for (i=0; i<nodeRotCache.size(); i++)
delete [] nodeRotCache[i];
for (i=0; i<nodeTransCache.size(); i++)
delete [] nodeTransCache[i];
for (i=0; i<nodeScaleCache.size(); i++)
delete [] nodeScaleCache[i];
nodeRotCache.setSize(nodes.size());
for (i=0;i<nodes.size();i++)
nodeRotCache[i] = new Quat16[numFrames];
nodeTransCache.setSize(nodes.size());
for (i=0;i<nodes.size();i++)
nodeTransCache[i] = new Point3F[numFrames];
nodeScaleCache.setSize(nodes.size());
for (i=0;i<nodes.size();i++)
nodeScaleCache[i] = new ScaleStruct[numFrames];
// get all the node transforms for every frame
F32 time = start;
for (frame = 0; frame<numFrames; frame++)
{
S32 maxTime = SecToTicks(time); // as in 3d studio max...
// may go just a tad over/under due to round-off...correct that here
if (maxTime>range.End())
maxTime = range.End();
else if (maxTime<range.Start())
maxTime = range.Start();
for (i=0;i<nodes.size();i++)
generateNodeTransform(nodes[i],maxTime,pShape,seqData.blend,seqData.blendReferenceTime,
nodeRotCache[i][frame],nodeTransCache[i][frame],
nodeScaleCache[i][frame].mRotate,nodeScaleCache[i][frame].mScale);
time += delta;
}
// test to see if max changes the transform over the interval
// in order to decide whether to animate the transform in 3space
// we don't use max's mechanism for doing this because it tests
// whether world space transform changes, and we care about local
// transform -- we handle scale a little different too...we have
// no scale on the default transforms
rotCount = setRotationMembership(pShape,seq,seqData,numFrames);
transCount = setTranslationMembership(pShape,seq,seqData,numFrames);
if (animatesArbitraryScale(seqData,numFrames))
arbitraryScaleCount = setArbitraryScaleMembership(pShape,seq,seqData,numFrames);
else if (animatesAlignedScale(seqData,numFrames))
alignedScaleCount = setAlignedScaleMembership(pShape,seq,seqData,numFrames);
else
uniformScaleCount = setUniformScaleMembership(pShape,seq,seqData,numFrames);
if (arbitraryScaleCount)
seq.flags |= TSShape::ArbitraryScale;
else if (alignedScaleCount)
seq.flags |= TSShape::AlignedScale;
else if (uniformScaleCount)
seq.flags |= TSShape::UniformScale;
}
S32 ShapeMimic::setRotationMembership(TSShape * pShape, TSSequence & seq, ExporterSequenceData & seqData, S32 numFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0;
S32 nodeCount = 0;
if (seqData.forceTransform)
{
// all transforms are forced to be members
seq.rotationMatters.setAll(nodes.size());
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
seq.rotationMatters.clear(i);
else
nodeCount++;
}
return nodeCount;
}
else if (!seqData.enableTransform)
return 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
continue;
// first rotation
Quat16 * firstRot = &nodeRotCache[i][0];
Quat16 * prevRot = firstRot;
Quat16 & defaultRot = pShape->defaultRotations[i];
if (!(*firstRot==defaultRot))
{
seq.rotationMatters.set(i);
nodeCount++;
continue;
}
for (S32 frame=1; frame<numFrames; frame++)
{
Quat16 * curRot = &nodeRotCache[i][frame];
if (!(*curRot==*prevRot) || !(*curRot==*firstRot))
{
seq.rotationMatters.set(i);
nodeCount++;
break;
}
prevRot = curRot;
}
}
return nodeCount;
}
S32 ShapeMimic::setTranslationMembership(TSShape * pShape, TSSequence & seq, ExporterSequenceData & seqData, S32 numFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0;
S32 nodeCount = 0;
if (seqData.forceTransform)
{
// all transforms are forced to be members
seq.translationMatters.setAll(nodes.size());
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
seq.translationMatters.clear(i);
else
nodeCount++;
}
return nodeCount;
}
else if (!seqData.enableTransform)
return 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
continue;
// first rotation
Point3F * firstTrans = &nodeTransCache[i][0];
Point3F * prevTrans = firstTrans;
Point3F & defaultTrans = pShape->defaultTranslations[i];
Point3F delta = *firstTrans-defaultTrans;
if (mFabs(delta.x)>0.0001f || mFabs(delta.y)>0.0001f || mFabs(delta.z)>0.0001f)
{
seq.translationMatters.set(i);
nodeCount++;
continue;
}
for (S32 frame=1; frame<numFrames; frame++)
{
Point3F * curTrans = &nodeTransCache[i][frame];
Point3F delta1 = *curTrans-*prevTrans;
Point3F delta2 = *curTrans-*firstTrans;
if (mFabs(delta1.x)>0.0001f || mFabs(delta1.y)>0.0001f || mFabs(delta1.z)>0.0001f ||
mFabs(delta2.x)>0.0001f || mFabs(delta2.y)>0.0001f || mFabs(delta2.z)>0.0001f)
{
seq.translationMatters.set(i);
nodeCount++;
break;
}
prevTrans = curTrans;
}
}
return nodeCount;
}
S32 ShapeMimic::setUniformScaleMembership(TSShape * pShape, TSSequence & seq, ExporterSequenceData & seqData, S32 numFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0;
S32 nodeCount = 0;
if (seqData.forceScale)
{
// all transforms are forced to be members
seq.scaleMatters.setAll(nodes.size());
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
seq.scaleMatters.clear(i);
else
nodeCount++;
}
return nodeCount;
}
else if (!seqData.enableUniformScale)
return 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
continue;
// first rotation
Point3F a = nodeScaleCache[i][0].mScale;
F32 firstScale = (a.x+a.y+a.z)/3.0f;
F32 prevScale = firstScale;
if (mFabs(firstScale-1.0f)>0.001f)
{
seq.scaleMatters.set(i);
nodeCount++;
continue;
}
for (S32 frame=1; frame<numFrames; frame++)
{
Point3F a = nodeScaleCache[i][frame].mScale;
F32 curScale = (a.x+a.y+a.z)/3.0f;
if (mFabs(curScale-prevScale)>0.001f)
{
seq.scaleMatters.set(i);
nodeCount++;
break;
}
prevScale = curScale;
}
}
return nodeCount;
}
S32 ShapeMimic::setAlignedScaleMembership(TSShape * pShape, TSSequence & seq, ExporterSequenceData & seqData, S32 numFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0;
S32 nodeCount = 0;
if (seqData.forceScale)
{
// all transforms are forced to be members
seq.scaleMatters.setAll(nodes.size());
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
seq.scaleMatters.clear(i);
else
nodeCount++;
}
return nodeCount;
}
else if (!seqData.enableArbitraryScale)
return 0;
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
continue;
// first rotation
Point3F * firstScale = &nodeScaleCache[i][0].mScale;
Point3F * prevScale = firstScale;
if (mFabs(firstScale->x-1.0f)>0.0001f || mFabs(firstScale->y-1.0f)>0.0001f || mFabs(firstScale->z-1.0f)>0.0001f)
{
seq.scaleMatters.set(i);
nodeCount++;
continue;
}
for (S32 frame=1; frame<numFrames; frame++)
{
Point3F * curScale = &nodeScaleCache[i][frame].mScale;
Point3F delta1 = *curScale-*prevScale;
Point3F delta2 = *curScale-*firstScale;
if (mFabs(delta1.x)>0.0001f || mFabs(delta1.y)>0.0001f || mFabs(delta1.z)>0.0001f ||
mFabs(delta2.x)>0.0001f || mFabs(delta2.y)>0.0001f || mFabs(delta2.z)>0.0001f)
{
seq.scaleMatters.set(i);
nodeCount++;
break;
}
prevScale = curScale;
}
}
return nodeCount;
}
S32 ShapeMimic::setArbitraryScaleMembership(TSShape * pShape, TSSequence & seq, ExporterSequenceData & seqData, S32 numFrames)
{
// for determining membership, we just care if scale factor is animated...
return setAlignedScaleMembership(pShape,seq,seqData,numFrames);
}
bool ShapeMimic::animatesAlignedScale(ExporterSequenceData & seqData,S32 numFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return false;
if (!seqData.enableArbitraryScale)
return false;
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
continue;
for (S32 frame=0; frame<numFrames; frame++)
{
Point3F delta = nodeScaleCache[i][frame].mScale - Point3F(1,1,1);
if ((mFabs(delta.x)>0.001f || mFabs(delta.y)>0.001f || mFabs(delta.z)>0.001f) &&
mFabs(delta.x-delta.y)>0.001f && mFabs(delta.y-delta.z)>0.001f && mFabs(delta.z-delta.x)>0.001f)
// we not only animate scale, but we do it non-uniformly
return true;
}
}
return false;
}
bool ShapeMimic::animatesArbitraryScale(ExporterSequenceData & seqData, S32 numFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return false;
if (!seqData.enableArbitraryScale)
return false;
for (S32 i=0; i<nodes.size(); i++)
{
if (neverAnimateNode(nodes[i]))
continue;
Quat16 idQuat16;
idQuat16.set(QuatF(0,0,0,1));
for (S32 frame=0; frame<numFrames; frame++)
{
Quat16 curRot = nodeScaleCache[i][frame].mRotate;
Point3F curScale = nodeScaleCache[i][frame].mScale;
if (curRot==idQuat16)
// scale factor is aligned, not arbitrary
continue;
Point3F delta = curScale - Point3F(1,1,1);
if (mFabs(delta.x)<0.001f && mFabs(delta.y)<0.001f && mFabs(delta.z)<0.001f)
// no scale
continue;
// we have scale and it isn't aligned...
return true;
}
}
return false;
}
//--------------------------------------------
// setup ifl membership-- called by generateSequences
S32 ShapeMimic::setIflMembership(TSShape::Sequence & seq,
Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0;
range,seqData;
S32 i,iflCount = 0;
FileStream fs;
char buffer[512];
char name[256];
// get start and end frame
U32 startFrame = (U32) ( 0.01f + TicksToSec(range.Start()) * 30.0f ); // 30 fps
U32 endFrame = (U32) ( 0.01f + TicksToSec( range.End()) * 30.0f ); // 30 fps
// decide object membership
if (seqData.enableIFL)
{
for (i=0; i<iflList.size(); i++)
{
// does ifl animate any materials during our range?
U32 totalTime = 0, duration;
fs.open(iflList[i]->fullFileName,FileStream::Read);
if (fs.getStatus() != Stream::Ok)
{
setExportError(avar("Error reading ifl file \"%s\"",iflList[i]->fullFileName));
fs.close();
return 0;
}
Vector<S32> durations;
bool countMe = false;
while (fs.getStatus() == Stream::Ok)
{
fs.readLine((U8*)buffer,512);
if (fs.getStatus() == Stream::Ok || fs.getStatus() == Stream::EOS)
{
char * pos = buffer;
while (*pos)
{
if (*pos=='\t')
*pos=' ';
pos++;
}
pos = buffer;
// strip off preceding spaces...
while (*pos && *pos==' ')
pos++;
char * pos2 = dStrchr(pos,' ');
duration = 1; // if nothing provided...
if (pos2)
{
// look for a supplied duration
pos = pos2+1;
while (*pos && *pos==' ')
pos++;
if (*pos)
duration = dAtoi(pos);
if (duration==0)
duration = 1;
}
durations.push_back(duration);
totalTime += duration;
if (totalTime>startFrame && totalTime<=endFrame)
{
// changing material during this sequence...
countMe = true;
break;
}
}
}
fs.close();
// file closed, but if we aren't through w/ sequence, loop ifl
S32 loop=0;
while (!countMe && totalTime<endFrame)
{
totalTime += durations[loop++ % durations.size()];
if (totalTime>startFrame && totalTime<=endFrame)
countMe=true;
}
if (countMe)
{
// changing material during this sequence...
iflCount++;
seq.iflMatters.set(i);
}
}
}
else
{
seq.iflMatters.clearAll();
iflCount = 0;
}
// at some point we may want to have a way to more selectively animate ifl materials
return iflCount;
}
//--------------------------------------------
// determine whether nodes with meshes which
// were cut during collapse phase have illegal
// animation
bool ShapeMimic::testCutNodes(Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return true;
// get the range of this sequence...
F32 start = TicksToSec(range.Start());
F32 end = TicksToSec(range.End());
F32 duration;
F32 delta;
S32 numFrames;
// we re-compute this here rather than pass in the data for
// readability sake only
getSequenceTiming(seqData,&start,&end,&duration,&delta,&numFrames);
// this shouldn't be allowed, but check anyway...
if (numFrames<2)
return false;
S32 i, frame;
Vector<Quat16> rotTrans;
Vector<Point3F> transTrans;
Vector<ScaleStruct> scaleTrans;
Vector<AffineParts> child0;
Vector<AffineParts> parent0;
rotTrans.setSize(numFrames);
transTrans.setSize(numFrames);
scaleTrans.setSize(numFrames);
child0.setSize(cutNodes.size());
parent0.setSize(cutNodesParents.size());
Quat16 tmpRot;
Point3F tmpTrans;
Quat16 tmpScaleRot;
Point3F tmpScaleTrans;
for (i=0;i<cutNodes.size();i++)
getLocalNodeTransform(cutNodes[i],cutNodesParents[i],child0[i],parent0[i],DEFAULT_TIME,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
F32 time = start;
for (frame = 0; frame<numFrames; frame++)
{
S32 maxTime = SecToTicks(time); // as in 3d studio max...
// may go just a tad over/under due to round-off...correct that here
if (maxTime>range.End())
maxTime = range.End();
else if (maxTime<range.Start())
maxTime = range.Start();
getLocalNodeTransform(cutNodes[i],cutNodesParents[i],child0[i],parent0[i],time,rotTrans[frame],transTrans[frame],scaleTrans[frame].mRotate,scaleTrans[frame].mScale);
time += delta;
}
// we now have all numFrames transforms...check to see if they change
Quat16 * firstRot = &rotTrans[0];
Quat16 * prevRot = firstRot;
Point3F * firstTrans = &transTrans[0];
Point3F * prevTrans = firstTrans;
Quat16 * firstScaleRot = &scaleTrans[0].mRotate;
Quat16 * prevScaleRot = firstScaleRot;
Point3F * firstScale = &scaleTrans[0].mScale;
Point3F * prevScale = firstScale;
for (frame=0; frame<numFrames; frame++)
{
Quat16 * curRot = &rotTrans[frame];
Point3F * curTrans = &transTrans[frame];
Quat16 * curScaleRot = &scaleTrans[frame].mRotate;
Point3F * curScale = &scaleTrans[frame].mScale;
Point3F delta = *curTrans-*prevTrans;
Point3F deltaScale = *curScale-*prevScale;
bool idScale1 = mFabs(curScale->x - 1.0f) < 0.01f && mFabs(curScale->y - 1.0f) < 0.01f && mFabs(curScale->z - 1.0f) < 0.01f;
bool idScale2 = mFabs(prevScale->x - 1.0f) < 0.01f && mFabs(prevScale->y - 1.0f) < 0.01f && mFabs(prevScale->z - 1.0f) < 0.01f;
bool pureScaleDiff = mFabs(deltaScale.x)>0.01f || mFabs(deltaScale.y)>0.01f || mFabs(deltaScale.z)>0.01f;
bool isScaled = pureScaleDiff || ((!idScale1 || !idScale2) && !(*curScale==*prevScale));
bool isTrans = mFabs(delta.x)>animationDelta || mFabs(delta.y)>animationDelta || mFabs(delta.z)>animationDelta;
bool isRot = !(*curRot==*prevRot);
if (isRot || isTrans || isScaled)
{
// going to report error -- add extra information to the dump file:
printDump(PDAlways,"\r\n----------------------------------------------\r\n");
printDump(PDAlways,avar("\r\nIllegal transform animiation detected between collapsed node \"%s\" and \"%s\".\r\n",
cutNodes[i]->GetName(),cutNodesParents[i]->GetName()));
printDump(PDAlways,"Transform dump:\r\n\r\n");
Point3F maxT(0,0,0);
Point4F maxQ(0,0,0,0);
Point3F startT = *firstTrans;
QuatF startQ;
startQ = firstRot->getQuatF(&startQ);
for (S32 f=0; f<numFrames; f++)
{
Point3F t = transTrans[f];
QuatF q;
q = rotTrans[f].getQuatF(&q);
printDump(PDAlways,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",t.x,t.y,t.z));
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));
printDump(PDAlways,"---------------------------------\r\n");
F32 diff;
// get maximum difference for the dump file
diff = mFabs(t.x-startT.x);
if (diff > maxT.x)
maxT.x = diff;
diff = mFabs(t.y-startT.y);
if (diff > maxT.y)
maxT.y = diff;
diff = mFabs(t.z-startT.z);
if (diff > maxT.z)
maxT.z = diff;
diff = mFabs(q.x-startQ.x);
if (diff > maxQ.x)
maxQ.x = diff;
diff = mFabs(q.y-startQ.y);
if (diff > maxQ.y)
maxQ.y = diff;
diff = mFabs(q.z-startQ.z);
if (diff > maxQ.z)
maxQ.z = diff;
diff = mFabs(q.w-startQ.w);
if (diff > maxQ.w)
maxQ.w = diff;
}
printDump(PDAlways,"Maximum deviation:\r\n");
printDump(PDAlways,avar(" translation: x=%3.5f, y=%3.5f, z=%3.5f\r\n",maxT.x,maxT.y,maxT.z));
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));
printDump(PDAlways," Scale may have animated too.\r\n");
printDump(PDAlways,"---------------------------------\r\n");
setExportError(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;
}
//--------------------------------------------
// generate ground transform animation -- called by generateSequences
void ShapeMimic::generateGroundAnimation(TSShape * pShape,
Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// get the sequence we're working on ... always working
// on the last sequence created
TSShape::Sequence & seq = pShape->sequences.last();
seq.firstGroundFrame = pShape->groundTranslations.size();
seq.numGroundFrames = 0; // will change if we have ground transform
if (seqData.ignoreGround)
// nothing more to do
return;
// does this sequence animate the bounds node, if so, add ground transform
Interval test = range;
S32 midpoint = (range.Start() + range.End()) / 2;
boundsNode->GetNodeTM(midpoint,&test);
if ( test.Start()==range.Start() && test.End()==range.End() )
// no ground animation
return;
// at this point we know that we do animate bounds node,
// so we do have ground animation...
F32 groundDelta;
S32 groundNumFrames;
F32 groundDuration = TicksToSec(range.End() - range.Start());
getGroundSequenceTiming(seqData,groundDuration,&groundDelta,&groundNumFrames);
seq.flags |= TSShape::MakePath;
seq.numGroundFrames = groundNumFrames-1; // we only really add this many frames
printDump(PDSequences,
avar("\r\nAdding %i ground transform frames at %3.5f sec per frame intervals.\r\n",
groundNumFrames,groundDelta));
// frame at start isn't added since it would just be identity anyway...
F32 time = TicksToSec(range.Start()) + groundDelta;
for (S32 i=0; i<groundNumFrames-1; i++)
{
S32 maxTime = SecToTicks(time); // as in 3d studio max...
// may go just a tad over/under due to round-off...correct that here
if (maxTime>range.End())
maxTime = range.End();
else if (maxTime<range.Start())
maxTime = range.Start();
pShape->groundTranslations.increment();
pShape->groundRotations.increment();
Quat16 & rot = pShape->groundRotations.last();
Point3F & trans = pShape->groundTranslations.last();
Quat16 srot; // ignored on ground transform
Point3F scale; // ignored on ground transform
getDeltaTransform(boundsNode,range.Start(),maxTime,rot,trans,srot,scale);
time += groundDelta;
}
}
//--------------------------------------------
// generate node animation -- called by generateSequences
void ShapeMimic::generateNodeAnimation(TSShape * pShape,
Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// get the sequence we're working on ... always working
// on the last sequence created
TSShape::Sequence & seq = pShape->sequences.last();
// get the range of this sequence...
F32 start = TicksToSec(range.Start());
F32 end = TicksToSec(range.End());
F32 duration;
F32 delta;
S32 numFrames;
// we re-compute this here rather than pass in the data for
// readability sake only
getSequenceTiming(seqData,&start,&end,&duration,&delta,&numFrames);
// add the states -- add all the states for each node in a row
// Note: this is new since 9-15-00...used to be that all states
// for each keyframe were consecutive.
seq.baseRotation = pShape->nodeRotations.size();
seq.baseTranslation = pShape->nodeTranslations.size();
seq.baseScale = seq.animatesArbitraryScale() ? pShape->nodeArbitraryScaleFactors.size() :
seq.animatesAlignedScale() ? pShape->nodeAlignedScales.size() : pShape->nodeUniformScales.size();
for (S32 i=0; i<nodes.size(); i++)
{
F32 time = start;
for (S32 frame = 0; frame<numFrames; frame++, time += delta)
{
S32 maxTime = SecToTicks(time); // as in 3d studio max...
// may go just a tad over/under due to round-off...correct that here
if (maxTime>range.End())
maxTime = range.End();
else if (maxTime<range.Start())
maxTime = range.Start();
if (seq.rotationMatters.test(i))
addNodeRotation(nodes[i],maxTime,pShape,seqData.blend,nodeRotCache[i][frame]);
if (seq.translationMatters.test(i))
addNodeTranslation(nodes[i],maxTime,pShape,seqData.blend,nodeTransCache[i][frame]);
if (seq.scaleMatters.test(i))
{
Quat16 & rot = nodeScaleCache[i][frame].mRotate;
Point3F scale = nodeScaleCache[i][frame].mScale;
if (seq.animatesArbitraryScale())
addNodeArbitraryScale(nodes[i],maxTime,pShape,seqData.blend,rot,scale);
else if (seq.animatesAlignedScale())
addNodeAlignedScale(nodes[i],maxTime,pShape,seqData.blend,scale);
else
addNodeUniformScale(nodes[i],maxTime,pShape,seqData.blend,(scale.x+scale.y+scale.z)/3.0f);
}
}
}
}
//--------------------------------------------
// generate object animation -- called by generateSequences
void ShapeMimic::generateObjectAnimation(TSShape * pShape,
Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// get the sequence we're working on ... always working
// on the last sequence created
TSShape::Sequence & seq = pShape->sequences.last();
// get the range of this sequence...
F32 start = TicksToSec(range.Start());
F32 end = TicksToSec(range.End());
F32 duration;
F32 delta;
S32 numFrames;
// we re-compute this here rather than pass in the data for
// readability sake only
getSequenceTiming(seqData,&start,&end,&duration,&delta,&numFrames);
// add the states -- add all the states for each object in a row
// Note: this is new since 9-15-00...used to be that all states
// for each keyframe were consecutive.
seq.baseObjectState = pShape->objectStates.size();
TSIntegerSet objectMembership = seq.frameMatters;
objectMembership.overlap(seq.matFrameMatters);
objectMembership.overlap(seq.visMatters);
for (S32 i=0; i<objectList.size(); i++)
{
if (objectMembership.test(i))
{
F32 time = start;
for (S32 frame = 0; frame<numFrames; frame++, time += delta)
{
S32 maxTime = SecToTicks(time); // as in 3d studio max...
// may go just a tad over/under due to round-off...correct that here
if (maxTime>range.End())
maxTime = range.End();
else if (maxTime<range.Start())
maxTime = range.Start();
generateObjectState(objectList[i],maxTime,pShape,seq.frameMatters.test(i),seq.matFrameMatters.test(i));
}
}
}
}
//--------------------------------------------
// generate decal animation -- called by generateSequences
void ShapeMimic::generateDecalAnimation(TSShape * pShape,
Interval & range,
ExporterSequenceData & seqData)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// get the sequence we're working on ... always working
// on the last sequence created
TSShape::Sequence & seq = pShape->sequences.last();
// get the range of this sequence...
F32 start = TicksToSec(range.Start());
F32 end = TicksToSec(range.End());
F32 duration;
F32 delta;
S32 numFrames;
// we re-compute this here rather than pass in the data for
// readability sake only
getSequenceTiming(seqData,&start,&end,&duration,&delta,&numFrames);
// add the states -- add all the states for each decal in a row
// Note: this is new since 9-15-00...used to be that all states
// for each keyframe were consecutive.
seq.baseDecalState = pShape->decalStates.size();
for (S32 i=0; i<decalObjectList.size(); i++)
{
if (seq.decalMatters.test(i))
{
F32 time = start;
for (S32 frame = 0; frame<numFrames; frame++, time+=delta)
{
S32 maxTime = SecToTicks(time); // as in 3d studio max...
// may go just a tad over/under due to round-off...correct that here
if (maxTime>range.End())
maxTime = range.End();
else if (maxTime<range.Start())
maxTime = range.Start();
generateDecalState(decalObjectList[i],maxTime,pShape,seqData.enableDecalFrame);
}
}
}
}
//--------------------------------------------
// generate a decal state at specific time
void ShapeMimic::generateDecalState(DecalObjectMimic * dom, S32 time, TSShape * pShape, bool multipleFrames)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
printDump(PDObjectStates,avar("Adding decal state to %i detail level(s) of mesh \"%s\".\r\n",dom->numDetails,dom->targetObject->name));
pShape->decalStates.increment();
TSShape::DecalState & ds = pShape->decalStates.last();
// is decal visible
if (!decalOn(dom,time))
{
ds.frameIndex = -1;
return;
}
// must have highest detail level...
if (!dom->details[0].decalMesh->tsMesh)
{
setExportError(avar("Missing highest detail level on decal mesh \"%s\" which decals \"%s\".",dom->decalNode->GetName(),dom->targetObject->name));
return;
}
S32 startNumMatFrames = dom->details[0].decalMesh->tsMesh->startPrimitive.size();
// new frame?
if (multipleFrames || startNumMatFrames==0)
// add frame -- may end up using old frame or being a blank frame
ds.frameIndex = generateDecalFrame(dom,time);
else
// only 1 frame allowed, use that one
ds.frameIndex = 0;
// new frame, add to dump file...
if (startNumMatFrames!=dom->details[0].decalMesh->tsMesh->startPrimitive.size())
printDump(PDObjectStates,"New frame added.\r\n");
// all added, add separator to dump file...
printDump(PDObjectStates|PDObjectStateDetails,"---------------------------------\r\n");
}
//--------------------------------------------
// test to see if decal is on at given time
bool ShapeMimic::decalOn(DecalObjectMimic * dom, S32 time)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return false;
return isVis(dom->decalNode,time);
}
//--------------------------------------------
// generate a decal frame at specific time -- put it into the shape
// this may be a repeat frame, or even an empty frame, so we return the frame number
S32 ShapeMimic::generateDecalFrame(DecalObjectMimic * dom, S32 time)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return -1;
if (!prepareDecal(dom->decalNode,time))
// some error occurred...or maybe just no decal
return -1;
S32 i,start, dl;
for (dl=0; dl<dom->numDetails; dl++)
{
if (!dom->details[dl].decalMesh)
continue;
generateDecalFrame(dom,time,dl);
}
return findLastDecalFrameNumber(dom);
}
// return frame number for last added frame...
// -- check to see if all the details just added a previous frame (or an empty frame) and if it's
// the same one...if so, we'll get rid of the new frame and return the old one (or empty one)
S32 ShapeMimic::findLastDecalFrameNumber(DecalObjectMimic * dom)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return -1;
S32 dl,i;
bool allEmpty = true;
S32 dupFrame = -1;
for (dl=0; dl<dom->numDetails; dl++)
{
if (!dom->details[dl].decalMesh)
continue;
TSDecalMesh * tsDecal = dom->details[dl].decalMesh->tsMesh;
if (tsDecal->startPrimitive.last()!=tsDecal->primitives.size())
{
allEmpty = false;
for (i=0; i<tsDecal->startPrimitive.size()-1; i++)
{
if (tsDecal->startPrimitive.last()==tsDecal->startPrimitive[i])
break;
}
if (i<tsDecal->startPrimitive.size()-1)
{
if (dupFrame>=0 && dupFrame!=i)
{
// different duplicate frame...keep the frame
dupFrame = -1;
break;
}
dupFrame = i;
}
}
}
if (dupFrame>=0 || allEmpty)
{
// we don't need the frame we added because we already had it (or it was empty)
for (dl=0; dl<dom->numDetails; dl++)
{
if (!dom->details[dl].decalMesh)
continue;
TSDecalMesh * tsDecal = dom->details[dl].decalMesh->tsMesh;
if (tsDecal->startPrimitive.size())
{
tsDecal->startPrimitive.decrement();
tsDecal->texgenS.decrement();
tsDecal->texgenT.decrement();
}
}
}
if (allEmpty)
return -1;
if (dupFrame>=0)
return dupFrame;
return dom->details[0].decalMesh->tsMesh->startPrimitive.size()-1;
}
bool ShapeMimic::prepareDecal(INode * decalNode, S32 time)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return false;
S32 matIndex = -1;
gDecalInfo.decalNode = decalNode;
gDecalInfo.filter = NULL;
gDecalInfo.pos = Point3ToPoint3F(decalNode->GetNodeTM(time).GetTrans(),gDecalInfo.pos);
gDecalInfo.x = Point3ToPoint3F(decalNode->GetNodeTM(time).GetRow(0),gDecalInfo.x);
gDecalInfo.y = Point3ToPoint3F(decalNode->GetNodeTM(time).GetRow(1),gDecalInfo.y);
gDecalInfo.z = Point3ToPoint3F(decalNode->GetNodeTM(time).GetRow(2),gDecalInfo.z);
if (!decalNode->GetUserPropFloat("DECAL::MAX_ANGLE",gDecalInfo.maxAngle))
gDecalInfo.maxAngle = 90.0f;
gDecalInfo.minCos = mCos( gDecalInfo.maxAngle * M_PI / 180.0f);
if (decalNode->GetObjectRef()->FindBaseObject()->ClassID() == Class_ID(SPHERE_CLASS_ID,0))
{
gDecalInfo.decalType = SphereDecal;
F32 radius;
S32 recenter;
Interval valid = FOREVER;
decalNode->GetObjectRef()->FindBaseObject()->GetParamBlock()->GetValue(0,time,radius,valid); // PB_RADIUS=0 (hard-code because can depend on object)
decalNode->GetObjectRef()->FindBaseObject()->GetParamBlock()->GetValue(5,time,recenter,valid); // PB_RECENTER=5
if (recenter)
{
gDecalInfo.z *= radius;
gDecalInfo.pos += gDecalInfo.z;
}
}
else if (decalNode->GetObjectRef()->FindBaseObject()->ClassID() == Class_ID(CYLINDER_CLASS_ID,0))
gDecalInfo.decalType = CylinderDecal;
else if (decalNode->GetObjectRef()->FindBaseObject()->ClassID() == Class_ID(BOXOBJ_CLASS_ID,0))
gDecalInfo.decalType = BoxDecal;
else if (decalNode->GetObjectRef()->FindBaseObject()->ClassID() == HACK_PLANE_CLASS_ID) // not in docs...added meself
gDecalInfo.decalType = PlaneDecal;
else
{
setExportError(avar("Unknown decal base type (%i:%i) -- should be sphere, cylinder, box, or plane.",decalNode->GetObjectRef()->FindBaseObject()->ClassID().PartA(),decalNode->GetObjectRef()->FindBaseObject()->ClassID().PartB()));
return false;
}
// get the decal mesh at this time...
bool delTri;
U32 saveDump = dumpMask;
dumpMask = 0;
TriObject * tri = getTriObject(decalNode,time,-1,delTri);
if (tri->mesh.getNumFaces()>0)
matIndex = tri->mesh.faces[0].getMatID(); // below we'll make sure there's only one material on the mesh and we'll use this id to get the filter bitmap
Vector<Point3F> normals;
Vector<U32> smooth;
cullOffTile = false; // temporarily disable this...
generateFaces(decalNode,tri->mesh,decalNode->GetObjTMAfterWSM(time),gDecalInfo.faces,normals,gDecalInfo.verts,gDecalInfo.tverts,gDecalInfo.indices,smooth);
cullOffTile=true; // re-enable it...
dumpMask = saveDump;
if (delTri)
delete tri;
Vector<TSDrawPrimitive> & decalFaces = gDecalInfo.faces;
Vector<U16> & decalIndices = gDecalInfo.indices;
Vector<Point3> & decalVerts = gDecalInfo.verts;
Vector<Point3> & decalTVerts = gDecalInfo.tverts;
Vector<Point3F> & n = gDecalInfo.n;
Vector<F32> & k = gDecalInfo.k;
S32 & tsMat = gDecalInfo.tsMat;
S32 i;
// make sure decal mesh only has 1 material, and get the filter bitmap if there is one
Mtl * mtl = decalNode->GetMtl();
if (mtl && mtl->ClassID() == Class_ID(MULTI_CLASS_ID,0))
{
// make sure we only have 1 material
tsMat = -1;
for (i=0; i<decalFaces.size(); i++)
{
// if this or previous material has no material, continue...
if (decalFaces[i].matIndex & TSDrawPrimitive::NoMaterial)
continue;
if (tsMat>=0 && (decalFaces[i].matIndex&TSDrawPrimitive::MaterialMask)!=tsMat)
{
setExportError(avar("Decal \"%s\" has more than one material. Only one material per decal is allowed.",decalNode->GetName()));
return false;
}
tsMat = decalFaces[i].matIndex&TSDrawPrimitive::MaterialMask;
}
// we only have 1 material...
// now get material -- don't use tsMat, use matIndex from earlier (max version of material id)
if (matIndex==-1 || matIndex>=((MultiMtl*)mtl)->NumSubMtls())
mtl=NULL;
else
mtl = (MultiMtl*)mtl->GetSubMtl(matIndex);
}
else if (!decalFaces.empty())
tsMat = decalFaces[0].matIndex;
if (!mtl)
{
// no material...no decal frame
return false;
}
if (mtl->ClassID() != Class_ID(DMTL_CLASS_ID,0))
{
setExportError(avar("Unexpected material type on decal node \"%\".",decalNode->GetName()));
return false;
}
StdMat * stdMat = (StdMat*)mtl;
if (stdMat->GetSubTexmap(ID_FI) && stdMat->MapEnabled(ID_FI))
{
if (stdMat->GetSubTexmap(ID_FI)->ClassID() != Class_ID(BMTEX_CLASS_ID,0))
{
setExportError(avar("Filter channel on decal node \"%s\" has a non-bitmap texture map.",decalNode->GetName()));
return false;
}
gDecalInfo.filter = ((BitmapTex*)stdMat->GetSubTexmap(ID_FI))->GetBitmap(time);
}
// find plane for each face on decal
n.setSize(decalFaces.size());
k.setSize(decalFaces.size());
for (i=0; i<decalFaces.size(); i++)
{
U32 idx0 = decalIndices[decalFaces[i].start+0];
U32 idx1 = decalIndices[decalFaces[i].start+1];
U32 idx2 = decalIndices[decalFaces[i].start+2];
Point3F v10 = Point3ToPoint3F(decalVerts[idx1]-decalVerts[idx0],v10);
Point3F v20 = Point3ToPoint3F(decalVerts[idx2]-decalVerts[idx0],v20);
Point3F v0 = Point3ToPoint3F(decalVerts[idx0],v0);
mCross(v20,v10,&n[i]);
n[i].normalize();
k[i] = mDot(v0,n[i]);
}
return true;
}
void ShapeMimic::generateDecalFrame(DecalObjectMimic * dom, S32 time, S32 dl)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
Vector<TSDrawPrimitive> meshFaces;
Vector<Point3F> normals;
Vector<Point3> meshVerts;
Vector<Point3> meshTVerts;
Vector<U16> meshIndices;
Vector<U32> smooth;
// turn off dump file since the following is just getting the mesh over again...
U32 saveDumpMask = dumpMask;
dumpMask = 0;
TSDecalMesh * tsDecal = dom->details[dl].decalMesh->tsMesh;
INode * target = dom->targetObject->details[dl].mesh->pNode;
S32 multiResVerts = getMultiResVerts(target,dom->targetObject->details[dl].mesh->multiResPercent);
MultiResMimic * mrm = getMultiRes(target);
target = mrm ? mrm->multiResNode : target;
bool delTri;
TriObject * tri = getTriObject(target,time,multiResVerts,delTri);
generateFaces(target,tri->mesh,target->GetObjTMAfterWSM(time),meshFaces,normals,meshVerts,meshTVerts,meshIndices,smooth);
if (delTri)
delete tri;
// restore dump file
dumpMask = saveDumpMask;
S32 i,j;
Vector<bool> used;
used.setSize(meshVerts.size());
// for each meshVert, find out where it maps to on the decal mesh
for (i=0; i<meshVerts.size(); i++)
{
used[i] = false; // till proven otherwise
meshTVerts[i] = Point3(0,0,0);
}
for (i=0; i<meshFaces.size(); i++)
{
// we assume that the verts are unshared between faces...that is, the mesh is still
// "unoptimized" and will be optimized later down the line...if this changes, be
// sure to cause some trouble below (see setExportError calls in this loop)...
// do projection for each vert of this face
U32 start = meshFaces[i].start;
if (meshFaces[i].numElements != 3)
{
setExportError("Something has broken decal generation...mesh should be unoptimized at this point");
return;
}
U32 idx0 = meshIndices[start+0];
U32 idx1 = meshIndices[start+1];
U32 idx2 = meshIndices[start+2];
Point3F v0 = Point3ToPoint3F(meshVerts[idx0],v0);
Point3F v1 = Point3ToPoint3F(meshVerts[idx1],v1);
Point3F v2 = Point3ToPoint3F(meshVerts[idx2],v2);
Point3F faceNormal;
mCross(v2-v0,v1-v0,&faceNormal);
if (mDot(faceNormal,faceNormal)>0.00000001f)
faceNormal.normalize();
else
// face real small...don't bother with decal
continue;
if (used[idx0] || used[idx1] || used[idx2])
{
setExportError("Assertion failed when adding decal -- re-using vertex no allowed");
return;
}
// pass in face normal rather than vertex normal to figure decal tverts (only used
// for box mapping and rejection text...i.e., if face points the wrong way, don't decal)
used[idx0] = getDecalTVert(gDecalInfo.decalType,meshVerts[idx0],faceNormal,meshTVerts[idx0]);
used[idx1] = getDecalTVert(gDecalInfo.decalType,meshVerts[idx1],faceNormal,meshTVerts[idx1]);
used[idx2] = getDecalTVert(gDecalInfo.decalType,meshVerts[idx2],faceNormal,meshTVerts[idx2]);
}
// now get rid of faces that aren't used in the decal
for (i=0; i<meshFaces.size(); i++)
{
U32 start = meshFaces[i].start;
U32 idx0 = meshIndices[start+0];
U32 idx1 = meshIndices[start+1];
U32 idx2 = meshIndices[start+2];
// is this face textured? if we don't wrap and face tverts don't intersect unit box [0..1,0..1]
// then we don't need this face...
bool onBmp = (materialFlags[gDecalInfo.tsMat&TSDrawPrimitive::MaterialMask] & (TSMaterialList::S_Wrap|TSMaterialList::T_Wrap)) || checkDecalFace(meshTVerts[idx0],meshTVerts[idx1],meshTVerts[idx2]);
if (!onBmp || !used[idx0] || !used[idx1] || !used[idx2] || !checkDecalFilter(meshTVerts[idx0],meshTVerts[idx1],meshTVerts[idx2]))
{
// don't want this face...
meshIndices.erase(start);
meshIndices.erase(start);
meshIndices.erase(start);
for (j=0;j<meshFaces.size();j++)
if (meshFaces[j].start>start)
meshFaces[j].start-=3; // assume faces are still triangles at this point
meshFaces.erase(i);
i--;
}
}
//---------------------------------------------------
// RIGHT HERE!
// Get rid of tverts and generate texgen info instead
//---------------------------------------------------
Matrix3 mat3 = dom->targetObject->maxTSParent->GetNodeTM(time);
zapScale(mat3);
MatrixF invMat = convertToMatrixF(mat3,invMat);
invMat.inverse();
Point4F texgenS, texgenT;
findDecalTexGen(invMat,texgenS,texgenT);
// do we match another frame?
S32 frame;
S32 numFrames = tsDecal->startPrimitive.size();
for (frame=0; frame<numFrames; frame++)
{
S32 numPrimitives;
if (frame+1<numFrames)
numPrimitives = tsDecal->startPrimitive[frame+1] - tsDecal->startPrimitive[frame];
else
numPrimitives = tsDecal->primitives.size() - tsDecal->startPrimitive[frame];
if (numPrimitives != meshFaces.size())
continue; // not same as this frame
Point4F delta;
delta.x = texgenS.x-tsDecal->texgenS[frame].x;
delta.y = texgenS.y-tsDecal->texgenS[frame].y;
delta.z = texgenS.z-tsDecal->texgenS[frame].z;
delta.w = texgenS.w-tsDecal->texgenS[frame].w;
if (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z + delta.w*delta.w>0.0001f)
// different s coords
continue;
delta.x = texgenT.x-tsDecal->texgenT[frame].x;
delta.y = texgenT.y-tsDecal->texgenT[frame].y;
delta.z = texgenT.z-tsDecal->texgenT[frame].z;
delta.w = texgenT.w-tsDecal->texgenT[frame].w;
if (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z + delta.w*delta.w>0.0001f)
// different t coords
continue;
// if we made it this far, then we have same number of faces and identical tverts to current frame...
// that's a repeat...
break;
}
// we either match a previous frame -- in which case we add a frame but not the faces/indices
// or we don't -- in which case we add everything
if (frame<numFrames)
{
S32 prm = tsDecal->startPrimitive[frame];
tsDecal->startPrimitive.push_back(prm);
}
else
{
tsDecal->startPrimitive.push_back(tsDecal->primitives.size());
for (i=0; i<meshFaces.size(); i++)
{
tsDecal->primitives.push_back(meshFaces[i]);
tsDecal->primitives.last().matIndex = numFrames | TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed;
tsDecal->primitives.last().start += tsDecal->indices.size();
}
for (i=0; i<meshIndices.size(); i++)
tsDecal->indices.push_back(meshIndices[i]);
tsDecal->texgenS.push_back(texgenS);
tsDecal->texgenT.push_back(texgenT);
tsDecal->materialIndex = gDecalInfo.tsMat;
}
}
void ShapeMimic::findDecalTexGen(MatrixF & invMat, Point4F & texgenS, Point4F & texgenT)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (gDecalInfo.faces.empty())
{
setExportError("Assertion failed");
texgenS.set(0,0,0,0);
texgenT.set(0,0,0,0);
}
Point3F n = gDecalInfo.n[0];
invMat.mulV(n);
// use first face of decal to determine texgen coords...
S32 start = gDecalInfo.faces[0].start;
Point3F v0 = Point3ToPoint3F(gDecalInfo.verts[gDecalInfo.indices[start+0]],v0);
invMat.mulP(v0);
Point3F v1 = Point3ToPoint3F(gDecalInfo.verts[gDecalInfo.indices[start+1]],v1);
invMat.mulP(v1);
Point3F v2 = Point3ToPoint3F(gDecalInfo.verts[gDecalInfo.indices[start+2]],v2);
invMat.mulP(v2);
// adjust origin to be center of face...this guarantees that we can invert matrix
Point3F center = v0+v1+v2;
center *= 1.0f/3.0f;
v0 -= center;
v1 -= center;
v2 -= center;
Point2F tv0,tv1,tv2;
tv0.x = gDecalInfo.tverts[gDecalInfo.indices[start+0]].x;
tv0.y = gDecalInfo.tverts[gDecalInfo.indices[start+0]].y;
tv1.x = gDecalInfo.tverts[gDecalInfo.indices[start+1]].x;
tv1.y = gDecalInfo.tverts[gDecalInfo.indices[start+1]].y;
tv2.x = gDecalInfo.tverts[gDecalInfo.indices[start+2]].x;
tv2.y = gDecalInfo.tverts[gDecalInfo.indices[start+2]].y;
MatrixF mat;
mat.identity();
mat.setRow(0,Point4F(v0.x,v0.y,v0.z,1));
mat.setRow(1,Point4F(v1.x,v1.y,v1.z,1));
mat.setRow(2,Point4F(v2.x,v2.y,v2.z,1));
mat.setRow(3,Point4F(n.x,n.y,n.z,0));
if (!mat.fullInverse())
{
setExportError("Assertion failed computing decal inverse transform");
return;
}
Point3F lastRow;
mat.getRow(3,&lastRow);
Point3F s,t;
mat.mulV(Point3F(tv0.x,tv1.x,tv2.x),&s);
mat.mulV(Point3F(tv0.y,tv1.y,tv2.y),&t);
F32 ws = mDot(lastRow,Point3F(tv0.x,tv1.x,tv2.x));
ws -= mDot(s,center); // adjust back for moving origin
F32 wt = mDot(lastRow,Point3F(tv0.y,tv1.y,tv2.y));
wt -= mDot(t,center); // adjust back for moving origin
printDump(-1,avar("S decal plane (%f,%f,%f,%f)\r\n",s.x,s.y,s.z,ws));
printDump(-1,avar("T decal plane (%f,%f,%f,%f)\r\n",t.x,t.y,t.z,wt));
texgenS.set(s.x,s.y,s.z,ws);
texgenT.set(t.x,t.y,t.z,wt);
}
bool ShapeMimic::getDecalTVert(U32 decalType, Point3 vert, Point3F normal, Point3 & tv)
{
Vector<Point3> & decalVerts = gDecalInfo.verts;
Vector<Point3> & decalTVerts = gDecalInfo.tverts;
Vector<TSDrawPrimitive> & decalFaces = gDecalInfo.faces;
Vector<U16> & decalIndices = gDecalInfo.indices;
Vector<Point3F> & n = gDecalInfo.n;
Vector<F32> & k = gDecalInfo.k;
for (S32 i=0; i<decalFaces.size(); i++)
{
Point3F p;
if (!getDecalProjPoint(decalType,vert,normal,i,p))
continue;
if (mFabs(mDot(p,n[i])-k[i])>0.001f)
{
// if not on plane, above computation has gone awry
setExportError("Assertion failed when adding decal (1)");
return false;
}
// is p in this face?
Point3F edge;
Point3F inVec;
U32 idx0 = decalIndices[decalFaces[i].start+0];
U32 idx1 = decalIndices[decalFaces[i].start+1];
U32 idx2 = decalIndices[decalFaces[i].start+2];
Point3F v0 = Point3ToPoint3F(decalVerts[idx0],v0);
Point3F v1 = Point3ToPoint3F(decalVerts[idx1],v1);
Point3F v2 = Point3ToPoint3F(decalVerts[idx2],v2);
edge = v1-v0;
mCross(edge,n[i],&inVec);
if (mDot(inVec,v0)>mDot(inVec,p))
// nope...
continue;
edge = v2-v1;
mCross(edge,n[i],&inVec);
if (mDot(inVec,v1)>mDot(inVec,p))
// nope...
continue;
edge = v0-v2;
mCross(edge,n[i],&inVec);
if (mDot(inVec,v2)>mDot(inVec,p))
// nope...
continue;
// ok, but does this face have a material
if (decalFaces[i].matIndex & TSDrawPrimitive::NoMaterial)
continue;
// yep...get tvert
interpolateTVert(v0,v1,v2,n[i],p,decalTVerts[idx0],decalTVerts[idx1],decalTVerts[idx2],tv);
return true;
}
if (decalType==PlaneDecal)
return getPlaneDecalTVSpecial(vert,normal,tv);
return false;
}
bool ShapeMimic::getDecalProjPoint(U32 decalType, Point3 vert, Point3F & normal, U32 decalFaceIdx, Point3F & p)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return false;
Vector<Point3F> & n = gDecalInfo.n;
Vector<F32> & k = gDecalInfo.k;
Point3F a,ray;
switch (decalType)
{
case SphereDecal:
{
ray = Point3ToPoint3F(vert,ray);
ray -= gDecalInfo.pos;
a = gDecalInfo.pos;
break;
}
case CylinderDecal:
{
// just like sphere decal, except project vert onto cylinder axis and use that as pos
Point3F pt = Point3ToPoint3F(vert,pt);
F32 t = mDot(pt,gDecalInfo.z) - mDot(gDecalInfo.pos,gDecalInfo.z);
a = gDecalInfo.z;
a *= t;
a += gDecalInfo.pos;
ray = pt;
ray -= a;
break;
}
case BoxDecal:
{
if (mDot(n[decalFaceIdx],normal) < 0.5f)
// only use closest face...
return false;
// we're on closest face (closest in the sense of pointing nearest to the same direction)
// just do plane map...
a = Point3ToPoint3F(vert,a);
ray = n[decalFaceIdx];
break;
}
case PlaneDecal:
{
a = Point3ToPoint3F(vert,a);
ray = n[decalFaceIdx];
break;
}
default:
setExportError("Unknown decal base type -- should be sphere, cylinder, box, or plane.");
return false;
};
// find point on plane j of line passing through "a" in direction of "ray"
if (mDot(ray,n[decalFaceIdx]) < 0.001f)
// parallel or behind the ray
return false;
if (mDot(normal,n[decalFaceIdx])<gDecalInfo.minCos)
// facing wrong way -- or too much the wrong way, anyway
return false;
F32 t = (k[decalFaceIdx] - mDot(a,n[decalFaceIdx])) / mDot(ray,n[decalFaceIdx]);
p = ray;
p *= t;
p += a;
return true;
}
bool ShapeMimic::getPlaneDecalTVSpecial(Point3 & vert, const Point3F & normal, Point3 & tv)
{
Vector<Point3> & decalVerts = gDecalInfo.verts;
Vector<Point3> & decalTVerts = gDecalInfo.tverts;
Vector<TSDrawPrimitive> & decalFaces = gDecalInfo.faces;
Vector<U16> & decalIndices = gDecalInfo.indices;
Vector<Point3F> & n = gDecalInfo.n;
Vector<F32> & k = gDecalInfo.k;
Point3F p = Point3ToPoint3F(vert,p);
if (n.size() && mDot(normal,n[0])<gDecalInfo.minCos)
// facing wrong way -- or too much the wrong way, anyway
return false;
// Plane is special case...if mesh projects off plane, we still generate tverts...
// we'll assume tvert scale is uniform across plane (if assumption unwarranted, artist
// needs to extend the plane). Since scale is uniform, we can just use 1st decal face...
if (decalFaces.empty())
return false;
U32 idx0 = decalIndices[decalFaces[0].start+0];
U32 idx1 = decalIndices[decalFaces[0].start+1];
U32 idx2 = decalIndices[decalFaces[0].start+2];
Point3F v0 = Point3ToPoint3F(decalVerts[idx0],v0);
Point3F v1 = Point3ToPoint3F(decalVerts[idx1],v1);
Point3F v2 = Point3ToPoint3F(decalVerts[idx2],v2);
// find edges of triangle...assume not co-linear.
Point3F edge1 = v1-v0;
Point3F edge2 = v2-v0;
// find number of steps to take along edge1 and edge2 to go from v0 to p
F32 t1,t2;
findNonOrthogonalComponents(edge1,edge2,p-v0,t1,t2);
Point3 tv0 = decalTVerts[idx0];
Point3 tv1 = decalTVerts[idx1];
Point3 tv2 = decalTVerts[idx2];
Point3 tvEdge1 = tv1-tv0;
Point3 tvEdge2 = tv2-tv0;
tv = tv0 + t1*tvEdge1 + t2*tvEdge2;
return true;
}
void ShapeMimic::findNonOrthogonalComponents(const Point3F & v1, const Point3F & v2, const Point3F & p, F32 & t1, F32 & t2)
{
// find t1, t2 s.t. p = v1 * t1 + v2 * t2
// we first find q which is the intersection of the line from the origin through v1
// and the line through p in the direction of v2
// we assume that v1, v2, and p are all on the same plane, so N is normal to that plane,
// L1 is vector which describes first line on that plane, and L2 is vector which describes
// second line on that plane.
Point3F L1, L2, N, q;
mCross(v2,v1,&N);
N.normalize(); // not needed, but keeps numbers smaller
mCross(N,v1,&L1);
mCross(N,v2,&L2);
// vec describes q: holds the dot products of q with N, L1, and L2, respectively.
Point3F vec(0, 0, mDot(L2,p));
MatrixF mat;
mat.identity();
mat.setRow(0,N);
mat.setRow(1,L1);
mat.setRow(2,L2);
mat.inverse();
// get q
mat.mulP(vec,&q);
t1 = mDot(v1,q);
t2 = mDot(v2,p-q);
F32 d1 = 1.0f / v1.len();
F32 d2 = 1.0f / v2.len();
t1 *= d1*d1;
t2 *= d2*d2;
}
void ShapeMimic::interpolateTVert(Point3F v0, Point3F v1, Point3F v2, Point3F planeNormal, Point3F & p, Point3 tv0, Point3 tv1, Point3 tv2, Point3 & tv)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// rotate points till v1 and v2 are furthest apart
Point3F leg1 = v1-v0;
Point3F leg2 = v2-v0;
Point3F c = v2-v1;
// Note: we actually NEED to use the following variables...compiler bug causes infinite loop
// if we put the mDot's directly into the "while" statement.
F32 fa = mDot(leg1,leg1);
F32 fb = mDot(leg2,leg2);
F32 fc = mDot(c,c);
while (fa>fc || fb>fc)
{
Point3F tmp = v0;
v0 = v1;
v1 = v2;
v2 = tmp;
Point3 tmp2 = tv0;
tv0 = tv1;
tv1 = tv2;
tv2 = tmp2;
leg1 = v1-v0;
leg2=v2-v0;
c=v2-v1;
fa = mDot(leg1,leg1);
fb = mDot(leg2,leg2);
fc = mDot(c,c);
}
// where does line passing through v0 and p intersect edge v1 to v2?
Point3F edgeNormal;
mCross(c,planeNormal,&edgeNormal);
Point3F vec = p-v0;
F32 t = (mDot(edgeNormal,v1) - mDot(edgeNormal,v0)) / mDot(edgeNormal,vec);
Point3F v12 = vec;
v12 *= t;
v12 += v0;
if (mFabs(mDot(edgeNormal,v1)-mDot(edgeNormal,v12)) > 0.001f)
{
// oops, v12 not on edge v1-v2
setExportError("Assertion failed when adding decal (2)");
return;
}
// how far up edge v1 to v2 is point v12
F32 k = mDot(v12-v1,c) / mDot(c,c);
if (k<0.0f || k>1.0f)
{
// oops, v12 is on line v1-v2 but is off the edge
setExportError("Assertion failed when adding decal (3)");
return;
}
// find texture coord for v12
Point3 tv12;
tv12.x = tv1.x * (1.0f - k) + tv2.x * k;
tv12.y = tv1.y * (1.0f - k) + tv2.y * k;
tv12.z = tv1.z * (1.0f - k) + tv2.z * k;
// how far along edge from v0 to v12 is p
k = mDot(v12-v0,p-v0) / mDot(v12-v0,v12-v0);
if (k<0.0f || k>1.0f)
{
// oops, p is supposed to be between v0 and v12
setExportError("Assertion failed when adding decal (4)");
return;
}
// finally, find texture coord for p
tv.x = tv0.x * (1.0f - k) + tv12.x * k;
tv.y = tv0.y * (1.0f - k) + tv12.y * k;
tv.z = tv0.z * (1.0f - k) + tv12.z * k;
}
bool intersectUnitSquare(Point3 & tv0, Point3 & tv1)
{
// return true if line from tv0 to tv1 intersects any edge of unit square
// compare with (0,0) to (0,1)
if ( (tv0.x <= 0.0f && tv1.x >= 0.0f) || (tv0.x >= 0.0f && tv1.x <= 0.0f) )
{
// tv0-tv1 crosses edge
F32 k = (0.0f - tv0.x) / (tv1.x - tv0.x);
F32 y = tv0.y + k * (tv1.y - tv0.y);
if (y>=0.0f && y<=1.0f)
return true; // we intersect
}
// compare with (0,0) to (1,0)
if ( (tv0.y <= 0.0f && tv1.y >= 0.0f) || (tv0.y >= 0.0f && tv1.y <= 0.0f) )
{
// tv0-tv1 crosses edge
F32 k = (0.0f - tv0.y) / (tv1.y - tv0.y);
F32 x = tv0.x + k * (tv1.x - tv0.x);
if (x>=0.0f && x<=1.0f)
return true; // we intersect
}
// compare with (1,0) to (1,1)
if ( (tv0.x <= 1.0f && tv1.x >= 1.0f) || (tv0.x >= 1.0f && tv1.x <= 1.0f) )
{
// tv0-tv1 crosses edge
F32 k = (1.0f - tv0.x) / (tv1.x - tv0.x);
F32 y = tv0.y + k * (tv1.y - tv0.y);
if (y>=0.0f && y<=1.0f)
return true; // we intersect
}
// compare with (0,1) to (1,1)
if ( (tv0.y <= 1.0f && tv1.y >= 1.0f) || (tv0.y >= 1.0f && tv1.y <= 1.0f) )
{
// tv0-tv1 crosses edge
F32 k = (1.0f - tv0.y) / (tv1.y - tv0.y);
F32 x = tv0.x + k * (tv1.x - tv0.x);
if (x>=0.0f && x<=1.0f)
return true; // we intersect
}
return false;
}
// returns true if face defined by v0,v1, & v2 intersects
// box [0..1,0..1]
// verts v0,v1, and v2 treated as Point2's even tho really Point3's
bool ShapeMimic::checkDecalFace(Point3 tv0, Point3 tv1, Point3 tv2)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return true;
// face intersects box iff:
// 1. any of 3 verts is in box [0..1,0..1]
// 2. (0,0) is in face (i.e. box is in face)
// 3. some edge of box intersects some edge of face
// 1. any of 3 verts is in box [0..1,0..1]
if (tv0.x >= 0.0f && tv0.x <= 1.0f && tv0.y >= 0.0f && tv0.y <= 1.0f)
return true;
// 2. (0,0) is in face (i.e. box is in face)
Point3F face[3];
Point3ToPoint3F(tv0,face[0]); face[0].z = 0;
Point3ToPoint3F(tv1,face[1]); face[1].z = 0;
Point3ToPoint3F(tv2,face[2]); face[2].z = 0;
if (pointInPoly(Point3F(0,0,0),Point3F(0,0,1),face,3))
return true;
// 3. some edge of box intersects some edge of face
if (intersectUnitSquare(tv0,tv1))
return true;
if (intersectUnitSquare(tv1,tv2))
return true;
if (intersectUnitSquare(tv2,tv0))
return true;
return false;
}
// returns true if some non-zero value is found in the filter bitmap
// within the triangle defined by v0-v1-v2 (z-coord ignored)
bool ShapeMimic::checkDecalFilter(Point3 v0, Point3 v1, Point3 v2)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return true;
if (!gDecalInfo.filter)
return true;
F32 width = gDecalInfo.filter->Width();
F32 height = gDecalInfo.filter->Height();
// rotate until min and max y is in v0 and v1
while ( (v2.y > v1.y && v2.y > v0.y) || (v2.y < v1.y && v2.y < v0.y))
{
Point3 tmp = v0;
v0 = v1;
v1 = v2;
v2 = tmp;
}
// make v0.y top, v1.y bottom -- don't care about winding direction
if (v0.y > v1.y)
{
Point3 tmp = v0;
v0 = v1;
v1 = tmp;
}
// scale v0,v1,v2 to be in the right range
v0.x *= width;
v1.x *= width;
v2.x *= width;
v0.y *= height;
v1.y *= height;
v2.y *= height;
for (F32 y = v0.y; y<=v1.y; y+=1.0f)
{
F32 x1 = v0.x + (v1.x-v0.x) * (y-v0.y)/(v1.y-v0.y);
F32 x2;
if (y<v2.y)
x2 = v0.x + (v2.x-v0.x) * (y-v0.y)/(v2.y-v0.y);
else
x2 = v2.x + (v1.x-v2.x) * (y-v2.y)/(v1.y-v2.y);
if (x2<x1)
{
F32 tmp = x1;
x1 = x2;
x2 = tmp;
}
for (F32 x=x1; x<=x2; x+=1.0f)
{
BMM_Color_64 color;
if (!gDecalInfo.filter->GetPixels(x,y,1,&color))
{
setExportError("Assertion failed during decal: trouble retrieving value from filter bitmap");
return false;
}
if (color.r || color.g || color.b)
return true;
}
}
// didn't find any non-zero polys...
return false;
}
//--------------------------------------------
// generate frame triggers -- called by generateSequences
void ShapeMimic::generateFrameTriggers(TSShape * pShape,
Interval & range,
ExporterSequenceData & seqData,
IParamBlock * pblock)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// get the sequence we're working on ... always working
// on the last sequence created
TSShape::Sequence & seq = pShape->sequences.last();
// get the range of this sequence...
F32 start = TicksToSec(range.Start());
F32 end = TicksToSec(range.End());
F32 duration;
F32 delta;
S32 numFrames;
// we re-compute this here rather than pass in the data for
// readability sake only...even though we only want duration
getSequenceTiming(seqData,&start,&end,&duration,&delta,&numFrames);
// initialize triggers...
seq.firstTrigger = pShape->triggers.size();
seq.numTriggers = 0;
if (!pblock->GetController(PB_SEQ_TRIGGERS))
return; // no triggers, rest now
// these are the triggers that get turned off by this shape..."normally" triggers
// aren't turned on/off, just on...if we are a sequence 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;
IKeyControl * kc = GetKeyControlInterface(pblock->GetController(PB_SEQ_TRIGGERS));
if (kc)
// triggers are already in order so this is a bit of overkill...but were going
// to have several tracks of triggers at one point...
addTriggersInOrder(kc,duration,pShape,offTriggers);
for (S32 i=seq.firstTrigger; i<seq.numTriggers; i++)
{
if ( (pShape->triggers[i].state&TSShape::Trigger::StateMask) & offTriggers )
// this trigger controls a state that is turned on and off...
// make sure state is reversed if played backwards
pShape->triggers[i].state |= TSShape::Trigger::InvertOnReverse;
}
}
//--------------------------------------------
// used by above method
void ShapeMimic::addTriggersInOrder(IKeyControl * kc, F32 duration, TSShape * pShape, U32 & offTriggers)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
// get the sequence we're working on ... always working
// on the last sequence created
TSShape::Sequence & seq = pShape->sequences.last();
for (S32 i=0; i<kc->GetNumKeys(); i++)
{
IBezFloatKey key;
kc->GetKey(i,&key);
if (key.val<-30 || key.val>30)
{
setExportError(avar("Sequence \"%s\" has trigger with magnitude greater than 30",pShape->getName(seq.nameIndex)));
return;
}
if ((U32)key.val==0)
{
setExportError(avar("Sequence \"%s\" has trigger with magnitude 0",pShape->getName(seq.nameIndex)));
return;
}
U32 val;
bool on;
if (key.val<0)
{
val = -key.val - 1;
on = false;
}
else
{
val = key.val - 1;
on = true;
}
// between 0 and 31
if (val<0 || val > 32)
{
setExportError("Assertion failed when processing frame triggers -- get programmer");
return;
}
S32 j;
F32 pos = TicksToSec(key.time) / duration;
for (j=seq.firstTrigger; j<pShape->triggers.size(); j++)
{
if (pos<pShape->triggers[j].pos)
break; // put new trigger here
}
pShape->triggers.insert(j);
TSShape::Trigger & trigger = pShape->triggers[j];
trigger.pos = pos;
if (on)
{
trigger.state = 1 << (U32)val;
trigger.state |= TSShape::Trigger::StateOn;
}
else
{
trigger.state = 1 << val;
offTriggers |= trigger.state; // this trigger is turned off by sequence (we assume on too)...see below for consequences
}
seq.numTriggers++;
}
}
//--------------------------------------------
// generate material list -- called by generate shape
void ShapeMimic::generateMaterialList(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
pShape->materialList = new TSMaterialList(materials.size(),
(const char**)materials.address(),
(const U32*)materialFlags.address(),
(const U32*)materialReflectionMaps.address(),
(const U32*)materialBumpMaps.address(),
(const U32*)materialDetailMaps.address(),
(const F32*)materialDetailScales.address(),
(const F32*)materialEmapAmounts.address());
}
//--------------------------------------------
// generate skins -- called by generate shape
void ShapeMimic::generateSkins(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) 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
{
// Note: using code block to scope index variables for convenience
S32 i,j,k;
j=0;
for (i=0; i<pShape->details.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) pShape->details[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() && !allowUnusedMeshes)
{
setExportError("Unused skins were found.");
return;
}
for (;j<skins.size();j++)
{
delete skins[j];
skins.erase(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)
{
setExportError("Assertion failed generating skins");
return;
}
// this mesh is a skin...set it up (it's already been created)
TSSkinMesh * skinMesh = (TSSkinMesh*)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->numMatFrames = 1;
skinMesh->vertsPerFrame = skin->verts.size();
skinMesh->primitives = skin->faces;
skinMesh->indices = skin->indices;
skinMesh->mergeIndices = skin->mergeIndices;
skinMesh->initialNorms = skin->normals;
skinMesh->tverts.setSize(skin->tverts.size());
S32 j,k;
for (j=0; j<skin->tverts.size(); j++)
skinMesh->tverts[j].set(skin->tverts[j].x,skin->tverts[j].y);
skinMesh->initialVerts.setSize(skin->verts.size());
for (j=0; j<skin->verts.size(); j++)
Point3ToPoint3F(skin->verts[j],skinMesh->initialVerts[j]);
// how many verts in the next smallest version of us
S32 numChildVerts = 0;
k = 1;
while (meshNum+k<objectList[i]->numDetails)
{
if (objectList[i]->details[meshNum+k].mesh)
{
numChildVerts = objectList[i]->details[meshNum+k].mesh->numVerts;
break;
}
k++;
}
// find merge indices
findMergeIndices(objectList[i]->details[meshNum].mesh,
objectList[i]->details[meshNum].mesh->objectOffset,
skinMesh->primitives,
skinMesh->initialVerts,
skinMesh->initialNorms,
skinMesh->tverts,
skinMesh->indices,
skinMesh->mergeIndices,
objectList[i]->details[meshNum].mesh->skinMimic->smoothingGroups,
objectList[i]->details[meshNum].mesh->skinMimic->vertId,
numChildVerts);
skinMesh->vertsPerFrame = skinMesh->initialVerts.size();
// 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
for (j=0; j<skin->bones.size(); j++)
{
// find node index
for (k=0; k<nodes.size(); k++)
if (nodes[k]->maxNode == skin->bones[j])
break;
if (k==nodes.size())
{
setExportError("Error: bone missing from shape");
return;
}
skinMesh->nodeIndex.push_back(k);
Matrix3 boundsTransform = boundsNode->GetNodeTM( DEFAULT_TIME );
zapScale(boundsTransform);
Matrix3 invBoneTransform = skin->bones[j]->GetNodeTM( DEFAULT_TIME );
zapScale(invBoneTransform);
invBoneTransform = Inverse(invBoneTransform);
Matrix3 initTransform = boundsTransform;
initTransform *= invBoneTransform;
MatrixF matf;
F32 * f = (F32*)matf;
f[0 * 4 + 0] = initTransform.GetAddr()[0][0];
f[0 * 4 + 1] = initTransform.GetAddr()[1][0];
f[0 * 4 + 2] = initTransform.GetAddr()[2][0];
f[0 * 4 + 3] = initTransform.GetAddr()[3][0];
f[1 * 4 + 0] = initTransform.GetAddr()[0][1];
f[1 * 4 + 1] = initTransform.GetAddr()[1][1];
f[1 * 4 + 2] = initTransform.GetAddr()[2][1];
f[1 * 4 + 3] = initTransform.GetAddr()[3][1];
f[2 * 4 + 0] = initTransform.GetAddr()[0][2];
f[2 * 4 + 1] = initTransform.GetAddr()[1][2];
f[2 * 4 + 2] = initTransform.GetAddr()[2][2];
f[2 * 4 + 3] = initTransform.GetAddr()[3][2];
f[3 * 4 + 0] = 0.0f;
f[3 * 4 + 1] = 0.0f;
f[3 * 4 + 2] = 0.0f;
f[3 * 4 + 3] = 1.0f;
skinMesh->initialTransforms.push_back(matf);
}
printDump(PDObjectStateDetails|PDPass2,avar("\r\nGenerating skin \"%s\".\r\n",skin->skinNode->GetName()));
// push all vertex, bone, weight triples
printDump(PDObjectStateDetails,"\r\nVertex, bone, & weight data:\r\n\r\n");
for (j=0; j<skinMesh->initialVerts.size(); j++)
{
printDump(PDObjectStateDetails,avar("Vertex %i\r\n",j));
for (k=0; k<skin->bones.size(); k++)
{
if ((*skin->weights[k])[j]>=weightThreshhold)
{
skinMesh->vertexIndex.push_back(j);
skinMesh->boneIndex.push_back(k);
skinMesh->weight.push_back((*skin->weights[k])[j]);
printDump(PDObjectStateDetails,avar(" Bone %i, weight = %5.3f, name = \"%s\"\r\n",
skinMesh->boneIndex.last(),skinMesh->weight.last(),skin->bones[k]->GetName()));
}
}
}
}
}
}
//--------------------------------------------
// various routines used by generate faces
void ShapeMimic::splitFaceX(TSDrawPrimitive & face, TSDrawPrimitive & faceA, TSDrawPrimitive & faceB,
Vector<Point3> & verts, Vector<Point3> & tverts, Vector<U16> & indices,
Vector<bool> & flipX, Vector<bool> & flipY, F32 splitAt,
Vector<U32> & smooth, Vector<U32> * vertId)
{
// find the odd man out (odd vertex out, really)
S32 code = 0, rogue;
if (tverts[indices[face.start+0]].x < splitAt)
code |= 1;
if (tverts[indices[face.start+1]].x < splitAt)
code |= 2;
if (tverts[indices[face.start+2]].x < splitAt)
code |= 4;
switch (code)
{
case 1:
case 6: rogue = 0; break;
case 2:
case 5: rogue = 1; break;
case 4:
case 3: rogue = 2; break;
case 0:
case 7: setExportError("splitFaceX: not off-tile, get programmer."); return;
}
// idx0,1,2 used to walk around triangle clock-wise starting at odd-vertex out (rogue vertex)
// note: a little confusing because idx0,1,2 are offsets from face.start in indices array.
// indices[face.start+idx0], for example, is the index into vert and tvert array of the rogue vertex...
// indices[face.start+idx1] is index for next vertex around the face clockwise...
S32 idx0 = rogue;
S32 idx1 = (rogue+1) % 3;
S32 idx2 = (rogue+2) % 3;
// the vert and tvert indices for the 3 vertices of the face
S32 v0 = indices[face.start+idx0];
S32 v1 = indices[face.start+idx1];
S32 v2 = indices[face.start+idx2];
// fill out the indices for the new faces and the split face
indices[face.start+idx1] = verts.size();
indices[face.start+idx2] = verts.size()+1;
faceA.start = indices.size();
faceA.numElements = 3;
indices.push_back(verts.size()+2);
indices.push_back(v1);
indices.push_back(verts.size()+3);
faceB.start = indices.size();
faceB.numElements = 3;
indices.push_back(v1);
indices.push_back(v2);
indices.push_back(verts.size()+3);
// find out the actual values of the 2 new vertices we've added
F32 k;
k = (splitAt - tverts[v0].x) / (tverts[v1].x - tverts[v0].x);
tverts.increment();
tverts.last() = tverts[v1] - tverts[v0];
tverts.last() *= k;
tverts.last() += tverts[v0];
tverts.last().x = splitAt; // make sure exactly this
verts.increment();
verts.last() = verts[v1] - verts[v0];
verts.last() *= k;
verts.last() += verts[v0];
k = (splitAt - tverts[v2].x) / (tverts[v0].x - tverts[v2].x);
tverts.increment();
tverts.last() = tverts[v0] - tverts[v2];
tverts.last() *= k;
tverts.last() += tverts[v2];
tverts.last().x = splitAt; // make sure exactly this
verts.increment();
verts.last() = verts[v0] - verts[v2];
verts.last() *= k;
verts.last() += verts[v2];
// duplicate the new tverts for faceA and faceB
// necessary because they will be flipped (x = 1-x)
// from face coords
tverts.increment();
tverts.last() = tverts[tverts.size()-3];
tverts.increment();
tverts.last() = tverts[tverts.size()-3];
verts.increment();
verts.last() = verts[verts.size()-3];
verts.increment();
verts.last() = verts[verts.size()-3];
// flip coords? if we don't wrap we don't flip. o.w., which verts to flip depends on
// whether rogue face was on-tile or off-tile.
if (!(materialFlags[face.matIndex & TSDrawPrimitive::MaterialMask] & TSMaterialList::S_Wrap))
{
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
}
else if (tverts[v0].x >=0.0f && tverts[v0].x <= 1.0f)
{
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = true;
flipX.increment();
flipX.last() = true;
}
else
{
flipX.increment();
flipX.last() = true;
flipX.increment();
flipX.last() = true;
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
}
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
// added 4 verts, all have same smoothing group...
S32 smooth0 = smooth[v0];
smooth.push_back(smooth0);
smooth.push_back(smooth0);
smooth.push_back(smooth0);
smooth.push_back(smooth0);
if (vertId)
{
S32 vid0 = (*vertId)[v0];
vertId->push_back(vid0);
vertId->push_back(vid0);
vertId->push_back(vid0);
vertId->push_back(vid0);
}
// these faces need to have the same material indices...
faceA.matIndex = faceB.matIndex = face.matIndex;
}
void ShapeMimic::splitFaceY(TSDrawPrimitive & face, TSDrawPrimitive & faceA, TSDrawPrimitive & faceB,
Vector<Point3> & verts, Vector<Point3> & tverts, Vector<U16> & indices,
Vector<bool> & flipX, Vector<bool> & flipY, F32 splitAt,
Vector<U32> & smooth, Vector<U32> * vertId)
{
// find the odd man out (odd vertex out, really)
S32 code = 0, rogue;
if (tverts[indices[face.start+0]].y < splitAt)
code |= 1;
if (tverts[indices[face.start+1]].y < splitAt)
code |= 2;
if (tverts[indices[face.start+2]].y < splitAt)
code |= 4;
switch (code)
{
case 1:
case 6: rogue = 0; break;
case 2:
case 5: rogue = 1; break;
case 4:
case 3: rogue = 2; break;
case 0:
case 7: setExportError("splitFaceY: not off-tile, get programmer."); return;
}
// indices used to walk around triangle clock-wise starting at odd-vertex out (rogue vertex)
// note: a little confusing because these are vertices to face.v[] and face.tv[] which are
// arrays of 3 indices to the verts and tverts of the triangle...
S32 idx0 = rogue;
S32 idx1 = (rogue+1) % 3;
S32 idx2 = (rogue+2) % 3;
// the verts and tverts for these indices
S32 v0 = indices[face.start+idx0];
S32 v1 = indices[face.start+idx1];
S32 v2 = indices[face.start+idx2];
// fill out the indices for the new faces and the split face
indices[face.start+idx1] = verts.size();
indices[face.start+idx2] = verts.size() + 1;
faceA.start = indices.size();
faceA.numElements = 3;
indices.push_back(verts.size()+2);
indices.push_back(v1);
indices.push_back(verts.size()+3);
faceB.start = indices.size();
faceB.numElements = 3;
indices.push_back(v1);
indices.push_back(v2);
indices.push_back(verts.size()+3);
// find out the actual values of the 2 new vertices we've added
F32 k;
k = (splitAt - tverts[v0].y) / (tverts[v1].y - tverts[v0].y);
tverts.increment();
tverts.last() = tverts[v1] - tverts[v0];
tverts.last() *= k;
tverts.last() += tverts[v0];
tverts.last().y = splitAt; // make sure exactly this
verts.increment();
verts.last() = verts[v1] - verts[v0];
verts.last() *= k;
verts.last() += verts[v0];
k = (splitAt - tverts[v2].y) / (tverts[v0].y - tverts[v2].y);
tverts.increment();
tverts.last() = tverts[v0] - tverts[v2];
tverts.last() *= k;
tverts.last() += tverts[v2];
tverts.last().y = splitAt; // make sure exactly this
verts.increment();
verts.last() = verts[v0] - verts[v2];
verts.last() *= k;
verts.last() += verts[v2];
// duplicate the new tverts for faceA and faceB
// necessary because they will be flipped (y = 1-y)
// from face coords
tverts.increment();
tverts.last() = tverts[tverts.size()-3];
tverts.increment();
tverts.last() = tverts[tverts.size()-3];
verts.increment();
verts.last() = verts[verts.size()-3];
verts.increment();
verts.last() = verts[verts.size()-3];
// flip coords? if we don't wrap we don't flip. o.w., which verts to flip depends on
// whether rogue face was on-tile or off-tile.
if (!(materialFlags[face.matIndex & TSDrawPrimitive::MaterialMask] & TSMaterialList::T_Wrap))
{
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
}
else if (tverts[v0].y >=0.0f && tverts[v0].y <= 1.0f)
{
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = true;
flipY.increment();
flipY.last() = true;
}
else
{
flipY.increment();
flipY.last() = true;
flipY.increment();
flipY.last() = true;
flipY.increment();
flipY.last() = false;
flipY.increment();
flipY.last() = false;
}
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
flipX.increment();
flipX.last() = false;
// added 4 verts, all have same smoothing group...
S32 smooth0 = smooth[v0];
smooth.push_back(smooth0);
smooth.push_back(smooth0);
smooth.push_back(smooth0);
smooth.push_back(smooth0);
if (vertId)
{
S32 vid0 = (*vertId)[v0];
vertId->push_back(vid0);
vertId->push_back(vid0);
vertId->push_back(vid0);
vertId->push_back(vid0);
}
// these faces need to have the same material indices...
faceA.matIndex = faceB.matIndex = face.matIndex;
}
void ShapeMimic::handleCropAndPlace(Point3 & tv, CropInfo & cropInfo)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (cropInfo.place)
{
tv.x = (tv.x - cropInfo.uOffset) / cropInfo.uWidth;
tv.y = (tv.y - cropInfo.vOffset) / cropInfo.vHeight;
return;
}
if (cropInfo.crop)
{
// if off-tile and we wrap, then adjust coords to be positive
if (cropInfo.uWrap)
{
while (cropInfo.cropLeft && tv.x<0.0f) tv.x += 1.0f;
while (cropInfo.cropRight && tv.x>1.0f) tv.x -= 1.0f;
}
else
{
if (cropInfo.cropLeft && tv.x<0.0f) tv.x = 0.0f;
if (cropInfo.cropRight && tv.x>1.0f) tv.x = 1.0f;
}
if (cropInfo.vWrap)
{
while (cropInfo.cropTop && tv.y<0.0f) tv.y += 1.0f;
while (cropInfo.cropBottom && tv.y>1.0f) tv.y -= 1.0f;
}
else
{
if (cropInfo.cropTop && tv.y<0.0f) tv.y = 0.0f;
if (cropInfo.cropBottom && tv.y>1.0f) tv.y = 1.0f;
}
// if on-tile, perform cropping here
// if we're off-tile, don't crop use these coords
// Note: in Max these off-tile faces will be textured
// according to the border of the cropped section of the bmp,
// whereas in game engine they will be textured according
// to greater bmp's border area...live with it
if (tv.x>=0.0f && tv.x<=1.0f)
tv.x = tv.x * cropInfo.uWidth + cropInfo.uOffset;
if (tv.y>=0.0f && tv.y<=1.0f)
tv.y = tv.y * cropInfo.vHeight + cropInfo.vOffset;
}
}
//--------------------------------------------
// make lower detail levels use higher detail
// levels verts, when possible
// we assume lower detail levels set up already
// (but by lower detail we mean higher dl # )
void ShapeMimic::shareVerts(ObjectMimic * om, S32 dl, TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
MultiResMimic * mrm = getMultiRes(om->details[dl].mesh->pNode);
if (!om->details[dl].mesh || !mrm)
return;
S32 i,j,k;
TSMesh * mesh = om->details[dl].mesh->tsMesh;
Vector<U32> & smooth = om->details[dl].mesh->smoothingGroups;
Vector<U32> remap;
remap.setSize(mesh->verts.size());
for (i=0;i<remap.size();i++)
remap[i]=i;
for (i=dl+1; i<om->numDetails; i++)
{
if (mrm!=getMultiRes(om->details[i].mesh->pNode))
continue;
TSMesh * mesh2 = om->details[i].mesh->tsMesh;
Vector<U32> & smooth2 = om->details[i].mesh->smoothingGroups;
if (mrm->totalVerts) // will be zero if not really a multires object... (hack,hack)
{
// ok, detail dl is generated via same multi-res object as detail i
// share verts...
S32 count=0;
for (j=0; j<mesh->verts.size(); j++)
{
for (k=0; k<mesh2->verts.size(); k++)
{
S32 frame1 = j/mesh->vertsPerFrame;
S32 frame2 = k/mesh2->vertsPerFrame;
if (!vertexSame(mesh->verts[j],mesh2->verts[k],mesh->tverts[j],mesh2->tverts[k],smooth[j],smooth2[k],-1,-1,NULL))
// different...
continue;
if (frame1!=frame2)
// different frames...
continue;
// same vert
remap[j] = k;
break;
}
if (k==mesh2->verts.size())
remap[j] = mesh2->verts.size() + count++;
}
}
else
{
// this is a faux multi-res node...we just want to copy verts between detail levels
// and this is already done (since generated from same mesh). But in order to share
// the following code, we just need to set up remap to be identity (already done)
// So at this point, just check to make sure we are legit (mesh and mesh2 are the same)
if (mesh->vertsPerFrame!=mesh2->vertsPerFrame || mesh->verts.size()!=mesh2->verts.size())
{
setExportError("Assertion failed sharing verts");
return;
}
}
// at this point we have remapped all shared verts into mesh2
// and we have a count of how many verts are unique to this mesh (count)
// we've also shifted remap down to account for verts moving to mesh2
Vector<Point3F> newVerts;
Vector<Point2F> newTVerts;
Vector<Point3F> newNorms;
Vector<U32> newSmooth;
Vector<U16> newMerge;
newVerts = mesh2->verts;
newTVerts = mesh2->tverts;
newNorms = mesh2->norms;
newSmooth = smooth2;
newMerge.setSize(mesh2->mergeIndices.size());
for (j=0; j<newMerge.size(); j++) // unlike other vectors, we don't share these with our child
newMerge[j]=j;
for (j=0; j<mesh->verts.size(); j++)
{
if (remap[j]>=mesh2->verts.size())
{
newVerts.push_back(mesh->verts[j]);
newTVerts.push_back(mesh->tverts[j]);
newNorms.push_back(mesh->norms[j]);
newSmooth.push_back(smooth[j]);
newMerge.push_back(remap[mesh->mergeIndices[j]]);
}
else
// normals on this detail level over-rule normals from lower detail levels
newNorms[remap[j]] = mesh->norms[j];
}
// adjust indices on mesh
for (j=0; j<mesh->indices.size(); j++)
mesh->indices[j] = remap[mesh->indices[j]];
// fixup remap on mesh
for (j=0; j<om->details[dl].mesh->remap.size(); j++)
{
U32 & idx = om->details[dl].mesh->remap[j];
idx = remap[idx];
}
// now equate verts on mesh and mesh2
mesh->verts = newVerts;
mesh->tverts = newTVerts;
mesh->norms = newNorms;
mesh->mergeIndices = newMerge;
mesh->vertsPerFrame = mesh->verts.size() / mesh->numFrames; // keep up to date...
smooth = newSmooth;
/*
for (j=dl+1; j<om->numDetails; j++)
{
if (om->details[j].mesh->tsMesh && getMultiRes(om->details[dl].mesh->pNode)==getMultiRes(om->details[j].mesh->pNode))
{
// who the hell are we? Find first mesh in shapes lists of meshes
S32 meshIdx =0;
while (meshIdx < pShape->meshes.size() && pShape->meshes[meshIdx]!=om->details[dl].mesh->tsMesh)
meshIdx++;
if (meshIdx==pShape->meshes.size())
{
setExportError("Assertion failed");
return;
}
om->details[j].mesh->tsMesh->parentMesh = meshIdx;
}
}
*/
// the above commented out code makes the highest detail the parent mesh of all lower detail version
// the code below creates a chain of inheritance instead
// who the hell are we? Find first mesh in shapes lists of meshes
S32 meshIdx =0;
while (meshIdx < pShape->meshes.size() && pShape->meshes[meshIdx]!=om->details[dl].mesh->tsMesh)
meshIdx++;
om->details[i].mesh->tsMesh->parentMesh = meshIdx;
if (meshIdx==pShape->meshes.size())
{
setExportError("Assertion failed");
return;
}
break;
}
}
void ShapeMimic::shareVerts(SkinMimic * sm, Vector<U32> & originalRemap, TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
MultiResMimic * mrm = getMultiRes(sm->skinNode);
if (!sm->skinMesh || !mrm)
return;
S32 i,j,k;
TSSkinMesh * mesh = sm->skinMesh;
Vector<U32> & smooth = sm->smoothingGroups;
Vector<U32> remap;
remap.setSize(mesh->verts.size());
for (i=0;i<remap.size();i++)
remap[i]=i;
// find next lowest detail level (next lowest in detail, next highest in number)
SkinMimic * sm2 = NULL;
for (i=0; i<skins.size(); i++)
{
if (skins[i]->detailSize < sm->detailSize && skins[i]->skinNode && mrm==getMultiRes(skins[i]->skinNode))
{
sm2 = skins[i];
sm2->skinMesh->parentMesh = sm->meshNum;
break;
}
}
if (!sm2)
return;
// ok, sm and sm2 are generated via same multi-res object
// share verts...
TSSkinMesh * mesh2 = sm2->skinMesh;
Vector<U32> & smooth2 = sm2->smoothingGroups;
S32 count=0;
for (j=0; j<mesh->verts.size(); j++)
{
for (k=0; k<mesh2->initialVerts.size(); k++)
{
if (!vertexSame(mesh->verts[j],mesh2->initialVerts[k],mesh->tverts[j],mesh2->tverts[k],smooth[j],smooth2[k],-1,-1,NULL))
// different...
continue;
if (sm->vertId[j]!=sm2->vertId[k])
// different vert ids...
continue;
// same vert
remap[j] = k;
break;
}
if (k==mesh2->initialVerts.size())
remap[j] = mesh2->initialVerts.size() + count++;
}
// at this point we have remapped all shared verts into mesh2
// and we have a count of how many verts are unique to this mesh (count)
// we've also shifted remap down to account for verts moving to mesh2
Vector<Point3F> newVerts;
Vector<Point2F> newTVerts;
Vector<Point3F> newNorms;
Vector<U32> newSmooth;
Vector<U32> newVertId;
Vector<U16> newMerge;
newVerts = mesh2->initialVerts;
newTVerts = mesh2->tverts;
newNorms = mesh2->initialNorms;
newSmooth = smooth2;
newVertId = sm2->vertId;
newMerge.setSize(mesh2->mergeIndices.size());
for (j=0; j<newMerge.size(); j++) // unlike other vectors, we don't share these with our child
newMerge[j]=j;
for (j=0; j<mesh->verts.size(); j++)
{
if (remap[j]>=mesh2->initialVerts.size())
{
newVerts.push_back(mesh->verts[j]);
newTVerts.push_back(mesh->tverts[j]);
newNorms.push_back(mesh->norms[j]);
newMerge.push_back(remap[mesh->mergeIndices[j]]);
newSmooth.push_back(smooth[j]);
newVertId.push_back(sm->vertId[j]);
}
else
// normals on this detail level over-rule normals from lower detail levels
newNorms[remap[j]] = mesh->norms[j];
}
// adjust indices on mesh
for (j=0; j<mesh->indices.size(); j++)
mesh->indices[j] = remap[mesh->indices[j]];
// fix up originalRemap
for (i=0; i<originalRemap.size(); i++)
originalRemap[i] = remap[originalRemap[i]];
// now equate verts on mesh and mesh2
mesh->verts = newVerts;
mesh->tverts = newTVerts;
mesh->norms = newNorms;
mesh->mergeIndices = newMerge;
mesh->vertsPerFrame = mesh->verts.size();
smooth = newSmooth;
sm->vertId = newVertId;
}
void ShapeMimic::shareBones(SkinMimic * sm, TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
MultiResMimic * mrm = getMultiRes(sm->skinNode);
if (!sm->skinMesh || !mrm)
return;
S32 i,j,k;
TSSkinMesh * mesh = sm->skinMesh;
// find next lowest detail level (next lowest in detail, next highest in number)
SkinMimic * sm2 = NULL;
for (i=0; i<skins.size(); i++)
{
if (skins[i]->detailSize < sm->detailSize && skins[i]->skinNode && mrm==getMultiRes(skins[i]->skinNode))
{
if (skins[i]->skinMesh->parentMesh != sm->meshNum)
{
// this was established in shareVerts...should still be true
setExportError("Assertion failed");
return;
}
sm2 = skins[i];
break;
}
}
if (!sm2)
return;
// ok, sm and sm2 are generated via same multi-res object
TSSkinMesh * mesh2 = sm2->skinMesh;
// make sure mesh2 isn't adding any bones...
if (mesh2->nodeIndex.size() >mesh->nodeIndex.size())
{
setExportError("Assertion failed: Lower skin detail level has more bones than higher one does!!!");
return;
}
for (k=0; k<mesh2->nodeIndex.size(); k++)
{
for (j=0; j<mesh->nodeIndex.size(); j++)
{
if (mesh->nodeIndex[j]==mesh2->nodeIndex[k])
break;
}
if (j==mesh->nodeIndex.size())
{
setExportError("Assertion failed: Lower skin detail level is adding bone!!!");
return;
}
}
// remap nodeIndex so that mesh2's nodeIndex array is first part of mesh1's
S32 count=0;
Vector<U32> remap;
remap.setSize(mesh->nodeIndex.size());
for (j=0; j<mesh->nodeIndex.size(); j++)
{
for (k=0; k<mesh2->nodeIndex.size(); k++)
{
if (mesh->nodeIndex[j]!=mesh2->nodeIndex[k])
// different...
continue;
// same node
remap[j] = k;
break;
}
if (k==mesh2->nodeIndex.size())
remap[j] = mesh2->nodeIndex.size() + count++;
}
// at this point, we have constructed remap so that node in nodeIndex vector
// of mesh that also occur in child mesh will occur first (and in the order
// it occurs in child mesh). Other nodes occur afterwards.
// Let's do some remapping:
Vector<S32> newNodeIndex;
newNodeIndex.setSize(mesh->nodeIndex.size());
Vector<MatrixF> newIMats;
newIMats.setSize(mesh->initialTransforms.size());
for (j=0;j<remap.size();j++)
{
newNodeIndex[remap[j]]=mesh->nodeIndex[j];
newIMats[remap[j]]=mesh->initialTransforms[j];
}
mesh->nodeIndex = newNodeIndex;
mesh->initialTransforms = newIMats;
// remap boneIndex (which are indices into nodeIndex list, which are indices into shape transform list)
for (j=0; j<mesh->boneIndex.size(); j++)
mesh->boneIndex[j] = remap[mesh->boneIndex[j]];
// we don't necessarily have weighting for all vertices, because lower detail level
// may have added a vertex (because of tvert/vert combination issues in max versus openGL and
// every other graphics standard). Look for such missing vertex,bone,weight combos and add them
// here
for (j=0; j<mesh->vertexIndex.size() && j<mesh2->vertexIndex.size(); j++)
{
if (mesh->vertexIndex[j]==mesh2->vertexIndex[j])
// all is cool, keep going
continue;
while (j<mesh->vertexIndex.size() && j<mesh2->vertexIndex.size() && mesh->vertexIndex[j]!=mesh2->vertexIndex[j])
{
mesh->vertexIndex.insert(j);
mesh->vertexIndex[j]=mesh2->vertexIndex[j];
mesh->boneIndex.insert(j);
mesh->boneIndex[j]=mesh2->boneIndex[j];
mesh->weight.insert(j);
mesh->weight[j]=mesh2->weight[j];
j++;
}
}
}
//--------------------------------------------
// find which verts merge with which verts
void ShapeMimic::findMergeIndices(MeshMimic * meshMimic,
Matrix3 & objectOffset3,
const Vector<TSDrawPrimitive> & faces,
Vector<Point3F> & verts,
Vector<Point3F> & norms,
Vector<Point2F> & tverts,
const Vector<U16> & indices,
Vector<U16> & mergeIndices,
Vector<U32> & smooth,
Vector<U32> & vertId,
S32 numChildVerts)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
MultiResMimic * mrm = getMultiRes(meshMimic->pNode);
if (!mrm || mrm->totalVerts==0)
return;
// we want to manipulate these but not change incoming data (other vectors will be changed)
Vector<TSDrawPrimitive> workFaces = faces;
Vector<U16> workIndices = indices;
S32 i,j,v;
mergeIndices.setSize(verts.size());
for (i=0;i<mergeIndices.size();i++)
mergeIndices[i]=i;
Vector<S32> mergeOrder;
Vector<S32> mergeToId;
// merge from and mergeTo vectors are in object space before applying object offset
// apply object offset to those points
Vector<Point3F> mergeFrom, mergeTo;
mergeFrom.setSize(mrm->mergeFrom.size());
mergeTo.setSize(mrm->mergeTo.size());
MatrixF objectOffset;
convertToMatrixF(objectOffset3,objectOffset);
for (i=0; i<mrm->mergeFrom.size(); i++)
{
objectOffset.mulP(mrm->mergeFrom[i],&mergeFrom[i]);
objectOffset.mulP(mrm->mergeTo[i],&mergeTo[i]);
}
mrm=NULL; // don't want to use anything in this for the rest of this method...crash if we accidentally do
// determine merge order of each vert
// this also uniquely id's each vert location
for (i=0; i<verts.size(); i++)
{
// find point to merge this vertex to
S32 idx = findClosestMatch(verts[i],mergeFrom);
if (idx<0 || mDot(verts[i]-mergeFrom[idx],verts[i]-mergeFrom[idx])>0.0001f)
{
setExportError(avar("Assertion error during vertex merge on mesh \"%s\"",meshMimic->pNode->GetName()));
return;
}
mergeOrder.push_back(idx);
}
// do same thing to mergeTo list...the point
// of this is to give each target a unique id
for (i=0; i<mergeTo.size(); i++)
{
S32 idx = findClosestMatch(mergeTo[i],mergeFrom);
if (idx<0 || mDot(mergeTo[i]-mergeFrom[idx],mergeTo[i]-mergeFrom[idx])>0.0001f || idx<i)
{
setExportError("Assertion error during vertex merge");
return;
}
if (idx < mergeToId.size())
{
setExportError("Assertion error during vertex merge");
return;
}
mergeToId.push_back(idx);
}
// check to make sure mergeFrom has no dups
for (i=0; i<mergeTo.size(); i++)
{
if (i!=findClosestMatch(mergeFrom[i],mergeFrom))
{
setExportError(avar("Assertion error during vertex merge: must weld verts on mesh \"%s\".",meshMimic->pNode->GetName()));
return;
}
}
// we now have easy access to the location in space
// that each vertex merges to and the order of merge.
// but what happens to the other attributes depends
// on the topology of the mesh. The key is that we
// want to maintain continuity across faces and also
// to maintain dis-continuous borders. To achieve this
// we follow the rules below. For these rules we consider
// each instance of a vertex as a unique vertex. So even
// if two faces have a vertex with the same location,
// tvert and material, they will be considered different
// vertices. (Note: this also turns out to be convenient
// here because we have not merged equivalent vertices
// so there are actually no vertices shared between faces).
// The rules:
// 1. if a vertex is to merge to a location occupied
// by one of the other vertices of the face, then
// the other attributes of the vertex will interpolate
// to the values of the other vertex.
// 2. if a vertex is to merge to a location occupied
// by a vertex of another face, and that face has
// a vertex with the same location, tvert, material, etc,
// as our first vertex, then the vertex parameters
// will once again interpolate to the values found on
// the vertex in the target location.
// 3. if neither of the above are true for any face,
// then the other parameters will not change as the
// position interpolates to the target position.
// merge verts in order, adding mergeIndices and new vertex targets as we go
if (numChildVerts==0)
numChildVerts=mergeFrom.size();
for (i=0; i<mergeFrom.size()-numChildVerts; i++)
{
// keep track of next vert index before we start this round -- see below
S32 startVerts = verts.size();
// go through faces and find all verts that need to be merged this round
for (j=0; j<workFaces.size(); j++)
{
TSDrawPrimitive & face = workFaces[j];
if ((face.matIndex & TSDrawPrimitive::TypeMask) != TSDrawPrimitive::Triangles)
{
setExportError("Assertion failed during vertex merge");
return;
}
// find vertex that needs to be merged this round
for (v=0; v<3; v++)
{
// merge to a vert on this face?
if (mergeOrder[workIndices[face.start+v]]==i)
// bingo
addMergeTargetAndMerge(workFaces,j,v,workIndices,mergeIndices,verts,tverts,norms,smooth,vertId,mergeOrder,mergeToId,mergeTo);
}
}
// now that we've merged all the verts to their targets, update indices to reflect new location
// -- repeat the above in same order so that we can use "startVerts" to figure out new indices
for (j=0; j<workFaces.size(); j++)
{
TSDrawPrimitive & face = workFaces[j];
if ((face.matIndex & TSDrawPrimitive::TypeMask) != TSDrawPrimitive::Triangles)
{
setExportError("Assertion failed during vertex merge");
return;
}
// find vertex that needs to be merged this round
for (v=0; v<3; v++)
{
// merge to a vert on this face?
if (mergeOrder[workIndices[face.start+v]]==i)
workIndices[face.start+v]=startVerts++;
}
}
}
// check to make sure we always merge to the right place
for (i=0; i<verts.size(); i++)
{
if (mergeIndices[i]==i)
// this is ok...if a face is terminated, we don't worry about the isolated verts...
continue;
S32 idx = findClosestMatch(verts[i],mergeFrom);
F32 dot = mDot(verts[mergeIndices[i]]-mergeTo[idx],verts[mergeIndices[i]]-mergeTo[idx]);
if (dot>0.001f)
{
setExportError("Assertion failed");
return;
}
}
// slightly different form of the above...here we make sure if one
// point merges someplace, another equivalent point does too
for (i=0; i<mergeOrder.size(); i++)
{
for (j=i+1; j<mergeOrder.size(); j++)
if (mergeOrder[i]==mergeOrder[j])
{
Point3F delta = verts[mergeIndices[i]]-verts[mergeIndices[j]];
if (mDot(delta,delta)>0.0001f)
{
setExportError("Assertion failed");
return;
}
}
}
}
void ShapeMimic::addMergeTargetAndMerge(Vector<TSDrawPrimitive> & faces, S32 & faceNum, S32 & v,
Vector<U16> & indices, Vector<U16> & mergeIndices,
Vector<Point3F> & verts, Vector<Point2F> & tverts, Vector<Point3F> & norms,
Vector<U32> & smooth, Vector<U32> & vertId,
Vector<S32> & mergeOrder, Vector<S32> & mergeToId, Vector<Point3F> & mergeTarget)
{
// get id for vertex target
TSDrawPrimitive & face = faces[faceNum];
S32 start = face.start;
S32 vertMergeOrder = mergeOrder[indices[start+v]];
S32 targetId = mergeToId[vertMergeOrder];
if (targetId<vertMergeOrder)
setExportError("Assertion error");
Point3F vertTarget;
Point3F normTarget;
Point2F tvertTarget;
U32 vid;
S32 idx,sm;
S32 i,j,v2;
// this is where the new vert will go, no matter how we get the info
mergeIndices[indices[face.start+v]] = verts.size();
mergeIndices.push_back(mergeIndices.size());
mergeOrder.push_back(targetId);
// search this face for the target location
bool found = false;
for (i=1; i<3; i++)
{
if (targetId == mergeOrder[indices[start + ((v+i)%3)]])
{
// merge to vertex on same face
idx = indices[start+((v+i)%3)];
vertTarget = verts[idx];
tvertTarget = tverts[idx];
normTarget = norms[idx];
vid = vertId[idx];
sm = smooth[idx];
verts.push_back(vertTarget);
tverts.push_back(tvertTarget);
norms.push_back(normTarget);
vertId.push_back(vid);
smooth.push_back(sm);
{
F32 dot = mDot(verts.last()-mergeTarget[vertMergeOrder],verts.last()-mergeTarget[vertMergeOrder]);
if (dot>0.001f)
setExportError("Assertion failed");
}
return;
}
}
// more complicated...try to find another face with a vert like us
for (i=0; i<faces.size(); i++)
{
if (i==faceNum)
continue;
TSDrawPrimitive & face2 = faces[i];
if (face.matIndex != face2.matIndex)
continue;
for (v2=0; v2<3; v2++)
{
if (mergeOrder[indices[face2.start+v2]]!=vertMergeOrder)
continue;
if (vertId[indices[face.start+v]]!=vertId[indices[face2.start+v2]])
// different vertId
continue;
for (j=1; j<3; j++)
{
if (targetId == mergeOrder[indices[face2.start + ((v2+j)%3)]])
break;
}
if (j==3)
// wrong target
continue;
Point2F delta = tverts[indices[face.start+v]]-tverts[indices[face2.start+v2]];
if (mFabs(delta.x)>sameTVertTOL || mFabs(delta.y)>sameTVertTOL)
{
// wrong tvert
continue;
}
// merge to vertex on this face
idx = indices[face2.start+((v2+j)%3)];
vertTarget = verts[idx];
tvertTarget = tverts[idx];
normTarget = norms[idx];
vid = vertId[idx];
sm = smooth[idx];
verts.push_back(vertTarget);
tverts.push_back(tvertTarget);
norms.push_back(normTarget);
vertId.push_back(vid);
smooth.push_back(sm);
{
F32 dot = mDot(verts.last()-mergeTarget[vertMergeOrder],verts.last()-mergeTarget[vertMergeOrder]);
if (dot>0.001f)
setExportError("Assertion failed");
}
return;
}
}
// merge to tvert of original vert but location of target
vertTarget = mergeTarget[vertMergeOrder]; // only this is different than ourself
// must go to special lengths to get the right vert id...
idx = findClosestMatch(vertTarget,verts);
vid = vertId[idx];
idx = indices[face.start+v]; // ourself
tvertTarget = tverts[idx]; // ourself
normTarget = norms[idx]; // ourself
sm = smooth[idx];
verts.push_back(vertTarget);
tverts.push_back(tvertTarget);
norms.push_back(normTarget);
smooth.push_back(sm);
vertId.push_back(vid);
{
F32 dot = mDot(verts.last()-mergeTarget[vertMergeOrder],verts.last()-mergeTarget[vertMergeOrder]);
if (dot>0.001f)
setExportError("Assertion failed");
}
}
//--------------------------------------------
// optimize meshes -- called by generate shape
void ShapeMimic::optimizeMeshes(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
S32 i,j,k;
printDump(PDObjectStateDetails,"\r\nOptimizing meshes...\r\n");
// go through meshes and optimize each one...
for (i=0; i<objectList.size(); i++)
{
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;
printDump(PDObjectStateDetails,avar("\r\nOptimizing mesh \"%s\" detail level %i.\r\n",om->name,om->details[j].size));
TSMesh * mesh = om->details[j].mesh->tsMesh;
Vector<U32> & smooth = om->details[j].mesh->smoothingGroups;
Vector<U32> & remap = om->details[j].mesh->remap;
// collapse vertices
collapseVertices(mesh,smooth,remap);
// need to sprinkle these here and there to avoid crashes...
if (isError()) return;
// now that verts are collapsed, delete any trivial facees
for (S32 k=0; k<mesh->primitives.size(); k++)
{
TSDrawPrimitive & face = mesh->primitives[k];
U32 start = face.start;
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)
{
mesh->indices.erase(face.start);
mesh->indices.erase(face.start);
mesh->indices.erase(face.start);
for (S32 l=0; l<mesh->primitives.size(); l++)
if (mesh->primitives[l].start >= start)
mesh->primitives[l].start -= 3;
mesh->primitives.erase(k);
k--;
}
}
// make detail levels share verts when possible
shareVerts(om,j,pShape);
//
if (om->details[j].mesh->sortedObject)
continue;
// strip
stripify(mesh->primitives,mesh->indices);
// need to sprinkle these here and there to avoid crashes...
if (isError()) return;
}
}
// need to sprinkle these here and there to avoid crashes...
if (isError()) return;
// optimize decals...
for (i=0; i<decalObjectList.size(); i++)
{
DecalObjectMimic * dom = decalObjectList[i];
for (j=0; j<dom->numDetails; j++)
{
if (!dom->details[j].decalMesh)
continue;
printDump(PDObjectStateDetails,avar("\r\nOptimizing decal \"%s\" on mesh \"%s\" on detail #%i.\r\n",dom->decalNode->GetName(),dom->targetObject->name,j));
TSDecalMesh * decalMesh = dom->details[j].decalMesh->tsMesh;
Vector<U32> & remap = dom->targetObject->details[j].mesh->remap; // old vert idx --> new vert idx
for (k=0; k<decalMesh->indices.size(); k++)
decalMesh->indices[k] = remap[decalMesh->indices[k]];
// strip
stripify(decalMesh->primitives,decalMesh->indices);
// adjust startPrimitive array
// at this point the material index of the decalMesh primitives actually refers to frame number
// use this to update decalMesh->startPrimitive array (and put matIndex back to what it should
// be while we're at it -- although it doesn't really matter, isn't used)
for (k=0; k<decalMesh->startPrimitive.size(); k++)
decalMesh->startPrimitive[k] = 0;
for (k=0; k<decalMesh->primitives.size(); k++)
{
if (decalMesh->startPrimitive[ decalMesh->primitives[k].matIndex&TSDrawPrimitive::MaterialMask ] == -1)
decalMesh->startPrimitive[ decalMesh->primitives[k].matIndex&TSDrawPrimitive::MaterialMask ] = k;
decalMesh->primitives[k].matIndex &= ~TSDrawPrimitive::MaterialMask;
decalMesh->primitives[k].matIndex |= decalMesh->materialIndex;
}
}
}
// need to sprinkle these here and there to avoid crashes...
if (isError()) return;
// optimize skins...
Vector<U32> remap;
for (i=skins.size()-1; i>=0; i--)
{
SkinMimic * skin = skins[i];
TSSkinMesh * skinMesh = skin->skinMesh;
Vector<U32> & smooth = skin->smoothingGroups;
Vector<U32> * vertId = &skin->vertId;
// first make sure we have no missing verts...
for (j=1; j<skinMesh->vertexIndex.size(); j++)
{
if (skinMesh->vertexIndex[j]-skinMesh->vertexIndex[j-1]>1)
{
setExportError(avar("Vertex %i missing weight on skin \"%s\"",skinMesh->vertexIndex[j]+1,skin->skinNode->GetName()));
return;
}
}
// start optimizing this skin...
printDump(PDObjectStateDetails,avar("\r\nOptimizing skin mesh \"%s\" detail level %i.\r\n",skin->skinNode->GetName(),skin->detailSize));
// set up skinMesh for optimizing (copy initial values into run-time slot)
skinMesh->verts = skinMesh->initialVerts;
skinMesh->norms = skinMesh->initialNorms;
// 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);
// share verts...
shareVerts(skin,remap,pShape);
// copy verts and normals back to initial arrays and clear out run-time versions
skinMesh->initialVerts = skinMesh->verts;
skinMesh->initialNorms = skinMesh->norms;
skinMesh->verts.clear();
skinMesh->norms.clear();
// remap some information
for (j=0; j<skinMesh->vertexIndex.size(); j++)
skinMesh->vertexIndex[j] = remap[skinMesh->vertexIndex[j]];
for (j=(S32)skinMesh->vertexIndex.size()-1; j>0; j--)
{
for (k=0; k<j; k++)
{
if (skinMesh->vertexIndex[k]==skinMesh->vertexIndex[j] && skinMesh->boneIndex[k]==skinMesh->boneIndex[j])
{
if (mFabs(skinMesh->weight[j]-skinMesh->weight[k])>0.01f)
{
setExportError("Assertion failed when collapsing vertices on skin (1)");
return;
}
// vertex and bone index for kth and jth tuple match...merge them
skinMesh->weight.erase(j);
skinMesh->vertexIndex.erase(j);
skinMesh->boneIndex.erase(j);
break; // out of k loop
}
}
}
// re-sort the vertexIndex, boneIndex, weight lists by vertex and bone, respectively...
for (j=0; j<(S32)skinMesh->vertexIndex.size()-1; j++)
{
for (k=j+1; k<skinMesh->vertexIndex.size(); k++)
{
if ((skinMesh->vertexIndex[k]<skinMesh->vertexIndex[j]) || (skinMesh->vertexIndex[k]==skinMesh->vertexIndex[j] && skinMesh->boneIndex[k]<skinMesh->boneIndex[j]))
{
// swap
S32 tmp = skinMesh->vertexIndex[k];
skinMesh->vertexIndex[k] = skinMesh->vertexIndex[j];
skinMesh->vertexIndex[j] = tmp;
tmp = skinMesh->boneIndex[k];
skinMesh->boneIndex[k] = skinMesh->boneIndex[j];
skinMesh->boneIndex[j] = tmp;
F32 tmp2 = skinMesh->weight[k];
skinMesh->weight[k] = skinMesh->weight[j];
skinMesh->weight[j] = tmp2;
}
}
}
// share bones...
shareBones(skin,pShape);
// strip
stripify(skinMesh->primitives,skinMesh->indices);
}
// convert merge indices into the form we need:
// 1. only include merge indices for verts not in our child detail
// 2. make sure mergeIndex points to vert in our child detail
// (keep following the trail till we get there)
fixupMergeIndices(pShape);
}
void ShapeMimic::fixupMergeIndices(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
S32 i,j,k;
Vector<bool> hasChild;
hasChild.setSize(pShape->meshes.size());
for (i=0;i<hasChild.size();i++)
hasChild[i]=false; // till we find the child
// convert merge indices into the form we need:
// 1. only include merge indices for verts not in our child detail
// 2. make sure mergeIndex points to vert in our child detail
// (keep following the trail till we get there)
for (i=0; i<objectList.size(); i++)
{
ObjectMimic * om = objectList[i];
for (j=0; j<om->numDetails; j++)
{
if (!om->details[j].mesh)
continue;
TSMesh * childMesh = om->isSkin ? om->details[j].mesh->skinMimic->skinMesh : om->details[j].mesh->tsMesh;
if (childMesh->parentMesh<0)
// we're here to work on parents, not children
continue;
TSMesh * parentMesh = pShape->meshes[childMesh->parentMesh];
S32 numParentVerts = om->isSkin ? ((TSSkinMesh*)parentMesh)->initialVerts.size() : parentMesh->verts.size();
hasChild[childMesh->parentMesh] = true;
if (numParentVerts!=parentMesh->mergeIndices.size() && parentMesh->mergeIndices.size()!=0)
{
setExportError("Assertion failed during vertex merge");
return;
}
// first, make sure each vert in parent mesh that isn't in child mesh has a
// mergeIndex that maps there (follow the chain till we get to chid mesh)
S32 numChildVerts = om->isSkin ? ((TSSkinMesh*)childMesh)->initialVerts.size() : childMesh->verts.size();
if (numChildVerts==numParentVerts)
// no merge needed
parentMesh->mergeIndices.clear();
else
{
// quick check to make sure merge is still ok
//testMerge(pShape,om,j,0);
for (k=numChildVerts; k<numParentVerts; k++)
{
#define self(a,b) a[a[b]]
while (parentMesh->mergeIndices[k] >= numChildVerts &&
parentMesh->mergeIndices[k]!=self(parentMesh->mergeIndices,k))
parentMesh->mergeIndices[k] = self(parentMesh->mergeIndices,k);
}
// now move mergeIndices down so that we only keep mergeIndices for those verts not found in child
dMemmove(&parentMesh->mergeIndices[0],&parentMesh->mergeIndices[numChildVerts],(parentMesh->mergeIndices.size()-numChildVerts)*sizeof(parentMesh->mergeIndices[0]));
parentMesh->mergeIndices.setSize(numParentVerts-numChildVerts);
// one more check -- search the verts unique to the parent...if any of them are at the same
// position as the child, make sure that they merge to themself
Vector<Point3F> & childVerts = om->isSkin ? ((TSSkinMesh*)childMesh)->initialVerts : childMesh->verts;
Vector<Point3F> & parentVerts = om->isSkin ? ((TSSkinMesh*)parentMesh)->initialVerts : parentMesh->verts;
for (k=childVerts.size(); k<parentVerts.size(); k++)
{
for (S32 l=0; l<childVerts.size(); l++)
{
Point3F delta = parentVerts[k]-childVerts[l];
if (mDot(delta,delta)<10E-20f)
{
// merge to ourself?
if (parentMesh->mergeIndices[k-childVerts.size()] != k)
{
// merging to different vert...bad
setExportError("Assertion failed during vertex merge");
return;
}
}
}
}
// yet one more check -- make sure all verts that merge someplace either merge
// to themselv or to a legit destination
testMerge(pShape,om,j,numChildVerts);
}
}
}
// make sure childless meshes have no mergeIndices
for (i=0; i<objectList.size(); i++)
{
ObjectMimic * om = objectList[i];
for (j=0; j<om->numDetails; j++)
{
if (!om->details[j].mesh)
continue;
TSMesh * mesh = om->isSkin ? om->details[j].mesh->skinMimic->skinMesh : om->details[j].mesh->tsMesh;
if (!hasChild[om->details[j].mesh->meshNum])
pShape->meshes[om->details[j].mesh->meshNum]->mergeIndices.clear();
}
}
}
void ShapeMimic::testMerge(TSShape * pShape, ObjectMimic * om, S32 dl, S32 mergeRemoved)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
MeshMimic * mm = om->details[dl].mesh;
TSMesh * childMesh = om->isSkin ? mm->skinMimic->skinMesh : mm->tsMesh;
TSMesh * parentMesh = childMesh;
if (childMesh->parentMesh>=0)
parentMesh = pShape->meshes[childMesh->parentMesh];
else
// ok, forget the child, just check out the parent
childMesh = NULL;
Vector<Point3F> emptyList;
Vector<Point3F> & childVerts = childMesh ? (om->isSkin ? ((TSSkinMesh*)childMesh)->initialVerts : childMesh->verts) : emptyList;
Vector<Point3F> & parentVerts = om->isSkin ? ((TSSkinMesh*)parentMesh)->initialVerts : parentMesh->verts;
if (mergeRemoved + parentMesh->mergeIndices.size() != parentVerts.size())
{
setExportError("Assertion failed");
return;
}
Vector<Point3F> mergeFrom, mergeTo;
{
MultiResMimic * mrm = getMultiRes(mm->pNode);
// merge from and mergeTo vectors are in object space before applying object offset
// apply object offset to those points
mergeFrom.setSize(mrm->mergeFrom.size());
mergeTo.setSize(mrm->mergeTo.size());
Matrix3 objectOffset3 = mm->objectOffset;
MatrixF objectOffset;
convertToMatrixF(objectOffset3,objectOffset);
for (S32 k=0; k<mrm->mergeFrom.size(); k++)
{
objectOffset.mulP(mrm->mergeFrom[k],&mergeFrom[k]);
objectOffset.mulP(mrm->mergeTo[k],&mergeTo[k]);
}
mrm=NULL; // don't want to use anything in this for the rest of this method...crash if we accidentally do
}
for (S32 k=0; k<parentMesh->mergeIndices.size(); k++)
{
Point3F vert = parentVerts[k+mergeRemoved];
for (S32 i=childVerts.size(); i<parentVerts.size(); i++)
{
Point3F delta1 = parentVerts[i]-vert;
if (mDot(delta1,delta1)<0.00000001f)
{
// same location, check to make sure it's the same merge
Point3F delta2 = parentVerts[parentMesh->mergeIndices[k]] - parentVerts[parentMesh->mergeIndices[i-mergeRemoved]];
if (mDot(delta2,delta2)>0.0001f)
{
setExportError("Assertion failed");
return;
}
}
}
if (parentMesh->mergeIndices[k]==k+mergeRemoved)
// ourself
continue;
while (1)
{
S32 idx = findClosestMatch(vert,mergeFrom);
vert = mergeTo[idx];
Point3F delta = vert - parentVerts[parentMesh->mergeIndices[k]];
if (mDot(delta,delta)<0.001f)
// ok
break;
S32 v;
for (v=childVerts.size(); v<parentVerts.size(); v++)
{
Point3F delta = vert-parentVerts[v];
if (mDot(delta,delta)<0.001f)
// ok, found it
break;
}
if (v==parentVerts.size())
{
// made it to child mesh without finding where we merged
setExportError("Assertion failed");
return;
}
}
}
}
bool ShapeMimic::vertexSame(Point3F & v1, Point3F & v2, Point2F & tv1, Point2F & tv2, U32 smooth1, U32 smooth2, U32 idx1, U32 idx2, Vector<U32> * vertId)
{
if (mFabs(v1.x-v2.x)>sameVertTOL || mFabs(v1.y-v2.y)>sameVertTOL || mFabs(v1.z-v2.z)>sameVertTOL ||
mFabs(tv1.x-tv2.x)>sameTVertTOL || mFabs(tv1.y-tv2.y)>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 max 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::collapseVertices(TSMesh * mesh, Vector<U32> & smooth, Vector<U32> & remap, Vector<U32> * vertId)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (mesh->verts.size() != mesh->norms.size())
{
setExportError("Assertion failed when collapsing vertices (2)");
return;
}
printDump(PDObjectStateDetails,avar("%i verts before joining verts\r\n",mesh->verts.size()));
S32 i,j;
// set up remap
remap.setSize(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)?
if (!vertexSame(mesh->verts[i],mesh->verts[j],mesh->tverts[i],mesh->tverts[j],smooth[i],smooth[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->numMatFrames; l++)
{
S32 startTVert = l * mesh->vertsPerFrame;
if (!vertexSame(mesh->verts[i+startVert],mesh->verts[j+startVert],mesh->tverts[i+startTVert],mesh->tverts[j+startTVert],smooth[i],smooth[j],i,j,vertId))
break;
}
if (l!=mesh->numMatFrames)
break;
}
if (k!=mesh->numFrames)
// not same throughout
continue;
//------------------------------------------
// ok, but do we have the same merge pattern?
if (!mesh->mergeIndices.empty())
{
U16 checkI=i;
U16 checkJ=j;
while (checkI!=checkJ && mesh->mergeIndices[checkI]!=checkI && mesh->mergeIndices[checkJ]!=checkJ)
{
checkI=mesh->mergeIndices[checkI];
checkJ=mesh->mergeIndices[checkJ];
// if (!vertexSame(mesh->verts[checkI],mesh->verts[checkJ],mesh->tverts[checkI],mesh->tverts[checkJ],smooth[checkI],smooth[checkI],checkI,checkJ,vertId))
if (!vertexSame(mesh->verts[checkI],mesh->verts[checkJ],mesh->tverts[checkI],mesh->tverts[checkJ],-1,-1,checkI,checkJ,vertId))
break;
}
// ok, either checkI and checkJ converged, or they are both stationary
// either way, just check to see if they reached an equivalent vertex
// if (!vertexSame(mesh->verts[checkI],mesh->verts[checkJ],mesh->tverts[checkI],mesh->tverts[checkJ],smooth[checkI],smooth[checkI],checkI,checkJ,vertId))
if (!vertexSame(mesh->verts[checkI],mesh->verts[checkJ],mesh->tverts[checkI],mesh->tverts[checkJ],-1,-1,checkI,checkJ,vertId))
// different vertices
continue;
}
//------------------------------------------
// alright, vertex i and j are the same...get rid of vertex i (i>j)
if (i<=j)
{
setExportError("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=0; k<mesh->mergeIndices.size(); k++)
{
if (mesh->mergeIndices[k] == i)
mesh->mergeIndices[k] = j;
else if (mesh->mergeIndices[k]>i)
mesh->mergeIndices[k]--;
}
if (!mesh->mergeIndices.empty())
mesh->mergeIndices.erase(i);
for (k=mesh->numFrames-1; k>=0; k--)
{
S32 startVert = mesh->vertsPerFrame * k;
mesh->verts.erase(i + startVert);
mesh->norms.erase(i + startVert);
}
for (k=mesh->numMatFrames-1; k>=0; k--)
{
S32 startTVert = mesh->vertsPerFrame * k;
mesh->tverts.erase(i + startTVert);
}
if (vertId)
vertId->erase(i);
smooth.erase(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
}
}
// re-generate normals since vertex sharing has changed...
computeNormals(mesh->primitives,mesh->indices,mesh->verts,mesh->norms,smooth,mesh->vertsPerFrame,mesh->numFrames,&mesh->mergeIndices);
printDump(PDObjectStateDetails,avar("%i verts after joining verts\r\n",mesh->verts.size()));
if (mesh->verts.size() * mesh->numMatFrames != mesh->tverts.size() * mesh->numFrames)
setExportError("ShapeMimic::collapseVertices (3)");
else if (mesh->verts.size() != mesh->norms.size())
setExportError("ShapeMimic::collapseVertices (4)");
}
//----------------------------------------------------------------------------
// compute normals, account for smoothing groups and vertices that are same
// location but different vertex anyway (two verts w/ same smoothing group
// and same location should get same normal)
void ShapeMimic::computeNormals(Vector<TSDrawPrimitive> & faces, Vector<U16> & indices, Vector<Point3F> & verts, Vector<Point3F> & norms, Vector<U32> & smooth, S32 vertsPerFrame, S32 numFrames, Vector<U16> * mergeIndices)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return;
if (vertsPerFrame * numFrames != verts.size() || vertsPerFrame!=smooth.size())
{
setExportError("Assertion failed: vertex number mismatch");
return;
}
if (mergeIndices && !mergeIndices->empty() && mergeIndices->size() != vertsPerFrame)
{
setExportError("Assertion failed: vertex number mismatch");
return;
}
S32 i,j;
Vector<S32> counts;
counts.setSize(verts.size());
norms.setSize(verts.size());
for (i=0; i<verts.size(); i++)
{
counts[i]=0;
norms[i].set(0.0f,0.0f,0.0f);
}
for (S32 frameNum = 0; frameNum<numFrames; frameNum++)
{
S32 startVert = frameNum * vertsPerFrame;
for (i=0; i<faces.size(); i++)
{
TSDrawPrimitive & tsFace = faces[i];
if ((tsFace.matIndex & TSDrawPrimitive::TypeMask) != TSDrawPrimitive::Triangles)
{
setExportError("Assertion error while computing normals");
return;
}
// find the normal to this face
S32 idx0 = indices[tsFace.start+0];
S32 idx1 = indices[tsFace.start+1];
S32 idx2 = indices[tsFace.start+2];
Point3F v0 = verts[startVert+idx0];
Point3F v1 = verts[startVert+idx1];
Point3F v2 = verts[startVert+idx2];
Point3F n,v20,v10;
v20 = v2-v0;
if (mDot(v20,v20)>0.0000001f)
v20.normalize();
v10 = v1-v0;
if (mDot(v10,v10)>0.0000001f)
v10.normalize();
mCross(v20,v10,&n);
if (mDot(n,n) > 0.0000001f)
{
n.normalize();
for (S32 j=0; j<vertsPerFrame; j++)
{
Point3F vj = verts[startVert+j];
if (mFabs(v0.x-vj.x) < sameVertTOL && mFabs(v0.y-vj.y) < sameVertTOL && mFabs(v0.z-vj.z) < sameVertTOL && (smooth[idx0]&smooth[j] || smooth[idx0]==smooth[j]))
{
norms[startVert+j] += n;
counts[startVert+j]++;
}
if (mFabs(v1.x-vj.x) < sameVertTOL && mFabs(v1.y-vj.y) < sameVertTOL && mFabs(v1.z-vj.z) < sameVertTOL && (smooth[idx1]&smooth[j] || smooth[idx1]==smooth[j]))
{
norms[startVert+j] += n;
counts[startVert+j]++;
}
if (mFabs(v2.x-vj.x) < sameVertTOL && mFabs(v2.y-vj.y) < sameVertTOL && mFabs(v2.z-vj.z) < 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] && mDot(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;
Point3F delta = verts[i]-verts[j];
if (mFabs(delta.x)>sameVertTOL || mFabs(delta.y)>sameVertTOL || mFabs(delta.z)>sameVertTOL)
continue;
if (smooth[i]==smooth[j])// || (smooth[i]&smooth[j])!=0)
{
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 and smoothing group
for (j=0; j<verts.size(); j++)
{
if (!counts[j])
continue;
Point3F delta = verts[i]-verts[j];
if (mFabs(delta.x)>sameVertTOL || mFabs(delta.y)>sameVertTOL || mFabs(delta.z)>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].set(0,0,1);
}
void ShapeMimic::computeNormals(Vector<TSDrawPrimitive> & faces, Vector<U16> & indices, Vector<Point3> & verts, Vector<Point3F> & norms, Vector<U32> & smooth, S32 vertsPerFrame, S32 numFrames)
{
computeNormals(faces,indices, (Vector<Point3F>&)verts,norms,smooth,vertsPerFrame,numFrames, NULL);
}
extern void nvStripWrap(Vector<TSDrawPrimitive> &, Vector<U16> &, S32, S32);
void ShapeMimic::stripify(Vector<TSDrawPrimitive> & primitives, Vector<U16> & indices)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) 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;
Vector<TSDrawPrimitive> faces = primitives;
Vector<U16> faceIndices = indices;
// in: primitives better just be faces and better use indexes
for (i=0; i<primitives.size(); i++)
{
if (primitives[i].matIndex == -1)
{
setExportError("Assertion failed when sripping -- negative material index");
return;
}
if ( (primitives[i].matIndex & ~(TSDrawPrimitive::NoMaterial^TSDrawPrimitive::MaterialMask)) != (TSDrawPrimitive::Triangles|TSDrawPrimitive::Indexed) || primitives[i].numElements!=3)
{
setExportError("Assertion failed when stripifying (1)");
return;
}
}
printDump(PDObjectStateDetails,avar("%i faces before stripping\r\n",faces.size()));
// do a quick and dirty pass at getting strips -- sometimes proves better
// Full pass is based on the Hoppes algorithm for optimizing (i.e., minimizing)
// cache misses (presented at Siggraph 99). The quick and dirty scheme is the
// same without look ahead simulation (still has vertex cache considerations).
Stripper quickAndDirty(primitives,indices);
quickAndDirty.setLimitStripLength(false);
quickAndDirty.makeStrips();
U32 qdMisses = quickAndDirty.getCacheMisses();
#ifdef QUICK_STRIP
quickAndDirty.getStrips(primitives,indices);
if (dumpMask & PDObjectStateDetails)
{
printDump(PDObjectStateDetails,avar("Using %s stripping method.\r\n","quick and dirty"));
float 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 = len - (faces.size() + 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();
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));
printDump(PDObjectStateDetails,avar("Results in %i cache misses\r\n",qdMisses));
}
#elif defined(NV_STRIP)
nvStripWrap(primitives,indices,16,1);
if (dumpMask & PDObjectStateDetails)
{
printDump(PDObjectStateDetails,avar("Using %s stripping method.\r\n","NVidia"));
float 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 = len - (faces.size() + 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();
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));
}
#else
Stripper stripper(primitives,indices);
stripper.makeStrips();
U32 misses = stripper.getCacheMisses();
bool useHoppes = misses<qdMisses;
if (useHoppes)
stripper.getStrips(primitives,indices);
else
quickAndDirty.getStrips(primitives,indices);
if (stripper.isError())
{
setExportError(stripper.getError());
return;
}
if (dumpMask & PDObjectStateDetails)
{
printDump(PDObjectStateDetails,avar("Using %s stripping method.\r\n",useHoppes ? "look ahead simulation" : "quick and dirty"));
float 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 = len - (faces.size() + 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();
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));
if (useHoppes)
printDump(PDObjectStateDetails,avar("Results in %i cache misses versus %i with quick and dirty scheme\r\n",misses,qdMisses));
else
printDump(PDObjectStateDetails,avar("Results in %i cache misses versus %i with look ahead simulation\r\n",qdMisses,misses));
}
#endif
}
void ShapeMimic::convertSortObjects(TSShape * pShape)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) 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)
continue;
TSSortedMesh * sortMesh = (TSSortedMesh*)om->details[j].mesh->tsMesh;
// get sort data from user properties...
INode * pNode = om->details[j].mesh->pNode;
S32 numBigFaces, maxDepth;
if (!pNode->GetUserPropInt("SORT::NUM_BIG_FACES",numBigFaces))
numBigFaces = 0; // default value...
if (!pNode->GetUserPropInt("SORT::MAX_DEPTH",maxDepth))
maxDepth = 2;
S32 tmp;
if (!pNode->GetUserPropBool("SORT::WRITE_Z",tmp))
tmp = 0;
sortMesh->alwaysWriteDepth = tmp;
if (!pNode->GetUserPropBool("SORT::Z_LAYER_UP",tmp))
tmp = false;
bool zLayerUp = tmp;
if (!pNode->GetUserPropBool("SORT::Z_LAYER_DOWN",tmp))
tmp = false;
bool zLayerDown = tmp;
if (zLayerUp && zLayerDown)
{
setExportError("Cannot use both Z_LAYER_UP and Z_LAYER_DOWN -- make up your mind.");
return;
}
if (sortMesh->primitives.size() > MAX_TS_SET_SIZE)
{
setExportError("Too many faces on sort object: up MAX_TS_SET_SIZE and recompile exporter");
return;
}
TranslucentSort::generateSortedMesh(sortMesh,numBigFaces,maxDepth,zLayerUp,zLayerDown);
S32 saveNumFrames = sortMesh->numFrames;
sortMesh->vertsPerFrame = sortMesh->verts.size();
Vector<U32> remap;
Vector<U32> smooth;
smooth.setSize(sortMesh->verts.size());
for (S32 k=0; k<smooth.size(); k++)
smooth[k]=0;
collapseVertices(sortMesh,smooth,remap);
sortMesh->numFrames = saveNumFrames;
sortMesh->vertsPerFrame = 0; // not used
}
}
}
F32 distFromPoly(Point3F & v0, Point3F & v1, Point3F & v2, Point3F v3)
{
// find distance of v0 from poly v1,v2,v3
// don't care about v1,v2,v3 winding
// check verts
F32 d, dist;
dist = mSqrt(mDot(v0-v1,v0-v1));
d = mSqrt(mDot(v0-v2,v0-v2));
if (d<dist)
dist = d;
d = mSqrt(mDot(v0-v3,v0-v3));
if (d<dist)
dist = d;
Point3F v12=v1-v2;
Point3F v23=v2-v3;
Point3F v31=v3-v1;
Point3F n;
mCross(v12,v23,&n);
if (mDot(n,n)>0.0000001f)
n.normalize();
else
return dist;
bool inTri=false;
F32 inTriDist;
Point3F varray[3];
varray[0]=v1;
varray[1]=v2;
varray[2]=v3;
if (pointInPoly(v0,n,varray,3))
{
// this should be it...but we'll keep going anyway
dist = mFabs(mDot(n,v0-v1));
inTri=true;
inTriDist = dist;
}
v12.normalize();
v23.normalize();
v31.normalize();
// find closest point along edge v12
F32 t = mDot(v0-v2,v12);
if (t>0 && t<1)
{
Point3F test = v12;
test *= t;
test += v2;
d = mSqrt(mDot(v0-test,v0-test));
if (d<dist)
dist=d;
}
// find closest point along edge v23
t = mDot(v0-v3,v23);
if (t>0 && t<1)
{
Point3F test = v23;
test *= t;
test += v3;
d = mSqrt(mDot(v0-test,v0-test));
if (d<dist)
dist=d;
}
// find closest point along edge v31
t = mDot(v0-v1,v31);
if (t>0 && t<1)
{
Point3F test = v31;
test *= t;
test += v1;
d = mSqrt(mDot(v0-test,v0-test));
if (d<dist)
dist=d;
}
if (inTri && dist<inTriDist-0.01f)
{
AssertFatal(1,"Doh");
dist=inTriDist;
}
return dist;
}
F32 ShapeMimic::findMaxDistance(TSMesh * loMesh, TSMesh * hiMesh, F32 & total, S32 & count)
{
// if already encountered an error, then
// we'll just go through the motions
if (isError()) return 0.0f;
// for each vertex of hiMesh, find distance to loMesh
// return the max of these distances (i.e., distance of farthest
// hiMesh vertex from loMesh).
Vector<Point3F> & loVerts = loMesh->getMeshType()==TSMesh::SkinMeshType ? ((TSSkinMesh*)loMesh)->initialVerts : loMesh->verts;
Vector<Point3F> & hiVerts = hiMesh->getMeshType()==TSMesh::SkinMeshType ? ((TSSkinMesh*)hiMesh)->initialVerts : hiMesh->verts;
F32 maxDist = 0.0f;
total = 0;
count = 0;
// loop through primitives so that stray verts don't mess us up...
for (S32 ii=0; ii<hiMesh->primitives.size(); ii++)
{
TSDrawPrimitive & hiDraw = hiMesh->primitives[ii];
for (S32 i=0; i<hiDraw.numElements; i++)
{
Point3F v0 = hiVerts[hiMesh->indices[hiDraw.start+i]];
F32 closestDist = 10E30f;
bool foundSomething = false;
for (S32 j=0; j<loMesh->primitives.size(); j++)
{
TSDrawPrimitive & draw = loMesh->primitives[j];
Point3F v1;
Point3F v2;
Point3F v3;
S32 numElements = 0;
while (numElements<draw.numElements)
{
if ((draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles || numElements==0)
{
v1 = loVerts[loMesh->indices[draw.start + numElements + 0]];
v2 = loVerts[loMesh->indices[draw.start + numElements + 1]];
v3 = loVerts[loMesh->indices[draw.start + numElements + 2]];
numElements += 3;
}
else
{
// ignore winding
v1 = v2;
v2 = v3;
v3 = loVerts[loMesh->indices[draw.start+numElements]];
numElements++;
}
foundSomething=true;
// find dist of v0 from v1,v2,v3
F32 dist = distFromPoly(v0,v1,v2,v3);
if (dist<closestDist)
closestDist = dist;
}
}
if (foundSomething)
{
if (closestDist>maxDist)
maxDist = closestDist;
total += closestDist;
count++;
}
}
}
return maxDist;
}