484 lines
16 KiB
C++
Executable File
484 lines
16 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "maxAppMesh.h"
|
|
#include "maxAppNode.h"
|
|
#include "appConfig.h"
|
|
#include "appIfl.h"
|
|
#include "skinHelper.h"
|
|
|
|
#pragma pack(push,8)
|
|
#include <max.h>
|
|
#include <iparamb2.h>
|
|
#include <ISkin.h>
|
|
#include <modstack.h>
|
|
#include <istdplug.h>
|
|
#include <stdmat.h>
|
|
#include <maxtypes.h>
|
|
#pragma pack(pop)
|
|
|
|
//----------------------------------------------------------------
|
|
// 3dsMax utility function:
|
|
// Grab the tri object from the max mesh node
|
|
//----------------------------------------------------------------
|
|
TriObject * getTriObject( INode *pNode, S32 time, bool & deleteIt)
|
|
{
|
|
TriObject * tri = NULL;
|
|
IParamBlock * paramBlock = NULL;
|
|
|
|
// 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;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// 3dsMax utility function:
|
|
// Grab the skin data from the max mesh node
|
|
//----------------------------------------------------------------
|
|
void findSkinData(INode * pNode, ISkin **skin, ISkinContextData ** skinData)
|
|
{
|
|
// till proven otherwise...
|
|
*skin = NULL;
|
|
*skinData = NULL;
|
|
|
|
// Get object from node. Abort if no object.
|
|
::Object* obj = pNode->GetObjectRef();
|
|
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;i<dobj->NumModifiers();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
|
|
}
|
|
|
|
namespace DTS
|
|
{
|
|
MaxAppMesh::MaxAppMesh(INode * maxNode, AppNode * appNode)
|
|
{
|
|
mMaxNode = maxNode;
|
|
mAppNode = appNode;
|
|
}
|
|
|
|
Matrix<4,4,F32> MaxAppMesh::getMeshTransform(const AppTime & time)
|
|
{
|
|
assert(!mLocked && "Mesh is locked");
|
|
|
|
Matrix<4,4,F32> ret;
|
|
Matrix3 mat = mMaxNode->GetObjTMAfterWSM(SecToTicks(time.getF32()));
|
|
return convertFromMaxMatrix(mat);
|
|
}
|
|
|
|
F32 MaxAppMesh::getVisValue(const AppTime & time)
|
|
{
|
|
assert(!mLocked && "Mesh is locked");
|
|
|
|
return mMaxNode->GetVisibility(SecToTicks(time.getF32()));
|
|
}
|
|
|
|
bool MaxAppMesh::getMaterial(S32 matIdx, Material & mat)
|
|
{
|
|
mat.reflectance = -1;
|
|
mat.bump = -1;
|
|
mat.detail = -1;
|
|
mat.detailScale = 1.0f;
|
|
mat.reflection = 1.0f;
|
|
mat.flags = 0;
|
|
|
|
// do some hocus pocus to get the material....
|
|
|
|
Mtl * mtl = mMaxNode->GetMtl();
|
|
if( !mtl )
|
|
return false;
|
|
|
|
if( mtl->ClassID() == Class_ID(MULTI_CLASS_ID,0) )
|
|
{
|
|
MultiMtl * multiMtl = (MultiMtl*)mtl;
|
|
if (multiMtl->NumSubMtls()==0)
|
|
return false;
|
|
matIdx %= multiMtl->NumSubMtls();
|
|
|
|
mtl = multiMtl->GetSubMtl( matIdx );
|
|
}
|
|
|
|
if( mtl->ClassID() != Class_ID(DMTL_CLASS_ID,0) )
|
|
{
|
|
AppConfig::SetExportError(avar("Unexpected material type on mesh \"%s\".",mMaxNode->GetName()));
|
|
return false;
|
|
}
|
|
|
|
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... :)
|
|
|
|
// get diffuse map...
|
|
|
|
if (stdMat->GetSubTexmap(ID_DI) == NULL || !stdMat->MapEnabled(ID_DI))
|
|
// no diffuse...
|
|
return false;
|
|
|
|
if (stdMat->GetSubTexmap(ID_DI)->ClassID() != Class_ID(BMTEX_CLASS_ID,0))
|
|
{
|
|
AppConfig::SetExportError(avar("Diffuse channel on mesh \"%s\" has a non-bitmap texture map.",mMaxNode->GetName()));
|
|
return false;
|
|
}
|
|
|
|
BitmapTex * diffuse = (BitmapTex*)stdMat->GetSubTexmap(ID_DI);
|
|
mat.name = diffuse->GetMapName();
|
|
|
|
// set up texture flags
|
|
if (stdMat->MapEnabled(ID_OP))
|
|
{
|
|
// note: translucent if opacity channel is enabled
|
|
mat.flags |= Material::Translucent;
|
|
if (stdMat->GetTransparencyType() == TRANSP_ADDITIVE)
|
|
mat.flags |= Material::Additive;
|
|
else if (stdMat->GetTransparencyType() == TRANSP_SUBTRACTIVE)
|
|
mat.flags |= Material::Subtractive;
|
|
}
|
|
if (!stdMat->MapEnabled(ID_RL))
|
|
// only environment map if reflectance check box is set (but no material necessary)
|
|
mat.flags |= Material::NeverEnvMap;
|
|
mat.reflectance = stdMat->GetTexmapAmt(ID_RL,0);
|
|
|
|
if (diffuse->GetUVGen()->GetTextureTiling() & U_WRAP)
|
|
mat.flags |= Material::SWrap;
|
|
if (diffuse->GetUVGen()->GetTextureTiling() & V_WRAP)
|
|
mat.flags |= Material::TWrap;
|
|
if ( stdMat->GetSelfIllum(0) > 0.99f)
|
|
mat.flags |= Material::SelfIlluminating;
|
|
|
|
//------------
|
|
// If this is an ifl, then create the ifl material if it doesn't exist and mark as ifl
|
|
const char * dot = strchr(mat.name.c_str(),'.');
|
|
if (dot && !stricmp(dot+1,"ifl"))
|
|
{
|
|
mat.flags |= Material::IFLMaterial;
|
|
while (mIfls.size()<=matIdx)
|
|
mIfls.push_back(NULL);
|
|
if (!mIfls[matIdx])
|
|
mIfls[matIdx] = new AppIfl(mat.name.c_str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MaxAppMesh::animatesFrame(const AppSequenceData & seqData)
|
|
{
|
|
assert(!mLocked && "Mesh is locked");
|
|
|
|
S32 startTime = SecToTicks(seqData.startTime.getF32());
|
|
S32 endTime = SecToTicks(seqData.endTime.getF32());
|
|
|
|
bool deleteIt;
|
|
TriObject * tri = getTriObject(mMaxNode,startTime,deleteIt);
|
|
if (!tri)
|
|
return false;
|
|
|
|
Interval ivalid;
|
|
ivalid = tri->ChannelValidity(startTime, GEOM_CHAN_NUM);
|
|
|
|
if (deleteIt)
|
|
tri->DeleteMe();
|
|
|
|
return (ivalid.Start() > startTime || ivalid.End() < endTime);
|
|
}
|
|
|
|
bool MaxAppMesh::animatesMatFrame(const AppSequenceData & seqData)
|
|
{
|
|
assert(!mLocked && "Mesh is locked");
|
|
|
|
S32 startTime = SecToTicks(seqData.startTime.getF32());
|
|
S32 endTime = SecToTicks(seqData.endTime.getF32());
|
|
|
|
bool deleteIt;
|
|
TriObject * tri = getTriObject(mMaxNode,startTime,deleteIt);
|
|
if (!tri)
|
|
return false;
|
|
|
|
Interval ivalid;
|
|
ivalid = tri->ChannelValidity(startTime, TEXMAP_CHAN_NUM);
|
|
|
|
if (deleteIt)
|
|
tri->DeleteMe();
|
|
|
|
return (ivalid.Start() > startTime || ivalid.End() < endTime);
|
|
}
|
|
|
|
AppMeshLock MaxAppMesh::lockMesh(const AppTime & time, const Matrix<4,4,F32> & objectOffset)
|
|
{
|
|
// are we mirrored?
|
|
AffineParts parts;
|
|
decomp_affine(objectOffset,&parts);
|
|
bool mirror = parts.sign < 0.0f;
|
|
|
|
bool delTri;
|
|
TriObject * tri = getTriObject(mMaxNode,SecToTicks(time.getF32()),delTri);
|
|
::Mesh & maxMesh = tri->mesh;
|
|
|
|
S32 i;
|
|
S32 lastMatIdx = -1;
|
|
Matrix3 uvTransform;
|
|
|
|
// start lists empty
|
|
mFaces.clear();
|
|
mVerts.clear();
|
|
mTVerts.clear();
|
|
mIndices.clear();
|
|
mSmooth.clear();
|
|
mVertId.clear();
|
|
|
|
// start out with faces and crop data allocated
|
|
mFaces.resize(maxMesh.getNumFaces());
|
|
|
|
// if no faces, detect it (use redundant test just in case
|
|
bool tmap = maxMesh.mapSupport(1) && maxMesh.tVerts; // 1 == default map
|
|
|
|
// get faces, points & materials
|
|
for (i=0; i<mFaces.size();i++)
|
|
{
|
|
Face & maxFace = maxMesh.faces[i];
|
|
Primitive & tsFace = mFaces[i];
|
|
|
|
// set faces material index
|
|
tsFace.type = maxFace.getMatID(); // use max mat idx (get's converted by shapemimic)
|
|
tsFace.firstElement = mIndices.size();
|
|
tsFace.numElements = 3;
|
|
tsFace.type |= Primitive::Triangles|Primitive::Indexed;
|
|
|
|
// set vertex indices
|
|
S32 idx0 = maxFace.v[0];
|
|
S32 idx1 = maxFace.v[2]; // switch the order to be CW
|
|
S32 idx2 = maxFace.v[1]; // switch the order to be CW
|
|
if (mirror)
|
|
{
|
|
S32 tmp = idx1;
|
|
idx1 = idx2;
|
|
idx2 = tmp;
|
|
}
|
|
Point3D v0(maxMesh.verts[idx0].x,maxMesh.verts[idx0].y,maxMesh.verts[idx0].z);
|
|
Point3D v1(maxMesh.verts[idx1].x,maxMesh.verts[idx1].y,maxMesh.verts[idx1].z);
|
|
Point3D v2(maxMesh.verts[idx2].x,maxMesh.verts[idx2].y,maxMesh.verts[idx2].z);
|
|
|
|
Point3D vert0 = objectOffset * v0;
|
|
Point3D vert1 = objectOffset * v1;
|
|
Point3D vert2 = objectOffset * v2;
|
|
|
|
// set texture vertex indices
|
|
Point2D tvert0(0,0);
|
|
Point2D tvert1(0,0);
|
|
Point2D tvert2(0,0);
|
|
if (tmap)
|
|
{
|
|
TVFace & maxTVFace = maxMesh.mapFaces(1)[i]; // 1 == default map
|
|
|
|
S32 tidx0 = maxTVFace.getTVert(0);
|
|
S32 tidx1 = maxTVFace.getTVert(2); // switch the order to be CW
|
|
S32 tidx2 = maxTVFace.getTVert(1); // switch the order to be CW
|
|
if (mirror)
|
|
{
|
|
S32 tmp = tidx1;
|
|
tidx1 = tidx2;
|
|
tidx2 = tmp;
|
|
}
|
|
|
|
if (lastMatIdx != maxFace.getMatID())
|
|
{
|
|
// get texture transform for this material
|
|
uvTransform.IdentityMatrix();
|
|
Mtl * mtl = mMaxNode->GetMtl();
|
|
if( mtl )
|
|
{
|
|
if( mtl->ClassID() == Class_ID(MULTI_CLASS_ID,0) )
|
|
{
|
|
MultiMtl * multiMtl = (MultiMtl*)mtl;
|
|
if (multiMtl->NumSubMtls()>0)
|
|
mtl = multiMtl->GetSubMtl( maxFace.getMatID() % multiMtl->NumSubMtls());
|
|
}
|
|
|
|
if( mtl->ClassID() == Class_ID(DMTL_CLASS_ID,0) )
|
|
{
|
|
StdMat * stdMat = (StdMat*)mtl;
|
|
if (stdMat->GetSubTexmap(ID_DI)!=NULL && stdMat->MapEnabled(ID_DI) && stdMat->GetSubTexmap(ID_DI)->ClassID() == Class_ID(BMTEX_CLASS_ID,0))
|
|
{
|
|
BitmapTex * diffuse = (BitmapTex*)stdMat->GetSubTexmap(ID_DI);
|
|
diffuse->GetUVGen()->GetUVTransform(uvTransform);
|
|
}
|
|
}
|
|
}
|
|
lastMatIdx = maxFace.getMatID();
|
|
}
|
|
|
|
Point2 tv0 = maxMesh.mapVerts(1)[tidx0] * uvTransform;
|
|
Point2 tv1 = maxMesh.mapVerts(1)[tidx1] * uvTransform;
|
|
Point2 tv2 = maxMesh.mapVerts(1)[tidx2] * uvTransform;
|
|
tvert0.x(tv0.x);
|
|
tvert0.y(1.0f-tv0.y);
|
|
tvert1.x(tv1.x);
|
|
tvert1.y(1.0f-tv1.y);
|
|
tvert2.x(tv2.x);
|
|
tvert2.y(1.0f-tv2.y);
|
|
}
|
|
tsFace.type |= maxFace.getMatID(); // return max mat id...gets converted to ts mat id by shapemimic
|
|
|
|
// now add indices...this is easy right now...later we'll mess this up
|
|
mIndices.push_back(addVertex(vert0,tvert0,idx0,maxFace.smGroup));
|
|
mIndices.push_back(addVertex(vert1,tvert1,idx1,maxFace.smGroup));
|
|
mIndices.push_back(addVertex(vert2,tvert2,idx2,maxFace.smGroup));
|
|
}
|
|
|
|
if (delTri)
|
|
delete tri;
|
|
|
|
return Parent::lockMesh(time,objectOffset);
|
|
}
|
|
|
|
void MaxAppMesh::unlockMesh()
|
|
{
|
|
Parent::unlockMesh();
|
|
|
|
// no more cleanup...but if there were some to do, we'd do it here
|
|
}
|
|
|
|
void MaxAppMesh::getSkinData()
|
|
{
|
|
if (mSkinDataFetched)
|
|
return;
|
|
mSkinDataFetched = true;
|
|
|
|
ISkin * skin;
|
|
ISkinContextData * skinData;
|
|
findSkinData(mMaxNode,&skin,&skinData);
|
|
if (!skin || !skinData || !skin->GetNumBones())
|
|
return;
|
|
|
|
// This is a hack to avoid making the skin helper a ws modifier
|
|
// A hack, but needed because ws modifiers screw things up (add
|
|
// offsets for some reason).
|
|
SkinHelper::smTheOnlyOne = mMaxNode;
|
|
|
|
// add skin helper modifier...
|
|
::Object * obj = mMaxNode->GetObjectRef();
|
|
IDerivedObject * dobj = (IDerivedObject*)CreateDerivedObject(mMaxNode->GetObjectRef());
|
|
SkinHelper * skinHelper = (SkinHelper*)CreateInstance(GetSkinHelperDesc()->SuperClassID(),GetSkinHelperDesc()->ClassID());
|
|
dobj->AddModifier(skinHelper);
|
|
mMaxNode->SetObjectRef(dobj);
|
|
|
|
// get bones
|
|
S32 i,j, numBones = skin->GetNumBones();
|
|
for (i=0; i<numBones; i++)
|
|
{
|
|
mBones.push_back(new MaxAppNode(skin->GetBone(i)));
|
|
AppConfig::PrintDump(PDPass2,avar("Adding skin object from skin \"%s\" to bone \"%s\" (%i).\r\n",mMaxNode->GetName(),mBones[i]->getName(),i));
|
|
}
|
|
|
|
bool delTri;
|
|
TriObject * tri = NULL;
|
|
|
|
// get skin mesh
|
|
tri = getTriObject(mMaxNode,0,delTri);
|
|
|
|
// get vertex weights from alternate tv channels
|
|
S32 numPoints = tri->mesh.getNumVerts();
|
|
if (tri->mesh.getNumMaps()<2+((1+numBones)>>1))
|
|
{
|
|
AppConfig::SetExportError("Assertion failed on skin object");
|
|
return;
|
|
}
|
|
mWeights.resize(numBones);
|
|
for (i=0; i<mWeights.size(); i++)
|
|
{
|
|
mWeights[i] = new std::vector<F32>;
|
|
mWeights[i]->resize(numPoints);
|
|
for (j=0; j<numPoints; j++)
|
|
(*mWeights[i])[j]=0.0f;
|
|
}
|
|
|
|
for (j=0; j<numBones; j++)
|
|
{
|
|
AppConfig::PrintDump(-1,avar("Adding weights for bone %i (\"%s\")\r\n",j,mBones[j]->getName()));
|
|
std::vector<bool> gotWeight;
|
|
gotWeight.resize(tri->mesh.numVerts);
|
|
for (i=0; i<gotWeight.size(); i++)
|
|
gotWeight[i]=false;
|
|
for (i=0; i<tri->mesh.numFaces; i++)
|
|
{
|
|
S32 ch = 2+(j>>1);
|
|
Face & face = tri->mesh.faces[i];
|
|
TVFace & tvFace = tri->mesh.mapFaces(ch)[i];
|
|
|
|
for (S32 count=0; count<3; count++)
|
|
{
|
|
S32 idx = face.v[count];
|
|
if (!gotWeight[idx])
|
|
{
|
|
UVVert tv = tri->mesh.mapVerts(ch)[tvFace.t[count]];
|
|
F32 w = (j&1) ? tv.y : tv.x;
|
|
(*mWeights[j])[idx] = w;
|
|
if (w>0.01f)
|
|
AppConfig::PrintDump(-1,avar(" Vertex %i, weight %f\r\n",idx,w));
|
|
gotWeight[idx]=true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delTri)
|
|
delete tri;
|
|
|
|
// done with helper...remove it now...
|
|
dobj->DeleteModifier(); // this'll be our skin helper
|
|
SkinHelper::smTheOnlyOne = NULL;
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
};
|