//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "max2dtsExporter/maxUtil.h" #pragma pack(push,8) #include #include #include #include #pragma pack(pop) #include "Core/tVector.h" // Special for biped stuff -- class id in no header, so we just def it here // NOTE/WARNING: This could change with Character Studio updates #define BipedObjectClassID Class_ID(37157,0) Point3F & Point3ToPoint3F(Point3 & p3, Point3F & p3f) { p3f.x = p3.x; p3f.y = p3.y; p3f.z = p3.z; return p3f; } void convertToTransform(Matrix3 & mat, Quat16 & rot, Point3F & trans, Quat16 & srot, Point3F & scale) { AffineParts parts; decomp_affine(mat,&parts); Point3ToPoint3F(parts.t,trans); rot.set(QuatF(parts.q[0],parts.q[1],parts.q[2],parts.q[3])); srot.set(QuatF(parts.u[0],parts.u[1],parts.u[2],parts.u[3])); Point3ToPoint3F(parts.k,scale); } MatrixF & convertToMatrixF(Matrix3 & mat3,MatrixF &matf) { matf.identity(); Point3F x,y,z,p; Point3 x3 = mat3 * Point3(1.0f,0.0f,0.0f); Point3 y3 = mat3 * Point3(0.0f,1.0f,0.0f); Point3 z3 = mat3 * Point3(0.0f,0.0f,1.0f); Point3 p3 = mat3 * Point3(0.0f,0.0f,0.0f); x = Point3ToPoint3F(x3,x); y = Point3ToPoint3F(y3,y); z = Point3ToPoint3F(z3,z); p = Point3ToPoint3F(p3,p); x -= p; y -= p; z -= p; matf.setColumn(0,x); matf.setColumn(1,y); matf.setColumn(2,z); matf.setColumn(3,p); return matf; } void getNodeTransform(INode *pNode, S32 time, Quat16 & rot, Point3F & trans, Quat16 & srot, Point3F & scale) { Matrix3 nodeMat = pNode->GetNodeTM( time ); convertToTransform(nodeMat,rot,trans,srot,scale); } TriObject * getTriObject( INode *pNode, S32 time, S32 multiResVerts, bool & deleteIt) { TriObject * tri = NULL; IParamBlock * paramBlock = NULL; // if we're a multiRes object, dial us down if (multiResVerts>0) { Animatable * obj = (Animatable*)pNode->GetObjectRef(); for (S32 i=0; iNumSubs(); i++) { if (!dStrcmp(obj->SubAnimName(i),"MultiRes")) { paramBlock = (IParamBlock*)obj->SubAnim(i)->SubAnim(0); Interval range = pNode->GetTimeRange(TIMERANGE_ALL|TIMERANGE_CHILDNODES|TIMERANGE_CHILDANIMS); paramBlock->SetValue(0,time,multiResVerts); break; } } } // if the object can't convert to a tri-mesh, eval world state to // get an object that can: const ObjectState &os = pNode->EvalWorldState(time); if ( os.obj->CanConvertToType(triObjectClassID) ) tri = (TriObject *)os.obj->ConvertToType( time, triObjectClassID ); deleteIt = (tri && (tri != os.obj)); return tri; } F32 findVolume(INode * pNode, S32 & polyCount) { bool deleteTri; TriObject * tri = getTriObject(pNode,DEFAULT_TIME,-1,deleteTri); if (!tri) { polyCount=0; return 0.0f; } Mesh & mesh = tri->mesh; polyCount = mesh.getNumFaces(); Box3F bounds; bounds.min.set( 10E30f, 10E30f, 10E30f); bounds.max.set(-10E30f,-10E30f,-10E30f); for (S32 i=0; iGetObjectRef(); if (!obj) return; Modifier * mod = NULL; // Is derived object ? S32 i; while (obj->SuperClassID() == GEN_DERIVOB_CLASS_ID) { IDerivedObject* dobj = (IDerivedObject*) obj; // Iterate over all entries of the modifier stack. for (i=0;iNumModifiers();i++) if (dobj->GetModifier(i)->ClassID() == SKIN_CLASSID) break; if (i!=dobj->NumModifiers()) { mod = dobj->GetModifier(i); break; } obj = dobj->GetObjRef(); } if (!mod) return; *skin = (ISkin*) mod->GetInterface(I_SKIN); if (!*skin) return; *skinData = (*skin)->GetContextInterface(pNode); if (!*skinData) *skin=NULL; // return both or neither } bool hasSkin(INode * pNode) { ISkin * skin; ISkinContextData * skinData; findSkinData(pNode,&skin,&skinData); return skin != NULL; } bool hasMesh(INode * pNode) { ObjectState os = pNode->EvalWorldState(0); return( os.obj->CanConvertToType(triObjectClassID) && !(os.obj->ClassID() == BipedObjectClassID) ); } void zapScale(Matrix3 & mat) { AffineParts parts; decomp_affine(mat,&parts); // now put the matrix back together again without the scale: // mat = mat.rot * mat.pos mat.IdentityMatrix(); mat.PreTranslate(parts.t); PreRotateMatrix(mat,parts.q); } Matrix3 & getLocalNodeMatrix(INode *pNode, INode *parent, S32 time, Matrix3 & matrix, AffineParts & a10, AffineParts & a20) { // Here's the story: the default transforms have no scale. In order to account for scale, the // scale at the default time is folded into the object offset (which is multiplied into the points // before exporting). Because of this, the local transform at a given time must take into account // the scale of the parent and child node at time 0 in addition to the current time. In particular, // the world transform at a given time is WT(time) = inverse(Tscale(0)) * T(time) // Note: above formula is max style. Torque style would have the order reveresed. :( // in order to avoid recomputing matrix at default time over and over, we assume that the first request // for the matrix will be at the default time, and thereafter, we will pass that matrix in and reuse it... Matrix3 m1 = pNode->GetNodeTM(time); Matrix3 m2 = parent ? parent->GetNodeTM(time) : Matrix3(true); if (time==DEFAULT_TIME) { decomp_affine(m1,&a10); decomp_affine(m2,&a20); } // build the inverse scale matrices Matrix3 stretchRot10,stretchRot20; Point3 sfactor10, sfactor20; Matrix3 invScale10, invScale20; a10.u.MakeMatrix(stretchRot10); a20.u.MakeMatrix(stretchRot20); sfactor10 = Point3(a10.f/a10.k.x,a10.f/a10.k.y,a10.f/a10.k.z); sfactor20 = Point3(a20.f/a20.k.x,a20.f/a20.k.y,a20.f/a20.k.z); invScale10 = Inverse(stretchRot10) * ScaleMatrix(sfactor10) * stretchRot10; invScale20 = Inverse(stretchRot20) * ScaleMatrix(sfactor20) * stretchRot20; // build world transforms m1 = invScale10 * m1; m2 = invScale20 * m2; // build local transform matrix = m1 * Inverse(m2); return matrix; } void getLocalNodeTransform(INode *pNode, INode *parent, AffineParts & child0, AffineParts & parent0, S32 time, Quat16 & rot, Point3F & trans, Quat16 & srot, Point3F & scale) { Matrix3 local; getLocalNodeMatrix(pNode,parent,time,local,parent0,child0); convertToTransform(local,rot,trans,srot,scale); } void getBlendNodeTransform(INode *pNode, INode *parent, AffineParts & child0, AffineParts & parent0, S32 time, S32 referenceTime, Quat16 & rot, Point3F & trans, Quat16 & srot, Point3F & scale) { Matrix3 m1; Matrix3 m2; getLocalNodeMatrix(pNode, parent, referenceTime, m1, child0, parent0); getLocalNodeMatrix(pNode, parent, time, m2, child0, parent0); m1 = Inverse(m1); m2 *= m1; convertToTransform(m2, rot,trans,srot,scale); } void computeObjectOffset(INode * pNode, Matrix3 & mat) { // compute the object offset transform... // this will be applied to all the verts of the meshes... // we grab this straight from the node, but we also // multiply in any scaling on the node transform because // the scaling will be stripped out when making a tsshape Matrix3 nodeMat = pNode->GetNodeTM(DEFAULT_TIME); zapScale(nodeMat); nodeMat = Inverse(nodeMat); mat = pNode->GetObjTMAfterWSM(DEFAULT_TIME); mat *= nodeMat; } void getDeltaTransform(INode * pNode, S32 time1, S32 time2, Quat16 & rot, Point3F & trans, Quat16 & srot, Point3F & scale) { Matrix3 m1 = pNode->GetNodeTM(time2); Matrix3 m2 = pNode->GetNodeTM(time1); m2 = Inverse(m2); m1 *= m2; convertToTransform(m1,rot,trans,srot,scale); } bool isVis(INode * pNode, S32 time) { return pNode->GetVisibility(time) > 0.0000001f; } F32 getVisValue(INode * pNode, S32 time) { return pNode->GetVisibility(time); } bool animatesVis(INode * pNode, const Interval & range, bool & error) { // running error...exit if already encountered an error if (error) return false; F32 startVis = getVisValue(pNode,range.Start()); for (S32 i=range.Start()+1; i<=range.End(); i++) if (mFabs(startVis-getVisValue(pNode,i))>0.01f) return true; return false; } bool animatesFrame(INode * pNode, const Interval & range, bool & error) { // running error...exit if already encountered an error if (error) return false; bool deleteIt; TriObject * tri = getTriObject(pNode,range.Start(),-1,deleteIt); if (!tri) { error = true; return false; } Interval ivalid; ivalid = tri->ChannelValidity(range.Start(), GEOM_CHAN_NUM); if (deleteIt) tri->DeleteMe(); return (ivalid.Start() > range.Start() || ivalid.End() < range.End()); } bool animatesMatFrame(INode * pNode, const Interval & range, bool & error) { // running error...exit if already encountered an error if (error) return false; bool deleteIt; TriObject * tri = getTriObject(pNode,range.Start(),-1,deleteIt); if (!tri) { error = true; return false; } Interval ivalid; ivalid = tri->ChannelValidity(range.Start(), TEXMAP_CHAN_NUM); if (deleteIt) tri->DeleteMe(); return (ivalid.Start() > range.Start() || ivalid.End() < range.End()); } bool isdigit(char ch) { return (ch>='0' && ch<='9'); } //---------------------------------------------------------------------------- // If 's' ends in a number, chopNum returns // a pointer to first digit in this number. // If not, then returns pointer to '\0' // at end of name or first of any trailing // spaces. char * chopNum(char * s) { if (s==NULL) return NULL; char * p = s + dStrlen(s); if (p==s) return s; p--; // trim spaces from the end while (p!=s && *p==' ') p--; // back up until we reach a non-digit // gotta be better way than this... if (isdigit(*p)) do { if (p--==s) return p+1; } while (isdigit(*p)); // allow negative numbers if (*p=='-') p--; // trim spaces separating name and number while (*p==' ') { p--; if (p==s) return p; } // return first space if there was one, // o.w. return first digit return p+1; } //---------------------------------------------------------------------------- // separates nodes names into base name // and detail number char * chopTrailingNumber(const char * fullName, S32 & size) { if (!fullName) { size = -1; return NULL; } char * buffer = dStrdup(fullName); char * p = chopNum(buffer); if (*p=='\0') { size = -1; return buffer; } size = dAtoi(p); *p='\0'; // terminate string return buffer; } //---------------------------------------------------------------------------- // returns a list of detail levels of this shape...grabs meshes off shape // and finds meshes on root level with same name but different trailing // detail number (i.e., head32 on shape, head16 at root, leads to detail // levels 32 and 16). void findDetails(INode * base, INode * root, Vector & details) { S32 i,j,k; // run through all the descendents of this node // and look for numbers indicating detail levels Vector nodeStack; nodeStack.push_back(base); while (!nodeStack.empty()) { INode * node = nodeStack.last(); nodeStack.pop_back(); if (hasMesh(node)) { // have a mesh...a little obscure below: // add # of this mesh + any mesh w/ same // name on the root level to details vector S32 detailSize; char * baseName = chopTrailingNumber(node->GetName(),detailSize); INode * meshNode = node; for (j=-1;jNumberOfChildren();j++) { if (j>=0) meshNode = root->GetChildNode(j); if (!hasMesh(meshNode)) continue; char * meshName = chopTrailingNumber(meshNode->GetName(),detailSize); if (!dStricmp(baseName,meshName)) { for (k=0; kNumberOfChildren();j++) nodeStack.push_back(node->GetChildNode(j)); } } //---------------------------------------------------------------------------- // find selected subtrees (nodes on root level only) and embed in subtree of // the style the exporter looks for: // Root // | // |-base // |-start // | | // | |- // | | . // | | . // | |- // | // |-detail 1 // | . // | . // |-detail N void embedSubtree(Interface * ip) { S32 i,j,k; // make dummy node named baseName TSTR baseName("base"); ip->MakeNameUnique(baseName); INode * base = ip->CreateObjectNode(new DummyObject); base->SetName(baseName); // make dummy node named startName TSTR startName("start"); ip->MakeNameUnique(startName); INode * start = ip->CreateObjectNode(new DummyObject); start->SetName(startName); // link later to former base->AttachChild(start); Vector details; // loop through selection set, link to start (only if child of root) S32 count = ip->GetSelNodeCount(); INode * root = ip->GetRootNode(); for (i=0; iGetSelNode(i); if (!node->GetParentNode()->IsRootNode()) continue; start->AttachChild(node); findDetails(node,root,details); } // now create detail markers for (i=0; iCreateObjectNode(new DummyObject); dSprintf(detailName,20,"detail%i",details[i]); detailNode->SetName(detailName); base->AttachChild(detailNode); } } //---------------------------------------------------------------------------- void registerDetails(Interface * ip) { S32 i,j,k; // loop through selection set, only care about nodes off root S32 count = ip->GetSelNodeCount(); INode * root = ip->GetRootNode(); for (i=0; iGetSelNode(i); if (!node->GetParentNode()->IsRootNode()) continue; Vector details; // search branches of tree for meshes and find their details for (j=0; jNumberOfChildren(); j++) { INode * child = node->GetChildNode(j); if (child->NumberOfChildren()>0) findDetails(child,root,details); } // go through children of base node and cull detail numbers // that don't need to be added (because detail marker already // present) for (j=0; jNumberOfChildren(); j++) { INode * child = node->GetChildNode(j); if (child->NumberOfChildren()==0) { // look for # S32 detailSize; char * baseName = chopTrailingNumber(child->GetName(),detailSize); dFree(baseName); for (k=0;kCreateObjectNode(new DummyObject); dSprintf(detailName,20,"detail%i",details[j]); detailNode->SetName(detailName); node->AttachChild(detailNode); } } } //---------------------------------------------------------------------------- void renumberNodes(Interface * ip, S32 newSize) { S32 i, count = ip->GetSelNodeCount(); char newName[128]; for (i=0; iGetSelNode(i); S32 oldSize; char * baseName = chopTrailingNumber(node->GetName(),oldSize); dSprintf(newName,128,"%s%i",baseName,newSize); node->SetName(newName); dFree(baseName); } } // this version of doDot used by m_pointInPoly inline float doDot(F32 v1x, F32 v1y, F32 v2x, F32 v2y, F32 px, F32 py) { return (v1x-px) * (v1y-v2y) + (v1y-py) * (v2x-v1x); } //------------------------------------------------------------------------------------ // pointInPoly returns true if point is inside poly defined by verts on plane w/ // normal "normal" -- based on m_pointInTriangle. //------------------------------------------------------------------------------------ bool pointInPoly(const Point3F & point, const Point3F & normal, const Point3F * verts, U32 n) { F32 thisDot, lastDot=0; U32 i; // we can ignore one of the dimensions because all points are on the same plane... if (mFabs(normal.y)>mFabs(normal.x)&&mFabs(normal.y)>mFabs(normal.z)) { // drop y coord thisDot = doDot(verts[n-1].x,verts[n-1].z,verts[0].x,verts[0].z,point.x,point.z); if (thisDot*lastDot<0) return false; lastDot = thisDot; for (i=0;imFabs(normal.y)&&mFabs(normal.x)>mFabs(normal.z)) { // drop x coord thisDot = doDot(verts[n-1].y,verts[n-1].z,verts[0].y,verts[0].z,point.y,point.z); if (thisDot*lastDot<0) return false; lastDot = thisDot; for (i=0;iGetNodeTM( time ); convertToMatrixF(nodeMat,matrix); return matrix; } MatrixF & getLocalNodeMatrix(INode *pNode, INode *parent, S32 time, MatrixF & matrix) { if (!parent) return getNodeMatrix(pNode,time,matrix); MatrixF m1,m2; getNodeMatrix(pNode,time,m1); getNodeMatrix(parent,time ,m2); m2.inverse(); matrix.mul(m1,m2); return matrix; } */