//----------------------------------------------------------------------------- // 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 #include #include #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 nodeRotCache; Vector nodeTransCache; Vector nodeScaleCache; Vector cutNodes; Vector 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 verts; Vector tverts; Vector faces; Vector indices; Vector n; Vector 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 t2AutoDetailSizes; Vector t2AutoDetailPercents; S32 findClosestMatch(Point3F & p, Vector & points) { F32 closestValue = 10E30f; S32 closestIndex = -1; for (S32 i=0; imultiResNode->GetObjectRef(); for (S32 j=0; jNumSubs(); 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;idetailNames.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;jsize > d2->size) return -1; if (d2->size > d1->size) return 1; return 0; } void sortTSDetails(Vector & 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 & 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 & 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 & faces) { dQsort(faces.address(),faces.size(),sizeof(TSDrawPrimitive),compareFaces); } //-------------------------------------------- // set up objects for sorting void ShapeMimic::setObjectPriorities(Vector & objectList) { // if already encountered an error, then // we'll just go through the motions if (isError()) return; S32 i,j; for (i=0; ipriority = (U32) om->tsNodeIndex; // use highest detail mesh we can find... for (j=0;jnumDetails;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; jpriority |= 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 & decals) { // if already encountered an error, then // we'll just go through the motions if (isError()) return; S32 i; for (i=0; ipriority = 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 & validDetails, Vector & detailNames, Vector & 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=0) lastVisibleDetail = i; S32 numVisibleDetails = lastVisibleDetail+1; while (numVisibleDetailsmaxDiff) { 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+2numDetails==0 || om->details[0].size<0) continue; for (S32 j=0; 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 & validDetails = pSubtree->validDetails; Vector & detailNames = pSubtree->detailNames; Vector & 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 branches; S32 i; for (i=0; iNumberOfChildren(); 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; jvalidDetails[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; istart.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 & 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; iNumberOfChildren(); 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; iisBone || 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 || sizedetails[j-1].size) { if (j
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; ipNode==pNode) return multiResList[i]; } return NULL; } void grabMeshData(INode * multiResNode, Vector & faces, Vector & 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 & hiMesh, Vector & loMesh, Vector & 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=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 & 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; iNumSubs(); 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 faces; Vector 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; jSetValue(0,DEFAULT_TIME,startNumVerts); } void ShapeMimic::collapseEdge(Vector & faces, S32 fromA, S32 toB) { for (S32 i=0; i & 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; iNumSubs(); 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 & multiResSize, Vector & 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+iGetUserPropString("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+iGetName())); return; } } //-------------------------------------------- // add a mesh -- detect MultiRes // if multi res, then add one mesh per detail ObjectMimic * ShapeMimic::addObject(INode * pNode, Vector * pValidDetails) { // if already encountered an error, then // we'll just go through the motions if (isError()) return NULL; ObjectMimic * om; // detect MultiRes... Vector multiResSize; Vector multiResPercent; getMultiResData(pNode,multiResSize,multiResPercent); if (multiResSize.size()) { addMultiRes(pNode,pNode); for (S32 i=0; iNumberOfChildren(); 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 * 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 multiResSize; Vector multiResPercent; const char * nodeName = pNode->GetName(); for (S32 i=0; iNumberOfChildren(); 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; iDeleteModifier(); // 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; ibones[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; iweights.size(); i++) { skinMimic->weights[i] = new SkinMimic::WeightList; skinMimic->weights[i]->setSize(numPoints); for (j=0; jweights[i])[j]=0.0f; } for (j=0; jbones[j]->GetName())); Vector gotWeight; gotWeight.setSize(tri->mesh.numVerts); for (i=0; imesh.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; ibones.size(); i++) { for (j=i+1; jbones.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; kweights[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;iweights[0]->size();i++) { F32 ** hi = new F32 * [weightsPerVertex]; for (k=0; kbones.size(); j++) { F32 & w = (*skinMimic->weights[j])[i]; for (k=0; kweightThreshhold) for (k=0;kbones.size();i++) { F32 sum = 0.0f; for (j=0;jweights[i]->size();j++) sum += (*skinMimic->weights[i])[j]; if (sumbones[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; istart.child; while (mimicNode) { if (mimicNode==&pSubtree->start) { // this should just never happen... setExportError("Assertion failed: Illegal condition."); return; } // a bone? for (j=0; jbones.size(); j++) { if (skinMimic->bones[j] == mimicNode->maxNode) { ObjectMimic * obj = addBoneObject(skinMimic->bones[j],i); if (!obj) return; for (k=0;kobjects.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 oldWeights = skinMimic->weights; for (i=0; iweights[i] = new SkinMimic::WeightList; for (i=0; ivertId.size(); j++) skinMimic->weights[i]->push_back((*(oldWeights[i]))[skinMimic->vertId[j]]); for (i=0; i & weights, INode * skinNode, INode * multiResNode) { // if already encountered an error, then // we'll just go through the motions if (isError()) return; Vector 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; iGetObjTMAfterWSM(DEFAULT_TIME); Matrix3 multiT = multiResNode->GetObjTMAfterWSM(DEFAULT_TIME); for (i=0; imesh.getNumVerts(); i++) { // find vert in triSkin that each vert in triMR is closest to F32 closest = 10E10f; S32 idx = -1; for (j=0; jmesh.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=0) { for (j=0; jpush_back((*weights[j])[idx]); } } for (i=0; ihasTVerts = 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; ifileName = 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; istart.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;iobjects.empty() && !SceneEnumProc::isDummy(mimicNode->maxNode)); } bool ShapeMimic::neverAnimateNode(NodeMimic * mimicNode) { const char * name = mimicNode->maxNode->GetName(); // search always export list for (S32 i=0;iobjects.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; iobjects.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; idetails.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; iobjects.size(); i++) { for (S32 j=0;jobjects[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; idecals.size(); i++) { for (j=0; jdecals[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; idetails.size(); i++) { TSShape::Detail & detail = pShape->details[i]; Vector & meshes = pShape->meshes; F32 totalMaxDist = 0.0f, curMaxMax = 0.0f; S32 count = 0; for (j=0; jobjects.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 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; idecals.size(); i++) { for (S32 j=0; jdecals[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; imeshes.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; icenter = (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; icenter; 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; icenter.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; ivalidDetails.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 checkers; S32 last = -999; for (i=0; idetails.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; jstart.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;jobjects.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; isubShapeFirstObject.increment(); pShape->subShapeFirstObject.last() = -1; } // go through mesh list and add objects as we go for (i=0; ipValidDetails) { 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 * 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; jnumDetails; j++) { for (k=0; ksize(); 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+1numDetails;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; lmeshes.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; jsize(); 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; ipValidDetails) 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; isubShapeFirstDecal.increment(); pShape->subShapeFirstDecal.last() = -1; } // loop through decal objects and generate decalObjects and decalMeshes on shape for (i=0; idecalNode->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; jtargetObject->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; itsDecal = &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; ipValidDetails) 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; i0.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; inumDetails,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; dlnumDetails; 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 faces; Vector normals; Vector verts; Vector tverts; Vector indices; Vector smooth; Vector 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;idetails[dl].mesh->vertId[i]) break; if (i!=vertId.size()) error=true; } else error=true; for (i=0; iprimitives.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; inorms[i+start] = normals[i]; // copy verts... start = tsMesh->verts.size(); tsMesh->verts.setSize(start+verts.size()); for (i=0; iverts[i+start]); tsMesh->numFrames++; } if (addMatFrame) { // copy tverts... start = tsMesh->tverts.size(); tsMesh->tverts.setSize(start+tverts.size()); for (i=0; itverts[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; dlnumDetails; 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+knumDetails) { 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 & faces, Vector & normals, Vector & verts, Vector & tverts, Vector & indices, Vector & smooth, Vector * 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 cropInfoList; Vector 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; ipush_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 flipX, flipY; flipX.setSize(tverts.size()); flipY.setSize(tverts.size()); for (i=0;i 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= 1.0f && tverts[idx1].x >= 1.0f && tverts[idx2].x >= 1.0f)) { // get rid of this face for (j=0; jstart) 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; jstart) 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()) { setExportError("Assertion failed"); return; } vertUsed[indices[i]] = true; } for (i=verts.size()-1; i>=0; i--) { if (!vertUsed[i]) { for (j=0; ji) 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 done; done.setSize(tverts.size()); for (i=0;itotalVerts * 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; iiflMaterials.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; iGetName(); 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; jGetNumKeys(); 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; imaxParent; if (objectList[i]->isSkin) { // in case force was set seq.frameMatters.clear(i); seq.matFrameMatters.clear(i); testNode=NULL; for (S32 dl=0; dlnumDetails; 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; jdecalNode,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; irange.End()) maxTime = range.End(); else if (maxTimedefaultRotations[i]; if (!(*firstRot==defaultRot)) { seq.rotationMatters.set(i); nodeCount++; continue; } for (S32 frame=1; framedefaultTranslations[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; frame0.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; i0.001f) { seq.scaleMatters.set(i); nodeCount++; continue; } for (S32 frame=1; frame0.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; ix-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; frame0.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; i0.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; ifullFileName,FileStream::Read); if (fs.getStatus() != Stream::Ok) { setExportError(avar("Error reading ifl file \"%s\"",iflList[i]->fullFileName)); fs.close(); return 0; } Vector 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 && totalTimestartFrame && 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 rotTrans; Vector transTrans; Vector scaleTrans; Vector child0; Vector 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;irange.End()) maxTime = range.End(); else if (maxTimex - 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 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; irange.End()) maxTime = range.End(); else if (maxTimegroundTranslations.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; irange.End()) maxTime = range.End(); else if (maxTimesequences.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; irange.End()) maxTime = range.End(); else if (maxTimesequences.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; irange.End()) maxTime = range.End(); else if (maxTimenumDetails,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; dlnumDetails; 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; dlnumDetails; 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; istartPrimitive.size()-1; i++) { if (tsDecal->startPrimitive.last()==tsDecal->startPrimitive[i]) break; } if (istartPrimitive.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; dlnumDetails; 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 normals; Vector 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 & decalFaces = gDecalInfo.faces; Vector & decalIndices = gDecalInfo.indices; Vector & decalVerts = gDecalInfo.verts; Vector & decalTVerts = gDecalInfo.tverts; Vector & n = gDecalInfo.n; Vector & 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=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 meshFaces; Vector normals; Vector meshVerts; Vector meshTVerts; Vector meshIndices; Vector 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 used; used.setSize(meshVerts.size()); // for each meshVert, find out where it maps to on the decal mesh for (i=0; i0.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; istart) 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; framestartPrimitive[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 (framestartPrimitive[frame]; tsDecal->startPrimitive.push_back(prm); } else { tsDecal->startPrimitive.push_back(tsDecal->primitives.size()); for (i=0; iprimitives.push_back(meshFaces[i]); tsDecal->primitives.last().matIndex = numFrames | TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed; tsDecal->primitives.last().start += tsDecal->indices.size(); } for (i=0; iindices.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 & decalVerts = gDecalInfo.verts; Vector & decalTVerts = gDecalInfo.tverts; Vector & decalFaces = gDecalInfo.faces; Vector & decalIndices = gDecalInfo.indices; Vector & n = gDecalInfo.n; Vector & k = gDecalInfo.k; for (S32 i=0; i0.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 & n = gDecalInfo.n; Vector & 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]) & decalVerts = gDecalInfo.verts; Vector & decalTVerts = gDecalInfo.tverts; Vector & decalFaces = gDecalInfo.faces; Vector & decalIndices = gDecalInfo.indices; Vector & n = gDecalInfo.n; Vector & k = gDecalInfo.k; Point3F p = Point3ToPoint3F(vert,p); if (n.size() && mDot(normal,n[0])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 (yGetPixels(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; itriggers[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; iGetNumKeys(); 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; jtriggers.size(); j++) { if (postriggers[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; idetails.size(); i++) { // search for skins of size details[i].size // place any found at j and advance j for (k=j; kdetailSize == (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 (;jisSkin) continue; // all meshes on this object should be skins... for (S32 meshNum=0; meshNumnumDetails; 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; jtverts.size(); j++) skinMesh->tverts[j].set(skin->tverts[j].x,skin->tverts[j].y); skinMesh->initialVerts.setSize(skin->verts.size()); for (j=0; jverts.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+knumDetails) { 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; jbones.size(); j++) { // find node index for (k=0; kmaxNode == 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; jinitialVerts.size(); j++) { printDump(PDObjectStateDetails,avar("Vertex %i\r\n",j)); for (k=0; kbones.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 & verts, Vector & tverts, Vector & indices, Vector & flipX, Vector & flipY, F32 splitAt, Vector & smooth, Vector * 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 & verts, Vector & tverts, Vector & indices, Vector & flipX, Vector & flipY, F32 splitAt, Vector & smooth, Vector * 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 & smooth = om->details[dl].mesh->smoothingGroups; Vector remap; remap.setSize(mesh->verts.size()); for (i=0;inumDetails; i++) { if (mrm!=getMultiRes(om->details[i].mesh->pNode)) continue; TSMesh * mesh2 = om->details[i].mesh->tsMesh; Vector & 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; jverts.size(); j++) { for (k=0; kverts.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 newVerts; Vector newTVerts; Vector newNorms; Vector newSmooth; Vector newMerge; newVerts = mesh2->verts; newTVerts = mesh2->tverts; newNorms = mesh2->norms; newSmooth = smooth2; newMerge.setSize(mesh2->mergeIndices.size()); for (j=0; jverts.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; jindices.size(); j++) mesh->indices[j] = remap[mesh->indices[j]]; // fixup remap on mesh for (j=0; jdetails[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; jnumDetails; 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 & 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 & smooth = sm->smoothingGroups; Vector remap; remap.setSize(mesh->verts.size()); for (i=0;idetailSize < 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 & smooth2 = sm2->smoothingGroups; S32 count=0; for (j=0; jverts.size(); j++) { for (k=0; kinitialVerts.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 newVerts; Vector newTVerts; Vector newNorms; Vector newSmooth; Vector newVertId; Vector newMerge; newVerts = mesh2->initialVerts; newTVerts = mesh2->tverts; newNorms = mesh2->initialNorms; newSmooth = smooth2; newVertId = sm2->vertId; newMerge.setSize(mesh2->mergeIndices.size()); for (j=0; jverts.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; jindices.size(); j++) mesh->indices[j] = remap[mesh->indices[j]]; // fix up originalRemap for (i=0; iverts = 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; idetailSize < 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; knodeIndex.size(); k++) { for (j=0; jnodeIndex.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 remap; remap.setSize(mesh->nodeIndex.size()); for (j=0; jnodeIndex.size(); j++) { for (k=0; knodeIndex.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 newNodeIndex; newNodeIndex.setSize(mesh->nodeIndex.size()); Vector newIMats; newIMats.setSize(mesh->initialTransforms.size()); for (j=0;jnodeIndex[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; jboneIndex.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; jvertexIndex.size() && jvertexIndex.size(); j++) { if (mesh->vertexIndex[j]==mesh2->vertexIndex[j]) // all is cool, keep going continue; while (jvertexIndex.size() && jvertexIndex.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 & faces, Vector & verts, Vector & norms, Vector & tverts, const Vector & indices, Vector & mergeIndices, Vector & smooth, Vector & 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 workFaces = faces; Vector workIndices = indices; S32 i,j,v; mergeIndices.setSize(verts.size()); for (i=0;i mergeOrder; Vector mergeToId; // merge from and mergeTo vectors are in object space before applying object offset // apply object offset to those points Vector mergeFrom, mergeTo; mergeFrom.setSize(mrm->mergeFrom.size()); mergeTo.setSize(mrm->mergeTo.size()); MatrixF objectOffset; convertToMatrixF(objectOffset3,objectOffset); for (i=0; imergeFrom.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; i0.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; i0.0001f || idxpNode->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; i0.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; i0.0001f) { setExportError("Assertion failed"); return; } } } } void ShapeMimic::addMergeTargetAndMerge(Vector & faces, S32 & faceNum, S32 & v, Vector & indices, Vector & mergeIndices, Vector & verts, Vector & tverts, Vector & norms, Vector & smooth, Vector & vertId, Vector & mergeOrder, Vector & mergeToId, Vector & 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 (targetId0.001f) setExportError("Assertion failed"); } return; } } // more complicated...try to find another face with a vert like us for (i=0; isameTVertTOL || 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; iisSkin) // 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 & smooth = om->details[j].mesh->smoothingGroups; Vector & 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; kprimitives.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; lprimitives.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; inumDetails; 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 & remap = dom->targetObject->details[j].mesh->remap; // old vert idx --> new vert idx for (k=0; kindices.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; kstartPrimitive.size(); k++) decalMesh->startPrimitive[k] = 0; for (k=0; kprimitives.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 remap; for (i=skins.size()-1; i>=0; i--) { SkinMimic * skin = skins[i]; TSSkinMesh * skinMesh = skin->skinMesh; Vector & smooth = skin->smoothingGroups; Vector * vertId = &skin->vertId; // first make sure we have no missing verts... for (j=1; jvertexIndex.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;jinitialVerts = skinMesh->verts; skinMesh->initialNorms = skinMesh->norms; skinMesh->verts.clear(); skinMesh->norms.clear(); // remap some information for (j=0; jvertexIndex.size(); j++) skinMesh->vertexIndex[j] = remap[skinMesh->vertexIndex[j]]; for (j=(S32)skinMesh->vertexIndex.size()-1; j>0; j--) { for (k=0; kvertexIndex[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; kvertexIndex.size(); k++) { if ((skinMesh->vertexIndex[k]vertexIndex[j]) || (skinMesh->vertexIndex[k]==skinMesh->vertexIndex[j] && skinMesh->boneIndex[k]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 hasChild; hasChild.setSize(pShape->meshes.size()); for (i=0;inumDetails; 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; kmergeIndices[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 & childVerts = om->isSkin ? ((TSSkinMesh*)childMesh)->initialVerts : childMesh->verts; Vector & parentVerts = om->isSkin ? ((TSSkinMesh*)parentMesh)->initialVerts : parentMesh->verts; for (k=childVerts.size(); kmergeIndices[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; inumDetails; 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 emptyList; Vector & childVerts = childMesh ? (om->isSkin ? ((TSSkinMesh*)childMesh)->initialVerts : childMesh->verts) : emptyList; Vector & parentVerts = om->isSkin ? ((TSSkinMesh*)parentMesh)->initialVerts : parentMesh->verts; if (mergeRemoved + parentMesh->mergeIndices.size() != parentVerts.size()) { setExportError("Assertion failed"); return; } Vector 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; kmergeFrom.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; kmergeIndices.size(); k++) { Point3F vert = parentVerts[k+mergeRemoved]; for (S32 i=childVerts.size(); imergeIndices[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 * 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 & smooth, Vector & remap, Vector * 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; ivertsPerFrame-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; knumFrames; k++) { S32 startVert = k * mesh->vertsPerFrame; for (l=1; lnumMatFrames; 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; kindices.size(); k++) { if (mesh->indices[k] == i) mesh->indices[k] = j; else if (mesh->indices[k]>i) mesh->indices[k]--; } for (k=0; kmergeIndices.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;ki) 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 & faces, Vector & indices, Vector & verts, Vector & norms, Vector & smooth, S32 vertsPerFrame, S32 numFrames, Vector * 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 counts; counts.setSize(verts.size()); norms.setSize(verts.size()); for (i=0; i0.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; j0.0000001f) norms[i].normalize(); } // for verts w/o a normal, search for someone with same vert location and smoothing group for (i=0; isameVertTOL || 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; isameVertTOL || 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 & faces, Vector & indices, Vector & verts, Vector & norms, Vector & smooth, S32 vertsPerFrame, S32 numFrames) { computeNormals(faces,indices, (Vector&)verts,norms,smooth,vertsPerFrame,numFrames, NULL); } extern void nvStripWrap(Vector &, Vector &, S32, S32); void ShapeMimic::stripify(Vector & primitives, Vector & 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 faces = primitives; Vector faceIndices = indices; // in: primitives better just be faces and better use indexes for (i=0; i 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 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 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; inumDetails; 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 remap; Vector smooth; smooth.setSize(sortMesh->verts.size()); for (S32 k=0; knumFrames = 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 (d0.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 (d0 && t<1) { Point3F test = v23; test *= t; test += v3; d = mSqrt(mDot(v0-test,v0-test)); if (d0 && t<1) { Point3F test = v31; test *= t; test += v1; d = mSqrt(mDot(v0-test,v0-test)); if (d & loVerts = loMesh->getMeshType()==TSMesh::SkinMeshType ? ((TSSkinMesh*)loMesh)->initialVerts : loMesh->verts; Vector & 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; iiprimitives.size(); ii++) { TSDrawPrimitive & hiDraw = hiMesh->primitives[ii]; for (S32 i=0; iindices[hiDraw.start+i]]; F32 closestDist = 10E30f; bool foundSomething = false; for (S32 j=0; jprimitives.size(); j++) { TSDrawPrimitive & draw = loMesh->primitives[j]; Point3F v1; Point3F v2; Point3F v3; S32 numElements = 0; while (numElementsindices[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 (distmaxDist) maxDist = closestDist; total += closestDist; count++; } } } return maxDist; }