tge/tools/maya2dtsExporter/mayaAppMesh.cpp
2025-02-17 23:17:30 -06:00

749 lines
24 KiB
C++
Executable File

//-----------------------------/------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifdef _MSC_VER
#pragma warning(disable : 4786)
#endif
#include "mayaAppMesh.h"
#include "mayaAppNode.h"
#include "appConfig.h"
#include "appIfl.h"
#include <maya/MFnDependencyNode.h>
#include <maya/MFnDagNode.h>
#include <maya/MGlobal.h>
#include <maya/MTime.h>
#include <maya/MDistance.h>
#include <maya/MMatrix.h>
#include <maya/MPlug.h>
#include <maya/MDGContext.h>
#include <maya/MAnimUtil.h>
#include <maya/MFnMesh.h>
#include <maya/MFnSet.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MItMeshVertex.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MItGeometry.h>
#include <maya/MFnSkinCluster.h>
#include <maya/MDagPathArray.h>
#include <maya/MFnReflectShader.h>
namespace DTS
{
bool getMeshAndSkin(MDagPath & path, MFnSkinCluster & skinCluster)
{
MStatus status;
// The following is the "correct" way to get the
// mesh and skinCluster in case the mesh is a skin,
// according to (and adapted from):
// http://www.greggman.com/pages/mayastuff.htm
// But we don't get the MeshFn from the input plug of the
// skinCluster as he does. Two reasons for this:
// 1) couldn't get it to work properly, not a valid MeshFn.
// 2) we want the final mesh, not the intermediate product.
// the deformed mesh comes into the visible mesh
// through its "inmesh" plug
MPlug inMeshPlug = MFnDagNode(path).findPlug("inMesh", &status);
if (status != MS::kSuccess || !inMeshPlug.isConnected())
return false; // no skin found
// walk the tree of stuff upstream from this plug
MItDependencyGraph dgIt(inMeshPlug,MFn::kInvalid,
MItDependencyGraph::kUpstream,
MItDependencyGraph::kDepthFirst,
MItDependencyGraph::kPlugLevel,
&status);
if (MS::kSuccess == status)
{
S32 count = 0;
dgIt.disablePruningOnFilter();
for ( ; ! dgIt.isDone(); dgIt.next() )
{
MObject thisNode = dgIt.thisNode();
// go until we find a skinCluster
if (thisNode.apiType() == MFn::kSkinClusterFilter)
{
skinCluster.setObject(thisNode);
return true; // found a skin
}
}
}
return false; // no skin found
}
MayaAppMesh::MayaAppMesh(MDagPath & nodePath, MDagPath & path)
{
mPath = path;
mNodePath = nodePath;
mName = NULL;
}
MayaAppMesh::~MayaAppMesh()
{
}
void MayaAppMesh::getSkinData()
{
MFnSkinCluster skinCluster;
if (!getMeshAndSkin(mPath,skinCluster))
return; // no skin, no sweat
MStatus status;
S32 i;
// get bones/influence array
MDagPathArray bones;
S32 numBones = skinCluster.influenceObjects(bones, &status);
if (!CheckMayaStatus(status))
return;
S32 bone;
for (bone=0; bone<numBones; bone++)
{
AppConfig::PrintDump(PDObjectStateDetails,avar("Adding bone %s\r\n",bones[bone].partialPathName().asChar()));
mBones.push_back(new MayaAppNode(bones[bone]));
mWeights.push_back(new std::vector<F32>);
}
MItGeometry gitr(mPath,&status);
if (!CheckMayaStatus(status))
return;
AppConfig::PrintDump(PDObjectStateDetails,avar("SkinPath: %s, # verts: %i, # bones: %i",
mPath.partialPathName().asChar(),gitr.count(),numBones));
for (; !gitr.isDone(); gitr.next())
{
while ( numBones && mWeights[0]->size() <= gitr.index())
for (i=0; i<numBones; i++)
mWeights[i]->push_back(0.0f);
MObject comp = gitr.component(&status);
if (!CheckMayaStatus(status))
return;
MFloatArray wts;
U32 weightCount;
status = skinCluster.getWeights(mPath,comp,wts,weightCount);
if (!CheckMayaStatus(status))
return;
if (weightCount != numBones)
{
AppConfig::SetExportError("Assertion failed on skinned mesh");
return;
}
for (i=0; i<numBones; i++)
(*mWeights[i])[gitr.index()] = wts[i];
}
// make sure we have weights for all bones and verts
S32 numVerts = MFnMesh(mPath,&status).numVertices();
if (!CheckMayaStatus(status))
return;
while ( numBones && mWeights[0]->size() < numVerts)
for (i=0; i<numBones; i++)
mWeights[i]->push_back(0.0f);
}
const char * MayaAppMesh::getName()
{
MStatus status;
if (!mName)
{
MFnDagNode fnNode(mNodePath,&status);
CheckMayaStatus(status);
const char * newname = fnNode.name(&status).asChar();
CheckMayaStatus(status);
if (newname)
{
mName = strnew(newname);
HandleMayaNegative(mName);
}
}
CheckMayaStatus(status);
return mName;
}
Matrix<4,4,F32> MayaAppMesh::getMeshTransform(const AppTime & time)
{
MStatus status;
MayaSetTime(time);
MMatrix mayaMat = mPath.inclusiveMatrix(&status);
CheckMayaStatus(status);
return MayaToDtsTransform(mayaMat);
}
F32 MayaAppMesh::getVisValue(const AppTime & time)
{
// Need to check for "visibility" plug, which is built into
// maya and is a bool, and "vis" plug, which is custom and
// assumed to be a float. We added the "vis" plug to allow
// non-bool vis values. The "vis" plug takes precedence.
MayaSetTime(time);
F32 val = 1.0f;
if (getFloat("vis",val))
return val;
bool ret = true;
getBool("visibility",ret);
return ret ? 1.0f : 0.0f;
}
bool MayaAppMesh::getFloat(const char * propName, F32 & defaultVal)
{
MStatus status;
MFnDagNode fnNode(mNodePath,&status);
CheckMayaStatus(status);
MPlug plug = fnNode.findPlug(MString(propName),&status);
if (status!=MStatus::kSuccess)
return false;
F32 val;
status = plug.getValue(val);
if (status == MStatus::kSuccess)
defaultVal = val;
return status==MStatus::kSuccess;
}
bool MayaAppMesh::getInt(const char * propName, S32 & defaultVal)
{
MStatus status;
MFnDagNode fnNode(mNodePath,&status);
CheckMayaStatus(status);
MPlug plug = fnNode.findPlug(MString(propName),&status);
if (status!=MStatus::kSuccess)
return false;
S32 val;
status = plug.getValue(val);
if (status == MStatus::kSuccess)
defaultVal = val;
return status==MStatus::kSuccess;
}
bool MayaAppMesh::getBool(const char * propName, bool & defaultVal)
{
MStatus status;
MFnDagNode fnNode(mNodePath,&status);
CheckMayaStatus(status);
MPlug plug = fnNode.findPlug(MString(propName),&status);
if (status!=MStatus::kSuccess)
return false;
bool val;
status = plug.getValue(val);
if (status == MStatus::kSuccess)
defaultVal = val;
return status==MStatus::kSuccess;
}
bool MayaAppMesh::getMaterial(S32 matIdx, Material & mat)
{
if (matIdx>=0 && matIdx<mMaterials.size())
{
mat = mMaterials[matIdx];
return true;
}
return false;
}
bool MayaAppMesh::animatesVis(const AppSequenceData & seqData)
{
MStatus status;
// Need to check to see if either "visibility" or "vis" is animated.
// "visibility" is the built-in visibility mechanism, but it is
// only a bool. So we also look for custom "vis" which is assumed
// to be a float.
MFnDagNode fnNode(mNodePath,&status);
CheckMayaStatus(status);
MPlug plug = fnNode.findPlug(MString("visibility"),&status);
CheckMayaStatus(status);
if (status!=MStatus::kSuccess)
return false;
if (MAnimUtil::isAnimated(plug,false,&status))
return Parent::animatesVis(seqData);
CheckMayaStatus(status);
// don't animate "visibility", what about "vis"
MPlug plug2 = fnNode.findPlug(MString("vis"),&status);
if (status!=MStatus::kSuccess)
return false;
if (MAnimUtil::isAnimated(plug2,false,&status))
return Parent::animatesVis(seqData);
return false;
}
AppMeshLock MayaAppMesh::lockMesh(const AppTime & time, const Matrix<4,4,F32> & objectOffset)
{
S32 i;
MayaSetTime(time);
MStatus status;
MObject component = MObject::kNullObj; // not really sure what this is for...
MFnMesh fnMesh(mPath,&status);
if (!CheckMayaStatus(status))
return (AppMeshLock&)Parent::lockMesh(time,objectOffset);
MItMeshPolygon polyIter(mPath,component,&status);
if (!CheckMayaStatus(status))
return (AppMeshLock&)Parent::lockMesh(time,objectOffset);
S32 polyVertexCount = polyIter.polygonVertexCount();
if (polyVertexCount != 3)
{
AppConfig::SetExportError("Non-triangle mesh not supported");
return (AppMeshLock&)Parent::lockMesh(time,objectOffset);
}
MItMeshVertex vtxIter(mPath,component,&status);
if (!CheckMayaStatus(status))
return (AppMeshLock&)Parent::lockMesh(time,objectOffset);
// Create transform based on objectOffset for transforming normals
Matrix<4,4,F32> normOffset = objectOffset;
normOffset[0][3] = 0.0f;
normOffset[1][3] = 0.0f;
normOffset[2][3] = 0.0f;
normOffset[3][0] = 0.0f;
normOffset[3][1] = 0.0f;
normOffset[3][2] = 0.0f;
normOffset[3][3] = 1.0f;
F32 normScale = 0.0f;
for (i=0; i<9; i++)
{
S32 j = i % 3;
normScale += normOffset[(i-j)/3][j];
}
normScale /= 3.0f;
normScale = 1.0f/normScale;
// Write out the vertex table
//
std::vector<Point3D> verts;
std::vector<Point2D> tverts;
for ( ; !vtxIter.isDone(); vtxIter.next() )
{
MPoint p = vtxIter.position( MSpace::kObject );
// convert from maya point to dts point...
// ...also, convert from internal units
// to the current ui units
Point3D pos;
MDistance dist; // starts as internal units
dist.setValue(p.x);
pos[0] = dist.asMeters();
dist.setValue(p.y);
pos[1] = dist.asMeters();
dist.setValue(p.z);
pos[2] = dist.asMeters();
verts.push_back(objectOffset * pos);
}
MFloatArray uArray, vArray;
fnMesh.getUVs( uArray, vArray );
S32 uvLength = uArray.length();
for (i=0; i<uvLength; i++ )
{
Point2D tvert(uArray[i],1.0f-vArray[i]);
tverts.push_back(tvert);
}
for ( ; !polyIter.isDone(); polyIter.next() )
{
Primitive face;
face.firstElement = mIndices.size();
face.numElements = 3;
face.type = Primitive::Triangles|Primitive::Indexed|Primitive::NoMaterial;
for (S32 vtx=polyVertexCount-1; vtx>=0; vtx-- )
{
U16 vertId = polyIter.vertexIndex(vtx);
Point3D vert = verts[vertId];
// compute normal (TODO: handle non-uniform scale properly)
MVector mayaNorm;
status = polyIter.getNormal(vtx,mayaNorm,MSpace::kObject);
if (!CheckMayaStatus(status))
return (AppMeshLock&)Parent::lockMesh(time,objectOffset);
Point3D norm(mayaNorm[0],mayaNorm[1],mayaNorm[2]);
norm = normOffset * norm;
norm *= normScale;
if (norm.length()>0.001f)
norm.normalize();
else
norm = Point3D(0,0,1);
// grab tvert
Point2D tvert(0,0);
if (fnMesh.numUVs() > 0)
{
if (polyIter.hasUVs())
{
S32 uvIndex;
if (polyIter.getUVIndex(vtx,uvIndex))
tvert = tverts[uvIndex];
}
}
mIndices.push_back(addVertex(vert,norm,tvert,vertId));
}
mFaces.push_back(face);
}
getMaterials();
// are we 2-sided?
bool twoSided = false;
getBool("twoSided",twoSided);
if (twoSided)
{
S32 numFaces = mFaces.size();
for (i=0; i<numFaces; i++)
{
S32 first = mFaces[i].firstElement;
Primitive face;
face.firstElement = mIndices.size();
face.numElements = 3;
face.type = mFaces[i].type;
Point3D vert = mVerts[mIndices[first + 0]];
Point2D tvert = mTVerts[mIndices[first + 0]];
Point3D norm = -mNormals[mIndices[first + 0]];
S32 vertId = mVertId[mIndices[first + 0]];
mIndices.push_back(addVertex(vert,norm,tvert,vertId));
vert = mVerts[mIndices[first + 2]];
tvert = mTVerts[mIndices[first + 2]];
norm = -mNormals[mIndices[first + 2]];
vertId = mVertId[mIndices[first + 2]];
mIndices.push_back(addVertex(vert,norm,tvert,vertId));
vert = mVerts[mIndices[first + 1]];
tvert = mTVerts[mIndices[first + 1]];
norm = -mNormals[mIndices[first + 1]];
vertId = mVertId[mIndices[first + 1]];
mIndices.push_back(addVertex(vert,norm,tvert,vertId));
mFaces.push_back(face);
}
}
return (AppMeshLock&)Parent::lockMesh(time,objectOffset);
}
void MayaAppMesh::getMaterials()
{
// Maya material extraction code pulled from
// findTexturesPerPolyCmd.cpp from Maya sample plugins.
MStatus status;
S32 instanceNum = 0;
if (mPath.isInstanced())
instanceNum = mPath.instanceNumber();
// Get a list of all sets pertaining to the selected shape and the
// members of those sets.
MFnMesh fnMesh(mPath);
MObjectArray sets;
MObjectArray comps;
if (!fnMesh.getConnectedSetsAndMembers(instanceNum, sets, comps, true))
{
AppConfig::SetExportError("ERROR: getMaterials: MFnMesh::getConnectedSetsAndMembers\r\n");
return;
}
// This is a (hack) workaround. If we already have got at least one
// material, don't accept mesh iterators that include the whole mesh
// (would be an error anyway).
bool gotOne = false;
// Loop through all the sets. If the set is a polygonal set, find the
// shader attached to the and print out the texture file name for the
// set along with the polygons in the set.
//
for ( S32 i=0; i<sets.length(); i++ )
{
MObject set = sets[i];
MObject comp = comps[i];
MFnSet fnSet( set, &status );
if (MS::kFailure == status)
{
AppConfig::SetExportError("ERROR: getMaterials: MFnSet::MFnSet\r\n");
return;
}
// Special (hack) case in which we have an iterator over everything
// yet we already have got at least one material...
if (gotOne && (comp.apiType() == MFn::kInvalid))
continue;
// Make sure the set is a polygonal set. If not, continue.
MItMeshPolygon piter(mPath, comp, &status);
if (MS::kFailure == status)
continue;
// Find the texture that is applied to this set. First, get the
// shading node connected to the set. Then, if there is an input
// attribute called "color", search upstream from it for a texture
// file node.
MObject shaderNode = findShader(set);
if (shaderNode == MObject::kNullObj)
continue;
MPlug colorPlug = MFnDependencyNode(shaderNode).findPlug("color", &status);
if (status == MS::kFailure)
continue;
MItDependencyGraph dgIt(colorPlug, MFn::kFileTexture,
MItDependencyGraph::kUpstream,
MItDependencyGraph::kBreadthFirst,
MItDependencyGraph::kNodeLevel,
&status);
if (status == MS::kFailure)
continue;
dgIt.disablePruningOnFilter();
// If no texture file node was found, just continue.
if (dgIt.isDone())
continue;
// Print out the texture node name and texture file that it references.
//
MObject textureNode = dgIt.thisNode();
MPlug filenamePlug = MFnDependencyNode(textureNode).findPlug("fileTextureName");
MString textureName;
filenamePlug.getValue(textureName);
// look for filename plug on transparency channel -- if it has one, material is translucent
bool hasTransparency = false;
MPlug transPlug = MFnDependencyNode(shaderNode).findPlug("transparency", &status);
if (status == MS::kFailure)
continue;
dgIt.resetTo(transPlug, MFn::kFileTexture,
MItDependencyGraph::kUpstream,
MItDependencyGraph::kBreadthFirst,
MItDependencyGraph::kNodeLevel);
dgIt.disablePruningOnFilter();
// If no texture file node was found, just continue.
if (!dgIt.isDone())
{
// Print out the texture node name and texture file that it references.
//
MObject textureNode = dgIt.thisNode();
MPlug filenamePlug = MFnDependencyNode(textureNode).findPlug("fileTextureName");
MString textureName;
filenamePlug.getValue(textureName);
if (textureName.length() != 0)
hasTransparency = true;
}
for (; !piter.isDone(); piter.next())
{
S32 faceIdx = piter.index();
if (mFaces[faceIdx].type & Primitive::NoMaterial)
{
mFaces[faceIdx].type ^= Primitive::NoMaterial;
mFaces[faceIdx].type |= mMaterials.size();
// we have at least one material now
gotOne = true;
}
else
AppConfig::SetExportError("Mesh::getMaterials: face with multiple materials found");
}
Material mat;
mat.name = textureName.asChar();
mat.reflectance = -1;
mat.bump = -1;
mat.detail = -1;
mat.detailScale = 1.0f;
mat.reflection = 0.0f;
mat.flags = Material::SWrap|Material::TWrap|Material::NeverEnvMap;
MFnLambertShader lshader(shaderNode,&status);
if (status == MStatus::kSuccess)
{
F32 translucence = lshader.translucenceCoeff();
F32 selfIlluminating = lshader.glowIntensity();
if (translucence>0.1f || hasTransparency)
mat.flags |= Material::Translucent;
if (selfIlluminating>0.1f)
mat.flags |= Material::SelfIlluminating;
}
MFnReflectShader rshader(shaderNode,&status);
if (status == MStatus::kSuccess)
{
F32 reflectivity = rshader.reflectivity();
if (reflectivity>0.01f)
{
mat.reflection = reflectivity;
mat.flags ^= Material::NeverEnvMap;
}
}
// now get the texture placement node
status = dgIt.resetTo(colorPlug, MFn::kPlace2dTexture,
MItDependencyGraph::kUpstream,
MItDependencyGraph::kBreadthFirst,
MItDependencyGraph::kNodeLevel);
dgIt.disablePruningOnFilter();
if (status != MS::kFailure)
if (!dgIt.isDone())
{
MObject placeNode = dgIt.thisNode();
MStatus stat;
MFnDependencyNode fnNode(placeNode,&stat);
bool swrap = true;
bool twrap = true;
MPlug plug = fnNode.findPlug(MString("wrapU"),&stat);
plug.getValue(swrap);
plug = fnNode.findPlug(MString("wrapV"),&stat);
plug.getValue(swrap);
if (!swrap)
mat.flags ^= Material::SWrap;
if (!twrap)
mat.flags ^= Material::TWrap;
}
/*
Need to deal with these flags:
Additive = 0x00000008,
Subtractive = 0x00000010,
IFLMaterial = 0x08000000,
*/
//------------
// If this is an ifl, then create the ifl material if it doesn't exist and mark as ifl
const char * dot = strchr(textureName.asChar(),'.');
if (dot && !stricmp(dot+1,"ifl"))
{
mat.flags |= Material::IFLMaterial;
S32 matIdx = mMaterials.size();
while (mIfls.size()<=matIdx)
mIfls.push_back(NULL);
if (!mIfls[matIdx])
mIfls[matIdx] = new AppIfl(mat.name.c_str());
}
mMaterials.push_back(mat);
}
}
MObject MayaAppMesh::findShader( MObject& setNode )
{
// Description: Find the shading node for the given shading group set node.
MFnDependencyNode fnNode(setNode);
MPlug shaderPlug = fnNode.findPlug("surfaceShader");
if (!shaderPlug.isNull())
{
MPlugArray connectedPlugs;
bool asSrc = false;
bool asDst = true;
shaderPlug.connectedTo( connectedPlugs, asDst, asSrc );
if (connectedPlugs.length() != 1)
AppConfig::SetExportError("Error getting shader\r\n");
else
return connectedPlugs[0].node();
}
return MObject::kNullObj;
}
void MayaAppMesh::unlockMesh()
{
Parent::unlockMesh();
// no more cleanup...but if there were some to do, we'd do it here
}
void MayaSetTime(const AppTime & time)
{
MTime mayaTime(time.getF64(),MTime::kSeconds);
MGlobal::viewFrame(mayaTime);
}
Matrix<4,4,F32> MayaToDtsTransform(MMatrix & mayaMat)
{
Matrix<4,4,F32> mat;
for (S32 i=0; i<4; i++)
for (S32 j=0; j<4; j++)
mat[j][i] = mayaMat[i][j];
F64 scale;
MDistance dist; // starts as internal units
dist.setValue(1.0f);
scale = dist.asMeters();
mat[0][3] *= scale;
mat[1][3] *= scale;
mat[2][3] *= scale;
return mat;
}
bool CheckMayaStatus(MStatus & status)
{
if (!bool(status))
{
AppConfig::SetExportError(avar("Maya error: %s",status.errorString().asChar()));
status = MStatus::kSuccess;
return false;
}
return true;
}
void HandleMayaNegative(char * name)
{
S32 pos = strlen(name);
while (pos)
{
--pos;
if (isdigit(name[pos]))
continue;
if (name[pos]=='_')
name[pos]='-';
break;
}
}
}; // namespace DTS